How to filter an object with choice filed values in django_filter
Question:
I have following choice field in my model IPInfo
class IPInfoModel(models.Model):
TYPE_INTRANET = 1
TYPE_INTERNET = 2
IP_TYPES = (
(TYPE_INTRANET, u'INTRANET'),
(TYPE_INTERNET, u'INTERNET'),
)
ip = models.GenericIPAddressField("IP", unique=True)
ip_type = models.SmallIntegerField(choices=IP_TYPES)
and I use django_filters to filter IPInfo.
from django_filters import rest_framework as django_filters
class IPInfoFilter(django_filters.FilterSet):
ip_type = django_filters.ChoiceFilter(choices=IPInfoModel.IP_TYPES)
class Meta:
model = IPInfoModel
fields = ["ip_type",]
class IPInfoViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
queryset = IPInfoModel.objects.all()
serializer_class = IPInfoSerializer
filter_class = IPInfoFilter
I want to filter IPInfo on ip_type. How can I filter IPInfo by either “INTRANET” or “INTERNET”. not use “1” or “2”.
Answers:
You can define a custom method for filtering:
class IPInfoFilter(django_filters.FilterSet):
ip_type = django_filters.CharFilter(method='filter_ip_type')
def filter_ip_type(self, queryset, name, value):
# create a dictionary string -> integer
value_map = {v: k for k, v in IPInfoModel.IP_TYPES.items()}
# get the integer value for the input string
value = value_map[value]
return queryset.filter(ip_type=value)
Do you can use something like this to filter multiple choices fields:
On models.py
from django.db import models
class MyModel(models.Model):
class Options(models.IntegerChoices):
OPTION_A = 1, 'FIRST'
OPTION_B = 2, 'SECOND'
OPTION_C = 3, 'THIRD'
option = models.PositiveSmallIntegerField(
verbose_name='Option',
choices=Options.choices,
null=False, default=0,
)
On views.py
from rest_framework import viewsets
from .filters import MyFilter
from .models import MyModel
from .serializers import MySerializer
class MyViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MySerializer
filter_backends = (MyFilter,)
On serializers.py
from rest_framework import serializers
from .models import MyModel
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
On filters.py
import operator
from functools import reduce
from django.db.models import Q
from rest_framework.filters import BaseFilterBackend
def get_option_value(option):
''' Returns the option value '''
# avoid circular import
from .models import MyModel
options = {
MyModel.Options.OPTION_A.label: MyModel.Options.OPTION_A.value,
MyModel.Options.OPTION_B.label: MyModel.Options.OPTION_B.value,
MyModel.Options.OPTION_C.label: MyModel.Options.OPTION_C.value,
}
return options.get(option.upper(), None)
class MyFilter(BaseFilterBackend):
''' Filter by option label '''
def filter_queryset(self, request, queryset, view):
option_list = request.GET.getlist('option', None)
if not option_list:
return queryset.all()
else:
try:
queries = []
for option in option_list:
value = get_option_value(option)
if value:
queries.append(Q(option=value))
return queryset.filter(reduce(operator.or_, queries))
except Exception:
return queryset.none()
With the configuration above, create a route to the MyViewSet
class then run:
python manage.py runserver
Then, in your browser access for example:
http://localhost:8000/my-view-set-url/?option=FIRST&option=THIRD
I have following choice field in my model IPInfo
class IPInfoModel(models.Model):
TYPE_INTRANET = 1
TYPE_INTERNET = 2
IP_TYPES = (
(TYPE_INTRANET, u'INTRANET'),
(TYPE_INTERNET, u'INTERNET'),
)
ip = models.GenericIPAddressField("IP", unique=True)
ip_type = models.SmallIntegerField(choices=IP_TYPES)
and I use django_filters to filter IPInfo.
from django_filters import rest_framework as django_filters
class IPInfoFilter(django_filters.FilterSet):
ip_type = django_filters.ChoiceFilter(choices=IPInfoModel.IP_TYPES)
class Meta:
model = IPInfoModel
fields = ["ip_type",]
class IPInfoViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
queryset = IPInfoModel.objects.all()
serializer_class = IPInfoSerializer
filter_class = IPInfoFilter
I want to filter IPInfo on ip_type. How can I filter IPInfo by either “INTRANET” or “INTERNET”. not use “1” or “2”.
You can define a custom method for filtering:
class IPInfoFilter(django_filters.FilterSet):
ip_type = django_filters.CharFilter(method='filter_ip_type')
def filter_ip_type(self, queryset, name, value):
# create a dictionary string -> integer
value_map = {v: k for k, v in IPInfoModel.IP_TYPES.items()}
# get the integer value for the input string
value = value_map[value]
return queryset.filter(ip_type=value)
Do you can use something like this to filter multiple choices fields:
On models.py
from django.db import models
class MyModel(models.Model):
class Options(models.IntegerChoices):
OPTION_A = 1, 'FIRST'
OPTION_B = 2, 'SECOND'
OPTION_C = 3, 'THIRD'
option = models.PositiveSmallIntegerField(
verbose_name='Option',
choices=Options.choices,
null=False, default=0,
)
On views.py
from rest_framework import viewsets
from .filters import MyFilter
from .models import MyModel
from .serializers import MySerializer
class MyViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MySerializer
filter_backends = (MyFilter,)
On serializers.py
from rest_framework import serializers
from .models import MyModel
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
On filters.py
import operator
from functools import reduce
from django.db.models import Q
from rest_framework.filters import BaseFilterBackend
def get_option_value(option):
''' Returns the option value '''
# avoid circular import
from .models import MyModel
options = {
MyModel.Options.OPTION_A.label: MyModel.Options.OPTION_A.value,
MyModel.Options.OPTION_B.label: MyModel.Options.OPTION_B.value,
MyModel.Options.OPTION_C.label: MyModel.Options.OPTION_C.value,
}
return options.get(option.upper(), None)
class MyFilter(BaseFilterBackend):
''' Filter by option label '''
def filter_queryset(self, request, queryset, view):
option_list = request.GET.getlist('option', None)
if not option_list:
return queryset.all()
else:
try:
queries = []
for option in option_list:
value = get_option_value(option)
if value:
queries.append(Q(option=value))
return queryset.filter(reduce(operator.or_, queries))
except Exception:
return queryset.none()
With the configuration above, create a route to the MyViewSet
class then run:
python manage.py runserver
Then, in your browser access for example:
http://localhost:8000/my-view-set-url/?option=FIRST&option=THIRD