NoReverseMatch after switching to generic views

Hello,

I am following this tutorial

After switching to generic views, I receive NoReverseMatch

The error message: Reverse for ‘vote’ with arguments ‘(1,)’ not found. 1 pattern(s) tried: [‘polls/<int:question_id/vote/$’]

And the error line is 5.

<h1>{{ question.question_text }}</h1>
2	
3	{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
4	
5	<form action="{% url 'polls:vote' question.id %}" method=post>
6	{% csrf_token %}
7	{% for choice in question.choice_set.all %}
8	    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
9	    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
10	{% endfor %}
11	<input type="submit" value="Vote">
12	</form>

Here’s my urls.py:

from django.urls import path

from . import views

app_name = 'polls'

urlpatterns = [

    path('', views.IndexView.as_view(), name='index'),

    path('<int:pk>', views.DetailView.as_view(), name='detail'),

    path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),

    path('<int:question_id/vote/', views.vote, name='vote'),

]

Because I’m following the official tutorial, I should not receive any error. Please assist.

You’re missing the > after question_id in the path for this item in your urls.py file.

Thanks a lot. I’m very embarrassed now.

No need to be - it’s just one of those things you’ll get used to looking for.

Hello, I am having a very similar error. Everything works perfectly before switching to Generic Views but after the switch I get the error:

NoReverseMatch at /stocks/11/

Reverse for ‘vote’ with arguments ‘(’‘,)’ not found. 1 pattern(s) tried: [‘stocks/(?P<tn_id>[0-9]+)/vote/\Z’]

I have tried retyping everything that changes from before generic till after but still no luck. I have changed the model names and polls-> stocks to aid my understanding, but have tried to point out the changes. Please let me know if a mapping isn’t clear. Here is the supporting code:

#models
#in place of the Question Model in the tutorial
> class Weighbridge(models.Model):
>     weighdate = models.DateTimeField(default=now) #like pubdate
>     tn = models.AutoField(primary_key=True) 
>     supplier = models.ForeignKey(Supplier,on_delete=models.PROTECT) #like question_text

#in place of the Choice model in the tutorial
class LastLoad(models.Model):
    tn = models.ForeignKey(Weighbridge, on_delete=models.CASCADE) #like question in the tutorial
    commodity_last = models.ForeignKey(Commodity, on_delete=models.PROTECT) #like choice_text in the tutorial 
    votes = models.IntegerField(default=0) #as the tuorial

#urls
app_name = 'stocks'
urlpatterns = [
    path('',  views.IndexView.as_view(), name='index'),
    #path("<int:tn_id>/", views.detail, name='detail'),
    #path("<int:tn_id>/results/", views.results, name='results'),
    path("<int:pk>/", views.DetailView.as_view(), name="detail"),
    path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),

    path("<int:tn_id>/vote/", views.vote, name="vote"),
]
from django.shortcuts import render, get_object_or_404
from .models import Weighbridge, LastLoad
from django.http import HttpResponse, HttpResponseRedirect
from django.views import generic
from django.urls import reverse

class IndexView(generic.ListView): 
    template_name = "stocks/index.html"
    context_object_name = "latest_weighbridge_list" #like latest_question_list

    def get_queryset(self):
        #Return the last five tn
        return Weighbridge.objects.order_by('weighdate')[5:]

class DetailView(generic.DetailView):
    model = Weighbridge
    template_name = "stocks/detail.html"

class ResultsView(generic.DetailView):
    model = Weighbridge
    template_name = 'stocks/results.html'


def vote(request, tn_id):
    ticket = get_object_or_404(Weighbridge, pk=tn_id)
    selected_choice = ticket.lastload_set.get(pk=request.POST["lastload"])
    try:
        
        selected_choice = ticket.lastload_set.get(pk=request.POST["lastload"])
        
        print(selected_choice.commodity_last) #just printing for my understanding
    except (KeyError, LastLoad.DoesNotExist):
        #Redisplay the ticket voting form.
        return render(request, 
                      "stocks/detail.html", 
                      {"ticket": ticket, "error_message": "You didn't select anything,"},
                      ) 
    else:
        selected_choice.votes += 1 
        selected_choice.save()
        #Always return an HttpResponseRedirect after successfully dealing
        #with POST data. This precents being posted twice if user hits Back button.
        return HttpResponseRedirect(reverse("stocks:results", args = (ticket.tn,)))
  

Is the error caused by something trivial or is it because the models are not exactly the same as the ones in the tutorial - how do I fix it? I don’t fully understand Generic Views and have tried with the source material (and this) referenced in other questions but not really sure what I’m looking for.

