restframework内置了一些搜索功能,可以快速的实现搜索
Django REST framework的各种技巧【目录索引】
写在上面
所有的代码都是在下面的两个版本来做的
django==1.8.8
djangorestframework==3.2.5
查询
我们经常要做一些查询的东东,大体有两种,如下图:
多字段模糊搜索
单字段相等搜索
restframework通过 filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter) 来很轻松的完成了这个工作。
文档
讲解
DjangoFilterBackend对应filter_fields属性,做相等查询
SearchFilter对应search_fields,对应模糊查询
两者都可以采用filter中使用的 外键__属性的方式来做查询
class CoursesView(ListCreateAPIView):
filter_backends = (SchoolPermissionFilterBackend, filters.DjangoFilterBackend, filters.SearchFilter)
permission_classes = (IsAuthenticated, ModulePermission)
queryset = Course.objects.filter(is_active=True).order_by('-id')
filter_fields = ('term',)
search_fields = ('name', 'teacher', 'school__name')
module_perms = ['course.course']
def get_serializer_class(self):
if self.request.method in SAFE_METHODS:
return CourseFullMessageSerializer
else:
return CourseSerializer
def get_queryset(self):
return Course.objects.select_related('school', ).filter(
is_active=True, school__is_active=True, term__is_active=True).order_by('-id')
机制是这样的,首先view调用get_queryset拿到queryset,然后在filter_backends用取到所有的backend进行filter,因此你可以写出很多通用的filter_backend然后组合调用,因为django的queryset是只有真正做list(list(qs))或者get(qs[2])或者for in的时候才会真正hit数据库,所以这种拼接是没有任何问题的。
class ListModelMixin(object):
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
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 class
我真正想说的是filter,因为默认的filter_field相关的东西文档说的非常详细,儿filter class你可能需要一番尝试。
首先看view, 当使用filter_class时就不要写filter_fields了,因为对应的Class中有一个Meta Class的fields属性代替了filter_fields。
class SchoolsView(ListCreateAPIView):
permission_classes = (IsAuthenticated, ModulePermission)
filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter)
filter_class = SchoolFilter
search_fields = ('name', 'contact')
module_perms = ['school.school']
看一个省市区的model
class BaseLocation(TimeStampedModel):
is_active = models.BooleanField(default=True, db_index=True)
class Meta:
abstract = True
def __unicode__(self):
return self.name
class Province(BaseLocation):
name = models.CharField(max_length=128, db_index=True)
class City(BaseLocation):
province = models.ForeignKey(Province)
name = models.CharField(max_length=255, db_index=True)
class District(BaseLocation):
city = models.ForeignKey(City)
name = models.CharField(max_length=255, db_index=True)
school model
class School(TimeStampedModel):
MIDDLE_SCHOOL = 1
COLLEGE = 2
school_choices = (
(MIDDLE_SCHOOL, u"中学"),
(COLLEGE, u"高校")
)
category = models.SmallIntegerField(
choices=school_choices, db_index=True, default=MIDDLE_SCHOOL)
name = models.CharField(max_length=255, db_index=True)
city = models.ForeignKey(City)
...
is_active = models.BooleanField(default=True, db_index=True)
基础filter见下面的filter,是用来做父类继承的,因为Meta class中要写一个model。
解释下面的两行,等号左边的city对应Meta中fields的city,name="city"是指Filter Meta class中对应Model的属性,lookup_type是指对应的外键属性。其实下面这两行翻译过来是model.objects.filter(city__name = fields中city变量传来的值, city__province__name=fields中province变量传来的值)
city = django_filters.Filter(name="city", lookup_type='name')
province = django_filters.Filter(name="city", lookup_type='province__name')
# -*- coding: utf-8 -*-
import django_filters
class CityFilter(django_filters.FilterSet):
city = django_filters.Filter(name="city", lookup_type='name')
province = django_filters.Filter(name="city", lookup_type='province__name')
# need to include Meta filed
class Meta:
fields = ['city', 'province']
class DistrictFilter(django_filters.FilterSet):
district = django_filters.Filter(name="district", lookup_type='name')
city = django_filters.Filter(name="district", lookup_type='city__name')
province = django_filters.Filter(name="district", lookup_type='city__province__name')
# need to include Meta filed
class Meta:
fields = ['city', 'province', 'district']
上面view中用到的SchoolFilter
class SchoolFilter(CityFilter):
class Meta:
model = School
fields = ['category', 'city', 'province']
注意
search fileds使用like做的,所以存在效率问题,如果有并发什么的需求,请接入其他搜索