Django.fun

Field 'id' expected a number but got '(string)

I'm creating a T-shirt shop with variants/colors. In adding to cart, I get the error: Field 'id' expected a number but got 'woodstock'. Woodstock is the name of the shirt, but I'm passing the cartid to the function. cartid is actually being used in the remove_from_cart function, but I'm not sure why the error is thrown when I push add to cart. Here's the cart model:

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

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

class CartItem(models.Model):
    cart = models.ForeignKey(Cart, on_delete=models.CASCADE, null=True, blank=True)
    product = models.ForeignKey(Product,on_delete=models.CASCADE)
    variations = models.ManyToManyField(Variation, blank=True)
    quantity = models.IntegerField(default=1)
    line_total = models.DecimalField(default=10.99, max_digits=100, decimal_places=2)
    timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        try:
            return str(self.cart.id)
        except:
            return self.product.name

views.py

def view(request):
    print("viewing cart")
    try:
        the_id = request.session['cart_id']
    except:
        the_id = None
    if the_id:
        cart = Cart.objects.get(id=the_id)
        new_total = 0.00
        for item in cart.cartitem_set.all():
            line_total = float(item.product.price) * item.quantity
            new_total += line_total
        request.session['items_total'] = cart.cartitem_set.count()
        cart.total = new_total
        cart.save()

        context = {"cart":cart}
    else:
        empty_message = "Your cart is empty, please keep shopping"
        context = {"empty": True, "empty_message": empty_message}

    template = "cart/view.html"
    return render(request, template, context)


def add_to_cart(request, slug):
    print("add_to_cart pressed")
    request.session.set_expiry(100000000)
    try:
        the_id = request.session['cart_id']
    except:
        #create cart id
        new_cart = Cart()
        new_cart.save()
        request.session['cart_id'] = new_cart.id
        the_id = new_cart.id
    cart = Cart.objects.get(id=the_id)
    try:
        product = Product.objects.get(slug=slug)
    except Product.DoesNotExist:
        pass
    except:
        pass
    product_variations = []
    if request.method == 'POST':
        qty = request.POST['qty']
        for item in request.POST:
            key = item
            val = request.POST[key]
            try:
                v = Variation.objects.get(product=product, category__iexact=key, title__iexact=val)
                product_variations.append(v)
            except:
                pass
        cart_item = CartItem.objects.create(cart=cart, product=product)
        if len(product_variations) > 0:
            cart_item.variations.add(*product_variations)
        cart_item.quantity = qty
        cart_item.save()
        print("cart item added")


        return HttpResponseRedirect(reverse("carts:cart"))


    return HttpResponseRedirect(reverse("carts:cart"))

def remove_from_cart(request, id):
    print(id) #this is showing the item name, the reason it's not working. Either traced back to html view or urls.py
    try:
        the_id = request.session['cart_id']
        cart = Cart.objects.get(id=the_id)
    except:
        return HttpResponseRedirect(reverse("carts:cart"))
        
    cartitem = CartItem.objects.get(id=id)#error is traced to this line
    cartitem.cart = None
    cartitem.save()
    return HttpResponseRedirect(reverse("carts:cart"))

html

{% block content %}
<div class="col-sm-8 offset-sm-2">
{% if empty %}
  <h1 style="text-align:center;">{{ empty_message }}</h1>
{% else %}

  <table class="table">
    <thead>
      <th>Item</th>
      <th>Price</th>
      <th>Qty</th>
      <th></th>
    </thead>
    <tfoot>
      <tr>
        <td></td>
        <td></td>
        <td>Total: {{ cart.total }}</td>
      </tr>
    </tfoot>
    {% for item in cart.cartitem_set.all %}
    <tr>
      <td>{{ item.product }}
        {% if item.variations.all %}
        <ul>
        {% for var in item.variations.all %}
        <li> {{ var.category|capfirst }} : {{ var.title|capfirst }}</li>
        {% endfor %}
        </ul>
        {% endif %}
      </td>

      <td>{{ item.product.price }}</td>
      <td>{{ item.quantity }}</td>
      <td><a href='{% url "carts:remove_from_cart" item.id %}'>Remove</a></td>
    </tr>
    {% endfor %}
  </table>

{% endif %}
</div>
{% endblock %}

urls.py for cart

app_name='carts'

urlpatterns =[
    path('cart/', views.view, name='cart'),
    path('cart/<id>/', views.remove_from_cart, name='remove_from_cart'),
    path('cart/<slug:slug>/', views.add_to_cart, name='add_to_cart'),

]

One suggestion in researching this problem was to delete migrations and run makemigrations/migrate, but that didn't work for me. Any other suggestions or why item.id is passing the name and not the id?

Answers: 1

Answered by Iain Shelvington, Oct. 13, 2021, 7:59 a.m.

Your remove_from_cart url path matches paths like cart/<id>/ where id does not have a type so matches anything, the add_to_cart path uses cart/<slug:slug>/ but because any slug will also match id from the previous path this view will ever be used. You need to make these paths unique so that they never match the same path.

One way is to make id an integer so that slugs will not match it

path('cart/<int:id>/', views.remove_from_cart, name='remove_from_cart'),
path('cart/<slug:slug>/', views.add_to_cart, name='add_to_cart'),

The better way is to add prefixes to these paths so that they never clash

path('cart/remove/<int:id>/', views.remove_from_cart, name='remove_from_cart'),
path('cart/add/<slug:slug>/', views.add_to_cart, name='add_to_cart'),