Django add to cart not working with product variation

I am building an e-commerce website where both products with variations and products without variation can be sold. So I have a cart view which adds a product to a cart and creates a cart instance, but it is not working now that I have modified it for products with variation. So if a product has variations, eg colors and sizes, users can select the variations, eg red and XL, and the selected variations show up on this section of product_detail.html:

                    <div id="selected-variations" class="mt-5">
                        <div class="text-title">Selected Variation:</div>
                        <div class="mt-3">
                            <span id="selected-color">Color: None</span> /
                            <span id="selected-size">Size: None</span>
                        </div>
                    </div>

Now what I want is to take those selected variation values from there and then add the product to the cart with the selected values. But in doing so, the js is not working, I think my cart view logic is fine. It all seemed very easy in the beginning, since I am literally printing the selected variation values from the user above, just was supposed to take those values and create a cart instance with them. But now for some reason my main.js code won’t accept it. Is there any way to fix this?

My models.py:

class Business(models.Model):
    BUSINESS_TYPE_CHOICES = [
        ('product', 'Product Business'),
        ('service', 'Service Business'),
    ]
    seller = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name='business')
    business_name = models.CharField(max_length=100)
    description = models.TextField()
    business_slug = models.SlugField(unique=True, blank=True)
    business_type = models.CharField(max_length=20, choices=BUSINESS_TYPE_CHOICES)
    countries = models.ManyToManyField(Country)
    states = models.ManyToManyField(State)  # Add this line
    address = models.CharField(max_length=200)
    phone = models.CharField(max_length=20)
    website = models.URLField(blank=True, null=True)
    email = models.EmailField(blank=True, null=True)
    profile_picture = models.ImageField(upload_to='business_profiles/', blank=True, null=True)
    banner_image = models.ImageField(upload_to='business_banners/', blank=True, null=True)
    is_featured = models.BooleanField(default=False)

    def __str__(self):
        return self.business_name

    def save(self, *args, **kwargs):
        if not self.business_slug:
            self.business_slug = slugify(self.business_name)
        super().save(*args, **kwargs)

class Product(models.Model):
    name = models.CharField(max_length=100)
    product_slug = models.SlugField(unique=True, blank=True)
    description = models.TextField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    image = models.ImageField(upload_to='products/')
    image2 = models.ImageField(upload_to='products/', null=True, blank=True)
    business = models.ForeignKey(Business, on_delete=models.CASCADE, related_name='products')
    in_stock = models.BooleanField(default=True)
    is_popular = models.BooleanField(default=False)
    is_best_seller = models.BooleanField(default=False)
    min_delivery_time = models.PositiveIntegerField(null=True, help_text="Minimum estimated delivery time in business days")
    max_delivery_time = models.PositiveIntegerField(null=True, help_text="Maximum estimated delivery time in business days")
    has_variations = models.BooleanField(default=False)

    def __str__(self):
        return f'{self.business.business_name}, {self.name}'

    def get_json_data(self):
        data = {
            'id': self.id,
            'name': self.name,
            'price': float(self.price),
            'description': self.description,
            'images': [self.image.url, self.image2.url] if self.image and self.image2 else [],
            'min_delivery_time': self.min_delivery_time,
            'max_delivery_time': self.max_delivery_time,
        }
        return json.dumps(data)

    def save(self, *args, **kwargs):
        if not self.product_slug:
            self.product_slug = slugify(self.name)
        super().save(*args, **kwargs)


VAR_CATEGORIES = (
    ('size', 'Size'),
    ('color', 'Color'),
)

class Variation(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='variations')
    name = models.CharField(max_length=50, choices=VAR_CATEGORIES, null=True, blank=True)

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

class ProductVariation(models.Model):
    variation = models.ForeignKey(Variation, on_delete=models.CASCADE, related_name='values', null=True)
    value = models.CharField(max_length=50, null=True)
    image = models.ImageField(upload_to='product_variations/', null=True, blank=True)

    class Meta:
        unique_together = (('variation', 'value'),)

    def __str__(self):
        return f"{self.variation.product.name} - {self.variation.get_name_display()} - {self.value}"

class Cart(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='cart')
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    variation = models.ForeignKey(ProductVariation, on_delete=models.CASCADE, null=True, blank=True)
    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} - {self.variation}"

My product_detail view and cartview:

class ProductDetailView(View):
    def get(self, request, business_slug, product_slug):
        business = get_object_or_404(Business, business_slug=business_slug)
        product = get_object_or_404(Product, product_slug=product_slug, business=business)

        color_variations = Variation.objects.filter(product=product, name='color')
        size_variations = Variation.objects.filter(product=product, name='size')

        context = {
            'product': product,
            'business': business,
            'color_variations': color_variations,
            'size_variations': size_variations,
        }

        return render(request, 'business/product_detail.html', context)

