Django Admin:多对多列表框不显示通过参数

发布于 2024-09-01 13:10:43 字数 800 浏览 4 评论 0原文

我有以下模型:

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 中的管理站点。

TIA

I have the following models:

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)

In the admin site, when I go to add/change a forum, I don't see the messages listbox as you'd expect. However, it shows up if I remove the 'through' parameter in the ManyToManyField declaration. What's up with that? I've registered all three models (plus Tag) to the admin site in admin.py.

TIA

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

悲念泪 2024-09-08 13:10:43

文档说:

当您使用 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

Documentation says:

When you specify an intermediary model using the through argument to a
ManyToManyField, the admin will not display a widget by default.

But it's probably possible to display M2M fields in the admin change view even if the through attribute is defined.

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
四叶草在未来唯美盛开 2024-09-08 13:10:43

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

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 case commit 为 False。但是,调用此函数会导致错误,因为此函数也尝试使用 through 保存多对多。您可能需要手动保存所有其他多对多关系,或者捕获异常,或者其他。

I learned a lot from @Fedor's answer, but some comments and cleanup may be still beneficial.

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

There's one caveat: if you have another many-to-many relationship in the models (that is without through), super(ForumAdminForm, self).save(self, commit) will set self.save_m2m in case commit is False. However, calling this would cause an error, because this function also tries to save the many-to-many with through as well. You may need to save all other many-to-many relationship manually, or catch the exception, or else.

吝吻 2024-09-08 13:10:43

Django admin 很好地支持使用 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)

Now in 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,)

官方文档中的更多信息:

模型,
管理员

Django admin nicely support many-to-many intermediary models that using the through argument .

For example you have these Person and Group models with intermediate Membership model:

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)

Now in admin.py file ,
Define an inline class for the intermediate Membership model:

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

And use them in admin views of models:

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

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

More info in official docs:

Models,
Admin

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文