【问题标题】:How to create multiple model instances without duplicated writable nested serializer in Django REST Framework?如何在 Django REST Framework 中创建多个模型实例而无需重复的可写嵌套序列化程序?
【发布时间】:2020-05-23 10:39:00
【问题描述】:

我有两个相关的模型: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."
  }
]

它会在产品列表中创建一个重复的Product

[
    {
        "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."
    }
]

【问题讨论】:

    标签: python django django-rest-framework


    【解决方案1】:

    ForeignKey 不适用于此工作,您应该使用ManyToManyField

    【讨论】:

      【解决方案2】:

      我认为您需要覆盖 ProductSerializer 的创建方法。也许你可以这样尝试:

      class ProductSerializer(serializers.ModelSerializer):
          collection = CustomRelatedField(queryset=ProductCollection.objects.all(), many=False)
          color = CustomRelatedField(queryset=ColorParent.objects.all(), many=False)
      
          def create(self, validated_data):
              instance, _ = Product.objects.get_or_create(**validated_data)
              return instance
      
          class Meta:
              model = Product
              fields = ['id', 'product_code', 'collection', 'color', 'video', 'status']

      因此,首先它会尝试获取产品是否存在,否则创建实例(因此减少重复条目)。

      【讨论】:

      • 感谢您回答我的问题,ruddra。 Product 实例仍然插入了两次。我是否也应该添加/更改代码的另一部分?
      【解决方案3】:

      为避免重复产品,您可以使用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 容易出现竞争条件。因此,如果两个相同的请求同时向您提供服务,您可能仍然有重复的产品。

      【讨论】:

      • 当我使用与我的帖子中相同的 JSON 格式时,我得到了"non_field_errors": ["Invalid data. Expected a dictionary, but got list."]。如何发布新的数据格式?
      • @Nathan 很奇怪,您是否更改了其他内容?您仍然应该使用 many=True 的解决方案。不要删除它。
      • 对不起。我确实错误地改变了一些东西。现在可以了。谢谢neverwalkaloner!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-03
      • 2019-02-06
      • 2016-05-29
      • 2015-12-16
      • 2018-06-23
      • 1970-01-01
      相关资源
      最近更新 更多