I have a tagging system in place for a model that my API exposes. The models look something like this:
class TaggableModel(models.Model):
name = models.CharField(max_length=255)
tags = models.ManyToManyField(Tag, related_name="taggable_models")
class Tag(models.Model):
tag = models.CharField(max_length=32)
I've then set up a serializer and view that look like:
class TaggableModelSerializer(serializers.ModelSerializer):
class Meta:
model = TaggableModel
fields = ('id', 'name', 'tags',)
read_only_fields = ('id',)
class TaggableModelViewSet(viewsets.ModelViewSet):
queryset = TaggableModel.objects.all()
serializer_class = TaggableModelSerializer
permission_classes = (AllowAny,)
filter_backend = [DjangoFilterBackend]
filterset_fields = ['tags']
If I want to grab all TaggableModels
that have tag ids 1, 2, or 3, I can do so via:
https://my-api-domain/api/taggable-models?tags=1&tags=2&tags=3
Is there a way to split on a delimiter, so I can pass it all as one parameter? e.g.:
https://my-api-domain/api/taggable-models?tags=1,2,3
It looks like I can write my own custom DjangoFilterBackend filters, but I am a bit unsure as to where to start. Or perhaps there is an easier way to accomplish this?
The DjangoFilterBackend class is used to filter the queryset based on a specified set of fields. This backend class automatically creates a FilterSet (django_filters. rest_framework. FilterSet) class for the given fields. We can also create our own FilterSet class with customized settings.
To use DjangoFilterBackend , first install django-filter . Then add 'django_filters' to Django's INSTALLED_APPS : INSTALLED_APPS = [ ... 'django_filters', ... ] Or add the filter backend to an individual View or ViewSet.
mixins are classes that generally inherit from object (unless you are django core developer) mixins are narrow in scope as in they have single responsibility. They do one thing and do it really well. mixins provide plug-in functionality. although mixins work through inheritence, they DONT create a subtyping relation.
Sure you can do this by having custom filterset class with specific field 'widget' (that's how it is called in django-filters)
Here's a sample you can try:
# filters.py
from django_filters.rest_framework import FilterSet, filters
from django_filters.widgets import CSVWidget
from .your_models import Tag, TaggableModel
class TaggableModelFilterSet(FilterSet):
tags = filters.ModelMultipleChoiceFilter(
queryset=Tag.objects.all(), widget=CSVWidget,
help_text=_("A list of ids, comma separated, identifying tags"),
method='filter_tags'
)
class Meta:
model = TaggableModel
fields = ['tags']
def filter_tags(self, queryset, name, value):
if value:
queryset = queryset.filter(tags__in=value)
return queryset
# views.py
class TaggableModelViewSet(viewsets.ModelViewSet):
queryset = TaggableModel.objects.all()
serializer_class = TaggableModelSerializer
permission_classes = (AllowAny,)
filter_backends = [DjangoFilterBackend]
filter_class = TaggableModelFilterSet
There is an even simpler way to achieve this using the django-filter
package. Deep within the django-filter
documentation, it mentions that you can use "a dictionary of field names mapped to a list of lookups".
Your code would be updated like so:
# views.py
from django_filters.rest_framework import DjangoFilterBackend
class TaggableModelViewSet(viewsets.ModelViewSet):
queryset = TaggableModel.objects.all()
serializer_class = TaggableModelSerializer
permission_classes = (AllowAny,)
filter_backend = [DjangoFilterBackend]
filterset_fields = {
'tags': ["in", "exact"] # note the 'in' field
}
Now in the URL you would add __in
to the filter before supplying your list of parameters and it would work as you expect:
https://my-api-domain/api/taggable-models?tags__in=1,2,3
The django-filter
documentation on what lookup filters are available is quite poor, but the in
lookup filter is mentioned in the Django documentation itself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With