Django-filters:单个查询字符串中的多个 ID
Django-filters: multiple IDs in a single query string
使用 django-filters,我看到了关于如何在单个查询字符串中提交多个相同类型的参数的各种解决方案,例如多个 ID。他们都建议使用一个单独的字段,其中包含一个以逗号分隔的值列表,例如:
http://example.com/api/cities?ids=1,2,3
使用单个参数提交一次或多次,有没有通用的解决方案?例如:
http://example.com/api/cities?id=1&id=2&id=3
我尝试使用 MultipleChoiceFilter
,但它期望实际选择被定义,而我想传递任意 ID(其中一些甚至可能不存在于数据库中)。
我建议您使用自定义过滤器,如下所示
from django_filters.filters import Filter
from rest_framework.serializers import ValidationError
from django_filters.fields import Lookup
class ListFilter(Filter):
def filter(self, queryset, value):
list_values = value.split(',')
if not all(item.isdigit() for item in list_values):
raise ValidationError('All values in %s the are not integer' % str(list_values))
return super(ListFilter, self).filter(queryset, Lookup(list_values, 'in'))
使用自定义过滤器解决,灵感来自 Jerin 的回答:
class ListFilter(Filter):
def filter(self, queryset, value):
try:
request = self.parent.request
except AttributeError:
return None
values = request.GET.getlist(self.name)
values = {int(item) for item in values if item.isdigit()}
return super(ListFilter, self).filter(queryset, Lookup(values, 'in'))
如果值是非数字的,例如color=blue&color=red
那么 isdigit()
验证当然是不需要的。
http://example.com/api/cities?ids=1,2,3
get_ids=1,2,3
id_list = list(get_ides]
Mdele_name.objects.filter(id__in= id_list)
我有一些问题
所以我稍微改了一下:
class ListFilter(Filter):
def __init__(self, query_param, *args, **kwargs):
super(ListFilter, self).__init__(*args, **kwargs)
# url = /api/cities/?id=1&id=2&id=3 or /api/cities/?id=1,2,3
# or /api/cities/?id=1&id=2&id=3?id=4,5,6
self.query_param = query_param
self.lookup_expr = 'in'
def filter(self, queryset, value):
try:
request = self.parent.request
except AttributeError:
return None
values = set()
query_list = request.GET.getlist(self.query_param)
for v in query_list:
values = values.union(set(v.split(',')))
values = set(map(int, values))
return super(ListFilter, self).filter(queryset, values)
class CityFilter(filterset.FilterSet):
id = ListFilter(field_name='id', query_param='id')
name = filters.CharFilter(field_name='name', lookup_expr='icontains')
class Meta:
model = City
fields = ['name']
如果您想使用自定义查询参数名称 - 更改 query_param
arg.
作为选项
class CityFilterSet(django_filters.FilterSet):
id = django_filters.NumberFilter(method='filter_id')
def filter_id(self, qs, name, value):
return qs.filter(id__in=self.request.GET.getlist('id'))
这是一个 可重复使用的解决方案 使用自定义 Filter
和自定义 Field
.
自定义 Field
重用 Django 的 MultipleChoiceField 但替换了验证函数。
相反,它使用我们传递给构造函数的另一个 Field
class 进行验证。
from django.forms.fields import MultipleChoiceField
class MultipleValueField(MultipleChoiceField):
def __init__(self, *args, field_class, **kwargs):
self.inner_field = field_class()
super().__init__(*args, **kwargs)
def valid_value(self, value):
return self.inner_field.validate(value)
def clean(self, values):
return values and [self.inner_field.clean(value) for value in values]
自定义 Filter
使用 MultipleValueField
并转发 field_class
参数。
它还将 lookup_expr
的默认值设置为 in
。
from django_filters.filters import Filter
class MultipleValueFilter(Filter):
field_class = MultipleValueField
def __init__(self, *args, field_class, **kwargs):
kwargs.setdefault('lookup_expr', 'in')
super().__init__(*args, field_class=field_class, **kwargs)
要使用此过滤器,只需使用适当的 field_class
创建一个 MultipleValueFilter
。例如,要用 id
过滤 City
,我们可以使用 IntegerField,像这样:
from django.forms.fields import IntegerField
class CityFilterSet(FilterSet):
id = MultipleValueFilter(field_class=IntegerField)
name = filters.CharFilter(lookup_expr='icontains')
class Meta:
model = City
fields = ['name']
Benoit Blanchon 我觉得最好
但我已尝试改进它以允许更多类型的 lookup_expr。
查看他的 以获得完整的代码片段。
from django.db.models import Q
class MultipleValueFilter(Filter):
field_class = MultipleValueField
def __init__(self, *args, field_class, **kwargs):
kwargs.setdefault('lookup_expr', 'in')
super().__init__(*args, field_class=field_class, **kwargs)
def filter(self, qs, value):
# if it's not a list then let the parent deal with it
if self.lookup_expr == 'in' or not isinstance(value, list):
return super().filter(qs, value)
# empty list
if not value:
return qs
if self.distinct:
qs = qs.distinct()
lookup = '%s__%s' % (self.field_name, self.lookup_expr)
filters = Q()
for v in value:
filters |= Q(**{lookup: v})
qs = self.get_method(qs)(filters)
return qs
通过此更改,您现在可以使用 iexact 作为示例。
您也可以使用 gte 等,但这些可能没有多大意义。
使用 django-filters,我看到了关于如何在单个查询字符串中提交多个相同类型的参数的各种解决方案,例如多个 ID。他们都建议使用一个单独的字段,其中包含一个以逗号分隔的值列表,例如:
http://example.com/api/cities?ids=1,2,3
使用单个参数提交一次或多次,有没有通用的解决方案?例如:
http://example.com/api/cities?id=1&id=2&id=3
我尝试使用 MultipleChoiceFilter
,但它期望实际选择被定义,而我想传递任意 ID(其中一些甚至可能不存在于数据库中)。
我建议您使用自定义过滤器,如下所示
from django_filters.filters import Filter
from rest_framework.serializers import ValidationError
from django_filters.fields import Lookup
class ListFilter(Filter):
def filter(self, queryset, value):
list_values = value.split(',')
if not all(item.isdigit() for item in list_values):
raise ValidationError('All values in %s the are not integer' % str(list_values))
return super(ListFilter, self).filter(queryset, Lookup(list_values, 'in'))
使用自定义过滤器解决,灵感来自 Jerin 的回答:
class ListFilter(Filter):
def filter(self, queryset, value):
try:
request = self.parent.request
except AttributeError:
return None
values = request.GET.getlist(self.name)
values = {int(item) for item in values if item.isdigit()}
return super(ListFilter, self).filter(queryset, Lookup(values, 'in'))
如果值是非数字的,例如color=blue&color=red
那么 isdigit()
验证当然是不需要的。
http://example.com/api/cities?ids=1,2,3
get_ids=1,2,3
id_list = list(get_ides]
Mdele_name.objects.filter(id__in= id_list)
我有一些问题
所以我稍微改了一下:
class ListFilter(Filter):
def __init__(self, query_param, *args, **kwargs):
super(ListFilter, self).__init__(*args, **kwargs)
# url = /api/cities/?id=1&id=2&id=3 or /api/cities/?id=1,2,3
# or /api/cities/?id=1&id=2&id=3?id=4,5,6
self.query_param = query_param
self.lookup_expr = 'in'
def filter(self, queryset, value):
try:
request = self.parent.request
except AttributeError:
return None
values = set()
query_list = request.GET.getlist(self.query_param)
for v in query_list:
values = values.union(set(v.split(',')))
values = set(map(int, values))
return super(ListFilter, self).filter(queryset, values)
class CityFilter(filterset.FilterSet):
id = ListFilter(field_name='id', query_param='id')
name = filters.CharFilter(field_name='name', lookup_expr='icontains')
class Meta:
model = City
fields = ['name']
如果您想使用自定义查询参数名称 - 更改 query_param
arg.
作为选项
class CityFilterSet(django_filters.FilterSet):
id = django_filters.NumberFilter(method='filter_id')
def filter_id(self, qs, name, value):
return qs.filter(id__in=self.request.GET.getlist('id'))
这是一个 可重复使用的解决方案 使用自定义 Filter
和自定义 Field
.
自定义 Field
重用 Django 的 MultipleChoiceField 但替换了验证函数。
相反,它使用我们传递给构造函数的另一个 Field
class 进行验证。
from django.forms.fields import MultipleChoiceField
class MultipleValueField(MultipleChoiceField):
def __init__(self, *args, field_class, **kwargs):
self.inner_field = field_class()
super().__init__(*args, **kwargs)
def valid_value(self, value):
return self.inner_field.validate(value)
def clean(self, values):
return values and [self.inner_field.clean(value) for value in values]
自定义 Filter
使用 MultipleValueField
并转发 field_class
参数。
它还将 lookup_expr
的默认值设置为 in
。
from django_filters.filters import Filter
class MultipleValueFilter(Filter):
field_class = MultipleValueField
def __init__(self, *args, field_class, **kwargs):
kwargs.setdefault('lookup_expr', 'in')
super().__init__(*args, field_class=field_class, **kwargs)
要使用此过滤器,只需使用适当的 field_class
创建一个 MultipleValueFilter
。例如,要用 id
过滤 City
,我们可以使用 IntegerField,像这样:
from django.forms.fields import IntegerField
class CityFilterSet(FilterSet):
id = MultipleValueFilter(field_class=IntegerField)
name = filters.CharFilter(lookup_expr='icontains')
class Meta:
model = City
fields = ['name']
Benoit Blanchon 我觉得最好
但我已尝试改进它以允许更多类型的 lookup_expr。
查看他的
from django.db.models import Q
class MultipleValueFilter(Filter):
field_class = MultipleValueField
def __init__(self, *args, field_class, **kwargs):
kwargs.setdefault('lookup_expr', 'in')
super().__init__(*args, field_class=field_class, **kwargs)
def filter(self, qs, value):
# if it's not a list then let the parent deal with it
if self.lookup_expr == 'in' or not isinstance(value, list):
return super().filter(qs, value)
# empty list
if not value:
return qs
if self.distinct:
qs = qs.distinct()
lookup = '%s__%s' % (self.field_name, self.lookup_expr)
filters = Q()
for v in value:
filters |= Q(**{lookup: v})
qs = self.get_method(qs)(filters)
return qs
通过此更改,您现在可以使用 iexact 作为示例。 您也可以使用 gte 等,但这些可能没有多大意义。