Django 休息框架:多对多通过模型可写

Django rest framework: many to many through model write-able

我有一个 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 = {
        }

编辑: 实际上我正在尝试的是,我想将对象列表发送到订单 API 端点并创建 OrderItems(通过模型)。例如:

{
    "number": "ap001",
    "status": "1",
    "ordered_by": 1,
    "location": "1",
    "vendor": 1,
    "items": [
        {
            "item": 1,
            "quantity": 2,
            "price": "10"
        },
        {
            "item": 2,
            "quantity": 3,
            "price": "15"
        }
    ]
}

项目数组由 item 组成,这是项目 table 的 pk。 quantityprice 用于直通模型。所以当我发送这种数据时,它应该通过模型将数据保存到。

编辑:

Environment:


Request Method: POST
Request URL: http://127.0.0.1:8000/orders/

Django Version: 3.2.9
Python Version: 3.9.9
Installed Applications:
['django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.staticfiles',
 'rest_framework',
 'InventoryApp.apps.InventoryappConfig']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback (most recent call last):
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\fields.py", line 457, in get_attribute
    return get_attribute(instance, self.source_attrs)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\fields.py", line 97, in get_attribute
    instance = getattr(instance, attr)

During handling of the above exception ('Item' object has no attribute 'item'), another exception occurred:
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
    raise exc
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\mixins.py", line 20, in create
    headers = self.get_success_headers(serializer.data)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\serializers.py", line 548, in data
    ret = super().data
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\serializers.py", line 246, in data
    self._data = self.to_representation(self.instance)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\serializers.py", line 515, in to_representation
    ret[field.field_name] = field.to_representation(attribute)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\serializers.py", line 663, in to_representation
    return [
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\serializers.py", line 664, in <listcomp>
    self.child.to_representation(item) for item in iterable
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\serializers.py", line 502, in to_representation
    attribute = field.get_attribute(instance)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\relations.py", line 190, in get_attribute
    return super().get_attribute(instance)
  File "C:\Users\Ahamed Arshad\Desktop\proj-inventory\env\lib\site-packages\rest_framework\fields.py", line 490, in get_attribute
    raise type(exc)(msg)

Exception Type: AttributeError at /orders/
Exception Value: Got AttributeError when attempting to get a value for field `item` on serializer `OrderItemSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Item` instance.
Original exception text was: 'Item' object has no attribute 'item'.

感谢您的帮助。提前致谢。

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"
        }
    ]
}

为了仅支持项目的 integer/primary 键,您可以使用 PrimaryKeyRelatedField:

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

感谢@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 = {
        }