NoReverseMatch error with item_id

I am carrying out a project for an online grocery store. I want to have a ‘View Ratings’ button beneath each item within the store that takes users to a ratings.html page where they can view previous ratings and also submit their own using a form. However, I am running into the following error when I attempt to click the ‘View Ratings’ button beneath an item. Also, if i managed to get this fixed, is there a way I can make it so I can view the ratings in my admin area. Any help appreciated!

Reverse for 'ratings' with arguments '('',)' not found. 1 pattern(s) tried: ['ratings/(?P<item_id>[0-9]+)/\\Z']
Request Method:	GET
Request URL:	http://localhost:8000/ratings/1/
Django Version:	4.1.7
Exception Type:	NoReverseMatch
Exception Value:	
Reverse for 'ratings' with arguments '('',)' not found. 1 pattern(s) tried: ['ratings/(?P<item_id>[0-9]+)/\\Z']
Exception Location:	C:\Users\Sammc\OneDrive - Ulster University\Desktop\django_project\venv\lib\site-packages\django\urls\resolvers.py, line 828, in _reverse_with_prefix
Raised during:	customer.views.Ratings
Python Executable:	C:\Users\Sammc\OneDrive - Ulster University\Desktop\django_project\venv\Scripts\python.exe
Python Version:	3.10.7
Python Path:	
['C:\\Users\\Sammc\\OneDrive - Ulster '
 'University\\Desktop\\django_project\\groc',
 'C:\\Python310\\python310.zip',
 'C:\\Python310\\DLLs',
 'C:\\Python310\\lib',
 'C:\\Python310',
 'C:\\Users\\Sammc\\OneDrive - Ulster '
 'University\\Desktop\\django_project\\venv',
 'C:\\Users\\Sammc\\OneDrive - Ulster '
 'University\\Desktop\\django_project\\venv\\lib\\site-packages']
Server time:	Thu, 04 May 2023 14:54:43 +0000
Error during template rendering
In template C:\Users\Sammc\OneDrive - Ulster University\Desktop\django_project\groc\customer\templates\customer\ratings.html, error at line 15

Reverse for 'ratings' with arguments '('',)' not found. 1 pattern(s) tried: ['ratings/(?P<item_id>[0-9]+)/\\Z']
5	<div class="container">
6	    <div class="row justify-content-center mt-3">
7	        <div class="col-md-6 col-sm-12 text-center">
8	            <h1>Item Ratings</h1>
9	            <h3>{{ item.name }}</h3>
10	        </div>
11	    </div>
12	
13	    <div class="row justify-content-center mt-5 mb-5">
14	        <div class="col-md-8 col-sm-12 text-center">
15	            <form method="GET" action="{% url 'ratings' item_id %}">
16	                <div class="form-group">
17	                    <label for="name">Name:</label>
18	                    <input class="form-control" name="name" type="text" required>
19	                </div>
20	                <div class="form-group">
21	                    <label for="comment">Comment:</label>
22	                    <textarea class="form-control" name="comment" rows="4" required></textarea>
23	                </div>
24	                <div class="form-group">
25	                    <label for="rating">Rating:</label>

I am not sure where the issue is occuring so ill supply the code for all relveant files (groceries.html, ratings.html, models.py, urls.py, views.py, forms.py):

groceries.html:


{% block content %}

<p>
    <button style="font-size: 16px;" onclick="increaseTextSize()">+A</button>
    <button style="font-size: 16px;" onclick="decreaseTextSize()">-A</button>
</p>

<div class="container">
    <div class="row justify-content-center mt-3">
        <div class="col-md-6 col-sm-12 text-center">
            <h1>Groceries</h1>
        </div>
    </div>

    <div class="row justify-content-center mt-5 mb-5">
        <div class="col-md-8 col-sm-12 text-center">
            <form method="GET" action="{% url 'groceries-search' %}">
                <div class="md-form mt-0 active-cyan-2">
                    <input class="form-control" name="q" type="text" placeholder="Search" aria-label="Search" value="{{ request.GET.q }}">
                </div>
            </form>
        </div>
    </div>
    
    <div class="row justify-content-center mb-5">
        {% for item in groc_items %}
        <div class="col-md-4 col-sm-12 text-center mb-5">
            <img class="rounded" src="{{ item.image.url }}" width=350 height="300">
            <h5 class="mt-3">{{ item.name }}</h5>
            <p>Price: £{{ item.price }}</p>
            <p>{{ item.description }}</p>
            <a href="{% url 'ratings' item.id %}">View Ratings</a>
        </div>
        {% endfor %}
    </div>

</div>
{% endblock content %}

ratings.html:


{% block content %}

