2

I am making API for cook book with Django Rest Framework. I don't know how to design ingredients model to make data to be like this:

{
        "id": 1,
        "name": "spaghetti",
        "recipe": "recipe",
        "ingredients": [
            [{name:'pasta',amount:100},{name:'tomato',amount:200},{...}]
        ],    
    }

My model:

class Meal(models.Model):
    name = models.TextField()    
    recipe = models.TextField()
    ingredients = ?

Also how to serialize this field?

2
  • 1
    Is this a project for you to learn or a real product? I'm asking because it depends on the situation, to be honest. Personally, I would rather use a separate table for ingredients, and another table that has a reference (FK) to ingredient, meal and stores the amount. This way, you'll leverage the database integrity (only valid ingredient can be added etc), you'll be able to analyze which ingredients are used in which meal with a single query, and I think it'll look better. JSONField is more like a field that stores non-critical data for a model. Commented Oct 31, 2021 at 12:11
  • I am building this project for my portfolio and i don't see good approach how to connect Ingredient field or model with his amont in Meal model. Actually i have separate model for Ingredients to filter data. But this amout is real pain. Amout as FK in db will store simple integers values so its actually a good idea? Commented Oct 31, 2021 at 15:23

4 Answers 4

1

You can create a separate model for ingredient.

Many to many relation will be the best for me, because of one meal can have many ingredients and in the opposite way one ingredient can be used to make many meals.

According to django docs, in your case:

models.py

from django.db import models

class Ingredient(models.Model):
    name = models.CharField(max_length=90)
    amount = models.FloatField()

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name

class Meal(models.Model):
    name = models.CharField(max_length=100)
    recipe = models.CharField(max_length=100)
    ingredients = models.ManyToManyField(Ingredient)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name

serializers.py

class IngredientSerializer(serializers.ModelSerializer):
    class Meta:
        model = Ingredient
        fields = '__all__'


class MealSerializer(serializers.ModelSerializer):
    ingredients = IngredientSerializer(read_only=True, many=True)

    class Meta:
        model = Meal
        fields = '__all__'
Sign up to request clarification or add additional context in comments.

8 Comments

I think this is the best way to go. Maybe having a separate Ingredient model (without the amount) and another table that stores the ingredient and amount would be better. Otherwise, there'll be lots of the same ingredient with different amounts in db, and you won't be able to customize the ingredient usage (maybe ingredient usage order, or any other extra info that might be needed in future)
I tired this but it's not good solution. Think of that: we have 2 meals, one needs 100 grams of salt other one needs 200 grams. But in Ingredient field, amout can have only one value. Is there a way to connect Ingredient model with his amount but it will be uniqe in every Meal model?
So you should create 2 ingredients with a different value. Also it can probably be separated into a separate table as @ÇağatayBarın said. It is your decision how tu store that data.
@non It's always your decision, of course. Just trying to help you to have the best practice for this. In that another model that I mentioned (let's call it IngredientUsage), it'll have a FK to Ingredient, FK to Meal, and IntegerField(or DecimalField, up to you) for amount. This way, you won't have to store an ingredient when it's used in each meal, you'll have a link to the ingredient so that you can use that info in future, or you can add additional data to the ingredient usage (like fry for x minutes, or usage order).
@ÇağatayBarın Thank you very much for your help. Your solution is best, i couldn't figure what you mean in first, but it works perfectly with simple relations. I can fetch all relevant data with one request. Thank u :)
|
0

I believe what you are looking for is JsonBField

from django.contrib.postgres.fields.jsonb import JSONField as JSONBField
ingredients = JSONBField(default=list,null=True,blank=True)

this should do what you expect, have a nice day

edit: thanks for the update as @Çağatay Barın mentioned below FYI, it is deprecated, Use django.db.models.JSONField instead.see the Doc

2 Comments

But what if i dont use PostgreSQL?
FYI, it is deprecated since version 3.1. You might want to change the answer to support the latest version. Use django.db.models.JSONField instead. Docs: docs.djangoproject.com/en/3.2/ref/contrib/postgres/fields/…
0

from , Django comes with JSONField

class Meal(models.Model):
    name = models.TextField()
    recipe = models.TextField()
    ingredients = models.JSONField()

Comments

0

There are two approaches to have this outcome [2] having another model:

  1. using WritableNestedModelSerializer.
  2. Overwriting the create() method of your serializer.

1st example, using WritableNestedModelSerializer:

# models.py --------------------------

class Ingredient(models.Model):
    # model that will be related to Meal.
    name = models.CharField(max_lenght=128)
    amount = models.IntergerField()
    
    def __str__(self):
        return str(self.name)

class Meal(models.Model):
    # meal model related to Ingredient.
    name = models.TextField()    
    recipe = models.TextField()
    ingredients = models.ForeigKey(Ingredient)

    def __str__(self):
        return str(self.name)

# serializers.py ----------------------

class IngredientSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = Ingredient
        fields = '__all__'

class MealSerializer(WritableNestedModelSerializer, serializers.ModelSerializer):

    ingredients_set = IngredientSerializer(required=False, many=True)
    
    class Meta:
        model = Ingredient
        fields = ["id", "name","recipe", "ingredients_set"]

2nd example rewriting the create() method:

# models.py --------------------------

# Follow the same approach as the first example....

# serializers.py ----------------------

class IngredientSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = Ingredient
        fields = '__all__'

class MealSerializer(serializers.ModelSerializer):

    ingredients = IngredientSerializer(required=False, many=True)
    
    class Meta:
        model = Meal
        fields = ["id", "name","recipe", "ingredients"]

    def create(self, validated_data):
        # 1st step.
        ingredients = validated_data('ingredients')
        # 2nd step.
        actual_instance = Meal.objects.create(**validated_data)
        # 3rd step.
        for ingredient in ingredients:
            ing_objects = Ingredients.object.create(**ingredient)
            actual_instance.ingredients.add(ing_objects.id)
        actua_instance.save()
   
        return actual_instance

What was done in the second example?

1st step: since you create a one-2-many relationship the endpoint will wait for a payload like this:

    {

        "name": null,
        "recipe": null,
        "ingredients": [],    
    }

ex of validated_data/ the data you sent:

    {
        "id": 1,
        "name": "spaghetti",
        "recipe": "recipe",
        "ingredients": [
            {name:'pasta',amount:100},{name:'tomato',amount:200},{...}
        ],    
    }

Therefore, since you are sending a payload with many ingredientes inside the ingredient array you will get this value from the validated_data.

For instance, if you make a print of the 'ingredients'(from the inside of the create() method) this is what you will get in your terminal:

[{name:'pasta',amount:100},{name:'tomato',amount:200},{...}]

2nd step: Alright, since you get the ingredients from validate_data it is time to create a Meal instance (where will be without the 'ingredients').

3rd step: You will loop all the ingredients objects from the 1st step you have done above and add them into the Meal.ingredients relationship saving the Meal instance.

-- about the extra model --

[2] Bear in mind that having a JSONField() allows anything to be added there even extra fields. Having a Meal model might be a better option if you want have a better control.

1 Comment

How to integrate with HyperlinkedModelSerializer?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.