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.
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')