Python 3x Pandas Django

Django Views & Templates


This tutorial begins where the previous tutorial session left off. We’re continuing the polls application and will focus on form processing and cutting down our code before finishing this tutorial session.

Write a minimal basic html form for submiting a vote

Let’s update our poll detail template “polls/templates/polls/detail.html” from the last tutorial, so that the template contains an HTML <form> element:

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
    <legend><h1>{{ question.question_text }}</h1></legend>
    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
    {% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>

Take a look at your browser, at http://127.0.0.1:8000/polls/ and click on each questions and it will redirect to the selected detail page and the results are,

Image

A quick rundow:

• The above template displays a radio button for each question choice. The value of each radio button is the associated question choice’s ID. The name of each radio button is "choice". That means, when somebody selects one of the radio buttons and submits the form, it’ll send the POST data choice=# where # is the ID of the selected choice. This is the basic concept of HTML forms.

• We set the form’s action to {% url 'polls:vote' question.id %}, and we set method="post". Using method="post" (as opposed to method="get") is very important, because the act of submitting this form will alter data server-side. Whenever you create a form that alters data server-side, use method="post". This tip isn’t specific to Django; it’s good Web development practice in general.

forloop.counter indicates how many times the for tag has gone through its loop

• Since we’re creating a POST form (which can have the effect of modifying data), we need to worry about Cross Site Request Forgeries. Thankfully, you don’t have to worry too hard, because Django comes with a helpful system for protecting against it. In short, all POST forms that are targeted at internal URLs should use the {% csrf_token %} template tag.

Now, let’s create a Django view that handles the submitted data and does something with it. Remember, in Django Views & Templates session, we created a URLconf for the polls application that includes this line: polls/urls.py

path('/vote/', views.vote, name='vote'),

We also created a dummy implementation of the vote() function. Let’s create a real version. Import HttpResponseRedirect, reverse, F & model Choice and Add the following to polls/views.py:

from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect
from django.urls import reverse
   from django.db.models import F

from .models import Question, Choice

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes = F('votes') + 1
        selected_choice.save()
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

IMPORTANT: Always return an HttpResponseRedirect after successfully dealing with POST data. This prevents data from being posted twice if a user hits the Back button.

This code includes a few things we haven’t covered yet in this tutorial:

request.POST is a dictionary-like object that lets you access submitted data by key name. In this case, request.POST['choice'] returns the ID of the selected choice, as a string. request.POST values are always strings.

Note that Django also provides request.GET for accessing GET data in the same way – but we’re explicitly using request.POST in our code, to ensure that data is only altered via a POST call.

request.POST['choice'] will raise KeyError if choice wasn’t provided in POST data. The above code checks for KeyError and redisplays the question form with an error message if choice isn’t given.

F('votes') in the line selected_choice.votes = F('votes') + 1, to avoids a race condition.

Race Condition

If two users of your website try to vote at exactly the same time, this might go wrong: The same value, let’s say 22, will be retrieved for votes. Then, for both users the new value of 23 is computed and saved, but 24 would be the expected value. This is called a race condition.

In other words, if two Python threads execute the code in the first example above, one thread could retrieve, increment, and save a field’s value after the other has retrieved it from the database. The value that the second thread saves will be based on the original value; the work of the first thread will be lost.

If the database is responsible for updating the field, the process is more robust: it will only ever update the field based on the value of the field in the database when the save() or update() is executed, rather than based on its value when the instance was retrieved.

• After incrementing the choice count, the code returns an HttpResponseRedirect rather than a normal HttpResponse. HttpResponseRedirect takes a single argument: the URL to which the user will be redirected .

• As the Python comment above points out, you should always return an HttpResponseRedirect after successfully dealing with POST data. This tip isn’t specific to Django; it’s good Web development practice in general.

• We are using the reverse() function in the HttpResponseRedirect constructor in this example. This function helps avoid having to hardcode a URL in the view function. It is given the name of the view that we want to pass control to and the variable portion of the URL pattern that points to that view. In this case, using the URLconf we set up in Tutorial 3, this reverse() call will return a string like '/polls/1/results/'

where the 1 is the value of question.id. This redirected URL will then call the 'results' view to display the final page.

After somebody votes in a question, the vote() view redirects to the results page for the question. Let’s re-write results in view: /polls/views.py

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})

Now, create a polls/results.html template:

<h1>{{ question.question_text }}</h1>

<ul>
  {% for choice in question.choice_set.all %}
  <li>
    {{ choice.choice_text }} -- {{ choice.votes }}
    vote{{choice.votes|pluralize}}
  </li>
  {% endfor %}
</ul>

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

Now, go to /polls/1/ in your browser and vote in the question. You should see a results page that gets updated each time you vote. If you submit the form without having chosen a choice, you should see the error message.

Step1: Go to http://127.0.0.1:8000/polls/1/

Image

Step2: Select your choice from the given list and submit the form.

Image

You will see the following result page which shows all previously voted count and yours for each listed choices.

Image

Congratulations! Finally, you have successfully completed Basic Polls Application in Django Web Framework.

If you have any doubts or queries related to this chapter, get them clarified from our Python Team experts on ibmmainframer Community!