【问题标题】:Adding a ManyToManyWidget to the reverse of a ManyToManyField in the Django Admin在 Django Admin 中将 ManyToManyWidget 添加到 ManyToManyField 的反面
【发布时间】:2012-04-10 09:40:12
【问题描述】:

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

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

class Tag(models.Model):
    name = …

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

我对 Django 没问题,所以可以创建一个适当的 AdminForm 和 Admin 对象,但我希望有一个 Right Way™ 来做。

【问题讨论】:

标签: python django django-models django-admin


【解决方案1】:

这可以通过自定义表单来实现。

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

【讨论】:

  • 嗯...我错过了什么,或者这完全错过了 OP 的重点?重点是“TagAdmin 中的帖子列表”。 TagAdmin,而不是 PostAdmin。不过,这种方法看起来不错。
  • 也许交换 TagAdmin 和 PostAdmin。
  • 也许吧。此外,当添加新对象时,保存不会以这种方式工作,因为 Django 管理员使用form.save(commit=False) 调用它,因此没有 pk。相反,将该代码移至“TagAdmin.save_model(...)”。
  • 好电话。我可能应该实际测试该代码,而不是仅仅在浏览器中编写它;)
【解决方案2】:

聚会有点晚了,但这是对我有用的解决方案(没有魔法):

# 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

【讨论】:

  • 是的,但这并不能真正做到问题所要求的,这是一个 select[multiple] 小部件。
【解决方案3】:

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

# 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)

【讨论】:

  • 这会破坏迁移、syncdb 等。有一个 hacky 解决方法:djangosnippets.org/snippets/1295 但我还没有尝试使用新的 Django 迁移
  • 据我所知,使用 Django 1.11,这实际上工作正常。 makemigrations 为冗余 M2M 字段生成迁移,但它的应用没有问题。
  • 此解决方案的一个通常理想的结果是“添加另一个”(+) 按钮显示在反向字段旁边,无需任何其他工作(使用 ManyToManyRelRelatedFieldWidgetWrapper) .
  • 不应该在文档中还是有一些警告?
  • 我看到了高耦合。还有什么?
【解决方案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'])

【讨论】:

    【解决方案5】:

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

    功劳归于 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)
    

    【讨论】:

      猜你喜欢
      • 2013-02-05
      • 2011-06-14
      • 1970-01-01
      • 2014-02-21
      • 1970-01-01
      • 2013-03-21
      • 2019-10-12
      • 2011-03-28
      • 2018-10-13
      相关资源
      最近更新 更多