简体   繁体   English

通过 REST 框架在 Django 模型中进行健壮搜索的正确方法

[英]Proper way to do a robust search in Django models via REST framework

I'm writing a web application (DRF + Vue.js) where frontend should have an ability to narrow down GET request results via different filters.我正在编写一个 Web 应用程序(DRF + Vue.js),其中前端应该能够通过不同的过滤器缩小 GET 请求结果的范围。
For example, I have a model like this:例如,我有一个这样的模型:

class Human(models.Model):
    first_name = models.CharField(_('first name'), max_length=50, null=True, blank=True)
    last_name = models.CharField(_('last name'), max_length=50, null=True, blank=True)
    birth_date = models.DateField(_('birth date'), blank=True, null=True)
    city = models.ForeignKey('City', on_delete=models.SET_NULL, blank=True, null=True)
    phone_number = models.ForeignKey('Contact' on_delete=models.SET_NULL, blank=True, null=True)

    @property
    def full_name(self):
        # Last name + First name
        return ' '.join(str(x) for x in (self.last_name, self.first_name) if x)

    @property
    def is_adult(self):
        now = timezone.now()
        if self.birth_date:
            if now.year - self.birth_date.year - \
                    ((now.month, now.day) < (self.birth_date.month, self.birth_date.day)) >= 18:
                return True
        return False

Now I have simple CRUD ViewSet where I can use a list of search_fields to search by all needed fields (in my case that's birth_date , city , phone_number and full_name / is_adult ).现在我有一个简单的 CRUD ViewSet,我可以在其中使用search_fields列表来搜索所有需要的字段(在我的例子中是birth_datecityphone_numberfull_name / is_adult )。 But here next problems arise:但是这里出现了下一个问题:

  1. Using search_fields I can do a search only by all fields specified in the ViewSet's search_fields (frontend can't search only by city or other distinct fields if it wants to) - other way I'll need to create a separate ViewSet (and separate URL?) for every combination of fields to filter by.使用search_fields我只能通过 ViewSet 的search_fields中指定的所有字段进行搜索(如果需要,前端不能仅按city或其他不同的字段进行搜索) - 其他方式我需要创建一个单独的 ViewSet (和单独的 URL ?) 用于过滤的每个字段组合。 Terrific.了不起。
    So it looks like the correct decision must be capable of filtering by several GET parameters at once.所以看起来正确的决定必须能够同时通过几个 GET 参数进行过滤。 Ideally - with opportunity to choose exact / icontains /etc comparison method on each query.理想情况下 - 有机会在每个查询中选择exact / icontains /etc 比较方法。
    That sounds like a work for django-filter but I'm not sure yet.这听起来像是django-filter的工作,但我还不确定。
  2. It's impossible to search/filter by full_name or is_adult because they are dynamic model properties, not usual fields.不可能通过full_nameis_adult搜索/过滤,因为它们是动态模型属性,而不是通常的字段。
    So it looks like instead of using model properties I need to use separate QuerySets (Manager methods?) that will do the logic of fiddling with model fields and creating the filtered result.所以看起来我需要使用单独的查询集(管理器方法?)而不是使用模型属性,这将执行摆弄模型字段和创建过滤结果的逻辑。
    But for now I didn't find a way to choose different QuerySets in a single ViewSet depending on GET parameters (or how to use search by these complex properties together with simple search from problem 1 ?).但是现在我还没有找到一种方法来根据 GET 参数在单个 ViewSet 中选择不同的 QuerySet(或者如何使用这些复杂属性的搜索以及问题 1中的简单搜索?)。
    And I have no understanding if it is possible to provide this kind of search by the same URL as a "simple" search - like site.com/api/people/?city=^New&full_name=John%20Doe (perfectly - with opportunity to document query parameters for OpenAPI schema / Swagger)而且我不知道是否可以通过与“简单”搜索相同的 URL 提供这种搜索 - 比如site.com/api/people/?city=^New&full_name=John%20Doe (完美 - 有机会OpenAPI 模式 / Swagger 的文档查询参数)

So maybe someone knows which is the most elegant way to provide a capability of such complex search with Django/DRF?所以也许有人知道用 Django/DRF 提供这种复杂搜索功能的最优雅的方法是什么? In which direction should I look?我应该朝哪个方向看?

"full_name" alias needs to be part of your queryset. “full_name”别名需要成为您的查询集的一部分。 You can achieve it by queryset annotation.您可以通过查询集注释来实现它。

In your People view ( api/people/ controller) you have to set your queryset to be:在您的人员视图( api/people/控制器)中,您必须将您的查询集设置为:

from django.db.models.functions import Concat
from django.db.models import Value

queryset = Human.objects.annotate(fullname=Concat('first_name', Value(' '), 'last_name'))

Also, customize your "filter_backed" to act the way you need.此外,自定义您的“filter_backed”以按照您需要的方式行事。

class PeopleFilterBackend(filters.BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        params_serializer = PeopleFilterSerializer(data=request.params)
        params_serializer.is_valid(raise_exception=True)
        valid_params = params_serializer.data

        if full_name := valid_params.get("full_name")
            queryset = queryset.filter(full_name__icountains=full_name)

        # insert here all of other filters logic
        return queryset

PeopleFilterSerializer is your custom params serializer, You have to specify there all of your accepted params: PeopleFilterSerializer 是您的自定义参数序列化程序,您必须在那里指定所有接受的参数:

from rest_framework import serializers


class PeopleFilterSerializer(serializers.Serializer):
    full_name = serializers.CharField()  #  set required=False if not required
    city = serializer.CharField()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM