默认已经配置好环境
# models.py
class PriceOrderModel(ModelBase):
"""询价单"""
code = models.CharField("编号", max_length=256, null=True, blank=True, unique=True)
state = models.CharField("状态", max_length=20, choices=[
("discussion", "洽谈中"),("cancel", "取消"),("ordered", "已转订单")
], default=‘discussion‘)
date = models.DateField(default=timezone.localdate, verbose_name=‘日期‘)
volume = models.FloatField("体积", null=True, blank=True)
amount = models.FloatField("金额", null=True, blank=True)
total = models.IntegerField("条目数", null=True, blank=True)
create_user = models.ForeignKey(Users, on_delete=models.CASCADE, related_name="price_orders", verbose_name="创建人", null=True, blank=True)
# my_filter.py
import django_filters
from order.models import PriceOrderModel
class PriceOrderModelFilter(django_filters.FilterSet):
company = django_filters.CharFilter(field_name=‘create_user__company‘, lookup_expr=‘icontains‘, label=‘客户‘)
product = django_filters.CharFilter(method=‘search_cargos‘, label=‘品名‘)
brand = django_filters.CharFilter(method=‘search_brand‘, label=‘品牌‘)
start_date = django_filters.DateFilter(field_name=‘date‘, lookup_expr=‘gte‘, label=‘开始时间‘) # 大于等于
end_date = django_filters.DateFilter(field_name=‘date‘, lookup_expr=‘lte‘, label=‘结束时间‘) # 小于等于
def search_brand(self, queryset, field, value):
# do somethings
return queryset.filter(xxxxxx)
class Meta:
model = PriceOrderModel
fields = [‘type‘, ‘state‘]
# views.py
from rest_framework import generics
from django_filters.rest_framework import DjangoFilterBackend
class PriceOrderListView(generics.ListCreateAPIView):
permission_classes = (IsAuthenticated, IsActivePermission)
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
queryset = PriceOrderModel.objects.all()
serializer_class = PriceOrderModelSerializer
filter_backends = [DjangoFilterBackend]
filter_class = PriceOrderModelFilter
这样通过路由就可以访问了
首先ListCreateAPIView
继承了如下:
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
在GenericAPIView
类中,实现了一个叫做filter_queryset
的方法
class GenericAPIView(views.APIView):
# .....
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
这个方法会根据你定义的filter_backends
,去执行filter_queryset
方法
filter_backends
定义的是DjangoFilterBackend
,所以要去找DjangoFilterBackend
的filter_queryset
方法
class DjangoFilterBackend(metaclass=RenameAttributes):
def filter_queryset(self, request, queryset, view):
filterset = self.get_filterset(request, queryset, view) # ①
if filterset is None: # ②
return queryset
if not filterset.is_valid() and self.raise_exception: # ③
raise utils.translate_validation(filterset.errors)
return filterset.qs
get_filterset
方法①:
def get_filterset(self, request, queryset, view):
filterset_class = self.get_filterset_class(view, queryset) # ①
if filterset_class is None:
return None
kwargs = self.get_filterset_kwargs(request, queryset, view)
return filterset_class(**kwargs)
get_filterset_class
方法①.1
这个方法内容有点多,简言之
# 拿到自定义的PriceOrderModelFilter
filterset_class = getattr(view, ‘filter_class‘, None)
# 拿到PriceOrderModelFilter,在queryset有数据集的时候,返回PriceOrderModelFilter
if filterset_class:
filterset_model = filterset_class._meta.model
# FilterSets do not need to specify a Meta class
if filterset_model and queryset is not None:
assert issubclass(queryset.model, filterset_model), ‘FilterSet model %s does not match queryset model %s‘ % (filterset_model, queryset.model)
return filterset_class
# 。。。。。
return None
这样get_filterset_class
方法就拿到了PriceOrderModelFilter
# 这一行就不解释了
if filterset_class is None:
return None
然后执行get_filterset_kwargs
方法
# 封装请求参数
def get_filterset_kwargs(self, request, queryset, view):
return {
‘data‘: request.query_params,
‘queryset‘: queryset,
‘request‘: request,
}
data就是筛选的请求url
<QueryDict: {‘type‘: [‘exists‘], ‘state‘: [‘discussion‘], ‘company‘: [‘‘], ‘product‘: [‘‘], ‘brand‘: [‘‘], ‘start_date‘: [‘2021-5-1‘], ‘end_date‘: [‘2021-5-20‘]}>
queryset就是还没有被筛选的数据集
request就是restframework再封装的请求
最后拿到实例化后的PriceOrderModelFilter
对象
拿到PriceOrderModelFilter
对象②
# 没有filter实例对象就直接返回所有的数据 ②
if filterset is None:
return queryset
# 这一步内容比较多,只挑部分有意义的讲
if not filterset.is_valid() and self.raise_exception: # ③
大概流程就是会将PriceOrderModelFilter里面的内容转化成django的form类
通过django的form校验,对url请求的参数进行校验
有错误就报错,没有就往下走
讲一下将PriceOrderModelFilter的内容转化为form的细节
# 会对url的customer键,对模型的客户公司(create_user__company)进行搜索,按照`icontains`(忽略大小写的包含搜索,也就是忽略大小写的模糊搜索)搜索
customer = django_filters.CharFilter(field_name=‘customer__company‘, lookup_expr=‘icontains‘, label=‘客户‘)
# 自定义方法,最后返回对应模型的queryset数据集就行
product = django_filters.CharFilter(method=‘search_cargos‘, label=‘品名‘)
# 对date字段,进行范围搜索gte开始时间lte结束时间
start_date = django_filters.DateFilter(field_name=‘date‘, lookup_expr=‘gte‘, label=‘开始时间‘)
end_date = django_filters.DateFilter(field_name=‘date‘, lookup_expr=‘lte‘, label=‘结束时间‘)
# Meta下的fields,这里定义的字段,会进行精准搜索
class Meta:
model = PriceOrderModel
fields = [‘type‘, ‘state‘]
最后执行filterset.qs
# django_filters/restframework/backends.py
# _qs防止重复搜索
@property
def qs(self):
if not hasattr(self, ‘_qs‘):
qs = self.queryset.all()
if self.is_bound:
# ensure form validation before filtering
self.errors
qs = self.filter_queryset(qs)
self._qs = qs
return self._qs
重点来看filter_queryset
方法
# django_filters/filterset.py
def filter_queryset(self, queryset):
"""
Filter the queryset with the underlying form‘s `cleaned_data`. You must
call `is_valid()` or `errors` before calling this method.
This method should be overridden if additional filtering needs to be
applied to the queryset before it is cached.
"""
for name, value in self.form.cleaned_data.items():
queryset = self.filters[name].filter(queryset, value)
assert isinstance(queryset, models.QuerySet), "Expected ‘%s.%s‘ to return a QuerySet, but got a %s instead." % (type(self).__name__, name, type(queryset).__name__)
return queryset
去前面转化好的form里面,获取cleaned_data
,然后开始遍历
通过遍历,拿到每一个django_filters.Filed
,再执行filter
方法
这里的filter
方法
# django_filters/filters.py
def filter(self, qs, value):
if value in EMPTY_VALUES:
return qs
if self.distinct:
qs = qs.distinct()
lookup = ‘%s__%s‘ % (self.field_name, self.lookup_expr) # ???
qs = self.get_method(qs)(**{lookup: value}) # ??
return qs
# ???
self.field_name = field_name
self.lookup_expr = lookup_expr
# django_filters/filters.py
# ??看是要过滤还是排除
def get_method(self, qs):
"""Return filter method based on whether we‘re excluding
or simply filtering.
"""
return qs.exclude if self.exclude else qs.filter
(**{lookup: value})
如果看不明白的话,可以点击参考
这样就把可筛选的数据集筛选出来了
建议搜索的字段别太多,每一次的filter,都是一个数据库访问,会影响性能。
原文:https://www.cnblogs.com/pywjh/p/14790959.html