Фильтрация Django Admin по Null / Is Not Null - PullRequest
10 голосов
/ 07 октября 2011

У меня есть простая модель Django, например:

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

В ModelAdmin этой модели, как я могу разрешить его фильтрацию по тому, является ли реферер нулевым?По умолчанию добавление реферера в list_filter приводит к отображению раскрывающегося списка, в котором перечислены каждая запись о человеке, которая может составлять сотни тысяч, что эффективно предотвращает загрузку страницы.Даже если он загружается, я все равно не могу фильтровать по критериям, которые я хочу.

т.е. как бы я изменил это так, чтобы в раскрывающемся списке отображались только варианты «Все», «Нуль» или «Не ноль»?

Я видел сообщений , в которых утверждается, что они выполняют нечто подобное с помощью пользовательских подклассов FilterSpec, но ни один из них не объясняет, как их использовать.Те немногие, которые я видел, применимы ко всем полям во всех моделях, чего я бы не хотел.Более того, есть ноль документация для FilterSpec, что меня нервирует, потому что я не хочу вкладывать деньги в большой пользовательский код, связанный с каким-то временным внутренним классом, который может исчезнуть в следующем выпуске.

Ответы [ 6 ]

13 голосов
/ 07 марта 2012

Поскольку Django 1.4 вносит некоторые изменения в фильтры, я решил сэкономить время, потраченное на изменение кода из принятого Cerin ответа на работу с Django 1.4 rc1.

У меня есть модель с TimeField (null = True) с именем "start", и я хотел отфильтровать нулевые и ненулевые значения, так что это почти та же проблема, что и 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, )
7 голосов
/ 09 октября 2014

У меня есть более простая версия ответа frnhr, которая на самом деле фильтрует условие __isnull. (Джанго 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 голосов
/ 04 апреля 2017

есть простой способ:

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,) 
2 голосов
/ 07 октября 2011

Фрагмент с лучшим объяснением может быть this . Django 1.4 будет поставляться с упрощенным механизмом фильтрации .

1 голос
/ 07 октября 2011

В итоге я использовал смесь верхнего решения здесь вместе с этого фрагмента .

Однако мне пришлось немного подправить фрагмент, убрав ограничение типа поля и добавив новый 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)
)
1 голос
/ 07 октября 2011

Билет в течение 4 лет пролетал (https://code.djangoproject.com/ticket/5833).). Он пропустил этап в 1,3, но достиг нового состояния и, по-видимому, нашел путь в багажник. Если вы не против убежать из багажника , вы можете использовать его сейчас. Тем не менее, патч предположительно совместим с 1.3, так что вы, вероятно, можете обойтись, просто установив патч на вашу текущую установку.

...