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<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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容