Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django REST: Nested object add on create (POST) not just update (PUT)

Using Django rest

Below is how I had my serializer.py.

class ProfileSerializer(serializers.ModelSerializer):


    class Meta:
        model = Profile
        fields = ('id', 'name', 'active', 'type')

Type is a flatview

I then changed it so 'type' was nested for each profile like this...

class TypeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Type
        fields = ('id', 'name', 'active')

class ProfileSerializer(serializers.ModelSerializer):

    type = TypeSerializer()

    class Meta:
        model = Profile
        fields = ('id', 'name', 'active', 'type'')

now this works perfect, but I can now only update 'type' when in profile detail it's now read-only.

How can I add type when creating a new profile and still keep this nested view?

I hope I have explained this clearly .

UPDATE:

Ok, I just read this:

Note: Nested serializers are only suitable for read-only representations, as there are cases where they would have ambiguous or non-obvious behavior if used when updating instances. For read-write representations you should always use a flat representation, by using one of the RelatedField subclasses.

So that makes sense. So I changed it to....

type = serializers.PrimaryKeyRelatedField()

That puts it back in the POST and work, but it's a shame, can I not represent 'type' with ID and the name so it makes more sense to the end user?

like image 514
jason Avatar asked Feb 20 '13 11:02

jason


3 Answers

This is now supported (I am using version 2.3.6 but it might be introduced earlier). You can use it in the serializer direcly like this:

class SongSerializer(serializers.ModelSerializer):
    class Meta:
        model = Song

class AlbumSerializer(serializers.ModelSerializer):
    songs = SongSerializer(many=True)

    class Meta:
        model = Album

Hope it helps :)

like image 153
Rares Musina Avatar answered Nov 13 '22 04:11

Rares Musina


Full support of writable nested serializers is a work in progress, but in the mean time one solution is to override the create method in the view in each case:

class FooListCreateView(ListCreateAPIView):
    model = Foo
    serializer_class = FooSerializer

    def create(self, request, *args, **kwargs):
        data=request.DATA

        f = Foo.objects.create()

        # ... create nested objects from request data ...  

        # ...
        return Response(serializer.data, 
                        status=status.HTTP_201_CREATED,
                        headers=headers)

Probably not ideal, but it works for me until the proper way comes along.

like image 22
Rob Agar Avatar answered Nov 13 '22 03:11

Rob Agar


I had the same problem in django-rest-framework, and I have created a View for doing this real quick, you can find it in this gist: https://gist.github.com/edulix/5311365

Basic usage of CRUDManyToManyView is the following:

views.py

from models import Project
from serializers import TaskSerializer
from lib.crudmanytomanyview import CRUDManyToManyView

class ProjectTasks(CRUDManyToManyView):
    model = Project
    field_name = 'tasks'
    serializer_class = TaskSerializer

urls.py

from django.conf.urls import patterns, url
import views

urlpatterns = patterns(
'',
    url(r'^projects/(?P<pk>[0-9]+)/tasks/((?P<field_pk>[0-9]+)/)?$',
        views.ProjectTasks.as_view()),
)

serializers.py

from rest_framework import serializers
from models import Task

class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = ('id', 'name')

models.py

from django.db import models

class Task(models.Model):
    name = models.CharField(max_length=140, blank=False, null=False)

class Project(models.Model):
    name = models.CharField(max_length=140, blank=False, null=False)
    tasks = models.ManyToManyField(Task, related_name='projects')

Then you can do things like:

  • GET projects/12/tasks/ will list the project tasks
  • POST projects/12/tasks/1/ which will add the task 1 to the list of project 12 tasks (task 1 must already exist)
  • DELETE projects/12/tasks/1/ which will remove the task 1 from the list of project 12 tasks
like image 6
edulix Avatar answered Nov 13 '22 04:11

edulix