I'm building a web app and trying to send Post data as FormData to a Django Rest Framework Serializer. In the request.data I see that all the Post data is there, however after validating and saving the serializer it seems like some of the data did not get passed into validated_data.
Views.py
@api_view(["GET","POST"])
def api_list(request):
if request.method=="GET":
data = Recipe.objects.all()
serializer = RecipeSerializer(data, many=True)
return Response(serializer.data)
elif request.method=="POST":
print("POST recieved")
print (request.data) <----See below
serializer = RecipeSerializer(data=request.data)
print("Validating..")
if serializer.is_valid():
print("validated!")
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
print (serializer.errors)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
request.data
<QueryDict:
{'description': ['"gfdgdfg"'],
'name': ['"fdgdfgdf"'],
'ingredients': [
'{"name":"dfgdfg","amount":"gdfgd"}',
'{"name":"fdgdfg","amount":"dfgdf"}',
'{"name":"dfgdfgdf","amount":"gdfgdf"}'
],
'directions': [
'{"content":"gdfgfd"}',
'{"content":"gdfgdfg"}',
'{"content":"dfgdfdfg"}'
],
'image': [
<InMemoryUploadedFile: luisana-zerpa-MJPr6nOdppw-unsplash.jpg (image/jpeg)>
]
}>
serializer.py
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = ('name', 'amount')
class DirectionSerializer(serializers.ModelSerializer):
class Meta:
model = Direction
fields = ('content',)
class RecipeSerializer(serializers.ModelSerializer):
owner = serializers.StringRelatedField()
ingredients = IngredientSerializer(many=True, read_only=False)
directions = DirectionSerializer(many=True, read_only=False)
class Meta:
model = Recipe
fields = (
'id',
'name',
'owner',
'image',
'description',
'created_at',
'ingredients',
'directions',
)
def create(self, validated_data):
print (validated_data) <----See Below
has_ingredients = validated_data.get("ingredients")
has_directions = validated_data.get("directions")
if has_ingredients:
ingredient_data = validated_data.pop('ingredients')
if has_directions:
direction_data = validated_data.pop('directions')
recipe_name = validated_data.get('name')
recipe_name = recipe_name.replace('"','')
recipe_description = validated_data.get('description')
recipe_description = recipe_description.replace('"','')
recipe = Recipe.objects.create(name=recipe_name, description=recipe_description, image=validated_data.get('image'))
if has_ingredients:
for ingredient in ingredient_data:
Ingredient.objects.create(recipe=recipe, name=ingredient.get("name"), amount=ingredient.get("amount"))
if has_directions:
for direction in direction_data:
Direction.objects.create(recipe=recipe, content=direction.get("content"))
return recipe
validated_data NOTE: I can only get this if I add "required=False" for ingredient and directions else it just return a 404err
{
'name': '"fdgdfgdf"',
'image': <InMemoryUploadedFile: luisana-zerpa-MJPr6nOdppw-unsplash.jpg (image/jpeg)>,
'description': '"gfdgdfg"'
}
I tried looking into overiding the .is_valid() method on the serializers but I couldn't find anything on the official documentation. If I used the Postman app to Post data everything works however whenever I sent data from my frontend this happens. I wonder if I has anything to do with how i'm sending the data but I don't want to include too much unnecessary.
Thanks in advance for any help :)
CodePudding user response:
I believe you are forgetting to send the ingredients and directions to the new recipe instance.
This is why your POST endpoint only works when they are not required
You can create the relationship with Recipe after you create the Ingredient instance
ingredient = Ingredient.objects.create(**inputs)
recipe.ingredient = ingredient
recipe.save()
Same goes for Direction:
direction = Direction.objects.create(**inputs)
recipe.direction = direction
recipe.save()
They were already validated in IngredientSerializer and DirectionSerializer, so you can safely create the relationship with Recipe
Quick tip: If you have a flag called has_ingredients, you probably mean that you can have recipies without ingredients, in this case, the field should be required=False
If you need the ingredients and directions filelds to be required, you will need to create the recipe with all the inputs, for example:
recipe = Recipe.objects.create(
name=recipe_name,
description=recipe_description,
image=validated_data.get('image'),
ingredient=ingredient,
direction=direction
)
Another way around: you can create a Recipe instance first with some fields, then add the ingredient and direction and finally call .save() to register the new database entry.
CodePudding user response:
I've Figured out the Answer!
The problem all along was in how I was Posting the data.
Originally I was sending the post data for "ingredients" and "directions like this"...
ingredients.forEach((ingredient) => {
formData.append('ingredients',{
'name':ingredients.name,
'amount':ingredient.amount
});
});
I had a forEach loop for "directions" as well and it pretty much followed the same pattern.
The Correct way to post a List of Objects using multipart/form-data should be like this
ingredients.forEach((ingredient, index) => {
formData.append(`ingredients[${index}]name`, JSON.stringify(ingredient.name));
formData.append(`ingredients[${index}]amount`, JSON.stringify(ingredient.amount))
});
The difference is that when posting a list we have to detail the the exact path to for each item in the list.
e.g formData.append(ingredients[${index}]name
, JSON.stringify(ingredient.name));
That's about all I know, if anyone has more information on what we need to do that please add on, since there is not much information on why this is the case.