This is a continuation of Last time. This time, I'll focus on form processing and code reduction with Django.
Let's update polls / detail.html created in the tutorial of Last time to include the \
polls/templates/polls/detail.html
<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>
The points are as follows.
--csrf_token is a cross-site request countermeasure provided by Django. --forloop.counter represents the count in Django's forloop. ――After that, on the royal road of Web development, when you submit, the selected name and value values are sent to the specified action destination.
Next, let's modify the destination polls / vote. It will be a modification of view.
polls / vote
polls/views.py
from django.shortcuts import render, get_object_or_404 from .models import Question, Choice #Added Choice model from django.http import HttpResponseRedirect, HttpResponse from django.urls import reverse #Add reverse def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] context = { 'latest_question_list': latest_question_list, } return render(request, 'polls/index.html', context) def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) context = { 'question': question } return render(request, 'polls/detail.html', context) def results(request, question_id): response = "You're looking at the results of question %s." return HttpResponse(response % question_id) #ViewAction modified this time 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): return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice" }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect( reverse('polls:results', args=(question.id)) )
Fixed the vote action. I will explain the parts that have not appeared in the explanation so far.
request.POST
We have always specified request as the first argument of the action. It's finally time to use it here.
It is a dictionary-like object that can access the POST data sent by request.POST. In this case, the choice associated with the question object is chosen_set.get, and pk is specified as the get filter.
At that time, if you do request.POST ['choice'], you can access the choice of POST data. (choice is the choice of name attribute)
Similarly, request.GET also exists, but this time we are sending POST data with method = POST, so we will get it with POST.
If there is no POST choice, KeyError will be thrown. The code above checks for KeyError and redisplays the question form with an error message if there is no choice.
This isn't unique to Django, but as a best practice for web development, after successfully processing POST data, you'll be redirected to the target page.
In this case, I'm using HttpResponseRedirect provided by Django to transition to result.
In HttpResponseRedirect, use the reverse function.
The first argument of the reverse function specifies the URLconf pattern. In the second argument, specify the parameters required for the transition.
Finally, create a result page.
polls/views/py
from django.shortcuts import render, get_object_or_404 from .models import Question, Choice from django.http import HttpResponseRedirect, HttpResponse from django.urls import reverse def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] context = { 'latest_question_list': latest_question_list, } return render(request, 'polls/index.html', context) def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) context = { 'question': question } return render(request, 'polls/detail.html', context) #View added this time def results(request, question_id): question = get_object_or_404(Question, pk=question_id) context = { 'question': question } return render(request, 'polls/results.html', context) 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): return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice" }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect( reverse('polls:results', args=(question.id,)) )
First, add a View. The content is very simple.
Next, create a template.
polls/templates/polls/results.html
<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> <br /> <a href="{% url 'polls:index' %}">To TOP</a>
The result of the vote is displayed by rotating the loop by the number of choices.
This completes the functions of the app.
One of the concepts in Python is that "less code is good". Django, of course, uses this idea.
Let's make a simpler view by slightly modifying the views we have created so far.
The view I have developed so far has been the method of The Web development.
Fetch data from the database according to the parameters passed via the URL. ↓ Load the template. ↓ Render and return the template.
The above process is so common that Django has a shortcut as a generic view.
A generic view is an abstraction of common patterns that allows you to write an application without even writing Python code.
Making a generic view a generic view is a step below.
① Convert URLconf. ② Delete the old unnecessary view. ③ Set Django's generic view as the new view.
First, convert the URLconf settings.
polls/urls.py
from django.conf.urls import url from . import views app_name = 'polls' urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), #Fixed to go through IndexView class url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'), #Modified to go through DetailView class url( r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results' ), #Modified to go through DetailView class url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
The name of the pattern that matched in the second and third regular expressions has been changed to .
Next, modify the View.
from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.urls import reverse from django.views import generic from .models import Question, Choice class IndexView(generic.ListView): template_name = 'polls/index.html' context_object_name = 'latest_question_list' def get_queryset(self): 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): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice" }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect( reverse('polls:results', args=(question.id,)) )
Here, the View for displaying the template is classified. The parent of the class inherits from the newly imported generic child class.
generic.DetailView
View for detail page.
Each view needs to know which model it works for. This is defined as model proty.
Also, the DetailView generic view is supposed to capture and pass the primary key from the URL with the name ** pk **. That's why I modified it to pk in URLconf earlier.
DetailView General view by default refers to the template `` ` / _detail.html```.
This time it's a unique template name, so you need to explicitly tell the template in the template_name property. (Template_name is not required if you are creating according to the naming convention.)
Also, the context variable uses the model name.
generic.ListView
The default template naming convention for ListView is `` ` / _list.html```.
Since this is our own template, we will use the template_name property.
In addition, the acquired data list by default is stored in the context variable `` ` _list```.
This time, the entire app uses a context variable called latest_question_list.
latest_question_list
Therefore, the context_objext_name property overwrites the name of the context variable.
context_objext_name
Finally, define the `get_queryset ()` method that stores the value in the context variable.
`get_queryset ()`
This time,
--Form creation --Use of general-purpose view
I explained.
Form creation was no different than frameworks in other languages, but generic views were a bit quirky.
As I remember, as in the tutorial, I was able to select a view from the URL, select a template from the view, render it, and return it as an HttpResponse with almost no code.
You'll have to get used to it, but it's worth remembering. (In the end, React will make a table, so it may not make sense ...)
Next time, I would like to introduce an automated test using the voting app I created.
GitHub
-Get started with Django! ~ Tutorial ① ~ -Get started with Django! ~ Tutorial ② ~ -Get started with Django! ~ Tutorial ③ ~ -Get started with Django! ~ Tutorial ④ ~ -Get started with Django! ~ Tutorial ⑤ ~ -Get started with Django! ~ Tutorial ⑥ ~
Recommended Posts