我可以使djangopipe理中的list_filter只显示引用的ForeignKeys?
我有一个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
的admin类看起来像这样:
class MyModelAdmin(admin.ModelAdmin): list_display = ('name', 'country',) list_filter = ('country',) admin.site.register(models.MyModel, MyModelAdmin)
Country
表包含大约250个国家。 只有less数国家实际上被一些MyModel
实例引用。
问题是,在Djangopipe理中的列表filter列出filter面板中的所有国家 。 列出所有国家(而不仅仅是那些被实例引用的国家)在这种情况下几乎无法实现列表filter的目的。
有没有一些只显示MyModel
引用的国家作为列表筛选器中的select? (我使用Django 1.3。)
从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
的子类。 应该可以创build一个简单的列表筛选器,列出所需的值。
如果你不能从Django 1.3升级,你需要使用内部的,没有logging的FilterSpec
api。 Django Admin中的堆栈溢出问题自定义filter应该指向正确的方向。
我知道问题是关于Django 1.3,但是你很快就会提到升级到1.4。 对于像我这样寻找1.4解决scheme的人来说,我也决定展示使用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'),你将拥有select器中的所有国家。 但是,这段代码只能过滤相关的国家 – 至less与城市有一个关系的国家。
原来的想法从这里 。 非常感谢作者。 改进了类名,以便更好地理解和使用model_admin.model,而不是硬编码的模型名称。
Django片段中也有示例: http : //djangosnippets.org/snippets/2885/
由于Django 1.8有: admin.RelatedOnlyFieldListFilter
示例用法是:
class BookAdmin(admin.ModelAdmin): list_filter = ( ('author', admin.RelatedOnlyFieldListFilter), )
我会改变在这个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]
这对数据库来说好多了)
@andi,感谢让我们知道Django 1.8将会拥有这个特性。
我看看它是如何实现的,并基于Django 1.7创build的版本。 这是比我以前的答案更好的实现,因为现在您可以重用此filter与任何外键字段。 只testing在Django 1.7中,不知道它是否在早期版本。
这是我的最终解决scheme:
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), # ... )
一个伟大的@ darklow的答案的广义可重用版本:
def make_RelatedOnlyFieldListFilter(attr_name, filter_title): class RelatedOnlyFieldListFilter(admin.SimpleListFilter): """Filter that shows only referenced options, ie 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"), )
这是我对Django 1.4的一个通用和可重用的实现,如果你恰好被困在那个版本。 它的灵感来自现在是Django 1.8及以上版本的内置版本 。 另外,将它调整到1.5-1.7应该是一个相当小的任务,主要是queryset方法已经改变了名字。 我已经把filter本身放在了我有的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