如何在 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应用程序中设计适当的错误处理代码?

如何在efcore中设计一对多关系?

django-rest-framework框架总结之认证权限限流过滤分页及异常处理

django-rest-framework框架总结之认证权限限流过滤分页及异常处理

如何在 django rest 框架中访问获取请求数据