class SearchFilter(BaseFilterBackend):
# The URL query parameter used for the search.
search_param = api_settings.SEARCH_PARAM
template = 'rest_framework/filters/search.html'
lookup_prefixes = {
'^': 'istartswith',
'=': 'iexact',
'@': 'search',
'$': 'iregex',
}
search_title = _('Search')
search_description = _('A search term.')
def get_search_fields(self, view, request):
"""
Search fields are obtained from the view, but the request is always
passed to this method. Sub-classes can override this method to
dynamically change the search fields based on request content.
"""
return getattr(view, 'search_fields', None)
def get_search_terms(self, request):
"""
Search terms are set by a ?search=... query parameter,
and may be comma and/or whitespace delimited.
"""
params = request.query_params.get(self.search_param, '')
params = params.replace('\x00', '') # strip null characters
params = params.replace(',', ' ')
return params.split()
# 删除掉无关函数
def filter_queryset(self, request, queryset, view):
search_fields = self.get_search_fields(view, request)
search_terms = self.get_search_terms(request)
if not search_fields or not search_terms:
return queryset
orm_lookups = [
self.construct_search(str(search_field))
for search_field in search_fields
]
base = queryset
conditions = []
for search_term in search_terms:
queries = [
models.Q(**{orm_lookup: search_term})
for orm_lookup in orm_lookups
]
conditions.append(reduce(operator.or_, queries))
queryset = queryset.filter(reduce(operator.and_, conditions))
if self.must_call_distinct(queryset, search_fields):
# Filtering against a many-to-many field requires us to
# call queryset.distinct() in order to avoid duplicate items
# in the resulting queryset.
# We try to avoid this if possible, for performance reasons.
queryset = distinct(queryset, base)
return queryset
首先我们分析一下这个django自带的SearchFilter他的执行流程。
举个:
class Product(models.Model):
name = models.CharField(max_length=100, verbose_name="产品名称")
desc = models.CharField(max_length=256, default="", verbose_name="产品描述")
created_at = models.DatetimeField()
class ProductSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
class ProductListView(ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = (SearchFilter, )
filter_class = ProductFilter
search_fields = ['name', 'desc']
默认情况下,你不自定义参数的话,
SearchFilter会先去你在ProductListView下找到search_fields参数,找到你要模糊匹配的字段
www.xxx.com/product/?search=汽车,红色
然后再从链接里拿到你search的value 也就是汽车,红色,然后拆分成对应的两个terms ['汽车', '红色']
没有查询字段或者搜索词的话就不进行查询
然后根据你的search_fields的前缀是否有 ^ = @ $ 来使用对应的匹配方式,默认就是使用icontains,其实就是%terms%
然后关键的来了
for search_term in search_terms:
queries = [
models.Q(**{orm_lookup: search_term})
for orm_lookup in orm_lookups
]
conditions.append(reduce(operator.or_, queries))
queryset = queryset.filter(reduce(operator.and_, conditions))
他会先遍历你的查询词,再遍历每一个你要匹配的字段,然后用 或 的关系匹配每一个词查的字段,最后用 且 的关系匹配数据。
在例子里就是 (名称包含汽车, 或者描述包含汽车) 并且 (名称包含红色,或者描述包含红色)
也就是说我搜的这个能匹配到的就是 ①名称里有红色 有汽车 ②名称里有汽车 描述里有红色 ③名称里有红色 描述里有汽车 ④描述里有红色 有汽车
但是我们现在的需求是name必须包含红色 汽车 或者 desc包含红色 汽车,直接使用这个SearchFilter就不好使了,所以我们就自己重写这个匹配逻辑即可,当然你有其他的特殊逻辑也可以修改他。
搜索词全部在一个字段里的逻辑就是
for orm_lookup in orm_lookups:
queries = [
models.Q(**{orm_lookup: search_term})
for search_term in search_terms
]
conditions.append(reduce(operator.and_, queries))
queryset = queryset.filter(reduce(operator.or_, conditions))
简单来说就是继承SearchFilter,然后重写他的filter_queryset方法,改变底层逻辑即可。
先遍历字段,再遍历搜索词,第一个运算符用 and 且的关系 再用或 ,完美匹配了我们的需求。