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 = {
}