0

I'm following a django book project based on django 1.8. It's a little outdated, but I tried and somewhat successful in translating some parts into django 2.0.

The project as of now has 2 apps, shop, then cart. shop app went out fine; when I introduced cart app, I got stuck for several days now but now without taking painstaking scrutiny to try and correct the error. The error is triggered when clicking on an product item link, supposedly it should show a product detail page like it did before the cart app, but now generates the following error:

NoReverseMatch at /1/coke-375ml

Reverse for 'cart_add' with arguments '('',)' not found. 1 pattern(s) tried: ['cart\\/add\\/(?P<product_id>[0-9]+)$']

I cannot figure out why django insist of using the cart_add view of the cart app instead of the product_detail view of the shop app. What is wrong with the code?

Below are the relevant files.

Project-wide urls.py

from django.contrib import admin
from django.urls import path, include

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('cart/', include('cart.urls')),
    path('', include('shop.urls')),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL,
                          document_root=settings.MEDIA_ROOT)

shop/urls.py

from django.urls import path
from . import views

app_name = 'shop'
urlpatterns = [
    path('', views.product_list, name='product_list'),
    path('<slug:category_slug>', 
          views.product_list, 
          name='product_list_by_category'),
    path('<int:id>/<slug:slug>',
          views.product_detail,
          name='product_detail'),
 ]

shop/views.py

from django.shortcuts import render, get_object_or_404
from .models import Category, Product

from cart.forms import CartAddProductForm

def product_list(request, category_slug=None):
    category = None
    categories = Category.objects.all()
    products = Product.objects.filter(available=True)
    if category_slug:
        category = get_object_or_404(Category, slug=category_slug)
        products = products.filter(category=category)
    return render(request,
                'shop/product/list.html',
                {'category': category,
                 'categories': categories,
                 'products': products})

def product_detail(request, id, slug):
    product = get_object_or_404(Product,
                                id=id,
                                slug=slug,
                                available=True)
    cart_product_form = CartAddProductForm()
    return render(request,
                  'shop/product/detail.html',
                  {'product': product,
                   'cart_product_form': cart_product_form})

shop/product/details.html template

{% extends "base.html" %}
{% load static %}

{% block title %}
  {% if category %}{{ category.title }}{% else %}Products{% endif %}
{% endblock %}

{% block content %}
  <div class="product-detail">
    <img src="{% if product.image %}{{ product.image.url }}{% else %}
      {% static "img/no_image.png" %}{% endif %}">
    <h1>{{ product.name }}</h1>
    <h2><a href="{{ product.category.get_absolute_url }}">{{ product.category }}</a></h2>
    <p class="price">${{ product.price }}</p>
    <form action="{% url "cart:cart_add" product_id %}" method="post">
      {{ cart_product_form }}
      {% csrf_token %}
      <input type="submit" value="Add to cart">
    </form>
    {{ product.description|linebreaks }}
  </div>
{% endblock %}

cart/urls.py

from django.urls import path
from cart import views

app_name = 'cart'
urlpatterns = [
    path('', views.cart_detail, name='cart_detail'),
    path('add/<int:product_id>', views.cart_add, name='cart_add'),
    path('remove/<int:product_id>', views.cart_remove, name='cart_remove'),
]

cart/views.py

from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
from shop.models import Product
from .cart import Cart
from .forms import CartAddProductForm

@require_POST
def cart_add(request, product_id):
    cart = Cart(request)
    product = get_object_or_404(Product, id=product_id)
    form = CartAddProductForm(request.POST)
    if form.is_valid():
        cd = form.cleaned_data
        cart.add(product=product,
                 quantity=cd['quantity'],
                 update_quantity=cd['update'])
    return redirect('cart:cart_detail')

def cart_remove(request, product_id):
    cart = Cart(request)
    product = get_object_or_404(Product, id=product_id)
    cart.remove(product)
    return redirect('cart:cart_detail')

def cart_detail(request):
    cart = Cart(request)
    return render(request, 'cart/detail.html', {'cart': cart})

cart/cart.py

from decimal import Decimal
from django.conf import settings
from shop.models import Product

class Cart(object):

    def __init__(self, request):
        """
        Initialize the cart.
        """
        self.session = request.session
        cart = self.session.get(settings.CART_SESSION_ID)
        if not cart:
            # save an empty cart in the session
            cart = self.session[settings.CART_SESSION_ID] = {}
        self.cart = cart

    def add(self, product, quantity=1, update_quantity=False):
        """
        Add a product to the cart or update its quantity
        """
        product_id = str(product.id)
        if product_id not in self.cart:
            self.cart[product_id] = {'quantity': 0,
                                     'price': str(product.price)}
        if update_quantity:
            self.cart[product_id]['quantity'] = quantity
        else:
            self.cart[product_id]['quantity'] += quantity
        self.save()

    def save(self):
        # update the session cart
        self.session[settings.CART_SESSION_ID] = self.cart
        # mark the session as "modified" to make sure it is saved
        self.session.modified = True

    def remove(self, product):
        """
        Remove a product from the cart.
        """
        product_id = str(product.id)
        if product_id in self.cart:
            del self.cart[prodcut_id]
            self.save()

    def __iter__(self):
        """
        Iterate over the items in the cart and get the products
        from the database
        """
        product_ids = self.cart.keys()
        # get the product objects and add them to the cart
        products = Product.objects.filter(id__in=product_ids)
        for product in products:
            self.cart[str(product.id)]['product'] = product

        for item in self.cart.values():
            item['price'] = Decimal(item['price'])
            item['total_price'] = item['price'] * item['quantity']
            yield item

    def __len__(self):
        """
        Count all items in the cart.
        """
        return sum(item['quantity'] for item in self.cart.values())

    def get_total_price(self):
        return sum(Decimal(item['price']) * item['quantity'] for item in self.cart.values())

    def clear(self):
        # remove cart from session
        del self.session[settings.CART_SESSION_ID]
        self.session.modified = True
JeffP
  • 539
  • 1
  • 5
  • 19
  • Did you mean `product.id` instead of `product_id` in the template? – Ignacio Vazquez-Abrams May 15 '18 at 08:16
  • No, I meant `product_id` because that's what I thought I saw. But omg, it really should be `product.id`. Thanks, you spotted it before I even finished editing. Can you put it up as an answer please. – JeffP May 15 '18 at 08:27

1 Answers1

0

The shop/product/details.html template references the non-existent product_id in {% url "cart:cart_add" product_id %}. It should probably be product.id to match the URL pattern in cart/urls.py.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • Yes, this corrected the error. I wonder though, in the course of debugging the code, I tried commenting the `form` tag portion in the template, but the error persists as if ignoring my commenting out the code. I erased the `form` tag altogether, and it the error did not occur anymore. I used this tag for the comment: `` – JeffP May 15 '18 at 08:50
  • That is for HTML comments. Django template comments are `{# ... #}`. – Ignacio Vazquez-Abrams May 15 '18 at 08:53
  • Ugh. Thanks. Lesson number 2 today. Not all that ends .html are HTML after all. – JeffP May 15 '18 at 08:56