Django学习4
在经过前三节的学习后,基本了解了数据库的连接,views的使用,但是还需要了解如何传回数据并处理,这里第四节学习的内容就是如何去用form来获取数据。
step1:熟悉form
将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
是表示当前循环进行了几次。而{% csrf_token %}
是一个Django 提供的template标记用来防止跨站伪造请求。
这之后由于表单提交给了vote页面,所以去重写vote的view来达到能够登记提交的查询的目的。
题外话,这里可以探讨一下id和name的作用,可以参考 stackoverflow the difference between id and name。
在vote这个view中就要实现:接收信息,查询信息,更新信息,显示结果页面,代码如下:
from django.shortcuts import render, get_object_or_404
# Create your views here.
from django.http import HttpResponse, HttpResponseRedirect
from .models import Question
from django.template import loader
from django.http import Http404
from django.urls import reverse
def vote(request, question_id):
#request直接接收信息
question = get_object_or_404(Question, pk=question_id)
try:
#用get来查询是否存在choice,并且raise自己定义的error_message
select_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:
#更新数据并且重定向到result页面
select_choice.vote += 1
select_choice.save()
return HttpResponseRedirect(
reverse('polls:results', args=(question.id,))
)
上述代码中有一些需要解释的代码:
request.post
是一个字典型的数据,可以通过key name来访问传入的数据,需要注意的是其中的数据皆为string类型request.post['choice']
如果没有在传入数据中有有效值的情况下,会raise一个keyerror错误,当出现这个错误的时候,本代码的处理方式就是重新返回detail.html并给出错误信息。- 当处理完成post数据后,一般都会使用
HttpResponseRedirect()
来重定位页面。这里使用了reserve
这个函数来定向到result这个view并且给出了question_id,也就是说这里的reserve('polls:result', args=(question.id))
相当于重定向到了"polls/question.id/results"页面。
重写一下result view来满足需求:
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(quest, 'polls/results.html', {'question':question})
添加一个result的页面:
<body>
<h1>{{ question.question_text }}</h1>
<ur>
{% for choice in question.choice_set.all %}
<li>
{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}
</li>
{% endfor %}
</ur>
<a href="{ url 'polls:detail' question.id %}">Vote again?</a>
</body>
上述代码每次就会显示每个choice的选择情况。值得注意学习的是这里的{{ choice.votes|pluralize }}
是根据votes的情况来加复数"s"。
需要补充的是,这个程序是有问题的,当两个人同时使用这个vote时,会导致数据的错误,可以使用F()函数来避免这一问题,详情见F() 。
step2:通用view
我们可以看到在detail中和results中的view的代码大同小异,此时我们就可以来简化代码,所以我们可以利用Django来做一个通用类的template来简化代码。
我们需要经历以下步骤:
- 改变urls的配置
- 删除老的无用代码
- 使用新的generic views
那么首先,需要对polls/urls.py进行修改:
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'),
]
而后修改polls/views.py文件为:
from django.views import generic
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'
上述代码利用了两个generic view,分别是Listview和DetailView。他们分别用于显示一个对象的list和显示一个具体类型的对象的detail页面。
每一个generic都需要知道它所作用的model,所以提供了model属性来指明model。
DetailView需要一个叫做pk的主键,所以在urls.py中我们使用pk来代替question_id。
在DetailView中,通常会使用"< app bane>/< model name>_detail.html"不过我们这里用template_name来指定了页面,同理ListView。
在DetailView中,当我们使用了model:Question,就会自动命名object的名字是question,然后我实验了ListView好像也是自动提供的,(把question改名程question2就不行了)。同时DetailView还会自动提供context的名字为question_list不过我们可以自己用context_objetc_name来改变名字。