15

I have a simple DRF REST API that I want to use to create blog articles. I want to be able to add tags to those blog articles so users can search tags and see related articles. However, the tags may not exist yet. I have created an Article Model with a ForeignKey field to a Tag Model like this:

class Tag(models.Model):

    name = models.CharField(max_length=32)

    def _str__(self):
        return self.name

    class Meta:
        ordering = ('name',)

class Article(models.Model):

    title = models.CharField(max_length=256)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    content = models.TextField()
    date = models.DateTimeField(auto_now_add=True)
    tags = models.ForeignKey(Tag, on_delete=models.CASCADE, blank=True, default=None)

    def __str__(self):
        return self.title

    class Meta:
        ordering = ('date', 'id')

Ideally what I want is to be able to POST a new Article with a set of tags, and if any of the tags don't exist, create them in the DB. However, as it is currently, the tags need to already exist to be added to the Article. Visually, DRF shows this as a dropdown that is populated with pre-existing tags:

DRF Interface

How can I add or create multiple Tags from my Article API endpoint?

EDIT: As requested, I've added my views.py

views.py:

from api.blog.serializers import ArticleSerializer, TagSerializer
from rest_framework import viewsets

# /api/blog/articles
class ArticleView(viewsets.ModelViewSet):

    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

# /api/blog/tags
class TagView(viewsets.ModelViewSet):

    queryset = Tag.objects.all()
    serializer_class = TagSerializer

For completeness, here are my serializers from my REST API's serializers.py.

serializers.py:

class ArticleSerializer(serializers.ModelSerializer):

    class Meta:
        model = Article
        fields = '__all__'


class TagSerializer(serializers.ModelSerializer):

    class Meta:
        model = Tag
        fields = '__all__'

urls.py:

from rest_framework import routers

router = routers.DefaultRouter()
router.register('articles', views.ArticleView)
router.register('tags', views.TagView)

urlpatterns = [
    path('', include(router.urls)),
]
2
  • Your models are fine, but you might need to do some work on your views. Why not post your views.py? Commented Oct 28, 2018 at 1:46
  • Good idea @RedCricket. I've added my views. If they were regular CBVs I'd have a fair idea about how to do it, but I'm kinda new to DRF. Commented Oct 28, 2018 at 1:50

2 Answers 2

21

Overriding the create() method of the serializer as

class ArticleSerializer(serializers.ModelSerializer):
    tags = serializers.CharField()

    class Meta:
        model = Article
        fields = '__all__'

    def create(self, validated_data):
        tag = validated_data.pop('tags')
        tag_instance, created = Tag.objects.get_or_create(name=tag)
        article_instance = Article.objects.create(**validated_data, tags=tag_instance)
        return article_instance
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks! Your answer has gotten me a lot closer! However, I was hoping to create multiple tag objects (each with a single word as the name). To extend your answer to do this, I think I need to move the ForeignKey from Article to Tag and change some other stuff around. Since I think their might have been some confusion about what the expected behaviour was meant to be, I'll mark your solution as correct, and then post mine as well when I get it working.
I think, M2M relation would be better for Tags
Your Article relation to tag need to be a ManyToManyField so you could add multiple tag objects.
2

Okay, thanks to @JPG for their help. This is what I've ended up with. It allows users to add space delimited tags into a CharField on the /api/blog/article endpoint. When a POST request is performed, the tags are split on spaces, get_or_create()d (for this to work I needed to make Tag.name the primary key), and then added to the Article with article.tags.set(tag_list). As @JPG and @Martins suggested, a ManyToManyField() was the best way to do this.

Here is my full code:

serializers.py:

class ArticleSerializer(serializers.ModelSerializer):

    class TagsField(serializers.CharField):

        def to_representation(self, tags):
            tags = tags.all()
            return "".join([(tag.name + " ") for tag in tags]).rstrip(' ')


    tags = TagsField()

    class Meta:
        model = Article
        fields = '__all__'

    def create(self, validated_data):

        tags = validated_data.pop('tags') # Removes the 'tags' entry
        tag_list = []
        for tag in tags.split(' '):
            tag_instance, created = Tag.objects.get_or_create(name=tag)
            tag_list += [tag_instance]

        article = Article.objects.create(**validated_data)
        print(tag_list)
        article.tags.set(tag_list)
        article.save()
        return article


class TagSerializer(serializers.ModelSerializer):

    class Meta:
        model = Tag
        fields = '__all__'

Note that I had to create a custom TagField() and override to_representation(). This is because if I used a regular serializer.CharField() tags were displayed as: "Blog.tag.None" instead of the tag values, like this:

Blog.tag.None

models.py:

class Tag(models.Model):

    name = models.CharField(max_length=32, primary_key=True)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ('name',)

class Article(models.Model):

    title = models.CharField(max_length=256)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    content = models.TextField()
    date = models.DateTimeField(auto_now_add=True)
    tags = models.ManyToManyField(Tag)

    def __str__(self):
        return self.title

    class Meta:
        ordering = ('date', 'id')

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.