"
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')
            color_variation_id = data.get('color_variation')
            size_variation_id = data.get('size_variation')
            quantity = int(data.get('quantity', 1))
        except json.JSONDecodeError:
            logger.error("Invalid JSON data in the request body")
            return JsonResponse({'error': 'Invalid JSON data'}, status=400)

        product = get_object_or_404(Product, id=product_id)

        variation = None
        if product.has_variations:
            if color_variation_id and size_variation_id:
                color_variation = get_object_or_404(ProductVariation, id=color_variation_id)
                size_variation = get_object_or_404(ProductVariation, id=size_variation_id)
                if color_variation.variation.product != product or size_variation.variation.product != product:
                    return JsonResponse({'error': 'Invalid variations selected'}, status=400)
                variation = color_variation if color_variation_id == size_variation_id else None
            elif color_variation_id:
                color_variation = get_object_or_404(ProductVariation, id=color_variation_id)
                if color_variation.variation.product != product:
                    return JsonResponse({'error': 'Invalid color variation selected'}, status=400)
                variation = color_variation
            elif size_variation_id:
                size_variation = get_object_or_404(ProductVariation, id=size_variation_id)
                if size_variation.variation.product != product:
                    return JsonResponse({'error': 'Invalid size variation selected'}, status=400)
                variation = size_variation
            else:
                return JsonResponse({'error': 'Variations required for this product'}, status=400)

        cart_item, created = Cart.objects.get_or_create(user=request.user, product=product, variation=variation)
        if not created:
            cart_item.quantity += quantity
        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 My urls.py:

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

My product_detail.html:

                    <div id="selected-variations" class="mt-5">
                        <div class="text-title">Selected Variation:</div>
                        <div class="mt-3">
                            <span id="selected-color">Color: None</span> /
                            <span id="selected-size">Size: None</span>
                        </div>
                    </div>
                    
                    {% if color_variations %}
                        <div class="choose-color mt-5">
                            <div class="text-title">Colors:</div>
                            <div class="list-color flex items-center gap-2 flex-wrap mt-3">
                                {% for color_variation in color_variations %}
                                    {% for product_variation in color_variation.values.all %}
                                        <div class="color-item w-12 h-12 rounded-xl duration-300 relative"
                                             data-color="{{ product_variation.value }}" 
                                             data-variation-id="{{ product_variation.id }}">
                                            {% if product_variation.image %}
                                                <img style="width: 100%; height: 100%; object-fit: cover" 
                                                     src="{{ product_variation.image.url }}" 
                                                     alt="color" class="rounded-xl">
                                            {% endif %}
                                            <div class="tag-action bg-black text-white caption2 capitalize px-1.5 py-0.5 rounded-sm">
                                                {{ product_variation.value }}
                                            </div>
                                        </div>
                                    {% endfor %}
                                {% endfor %}
                            </div>
                        </div>
                    {% endif %}
                    
                    {% if size_variations %}
                        <div class="choose-size mt-5">
                            <div class="heading flex items-center justify-between">
                                <div class="text-title">Sizes:</div>
                                <div class="caption1 size-guide text-red underline">Size Guide</div>
                            </div>
                            <div class="list-size flex items-center gap-2 flex-wrap mt-3">
                                {% for size_variation in size_variations %}
                                    {% for product_variation in size_variation.values.all %}
                                        <div class="size-item w-12 h-12 flex items-center justify-center text-button rounded-full bg-white border border-line"
                                             data-size="{{ product_variation.value }}" 
                                             data-variation-id="{{ product_variation.id }}">
                                            {{ product_variation.value }}
                                        </div>
                                    {% endfor %}
                                {% endfor %}
                            </div>
                        </div>
                    {% endif %}
                    
                    <div class="text-title mt-5">Quantity:</div>
                    <div class="choose-quantity flex items-center max-xl:flex-wrap lg:justify-between gap-5 mt-3">
                        <div
                            class="quantity-block md:p-3 max-md:py-1.5 max-md:px-3 flex items-center justify-between rounded-lg border border-line sm:w-[140px] w-[120px] flex-shrink-0">
                            <i class="ph-bold ph-minus cursor-pointer body1"></i>
                            <div class="quantity body1 font-semibold">1</div>
                            <i class="ph-bold ph-plus cursor-pointer body1"></i>
                        </div>
                        <div
                            class="add-cart-btn button-main whitespace-nowrap w-full text-center bg-white text-black border border-black" data-product-id="{{ product.id }}">
                            Add To Cart
                        </div>
                    </div>

                    <script>
                        document.addEventListener('DOMContentLoaded', function () {
                            let selectedColor = null;
                            let selectedSize = null;
                        
                            document.querySelectorAll('.color-item').forEach(function (item) {
                                item.addEventListener('click', function () {
                                    selectedColor = this.dataset.variationId;
                                    document.getElementById('selected-color').textContent = 'Color: ' + this.dataset.color;
                                });
                            });
                        
                            document.querySelectorAll('.size-item').forEach(function (item) {
                                item.addEventListener('click', function () {
                                    selectedSize = this.dataset.variationId;
                                    document.getElementById('selected-size').textContent = 'Size: ' + this.dataset.size;
                                });
                            });
                        
                            document.querySelector('.add-cart-btn').addEventListener('click', function () {
                                const productId = this.dataset.productId;
                                const quantity = document.querySelector('.quantity').textContent;
                        
                                fetch('/cart/add/', {
                                    method: 'POST',
                                    headers: {
                                        'Content-Type': 'application/json',
                                        'X-CSRFToken': '{{ csrf_token }}'
                                    },
                                    body: JSON.stringify({
                                        product_id: productId,
                                        color_variation: selectedColor,
                                        size_variation: selectedSize,
                                        quantity: quantity
                                    })
                                }).then(response => response.json())
                                  .then(data => {
                                      if (data.success) {
                                          alert('Product added to cart successfully!');
                                      } else {
                                          alert('Error adding product to cart.');
                                      }
                                  });
                            });
                        });
                    </script>

