условные наборы фильтров в DRF 3.7 autogen docs: можно ли добавить фильтр queryparam для маршрута (но только для определенных HTTP-глаголов) - PullRequest
1 голос
/ 05 апреля 2019

(DRF v3.7, django-filters v1.1.0)

Привет!У меня есть работающий FilterSet, который позволяет мне фильтровать мои результаты с помощью параметра запроса, например, http://localhost:9000/mymodel?name=FooOnly

Это работает очень хорошо.

class MyNameFilter(FilterSet):
    name = CharFilter(field_name='name', help_text='Filter by name')

    class Meta:
        model = MyModel
        fields = ('name',)


class MyModel(...):
    ...
    filter_backends = (DjangoFilterBackend,)
    filter_class = MyNameFilter

Но когда я рендерим встроенный автосгенерированные документы для моего API, я вижу этот параметр запроса, документированный для всех методов в моем маршруте, например, GET, PUT, PATCH и т. д.

Я только собираюсь фильтровать по этому запросупараметр для некоторых из этих HTTP-глаголов, так как он не имеет смысла для других, например PUT

Есть ли хороший способ сделать мой FilterSet условным таким образом?Условный метод маршрутизации.

Я пытался применить эту логику на обоих уровнях маршрутизатора (ошибочная идея).Также на уровне ViewSet - но нет метода переопределения get_filter_class, как, например, get_serializer_class.

Спасибо за помощь.

Ответы [ 2 ]

1 голос
/ 05 апреля 2019

вы получите get_filter_class в DjangoFilterBackend.Вам необходимо создать новый FilterBackend, который переопределяет метод filter_queryset.

class GETFilterBackend(DjangoFilterBackend):

    def filter_queryset(self, request, queryset, view):
        if request.method == 'GET':
            return super().filter_queryset(request, queryset, view)
        return queryset


class MyModel(...):
    ...
    filter_backends = (GETFilterBackend,)
    filter_class = MyNameFilter
0 голосов
/ 06 апреля 2019

Понял это, с помощью Карлтона Дж. На форуме django-filters групп Google (спасибо, Карлтон).

Мое решение состояло в том, чтобы подняться на новый уровень и перехватить появившуюся схему CoreAPI.проверки AutoSchema, но до того, как она попала в автоматически сгенерированные документы.

В этой точке перехвата я переопределяю _allows_filters, чтобы применять только к моим интересующим HTTP-глаголам.(Несмотря на то, что он префикс _ и, следовательно, предназначен как частный метод, не предназначенный для переопределения, комментарии метода явно поощряют это. Introduced in v3.7: Initially "private" (i.e. with leading underscore) to allow changes based on user experience.

Мой код ниже:

from rest_framework.schemas import AutoSchema


# see https://www.django-rest-framework.org/api-guide/schemas/#autoschema
#     and https://www.django-rest-framework.org/api-guide/filtering/

class LimitedFilteringViewSchema(AutoSchema):
    # Initially copied from lib/python2.7/site-packages/rest_framework/schemas/inspectors.py:352,
    # then modified to restrict our filtering by query-parameters to only certain view
    # actions or HTTP verbs
    def _allows_filters(self, path, method):
        if getattr(self.view, 'filter_backends', None) is None:
            return False

        if hasattr(self.view, 'action'):
            return self.view.action in ["list"]  # original code:  ["list", "retrieve", "update", "partial_update", "destroy"]

        return method.lower() in ["get"]  # original code:  ["get", "put", "patch", "delete"]

А потом, на моем уровне APIView:

class MyViewSchema(LimitedFilteringViewSchema):

    # note to StackOverflow:  this was some additional schema repair work I 
    # needed to do, again adding logic conditional on the HTTP verb.  
    # Not related to the original question posted here, but hopefully relevant
    # all the same.

    def get_serializer_fields(self, path, method):
        fields = super(MyViewSchema, self).get_serializer_fields(path, method)

        # The 'name' parameter is set in MyModelListItemSerializer as not being required.
        # However, when creating an access-code-pool, it must be required -- and in DRF v3.7, there's
        # no clean way of encoding this conditional logic, short of what you see here:
        #
        # We override the AutoSchema inspection class, so we can intercept the CoreAPI Fields it generated,
        # on their way out but before they make their way into the auto-generated api docs.
        #
        # CoreAPI Fields are named tuples, hence the poor man's copy constructor below.

        if path == u'/v1/domains/{domain_name}/access-code-pools' and method == 'POST':
            # find the index of our 'name' field in our fields list
            i = next((i for i, f in enumerate(fields) if (lambda f: f.name == 'name')(f)), -1)
            if i >= 0:
                name_field = fields[i]
                fields[i] = Field(name=name_field.name, location=name_field.location,
                                  schema=name_field.schema, description=name_field.description,
                                  type=name_field.type, example=name_field.example,
                                  required=True)  # all this inspection, just to set this here boolean.
        return fields


class MyNameFilter(FilterSet):
    name = CharFilter(field_name='name', help_text='Filter returned access code pools by name')

    class Meta:
        model = MyModel
        fields = ('name',)


class MyAPIView(...)

    schema = MyViewSchema()
    filter_backends = (DjangoFilterBackend,)
    filter_class = MyNameFilter


...