Django入門-從0開始編寫一個(gè)投票網(wǎng)站(二)

接上一篇Django入門-從0開始編寫一個(gè)投票網(wǎng)站(一)
開始筆記的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/'可以查看結(jié)果。這里的過程是這樣的:
    有人用'polls/12/'請(qǐng)求你的網(wǎng)站時(shí),django會(huì)根據(jù)settings里的ROOT_URLCONF的值mysite.urls去加載mysite/urls.py模塊,在這個(gè)模塊里如果發(fā)現(xiàn)urlpatterns變量那么會(huì)按順序傳入去匹配正則。當(dāng)匹配'^polls/'時(shí),匹配成功會(huì)去掉polls/,把剩下的12/發(fā)送到include('polls.urls')里指定的polls/urls來進(jìn)一步處理。在polls/urls中,12會(huì)匹配r'^(?P<question_id>[0-9]+)/$',然后會(huì)這樣調(diào)用detail()方法:
    detail(request=<HttpRrquest object>, question_id='12')
    
    這里的question_id='12'來自于(?P<question_id>[0-9]+),使用小括號(hào)會(huì)把括號(hào)里面正則匹配出來的結(jié)果“捕捉”起來做為方法的參數(shù)。?P<question_id>定義了匹配出來的結(jié)果的name,[0-9]+就是一般的匹配一串?dāng)?shù)字的正則

  • 寫一些實(shí)際做事情的view
    django里每一個(gè)view都要做這樣一件事:返回HttpResponse對(duì)象或者拋出異常。就像這樣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)
    
    這里有個(gè)問題,頁面的樣式在這里是硬編碼,如果要改變頁面樣式就要改這里的代碼,按照一般的需求根本不可能這樣,所以要把頁面和代碼分開。這里可以使用django的模版系統(tǒng)。
    1. 首先在polls目錄下創(chuàng)建一個(gè)templates目錄。settings.py里的TEMPLATES描述了django加載和渲染模版的方法:有個(gè)叫DjangoTemplates的按照約定會(huì)在每一個(gè)INSTALLED_APPS下尋找templates文件夾。
    2. templates目錄下再創(chuàng)建一個(gè)polls文件夾,在這個(gè)polls目錄下創(chuàng)建一個(gè)index.html文件,換句話說,這個(gè)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 %}
    
    這個(gè)怪怪的二不像是模版語法,下面會(huì)講到,先抄著。
    然后在views里使用這個(gè)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

  • 簡(jiǎn)便函數(shù)render()
    上面的index函數(shù)可以用render()簡(jiǎn)寫成這樣:
    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錯(cuò)誤。
    templates下新建一個(gè)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})
    
    這個(gè)拋404也有簡(jiǎn)便函數(shù)get_object_or_404(),第一個(gè)參數(shù)是模型的class,其它的是任意數(shù)量的關(guān)鍵字參數(shù):
    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})
    
    還有一個(gè)get_list_or_404(),如果list為空就返回404。

  • 使用模版系統(tǒng)。
    先改寫一下detail.html
    <h1>{{ question.question_text }}<h1>
    <ul>
    {% for choice in question.choice_set.all %}
         <li>{{ choice.choice_text }}</li>
    {% endfor %}
    </ul>
    
    django的模版系統(tǒng)使用.去查找。在上面的例子里,{{question.question_text}}先當(dāng)成字典查找key,失敗了就當(dāng)對(duì)象查找屬性,這里成功了。如果還失敗,那就當(dāng)成list查找索引。
    模版語法里{{}}里面包的是變量,{%%}是塊標(biāo)簽。上面的for循環(huán)里,question.choice_set.all會(huì)被解釋成question.choice_set.all(),返回的是可迭代對(duì)象。

  • 移除templates里的硬編碼。
    polls/index.html
    <li><a href="/polls/{{question.id}}">{{question.question_text}}</a></li>
    
    <a>標(biāo)簽的屬性里有硬編碼,如果以后工程有大量的view的地址需要更改那會(huì)比較麻煩,所以更好的方式是這樣寫:
    <li><a href="{% url 'detail' question_id %}">{{question.question_text}}</a></li>
    
    模版標(biāo)簽會(huì)在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的命名空間
    真實(shí)的django應(yīng)用會(huì)有好幾個(gè)app而不是像現(xiàn)在這樣只有一個(gè)polls,所以為了使django更好的區(qū)分不同應(yīng)用中可能出現(xiàn)的名字相同的頁面,就需要在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

  • 編寫簡(jiǎn)單的表單。在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>
    
    在這里,每一個(gè)選項(xiàng)有一個(gè)單選按鈕,選擇選項(xiàng)提交表單以后會(huì)發(fā)送post請(qǐng)求到{% url 'polls: vote' question.id%},經(jīng)過urlpatterns匹配后調(diào)用views里的vote函數(shù)。
    這里<label>的for表示綁定到哪個(gè)表單元素,for屬性的值就設(shè)置成這個(gè)元素的id。
    forloop.counter的值是到目前為止循環(huán)了幾次。
    在使用post請(qǐng)求時(shí),django為了防止跨域偽造請(qǐng)求(cross site request forgeries, XSRF),提供了{% csrf_toekn% }標(biāo)簽。
    創(chuàng)建一個(gè)view函數(shù)(就是上面提到的vote函數(shù))來處理提交的數(shù)據(jù)。首先修改一下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有點(diǎn)像字典,可以通過key存取數(shù)據(jù),并且永遠(yuǎn)返回字符串,這里request.POST['choice']返回choice的id的字符串。
    如果POST的數(shù)據(jù)里choice為空那么拋出KeyError的錯(cuò)誤。
    返回的HttpResponseRedirect函數(shù)只接收一個(gè)參數(shù):將要訪問的url。
    所有情況下,post請(qǐng)求都要返回HttpResponseRedirect對(duì)象,防止用戶點(diǎn)擊返回造成2次提交。
    reverse()函數(shù)避免了url的硬編碼,這里傳的參數(shù)是一個(gè)name,參考views.py里定義的;一個(gè)是這個(gè)url里的可變部分,對(duì)于這里就是question.id。
    投票以后,vote()會(huì)跳轉(zhuǎn)到結(jié)果頁,在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})
    
    創(chuàng)建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表示前面的列表或數(shù)字不是1時(shí),返回s,否則返回空字符串(因?yàn)槲覀兿M@示的時(shí)候,1 vote,0 votes這樣比較規(guī)范的單復(fù)數(shù)形式)。

  • 使用generic view來減少代碼量
    到目前我們的views.py里的detail()、results()index()都比較簡(jiǎn)單,而且歸納起來都在做這樣一件事:通過傳過來的url去數(shù)據(jù)庫撈數(shù)據(jù)---->加載模版---->返回渲染好的模版,對(duì)于這種比較單一普通的情況,django提供了一個(gè)叫做generic views的系統(tǒng)。
    使用它大致分為以下3步:
    1. 轉(zhuǎn)化URLconf。
    2. 刪除無用的views。
    3. 引入基于generic system的新views

