python - 如何在 Django REST Framework 中创建多个模型实例而无需重复的可写嵌套序列化程序?

标签 python django django-rest-framework

我有两个相关模型:ProductProductDescription。在 1 次提交操作中,用户可以插入具有多个描述的新 Product,具体取决于可用语言。我使用可写嵌套序列化器同时插入 ProductProductDescription 中。我通过重写 ProductDescriptionSerializer 类中的 create 函数来实现,它可以工作。但是,我一次只能插入 1 个 ProductDescription

然后我尝试使用这个answer一次创建多个模型实例。问题是它还会创建相同的 Product 两次,而不是使用新创建的 Product Id 插入下一个 ProductDescription

我的models.py:

class Product(models.Model, ProductStatus):
    product_code = models.CharField(max_length=6)
    color = models.ForeignKey(ColorParent, on_delete=models.SET_NULL, null=True)
    collection = models.ForeignKey(ProductCollection, on_delete=models.SET_NULL, null=True)
    video = models.URLField(verbose_name='Video URL', max_length=250, null=True, blank=True)
    status = models.CharField(max_length=20, choices=ProductStatus.status, default=ProductStatus.active)


class ProductDescription(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    language = models.ForeignKey(Language, on_delete=models.CASCADE)
    description = models.TextField(max_length=500, null=True, blank=True)

    def __str__(self):
        return '%s - %s' % (self.product, self.language)

我的serializers.py:

class CustomRelatedField(serializers.RelatedField):
    def display_value(self, instance):
        return instance

    def to_representation(self, value):
        return str(value)

    def to_internal_value(self, data):
        model = self.queryset.model
        return model.objects.get(id=data)


class ProductSerializer(serializers.ModelSerializer):
    collection = CustomRelatedField(queryset=ProductCollection.objects.all(), many=False)
    color = CustomRelatedField(queryset=ColorParent.objects.all(), many=False)

    class Meta:
        model = Product
        fields = ['id', 'product_code', 'collection', 'color', 'video', 'status']


class ProductDescriptionSerializer(serializers.ModelSerializer):
    product = ProductSerializer()
    language = CustomRelatedField(many=False, queryset=Language.objects.all())

    class Meta:
        model = ProductDescription
        fields = ['id', 'product', 'language', 'description']

    def to_representation(self, instance):
        data = super().to_representation(instance)
        if self.context['request'].method == 'GET':
            data['product'] = instance.product.product_code
            return data
        return Serializer.to_representation(self, instance)

    # The `.create()` method does not support writable nested fields by default.
    def create(self, validated_data):
        # create product data for Product model.
        product_data = validated_data.pop('product')
        product = Product.objects.create(**product_data)

        # create ProductDescription and set product FK.
        product_description = ProductDescription.objects.create(product=product, **validated_data)

        # return ProductDescription instance.
        return product_description

我的views.py:

class CreateListModelMixin(object):
    def get_serializer(self, *args, **kwargs):
        if isinstance(kwargs.get('data', {}), list):
            kwargs['many'] = True
        return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)


class ProductDescriptionView(CreateListModelMixin, viewsets.ModelViewSet):
    permission_classes = [permissions.DjangoModelPermissions]
    queryset = ProductDescription.objects.all()
    serializer_class = ProductDescriptionSerializer
    http_method_names = ['get', 'head', 'post', 'put', 'patch', 'delete']

我用来 POST 数据的 JSON 格式:

[
  {
    "product": {
        "product_code": "BQ1080",
        "collection": 5,
        "color": 7,
        "video": "https://www.youtube.com/watch?v=",
        "status": "Continue"
    },
    "language": 1,
    "description": "English description."
  },
  {
    "product": {
        "product_code": "BQ1080",
        "collection": 5,
        "color": 7,
        "video": "https://www.youtube.com/watch?v=",
        "status": "Continue"
    },
    "language": 2,
    "description": "Vietnamese description."
  }
]

它在产品列表中创建重复的产品:

[
    {
        "id": 26,
        "product_code": "BQ1080",
        "collection": 5,
        "color": 7,
        "video": "https://www.youtube.com/watch?v=",
        "status": "Continue"
    },
    {
        "id": 27,
        "product_code": "BQ1080",
        "collection": 5,
        "color": 7,
        "video": "https://www.youtube.com/watch?v=",
        "status": "Continue"
    }
]

ProductDescription 数据是正确的:

[
    {
        "id": 5,
        "product": "BQ1080",
        "language": "English",
        "description": "English description."
    },
    {
        "id": 6,
        "product": "BQ1080",
        "language": "Vietnam",
        "description": "Vietnamese description."
    }
]

最佳答案

为了避免重复产品,您可以使用 get_or_create()方法:

class ProductDescriptionSerializer(serializers.ModelSerializer):

    ...

    def create(self, validated_data):
        # create product data for Product model.
        product_data = validated_data.pop('product')
        product_code = product_data.pop("product_code") 
        product, _ = Product.objects.get_or_create(product_code=product_code, defaults=product_data)

        # create ProductDescription and set product FK.
        product_description = ProductDescription.objects.create(product=product, **validated_data)

        # return ProductDescription instance.
        return product_description

请注意,get_or_create 很容易出现竞争条件。因此,如果您同时收到两个相同的请求,您可能仍然有重复的产品。

关于python - 如何在 Django REST Framework 中创建多个模型实例而无需重复的可写嵌套序列化程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60110967/

相关文章:

python - 除非按顺序,否则不会读取路径

python - 连接两个 Pyspark 数据帧的两种方法有什么区别

python - 用另一个替换 DataFrame 的某些列(基于列名)

django - ValueError : path is on mount 'C:' , 在安装 'F:' 时开始,而 Windows 中的 Django 迁移

django - 使用 Django Rest 框架发布空日期字段错误

python - Scrapy 从列表的每个元素中提取内容

django - Sentry 如何汇总错误?

使用 Django 的 Javascript?

javascript - Axios 没有通过 useEffect() 得到任何东西(React Js)

Django REST框架-反向ForeignKey关系