Как спроектировать ArrayField в django rest framework?

Я делаю API для кулинарной книги с помощью Django Rest Framework. Я не знаю, как разработать модель ингредиентов, чтобы данные были такими:

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

Моя модель:

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

А также как сериализовать это поле?

Я полагаю, что то, что вы ищете, это JsonBField

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

Это должно сделать то, что вы ожидаете, хорошего дня

edit: спасибо за обновление, как @Çağatay Barın упомянул ниже. К вашему сведению, он устарел, используйте django.db.models.JSONField вместо него. смотрите Doc

начиная с , Django поставляется с JSONField

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

Вы можете создать отдельную модель для ингредиента.

Отношение "многие ко многим" будет лучшим для меня, потому что в одном блюде может быть много ингредиентов, и наоборот, один ингредиент может быть использован для приготовления многих блюд.

Согласно django docs, в вашем случае:

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__'

Есть два подхода для получения такого результата [2] имея другую модель:

  1. использование WritableNestedModelSerializer.
  2. Переписывание метода create() вашего сериализатора.

1-й пример, используя 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"]

2-й пример переписывания метода create():

# 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

Что было сделано во втором примере?

1-й шаг: поскольку вы создаете отношения один-2-много, конечная точка будет ожидать полезной нагрузки следующим образом:

    {

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

ex of validated_data/ данные, которые вы отправили:

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

Поэтому, поскольку вы отправляете полезную нагрузку с большим количеством ингредиентов внутри массива ингредиентов, вы получите это значение из validated_data.

Например, если вы сделаете печать 'ingredients' (изнутри метода create()), вот что вы получите в терминале:

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

2-й шаг: Хорошо, поскольку вы получили ингредиенты из validate_data, пришло время создать экземпляр Meal (в котором не будет "ингредиентов").

3-й шаг: Вы зациклите все объекты ингредиентов из 1-го шага, который вы сделали выше, и добавите их в отношения Meal.ingredients, сохранив экземпляр Meal.

-- о дополнительной модели -

[2] Имейте в виду, что наличие модели JSONField() позволяет добавлять туда что угодно, даже дополнительные поля. Наличие модели Meal может быть лучшим вариантом, если вы хотите иметь лучший контроль.

Вернуться на верх