我可以在 django admin 中制作 list_filter 以仅显示引用的 ForeignKeys 吗?
Posted
技术标签:
【中文标题】我可以在 django admin 中制作 list_filter 以仅显示引用的 ForeignKeys 吗?【英文标题】:Can I make list_filter in django admin to only show referenced ForeignKeys? 【发布时间】:2012-08-26 07:05:38 【问题描述】:我有一个 django 应用程序,它有两个这样的模型:
class MyModel(models.Model):
name = models.CharField()
country = models.ForeignKey('Country')
class Country(models.Model):
code2 = models.CharField(max_length=2, primary_key=True)
name = models.CharField()
MyModel
的管理类如下所示:
class MyModelAdmin(admin.ModelAdmin):
list_display = ('name', 'country',)
list_filter = ('country',)
admin.site.register(models.MyModel, MyModelAdmin)
Country
表包含约 250 个国家/地区。一些MyModel
实例实际上引用了少数几个国家/地区。
问题在于 django admin 中的列表过滤器在过滤器面板中列出了所有国家/地区。在这种情况下,列出所有国家(而不仅仅是实例引用的国家)几乎违背了使用列表过滤器的目的。
是否有一些仅将MyModel
引用的国家/地区显示为列表过滤器中的选项? (我使用 Django 1.3。)
【问题讨论】:
【参考方案1】:从 Django 1.8 开始,有一个内置的RelatedOnlyFieldListFilter
,您可以使用它来显示相关国家/地区。
class MyModelAdmin(admin.ModelAdmin):
list_display = ('name', 'country',)
list_filter = (
('country', admin.RelatedOnlyFieldListFilter),
)
对于 Django 1.4-1.7,list_filter
允许您使用 SimpleListFilter
的子类。应该可以创建一个简单的列表过滤器来列出您想要的值。
如果您无法从 Django 1.3 升级,则需要使用内部的、未记录的 FilterSpec
api。 Stack Overflow 问题Custom Filter in Django Admin 应该为您指明正确的方向。
【讨论】:
感谢您的回复。计划在不久的将来迁移到 Django 1.4,因此我将推迟对该问题的任何修复。 @andi 谢谢,我已经用新信息更新了答案【参考方案2】:我知道问题是关于 Django 1.3 的,但是你提到很快升级到 1.4。 对于像我这样正在寻找 1.4 解决方案但发现此条目的人来说,我决定展示使用 SimpleListFilter(可用 Django 1.4)的完整示例,以仅显示引用的(相关的、使用的)外键值
from django.contrib.admin import SimpleListFilter
# admin.py
class CountryFilter(SimpleListFilter):
title = 'country' # or use _('country') for translated title
parameter_name = 'country'
def lookups(self, request, model_admin):
countries = set([c.country for c in model_admin.model.objects.all()])
return [(c.id, c.name) for c in countries]
# You can also use hardcoded model name like "Country" instead of
# "model_admin.model" if this is not direct foreign key filter
def queryset(self, request, queryset):
if self.value():
return queryset.filter(country__id__exact=self.value())
else:
return queryset
# Example setup and usage
# models.py
from django.db import models
class Country(models.Model):
name = models.CharField(max_length=64)
class City(models.Model):
name = models.CharField(max_length=64)
country = models.ForeignKey(Country)
# admin.py
from django.contrib.admin import ModelAdmin
class CityAdmin(ModelAdmin):
list_filter = (CountryFilter,)
admin.site.register(City, CityAdmin)
在示例中,您可以看到两个模型 - 城市和国家。城市有国家的外键。如果您使用常规 list_filter = ('country',) 您将拥有选择器中的所有国家/地区。然而,这个 sn-p 只过滤相关国家 - 与城市至少有一种关系的国家。
来自here的原创想法。非常感谢作者。改进了类名,以便更清晰地使用 model_admin.model 而不是硬编码的模型名。
在 Django Snippets 中也有示例: http://djangosnippets.org/snippets/2885/
【讨论】:
这个例子与我正在寻找的非常接近,但有一个例外:我试图通过 UserProfile 模型上名为 user_type 的字段为 User 对象添加一个 list_filter。所以我定义了一个类,UserTypeFilter(SimpleListFilter): 但我不知道你在queryset函数的return queryset.filter(model_admin.model.objects.order_by('country__id').values_list('country__id', 'country__name').distinct()
【参考方案3】:
我会像这样在 darklow 的代码中更改查找:
def lookups(self, request, model_admin):
users = User.objects.filter(id__in = model_admin.model.objects.all().values_list('user_id', flat = True).distinct())
return [(user.id, unicode(user)) for user in users]
这对数据库来说要好得多;)
【讨论】:
【参考方案4】:从 Django 1.8 开始有:admin.RelatedOnlyFieldListFilter
示例用法为:
class BookAdmin(admin.ModelAdmin):
list_filter = (
('author', admin.RelatedOnlyFieldListFilter),
)
【讨论】:
1.8 已经到来,这个功能现在可用 @Rrrrrrrrrk 更新了我的答案,明确了当前状态;) 由于某种原因,这似乎不在 geodjango 管理员中,并且从常规管理员中提取它似乎不起作用【参考方案5】:@andi,感谢您告知 Django 1.8 将具有此功能的事实。
我查看了它是如何实现的,并基于适用于 Django 1.7 的创建版本。这是比我之前的答案更好的实现,因为现在您可以将此过滤器与任何外键字段重用。仅在 Django 1.7 中测试,不确定它是否适用于早期版本。
这是我的最终解决方案:
from django.contrib.admin import RelatedFieldListFilter
class RelatedOnlyFieldListFilter(RelatedFieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
super(RelatedOnlyFieldListFilter, self).__init__(
field, request, params, model, model_admin, field_path)
qs = field.related_field.model.objects.filter(
id__in=model_admin.get_queryset(request).values_list(
field.name, flat=True).distinct())
self.lookup_choices = [(each.id, unicode(each)) for each in qs]
用法:
class MyAdmin(admin.ModelAdmin):
list_filter = (
('user', RelatedOnlyFieldListFilter),
('category', RelatedOnlyFieldListFilter),
# ...
)
【讨论】:
不幸的是,在 Django 1.4 中不起作用 :('ForeignKey' object has no attribute 'related_field'
在 Django 1.6 上运行良好:D【参考方案6】:
伟大的@darklow 答案的通用可重复使用版本:
def make_RelatedOnlyFieldListFilter(attr_name, filter_title):
class RelatedOnlyFieldListFilter(admin.SimpleListFilter):
"""Filter that shows only referenced options, i.e. options having at least a single object."""
title = filter_title
parameter_name = attr_name
def lookups(self, request, model_admin):
related_objects = set([getattr(obj, attr_name) for obj in model_admin.model.objects.all()])
return [(related_obj.id, unicode(related_obj)) for related_obj in related_objects]
def queryset(self, request, queryset):
if self.value():
return queryset.filter(**'%s__id__exact' % attr_name: self.value())
else:
return queryset
return RelatedOnlyFieldListFilter
用法:
class CityAdmin(ModelAdmin):
list_filter = (
make_RelatedOnlyFieldListFilter("country", "Country with cities"),
)
【讨论】:
【参考方案7】:这是我对 Django 1.4 的通用和可重用实现的看法,如果你碰巧卡在那个版本。它的灵感来自built-in version that is now part of Django 1.8 及更高版本。此外,将其适应 1.5-1.7 应该是一项相当小的任务,主要是查询集方法在其中更改了名称。我已将过滤器本身放在我拥有的core
应用程序中,但您显然可以将它放在任何地方。
实施:
# myproject/core/admin/filters.py:
from django.contrib.admin.filters import RelatedFieldListFilter
class RelatedOnlyFieldListFilter(RelatedFieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
self.request = request
self.model_admin = model_admin
super(RelatedOnlyFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path)
def choices(self, cl):
limit_choices_to = set(self.model_admin.queryset(self.request).values_list(self.field.name, flat=True))
self.lookup_choices = [(pk_val, val) for pk_val, val in self.lookup_choices if pk_val in limit_choices_to]
return super(RelatedOnlyFieldListFilter, self).choices(cl)
用法:
# myapp/admin.py:
from django.contrib import admin
from myproject.core.admin.filters import RelatedOnlyFieldListFilter
from myproject.myapp.models import MyClass
class MyClassAdmin(admin.ModelAdmin):
list_filter = (
('myfield', RelatedOnlyFieldListFilter),
)
admin.site.register(MyClass, MyClassAdmin)
如果您稍后更新到 Django 1.8,您应该可以只更改此导入:
from myproject.core.admin.filters import RelatedOnlyFieldListFilter
到这里:
from django.contrib.admin.filters import RelatedOnlyFieldListFilter
【讨论】:
以上是关于我可以在 django admin 中制作 list_filter 以仅显示引用的 ForeignKeys 吗?的主要内容,如果未能解决你的问题,请参考以下文章