TDLR : what is the best way to implement tags in django-rest-framework. where the tags has a created_by
field which is the currently authenticated user.
I am trying to achieve a very simple/common thing, add tags to posts. But apparently its not a piece of cake.
So i have a posts model and a tags models (may to many relation). I want the user to be able to update and create the posts. when creating or updating posts he should be able to update the tags of the posts. When a post is tagged with a new tag, that tag should be created if it dosent exist. Also i want to user to be able to specify the tags as a list of strings in the request.
Example request
{
"name": "testpost1",
"caption": "test caption",
"tags": ["tag1", "tag2"],
},
models.py
class Tags(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
name = models.CharField(max_length=50, unique=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="created_tags")
class Posts(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
name = models.CharField(max_length=50)
caption = models.TextField(max_length=1000)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
tags = models.ManyToManyField('Tags', related_name='posts')
serializers.py
class TagsSerializerMini(serializers.ModelSerializer):
created_by = serializers.PrimaryKeyRelatedField(default=serializers.CurrentUserDefault(), queryset=User.objects.all())
class Meta:
model = Tags
fields = ('name', 'created_by')
extra_kwargs = {
'created_by': {'write_only': True},
'name': {'validators': []},
}
def create(self, validated_data):
tag, created = Tags.objects.get_or_create(**validated_data)
if not created:
raise exceptions.ValidationError(validated_data['name']+" already exists.")
return tag
def to_representation(self, instance):
ret = super(TagsSerializerMini, self).to_representation(instance)
data = dict()
data['name'] = ret['name']
return data
I have tried two methods. Using nested serializer and using slug related field.
When using SlugRealtedfield, it throws as validation error that the tag object dosent exists. I was planning if i could deisable this check, i could create all tags before create() and call super create. But i could'nt bypass that validation check. Also i couldnt figure out how to pass the current user to the slugrelatedfield.
After some searching, i planned to use nested serializers. But i have to specify the tags as dict [{"name":"tag1"}]
. Also i have to define custom create and update. I could get the create to work, but not the update.
class PostsSerializer(QueryFieldsMixin, WritableNestedModelSerializer):
created_by = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = Posts
fields = ('id', 'name', 'caption', 'tags', 'created_by')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['tags'] = TagsSerializerMini(many=True, required=False, context=self.context)
def create(self, validated_data):
tags_data = validated_data.pop('tags', [])
post = Posts.objects.create(**validated_data)
for tag in tags_data:
t, _ = Tags.objects.get_or_create(name=tag["name"])
post.tags.add(t)
return post
Django REST framework (DRF) is a powerful and flexible toolkit for building Web APIs. Its main benefit is that it makes serialization much easier. Django REST framework is based on Django's class-based views, so it's an excellent option if you're familiar with Django.
Using template packsThe render_form tag takes an optional template_pack argument, that specifies which template directory should be used for rendering the form and form fields. REST framework includes three built-in template packs, all based on Bootstrap 3. The built-in styles are horizontal , vertical , and inline .
In my opinion, it is more elegant to use SlugRelatedField and not a nested serializer, because this way you will have an array of tags (and an array of tag names in the response) instead of an array of dictionaries [{ "name": "tag name" }]
As you mentioned, the validation check fails if the tag doesn't exist. I managed to overcome this by subclassing SlugRelatedField and overriding "to_internal_value" method. In the original implementation this method tries to get an object from the queryset, and if an object doesn't exist it fails the validation. So instead of calling "get" method, I'm calling "get_or_create":
class CustomSlugRelatedField(serializers.SlugRelatedField):
def to_internal_value(self, data):
try:
obj, created = self.get_queryset().get_or_create(**{self.slug_field: data})
return obj
except (TypeError, ValueError):
self.fail('invalid')
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