在 Django Admin 中过滤多对多框

Posted

技术标签:

【中文标题】在 Django Admin 中过滤多对多框【英文标题】:Filter ManyToMany box in Django Admin 【发布时间】:2010-11-16 15:21:24 【问题描述】:

我有一个与另一个对象具有多对多关系的对象。 在 Django Admin 中,这会导致多选框中的列表很长。

我想过滤 ManyToMany 关系,因此我只获取客户选择的城市中可用的类别。

这可能吗?我必须为它创建一个小部件吗?如果是这样,我如何将标准 ManyToMany 字段中的行为复制到它,因为我也想要 filter_horizontal 函数。

这些是我的简化模型:

class City(models.Model):
    name = models.CharField(max_length=200)


class Category(models.Model):
    name = models.CharField(max_length=200)
    available_in = models.ManyToManyField(City)
    

class Customer(models.Model):
    name = models.CharField(max_length=200)
    city = models.ForeignKey(City)
    categories = models.ManyToManyField(Category)

【问题讨论】:

【参考方案1】:

好的,这是我使用上述类的解决方案。 我添加了更多过滤器来正确过滤它,但我想让代码在这里可读。

这正是我一直在寻找的,我在这里找到了我的解决方案:http://www.slideshare.net/lincolnloop/customizing-the-django-admin#stats-bottom(幻灯片 50)

将以下内容添加到我的 admin.py:

class CustomerForm(forms.ModelForm): 
    def __init__(self, *args, **kwargs):
        super(CustomerForm, self).__init__(*args, **kwargs)
        wtf = Category.objects.filter(pk=self.instance.cat_id);
        w = self.fields['categories'].widget
        choices = []
        for choice in wtf:
            choices.append((choice.id, choice.name))
        w.choices = choices


class CustomerAdmin(admin.ModelAdmin):
    list_per_page = 100
    ordering = ['submit_date',] # didnt have this one in the example, sorry
    search_fields = ['name', 'city',]
    filter_horizontal = ('categories',)
    form = CustomerForm

这会过滤“类别”列表而不删除任何功能! (即:我仍然可以拥有我心爱的 filter_horizo​​ntal :))

ModelForms 非常强大,我有点惊讶文档/书中没有详细介绍它。

【讨论】:

我注意到,在将此代码添加到项目中后,即使从“可用类别的字段。我在实现这一点时是否遗漏了什么? 使用列表推导进一步简化:self.fields['categories'].widget.choices = [(choice.id,choice.name) for selection in wtf] 如何将归档的类别设为只读。我正在尝试 read only_fields = ('users',) 。但它以逗号分隔的单行显示。我想在换行符中显示... 请注意,您不必自己构建widget.choices。设置field.queryset就足够了:self.fields['categories'].queryset = Category.objects.filter(pk=self.instance.cat_id) @FraserHarris 你不是我们应得的英雄,但你是我们需要的英雄:D【参考方案2】:

据我所知,您基本上是想根据某些标准(根据城市的类别)过滤显示的选择。

您可以通过使用models.ManyToManyFieldlimit_choices_to 属性来做到这一点。因此,将您的模型定义更改为...

class Customer(models.Model):
    name = models.CharField(max_length=200)
    city = models.ForeignKey(City)
    categories = models.ManyToManyField(Category, limit_choices_to = 'available_in': cityId)

这应该可以工作,因为limit_choices_to 可用于此目的。

但有一点需要注意,limit_choices_to 在带有自定义中间表的 ManyToManyField 上使用时无效。希望这会有所帮助。

【讨论】:

这看起来可行!但是...这让我意识到我必须重新建模我的模型:) 我在文档中读到管理员也不关心 limit_choices_to,你对此有何看法? 我正在尝试按照您描述@sim 的方式做同样的事情,但我收到ValueError at /admin/foo/bar/: invalid literal for int() with base 10: 'city' 的错误。如何实现这种过滤方法有什么我遗漏的吗? @nhinkle 值中的“城市”应该表示您希望类别限制到的城市对象的 ID。我很抱歉。我将编辑答案以使其更清楚。【参考方案3】:

另一种方法是在 Django Admin 中使用 formfield_for_manytomany

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_manytomany(self, db_field, request, **kwargs):
        if db_field.name == "cars":
            kwargs["queryset"] = Car.objects.filter(owner=request.user)
        return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)

考虑到“汽车”是多对多字段。

查看this link了解更多信息。

【讨论】:

【参考方案4】:

我想这就是你要找的东西:

http://blog.philippmetzler.com/?p=52

我们使用 django-smart-selects:

http://github.com/digi604/django-smart-selects

菲利普

【讨论】:

你能用例子扩展你的答案吗?这实际上是一个仅 URL 的答案。为什么那个博客是他们所追求的?为什么要使用 Django-Smart-Selects?【参考方案5】:

由于您在同一表单中选择客户的城市和类别,因此您需要一些 javascript 来动态地将类别选择器缩减为所选城市中可用的类别。

【讨论】:

我并不热衷于使用 javascript 迭代数以万计的 DOM 元素并与另一个庞大的列表进行比较。我会说 Javascript 绝对不是要走的路,这必须在从数据库中选择类别时在后端完成。【参考方案6】:

就像 Ryan 所说,必须有一些 javascript 来根据用户的选择动态更改选项。如果保存了城市并重新加载了管理表单,则发布的解决方案有效,这就是过滤器工作的时候,但考虑一下用户想要编辑对象然后更改城市下拉列表但类别中的选项不会刷新的情况。

【讨论】:

【参考方案7】:
Category.objects.filter(available_in=cityobject)

应该这样做。视图应该包含用户选择的城市,无论是在请求中还是作为该视图函数的参数。

【讨论】:

但是我在谈论 django 管理员,你是说我应该复制标准视图并添加以上内容吗? 啊,我完全错过了您问题标题的整个“Django Admin”部分。我仍然认为这是正确的方法,虽然我不确定你会把它放在哪里,或者这是否可能。

以上是关于在 Django Admin 中过滤多对多框的主要内容,如果未能解决你的问题,请参考以下文章

Django Admin批量编辑多对多关系

如何在 django admin 中自定义多对多内联模型

Django如何过滤多对多字段中的对象,而不是原始查询集

用于递归多对多的 Django Admin 内联

Django 多对多字段过滤器列表

在 django admin 中使用 related_name 配置多对多字段