This article is a series of steps through Django's official tutorials. This time, we'll move on to the fourth article, "Creating your first Django app, part 4."
Django tutorial summary for beginners by beginners ① (project creation ~) Django tutorial summary for beginners by beginners ② (Model, Admin) Django tutorial summary for beginners by beginners ③ (View) Django tutorial summary for beginners by beginners ④ (Generic View) Django tutorial summary for beginners by beginners ⑤ (test) Django tutorial summary for beginners by beginners ⑥ (static file) Summary of Django tutorials for beginners by beginners ⑦ (Customize Admin)
https://docs.djangoproject.com/ja/3.0/intro/tutorial04/
Write a minimal form
Place the voting form in the detail.html
created in the previous article.
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>
forloop.counter
is a value that indicates how many times the loop of the for tag was executed.
If you look at the contents, you can see that all the choices related to Question are displayed as radio button choices in the for statement.
It is necessary to insert {% csrf_token%}
when creating a POST form for CSRF measures.
Then edit polls / views.py
so that votes work well.
polls/views.py
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,)))
By request.POST ['choice']
, the value POSTed by the name of'choice' in the above Form is read as selected_choice
.
A KeyError is thrown when request.POST ['choice']
does not exist (ie, if the user does not select anything).
In that case, the form will be rendered again with an error message.
When the process ends normally, it redirects to results. (Redirect should be applied when processing such as POST is completed normally (regardless of Django))
You can prevent URL hardcoating with the reverse
function.
The usage is similar to the URL specification used in ʻindex.html`.
Now, add the views of results to polls / views.py
.
polls/views.py
from django.shortcuts import get_object_or_404, render
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
The content is almost the same as the view of detail
.
Let's also 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>
Did you notice that there is one problem so far?
In the current processing flow, first get selected_choice
from DB, calculate a new value from its votes and save it in DB.
Even if the value of votes was originally 42, if Mr. A and Mr. B vote at the same time, the place that should originally be 44 becomes 43.
It seems that such a problem is called a "conflict problem", and the following article will be helpful for the explanation of F ()
for solving it.
All records until uncle Java can use Python (No. 16)
Django has a shortcut called General View (generic view)
Retrieves data from the database according to the parameters passed via the URL, loads the template and returns the rendered template. This is so common that Django offers a shortcut called the generic view. A generic view is an abstraction of a common pattern that allows you to write an application without even writing Python code.
... apparently ...
Modify polls / urls.py
as follows
polls/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'),
]
The difference is that views.IndexView.as_view ()
etc. is passed to the view specified by path.
Also, question_id
has been renamed to pk
. (The reason will be described later)
Create the ʻIndexView` class etc. in the view.
polls/views.py
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.
ListView
and DetailView
are used.
The question_id
was renamed as pk
because it is specified as such in the DetailView
.
By default, the DetailView
generic view uses a template named template_name
attribute. (Same for ListView
)
Recommended Posts