第一个django应用[part4]
写一个最小的表单
更新part3中的detail模版(polls/templates/polls/detail.html),让其包含表单<form>元素:
<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>
快速纲要:
- 上面的模版显示了一个为每个问题选项的radio按钮。radio按钮的值是关联到选择问题的id上。每个radio按钮的名字都是”choice”,这意味着当有人选择了一个radio按钮并点击了表单的“提交”,它会发送POST数据choice=#,这里的#是已经选择的ID。这属于基础的HTML表单概念。
- 设置表单的action到{% url ‘polls:vote’ question.id %},并且设置method=”post”。使用method=”post”(与其相对应的是metthod=”get”)非常重要,因为这会让提交的数据在服务器端修改。 无论何时只要数据是在服务器端修改,就要使用method=”post”。这不仅仅是django这么做,所有好的web开发都这么干。
- forloop.counter指出for标签已经循环了多少次。
- 因为创建的是POST表单,所以需要担心CSRF攻击。万幸的是django有一个非常有用的防御系统,只要简单的使用{% csrf_token %}模版标签就可以。
现在创建一个视图处理提交的数据,修改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,)))
这段代码包含了下面几点之前还没有说过:
- request.POST:是一个类似字典的对象,它让你通过关键字名访问被提交的数据。在这个例子中request.POST[‘choice’]返回一个ID,request.POST一直返回字符型数据。
- request.POST[‘choice’]:如果‘choice’不在POST数据中会抛出一个KeyError错误异常。本例中如果choice不存在,会重新显示表单,并提示一个错误信息。
- 使用了HttpResponseRedirect而不是HttpResponse。HttpResponseRedirect接受一个参数:用户将重定向到的URL。
- reverse()函数:在视图中动态构建URL。第一个位置参数是视图名(在url.py中定义),第二个关键字参数args是用来传递位置参数给视图(args需要的是一个元组,所以要加逗号)。在本例中它会返回一个类似“/polls/3/results”的字符串,这里的3就是question.id的值。
当有人点了提交后,vote()视图会重定向到results页,重写results视图:
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})
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>
使用一般视图:更少的代码更好
之前的detail()、results()和index()都是简单的根据URL从数据库中提取数据传送到相应的模版中,加载模版并传回渲染好的模版。因为这是很常用的工能django提供了一种快捷方式,被叫做“”一般视图“系统。
现在转换我们的poll应用使用一般视图,现在必须做如下步骤进行转换:
1、转换URLconf 2、删除旧视图 3、使用新的一般视图
修改URLconf
from django.urls import path
from . import views
app_name = “polls”
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”),
]
修改视图
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和DetailView。
- 每个一般视图需要知道它操作在哪个数据库模型上,这通过使用model属性提供。
- DetailView从URL中获得主键值——”pk“,所以需要在URLconf中改变question_id到pk。
默认情况下,DetailView使用的模版被命名为:
<app name>/<mode name>_detail.html
在我们的例子中它会使用模版”polls/question_detail.html“。而template_name属性用于告诉django使用指定的模版名,而不是默认的。
在之前的教程中模版都被提供了一个上下文,它包含了question和latest_question_list上下文变量。对于DetailView的question变量是自动生成的——因为使用了Django模型(Question),Django能判断适合的名字用于上下文。但是ListView自动生成的上下文变量是question_list。我们想使用latest_question_list代替,所以使用了context_object_name属性来覆盖默认值。