Объедините несколько фильтров в один фильтр () с django-фильтрами - PullRequest
2 голосов
/ 12 ноября 2011

Я использую приложение django-filter . Однако есть одна проблема, которую я не знаю, как решить. Это почти то же самое, что описано в документации django:

https://docs.djangoproject.com/en/1.2/topics/db/queries/#spanning-multi-valued-relationships

Я хочу сделать запрос, в котором я выбираю все блоги, в которых есть запись с и"Lennon" в заголовке и были опубликованы в 2008 году, например:

Blog.objects.filter(entry__headline__contains='Lennon', 
    entry__pub_date__year=2008)

Не выбирать блоги, в которых есть запись с заголовком "Lennon" и другая запись (возможно, такая же), которая была опубликована в 2008 году:

Blog.objects.filter(entry__headline__contains='Lennon').filter(
    entry__pub_date__year=2008)

Однако, если я настрою Filter так, чтобы было два поля (nevermind __contains x __exact, просто пример):

class BlogFilter(django_filters.FilterSet):
    entry__headline = django_filters.CharFilter()
    entry__pub_date = django_filters.CharFilter()

    class Meta:
        model = Blog
        fields = ['entry__headline', 'entry__pub_date', ]

Джанго-фильтр генерирует последнее:

Blog.objects.filter(entry__headline__exact='Lennon').filter(
    entry__pub_date__exact=2008)

Есть ли способ объединить оба фильтра в одно поле фильтра?

1 Ответ

1 голос
/ 06 сентября 2012

Ну, я пришел с решением. Это невозможно сделать с помощью обычных django-фильтров, поэтому я немного его расширил. Можно было бы улучшить это быстрое и грязное решение.

1-е добавлено пользовательское «сгруппированное» поле в django_filters.Filter и метод filter_grouped (почти копия метода filter)

class Filter(object):

    def __init__(self, name=None, label=None, widget=None, action=None,
        lookup_type='exact', required=False, grouped=False, **kwargs):
        (...)
        self.grouped = grouped

    def filter_grouped(self, qs, value):
        if isinstance(value, (list, tuple)):
            lookup = str(value[1])
            if not lookup:
                lookup = 'exact' # we fallback to exact if no choice for lookup is provided
            value = value[0]
        else:
            lookup = self.lookup_type
        if value:
            return {'%s__%s' % (self.name, lookup): value}
        return {}

единственное отличие состоит в том, что вместо создания фильтра по набору запросов он возвращает словарь.

2-й обновленный метод / свойство BaseFilterSet qs:

class BaseFilterSet(object):
    (...)
    @property
    def qs(self):
        if not hasattr(self, '_qs'):
            qs = self.queryset.all()
            grouped_dict = {}
            for name, filter_ in self.filters.iteritems():
                try:
                    if self.is_bound:
                        data = self.form[name].data
                    else:
                        data = self.form.initial.get(name, self.form[name].field.initial)
                    val = self.form.fields[name].clean(data)
                    if filter_.grouped:
                        grouped_dict.update(filter_.filter_grouped(qs, val))
                    else:
                        qs = filter_.filter(qs, val)
                except forms.ValidationError:
                    pass

            if grouped_dict:
                qs = qs.filter(**grouped_dict)

        (...)
    return self._qs

Хитрость заключается в том, чтобы хранить все «сгруппированные» фильтры в словаре, а затем использовать их все как один фильтр.

Тогда фильтр будет выглядеть примерно так:

class BlogFilter(django_filters.FilterSet):
    entry__headline = django_filters.CharFilter(grouped=True)
    entry__pub_date = django_filters.CharFilter(grouped=True)

    class Meta:
        model = Blog
        fields = ['entry__headline', 'entry__pub_date', ]
...