Search code examples
pythondjangodjango-rest-frameworkdjango-q

Build request URL to filter Django queryset multiple times by the same field


I want to filter Django queryset by the same field multiple times using Q to include/exclude records with specific value in this field.

I'll use sample model to illustrate my case. Say I have a Record model with field status. This field can be one of three states A, B, C.

class Record(models.Model):
    STATUS_A = 'A'
    STATUS_B = 'B'
    STATUS_C = 'C'

    SOME_STATUSES = (
        (STATUS_A, 'Something A'),
        (STATUS_B, 'Something B'),
        (STATUS_C, 'Something C'),
    )

    status = models.CharField(
    max_length=1,
    choices= SOME_STATUSES,
    default= STATUS_A,
    )

I have a DRF ViewSet that's responsible for returning filtered queryset of Record objects.

Right now I'm filtering query set by a single status, so my URL looks something like:

.../?status=A
.../?status=B
.../?status=C

But say I want to filter queryset by multiple statuses: Return all records with status A and B. Or instead I want to return all the records except those with status C. I'm not sure how to build URL in those cases. I know that duplicating parameters in URL is a very bad practice:

.../?status=A&status=B

How does one request A AND B or NOT C?

The remaining part of the question how to handle those multiple values is unclear probably because I don't understand how to build such a query in the first place.


Solution

  • By writing CustomDjangoFilter you can achieve this.

    Sample Code

    URL examples and Usage

    URL  : localhost:8000/records/?status_include=A,B
    URL  : localhost:8000/records/?status_exclude=A
    URL  : localhost:8000/records/?status_include=A,B,C&status_exclude=D,E,F
    

    Code Snippet

    views.py

    from django_filters.rest_framework import DjangoFilterBackend
    from .filters import CustomRecordFilter
    
    class RecordViewSet(viewsets.ModelViewSet):
        queryset = Record.objects.all()
        serializer_class = RecordSerializer
    
        # django-filter-backend and custom-filter-class
        filter_backends = (DjangoFilterBackend, )
        filter_class = CustomRecordFilter
    

    filters.py

    import django_filters
    
    class CustomRecordFilter(django_filters.FilterSet):
        status_exclude = django_filters.CharFilter(field_name='status', method='filter_status_exclude')
        status_include = django_filters.CharFilter(field_name='status', method='filter_status_include')
    
    def filter_status_include(self, queryset, name, value):
        if not value:
            return queryset
        values = ''.join(value.split(' ')).split(',')
        queryset = queryset.filter(status__in=values)
        return queryset
    
    def filter_status_exclude(self, queryset, name, value):
        if not value:
            return queryset
        values = ''.join(value.split(' ')).split(',')
    
        # exclude status
        queryset = queryset.exclude(status__in=values)
        return queryset
    
    class Meta:
        model = UserRoleGroup
        fields = ('status', 'status_include', 'status_exclude')