<div class="container">
    <div class="row justify-content-center mt-3">
        <div class="col-md-6 col-sm-12 text-center">
            <h1>Item Ratings</h1>
            <h3>{{ item.name }}</h3>
        </div>
    </div>

    <div class="row justify-content-center mt-5 mb-5">
        <div class="col-md-8 col-sm-12 text-center">
            <form method="GET" action="{% url 'ratings' item_id %}">
                <div class="form-group">
                    <label for="name">Name:</label>
                    <input class="form-control" name="name" type="text" required>
                </div>
                <div class="form-group">
                    <label for="comment">Comment:</label>
                    <textarea class="form-control" name="comment" rows="4" required></textarea>
                </div>
                <div class="form-group">
                    <label for="rating">Rating:</label>
                    <select class="form-control" name="rating" required>
                        <option value="" disabled selected>Select rating</option>
                        <option value="5">5 - Excellent</option>
                        <option value="4">4 - Very Good</option>
                        <option value="3">3 - Good</option>
                        <option value="2">2 - Fair</option>
                        <option value="1">1 - Poor</option>
                    </select>
                </div>
                <button type="submit" class="btn btn-primary">Submit</button>
            </form>
        </div>
    </div>

    <div class="row justify-content-center mb-5">
        {% if ratings %}
        <div class="col-md-8 col-sm-12 text-center">
            <h3>Reviews</h3>
            <hr>
            {% for rating in ratings %}
            <div class="card mb-3">
                <div class="card-body">
                    <h5 class="card-title">{{ rating.name }} - {{ rating.get_rating_display }}</h5>
                    <p class="card-text">{{ rating.comment }}</p>
                </div>
            </div>
            {% endfor %}
        </div>
        {% endif %}
    </div>

</div>
{% endblock content %}

models.py:

from django.core.validators import MinValueValidator, MaxValueValidator


class ShopItem(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField()
    image = models.ImageField(upload_to='item_images/')
    price = models.DecimalField(max_digits=5, decimal_places=2)
    category = models.ManyToManyField('Category', related_name='item')

    def __str__(self):
        return self.name

class Category(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name
    
class OrderModel(models.Model):
    created_on = models.DateTimeField(auto_now_add=True)
    price = models.DecimalField(max_digits=7, decimal_places=2)
    items = models.ManyToManyField(
        'ShopItem', related_name='order', blank=True)
    name = models.CharField(max_length=50, blank=True)
    email = models.CharField(max_length=50, blank=True)
    address = models.CharField(max_length=50, blank=True)
    city = models.CharField(max_length=50, blank=True)
    county = models.CharField(max_length=15, blank=True)
    postcode = models.CharField(max_length=7, null=True)
    is_paid = models.BooleanField(default=False)
    out_for_delivery = models.BooleanField(default=False)
    
    def __str__(self):
        return f'Order: {self.created_on.strftime("%b %d %I: %M %p")}'
    
class Rating(models.Model):
    item = models.ForeignKey('ShopItem', on_delete=models.CASCADE, related_name='ratings')
    name = models.CharField(max_length=100)
    comment = models.TextField()
    rating = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(5)])
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.name}'s rating for {self.item.name}"

urls.py


The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from customer.views import Index, About, Order, OrderConfirmation, OrderPayConfirmation, Groceries, GroceriesSearch, Ratings


urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('allauth.urls')),
    path('shop/', include('shop.urls')),
    path('', Index.as_view(), name='index'),
    path('about/', About.as_view(), name='about'),
    path('ratings/<int:item_id>/', Ratings.as_view(), name='ratings'),
    path('groceries/', Groceries.as_view(), name='groceries'),
    path('groceries/search', GroceriesSearch.as_view(), name='groceries-search'),
    path('order/', Order.as_view(), name='order'),
    path('order-confirmation/<int:pk>', OrderConfirmation.as_view(), name='order-confirmation'),
    path('payment-confirmation/', OrderPayConfirmation.as_view(), name='order-pay-confirmation'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

views.py:

from urllib import request
from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.views import View
from django.db.models import Q
from .models import ShopItem, OrderModel, Rating
from django.core.mail import send_mail
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
from django.utils.timezone import datetime
from customer.forms import RatingForm

class Index(View):
    def get(self, request, *args, **kwargs):
        return render(request, 'customer/index.html')


class About(View):
    def get(self, request, *args, **kwargs):
        return render(request, 'customer/about.html')


class Order(View):
    def get(self, request, *args, **kwargs):
        # get every item from each category
        foods = ShopItem.objects.filter(category__name__contains='Food')
        meats = ShopItem.objects.filter(category__name__contains='Meat')
        fruits = ShopItem.objects.filter(category__name__contains='Fruit')
        cleaning = ShopItem.objects.filter(category__name__contains='Cleaning')
        bakery = ShopItem.objects.filter(category__name__contains='Bakery')

        # pass into context
        context = {
            'foods': foods,
            'meats': meats,
            'fruits': fruits,
            'cleaning': cleaning,
            'bakery': bakery,
        }

        # render the template
        return render(request, 'customer/order.html', context)

    def post(self, request, *args, **kwargs):
        name = request.POST.get('name')
        email = request.POST.get('email')
        address = request.POST.get('address')
        city = request.POST.get('city')
        county = request.POST.get('county')
        postcode = request.POST.get('postcode')

        order_items = {
            'items': []
        }

        items = request.POST.getlist('items[]')

        for item in items:
            print(item)
            shop_item = ShopItem.objects.get(pk=int(item))
            item_data = {
                'id': shop_item.pk,
                'name': shop_item.name,
                'price': shop_item.price
            }

            order_items['items'].append(item_data)

            price = 0
            item_ids = []

        for item in order_items['items']:
            price += item['price']
            item_ids.append(item['id'])

        order = OrderModel.objects.create(
            price=price,
            name=name,
            email=email,
            address=address,
            city=city,
            county=county,
            postcode=postcode
        )
        order.items.add(*item_ids)

        # After everything is done, send confirmation email to user
        body = ('Thanks for your order! Your order is being prepared and will be delivered as soon as possible!\n'
            f'Total price: {price}\n'
            'Thanks for your order!')
        
        send_mail(
            'Thanks For Your Order!',
            body,
            'example@example.com',
            [email],
            fail_silently=False
        )

        context = {
            'items': order_items['items'],
            'price': price
        }

        return redirect('order-confirmation', pk=order.pk)

class OrderConfirmation(View):
    def get(self, request, pk, *args, **kwargs):
        order = OrderModel.objects.get(pk=pk)

        context = {
            'pk': order.pk,
            'items': order.items,
            'price': order.price,
        }

        return render(request, 'customer/order_confirmation.html', context)

    def post(self, request, pk, *args, **kwargs):
        data = json.loads(request.body)

        if data['isPaid']:
            order = OrderModel.objects.get(pk=pk)
            order.is_paid = True
            order.save()

        return redirect('payment-confirmation')


class OrderPayConfirmation(View):
    def get(self, request, *args, **kwargs):
        return render(request, 'customer/order_pay_confirmation.html')
    

def change_text_size(request, size):
    response = HttpResponse()
    response.set_cookie('text_size', size)
    return response

class Groceries(View):
    def get(self, request, *args, **kwargs):
        groc_items = ShopItem.objects.all()

        context = {
            'groc_items': groc_items
        }

        return render(request, 'customer/groceries.html', context)
    
class GroceriesSearch(View):
    def get(self, request, *args, **kwargs):
        query = self.request.GET.get("q")

        groc_items = ShopItem.objects.filter(
            Q(name__icontains=query) |
            Q(price__icontains=query) |
            Q(description__icontains=query)
        )

        context = {
            'groc_items': groc_items
        }

        return render(request, 'customer/groceries.html', context)
    

class Ratings(View):
    def get(self, request, *args, **kwargs):
        return render(request, 'customer/ratings.html')
    
        
def ratings(request, item_id):
    item = get_object_or_404(ShopItem, pk=item_id)
    ratings = item.ratings.all().order_by('-created_at')
    form = RatingForm()
    
    if request.method == 'POST':
        form = RatingForm(request.POST)
        if form.is_valid():
            rating = form.save(commit=False)
            rating.item = item
            rating.save()
            return redirect('ratings', item_id=item.id)
    
    context = {
        'item': item,
        'ratings': ratings,
        'form': form
    }
    return render(request, 'ratings.html', context)

forms.py:

from .models import Rating

class RatingForm(forms.ModelForm):
    class Meta:
        model = Rating
        fields = ['name', 'comment', 'rating']

Looks like a typo ratings vs Ratings? You kinda have both defined, Ratings as class based view (which is not quite correct yet but used in urls.py, thus the router error), and ratings as view function, which seems to contain the correct code, but gets not used anywhere.

How to fix things: Either use the view function in urls.py, or populate the view class with the missing functionality as already done in the view function. If you are unsure, how to reshape the view class properly - you have already done something similar above in the OrderConfirmation view :wink:

Thanks for taking the time to respond.

From what my understanding of what you said, I needed to change the urls to use the ‘def ratings()’ rather than Class ‘Ratings(View)’, like so:

from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from customer.views import Index, About, Order, OrderConfirmation, OrderPayConfirmation, Groceries, GroceriesSearch, Ratings, ratings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('allauth.urls')),
    path('shop/', include('shop.urls')),
    path('', Index.as_view(), name='index'),
    path('about/', About.as_view(), name='about'),
    path('ratings/<int:item_id>/', ratings.as_view(), name='ratings'),
    path('groceries/', Groceries.as_view(), name='groceries'),
    path('groceries/search', GroceriesSearch.as_view(), name='groceries-search'),
    path('order/', Order.as_view(), name='order'),
    path('order-confirmation/<int:pk>', OrderConfirmation.as_view(), name='order-confirmation'),
    path('payment-confirmation/', OrderPayConfirmation.as_view(), name='order-pay-confirmation'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

However, this now returns this error in the cmd terminal when i attempt to run server:

    path('ratings/<int:item_id>/', ratings.as_view(), name='ratings'),
AttributeError: 'function' object has no attribute 'as_view'

Thanks!

Oh thats not how you would use a view function in urls.py, just give it the function reference instead. The .as_view() method comes from class based views.

Whether using the view function fixes things for you I cannot tell. There might be a reason why the implementation stopped there and/or is only half done. Also, if there is more state to be handled for the ratings views later on, the class based approach might turn out as the better choice.

If you are unsure what to do, plz read URL dispatcher | Django documentation | Django, and maybe the other introduction chapters as well. Without these basics you’ll have a hard time to continue working this project.

What this error:

is actually telling you is that at this line of your template:

The value item_id is null. This means you’re either passing a null value for item_id in your context to the render function, or that you’re not supplying a value for item_id at all.

You’re rendering ratings.html in two different views - Ratings and ratings. Your ratings view is passing item into the context, but no item_id. Your Ratings view isn’t passing anything into render as a context.

It’s not clear from what you’ve posted here which view you’re intending to use or what specifically you want to render on the page.

In addition to the docs referenced above, you may also want to review the work you would have done in the Django tutorial regarding rendering templates and passing data to a template in the context.

1 Like

Okay, I changed it to a function reference and the page now shows up. However, when I submit a rating on the form, it doesnt appear in my Ratings object in the admin area.

Here is the ratings model object in my models.py:

class RatingModel(models.Model):
    item = models.ForeignKey(ShopItem, on_delete=models.CASCADE, related_name='ratings')
    name = models.CharField(max_length=100)
    comment = models.TextField()
    rating = models.PositiveSmallIntegerField(choices=(
        (5, 'Excellent'),
        (4, 'Very Good'),
        (3, 'Good'),
        (2, 'Fair'),
        (1, 'Poor')
    ))
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f'{self.item.name} ({self.rating}) by {self.name}'

I have also added everything to the admin.py:

from .models import ShopItem, Category, OrderModel, Rating

admin.site.register(ShopItem)
admin.site.register(Category)
admin.site.register(OrderModel)
admin.site.register(Rating)

Well something is messed up - your model name is now RatingModel, but you import Rating in admin.py (and also in the form class above). Did you just rename the old model (as its still Rating in your first post)? If so, then nothing should work anymore, as Python would quit with an import error.

When you change a name of a function or a class in python (well any reused identifier), you’ll have to refactor the code at every place, where it gets used, until all import/name errors are gone. If you use an IDE then there might be a refactoring functionality, which makes this alot easier, if the IDE knows how to follow python symbols in a file and across modules.

Changes on models in django are furthermore special, as they have to be replicated at the database as well. For this django has a db migration system, which you have to call after you made changes on a model, e.g.

$> ./manage.py makemigrations  # create migrations from new model changes
$> ./manage.py migrate         # apply migrations to the DB

Currently there is no way to tell, what goes wrong at the admin interface, as your last question does not reflect your current code state.

On a sidenote: Idk how strict the forum rules are regarding new issues within the same thread. Often it is a good idea not to ask new questions below, as it keeps threads shorter and prolly more helpful for others with similar issues.

I’d like to address the sidenote.

<moderator hat on>
<opinion>
The forum rules are not strict. If the first topic is suitably resolved, the subsequent topics are a follow-up or otherwise directly related to the original, and it’s the original poster asking the new question, I don’t see a problem with letting the discussion continue in the same thread.

I agree, it is of less help to those searching for solutions to the subsequent topics.

But I believe it is of more help to those who are trying to answer the questions as it allows for some sense of continuity to the discussion, makes it easier to refer back to previous posts in the thread, and avoids having the original poster having to repost all the necessary code and background information to establish the proper context for the discussion.

If the original poster decides that the follow-up deserves a new topic, I’m perfectly ok with that, too.
</opinion>

Again, this is just my opinion - this is not a topic that has been discussed in any great detail among the moderators / admins
</moderator hat off>