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

接上一篇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[0-9]+)/$', views.detail, name='detail'),
        url(r'^(?P[0-9]+)/results/$', views.results, name='results'),
        url(r'^(?P[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[0-9]+)/$',然后会这样调用detail()方法:
    detail(request=, question_id='12')
    
    这里的question_id='12'来自于(?P[0-9]+),使用小括号会把括号里面正则匹配出来的结果“捕捉”起来做为方法的参数。?P定义了匹配出来的结果的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 %}
        
    {% else %}
        

    No polls are available.

    {% 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/'就是这样:
    Django入门-从0开始编写一个投票网站(二)_第1张图片
    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:

    {{ question.question_text}}

    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

    {{ question.question_text }}

      {% for choice in question.choice_set.all %}
    • {{ choice.choice_text }}
    • {% endfor %}

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


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

part 4

  • 编写简单的表单。在detail.html里,加入
    元素

    {{ question.question_text }}

    {% if error_message %}

    {{ error_message }}

    {% endif %} {% csrf_token %} {% for choice in question.choice_set.all %}
    {% endfor %}
    在这里,每一个选项有一个单选按钮,选择选项提交表单以后会发送post请求到{% url 'polls: vote' question.id%},经过urlpatterns匹配后调用views里的vote函数。
    这里的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模版

    {{ question.question_text }}

      {% for choice in question.choice_set.all %}
    • {{ choice.choice_text}} -- {{ choice.votes}} vote{{choice.votes|pluralize}}
    • {% endfor %}
    Vote again?
    这里的{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[0-9]+)/$', views.DetailView.as_view(), name="detail"),
        url(r'^(?P[0-9]+)/results/$', views.ResultsView.as_view(), name="results"),
        url(r'^(?P[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:ListView和DetailView,一个用来展示列表,一个用来详细描述对象,每一个通用的view需要给model属性赋值一个它将要起作用的模型class,这里是Question
    DetailView希望从url捕捉下来的id的值的名字叫做pk,所以urlpatterns里的question_id要改成pk
    DetailView默认使用一个这样格式名字的模版:/_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开始编写一个投票网站(二))