NoReverseMatchError, path, and args vs. kwargs

Hi everyone,

I have the following view: 
    def update_cart(request, id):
    cart = Cart.objects.all()
    product = Product.objects.get(pk=id)
    cart.products.add(product)
    return HttpResponseRedirect(reverse("update_cart_url", args=(product.id)))

And URL pattern in my project level urls.py (after importing update_cart from carts.views):
        path('cart/<int:product.id>/', update_cart, name="update_cart_url")

Which are called in a template: 
    <h3>{{ product.name}}<a href=" {%  url 'update_cart_url' product.id %}">Add to Cart</a></h3>

When rendering the template, I receive the following error: 
    NoReverseMatch at /webCart/detail/6/
Reverse for 'update_cart_url' with arguments '(6,)' not found. 1 pattern(s) tried: ['cart/<int:product\\.id>/$']

Note that the argument 6 above correctly matches the id of the item that the template was attempting to render.

There are lots of resources online in respect to this exception, and reading through them I believe I narrowed down my problem to several possibilities:

  1. I am using args when I should be using kwargs or vice versa

  2. I am attempting to reverse a URL by using path instead of url in urls.py

  3. Should the url pattern be defined within the app directory rather than the project directory?

Does anyone have any ideas?

Dots aren’t supported in url pattern variable names. Use an underscore instead and your problem should be solved.

1 Like

I believe the issue may be here:

The url just contains an integer, which you can then use as the parameter being passed to your view to retrieve the specific object to which it refers.

try:

path('cart/<int:id>/', update_cart, name="update_cart_url")

Also, and I’m not sure if it makes a difference here, but:

I believe args needs to be an iterable. This is one of those python gotchas that gets me all-too-often. Try either:

return HttpResponseRedirect(reverse("update_cart_url", args=(product.id,)))

or

return HttpResponseRedirect(reverse("update_cart_url", args=[product.id]))

In the first example, adding the trailing comma forces args to be a tuple and not just the singular value. In the second, you’re definitely passing a list - both of which are iterables.

Ken

1 Like

Thank you both, this is super helpful. The he tutorial is helpful as well.

So Django wasn’t parsing your URL attempt correctly - it would see the dot in <int:product.id> and “skip over” trying to convert it. It therefore didn’t hit its built-in warning that you’ve typed something that’s not a valid identifier.

I’ve made a ticket and PR to try fix this for future versions in Django, so others won’t hit the same problem again :slight_smile:


@KenWhitesell was right about the brackets for the tuple as well. I prefer using lists wherever possible for this reason. See my post.

1 Like