过滤Django管理员Null /不是空

时间:2011-10-07 19:18:05

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

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

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

在这个模型的ModelAdmin中,我如何通过referrer是否为null来过滤它?默认情况下,将referrer添加到list_filter会导致显示一个下拉列表,其中列出每个人员记录(可能数十万),从而有效阻止页面加载。即使加载,我仍然无法按照我想要的标准进行过滤。

即。我如何修改此选项,以便下拉列表仅列出“全部”,“空”或“非空”选项?

我见过一些声称使用自定义FilterSpec子类完成类似操作的posts,但没有一个解释如何使用它们。我见过的少数似乎适用于所有型号的所有领域,我不想要。此外,还有针对FilterSpec的文档,这让我很紧张,因为我不想投入大量与下一版本可能会消失的瞬态内部类相关的自定义代码。

8 个答案:

答案 0 :(得分:13)

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

我有一个名为“started”的TimeField(null = True)模型,我想过滤掉null和非null值,因此它与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 :(得分:7)

我有一个简单版本的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')), )

答案 2 :(得分:4)

在Django 3.1之后,您可以使用 EmptyFieldListFilter

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

答案 3 :(得分:2)

具有更好解释的摘录可能是this。 Django 1.4将附带simplified filter mechanism

答案 4 :(得分:2)

有一种简单的方法:

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,) 

答案 5 :(得分:1)

已经有一张票在这里徘徊4年(https://code.djangoproject.com/ticket/5833)。它错过了1.3里程碑,但已达到新的功能状态,并且可能已经发现它已经进入后备箱。如果你不介意跑掉行李箱,你现在可以使用它。但是,补丁应该是1.3兼容的,所以你可以通过修补当前的安装来完成。

答案 6 :(得分:1)

我最终使用the top solution herethis snippet的混合物。

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

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 :(得分:0)

对于低于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,
            }

您可以使用它在 admin 中将空字段值过滤器定义为

import myfile

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