I have a Order
model and Item
model. Each order consist of multiple Items. I connect the relationship with through model called OrderItem
. Below is my code
Models:
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']]
I wanna know how to make serializers for through model which is writeable. I have written serializers but it doesn't work.
Serializers:
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 = {
}
EDIT: Actually what I am trying is, I want to send a list of object to orders API endpoint and create the OrderItems (which is through model). For an example:
{
"number": "ap001",
"status": "1",
"ordered_by": 1,
"location": "1",
"vendor": 1,
"items": [
{
"item": 1,
"quantity": 2,
"price": "10"
},
{
"item": 2,
"quantity": 3,
"price": "15"
}
]
}
The items array consist of item
which is the pk of Item table. quantity
and price
are for the through model. So when I send this kind of data it should save data to through model.
Edit:
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'.
Appreciate your helps. Thanks in advance.
With the current implementation of OrderItemSerializer
, the data your API expects is actually something like:
{
"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"
}
]
}
In order to support just the integer/primary key for item, you can use PrimaryKeyRelatedField
:
class OrderItemSerializer(serializers.ModelSerializer):
item = serializers.PrimaryKeyRelatedField(read_only=False, required=True, queryset=Item.objects.all())