【问题标题】:Django Admin: Many-to-Many listbox doesn't show up with a through parameterDjango Admin:多对多列表框不显示通过参数
【发布时间】:2011-02-20 17:06:33
【问题描述】:

我有以下型号:

class Message(models.Model):
    date = models.DateTimeField()
    user = models.ForeignKey(User)    
    thread = models.ForeignKey('self', blank=True, null=True)
    ...

class Forum(models.Model):
    name = models.CharField(max_length=24)
    messages = models.ManyToManyField(Message, through="Message_forum", blank=True, null=True)
    ...

class Message_forum(models.Model):
    message = models.ForeignKey(Message)
    forum = models.ForeignKey(Forum)
    status = models.IntegerField()
    position = models.IntegerField(blank=True, null=True)
    tags = models.ManyToManyField(Tag, blank=True, null=True)

在管理站点中,当我去添加/更改论坛时,我没有看到您所期望的消息列表框。但是,如果我删除 ManyToManyField 声明中的 'through' 参数,它就会显示出来。那是怎么回事?我已在 admin.py 中将所有三个模型(加上 Tag)注册到管理站点。

TIA

【问题讨论】:

    标签: django-admin many-to-many manytomanyfield


    【解决方案1】:

    文档说:

    当您使用 through 参数指定中间模型时 ManyToManyField,管理员默认不会显示小部件。

    但即使定义了through 属性,也可能在管理员更改视图中显示 M2M 字段。

    class ForumAdminForm(forms.ModelForm):
        mm = forms.ModelMultipleChoiceField(
            queryset=models.Message.objects.all(),
            widget=FilteredSelectMultiple(_('ss'), False, attrs={'rows':'10'}))
    
        def __init__(self, *args, **kwargs):
            if 'instance' in kwargs:
                initial = kwargs.setdefault('initial', {})
                initial['mm'] = [t.service.pk for t in kwargs['instance'].message_forum_set.all()]
    
            forms.ModelForm.__init__(self, *args, **kwargs)
    
        def save(self, commit=True):
            instance = forms.ModelForm.save(self, commit)
    
            old_save_m2m = self.save_m2m
            def save_m2m():
                old_save_m2m()
    
                messages = [s for s in self.cleaned_data['ss']]
                for mf in instance.message_forum_set.all():
                    if mf.service not in messages:
                        mf.delete()
                    else:
                        messages.remove(mf.service)
    
                for message in messages:
                    Message_forum.objects.create(message=message, forum=instance)
    
            self.save_m2m = save_m2m
    
            return instance
    
        class Meta:
            model = models.Forum
    
    class ForumAdmin(admin.ModelAdmin):
        form = ForumAdminForm
    

    【讨论】:

    • 完美运行,但代码service中有无效引用。
    【解决方案2】:

    【讨论】:

    • 您的链接已损坏,因为他们的网站上不再提供 1.6 的文档。
    • django 2.0 的更新链接是here
    • 这没什么用。如何在不使用内联的情况下显示它?也许我遗漏了一些东西,但文档似乎没有说。
    【解决方案3】:

    我从@Fedor 的回答中学到了很多,但一些 cmets 和清理可能仍然有益。

    class ForumAdminForm(forms.ModelForm):
        messages = forms.ModelMultipleChoiceField(
                       queryset=Message.objects.all(),
                       widget=FilteredSelectMultiple('Message', False))
    
    
        # Technically, you don't need to manually set initial here for ForumAdminForm
        # However, you NEED to do the following for MessageAdminForm
        def __init__(self, *args, **kwargs):
            if 'instance' in kwargs:
                # a record is being changed. building initial
                initial = kwargs.setdefault('initial', {})
                initial['messages'] = [t.message.pk for t in kwargs['instance'].message_forum_set.all()]
            super(ForumAdminForm, self).__init__(*args, **kwargs)
    
        def save(self, commit=True):
            if not self.is_valid():
                raise HttpResponseForbidden
            instance = super(ForumAdminForm, self).save(self, commit)
            def save_m2m_with_through():
                messages = [t for t in self.cleaned_data['messages']
                old_memberships = instance.message_forum_set.all()
                for old in old_memberships:
                    if old.message not in messages:
                        # and old membership is cleaned by the user
                        old.delete()
                for message in [x for x in messages not in map(lambda x: x.message, old_memberships)]:                   
                    membership = Member_forum(message=messsage, forum=instance) 
                    # You may have to initialize status, position and tag for your need
                    membership.save()
            if commit:
                save_m2m_with_through()
            else:
                self.save_m2m = save_m2m_with_through
            return instance
    
        class Meta:
            model = Forum
            fields = {'name', 'messages')
    

    有一个警告:如果模型中有另一个多对多关系(即没有通过),super(ForumAdminForm, self).save(self, commit) 将设置 self.save_m2m 以防commit 为 False。但是,调用它会导致错误,因为此函数也尝试使用 through 保存多对多。您可能需要手动保存所有其他多对多关系,或者捕获异常,否则。

    【讨论】:

      【解决方案4】:

      Django 管理员很好地支持使用 through 参数的多对多中间模型。

      例如,您有这些 PersonGroup 模型以及中间 Membership 模型:

      models.py

      from django.db import models
      
      class Person(models.Model):
          name = models.CharField(max_length=128)
      
      class Group(models.Model):
          name = models.CharField(max_length=128)
          members = models.ManyToManyField(Person, through='Membership')
      
      class Membership(models.Model):
          person = models.ForeignKey(Person, on_delete=models.CASCADE)
          group = models.ForeignKey(Group, on_delete=models.CASCADE)
          date_joined = models.DateField()
          invite_reason = models.CharField(max_length=64)
      

      现在在admin.py 文件中, 为中间 Membership 模型定义一个内联类:

      @admin.register(Membership)
      class MembershipInline(admin.TabularInline):
          model = Membership
          extra = 1
      

      并在模型的管理视图中使用它们:

      @admin.register(Person)
      class PersonAdmin(admin.ModelAdmin):
          inlines = (MembershipInline,)
      
      @admin.register(Group)
      class GroupAdmin(admin.ModelAdmin):
          inlines = (MembershipInline,)
      

      官方文档中的更多信息:

      Models, Admin

      【讨论】:

        猜你喜欢
        • 2010-11-16
        • 2018-02-09
        • 2013-08-09
        • 2021-06-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-04
        相关资源
        最近更新 更多