Missing data from validated_data in django rest framwork

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 :)

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
)        

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.

Back to Top