Django Step by Step in Windows_Adding Dynamic Features

This is based on tutorial 4 after the dummy app is up and running per previous blog.

  1. the detail html is revised to be
<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% 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 %}
<input type="submit" value="Vote">
</form>

To get a deep understanding in designing the vote radio button, we need to go over the models.py file contained in Django. In the backend, there is default Qlite3 with two class/database-Question and Choice, with question_id as primary key interconnected.

import datetime
from django.db import models
from django.utils import timezone

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
	
    def __str__(self):
        return self.question_text	

    def was_published_recently(self):
	    return self.pub_date >= timezone.now() - datetime.timedelta(days=1)


class Choice(models.Model):

    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    def __str__(self):
        return self.choice_text

Django also offers a shell function to manipulate this database easily by typing in python manage.py shell. Hence the following representative commands are useful in future.

from polls.models import Choice, Question
from django.utils import timezone 
>>> q = Question(question_text="What's new?", pub_date=timezone.now()) 
>>> q.save() # Now it has an ID. 
>>> q.id 1 # Access model field values via Python attributes. 
>>> q.question_text "What's new?" 
>>> q.pub_date datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>) # Change values by changing the attributes, then calling save(). 
>>> q.question_text = "What's up?" 
>>> q.save() # objects.all() displays all the questions in the database. >>> Question.objects.all() <QuerySet [<Question: Question object (1)>]> 
 >>> Question.objects.filter(id=1) <QuerySet [<Question: What's up?>]> 
>>> Question.objects.filter(question_text__startswith='What') <QuerySet [<Question: What's up?>]> # Get the question that was published this year. >>> current_year = timezone.now().year 
>>> Question.objects.get(pub_date__year=current_year) <Question: What's up?> # Request an ID that doesn't exist, this will raise an exception. 
>>> Question.objects.get(id=2) Traceback (most recent call last):     ... DoesNotExist: Question matching query does not exist. # Lookup by a primary key is the most common case, so Django provides a # shortcut for primary-key exact lookups. # The following is identical to Question.objects.get(id=1). 
>>> Question.objects.get(pk=1) <Question: What's up?> # Make sure our custom method worked. 
>>> q = Question.objects.get(pk=1) 
>>> q.was_published_recently() True # Give the Question a couple of Choices. The create call constructs a new # Choice object, does the INSERT statement, adds the choice to the set # of available choices and returns the new Choice object. Django creates # a set to hold the "other side" of a ForeignKey relation # (e.g. a question's choice) which can be accessed via the API. 
>>> q = Question.objects.get(pk=1) # Display any choices from the related object set -- none so far. 
>>> q.choice_set.all() <QuerySet []> # Create three choices. 
>>> q.choice_set.create(choice_text='Not much', votes=0) <Choice: Not much> >>> q.choice_set.create(choice_text='The sky', votes=0) <Choice: The sky> >>> c = q.choice_set.create(choice_text='Just hacking again', votes=0) # Choice objects have API access to their related Question objects. 
>>> c.question <Question: What's up?> # And vice versa: Question objects get access to Choice objects. >>> q.choice_set.all() <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> 
>>> q.choice_set.count() 3 # The API automatically follows relationships as far as you need. # Use double underscores to separate relationships. # This works as many levels deep as you want; there's no limit. # Find all Choices for any question whose pub_date is in this year # (reusing the 'current_year' variable we created above). 
>>> Choice.objects.filter(question__pub_date__year=current_year) <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> # Let's delete one of the choices. Use delete() for that. 
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking') 
>>> c.delete() 

Now to modify the views.py for vote function work:

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

from .models import Choice, Question
# ...
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 += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

There are two points worth mentioning: 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. 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. Second, you should always return an HttpResponseRedirect after successfully dealing with POST data. This tip isn’t specific to Django; it’s just good Web development practice. 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/3/results/”.

Moreover, we should take full utilization of Django’s embedded generic view system – IndexView, DetailView and ResultsView.

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic

from .models import Choice, Question


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by('-pub_date')[:5]


class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'


class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'


def vote(request, question_id):
    ... # same as above, no changes needed.

Accordingly, the urlpattern also need to be updated to

urlpatterns = [
path(”, views.IndexView.as_view(), name=’index’),
path(‘/’, views.DetailView.as_view(), name=’detail’),
path(‘/results/’, views.ResultsView.as_view(), name=’results’),
path(‘/vote/’, views.vote, name=’vote’),
]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.