DRF 3.7 autogen 文档中的条件 FilterSets:我可以为路由添加查询参数过滤器吗(但仅适用于某些 HTTP 动词)
conditional FilterSets in DRF 3.7 autogen docs: can I add a queryparam filter for a route (but only for certain HTTP verbs)
(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
.
感谢您的帮助。
您将在 DjangoFilterBackend
中获得 get_filter_class
。您需要创建一个新的 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
在 django-filters Google 群组论坛上 Carlton G. 的帮助下解决了这个问题(谢谢 Carlton)。
我的解决方案是提高一个级别并拦截来自 AutoSchema 检查的 CoreAPI 模式,但在它进入自动生成的文档之前。
在这个拦截点,我覆盖 _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 Whosebug: 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
(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
.
感谢您的帮助。
您将在 DjangoFilterBackend
中获得 get_filter_class
。您需要创建一个新的 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
在 django-filters Google 群组论坛上 Carlton G. 的帮助下解决了这个问题(谢谢 Carlton)。
我的解决方案是提高一个级别并拦截来自 AutoSchema 检查的 CoreAPI 模式,但在它进入自动生成的文档之前。
在这个拦截点,我覆盖 _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 Whosebug: 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