1

I'm writing a web application (DRF + Vue.js) where frontend should have an ability to narrow down GET request results via different filters.
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). 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. Terrific.
    So it looks like the correct decision must be capable of filtering by several GET parameters at once. Ideally - with opportunity to choose exact/icontains/etc comparison method on each query.
    That sounds like a work for django-filter but I'm not sure yet.
  2. It's impossible to search/filter by full_name or is_adult because they are dynamic model properties, not usual fields.
    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?).
    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)

So maybe someone knows which is the most elegant way to provide a capability of such complex search with Django/DRF? In which direction should I look?

  • At the moment the best idea I came with is to use django-filter while moving model properties to calculated model fields (will need to set it read only in admin panel / in serializer) and overriding model `save()` method as it was stated here: https://stackoverflow.com/questions/44805303/django-model-method-or-calculation-as-field-in-database – Igor Avizov Sep 04 '20 at 12:59

1 Answers1

0

"full_name" alias needs to be part of your queryset. You can achieve it by queryset annotation.

In your People view (api/people/ controller) you have to set your queryset to be:

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.

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:

from rest_framework import serializers


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

Medanko
  • 708
  • 1
  • 7
  • 16
  • Thank you very much sir! Though my initial problem was partially solved by django-filter, I still did not know about queryset annotations - interesting feature! – Igor Avizov Sep 03 '22 at 18:19