Django rest framework: многие ко многим через модель с возможностью записи

У меня есть модель Order и модель Item. Каждый заказ состоит из нескольких позиций. Я связываю отношения с помощью модели OrderItem. Ниже приведен мой код

Модели:

class Order(models.Model):
    PAYMENT_TYPES = [
        ('COD', 'Cash On Delivery'),
        ('STRIPE', 'Stripe Credit/Debit'),
        ('PAYPAL', 'Paypal')
    ]

    STATUSES = [
        (1, 'Process'),
        (2, 'Completed'),
        (3, 'Hold')
    ]

    number = models.CharField(max_length=255)
    total = models.FloatField(null=True)
    credits_issued = models.FloatField(null=True)
    paid = models.FloatField(null=True)
    expected_delivery = models.DateTimeField(null=True)
    payment_type = models.CharField(max_length=255, choices=PAYMENT_TYPES, null=True)
    date = models.DateTimeField(default=now)
    status = models.CharField(max_length=2, choices=STATUSES)
    note = models.CharField(max_length=255, null=True)
    ordered_by = models.ForeignKey(User, on_delete=models.CASCADE)
    location = models.ForeignKey(Location, on_delete=models.CASCADE)
    vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE)
    items = models.ManyToManyField(Item, through='OrderItem', related_name='orders')

    class Meta:
        app_label = 'InventoryApp'
        db_table = 'order'

class Item(models.Model):
    STATUSES = [
        (1, 'Active'),
        (0, 'Inactive')
    ]
    DEFAULT_STATUS = 1

    name = models.CharField(max_length=255)
    quantity = models.IntegerField(null=True)
    last_bought_price = models.FloatField(null=True)
    order_by = models.DateTimeField(null=True)
    file_name = models.CharField(max_length=255, null=True)
    status = models.CharField(max_length=2, choices=STATUSES)
    category = models.ForeignKey(ItemCategory, on_delete=models.CASCADE)

    class Meta:
        app_label = 'InventoryApp'
        db_table = 'item'


class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    quantity = models.IntegerField()
    price = models.FloatField()

    class Meta:
        app_label = 'InventoryApp'
        db_table = 'order_item'
        unique_together = [['order', 'item']]

Я хочу знать, как сделать сериализаторы для сквозной модели, которая доступна для записи. Я написал сериализаторы, но они не работают.

Сериализаторы:

class OrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer(many=True)

    class Meta:
        model = Order
        fields = ['id','number', 'total', 'credits_issued', 'paid', 'expected_delivery', 'payment_type', 'date', 'status', 'note', 'ordered_by', 'location', 'vendor', 'items']
        extra_kwargs = {
            'ordered_by': {'required': True, 'read_only': False},
            'location': {'required': True, 'read_only': False},
            'vendor': {'required': True, 'read_only': False},
            'items': {
                'required': True,
                'read_only': False
            }
        }

    def create(self, validated_data):
        items = validated_data.pop('items')
        order = Order.objects.create(**validated_data)
        for item_data in items:
            item = item_data.pop('item')
            OrderItem.objects.create(order=order, item=item, **item_data)
        return order


    def validate_items(self, value):
        if len(value) < 1:
            raise serializers.ValidationError("Order should have at least 1 item")
        return value

    def to_internal_value(self, data):
        if 'items' in data:
            if not isinstance(data.get('items', []), list):
                raise serializers.ValidationError({
                    'items': ['Expected a list of items but got type {input_type}'.format(input_type=type(data.get('items')).__name__)]
                })

        return super().to_internal_value(data)


class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Item
        fields = ['id', 'name', 'quantity', 'last_bought_price', 'order_by', 'file_name', 'status', 'category']
        extra_kwargs = {
            'category': { 'required': True, 'read_only': False }
        }


class OrderItemSerializer(serializers.ModelSerializer):
    item = ItemSerializer(read_only=False, required=True)

    class Meta:
        model = OrderItem
        fields = ['item', 'quantity', 'price']
        extra_kwargs = {
        }

Благодарю за помощь. Заранее спасибо.

При текущей реализации OrderItemSerializer данные, которые ожидает ваш API, на самом деле выглядят примерно так:

{
    "number": "ap001",
    "status": "1",
    "ordered_by": 1,
    "location": "1",
    "vendor": 1,
    "items": [
        {
            "item": {
                "name": "someitem",
                "quantity": 1,
                ...other item fields...
            },
            "quantity": 2,
            "price": "10"
        },
        {
            "item": {
                "name": "someotheritem",
                "quantity": 2,
                ...other item fields...
            },
            "quantity": 3,
            "price": "15"
        }
    ]
}

