If you have the django-rest-framework in your project to provide a REST-API, and want to expose items, which have a TaggableManager from django-taggit, you have a problem: When adding an item with tags through the REST-API, no tags getting saved or the serializer rejects your request data. I found a way to work around this issue. Don’t know if it’s the best approach, but it is quite straight-forward and works!
Assuming we have a model called
Bookmark, which includes the TaggableManager from django-taggit.
1 2 3 4 5 6
from django.db import models from taggit.managers import TaggableManager class Bookmark(models.Model): url = models.URLField("URL") tags = TaggableManager(blank=True)
At first it is a good idea to write a
ModelSerializer (more info), which maps the fields of our Bookmark-model to the corresponding serializer fields.
1 2 3 4 5 6 7 8 9 10
from bookmarks.models import Bookmark from rest_framework import serializers from rest_framework.exceptions import ParseError class BookmarkSerializer(serializers.ModelSerializer): tags = TagListSerializer(blank=True) class Meta: model = Bookmark fields = ('url', 'tags')
As you can see, a custom field (more info) called
TagListSerializer is defined for the tags-field. The next step is to implement this
TagListSerializer. To to that, we create new class, which inherits from
serializers.WritableField and override the methods
to_native(). This is how we gain control over the serialization- and deserialization-process and define our own logic:
1 2 3 4 5 6 7 8 9 10 11
class TagListSerializer(serializers.WritableField): def from_native(self, data): if type(data) is not list: raise ParseError("expected a list of data") return data def to_native(self, obj): if type(obj) is not list: return [tag.name for tag in obj.all()] return obj
data of the
from_native()-method is a parsed (Python-)list of tags from the HTTP-request’s JSON-object. If the client provides a list like [‘tag1’, ‘tag2’], we’re fine. But if the client provides a string like ‘tag1, tag2’ or a single tag like ‘single'tag’, an exception is raised, resulting in a 400-response to the client. The method
to_native() expects either the TaggableManager-object or a list of tags and returns the tag-list.
At the end we have to define a ModelViewSet (more info), which implements the complete set of default read and write operations for our
Bookmark model. We have to override the
post_save method, to finally save the tags to the previously saved Bookmark-model.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
from rest_framework import viewsets from bookmarks.models import Bookmark from bookmarks_api.serializers import BookmarkSerializer class BookmarkViewSet(viewsets.ModelViewSet): serializer_class = BookmarkSerializer queryset = Bookmark.objects.all() def post_save(self, bookmark, *args, **kwargs): if type(bookmark.tags) is list: # If tags were provided in the request saved_bookmark = Bookmark.objects.get(pk=bookmark.pk) for tag in bookmark.tags: saved_bookmark.tags.add(tag)
This is my approach of implementing django-taggit tags in conjunction with django-rest-framework. If you know a better way to deal with the TaggableManager please leave a reply below!