My main.js:

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, colorVariation, sizeVariation, quantity) => {
  console.log('Product ID:', productId);

  fetch('/cart/add/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRFToken': csrftoken,
    },
    body: JSON.stringify({
      product_id: productId,
      color_variation: colorVariation,
      size_variation: sizeVariation,
      quantity: quantity
    }),
  })
    .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() {
  let selectedColor = null;
  let selectedSize = null;

  document.querySelectorAll('.color-item').forEach(function (item) {
    item.addEventListener('click', function () {
      selectedColor = this.dataset.variationId;
      document.getElementById('selected-color').textContent = 'Color: ' + this.dataset.color;
    });
  });

  document.querySelectorAll('.size-item').forEach(function (item) {
    item.addEventListener('click', function () {
      selectedSize = this.dataset.variationId;
      document.getElementById('selected-size').textContent = 'Size: ' + this.dataset.size;
    });
  });

  document.querySelector('.add-cart-btn').addEventListener('click', function () {
    const productId = this.dataset.productId;
    const quantity = document.querySelector('.quantity').textContent;

    addToCart(productId, selectedColor, selectedSize, quantity);
  });

  const plusIcons = document.querySelectorAll(".ph-plus");
  const minusIcons = document.querySelectorAll(".ph-minus");

  plusIcons.forEach(icon => {
    icon.addEventListener("click", function () {
      const quantityElement = this.closest('.quantity-block').querySelector('.quantity');
      let quantity = parseInt(quantityElement.textContent);
      quantityElement.textContent = ++quantity;
    });
  });

  minusIcons.forEach(icon => {
    icon.addEventListener("click", function () {
      const quantityElement = this.closest('.quantity-block').querySelector('.quantity');
      let quantity = parseInt(quantityElement.textContent);
      if (quantity > 1) {
        quantityElement.textContent = --quantity;
      }
    });
  });
});

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) {
        updateCartModalContent();
      }
    })
    .catch(error => console.error('Error:', error));
}

function updateCartModalContent() {
  console.log('Updating cart modal content...');
  fetchCartData()
    .then((cartData) => {
      console.log('Cart data fetched:', cartData);
      const cartItemsContainer = document.querySelector('.modal-cart-main .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('.modal-cart-main .total-price');
      const subtotal = parseFloat(cartData.subtotal) || 0;
      subtotalElement.textContent = `$${subtotal.toFixed(2)}`;
    })
}

function fetchCartData() {
  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">
            ${item.product.color || 'N/A'}/${item.product.size || 'N/A'}
          </div>
          <div class="product-price text-title">$${item.product.price}</div>
        </div>
      </div>
    </div>
  `;

  return cartItemElement;
}

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

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

Now when I click add to cart button, my console says:

Product ID from button: 9
main.js:500 
        
        
       POST http://127.0.0.1:8000/cart/ 400 (Bad Request)
addToCart @ main.js:500
(anonymous) @ main.js:596
main.js:520 Error: Error: Failed to add product to cart
    at main.js:510:15
(anonymous) @ main.js:520
Promise.catch (async)
addToCart @ main.js:520
(anonymous) @ main.js:596
main.js:500 
        
        
       POST http://127.0.0.1:8000/cart/ 400 (Bad Request)
addToCart @ main.js:500
(anonymous) @ main.js:681
main.js:520 Error: Error: Failed to add product to cart
    at main.js:510:15