我有一个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。)
答案 0 :(得分:70)
自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应该指向正确的方向。
答案 1 :(得分:31)
我知道有关Django 1.3的问题,但是你提到很快升级到1.4。 同样对于那些正在寻找1.4解决方案的人,但是发现这个条目的人,我决定展示使用SimpleListFilter(可用的Django 1.4)来显示仅引用(相关的,使用过的)外键值的完整示例< / p>
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'),您将拥有选择器中的所有国家/地区。但是,此代码段仅过滤相关国家/地区 - 与城市至少有一种关系的国家/地区。
来自here的原创想法。非常感谢作者。改进了类名,以便更清晰地使用model_admin.model而不是硬编码的模型名称。
Django Snippets中也提供了示例: http://djangosnippets.org/snippets/2885/
答案 2 :(得分:20)
自Django 1.8以来,有:admin.RelatedOnlyFieldListFilter
示例用法是:
class BookAdmin(admin.ModelAdmin):
list_filter = (
('author', admin.RelatedOnlyFieldListFilter),
)
答案 3 :(得分:5)
我会在黑暗的代码中更改查找:
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 :(得分:2)
这是我对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
答案 5 :(得分:1)
@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),
# ...
)
答案 6 :(得分:1)
伟大的@ 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"),
)