【问题标题】:Can I make list_filter in django admin to only show referenced ForeignKeys?我可以在 django admin 中制作 list_filter 以仅显示引用的 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。)

【问题讨论】:

    标签: django django-admin


    【解决方案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(=self.value())中放了什么。
    • 我的问题的答案在这里:stackoverflow.com/questions/19187027/…
    • 如果您需要这个出色答案的通用可重复使用版本,请在下面查看我的答案:stackoverflow.com/a/29501136/304209
    • 在您的查找方法中,您正在遍历每个城市,这是非常低效的。而不是将查询集转换为集合,您应该只返回如下内容: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
        

        【讨论】:

          猜你喜欢
          • 2016-06-02
          • 2020-03-18
          • 2013-05-15
          • 2012-10-18
          • 2012-03-30
          • 2012-04-06
          • 1970-01-01
          • 2017-03-28
          • 2020-09-20
          相关资源
          最近更新 更多