如何在 Django REST 框架中创建部分搜索过滤器?

How can I create a partial search filter in Django REST framework?

我正在使用 Django REST 框架库,我正在尝试制作一个可以按 first_name、last_name 或两者进行过滤的过滤器。 这是我的 ContactViewSet.py:

class ContactViewSet(viewsets.ModelViewSet):
    queryset = Contact.objects.all()
    serializer_class = ContactSerializer
    filter_backends = (DjangoFilterBackend, )
    filter_fields = ('first_name', 'last_name')
    lookup_field = 'idContact'

我的 DRF settings.py

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
}

我当前的请求 URL 看起来像:

http://localhost:8000/api/v1/contacts/?first_name=Clair&last_name=Test

但我正在寻找这样的东西:

http://localhost:8000/api/v1/contacts/?first_name=Cl**&last_name=Tes**

我认为DjangoFilterBackend主要是基于相等的过滤。但是你可以 customize the filtering method.

同样在 DRF 中,对于非精确过滤,有 SearchFilter 默认情况下进行不区分大小写的部分匹配搜索。

我通过像这样修改 class ContactFilter 解决了我的问题:

import django_filters
from .models import Contact

class ContactFilter(django_filters.FilterSet):
    class Meta:
        model = Contact
        fields = {
            'first_name': ['startswith'],
            'last_name': ['startswith'],
        }
        together = ['first_name', 'last_name']

在我看来,我只需要这样做:

class ContactViewSet(viewsets.ModelViewSet):
    queryset = Contact.objects.all()
    serializer_class = ContactSerializer
    filter_class = ContactFilter

我的请求 URL 如下所示:

http://localhost:8000/api/v1/contact/?first_name__contains=Cl&last_name__contains=Tes

但我仍然想知道我是否可以在 Django 中拥有这样的东西:

http://localhost:8000/api/v1/contacts/?first_name=Cl**&last_name=Tes**

如果您的要求不是太复杂,您还可以使用:

class YourModelViewSet(viewsets.ModelViewSet):
    queryset = YourModel.objects.all()
    serializer_class = YourModelSerializer
    filter_fields = {'some_field': ['startswith']}

这将在请求查询参数中启用 '?some_field__starswith=text' 语法支持。

我想 'startswith' 可以替换为任何 django 标准查询集过滤器参数。

我所做的是编写自定义 FilterBackend。像这样:

# views.py
from rest_framework import filters

class ObjektFilterBackend(filters.BaseFilterBackend):
    allowed_fields = ['objekt', 'naziv', 'kategorija', 'zadnja_sprememba']

    def filter_queryset(self, request, queryset, view):
        flt = {}
        for param in request.query_params:
            for fld in self.allowed_fields:
                if param.startswith(fld):
                    flt[param] = request.query_params[param]

        return queryset.filter(**flt)


class ObjektiViewSet(mixins.ListModelMixin,
                 mixins.RetrieveModelMixin,
                 viewsets.GenericViewSet):
    authentication_classes = (
        authentication.TokenAuthentication,
        authentication.SessionAuthentication)
    permission_classes = (IsAuthenticated,)
    queryset = models.Objekt.objects.all()
    serializer_class = serializers.ObjektSerializer
    filter_backends = (ObjektFilterBackend, ObjektOrderBackend,)
    ....

除了基本过滤(字段名=值对)外,我还可以在我的网址中使用任何 Django queryset Field Lookups(__gt、__gte、__startswith、...)像这样:

http://localhost:8000/api/v2/objekti/?naziv__startswith=Apartma&zadnja_sprememba__gte=2018-01-01

并且 ObjektFilterBackend class 可以很容易地适应以支持按模式搜索。

只是一个小警告 - 这种方法有潜在的危险,因为它允许最终用户也可以通过外键字段进行过滤。这样的事情也有效:

http://localhost:8000/api/v2/objekti/?kategorija__naziv__icontains=sobe

因此请仔细限制 allowed_fields,不要包含可能导致相关用户模型的外键。

对于模糊搜索查找,我建议使用这种方法:

filters.py

from django_filters import rest_framework as filters
from django.db.models import Q
from . import models

def filter_name(queryset, name, value):
    """
    Split the filter value into separate search terms and construct a set of queries from this. The set of queries
    includes an icontains lookup for the lookup fields for each of the search terms. The set of queries is then joined
    with the OR operator.
    """
    lookups = [name + '__icontains', ]

    or_queries = []

    search_terms = value.split()

    for search_term in search_terms:
        or_queries += [Q(**{lookup: search_term}) for lookup in lookups]

    return queryset.filter(reduce(operator.or_, or_queries))


class ContactFilter(filters.FilterSet):
    first_name = filters.CharFilter(method=filter_name, name='first_name')
    last_name = filters.CharFilter(method=filter_name, name='last_name')

    class Meta:
        model = models.Contact
        fields = [
            'first_name',
            'last_name',
        ]

api.py

class ContactViewSet(viewsets.ModelViewSet):
    queryset = Contact.objects.all()
    serializer_class = ContactSerializer
    filter_class = ContactFilter
    ...