【问题标题】:Django nested serializer for multiple models, with chained foreignKeys用于多个模型的 Django 嵌套序列化程序,带有链式外键
【发布时间】:2021-03-11 01:51:42
【问题描述】:

让我们以这 3 个简单的模型为例。一个城市可以有多个店铺,一个店铺可以有多个产品

models.py

class City(models.Model):
    name=models.CharField(max_length=300)

class Shop(models.Model):
    name = models.CharField(max_length=300)
    city = models.ForeignKey(City, related_name='related_city', on_delete=models.CASCADE)

class Product(models.Model):
    name=models.CharField(max_length=300)
    shop=models.ForeignKey(Shop, related_name='related_shop', on_delete=models.CASCADE)

serializers.py

class CitySerializer(serializers.ModelSerializer):
    class Meta:
        model = City
        fields=['id','name']

class ShopSerializer(serializers.ModelSerializer):
    related_shop = CitySerializer(many=True, read_only=True)
    class Meta:
        model = Shop
        fields=['id','name','related_city']

class ProductSerializer(serializers.ModelSerializer):
    related_shop = ShopSerializer(many=True, read_only=True)
    class Meta:
        model = Product
        fields=['id','name','related_shop']

在views.py 的get_queryset() 中,我使用.select_related().all() 来获取外来对象。 ProductSerializer 会给我所有的产品,并且会获取 foreignKey 商店,我也会得到找到这个产品的商店的名称。

ShopSerializer,以类似的方式,会给我所有商店的名称,以及可以找到这家商店的所有城市。

但是我怎样才能制作一个序列化程序,它会同时从所有 3 个表中检索? 我想要的字段是: fields=['product_name','shop_name', 'city_name']

我知道,我将得到的那个列表会有重复,可以被认为是 1NF 或 2NF,与我作为数据库 3NF 的模型设计相反。但这就是我想要的查询。

我实际上正在考虑对我的数据库进行非规范化,所以我可以轻松实现这一点。

我的第二个问题是,将其非规范化为 1NF 并进行重复是否更好,以便减少这 3 个表之间“id”上的 CPU 密集型内部连接?我对此进行了很多研究,通常懒惰的答案是:尝试两种变体,基准测试,然后自己决定。

【问题讨论】:

    标签: django django-models django-rest-framework django-views django-serializer


    【解决方案1】:

    您的问题将采用不同的方法。一次从数据库中获取相关对象实际上并不依赖于序列化程序本身。这是在您的视图层中完成的,您可以将select_related('shop__city') 附加到您的查询集。通过附加它,您将在单个查询中将 shopshop.city 值预加载到 Productobjects 上。

    序列化这些字段的一种简单方法是在序列化器字段中设置source,如下所示:

    class ProductSerializerV2(serializers.ModelSerializer):
        shop_name = serializers.CharField(source='shop.name')
        city_name = serializers.CharField(source='shop.city.name')
    
        class Meta:
            model = Product
            fields = ['name', 'shop_name', 'city_name']
    
    

    结论

    根据上面的描述,下面的sn-p只会对数据库进行一次查询

    p = Product.objects.select_related('shop__city').last()
    print(ProductSerializerV2(p).data)  
    # {'name': 'product-z', 'shop_name': 'shop-z', 'city_name': 'z'} as a sample output
    

    【讨论】:

    • 非常感谢,该代码有效!我很好奇您是否从经验中知道,如果您也可以帮助解决第二个问题 - 将数据库非规范化为 1NF 或 2NF 是否更有效,或者将其保持在 3NF 中,并进行这两个内部连接这3张桌子之间?场景是,电子商务网站,显示产品页面,每个我想立即显示该产品可用的商店和城市。我会在内部连接处遇到 CPU 瓶颈,还是在传输带有 1NF 中所有重复字段的更大表时遇到瓶颈?
    • 恕我直言,在遇到性能问题之前,您可能希望避免非规范化。同样,正确的答案取决于您使用的数据库和您正在使用的数据规模等变量。我认为通过django-silk 等工具分析请求将帮助您决定是否需要非规范化表或接受连接开销。
    【解决方案2】:

    像这样使用源代码:

    class ProductSerializer(serializers.ModelSerializer):
        shop_name = serializers.CharField(source='shop.name')
        city_name = serializers.CharField(source='shop.city.name')
    
        class Meta:
            model = Product
            fields = ['id', 'name', 'shop_name', 'city_name']
    

    第二个问题。你可以这样做:

    class City(models.Model):
        name=models.CharField(max_length=300)
    
    class Shop(models.Model):
        name = models.CharField(max_length=300)
    
    class Product(models.Model):
        name=models.CharField(max_length=300)
        city=models.ForeignKey(City, related_name='product_city', on_delete=models.CASCADE)
        shop=models.ForeignKey(Shop, related_name='product_shop', on_delete=models.CASCADE)
    

    通过使用它,您可以在模板中轻松访问每个产品的城市和商店,或者您将用于 forntend 的任何内容,并且数据库对实现产品的商店和城市的压力较小

    【讨论】:

    • 非常感谢,该代码有效!我很好奇您是否从经验中知道,如果您也可以帮助解决第二个问题 - 将数据库非规范化为 1NF 或 2NF 是否更有效,或者将其保持在 3NF 中,并进行这两个内部连接这3张桌子之间?场景是,电子商务网站,显示产品页面,每个我想立即显示该产品可用的商店和城市。我会在内部连接处遇到 CPU 瓶颈,还是在传输带有 1NF 中所有重复字段的更大表时遇到瓶颈?
    • 你是对的,避免一次加入的聪明方法。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2018-12-10
    • 1970-01-01
    • 1970-01-01
    • 2019-01-30
    • 1970-01-01
    • 2011-04-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多