Consider a Listing model that has an associated Category. I want to create a new Listing for an existing Category by doing a POST with data:
{"title": "myapp", "category": {"name": "Business"}}
, where title
is the title of the Listing that should be created, and Business
is the name of an existing category to use for this new listing.
When I try to make such a request and instantiate the ListingSerializer
for this, I get an error indicating that the Category name must be unique - I don't want to create a new Category, but use an existing one instead. I've tried setting the validators on the category field to []
, but that didn't change the behavior.
I can use a SlugRelatedField
, but that forces my request data to look more like {"title": "myapp", "category": "Business"}
, which isn't what I want. I tried using the source
argument for the SlugRelatedField
to specify a nested relationship, but that didn't work either:
category = serializers.SlugRelatedField(
slug_field='category.name',
queryset=models.Category.objects.all()
)
yields:
"category": [
"Object with name={'name': 'Business'} does not exist."
]
models.py:
import django.contrib.auth
from django.db import models
from django.conf import settings
class Profile(models.Model):
display_name = models.CharField(max_length=255)
user = models.OneToOneField(settings.AUTH_USER_MODEL)
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
description = models.CharField(max_length=200)
class Listing(models.Model):
title = models.CharField(max_length=50, unique=True)
category = models.ForeignKey(Category, related_name='listings', null=True)
owners = models.ManyToManyField(
Profile,
related_name='owned_listings',
db_table='profile_listing',
blank=True
)
serializers.py:
import logging
import django.contrib.auth
from rest_framework import serializers
import myapp.models as models
logger = logging.getLogger('mylogger')
class ShortUserSerializer(serializers.ModelSerializer):
class Meta:
model = django.contrib.auth.models.User
fields = ('username', 'email')
class ProfileSerializer(serializers.ModelSerializer):
user = ShortUserSerializer()
class Meta:
model = models.Profile
fields = ('user', 'display_name')
read_only = ('display_name',)
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = models.Category
fields = ('name', 'description')
read_only = ('description',)
class ListingSerializer(serializers.ModelSerializer):
owners = ProfileSerializer(required=False, many=True)
# TODO: how to indicate that this should look for an existing category?
category = CategorySerializer(required=False, validators=[])
class Meta:
model = models.Listing
depth = 2
def validate(self, data):
logger.info('inside ListingSerializer validate')
return data
def create(self, validated_data):
logger.info('inside ListingSerializer.create')
# not even getting this far...
views.py:
import logging
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
import django.contrib.auth
from rest_framework import viewsets
from rest_framework.response import Response
import myapp.serializers as serializers
import myapp.models as models
# Get an instance of a logger
logger = logging.getLogger('mylogger')
class CategoryViewSet(viewsets.ModelViewSet):
queryset = models.Category.objects.all()
serializer_class = serializers.CategorySerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = django.contrib.auth.models.User.objects.all()
serializer_class = serializers.ShortUserSerializer
class ProfileViewSet(viewsets.ModelViewSet):
queryset = models.Profile.objects.all()
serializer_class = serializers.ProfileSerializer
class ListingViewSet(viewsets.ModelViewSet):
logger.info('inside ListingSerializerViewSet')
queryset = models.Listing.objects.all()
serializer_class = serializers.ListingSerializer
Full example: https://github.com/arw180/drf-example
TurnCategorySerializer.create
into anupdate_or_create
method onname
I recommend looking at the
DRF
source when ever you need to create custom functionality.Related question answered by the creator of
DRF
: django-rest-framework 3.0 create or update in nested serializerEdit
So I was still in the DRF 2 mindset where nested writable fields are handled automatically. You can read up on the subject here: http://www.django-rest-framework.org/topics/3.0-announcement/
I've tested the following code and it works:
Edit
The correct way of using
SlugRelatedField
is like this, in case you were wondering:This isn't ideal, but I did find a solution that solved my problem (I'm waiting to accept it as the answer, hoping someone else can do better). There are two parts:
First, use the
partial=True
argument when initializing theListingSerializer
( http://www.django-rest-framework.org/api-guide/serializers/#partial-updates). Then use the serializer'svalidate
method to get the actual model instance corresponding to the input data.Second, explicitly remove the validators for the
name
field in theCategorySerializer
. This is especially lousy because it effects more than just theListingSerializer
.Leaving out either piece will result in the validation errors being thrown at the time the serializer is instantiated.
modifications to views.py:
modifications to serializers.py:
I'll wait a bit to accept this as the answer in case someone can come up with a better solution (like how to make the
source
argument work properly with aSlugRelatedField
) - I have a working example using the solution above at https://github.com/arw180/drf-example if you want to experiment. I'd also love to hear comments regarding why theextra_kwargs
stuff is necessary in theCategorySerializer
- why isn't instantiating it like this:category = CategorySerializer(required=False, validators=[])
sufficient (in theListingSerializer
)? UPDATE: I believe that doesn't work because the unique validator is added automatically from the DB constraints and run regardless of any explicit validators set here, as explained in this answer: http://iswwwup.com/t/3bf20dfabe1f/python-order-of-serializer-validation-in-django-rest-framework.htmlI had similar problem: I needed to check if nested serializer (
CategorySerializer
) exists if yes to use it and if not - create it from nesting serializer (ListingSerializer
). The solution of @demux totally worked for me only if I didn't use custom validation for a field in nested serializer (the field by which I would check from nesting serializer if this instance exists). So I addedcreate()
method to nested serializer and @demux customupdate_or_create_category()
,create()
,update()
forListingSerializer
worked perfectly.