69

我有一个 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 管理员中的列表过滤器在过滤器面板中列出了所有国家/地区。在这种情况下,列出所有国家(而不仅仅是那些被实例引用的国家)几乎违背了使用列表过滤器的目的。

是否有一些仅MyModel在列表过滤器中显示作为选项引用的国家?(我使用 Django 1.3。)

4

7 回答 7

100

从 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 升级,则需要使用内部且未记录的FilterSpecapi。Django Admin 中的 Stack Overflow 问题Custom Filter应该为您指明正确的方向。

于 2012-08-31T13:19:46.517 回答
35

我知道问题是关于 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',) 您将拥有选择器中的所有国家/地区。然而,此片段仅过滤相关国家 - 与城市至少有一种关系的国家。

最初的想法来自这里。非常感谢作者。改进了类名,以便更清晰地使用 model_admin.model 而不是硬编码的模型名。

示例也可在 Django Snippets 中找到:http: //djangosnippets.org/snippets/2885/

于 2013-01-28T19:48:32.653 回答
25

自 Django 1.8 以来,有:admin.RelatedOnlyFieldListFilter

示例用法是:

class BookAdmin(admin.ModelAdmin):
    list_filter = (
        ('author', admin.RelatedOnlyFieldListFilter),
    )
于 2015-01-08T09:41:14.810 回答
5

我会像这样在 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]

这对数据库来说要好得多;)

于 2014-03-28T09:10:34.917 回答
2

伟大的@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"),
    )
于 2015-04-07T20:55:12.720 回答
2

这是我对 Django 1.4 的通用和可重用实现的看法,如果你碰巧卡在那个版本上。它的灵感来自现在是 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
于 2015-05-07T07:41:36.963 回答
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),
        # ...
    )
于 2015-03-24T13:57:45.860 回答