如何在 django rest 框架中设计 ArrayField?
Posted
技术标签:
【中文标题】如何在 django rest 框架中设计 ArrayField?【英文标题】:How to design ArrayField in django rest framework? 【发布时间】:2021-12-15 13:48:54 【问题描述】:我正在使用 Django Rest 框架为食谱制作 API。我不知道如何设计成分模型以使数据像这样:
"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 = ?
还有如何序列化这个字段?
【问题讨论】:
这是一个供你学习的项目还是一个真正的产品?老实说,我问是因为这取决于情况。就个人而言,我宁愿使用单独的表格来存放食材,而另一个表格可以参考(FK)食材,膳食和存储数量。这样,您将利用数据库完整性(只能添加有效成分等),您将能够通过单个查询分析哪顿饭中使用了哪些成分,我认为它会更好看。 JSONField 更像是一个为模型存储非关键数据的字段。 我正在为我的投资组合构建这个项目,但我看不到如何将成分字段或模型与他在膳食模型中的 amont 连接起来的好方法。实际上,我有单独的成分模型来过滤数据。但是这个数量是真正的痛苦。 Amout as FK in db 将存储简单的整数值,所以它实际上是个好主意? 【参考方案1】:您可以为成分创建单独的模型。
多对多关系对我来说是最好的,因为一顿饭可以有很多配料,相反,一种配料可以用来做很多饭菜。
根据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
序列化器.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__'
【讨论】:
我认为这是最好的方法。也许有一个单独的成分模型(没有数量)和另一个存储成分和数量的表会更好。否则,db 中将有许多相同成分但数量不同,您将无法自定义成分使用(可能是成分使用顺序,或将来可能需要的任何其他额外信息) 我厌倦了这个,但这不是一个好的解决方案。想一想:我们有两餐,一餐需要 100 克盐,另一餐需要 200 克盐。但是在成分字段中,数量只能有一个值。有没有办法将成分模型与他的数量联系起来,但它在每个膳食模型中都是独一无二的? 因此您应该创建 2 个具有不同值的成分。正如@ÇağatayBarın 所说,它也可能被分成一个单独的表格。如何存储这些数据由您决定。 @non 当然,这始终是您的决定。只是想帮助您获得最佳实践。在我提到的另一个模型中(我们称之为成分使用),它将有一个 FK 到成分,FK 到膳食,以及 IntegerField(或 DecimalField,取决于你)的数量。这样一来,您就不必在每顿饭中使用成分时都存储它,您将获得该成分的链接,以便您将来可以使用该信息,或者您可以将其他数据添加到成分使用中(例如煎 x 分钟,或使用顺序)。 @ÇağatayBarın 非常感谢您的帮助。您的解决方案是最好的,我无法理解您的意思,但它与简单的关系完美结合。我可以通过一个请求获取所有相关数据。谢谢你:)【参考方案2】:我相信您正在寻找的是 JsonBField
from django.contrib.postgres.fields.jsonb import JSONField as JSONBField
ingredients = JSONBField(default=list,null=True,blank=True)
这应该会如你所愿,祝你有美好的一天
编辑:感谢下面提到的@Çağatay Barın 的更新 仅供参考,已弃用,请改用 django.db.models.JSONField。请参阅Doc
【讨论】:
但是如果我不使用 PostgreSQL 怎么办? 仅供参考,自 3.1 版起已弃用。您可能想要更改答案以支持最新版本。请改用django.db.models.JSONField
。文档:docs.djangoproject.com/en/3.2/ref/contrib/postgres/fields/…【参考方案3】:
来自django-3.1,Django自带JSONField
class Meal(models.Model):
name = models.TextField()
recipe = models.TextField()
ingredients = models.JSONField()
【讨论】:
【参考方案4】:有两种方法可以得到这个结果[2] 有另一个模型:
-
使用
WritableNestedModelSerializer
。
覆盖序列化程序的create()
方法。
第一个示例,使用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"]
第二个示例重写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
第二个例子做了什么?
第一步:由于您创建一对二多关系,端点将等待这样的有效负载:
"name": null,
"recipe": null,
"ingredients": [],
您发送的数据/已验证数据的前:
"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,...]
第二步:好的,既然您从 validate_data 中获取了成分,那么是时候创建一个 Meal 实例了(其中没有“成分”)。
第 3 步:您将从上述第 1 步中循环所有成分对象,并将它们添加到 Meal.ingredients
关系中以保存 Meal 实例。
-- 关于额外模型--
[2] 请记住,JSONField()
允许在其中添加任何内容,甚至是额外的字段。如果您想要更好地控制,使用 Meal 模型可能是更好的选择。
【讨论】:
以上是关于如何在 django rest 框架中设计 ArrayField?的主要内容,如果未能解决你的问题,请参考以下文章
如何制定公交车座位计划?在ios目标C中设计它的最佳技术是啥
在Django / Python应用程序中设计适当的错误处理代码?
django-rest-framework框架总结之认证权限限流过滤分页及异常处理