在Django Admin中将ManyToManyWidget添加到ManyToManyField的反向

时间:2012-03-26 20:53:49

标签: python django django-models django-admin

假设我在Django 1.4中有一个简单的博客应用程序:

class Post(models.Model):
    title = …
    published_on = …
    tags = models.ManyToManyField('Tag')

class Tag(models.Model):
    name = …

即。帖子有很多标签。在Django管理员上,如果我在<select multi> tags中包含fields,我会得到一个不错的小PostAdmin。是否有一种简单的方法可以在<select multi>中添加帖子列表(简单TagAdmin)?我尝试将fields = ['name', 'posts']放入TagAdmin并收到ImproperlyConfigured错误。 (post_set)的结果相同。

我对Django很好,所以可以提供一个合适的AdminForm和Admin对象,但我希望有一个Right Way™来做它。

6 个答案:

答案 0 :(得分:27)

这可以使用自定义表单。

from django.contrib import admin
from django import forms

from models import Post, Tag

class PostAdminForm(forms.ModelForm):
    tags = forms.ModelMultipleChoiceField(
        Tag.objects.all(),
        widget=admin.widgets.FilteredSelectMultiple('Tags', False),
        required=False,
    )

    def __init__(self, *args, **kwargs):
        super(PostAdminForm, self).__init__(*args, **kwargs)
        if self.instance.pk:
            self.initial['tags'] = self.instance.tags.values_list('pk', flat=True)

    def save(self, *args, **kwargs):
        instance = super(PostAdminForm, self).save(*args, **kwargs)
        if instance.pk:
            instance.tags.clear()
            instance.tags.add(*self.cleaned_data['tags'])
        return instance

class PostAdmin(admin.ModelAdmin):
    form = PostAdminForm

admin.site.register(Post, PostAdmin)

如果您想要垂直堆叠的小部件,那么False可以替换为True

答案 1 :(得分:12)

派对有点晚了,但这是适合我的解决方案(没有魔法):

# admin.py

from django.contrib import admin
from models import Post

class TagPostInline(admin.TabularInline):
    model = Post.tags.through
    extra = 1

class PostAdmin(admin.ModelAdmin):
    inlines = [TagPostInline]

admin.site.register(Post, PostAdmin)

参考:https://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-models

答案 2 :(得分:4)

Matthew的解决方案在创建新条目时对我(Django 1.7)没有用,所以我不得不改变一下。我希望它对某人有用:)

class PortfolioCategoriesForm(forms.ModelForm):
    items = forms.ModelMultipleChoiceField(
        PortfolioItem.objects.all(),
        widget=admin.widgets.FilteredSelectMultiple('Portfolio items', False),
        required=False
    )

    def __init__(self, *args, **kwargs):
        super(PortfolioCategoriesForm, self).__init__(*args, **kwargs)
        if self.instance.pk:
            initial_items = self.instance.items.values_list('pk', flat=True)
            self.initial['items'] = initial_items

    def save(self, *args, **kwargs):
        kwargs['commit'] = True
        return super(PortfolioCategoriesForm, self).save(*args, **kwargs)

    def save_m2m(self):
        self.instance.items.clear()
        self.instance.items.add(*self.cleaned_data['items'])

答案 3 :(得分:0)

修改模型以添加反向字段:

# models.py
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    published_on = models.DateTimeField()
    tags = models.ManyToManyField('Tag')

class Tag(models.Model):
    name = models.CharField(max_length=10)
    posts = models.ManyToManyField('blog.Post', through='blog.post_tags')

然后以标准方式向ModelAdmin添加字段:

#admin.py
from django.contrib import admin

class TagAdmin(admin.ModelAdmin):
    list_filter = ('posts', )

admin.site.register(Tag, TagAdmin)

答案 4 :(得分:0)

在那边查看我的答案,以双向显示ManyToMany管理小部件:

https://stackoverflow.com/a/54147741/3433137

答案 5 :(得分:0)

您可以通过这种方式添加对称的多对多过滤器。

贷方转到 https://gist.github.com/Grokzen/a64321dd69339c42a184

from django.db import models

class Pizza(models.Model):
  name = models.CharField(max_length=50)
  toppings = models.ManyToManyField(Topping, related_name='pizzas')

class Topping(models.Model):
  name = models.CharField(max_length=50)

### pizza/admin.py ###

from django import forms
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin.widgets import FilteredSelectMultiple

from .models import Pizza, Topping

class PizzaAdmin(admin.ModelAdmin):
  filter_horizonal = ('toppings',)

class ToppingAdminForm(forms.ModelForm):
  pizzas = forms.ModelMultipleChoiceField(
    queryset=Pizza.objects.all(), 
    required=False,
    widget=FilteredSelectMultiple(
      verbose_name=_('Pizzas'),
      is_stacked=False
    )
  )

  class Meta:
    model = Topping

  def __init__(self, *args, **kwargs):
    super(ToppingAdminForm, self).__init__(*args, **kwargs)

    if self.instance and self.instance.pk:
      self.fields['pizzas'].initial = self.instance.pizzas.all()

  def save(self, commit=True):
    topping = super(ToppingAdminForm, self).save(commit=False)

    if commit:
      topping.save()

    if topping.pk:
      topping.pizzas = self.cleaned_data['pizzas']
      self.save_m2m()

    return topping

class ToppingAdmin(admin.ModelAdmin):
  form = ToppingAdminForm

admin.site.register(Pizza, PizzaAdmin)
admin.site.register(Topping, ToppingAdmin)