【问题标题】:Filtering Django Admin by Null/Is Not Null按 Null/Is Not Null 过滤 Django Admin
【发布时间】:2011-12-03 06:17:27
【问题描述】:

我有一个简单的 Django 模型,例如:

class Person(models.Model):
    referrer = models.ForeignKey('self', null=True)
    ...

在这个模型的 ModelAdmin 中,我如何允许它通过referrer 是否为空来过滤?默认情况下,向 list_filter 添加推荐人会导致显示一个下拉列表,其中列出了 每个 人的记录,可能有数十万条记录,从而有效地阻止了页面加载。即使它加载了,我仍然无法按我想要的条件进行过滤。

即我将如何修改它以使下拉列表仅列出“All”、“Null”或“Not Null”选项?

我见过一些posts 声称使用自定义FilterSpec 子类来完成类似的事情,但没有一个解释如何使用它们。我见过的少数似乎适用于所有模型中的所有领域,这是我不想要的。此外,FilterSpec 的文档,这让我很紧张,因为我不想投资大量与某些可能在下一个版本中消失的临时内部类相关的自定义代码。 p>

【问题讨论】:

    标签: python django django-admin django-admin-filters


    【解决方案1】:

    对于3.1以下的django版本,复制下面EmptyFieldListFilter的代码

    from django.contrib.admin import FieldListFilter
    from django.contrib.admin.options import IncorrectLookupParameters
    from django.core.exceptions import ImproperlyConfigured
    from django.db import models
    from django.utils.translation import gettext_lazy as _
    
    
    class EmptyFieldListFilter(FieldListFilter):
        def __init__(self, field, request, params, model, model_admin, field_path):
            if not field.empty_strings_allowed and not field.null:
                raise ImproperlyConfigured(
                    "The list filter '%s' cannot be used with field '%s' which "
                    "doesn't allow empty strings and nulls." % (
                        self.__class__.__name__,
                        field.name,
                    )
                )
            self.lookup_kwarg = '%s__isempty' % field_path
            self.lookup_val = params.get(self.lookup_kwarg)
            super().__init__(field, request, params, model, model_admin, field_path)
    
        def queryset(self, request, queryset):
            if self.lookup_kwarg not in self.used_parameters:
                return queryset
            if self.lookup_val not in ('0', '1'):
                raise IncorrectLookupParameters
    
            lookup_condition = models.Q()
            if self.field.empty_strings_allowed:
                lookup_condition |= models.Q(**{self.field_path: ''})
            if self.field.null:
                lookup_condition |= models.Q(**{'%s__isnull' % self.field_path: True})
            if self.lookup_val == '1':
                return queryset.filter(lookup_condition)
            return queryset.exclude(lookup_condition)
    
        def expected_parameters(self):
            return [self.lookup_kwarg]
    
        def choices(self, changelist):
            for lookup, title in (
                (None, _('All')),
                ('1', _('Empty')),
                ('0', _('Not empty')),
            ):
                yield {
                    'selected': self.lookup_val == lookup,
                    'query_string': changelist.get_query_string({self.lookup_kwarg: lookup}),
                    'display': title,
                }
    

    您可以使用它来将管理员中的空字段值过滤器定义为

    import myfile
    
    class MyAdmin(admin.ModelAdmin):
        list_filter =  (
            ("model_field", myfile.EmptyFieldListFilter),
        )
    

    【讨论】:

      【解决方案2】:

      在 Django 3.1 之后你可以使用 EmptyFieldListFilter:

      class MyAdmin(admin.ModelAdmin):
          list_filter =  (
              ("model_field", admin.EmptyFieldListFilter),
          )
      

      【讨论】:

      • 作为替代方案,如果您使用的是较低版本的 Django,请创建一个文件(例如:admin_filter.py)并将 EmptyFieldListFilter 的代码添加到文件中并将其导入到管理文件中并使用类 MyAdmin (admin.ModelAdmin): list_filter = ( ("model_field", admin_filter.EmptyFieldListFilter), )
      【解决方案3】:

      有一个简单的方法:

      class RefererFilter(admin.SimpleListFilter):
          title = 'has referer'
          # Parameter for the filter that will be used in the URL query.
          parameter_name = 'referer__isnull'
      
          def lookups(self, request, model_admin):
              return (
                  ('False', 'has referer'),
                  ('True', 'has no referer'),
              )
      
          def queryset(self, request, queryset):
              if self.value() == 'False':
                  return queryset.filter(referer__isnull=False)
              if self.value() == 'True':
                  return queryset.filter(referer__isnull=True)
      

      然后在 ModelAdmin 中使用它们:

      class PersonAdmin(admin.ModelAdmin):
          list_filter =  (RefererFilter,) 
      

      【讨论】:

        【解决方案4】:

        由于 Django 1.4 对过滤器进行了一些更改,我想我可以节省一些时间,因为我只是花时间从 Cerin 接受的答案中修改代码以使用 Django 1.4 rc1。

        我有一个名为“started”的 TimeField(null=True) 模型,我想过滤空值和非空值,因此它与 OP 的问题几乎相同。
        所以,这对我有用......

        在 admin.py 中定义(实际上包含)这些:

        from django.contrib.admin.filters import SimpleListFilter
        
        class NullFilterSpec(SimpleListFilter):
            title = u''
        
            parameter_name = u''
        
            def lookups(self, request, model_admin):
                return (
                    ('1', _('Has value'), ),
                    ('0', _('None'), ),
                )
        
            def queryset(self, request, queryset):
                kwargs = {
                '%s'%self.parameter_name : None,
                }
                if self.value() == '0':
                    return queryset.filter(**kwargs)
                if self.value() == '1':
                    return queryset.exclude(**kwargs)
                return queryset
        
        
        
        class StartNullFilterSpec(NullFilterSpec):
            title = u'Started'
            parameter_name = u'started'
        

        不仅仅是在 ModelAdmin 中使用它们:

        class SomeModelAdmin(admin.ModelAdmin):
            list_filter =  (StartNullFilterSpec, )
        

        【讨论】:

        • 在 1.4 中,BooleanFieldListFilter 默认会执行此操作。 list_filter = (('myfield', BooleanFieldListFilter), 'other_field', 'other_field2')。在非布尔字段中,它达到与 null/not null 相同的效果。
        • @KyleMacFarlane 似乎不适用于 DateTime 字段
        • 使用 1.6 似乎也不适用于 ForeignKeys。
        【解决方案5】:

        我有一个更简单的 frnhr 答案版本,它实际上过滤了__isnull 条件。 (Django 1.4+):

        from django.contrib.admin import SimpleListFilter
        
        class NullListFilter(SimpleListFilter):
            def lookups(self, request, model_admin):
                return (
                    ('1', 'Null', ),
                    ('0', '!= Null', ),
                )
        
            def queryset(self, request, queryset):
                if self.value() in ('0', '1'):
                    kwargs = { '{0}__isnull'.format(self.parameter_name) : self.value() == '1' }
                    return queryset.filter(**kwargs)
                return queryset
        

        然后:

        class StartNullListFilter(NullListFilter):
            title = u'Started'
            parameter_name = u'started'
        

        最后:

        class SomeModelAdmin(admin.ModelAdmin):
            list_filter =  (StartNullListFilter, )
        

        我个人不喜欢把我的admin.py 扔掉几十个类,所以我想出了这样一个辅助函数:

        def null_filter(field, title_=None):
            class NullListFieldFilter(NullListFilter):
                parameter_name = field
                title = title_ or parameter_name
            return NullListFieldFilter
        

        我以后可以申请如下:

        class OtherModelAdmin(admin.ModelAdmin):
            list_filter =  (null_filter('somefield'), null_filter('ugly_field', _('Beautiful Name')), )
        

        【讨论】:

          【解决方案6】:

          我最终混合使用了the top solution herethis snippet

          但是,我不得不稍微调整 sn-p,删除字段类型限制并添加新的 field_path,最近在 1.3 中添加。

          from django.contrib.admin.filterspecs import FilterSpec
          from django.db import models
          from django.utils.safestring import mark_safe
          from django.utils.translation import ugettext as _
          
          class NullFilterSpec(FilterSpec):
              #fields = (models.CharField, models.IntegerField, models.FileField)
          
              @classmethod
              def test(cls, field):
                  #return field.null and isinstance(field, cls.fields) and not field._choices
                  return field.null and not field._choices
              #test = classmethod(test)
          
              def __init__(self, f, request, params, model, model_admin, field_path=None):
                  super(NullFilterSpec, self).__init__(f, request, params, model, model_admin, field_path)
                  self.lookup_kwarg = '%s__isnull' % f.name
                  self.lookup_val = request.GET.get(self.lookup_kwarg, None)
          
              def choices(self, cl):
                  # bool(v) must be False for IS NOT NULL and True for IS NULL, but can only be a string
                  for k, v in ((_('All'), None), (_('Has value'), ''), (_('Omitted'), '1')):
                      yield {
                          'selected' : self.lookup_val == v,
                          'query_string' : cl.get_query_string({self.lookup_kwarg : v}),
                          'display' : k
                      }
          
          # Here, we insert the new FilterSpec at the first position, to be sure
          # it gets picked up before any other
          FilterSpec.filter_specs.insert(0,
              # If the field has a `profilecountry_filter` attribute set to True
              # the this FilterSpec will be used
              (lambda f: getattr(f, 'isnull_filter', False), NullFilterSpec)
          )
          

          【讨论】:

            【解决方案7】:

            有一张票在 4 年内一直存在 (https://code.djangoproject.com/ticket/5833)。它错过了 1.3 的里程碑,但已经达到了新的功能状态,并且可能已经找到了进入主干的方式。如果您不介意用完后备箱,现在可以使用它。不过,该补丁据称与 1.3 兼容,因此您可能只需修补当前安装的补丁即可。

            【讨论】:

              【解决方案8】:

              有更好解释的 sn-p 可能是this。 Django 1.4 将附带simplified filter mechanism

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2022-11-21
                • 1970-01-01
                • 2014-12-18
                • 2013-02-04
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多