我可以在django admin中创建list_filter,只显示引用的ForeignKeys吗?

时间:2012-08-31 12:51:46

标签: django django-admin

我有一个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。)

7 个答案:

答案 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"),
    )