Как спроектировать 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-3.1, 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] имея другую модель:
- использование
WritableNestedModelSerializer
. - Переписывание метода
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 может быть лучшим вариантом, если вы хотите иметь лучший контроль.