Продукты Django не добавляются в корзину сразу

[Продукты Django не добавляются в корзину сразу

На первом изображении показана кнопка «Добавить в корзину», при нажатии на которую продукт добавляется, и должно появиться модальное окно корзины с добавленным продуктом.

На втором изображении показано то, что я ожидаю увидеть, но вижу это только после обновления страницы.

На третьем изображении показано то, что я на самом деле вижу, когда нажимаю кнопку «Добавить в корзину», появляется просто пустая модальная корзина. .

Итак, я пытаюсь создать веб-сайт электронной коммерции Django, на котором есть модальное окно корзины, которое появляется, когда я пытаюсь добавить любой продукт, нажав кнопку «Добавить в корзину».

Хотя продукт правильно добавляется в серверную часть (что я могу проверить, перейдя в панель администратора), продукт просто не отображается сразу в моей модальной корзине, что важно для того, чтобы веб-сайт выглядел хорошо. Только когда я обновляю страницу, продукт появляется в моем модальном окне. Застрял на этом последние 3 дня, понятия не имею, что делать. Может кто-нибудь помочь мне здесь? Спасибо!

Модель моей тележки:


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}"

Мои представления.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)

Мои URL.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'),
]

Моя модальная корзина в моем 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>

Моя модальная корзина в моем 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[i].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.info('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.info('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.info('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.info('Updating cart modal content...');
  fetchCartData()
    .then((cartData) => {
      console.info('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.info('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;
}

Примечание:

Это мои операторы отладки консоли:

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

Это мои операторы отладки терминала:

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}]

drive.google.com/drive/folders/…

Jack 16.05.2024 15:50

Я добавил ссылку на свой проект через Google Диск, надеюсь, это поможет.

Jack 16.05.2024 15:51
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
2
234
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

На вашей странице более одного элемента .list-product.

Используйте более конкретный селектор, например, как вы сделали с closeCartIcon и continueCartIcon:

//const cartItemsContainer = document.querySelector('.list-product');
const cartItemsContainer = document.querySelector('.modal-cart-main .list-product');

Итак, это сработало, но промежуточный итог в модальном окне моей корзины не меняется сразу.

... оно либо остается равным 0, если это был первый добавленный продукт, либо остается тем, что было до добавления нового продукта.

В вашем селекторе есть опечатка, которая должна соответствовать существующему элементу:

//const subtotalElement = document.querySelector('.total-cart');
const subtotalElement = document.querySelector('.total-price');

изменил его, оно возвращается к 0,00

Decimal-тип subtotal будет возвращен как строка в cartData.

Разберите его как float:

//const subtotal = typeof cartData.subtotal === 'number' ? cartData.subtotal : 0;
const subtotal = parseFloat(cartData.subtotal) || 0;

Привет, Аарон, не мог бы ты взглянуть на этот вопрос stackoverflow.com/questions/78483046/…, не получая никаких отзывов по этому поводу, я не хочу, чтобы 50 баллов пропали зря. Могу поделиться с вами воспроизводимой ссылкой.

Jack 24.05.2024 03:46

Другие вопросы по теме

Мои потери при тестировании увеличиваются, но потери поездов для нейронной сети уменьшаются. Что я должен делать?
Установлен модуль Python `owiener`, но во время импорта продолжает появляться сообщение «Нет модуля с именем «owiener»»
Comfyui: python torch/изменение размера изображения – высота регулирует ширину, а ширина регулирует цвет?
Какие части несбалансированного конвейера обучения применяются к набору тестов?
Панды применяют стиль к одной строке на основе сравнения двух строк
PermissionError: [Errno 13] Разрешение отклонено. Как я могу переписать код, чтобы поместить папку «a», которая находится в папке «b», в папку «c»
Функции подмодуля маршрутизации с использованием APIRouter FastAPI
Как вернуть несколько списков в операторе возврата Python
Python-Polars: group_by столбец объединяет оставшиеся столбцы в поле структуры?
Проблема с форматированием строки Python