Search code examples
pythondjangodjango-modelsdjango-rest-frameworkmany-to-many

How to create and handle a related field in django with react?


I have created 2 models - Tags and Startups. Startups has a tags field with a ManytoMany relationship with Tag.

Models.py file -

from django.db import models from django_extensions.db.fields import AutoSlugField from django.db.models import CharField, TextField, DateField, EmailField, ManyToManyField

class Tag(models.Model):

    name = CharField(max_length=31, unique=True, default="tag-django")
    slug = AutoSlugField(max_length=31, unique=True, populate_from=["name"])

    def __str__(self):
        return self.name


class Startup(models.Model):

    name = CharField(max_length=31, db_index=True)
    slug = AutoSlugField(max_length=31, unique=True, populate_from=["name"])
    description = TextField()
    date_founded = DateField(auto_now_add=True)
    contact = EmailField()
    tags = ManyToManyField(Tag, related_name="tags")

    class Meta:
        get_latest_by = ["date_founded"]

    def __str__(self):
        return self.name

My serializers.py file -

from rest_framework.serializers import HyperlinkedModelSerializer, PrimaryKeyRelatedField, ModelSerializer
from .models import Startup, Tag


class TagSerializer(HyperlinkedModelSerializer):
    class Meta:
        model = Tag
        fields = "__all__"
        extra_kwargs = {
            "url": {
                "lookup_field": "slug",
                "view_name": "tag-api-detail"
            }
        }


class StartupSerializer(HyperlinkedModelSerializer):
    tags = TagSerializer(many=True, read_only=True)

    class Meta:
        model = Startup
        fields = "__all__"
        extra_kwargs = {
            "url": {
                "lookup_field": "slug",
                "view_name": "startup-api-detail"
            }
        }

My viewsets.py file -

from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from .serializers import TagSerializer, StartupSerializer
from .models import Tag, Startup
from rest_framework.decorators import action
from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_200_OK, HTTP_204_NO_CONTENT
from django.shortcuts import get_object_or_404


class TagViewSet(ModelViewSet):

    queryset = Tag.objects.all()
    serializer_class = TagSerializer
    lookup_field = "slug"


class StartupViewSet(ModelViewSet):

    serializer_class = StartupSerializer
    queryset = Startup.objects.all()
    lookup_field = "slug"


    @action(detail=True, methods=["HEAD", "GET", "POST"], url_path="tags")
    def tags(self, request, slug=None):
        
        startup = self.get_object()
        print(startup)
        if request.method in ("HEAD", "GET"):
            s_tag = TagSerializer(
                startup.tags,
                many=True,
                context={"request": request}
            )
            return Response(s_tag.data)

        tag_slug = request.data.get("slug")
        if not tag_slug:
            return Response(
                "Slug of Tag must be specified",
                status=HTTP_400_BAD_REQUEST
            )
        tag = get_object_or_404(Tag, slug__iexact=tag_slug)
        startup.tags.add(tag)
        return Response(HTTP_204_NO_CONTENT)

I can create startups and relate tags through my django admin. I have a dropdown list with all the created tags in my django admin from where I can relate tags with startups.

I do not understand how can I relate tags with startup when creating startup in react. I tried posting data in this form -

{
    "name": "Test Startup 1",
    "description": "First Desc",
    "contact": "[email protected]",
    "tags": [
        {
            "url": "http://127.0.0.1:8000/api/v1/tag/first-tag/",
            "name": "First Tag",
            "slug": "first-tag"
        }
    ]
}

I cannot get the tags to relate with startup.

How to handle related fields?


Solution

  • You have multiple options.

    If you want to perform association (meaning tags and startup already exists before making the request, kinda what happen with django-admin), you could create a new serializer that have a different field for tags, accepting ids instead of the nested serializer.

    If you want to have nested creation/edition, you could checkout WritableNestedSerializer from here. Because the doc says that it does not handle such usecase, because they might be many way to perform this depending on your business logic, but provide ways to perform that yourself here

    Another approach would be to have a route with nested ressources (with nested routers for instance) so when you POST a tag in /startup/1/tags/ you create AND associate your tag automatically, like you did.

    Now, concerning your endpoint, you need to get the data of your request and pass it to the tag serializer. This serializer will then validate your data, and if it is valid, you can perform tag creation.

    To do that, you can do something like:

    tag_data = request.data
    tag_serializer = TagSerializer(data=request.data)
    tag_serializer.is_valid()
    tag = tag_serializer.save()
    tag.startup_set.add(startup)
    

    Adding the relationship have to be done in two step. You should use a transaction to ensure it is correctly created. Also, instead of adding this logic in your view, you should override your TagSerializer/StartupSerializer create method to do that.