Django nested serializers – Direct assignment to the reverse side of a related set is prohibited

Question:

I want to implement my own create function on my serializer but I’m getting the following error:

TypeError: Direct assignment to the reverse side of a related set is prohibited. Use lines.set() instead.

Internal Server Error: /api/cheatsheets/
Traceback (most recent call last):
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/rest_framework/mixins.py", line 19, in create
    self.perform_create(serializer)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/rest_framework/mixins.py", line 24, in perform_create
    serializer.save()
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/rest_framework/serializers.py", line 212, in save
    self.instance = self.create(validated_data)
  File "/home/christian-sama/Code/cheatsheet-app/api/serializers.py", line 35, in create
    Section.objects.create(cheatsheet=cheatsheet, **section)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/django/db/models/query.py", line 669, in create
    obj = self.model(**kwargs)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/django/db/models/base.py", line 562, in __init__
    _setattr(self, prop, value)
  File "/home/christian-sama/Code/cheatsheet-app/venv/lib/python3.10/site-packages/django/db/models/fields/related_descriptors.py", line 595, in __set__
    raise TypeError(
TypeError: Direct assignment to the reverse side of a related set is prohibited. Use lines.set() instead.

My models.py

from django.db import models

class Cheatsheet(models.Model):
    title = models.CharField(max_length=500)
    description = models.CharField(max_length=500)
    score = models.IntegerField()

    def __str__(self):
        return self.title

class Section(models.Model):
    title = models.CharField(max_length=500)
    description = models.CharField(max_length=500)
    cheatsheet = models.ForeignKey(Cheatsheet, on_delete=models.CASCADE, related_name='sections', null=True, blank=True)

    def __str__(self):
        return self.title

class Line(models.Model):
    description = models.CharField(max_length=500)
    snippet = models.CharField(max_length=500)
    section = models.ForeignKey(Section, on_delete=models.CASCADE, related_name='lines', null=True, blank=True)

    def __str__(self):
        return self.description

My serializers.py

from rest_framework import serializers
from .models import Cheatsheet, Section, Line

class LineSerializer(serializers.ModelSerializer):
    class Meta:
        model = Line
        fields = "__all__"

class SectionSerializer(serializers.ModelSerializer):
    lines = LineSerializer(many=True)

    class Meta:
        model = Section
        fields = "__all__"

    def create(self, validated_data):
        lines_data = validated_data.pop('lines')
        section = Section.objects.create(**validated_data)
        for line in lines_data:
            Line.objects.create(section=section, **line)
        return section

class CheatsheetSerializer(serializers.ModelSerializer):
    sections = SectionSerializer(many=True)

    class Meta:
        model = Cheatsheet
        fields = "__all__"

    def create(self, validated_data):
        sections_data = validated_data.pop('sections')
        print(sections_data)
        cheatsheet = Cheatsheet.objects.create(**validated_data)
        print(cheatsheet)
        for section in sections_data:
            Section.objects.create(cheatsheet=cheatsheet, **section)
        return cheatsheet

My views.py

from api.serializers import CheatsheetSerializer
from .models import Cheatsheet
from rest_framework import viewsets

class CheatsheetViewSet(viewsets.ModelViewSet):
    queryset = Cheatsheet.objects.all()
    serializer_class = CheatsheetSerializer

And this is the json I’m sending in the request:

{
  "title": "Cheatsheet title",
  "description": "Cheatsheet description",
  "score": 24,
  "sections": [
    {
      "title": "Section title",
      "description": "Section description",
      "lines": [
        {
          "description": "Line description",
          "snippet": "line snippet"
        },
        {
          "description": "Line description2",
          "snippet": "line snippet2"
        }
      ]
    }
  ]
}

I’ve been following this tutorial but can’t figure out why my code is failing. Other answers suggest using set() instead of get(), but I don’t think that’s my case.

Answers:

The way you handled line data in SectionSerializer needs to also be done on CheatsheetSerializer, so something like:

class CheatsheetSerializer(serializers.ModelSerializer):
    # ...
    for section in sections_data:
        # pop the lines
        lines_data  = section.pop('lines')
        section = Section.objects.create(cheatsheet=cheatsheet, **section)

        # and create them after the section:
        for line in lines_data:
            Line.objects.create(section=section, **line)
Answered By: Brian Destura