美文网首页
Django入门-从0开始编写一个投票网站(二)

Django入门-从0开始编写一个投票网站(二)

作者: 幂琪有天 | 来源:发表于2017-09-25 15:35 被阅读123次

    接上一篇Django入门-从0开始编写一个投票网站(一)
    开始笔记的part3-4。

    part3

    • 添加更多的页面,这些页面有一些不一样,在polls/views.py里:
      def detail(request, question_id):
          return HttpResponse("You're looking at question %s." % question_id)
      
      def results(request, question_id):
          response = "You're looking at the results of question %s."
          return HttpResponse(response % question_id)
      
      def vote(request, question_id):
          return HttpResponse("You're voting on question %s." % question_id)
      

    • 把这些页面跟polls.urls链接起来:
      from django.conf.urls import url
      from . import views
      urlpatterns = [
          url(r'^$', views.index, name='index'),
          url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
          url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
          url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
      ]
      
      在浏览器输入'polls/12''polls/12/results/''polls/12/vote/'可以查看结果。这里的过程是这样的:
      有人用'polls/12/'请求你的网站时,django会根据settings里的ROOT_URLCONF的值mysite.urls去加载mysite/urls.py模块,在这个模块里如果发现urlpatterns变量那么会按顺序传入去匹配正则。当匹配'^polls/'时,匹配成功会去掉polls/,把剩下的12/发送到include('polls.urls')里指定的polls/urls来进一步处理。在polls/urls中,12会匹配r'^(?P<question_id>[0-9]+)/$',然后会这样调用detail()方法:
      detail(request=<HttpRrquest object>, question_id='12')
      
      这里的question_id='12'来自于(?P<question_id>[0-9]+),使用小括号会把括号里面正则匹配出来的结果“捕捉”起来做为方法的参数。?P<question_id>定义了匹配出来的结果的name,[0-9]+就是一般的匹配一串数字的正则

    • 写一些实际做事情的view
      django里每一个view都要做这样一件事:返回HttpResponse对象或者抛出异常。就像这样polls/views.py
      from django.http import HttpResponse
      from .models import Question
      def index(request):
          latest_qeustion_list = Question.objects.order_by('-pub_date')[:5]
          output = ','.join([q.question_text for q in latest_qeustion_list])
          return HttpResponse(output)
      
      这里有个问题,页面的样式在这里是硬编码,如果要改变页面样式就要改这里的代码,按照一般的需求根本不可能这样,所以要把页面和代码分开。这里可以使用django的模版系统。
      1. 首先在polls目录下创建一个templates目录。settings.py里的TEMPLATES描述了django加载和渲染模版的方法:有个叫DjangoTemplates的按照约定会在每一个INSTALLED_APPS下寻找templates文件夹。
      2. templates目录下再创建一个polls文件夹,在这个polls目录下创建一个index.html文件,换句话说,这个html文件的路径是这样的:polls/templates/polls/index.html
      {% if latest_question_list %}
          <ul>
          {% for question in latest_question_list %}
               <li><a href="polls/{{question.id}}">{{question.question_text}}</a></li>
          {% endfor %}
          </ul>
      {% else %}
          <p>No polls are available.</p>
      {% endif %}
      
      这个怪怪的二不像是模版语法,下面会讲到,先抄着。
      然后在views里使用这个html:
      from django.http import HttpResponse
      from django.template import loader
      from .models import Question
      def index(request):
          latest_question_list = Question.objects.order_by('-pub_date')[:5]
          template = loader.get_template('polls/index.html')
          context = {
              'latest_question_list': latest_question_list,
          }
          return HttpResponse(template.render(context, request))
      
      在浏览器访问'/polls/'就是这样:
      tempalte_index.png

    • 简便函数render()
      上面的index函数可以用render()简写成这样:
      from django.shortcuts import render
      from .models import Question
      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)
      

    • 抛404错误。
      templates下新建一个detail.html:
      <p>{{ question.question_text}}</p>
      
      views.py
      from django.http import Http404
      def detail(request, question_id):
          try:
              question = Question.objects.get(pk=question_id)
          exception Question.DoesNotExist:
              raise Http404('Question does not exist')
          return render(request, 'polls/detail.html', {'question': question})
      
      这个抛404也有简便函数get_object_or_404(),第一个参数是模型的class,其它的是任意数量的关键字参数:
      from django.shorcuts import get_object_or_404, render
      def detail(request, question_id):
          question = get_object_or_404(Question, pk=question_id)
          return render(request, 'polls/detail.html', {'question': question})
      
      还有一个get_list_or_404(),如果list为空就返回404。

    • 使用模版系统。
      先改写一下detail.html
      <h1>{{ question.question_text }}<h1>
      <ul>
      {% for choice in question.choice_set.all %}
           <li>{{ choice.choice_text }}</li>
      {% endfor %}
      </ul>
      
      django的模版系统使用.去查找。在上面的例子里,{{question.question_text}}先当成字典查找key,失败了就当对象查找属性,这里成功了。如果还失败,那就当成list查找索引。
      模版语法里{{}}里面包的是变量,{%%}是块标签。上面的for循环里,question.choice_set.all会被解释成question.choice_set.all(),返回的是可迭代对象。

    • 移除templates里的硬编码。
      polls/index.html
      <li><a href="/polls/{{question.id}}">{{question.question_text}}</a></li>
      
      <a>标签的属性里有硬编码,如果以后工程有大量的view的地址需要更改那会比较麻烦,所以更好的方式是这样写:
      <li><a href="{% url 'detail' question_id %}">{{question.question_text}}</a></li>
      
      模版标签会在polls/urls里定义的URL去找name是detailurl()并传入使用。这样的话如果要更改view的url,比如改成polls/specifics/12,那么只要在polls/urls.py里这么改:
      url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail')
      

    • URL的命名空间
      真实的django应用会有好几个app而不是像现在这样只有一个polls,所以为了使django更好的区分不同应用中可能出现的名字相同的页面,就需要在urls.py里增加命名空间,像这样:
      from django.conf.urls import url
      from . import views
      app_name = 'polls'
      urlpatterns = [
          ...
      ]
      
      增加命名空间以后,{% url %}就要改一下:
      <li><a href="{% url 'polls:detail' question.id %}">{{question.question_text}}</a></li>
      

    part 4

    • 编写简单的表单。在detail.html里,加入<form>元素
      <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.choice_text }}"/>
          <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
      {% endfor %}
      <input type="submit" value="Vote"/>
      </form>
      
      在这里,每一个选项有一个单选按钮,选择选项提交表单以后会发送post请求到{% url 'polls: vote' question.id%},经过urlpatterns匹配后调用views里的vote函数。
      这里<label>的for表示绑定到哪个表单元素,for属性的值就设置成这个元素的id。
      forloop.counter的值是到目前为止循环了几次。
      在使用post请求时,django为了防止跨域伪造请求(cross site request forgeries, XSRF),提供了{% csrf_toekn% }标签。
      创建一个view函数(就是上面提到的vote函数)来处理提交的数据。首先修改一下views.py里的vote()
      from django.shortcuts import get_object_or_404, render
      from django.http import HttpResponseRedirect, HttpResponse
      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):
              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, ]))
      
      分析一波:request.POST有点像字典,可以通过key存取数据,并且永远返回字符串,这里request.POST['choice']返回choice的id的字符串。
      如果POST的数据里choice为空那么抛出KeyError的错误。
      返回的HttpResponseRedirect函数只接收一个参数:将要访问的url。
      所有情况下,post请求都要返回HttpResponseRedirect对象,防止用户点击返回造成2次提交。
      reverse()函数避免了url的硬编码,这里传的参数是一个name,参考views.py里定义的;一个是这个url里的可变部分,对于这里就是question.id。
      投票以后,vote()会跳转到结果页,在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})
      
      创建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>
      
      这里的{xx|xx}是过滤器写法,pluralize表示前面的列表或数字不是1时,返回s,否则返回空字符串(因为我们希望显示的时候,1 vote,0 votes这样比较规范的单复数形式)。

    • 使用generic view来减少代码量
      到目前我们的views.py里的detail()results()index()都比较简单,而且归纳起来都在做这样一件事:通过传过来的url去数据库捞数据---->加载模版---->返回渲染好的模版,对于这种比较单一普通的情况,django提供了一个叫做generic views的系统。
      使用它大致分为以下3步:
      1. 转化URLconf。
      2. 删除无用的views。
      3. 引入基于generic system的新views

    下面开始


    • 改进URLconf,在polls/urls.py
      from django.conf.urls import url
      from . import views
      app_name = 'polls'
      urlpatterns = [
          url(r'^$', views.IndexView.as_view(), name="index"),
          url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name="detail"),
          url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name="results"),
          url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name="vote"),
      ]
      
      注意下这里的detail跟results的question_id被改成了pk

    • 改进views。这里要移除老的detail()results()index(),使用通用的views。在views.py里:
      ...
      from django.views import generic
      class Index IndexView(generic.ListView):
            model = Question
            template_name = 'polls/index.html'
            context_object_name='latest_question_list'
            def get_queryset(self):
                return Question.objects.order('-pub_date')[:5]
      
      class DetailView(generic.View):
            model = Question
            template_name = 'polls/detail.html'
      
      class ResultsView(generic.View):
            model = Question
            template_name = 'polls/results.html'
      ...
      # vote() 不用改
      
      这里使用了2个generic view:ListViewDetailView,一个用来展示列表,一个用来详细描述对象,每一个通用的view需要给model属性赋值一个它将要起作用的模型class,这里是Question
      DetailView希望从url捕捉下来的id的值的名字叫做pk,所以urlpatterns里的question_id要改成pk
      DetailView默认使用一个这样格式名字的模版:<app name>/<model name>_detail.html,对应到我们的例子里就是polls/question_detail.html,然而我们已经有写好的模版了,所以要替换掉默认的,方法就是给template_name赋值我们希望它渲染的模版。
      ListView也一样会使用默认的,所以也要改。
      前面的教程里,我们给模版提供了一个context对象,里面包装了question或者latest_question_list。对于DetailView,question对象是自动提供的。因为Question是一个django模型,django可以为context指定合适的key的name;但是对于ListView,django会指定的name在这里叫做question_list,然而我们的index模版里的叫做latest_question_list,所以就要通过给context_object_name赋值来手动指定。
      访问/polls/看看。
      戳这里查看下面的教程:Django入门-从0开始编写一个投票网站(三):part5-6

    相关文章

      网友评论

          本文标题:Django入门-从0开始编写一个投票网站(二)

          本文链接:https://www.haomeiwen.com/subject/mvbvsxtx.html