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?
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.