How can an object be deleted if another one is emty?

Question:

So, I am a begginer coding an e-commerce project on Django. I’ve managed to add a "delete item"function to remove items from the cart. However, when the item is removed, the order isn’t. OrderItem and Order are 2 sepparate models, I’d like so that when an OrderItem i fully deleted and the cart is empty, for the order itself to be deleted too. I’ll attach de code for the models and the functios.

Order model:

class Order(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True)
date_ordered = models.DateTimeField(auto_now_add=True)
complete = models.BooleanField(default=False)
transaction_id = models.CharField(max_length=100, null=True)

def __str__(self):
    return str(self.id)
    
@property
def get_cart_total(self):
    orderitems = self.orderitem_set.all()
    total = sum([item.get_total for item in orderitems])
    return total 

@property
def get_cart_items(self):
    orderitems = self.orderitem_set.all()
    total = sum([item.quantity for item in orderitems])
    return total 

OrderItem model:

class OrderItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True)
quantity = models.IntegerField(default=0, null=True, blank=True)
date_added = models.DateTimeField(auto_now_add=True)

@property
def get_total(self):
    total = self.product.price * self.quantity
    return total

The function for deleting the items:

def eliminaritem(request, id):
if request.method == "POST":
    item = OrderItem.objects.get(product = id)
    item.delete()
    return render(request, "store/deletecartitem.html", {'item': item})

if  OrderItem.objects.all == None:
    customer = request.user.customer
    order = Order.objects.all(customer = customer)
    order.delete()

The first part of this functions works, however the way I approached the second IF is horrible. So basically when there aren’t any OrderItems, the Order should be deleted. Whenever an item is added to cart, the Order is created

This is the logic behind the cart function :

def cart(request):
if request.user.is_authenticated:
    customer = request.user.customer
    order, created = Order.objects.get_or_create(customer = customer, complete = False)
    items = order.orderitem_set.all()
else:
    items = []
    order = {'get_cart_total':0, 'get_cart_items':0}

context = {'items': items, 'order': order}
return render(request, 'store/cart.html', context)

This is the logic in the template:

{% if order != None  %}

then it shows the items which were added
else:
No items are in the cart

So when an item is added to the cart, and then it is deleted, leaving the cart empty, the page still won’t say No items are in the cart, that is the prohblem.

I dont really care about the order being deleted if the page shows no items in cart when it is empty

Asked By: Mateo Doherty

||

Answers:

The modeling is not very effective I guess. I would try to enforce that a user has only one non-completed order, otherwise it is hard to know what will be the order you are looking for:

from django.db.models import Q


class Order(models.Model):
    customer = models.ForeignKey(
        Customer, on_delete=models.SET_NULL, null=True, blank=True
    )
    date_ordered = models.DateTimeField(auto_now_add=True)
    complete = models.BooleanField(default=False)
    transaction_id = models.CharField(max_length=100, null=True)

    def __str__(self):
        return f'{self.id}'

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=('customer',),
                condition=Q(complete=False),
                name='incomplete_order_per_customer',
            )
        ]

Now for the view, a view can only have side-effects if the method is a POST, PUT, PATCH or DELETE request. Your second if does not guarantee that, and is thus not HTTP compliant. Your view will furthermore remove an OrderItem if there is exactly one such item. This means that if two users have the same OrderItem, then it will error, and if another user has such OrderItem, then another user can make such request to get that OrderItem removed.

You can determine the OrderItem for the logged in customer, and remove that one with:

from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, redirect
from django.views.decorators.http import require_http_methods


@login_required
@require_http_methods(['DELETE', 'POST'])
def eliminaritem(request, id):
    order_items = OrderItem.objects.filter(
        order__complete=False, order__customer__user=request.user
    )
    item = get_object_or_404(order_items, product_id=id)
    item.delete()
    if not order_items.exists():
        Order.objects.filter(
            customer__user=request.user, completed=False
        ).delete()
    return redirect('name-of-some-view')

Note: You can limit views to a view to authenticated users with the
@login_required decoratorĀ [Django-doc].


Note: In case of a successful POST request, you should make a redirect
[Django-doc]

to implement the Post/Redirect/Get patternĀ [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.

Answered By: Willem Van Onsem

I assume you are using the eliminaritem() view to delete the item.

def eliminaritem(request, id):
    if request.method == "POST":
        item = OrderItem.objects.get(product = id)
        item.delete()
        return render(request, "store/deletecartitem.html", {'item': item})

    if  OrderItem.objects.all == None:
        customer = request.user.customer
        order = Order.objects.all(customer = customer)
        order.delete()

The problem here is that OrderItem.objects.all() would return all OrderItems of all Orders. As long as there is somewhere a cart with an item the quer will never be empty. Also since you return before the second if can be executed the code will never be reached.

def eliminaritem(request, id):
    if request.method == "POST":
        item = OrderItem.objects.get(product = id)

        # First get the order behind the item.
        relevant_order = item.order

        # Check if order is from that user before deletion.
        if not relevant_order.customer == request.user
            # return some 403 error

        item.delete()

        # After deletion of the item check if there are items left.
        if not relevant_order.orderitem_set.all()
            relevant_order.delete()

        return render(request, "store/deletecartitem.html", {})

I used your code so you can incorporate the changes quickly.

Answered By: coderiot
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.