Django-Rest-Framework-Filters: использование пользовательских методов набора запросов в фильтре, потребляемом через RelatedFilter

Я использую Django-Rest-Framework-Filters аналогично задокументировано здесь. Я хотел бы отфильтровать автора по некоторому условию в соответствующем классе Post, который использует настраиваемый метод PostQuerySet queryset. Фильтр myfilter определен в PostFilter наборе фильтров как:

class PostFilter(filters.FilterSet):
    myfilter = filters.BooleanFilter(name='date_published', method='filter_myfilter')

    class Meta:
        model = Post
        fields = ['title', 'content']

    def filter_myfilter(self, qs, name, value):
        """
        Calls myqueryset_method defined on PostQuerySet 
        """
        return qs.myqueryset_method()

class AuthorFilter(filters.FilterSet):
    posts = filters.RelatedFilter('PostFilter', queryset=Post.objects.all())

    class Meta:
        model = Author
        fields = ['name']

Проблема в том, что при попытке использовать этот фильтр как часть авторского API, например

/api/authors?posts__myfilter=true

выдается ошибка:

"AttributeError: объект 'Manager' не имеет атрибута 'myqueryset_method'"

Это кажется нелогичным, но похоже, что вы не можете выполнить метод PostQuerySet для qs arugment, потому что он не является набором запросов Post при вызове с помощью RelatedFilter. Как объяснено в документах:

[when making the filter calls]

/api/posts?is_published=true
/api/authors?posts__is_published=true

"In the first API call, the filter method receives a queryset of posts. In the second, it receives a queryset of users."

Итак, как можно использовать методы настраиваемого набора запросов в фильтре, который используется через RelatedFilter?


person Greg Brown    schedule 11.08.2017    source источник
comment
Короткий ответ заключается в том, что в настоящее время это невозможно и зависит от решения # 99. Некоторое время я медленно пытался решить эту проблему, но столкнулся с проблемами производительности.   -  person Sherpa    schedule 11.08.2017


Ответы (1)


Мне удалось обойти эту проблему, достигнув семантики «цепного вложенного соединения» (q3), описанного в обсуждение, связанное с sherpa.

Приведенный ниже код немного груб, но в основном он просто связывает фильтр с qs, когда типы моделей одинаковы (qs.model == filterset._meta.model).

Если они разные, то эффективно выполните вложенное соединение для (Автор) qs.

В utils.py:

from django.db.models.constants import LOOKUP_SEP

def get_lookup_expression(name):

    # The name argument contains the filter to apply. NOTE - if this filter is consumed via a RelatedFilter,
    # i.e. on AuthorFilter, the filters list will contain ['author', 'filter_myfilter']
    filters = name.split(LOOKUP_SEP)

    # Remove the final filter name from filter list -this should handle filtering over multiple traversed models 
    filter = LOOKUP_SEP.join(filters[:-1])

    # Create new lookup expression, i.e. 'author__in'
    lookup_expr = LOOKUP_SEP.join([filter, 'in'])

    return lookup_expr


def related_filter_handler(func, filterset, qs, name, value):

    # Get model in the queryset and the model defined in the filterset
    qs_model = qs.model
    filter_model = filterset._meta.model

    if (qs_model == filter_model):
        # If they are equal then we are applying the filter directly on the model via its API, e.g. '/api/posts?customfilter=true'
        return func(qs, value)
    else:
        # They will be different when calling '/api/author?post__customfilter=true'

        # Apply filter to filter_model
        T2 = func(filter_model.objects.all(), value)

        # Construct lookup_expr 'X__in' where X is the path to traverse from the related (qs) model
        lookup_expr = get_lookup_expression(name)

        # Filters RelatedFilter qs, i.e. qs.filter(post__in=T2)
        return qs.filter(**{lookup_expr: T2})

В filter.py:

import rest_framework_filters as filters
from .utils import related_filter_handler
from .models import Post

class PostFilter(filters.FilterSet):
    myfilter = filters.BooleanFilter(name='date_published', method='customfilter')

    class Meta:
        model = Post
        fields = ['title', 'content']

    def customfilter(self, qs, name, value):
        """
        Calls myqueryset_method defined on PostQuerySet 
        """
        # Wrap the filter logic in function/lambda
        def apply(qs, value):
            # e.g. passing in value to the queryset method
            return qs.myqueryset_method(value) 

        # Pass filter expression into helper function which handles chaining the filter as is or chaining a nested join 
        return related_filter_handler(apply, self, qs, name, value)

Работа проходит мои модульные тесты. Но могут возникнуть проблемы, если вы имеете дело с прокси-моделями. Не стесняйтесь редактировать, чтобы улучшить код.

Было бы намного лучше, если бы это было встроенным поведением RelatedFilter, так как вам пришлось бы закодировать все свои собственные методы фильтрации против будущего / неизвестного использования через RelatedFilters.

person Greg Brown    schedule 14.08.2017