Thanks
Georgia

This particular issue is frequently caused by a mis-match between the view and the template. Please post your stocks/detail.html template, particularly the section where you’re rendering the links to the vote view.

Hi Ken,

Here it is:

#stocks/detail.html

<form action="{% url 'stocks:vote' ticket.tn %}" method="post"> 
    {% csrf_token %}
    <fieldset>
        <legend><h1>{{ ticket.supplier}}</h1></legend>
        {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
        {% for lastload in ticket.lastload_set.all %}
            <input type="radio" name="lastload" id="lastload{{ forloop.counter }}" value="{{ lastload.id }}">
            <label for="lastload{{ forloop.counter }}">{{ lastload.commodity_last }}</label><br>
        {% endfor %}
    </fieldset>
    <input type="submit" value="Vote">
    </form>

And in case you need this too:

#stocks/results.html
<h1>{{ ticket.supplier }}</h1>

<ul>
{% for lastload in ticket.lastload_set.all %}
    <li>{{ lastload.commodity_last }} -- {{ lastload.votes }} vote{{ lastload.votes|pluralize }}</li>
{% endfor %}
</ul>


<a href="{% url 'stocks:detail' ticket.tn %}">Vote again?</a>


What type of object (Which model) is ticket at this point in the template? Is it a Weighbridge?

Specifically, this error:

is telling you that it’s trying to reverse the url named vote, but the parameter being supplied is null. If the line I quoted above is the correct line, then the template is trying to render a null field.

It is meant to be referencing the ticket number of the Weighbridge (I believe so) (ticket number tn is the primary key of Weighbridge) so it returns the data from the form to

“/stocks/tn/vote”

.

I did this because in the tutorial it has it like this: <form action="{% url 'polls:vote' question.id %}" method="post">
where question is question = get_object_or_404(Question, pk=question_id) in the tutorial.

I don’t understand why it is null using the Generic views but works with the views written out as functions.

So the question is, is the ticket object referenced in the template something that you’re supposed to supply to the template through the context directly, or is it something generated within the template itself?

What did your original FBV look like?

sorry what do you mean by FBV?

ticket was supplied directly using the function method:

def detail(request, tn_id): 
    ticket = get_object_or_404(Weighbridge, pk=tn_id)
    
    return render(request, "stocks/detail.html", {"ticket": ticket})

but in the Generic way it isn’t


class DetailView(generic.DetailView):
    model = Weighbridge
    template_name = "stocks/detail.html"

so that is why it’s not working but I don’t think it said anything in the tutorial about this? So I’m a bit stumped here…

Sorry, FBV = function-based view, as opposed to CBV being a Class-based view.

1 Like

I have supplied my original FBV above (didn’t know that is what it was called but I thought it would be useful anyway).

1 Like

The CBVs define defaults so that you don’t need to specify them within your code.

The tutorial code (and templates) were written to use those defaults, that’s why there were no changes mentioned in the tutorial - they aren’t necessary.

A CBV, by default, supplies the instance of the model in a DetailView under two different names, object, and the lower-case version of the model being rendered - in this case it would be weighbridge.

If you notice in the tutorial, the model name is Question, and so they reference question within the template.

This is all documented within the tutorial in the Amend views section.

This actually gives you three different ways to resolve this:

  • Change your template to refer to an object named weighbridge instead of ticket.
  • Change your template to refer to an object named object instead of ticket. (Note: This form is particularly useful if you’re using the same template for more than one model.)
  • Change the context_object_name attribute of the CBV to ticket.

Note, when you’re trying to learn how to really work with the CBVs, I always recommend people become familiar with the Classy Class-Based Views site and the CBV diagrams page. They really are very useful resources.

1 Like

ah so its because I have Weighbridge and ticket, rather than Weighbridge and weighbridge. Yes I have looked over those resources (got from another post of yours) but there’s a lot there to digest. Thanks Ken I will try one of those (or probably all of them to see if I can get it to work).

Aside - Do you have any resources/opinions on using django_plotly_dash over html?

Sorry, no. I’ve never used it and know very little about it.

ok thanks anyway. That’s what I’m finding! I’ve used plotly dash without django and found it very easy but I suspect the functionality will be more limited than just using html (plus its good to force oneself to learn html properly for once!)

Hi Ken just to confirm I tried these methods this morning and they all work fine - thank you for explaining so I can understand to solve more problems in the future without assistance.

1 Like