首页 > 解决方案 > 用于多个模型的 Django 嵌套序列化程序,带有链式外键

问题描述

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

模型.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)

序列化程序.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 密集型内部连接?我对此进行了很多研究,通常懒惰的答案是:尝试两种变体,基准测试,然后自己决定。

标签: djangodjango-modelsdjango-rest-frameworkdjango-viewsdjango-serializer

解决方案


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

序列化这些字段的一种简单方法是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']

结论

根据上面的描述,下面的代码片段将只有一个对数据库的查询

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

推荐阅读