[Python] Django products not being added to cart immediately

Discussão em 'Python' iniciado por Stack, Fevereiro 10, 2025.

  1. Stack

    Stack Membro Participativo

    [​IMG]

    [[​IMG]

    The first picture shows the add to cart button which when I click on, the product is added and the cart modal should show up with the added product.

    The second picture shows what I am expecting to see, but I only see this after refreshing the page.

    The third picture shows what I am actually seeing when I click on the add to cart button, just an empty cart modal pops up.

    [​IMG].

    So I am trying to build a Django e-commerce website, where there is a cart modal which pops up when I try to add any product by clicking the add to cart button.

    While the product is being added correctly in the backend (which I can verify by going to admin panel), the product just doesn't show up immediately on my cart modal, something which is essential for the website to look good. Only when I am refreshing the page, the product shows up on my modal. What can I try next?

    My cart model:


    class Cart(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='cart')
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField(default=1)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
    return f"{self.user.username} - {self.product.name}"


    My views.py:

    class CartView(LoginRequiredMixin, View):
    def get(self, request):
    cart_items = Cart.objects.filter(user=request.user)
    total_price = sum(item.product.price * item.quantity for item in cart_items)
    return render(request, 'business/cart.html', {'cart_items': cart_items, 'total_price': total_price})

    def post(self, request):
    try:
    data = json.loads(request.body)
    product_id = data.get('product_id')
    except json.JSONDecodeError:
    logger.error("Invalid JSON data in the request body")
    return JsonResponse({'error': 'Invalid JSON data'}, status=400)

    logger.debug(f'Received product_id: {product_id}')

    if not product_id:
    logger.error("No product_id provided in the request")
    return JsonResponse({'error': 'No product_id provided'}, status=400)

    product = get_object_or_404(Product, id=product_id)
    cart_item, created = Cart.objects.get_or_create(user=request.user, product=product)
    if not created:
    cart_item.quantity += 1
    cart_item.save()

    cart_items = Cart.objects.filter(user=request.user)
    cart_data = []
    for item in cart_items:
    cart_data.append({
    'id': item.id,
    'product': {
    'id': item.product.id,
    'name': item.product.name,
    'price': float(item.product.price),
    'image': item.product.image.url if item.product.image else None,
    },
    'quantity': item.quantity,
    })
    total_price = sum(item.product.price * item.quantity for item in cart_items)

    logger.debug("Returning updated cart data as JSON response")
    return JsonResponse({'success': True, 'items': cart_data, 'subtotal': total_price})

    def get_cart_data(self, request):
    logger.debug("Received request to fetch cart data")
    cart_items = Cart.objects.filter(user=request.user)
    cart_data = []
    for item in cart_items:
    cart_data.append({
    'id': item.id,
    'product': {
    'id': item.product.id,
    'name': item.product.name,
    'price': float(item.product.price),
    'image': str(item.product.image.url) if item.product.image else None,
    },
    'quantity': item.quantity,
    })
    total_price = sum(item.product.price * item.quantity for item in cart_items)
    logger.debug(f"Returning cart data: {cart_data}")
    return JsonResponse({'items': cart_data, 'subtotal': total_price})

    def update_quantity(self, request):
    try:
    data = json.loads(request.body)
    item_id = data.get('item_id')
    action = data.get('action')
    except json.JSONDecodeError:
    return JsonResponse({'error': 'Invalid JSON data'}, status=400)

    # Retrieve the cart item
    cart_item = get_object_or_404(Cart, id=item_id)

    if action == 'increase':
    cart_item.quantity += 1
    elif action == 'decrease':
    if cart_item.quantity > 1:
    cart_item.quantity -= 1

    cart_item.save()

    # Calculate total price and prepare cart data
    cart_items = Cart.objects.filter(user=request.user)
    cart_data = [
    {
    'id': item.id,
    'product': {
    'id': item.product.id,
    'name': item.product.name,
    'price': float(item.product.price),
    'image': item.product.image.url if item.product.image else None,
    },
    'quantity': item.quantity,
    }
    for item in cart_items
    ]
    total_price = sum(item.product.price * item.quantity for item in cart_items)

    return JsonResponse({'success': True, 'items': cart_data, 'subtotal': total_price})

    def dispatch(self, request, *args, **kwargs):
    if request.method == 'POST':
    if request.path == '/cart/update_quantity/':
    return self.update_quantity(request, *args, **kwargs)
    else:
    # Handle other POST requests here
    pass
    elif request.method == 'GET':
    if request.path == '/cart/data/':
    logger.debug(f"Request Path: {request.path}")
    return self.get_cart_data(request)
    else:
    # Handle other GET requests here
    pass

    # Fall back to the default behavior if the request doesn't match any of the above conditions
    return super().dispatch(request, *args, **kwargs)


    My urls.py:


    urlpatterns = [
    path('cart/', views.CartView.as_view(), name='cart'),
    path('cart/data/', views.CartView.as_view(), name='cart_data'),
    path('cart/update_quantity/', views.CartView.as_view(), name='update_quantity'),
    ]



    My cart modal in my base.html:

    <div class='modal-cart-block'>
    <div class='modal-cart-main flex'>

    <div class="right cart-block md:w-1/2 w-full py-6 relative overflow-hidden">
    <div class="heading px-6 pb-3 flex items-center justify-between relative">
    <div class="heading5">Shopping Cart</div>
    <div
    class="close-btn absolute right-6 top-0 w-6 h-6 rounded-full bg-surface flex items-center justify-center duration-300 cursor-pointer hover:bg-black hover:text-white">
    <i class="ph ph-x text-sm"></i>
    </div>
    </div>
    <div class="time countdown-cart px-6">
    <div class=" flex items-center gap-3 px-5 py-3 bg-green rounded-lg">
    <p class='text-3xl'></p>
    <div class="caption1">Your cart will expire in <span
    class='text-red caption1 font-semibold'><span class="minute">04</span>:<span
    class="second">59</span></span>
    minutes!<br />
    Please checkout now before your items sell out!</div>
    </div>
    </div>
    <div class="heading banner mt-3 px-6">
    <div class="text">Buy <span class="text-button"> $<span class="more-price">0</span>.00 </span>
    <span>more to get </span>
    <span class="text-button">freeship</span>
    </div>
    <div class="tow-bar-block mt-3">
    <div class="progress-line"></div>
    </div>
    </div>

    <div class="list-product px-6">
    {% for item in cart_items %}
    <div data-item="2" class="item py-5 flex items-center justify-between gap-3 border-b border-line">
    <div class="infor flex items-center gap-3 w-full">
    <div class="bg-img w-[100px] aspect-square flex-shrink-0 rounded-lg overflow-hidden">
    <img style="height: 130px" src="{{ item.product.image.url }}" alt="product" class="w-full h-full">
    </div>
    <div class="w-full">
    <div class="flex items-center justify-between w-full">
    <div class="name text-button">{{ item.product.name }}</div>
    <div class="remove-cart-btn remove-btn caption1 font-semibold text-red underline cursor-pointer">
    Remove
    </div>
    </div>
    <div class="flex items-center justify-between gap-2 mt-3 w-full">
    <div class="flex items-center text-secondary2 capitalize">
    XS/white
    </div>
    <div class="product-price text-title">${{ item.product.price }}</div>
    </div>
    </div>
    </div>
    </div>
    {% endfor %}
    </div>
    <div class="footer-modal bg-white absolute bottom-0 left-0 w-full">
    <div class="flex items-center justify-center lg:gap-14 gap-8 px-6 py-4 border-b border-line">
    <div class="note-btn item flex items-center gap-3 cursor-pointer">
    <i class="ph ph-note-pencil text-xl"></i>
    <div class="caption1">Note</div>
    </div>
    <div class="shipping-btn item flex items-center gap-3 cursor-pointer">
    <i class="ph ph-truck text-xl"></i>
    <div class="caption1">Shipping</div>
    </div>
    <div class="coupon-btn item flex items-center gap-3 cursor-pointer">
    <i class="ph ph-tag text-xl"></i>
    <div class="caption1">Coupon</div>
    </div>
    </div>
    <div class="flex items-center justify-between pt-6 px-6">
    <div class="heading5">Subtotal</div>
    <div class="total-price">${{ total_price }}</div>
    </div>
    <div class="block-button text-center p-6">
    <div class="flex items-center gap-4">
    <a href='{% url "cart" %}'
    class='button-main basis-1/2 bg-white border border-black text-black text-center uppercase'>
    View cart
    </a>
    <a href='checkout.html' class='button-main basis-1/2 text-center uppercase'>
    Check Out
    </a>
    </div>
    <div
    class="text-button-uppercase continue mt-4 text-center has-line-before cursor-pointer inline-block">
    Or continue shopping</div>
    </div>
    <div class='tab-item note-block'>
    <div class="px-6 py-4 border-b border-line">
    <div class="item flex items-center gap-3 cursor-pointer">
    <i class="ph ph-note-pencil text-xl"></i>
    <div class="caption1">Note</div>
    </div>
    </div>
    <div class="form pt-4 px-6">
    <textarea name="form-note" id="form-note" rows=4
    placeholder='Add special instructions for your order...'
    class='caption1 py-3 px-4 bg-surface border-line rounded-md w-full'></textarea>
    </div>
    <div class="block-button text-center pt-4 px-6 pb-6">
    <div class='button-main w-full text-center'>Save</div>
    <div class="cancel-btn text-button-uppercase mt-4 text-center
    has-line-before cursor-pointer inline-block">Cancel</div>
    </div>
    </div>
    <div class='tab-item shipping-block'>
    <div class="px-6 py-4 border-b border-line">
    <div class="item flex items-center gap-3 cursor-pointer">
    <i class="ph ph-truck text-xl"></i>
    <div class="caption1">Estimate shipping rates</div>
    </div>
    </div>
    <div class="form pt-4 px-6">
    <div class="">
    <label for='select-country' class="caption1 text-secondary">Country/region</label>
    <div class="select-block relative mt-2">
    <select id="select-country" name="select-country"
    class='w-full py-3 pl-5 rounded-xl bg-white border border-line'>
    <option value="Country/region">Country/region</option>
    <option value="France">France</option>
    <option value="Spain">Spain</option>
    <option value="UK">UK</option>
    <option value="USA">USA</option>
    </select>
    <i
    class="ph ph-caret-down text-xs absolute top-1/2 -translate-y-1/2 md:right-5 right-2"></i>
    </div>
    </div>
    <div class="mt-3">
    <label for='select-state' class="caption1 text-secondary">State</label>
    <div class="select-block relative mt-2">
    <select id="select-state" name="select-state"
    class='w-full py-3 pl-5 rounded-xl bg-white border border-line'>
    <option value="State">State</option>
    <option value="Paris">Paris</option>
    <option value="Madrid">Madrid</option>
    <option value="London">London</option>
    <option value="New York">New York</option>
    </select>
    <i
    class="ph ph-caret-down text-xs absolute top-1/2 -translate-y-1/2 md:right-5 right-2"></i>
    </div>
    </div>
    <div class="mt-3">
    <label for='select-code' class="caption1 text-secondary">Postal/Zip Code</label>
    <input class="border-line px-5 py-3 w-full rounded-xl mt-3" id="select-code" type="text"
    placeholder="Postal/Zip Code" />
    </div>
    </div>
    <div class="block-button text-center pt-4 px-6 pb-6">
    <div class='button-main w-full text-center'>Calculator
    </div>
    <div class="cancel-btn text-button-uppercase mt-4 text-center
    has-line-before cursor-pointer inline-block">Cancel</div>
    </div>
    </div>
    <div class='tab-item coupon-block'>
    <div class="px-6 py-4 border-b border-line">
    <div class="item flex items-center gap-3 cursor-pointer">
    <i class="ph ph-tag text-xl"></i>
    <div class="caption1">Add A Coupon Code</div>
    </div>
    </div>
    <div class="form pt-4 px-6">
    <div class="">
    <label for='select-discount' class="caption1 text-secondary">Enter Code</label>
    <input class="border-line px-5 py-3 w-full rounded-xl mt-3" id="select-discount"
    type="text" placeholder="Discount code" />
    </div>
    </div>
    <div class="block-button text-center pt-4 px-6 pb-6">
    <div class='button-main w-full text-center'>Apply</div>
    <div class="cancel-btn text-button-uppercase mt-4 text-center
    has-line-before cursor-pointer inline-block">Cancel</div>
    </div>
    </div>
    </div>
    </div>
    </div>
    </div>


    My cart modal in my main.js:

    // Modal Cart
    const cartIcon = document.querySelector(".cart-icon");
    const modalCart = document.querySelector(".modal-cart-block");
    const modalCartMain = document.querySelector(".modal-cart-block .modal-cart-main");
    const closeCartIcon = document.querySelector(".modal-cart-main .close-btn");
    const continueCartIcon = document.querySelector(".modal-cart-main .continue");
    const addCartBtns = document.querySelectorAll(".add-cart-btn");

    const openModalCart = () => {
    modalCartMain.classList.add("open");
    };

    const closeModalCart = () => {
    modalCartMain.classList.remove("open");
    };

    function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
    const cookies = document.cookie.split(';');
    for (let i = 0; i < cookies.length; i++) {
    const cookie = cookies.trim();
    if (cookie.substring(0, name.length + 1) === (name + '=')) {
    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
    break;
    }
    }
    }
    return cookieValue;
    }

    const csrftoken = getCookie('csrftoken');


    const addToCart = (productId) => {
    const product_id = productId;
    console.log('Product ID:', product_id);

    fetch('/cart/', {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    'X-CSRFToken': csrftoken,
    },
    body: JSON.stringify({ product_id }),
    })
    .then(response => {
    if (!response.ok) {
    throw new Error('Failed to add product to cart');
    }
    return response.json();
    })
    .then(data => {
    if (data.success) {
    console.log('Product added successfully:', data);
    updateCartModalContent(); // Ensure this function is called immediately after adding the product
    openModalCart();
    }
    })
    .catch(error => console.error('Error:', error));
    };




    document.addEventListener("DOMContentLoaded", function() {
    const plusIcons = document.querySelectorAll(".ph-plus");
    const minusIcons = document.querySelectorAll(".ph-minus");

    plusIcons.forEach(icon => {
    icon.addEventListener("click", function() {
    const itemId = icon.dataset.itemId;
    updateQuantity(itemId, 'increase');
    });
    });

    minusIcons.forEach(icon => {
    icon.addEventListener("click", function() {
    const itemId = icon.dataset.itemId;
    updateQuantity(itemId, 'decrease');
    });
    });

    function updateQuantity(itemId, action) {
    fetch('/cart/update_quantity/', {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    'X-CSRFToken': csrftoken,
    },
    body: JSON.stringify({ item_id: itemId, action: action }),
    })
    .then(response => {
    if (!response.ok) {
    throw new Error('Failed to update quantity');
    }
    return response.json();
    })
    .then(data => {
    if (data.success) {
    // Update the cart display based on the response
    updateCartModalContent();
    }
    })
    .catch(error => console.error('Error:', error));
    }
    });


    addCartBtns.forEach((btn) => {
    btn.addEventListener('click', () => {
    const productId = btn.dataset.productId;
    console.log('Product ID from button:', productId); // Add this line
    addToCart(productId);
    });
    });

    cartIcon.addEventListener("click", openModalCart);
    modalCart.addEventListener("click", closeModalCart);
    closeCartIcon.addEventListener("click", closeModalCart);
    continueCartIcon.addEventListener("click", closeModalCart);

    modalCartMain.addEventListener("click", (e) => {
    e.stopPropagation();
    });

    function updateCartModalContent() {
    console.log('Updating cart modal content...');
    fetchCartData()
    .then((cartData) => {
    console.log('Cart data fetched:', cartData);
    const cartItemsContainer = document.querySelector('.list-product');
    cartItemsContainer.innerHTML = '';

    if (cartData.items.length === 0) {
    cartItemsContainer.innerHTML = '<p class="mt-1">No product in cart</p>';
    } else {
    cartData.items.forEach((item) => {
    const cartItem = createCartItemElement(item);
    cartItemsContainer.appendChild(cartItem);
    });
    }

    const subtotalElement = document.querySelector('.total-cart');
    const subtotal = typeof cartData.subtotal === 'number' ? cartData.subtotal : 0;
    subtotalElement.textContent = `$${subtotal.toFixed(2)}`;
    })
    .catch((error) => {
    console.error('Error fetching cart data:', error);
    });
    }




    function fetchCartData() {
    // Make an AJAX request to fetch the current cart data
    return fetch('/cart/data/')
    .then((response) => response.json())
    .then((data) => data);
    }

    function createCartItemElement(item) {
    console.log('Creating cart item element for:', item);
    const cartItemElement = document.createElement('div');
    cartItemElement.classList.add('item', 'py-5', 'flex', 'items-center', 'justify-between', 'gap-3', 'border-b', 'border-line');
    cartItemElement.dataset.item = item.id;

    const imageUrl = item.product.image || '/static/path/to/default-image.png';

    cartItemElement.innerHTML = `
    <div class="infor flex items-center gap-3 w-full">
    <div class="bg-img w-[100px] aspect-square flex-shrink-0 rounded-lg overflow-hidden">
    <img src="${imageUrl}" alt="product" class="w-full h-full">
    </div>
    <div class="w-full">
    <div class="flex items-center justify-between w-full">
    <div class="name text-button">${item.product.name}</div>
    <div class="remove-cart-btn remove-btn caption1 font-semibold text-red underline cursor-pointer">
    Remove
    </div>
    </div>
    <div class="flex items-center justify-between gap-2 mt-3 w-full">
    <div class="flex items-center text-secondary2 capitalize">
    XS/white
    </div>
    <div class="product-price text-title">$${item.product.price}</div>
    </div>
    </div>
    </div>
    `;

    return cartItemElement;
    }


    Note:

    These are my console debug statements:

    Product ID from button: 4
    main.js:496 Product ID: 4
    main.js:514 Product added successfully: Object
    main.js:587 Updating cart modal content...
    main.js:590 Cart data fetched: Object
    main.js:623 Creating cart item element for: Object
    main.js:608 Error fetching cart data: TypeError: Cannot set properties of null (setting 'textContent')
    at main.js:605:35


    These are my terminal debug statements:

    Received product_id: 4
    Returning updated cart data as JSON response
    [14/May/2024 07:42:32] "POST /cart/ HTTP/1.1" 200 169
    Request Path: /cart/data/
    Received request to fetch cart data
    Returning cart data: [{'id': 71, 'product': {'id': 4, 'name': 'Marlin Knit', 'price': 199.0, 'image': '/products/RG1.jpeg'}, 'quantity': 1}]

    Continue reading...

Compartilhe esta Página