Для поддержки только целого числа/первичного ключа для элемента можно использовать PrimaryKeyRelatedField:

class OrderItemSerializer(serializers.ModelSerializer):
    item = serializers.PrimaryKeyRelatedField(read_only=False, required=True, queryset=Item.objects.all(), source='orderitem_set')

Спасибо @briandestura. С его помощью я смог найти ответ. Я выкладываю обновленную модель и сериализаторы для всех, кому это понадобится в будущем. Потому что я потратил 3 дня на это, чтобы заставить его работать.

Models.py

class Item(models.Model):
    STATUSES = [
        (1, 'Active'),
        (0, 'Inactive')
    ]
    DEFAULT_STATUS = 1

    name = models.CharField(max_length=255)
    quantity = models.IntegerField(null=True)
    last_bought_price = models.FloatField(null=True)
    order_by = models.DateTimeField(null=True)
    file_name = models.CharField(max_length=255, null=True)
    status = models.CharField(max_length=2, choices=STATUSES)
    category = models.ForeignKey(ItemCategory, on_delete=models.CASCADE)

    class Meta:
        app_label = 'InventoryApp'
        db_table = 'item'


class Order(models.Model):
    PAYMENT_TYPES = [
        ('COD', 'Cash On Delivery'),
        ('STRIPE', 'Stripe Credit/Debit'),
        ('PAYPAL', 'Paypal')
    ]

    STATUSES = [
        (1, 'Process'),
        (2, 'Completed'),
        (3, 'Hold')
    ]

    number = models.CharField(max_length=255)
    total = models.FloatField(null=True)
    credits_issued = models.FloatField(null=True)
    paid = models.FloatField(null=True)
    expected_delivery = models.DateTimeField(null=True)
    payment_type = models.CharField(max_length=255, choices=PAYMENT_TYPES, null=True)
    date = models.DateTimeField(default=now)
    status = models.CharField(max_length=2, choices=STATUSES)
    note = models.CharField(max_length=255, null=True)
    ordered_by = models.ForeignKey(User, on_delete=models.CASCADE)
    location = models.ForeignKey(Location, on_delete=models.CASCADE)
    vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE)
    items = models.ManyToManyField(Item, through='OrderItem', related_name='orders')

    class Meta:
        app_label = 'InventoryApp'
        db_table = 'order'


class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    quantity = models.IntegerField()
    price = models.FloatField()

    class Meta:
        app_label = 'InventoryApp'
        db_table = 'order_item'
        unique_together = [['order', 'item']]

Serializers.py

class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Item
        fields = ['id', 'name', 'quantity', 'last_bought_price', 'order_by', 'file_name', 'status', 'category']
        extra_kwargs = {
            'category': { 'required': True, 'read_only': False }
        }


class OrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer(many=True, source='orderitem_set')

    class Meta:
        model = Order
        fields = ['id','number', 'total', 'credits_issued', 'paid', 'expected_delivery', 'payment_type', 'date', 'status', 'note', 'ordered_by', 'location', 'vendor', 'items']
        extra_kwargs = {
            'ordered_by': {'required': True, 'read_only': False},
            'location': {'required': True, 'read_only': False},
            'vendor': {'required': True, 'read_only': False},
            'items': {'required': True, 'read_only': False, 'many': True, 'queryset': OrderItem.objects.all()}
        }

    def create(self, validated_data):
        print(validated_data)
        items = []
        if 'orderitem_set' in validated_data:
            items = validated_data.pop('orderitem_set')
        order = Order.objects.create(**validated_data)
        for item_data in items:
            OrderItem.objects.create(order=order, item=item_data['item'], quantity=item_data['quantity'], price=item_data['price'])
        return order


    def validate_items(self, value):
        if len(value) < 1:
            raise serializers.ValidationError("Order should have at least 1 item")
        return value

    def to_internal_value(self, data):
        if 'items' in data:
            if not isinstance(data.get('items', []), list):
                raise serializers.ValidationError({
                    'items': ['Expected a list of items but got type {input_type}'.format(input_type=type(data.get('items')).__name__)]
                })
        return super().to_internal_value(data)


class OrderItemSerializer(serializers.ModelSerializer):
    item = serializers.PrimaryKeyRelatedField(read_only=False, required=True, queryset=Item.objects.all())

    class Meta:
        model = OrderItem
        fields = ['item', 'quantity', 'price']
        extra_kwargs = {
        }
Вернуться на верх