下面開始


  • 改進(jìn)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。

  • 改進(jìn)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個(gè)generic view:ListViewDetailView,一個(gè)用來展示列表,一個(gè)用來詳細(xì)描述對(duì)象,每一個(gè)通用的view需要給model屬性賦值一個(gè)它將要起作用的模型class,這里是Question。
    DetailView希望從url捕捉下來的id的值的名字叫做pk,所以u(píng)rlpatterns里的question_id要改成pk。
    DetailView默認(rèn)使用一個(gè)這樣格式名字的模版:<app name>/<model name>_detail.html,對(duì)應(yīng)到我們的例子里就是polls/question_detail.html,然而我們已經(jīng)有寫好的模版了,所以要替換掉默認(rèn)的,方法就是給template_name賦值我們希望它渲染的模版。
    ListView也一樣會(huì)使用默認(rèn)的,所以也要改。
    前面的教程里,我們給模版提供了一個(gè)context對(duì)象,里面包裝了question或者latest_question_list。對(duì)于DetailView,question對(duì)象是自動(dòng)提供的。因?yàn)镼uestion是一個(gè)django模型,django可以為context指定合適的key的name;但是對(duì)于ListView,django會(huì)指定的name在這里叫做question_list,然而我們的index模版里的叫做latest_question_list,所以就要通過給context_object_name賦值來手動(dòng)指定。
    訪問/polls/看看。
    戳這里查看下面的教程:Django入門-從0開始編寫一個(gè)投票網(wǎng)站(三):part5-6
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容