第一个django应用[part4]

第一个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.

上面使用了两个一般视图:ListViewDetailView

  • 每个一般视图需要知道它操作在哪个数据库模型上,这通过使用model属性提供。
  • DetailView从URL中获得主键值——”pk“,所以需要在URLconf中改变question_id到pk。

默认情况下,DetailView使用的模版被命名为:

<app name>/<mode name>_detail.html

在我们的例子中它会使用模版”polls/question_detail.html“。而template_name属性用于告诉django使用指定的模版名,而不是默认的。

在之前的教程中模版都被提供了一个上下文,它包含了questionlatest_question_list上下文变量。对于DetailViewquestion变量是自动生成的——因为使用了Django模型(Question),Django能判断适合的名字用于上下文。但是ListView自动生成的上下文变量是question_list。我们想使用latest_question_list代替,所以使用了context_object_name属性来覆盖默认值。

Comments are closed.