Django REST framework的各類技巧——5.搜索

restframework內置了一些搜索功能,能夠快速的實現搜索數據庫

Django REST framework的各類技巧【目錄索引】django

寫在上面

全部的代碼都是在下面的兩個版原本作的api

django==1.8.8
djangorestframework==3.2.5

查詢

咱們常常要作一些查詢的東東,大致有兩種,以下圖: 查詢併發

  1. 多字段模糊搜索
  2. 單字段相等搜索

restframework經過 filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter) 來很輕鬆的完成了這個工做。app

文檔ide

講解

DjangoFilterBackend對應filter_fields屬性,作相等查詢 SearchFilter對應search_fields,對應模糊查詢ui

二者均可以採用filter中使用的 外鍵__屬性的方式來作查詢this

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(qs2)或者for in的時候纔會真正hit數據庫,因此這種拼接是沒有任何問題的。.net

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作的,因此存在效率問題,若是有併發什麼的需求,請接入其餘搜索

相關文章
相關標籤/搜索