Как создать несколько экземпляров модели без дублированного вложенного сериализатора с возможностью записи в Django REST Framework? - PullRequest
1 голос
/ 07 февраля 2020

У меня есть две родственные модели: Product и ProductDescription. В 1 отправке действие пользователь может вставить новый Product с несколькими описаниями в зависимости от доступных языков. Я использую записываемый вложенный сериализатор для одновременной вставки в Product и ProductDescription. Я делаю это, переопределяя функцию create в классе ProductDescriptionSerializer, это работает. Однако я могу вставить только 1 ProductDescription за раз.

Затем я попытался использовать этот ответ , чтобы создать несколько экземпляров модели одновременно. Проблема заключается в том, что он также создает один и тот же Product дважды вместо использования вновь созданного Product Id для вставки следующего ProductDescription.

My 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)

My 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

My 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']

Формат JSON, который я использую для POST-данных:

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

Ответы [ 3 ]

1 голос
/ 07 февраля 2020

Я думаю, вам нужно переопределить ваш ProductSerializer метод создания. Может быть, вы можете попробовать вот так:

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

    <b>def create(self, validated_data):
        instance, _ = Product.objects.get_or_create(**validated_data)
        return instance</b>

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

Чтобы сначала он попытался получить, если Продукт существует, иначе создайте экземпляр (следовательно, уменьшите дублирующую запись).

1 голос
/ 07 февраля 2020

Чтобы избежать дублирования товара, вы можете использовать метод 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 подвержен гонкам. Таким образом, если к вам обратились два одинаковых запроса одновременно, у вас могут остаться дубликаты продуктов.

0 голосов
/ 07 февраля 2020

ForeignKey не для этой работы вы должны использовать ManyToManyField

...