Here's my code:
Models
class Recipe(models.Model):
name = models.CharField(max_length=50, unique=True)
ingredient = models.ManyToManyField(Ingredient)
class Ingredient(models.Model):
name = models.CharField(max_length=50, unique=True)
View
class RecipeDetailAPIView(RetrieveUpdateDestroyAPIView):
permission_classes = (IsAdminOrReadOnly,)
serializer_class = RecipeSerializer
queryset = Recipe.objects.all()
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def perform_update(self, serializer):
serializer.save(updated_by_user=self.request.user)
Serializers
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = [
'id',
'name',
]
class RecipeSerializer(serializers.ModelSerializer):
ingredient = IngredientSerializer(many=True, read_only=False)
class Meta:
model = Recipe
fields = [
'id',
'name',
'ingredient',
]
I'm starting with the following Recipe object:
{
"id": 91
"name": "Potato Salad"
"ingredient": [
{
"id": 5,
"name": "Potato"
}
]
}
Now, I am attempting to update that object by putting the following JSON object to the IngredientSerializer:
{
"id": 91
"name": "Potato Salad"
"ingredient": [
{
"id": 5,
"name": "Potato"
},
{
"id": 6,
"name": "Mayo"
}
]
}
What I want is it to recognize that the relationship to Potato already exists and skip over that, but add a relationship to the Mayo object. Note that the Mayo object already exists in Ingredients, but is not yet tied to the Potato Salad object.
What actually happens is the Serializer tries to create a new Ingredient object and fails because "ingredient with this name already exists."
How do I accomplish this?
DRF does not have any automatic "write" behavior for nested serializers, precisely because it does not know things like how to go about updates in the scenario you mentioned. Therefore, you need to write your own update
method in your RecipeSerializer
.
class IngredientSerializer(serializers.ModelSerializer):
def validate_name(self, value):
# manually validate
pass
class Meta:
model = Ingredient
fields = ['id', 'name']
extra_kwargs = {
'name': {'validators': []}, # remove uniqueness validation
}
class RecipeSerializer(serializers.ModelSerializer):
ingredient = IngredientSerializer(many=True, read_only=False)
def update(self, instance, validated_data):
ingredients = validated_data.pop('ingredient')
# ... logic to save ingredients for this recipe instance
return instance
class Meta:
model = Recipe
fields = ['id', 'name', 'ingredient']
Relevant DRF documentation:
Saving Instances
Writable Nested Serializer
Updating nested serializers
Update:
If DRF validation fails for the uniqueness constraint in the name field, you should try removing validators for that field.
Alternative solution: Only use full serializer as read only field
You can change you RecipeSerializer to the following:
class RecipeSerializer(serializers.ModelSerializer):
ingredient_details = IngredientSerializer(many=True, read_only=True, source='ingredient')
class Meta:
model = Recipe
fields = ['id', 'name', 'ingredient', 'ingredient_details']
And that's it. No need to override update
or anything. You'll get the detailed representation when you get a recipe, and you can just PUT with the ingredient ids when updating. So your json when updating will look something like this:
{
"id": 91
"name": "Potato Salad"
"ingredient": [5, 6]
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With