I'm trying to figure out how to save related models using Django REST framework.
In my app I have a model Recipe
with 2 related models: RecipeIngredient
and RecipeStep
. A Recipe
object MUST have at least 3 related RecipeIngredient
and 3 RecipeStep
. Before the introduction of the REST framework I was using a Django CreateView
with two formsets and the save process was the following (follow the code from form_valid()
):
def save_formsets(self, recipe):
for f in self.get_formsets():
f.instance = recipe
f.save()
def save(self, form):
with transaction.atomic():
recipe = form.save()
self.save_formsets(recipe)
return recipe
def formsets_are_valid(self):
return all(f.is_valid() for f in self.get_formsets())
def form_valid(self, form):
try:
if self.formsets_are_valid():
try:
return self.create_ajax_success_response(form)
except IntegrityError as ie:
return self.create_ajax_error_response(form, {'IntegrityError': ie.message})
except ValidationError as ve:
return self.create_ajax_error_response(form, {'ValidationError': ve.message})
return self.create_ajax_error_response(form)
Now I have my RecipeViewSet
:
class RecipeViewSet(ModelViewSet):
serializer_class = RecipeSerializer
queryset = Recipe.objects.all()
permission_classes = (RecipeModelPermission, )
which uses RecipeSerializer
:
class RecipeSerializer(serializers.ModelSerializer):
class Meta:
model = Recipe
fields = (
'name', 'dish_type', 'cooking_time', 'steps', 'ingredients'
)
ingredients = RecipeIngredientSerializer(many=True)
steps = RecipeStepSerializer(many=True)
and these are the related serializers:
class RecipeIngredientSerializer(serializers.ModelSerializer):
class Meta:
model = RecipeIngredient
fields = ('name', 'quantity', 'unit_of_measure')
class RecipeStepSerializer(serializers.ModelSerializer):
class Meta:
model = RecipeStep
fields = ('description', 'photo')
Now... how I'm supposed to validate related models (RecipeIngredient
and RecipeStep
) and save them when RecipeViewSet
's create()
method is called? (is_valid()
in RecipeSerializer
is actually ignoring nested relationships and reporting only errors related to the main model Recipe
).
At the moment I tried to override the is_valid()
method in RecipeSerializer
, but is not so simple... any idea?
I was dealing with similiar issue this week and I found out, that django rest framework 3 actually supports nested writable serialisation (http://www.django-rest-framework.org/topics/3.0-announcement/#serializers in subchapter Writable nested serialization.)
Im not sure if nested serialisers are writable be default, so I declared them:
ingredients = RecipeIngredientSerializer(many=True, read_only=False)
steps = RecipeStepSerializer(many=True, read_only=False)
and you should rewrite your create methon inside RecipeSerializer:
class RecipeSerializer(serializers.ModelSerializer):
ingredients = RecipeIngredientSerializer(many=True, read_only=False)
steps = RecipeStepSerializer(many=True, read_only=False)
class Meta:
model = Recipe
fields = (
'name', 'dish_type', 'cooking_time', 'steps', 'ingredients'
)
def create(self, validated_data):
ingredients_data = validated_data.pop('ingredients')
steps_data = validated_data.pop('steps')
recipe = Recipe.objects.create(**validated_data)
for ingredient in ingredients_data:
#any ingredient logic here
Ingredient.objects.create(recipe=recipe, **ingredient)
for step in steps_data:
#any step logic here
Step.objects.create(recipe=recipe, **step)
return recipe
if this structure Step.objects.create(recipe=recipe, **step) wont work, maybe you have to select data representeng each field separatly from steps_data / ingredients_data.
This is link to my earlier (realted) question/answer on stack: How to create multiple objects (related) with one request in DRF?