1

So, I am developing a cart system for an e-commerce website using Django. There are two separate functions for handling adding and removing cart items. (No I can't consolidate them into one function without changing my database and template structure)

The first function, 'update_cart' is working just fine. It properly updates the appropriate model.

However, the second function 'remove_item' is triggered when the user clicks the 'remove' button in the item list, and the function does in fact execute and redirect properly.

However, it fails to update the many-to-many, foreign key-identified model object associated with the item.

It's so weird because I'm accessing the object the same way in both functions. The only difference is that in the first uses .add() and the second .remove().

Also, the template is supposed to load the product's id into the GET data but doesn't show it in the URL like with the other template that triggers the 'update_cart' function.

But it does show the mycart/?id=86 when the cursor is hovering over the button. (this is a chrome feature). It's very confusing.

Can you guys help me to see what I am overlooking? Thanks for your time. :)

view.py functions:

def update_cart(request):
    context={}
    
    if request.user.is_authenticated:
        print('triggered')
        owner = request.user.id
        cart = Cart.objects.get(cart_owner=owner)


        productID = request.GET['id']
        product = Product.objects.get(pk=productID) 
        cart.products.add(product)


        new_total = 0.00
        for item in cart.products.all():
            new_total += float(item.priceNotax)

        cart.total = new_total
        cart.save()

def remove_item(request):
    if request.user.is_authenticated:
        owner = request.user.id
        cart = Cart.objects.get(cart_owner=owner)


        productID = request.GET['id']
        print(productID)
        product = Product.objects.get(pk=productID) 
        cart.products.remove(product)
        cart.save()


        new_total = 0.00
        for item in cart.products.all():
            new_total += float(item.priceNotax)

        cart.total = new_total
        cart.save()
        return HttpResponseRedirect(reverse("myCart"))
    else:
        return HttpResponseRedirect(reverse("login"))

The model.py file containing the affected 'Cart' model:

class Cart(models.Model):
    cart_owner = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
    products = models.ManyToManyField(Product, null=True, blank=True)
    #held_items = models.ManyToManyField(Order_holding, null=True, blank=True)
    total = models.DecimalField(max_digits=100, decimal_places=2, default=0.00)
    timestamp = models.DateTimeField(auto_now_add=False, auto_now=True)
    updated = models.DateTimeField(auto_now_add=False, auto_now=True)
    active = models.BooleanField(default=True)

    def __unicode__(self):
        return "Cart id: %s" %(self.id)

The template that triggers the 'remove_item' function:

{% extends 'base.html' %}
{% load static %}

{% block content %}


<div class="container-fluid mb-2">
    <div class="card">
        <div class="card-header"><h3>Shopping Cart</h3></div>
    </div>
</div>

{% for item in cart.products.all %}
<div class="container-fluid mb-2">
    <div class=".row-4 .row-sm-4 m-4 card justify-content-center">
        <div><h4>{{ item.name }}</h4></div>
        <div><h4>Price: ${{ item.priceNotax }}</h4><a href="{% url 'remove_item'%}?id={{item.id}}"><button class='btn-danger justify-content-right'>Remove</button></div></a>

    </div>
</div>

{% endfor %}
<h2>Sub-Total: ${{total}}</h2>

<a href="{% url 'products'%}"><button  class="btn-primary">Continue Shoping</button></a>



{% endblock content %}
# urls.py

from django.contrib import admin 
from django.urls import path, include 
from django.conf import settings 
from django.conf.urls import patterns, include, url 
from django.conf.urls.static import static 
from cart.views import addToCart, update_cart 
from products.views import productsPage 
from products import urls admin.autodiscover() 
from . import views 

urlpatterns = [ path('myCart/', views.myCart,name='myCart'),
                path('products/', views.productsPage,name='products'),
                path('addToCart/', update_cart, name='update_cart'), 
              ]
raphael
  • 2,469
  • 2
  • 7
  • 19
  • 1
    Not that you should be modifying databases with get requests, but I can't see any errors. Perhaps you can share more of your code. Sometimes the error is not in the place you expect it to be. – raphael Jan 02 '22 at 00:59
  • what do your urls.py look like? – user14665310 Jan 02 '22 at 01:06
  • Like I said, it redirects just fine so I doubt its the ursl.py file. But just in case here it is. – Micah Matthews Jan 02 '22 at 01:12
  • from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls import patterns, include, url from django.conf.urls.static import static from cart.views import addToCart, update_cart from products.views import productsPage from products import urls admin.autodiscover() from . import views urlpatterns = [ path('myCart/', views.myCart,name='myCart'), path('products/', views.productsPage,name='products'), path('addToCart/', update_cart, name='update_cart'), ] – Micah Matthews Jan 02 '22 at 01:13
  • Edit your question instead of adding code in comments. Also, it is not valid HTML to nest a button within an anchor tag (https://stackoverflow.com/questions/6393827/can-i-nest-a-button-element-inside-an-a-using-html5) – raphael Jan 02 '22 at 01:18
  • Thank you for your feedback. I went ahead and make those edits you suggested. How would you accomplish the same thing I am trying to do without the anchor tags? – Micah Matthews Jan 02 '22 at 20:26
  • 2
    FWIW, I thought I'd try and replicate and with a straight copy paste of your code, it seems to work fine as intended on my side (I know - that's not very helpful). However R. Uziel's comments, it is worth refactoring some of it as you hit on multiple [anti-patterns](https://www.django-antipatterns.com/), see #1, and #14 in particular. – AdT Jan 04 '22 at 01:24

2 Answers2

1

Without seeing the rest of your code, I can't answer the original question of why the two view functions behave differently.

As far as the issue with the anchor, you could just style the link to look like a button in bootstrap, like this:

<a href="{% url 'remove_item'%}?id={{item.id}}" class='btn btn-danger justify-content-right'>Remove</a>

Or, you could wrap the button in a form tag, like this (I have not tested this):

<form style="display: inline" action="{% url 'remove_item' %}" method="get">
  <button class='btn-danger justify-content-right'>Remove</button>
</form>

But GET requests are not secure and should never be used to modify a database. Instead you could use a POST request, like this:

<form action="{% url 'remove_item' %}" method="post">
    {% csrf_token %}
    <input type="hidden" name="item_id" value="{{ item.id }}">
    <button type='submit' class='btn-danger justify-content-right'>Remove</button>
</form>

This will send the item id through the hidden input, and then in your views you could access the same information in a more secure way than as part of a URL, which is what happens with a GET request.

def remove_item(request):
    if request.method == 'POST':
        item_id = request.POST.get('item_id')
        # Here you have your code that actually removes the item
raphael
  • 2,469
  • 2
  • 7
  • 19
  • Hey thanks for that information! As far as the get request goes, it doesn't actually modify the database. It is just how I transfer the product id from template to template. The data base is modified based on that product id number. There was no other way to send it along through the "product detail" button without creating a form for it which seemed unessasary. – Micah Matthews Jan 03 '22 at 02:52
0

I figured it out, guys. I missed up on the main app's URL file. I had different views being called but they had the same path and the wrong one was listed first so it was being called before the one that was needed was. lol Sorry!