11

restframework内置了一些搜索功能,可以快速的实现搜索

Django REST framework的各种技巧【目录索引】

写在上面

所有的代码都是在下面的两个版本来做的

django==1.8.8
djangorestframework==3.2.5 

查询

我们经常要做一些查询的东东,大体有两种,如下图:
查询

  1. 多字段模糊搜索

  2. 单字段相等搜索

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做的,所以存在效率问题,如果有并发什么的需求,请接入其他搜索


D咄咄
1.7k 声望257 粉丝

Life is to short, please use python.