Django: After saving the Order and deleting the cart, Order looses information of ordered items
I want the cart items to be saved in each order. But after the cart is deleted, "service_items" becomes empty.
Steps:
- Add items to the cart
- Save order
- Delete cart
I think service_items = models.ManyToManyField(Cart)
is causing the issue, but I don't know how to fix it.
Current Order object after saving:
{
"id": 1,
"customer": {
"id": 1,
"first_name": "Some",
"last_name": "User",
"email": "someuser@gmail.com",
"phone_number": "+817056781234",
"member_date": "2023-01-28T14:38:43.196580Z"
},
"status": false,
"total": "15.00",
"order_date": "2023-01-28T16:05:11.704636Z",
"delivery_date": "2023-01-31T12:52:16.193078Z",
"advance_payment": "9.00",
"payment_method": "cash",
"due_payment": 6.0,
"service_items": []
}
Expected outcome:
{
"id": 1,
"customer": {
"id": 1,
"first_name": "Some",
"last_name": "User",
"email": "someuser@gmail.com",
"phone_number": "+817056781234",
"member_date": "2023-01-28T14:38:43.196580Z"
},
"status": false,
"total": "15.00",
"order_date": "2023-01-28T16:05:11.704636Z",
"delivery_date": "2023-01-31T12:52:16.193078Z",
"advance_payment": "9.00",
"payment_method": "cash",
"due_payment": 6.0,
"service_items": [
{
"id": 1,
"service_item": {
"id": 1,
"item_name": "Black Shirt",
"description": "Half and Full Sleeve Black Shirts.",
"wash_price": "3.00",
"stain_treatment_price": "1.00",
"iron_price": "2.00",
"dry_wash_price": "4.00",
"category": {
"id": 2,
"title": "Shirt"
}
},
"is_wash": false,
"is_stain_treatment": true,
"is_iron": false,
"is_dry_wash": true,
"quantity": 3,
"unit_price": "5.00",
"price": "15.00",
"customer": 1
}
]
}
models.py
class ServiceItem(models.Model):
item_name = models.CharField(max_length=200)
description = models.TextField(null=True)
wash_price = models.DecimalField(max_digits=6, decimal_places=2, null=False, default=0)
stain_treatment_price = models.DecimalField(max_digits=6, decimal_places=2, null=False, default=0)
iron_price = models.DecimalField(max_digits=6, decimal_places=2, null=False, default=0)
dry_wash_price = models.DecimalField(max_digits=6, decimal_places=2, null=False, default=0)
category = models.ForeignKey(Category, on_delete=models.PROTECT)
def __str__(self) -> str:
return self.item_name
class Cart(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
service_item = models.ForeignKey(ServiceItem, on_delete=models.CASCADE)
is_wash = models.BooleanField(default=False)
is_stain_treatment = models.BooleanField(default=False)
is_iron = models.BooleanField(default=False)
is_dry_wash = models.BooleanField(default=False)
quantity = models.SmallIntegerField()
unit_price = models.DecimalField(max_digits=6, decimal_places=2)
price = models.DecimalField(max_digits=6, decimal_places=2)
class Meta:
unique_together = ('service_item', 'customer')
def __str__(self):
return f'{self.customer} - {self.service_item}'
class Order(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
service_items = models.ManyToManyField(Cart)
status = models.BooleanField(db_index=True, default=False)
total = models.DecimalField(max_digits=6, decimal_places=2)
order_date = models.DateTimeField(db_index=True, auto_now_add=True)
delivery_date = models.DateTimeField(db_index=True, default=datetime.now() + timedelta(days = 1))
advance_payment = models.DecimalField(max_digits=6, decimal_places=2, default=0)
payment_method = models.CharField(max_length=70, db_index=True, default="cash")
def __str__(self):
return f'{self.customer} - {self.order_date}'
serializers.py
class CartSerializer(serializers.ModelSerializer):
service_item = ServiceItemSerializer(read_only=True)
class Meta:
model = Cart
fields = '__all__'
class OrderSerializer(serializers.ModelSerializer):
due_payment = serializers.SerializerMethodField(method_name='calculate_balance')
service_items = CartSerializer(many=True)
class Meta:
model = Order
fields = ['id', 'customer', 'status', 'total', 'order_date', 'delivery_date',
'advance_payment', 'payment_method', 'due_payment', 'service_items']
depth = 1
def calculate_balance(self, product:Order):
if product.advance_payment > 0:
return Decimal(product.total - product.advance_payment)
else:
return Decimal(product.total)
views.py
@api_view(['GET', 'POST'])
def cart(request):
if request.method == 'GET':
items = Cart.objects.all()
serialized_items = CartSerializer(items, many=True)
return Response(serialized_items.data, status=status.HTTP_200_OK)
elif request.method == 'POST':
customer_phone = request.data.get("customer_phone")
customer = get_object_or_404(Customer, phone_number=customer_phone) # Customer Info
item_name = request.data['item']
item = get_object_or_404(ServiceItem, item_name=item_name)
item_quantity = int(request.data['quantity'])
wash = request.data['is_wash']
stain_treatment = request.data['is_stain_treatment']
iron = request.data['is_iron']
dry_wash = request.data['is_dry_wash']
item_price = Decimal(0)
if wash:
item_price += item.wash_price
if stain_treatment:
item_price += item.stain_treatment_price
if iron:
item_price += item.iron_price
if dry_wash:
item_price += item.dry_wash_price
cart_item = Cart(customer_id=customer.id, service_item=item, is_wash=wash,
is_stain_treatment=stain_treatment, is_iron=iron, is_dry_wash=dry_wash,
quantity=item_quantity, unit_price=item_price, price=Decimal(item_price*item_quantity))
try:
cart_item.save()
except:
return Response({'message': 'The item may have already been added to the cart'},
status=status.HTTP_406_NOT_ACCEPTABLE)
return Response({'message': '{} {} has been added succesfully'.format(item_quantity,item_name)},
status=status.HTTP_201_CREATED)
@api_view(['GET', 'POST'])
# @permission_classes([IsAuthenticated])
@throttle_classes([UserRateThrottle])
def orders(request):
if request.method == 'GET':
orders = Order.objects.all()
serialized_orders = OrderSerializer(orders, many=True)
return Response(serialized_orders.data, status=status.HTTP_200_OK)
elif request.method == 'POST':
customer = get_object_or_404(Customer, phone_number=customer_phone)
cart = Cart.objects.filter(customer=customer)
if cart:
total = cart.aggregate(Sum('price'))['price__sum']
# discount = ???
# staff = ??
delivery_date = request.data.get("delivery_date")
advance_payment = Decimal(request.data.get("advance_payment"))
payment_method = request.data.get("payment_method")
order = Order.objects.create(customer=customer, total=total, delivery_date=delivery_date,
advance_payment=advance_payment, payment_method=payment_method)
order.service_items.set(cart)
order.save()
cart.delete()
return Response({'message': 'Order successfully placed'}, status=status.HTTP_201_CREATED)
else:
return Response({'message': 'Cart is Empty'}, status=status.HTTP_204_NO_CONTENT)
Is there any other way to implement what I am expecting?
models.py
class ServiceItem(models.Model):
item_name = models.CharField(max_length=200)
description = models.TextField(null=True)
wash_price = models.DecimalField(max_digits=6, decimal_places=2, null=False, default=0)
stain_treatment_price = models.DecimalField(max_digits=6, decimal_places=2, null=False, default=0)
iron_price = models.DecimalField(max_digits=6, decimal_places=2, null=False, default=0)
dry_wash_price = models.DecimalField(max_digits=6, decimal_places=2, null=False, default=0)
category = models.ForeignKey(Category, on_delete=models.PROTECT)
def __str__(self) -> str:
return self.item_name
class Cart(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
service_item = models.ForeignKey(ServiceItem, on_delete=models.CASCADE)
is_wash = models.BooleanField(default=False)
is_stain_treatment = models.BooleanField(default=False)
is_iron = models.BooleanField(default=False)
is_dry_wash = models.BooleanField(default=False)
is_placed = models.BooleanField(default=False)
quantity = models.SmallIntegerField()
unit_price = models.DecimalField(max_digits=6, decimal_places=2)
price = models.DecimalField(max_digits=6, decimal_places=2)
class Meta:
unique_together = ('service_item', 'customer')
def __str__(self):
return f'{self.customer} - {self.service_item}'
class Order(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
service_items = models.ManyToManyField(Cart)
status = models.BooleanField(db_index=True, default=False)
total = models.DecimalField(max_digits=6, decimal_places=2)
order_date = models.DateTimeField(db_index=True, auto_now_add=True)
delivery_date = models.DateTimeField(db_index=True, default=datetime.now() + timedelta(days = 1))
advance_payment = models.DecimalField(max_digits=6, decimal_places=2, default=0)
payment_method = models.CharField(max_length=70, db_index=True, default="cash")
def __str__(self):
return f'{self.customer} - {self.order_date}'
#views.py
api_view(['GET', 'POST'])
def cart(request):
if request.method == 'GET':
items = Cart.objects.filter(is_placed=0)
serialized_items = CartSerializer(items, many=True)
return Response(serialized_items.data, status=status.HTTP_200_OK)
elif request.method == 'POST':
customer_phone = request.data.get("customer_phone")
customer = get_object_or_404(Customer, phone_number=customer_phone) # Customer Info
item_name = request.data['item']
item = get_object_or_404(ServiceItem, item_name=item_name)
item_quantity = int(request.data['quantity'])
wash = request.data['is_wash']
stain_treatment = request.data['is_stain_treatment']
iron = request.data['is_iron']
dry_wash = request.data['is_dry_wash']
item_price = Decimal(0)
if wash:
item_price += item.wash_price
if stain_treatment:
item_price += item.stain_treatment_price
if iron:
item_price += item.iron_price
if dry_wash:
item_price += item.dry_wash_price
cart_item = Cart(customer_id=customer.id, service_item=item, is_wash=wash,
is_stain_treatment=stain_treatment, is_iron=iron, is_dry_wash=dry_wash,
quantity=item_quantity, unit_price=item_price, price=Decimal(item_price*item_quantity))
try:
cart_item.save()
except:
return Response({'message': 'The item may have already been added to the cart'},
status=status.HTTP_406_NOT_ACCEPTABLE)
return Response({'message': '{} {} has been added succesfully'.format(item_quantity,item_name)},
status=status.HTTP_201_CREATED)
@api_view(['GET', 'POST'])
# @permission_classes([IsAuthenticated])
@throttle_classes([UserRateThrottle])
def orders(request):
if request.method == 'GET':
orders = Order.objects.all()
serialized_orders = OrderSerializer(orders, many=True)
return Response(serialized_orders.data, status=status.HTTP_200_OK)
elif request.method == 'POST':
customer = get_object_or_404(Customer, phone_number=customer_phone)
cart = Cart.objects.filter(customer=customer,is_placed=0) # be shure here you shoud have only one cart for the user other will be is_placed=1
if cart:
total = cart.aggregate(Sum('price'))['price__sum']
# discount = ???
# staff = ??
delivery_date = request.data.get("delivery_date")
advance_payment = Decimal(request.data.get("advance_payment"))
payment_method = request.data.get("payment_method")
order = Order.objects.create(customer=customer, total=total, delivery_date=delivery_date,
advance_payment=advance_payment, payment_method=payment_method)
order.service_items.set(cart)
order.save()
cart.update(
is_placed=1
)
# so when you place the order cart is not deleted and also deleting the data from the database is not a good practice e.g if you work in a company you are not allowed to use delete rather than you use check is_deleted.
return Response({'message': 'Order successfully placed'}, status=status.HTTP_201_CREATED)
else:
return Response({'message': 'Cart is Empty'}, status=status.HTTP_204_NO_CONTENT)