[Django]Django官方文档的投票Web应用笔记

来源:互联网 发布:非常嫌疑犯影评知乎 编辑:程序博客网 时间:2024/05/22 14:35

摘要:Django官方文档(1.8.2)的投票web应用,django的入门示例,展示了一个django网页应用建立的大致过程。


<创建项目>

$ django-admin startproject mysite

在目标文件夹下打开命令行,输入创建django项目的指令(其中mysite为自定义的项目名),将会创建一个django项目的初始化文件夹,名为mysite。文件夹内有子文件夹mysite,内有文件setting.py,是该项目的基础设置文件。对该文件进行一些初始化修改:

(1)找到 LANGUAGE_CODE ,改为'zh-hans',修改文字为汉字;
(2)找到 TIME_ZONE ,改为'Asia/Shanghai',修改为本地时区。


<数据库初始化>
$ python manage.py migrate
在manage.py文件的路径下打开命令行,执行上面的指令(manage.py是协助项目管理的文件),将会创建一个空白的数据库,该数据库内没有任何表单,表单将通过创建模型而建立。

<运行服务器>
$ python manage.py runserver
执行指令后将会运行一个轻量级的服务器,然后可以通过 http://127.0.0.1:8000/ 或 http://localhost:8000/ 访问django的欢迎页面。可以手动选择服务器的端口,只需在指令后加上端口的数字,如;
$ python mange.py runserver 8080
则访问方式变更为http://127.0.0.1:8080/ 或 http://localhost:8080/

<创建应用>
$ python mange.py startapp polls

和创建项目类似,创建应用的指令由startproject变为startapp,执行指令后创建应用polls(投票),在项目文件夹里生成应用文件夹polls。但是此时应用没有完全创建成功,我们需要在项目内安装该应用。安装方法是在上文提到的settings.py文件内找到:

INSTALLED_APPS = (...)

然后在该元组的末尾添加上应用,示例:

INSTALLED_APP = (    .......,    'polls',)

至此应用创建并安装成功。

<创建模型>
# -*- coding: utf-8 -*-from __future__ import unicode_literalsimport datetimefrom django.db import modelsfrom django.utils import timezone# Create your models here.class Question(models.Model):    question_text = models.CharField(max_length=200)    pub_date = models.DateTimeField('date published')    def __str__(self):        return self.question_text    def was_published_recently(self):        '''模型也是类,所以也可以有方法。timezone.now-timedelta(days=1)表示一天前'''        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)class Choice(models.Model):    question = models.ForeignKey(Question)    choice_text = models.CharField(max_length=200)    votes = models.IntegerField(default=0)    def __str__(self):        return self.choice_text

模型代码放在路径polls/models.py的文件内,上文已经提到,模型是保存进数据库里的表的格式定义。上述代码创建了两种模型:Question和Choice(它们都是models.Model的子类)。其中表Question有两列,问题描述question_text和发布时间pub_date。表Choice同理,其中question是Question的子类。注意到代码中每一列除了ForeignKey外都是某种Field,这是数据保存进数据库的格式,类似的Field有很多种。

<激活模型>
创建模型后还需要激活模型才能把模型描述的表放进数据库里。执行下述指令告诉django模型文件发生了变动:
$ python manage.py makemigrations polls

再执行下述指令在数据库中创建模型所对应的表:

$ python manage.py migrate
执行完毕后数据库依然是空白的,但不再是空的了,现在数据库里已经有待填入数据的表了。接下来通过django的shell来测试一下模型的工作情况。

<shell>
$ python manage.py shell
执行指令将在命令行里进入熟悉的python的shell界面,但这个shell是已经准备好django的环境了。接下来示范在shell里对数据库做一些数据修改,了解它的工作情况。
>>> from polls.models import Question, Choice #从polls.models包导入两个模型(类)>>> Question.objects.all()<QuerySet []> 或 []  #此时表Question是空的,Question.objects.all()是查询表所有数据的操作#创建并保存数据>>> from django.utils import timezone#timezone,django里的时间辅助>>> q = Question(question_text="What's new?", pub_date=timezone.now())>>> q.save()#查询>>>q.id1>>>q.question_text"what's new?"#查看问题q所对应的Choice表#因为Choice()里有models.ForeignKey(Question),所以Question的实例有choice_set属性>>> q.choice_set.all()(QuerySet [])#为问题q创建投票选项>>> q.choice_set.create(choice_text='Not much',votes=0)>>> q.choice_set.create(choice_text='The sky',votes=0)>>> q.choice_set.create(choice_text='Just hacking again',votes=0)#再次查看问题q所对应的Choice表>>> q.choice_set.all()(QuerySet [(Choice:Not much),(Choice:The sky),(Choice:Just hacking again)])

<管理用户>

运行服务器后,在网址后加上/admin,如:http://localhost:8000/admin即可进入管理员界面,可以直观的对数据库内的数据进行浏览和管理,详见:Django官方文档-管理站点

<URL-视图-模板>

Django管理网页访问的原理是:将一个URl网址在urls.py文件里进行正则表达式匹配,匹配成功则将运行视图文件views.py里对应的视图函数,视图函数再对请求做出相应的处理。
(1)文件路径 mysite/urls.py
from django.conf.urls import url,includefrom django.contrib import adminurlpatterns = [    url(r'^admin/', admin.site.urls),    url(r'^polls/',include('polls.urls',namespace="polls")),]

新增一个导入include,和一行url()函数,意思是匹配正则表达式成功的,则跳转到polls.urls处理链接URL。nameplace是URL名字的命名空间,避免应用过多时命名混乱。跳转到应用polls的urls.py文件是为了保证主项目的urls.py的整洁,相当于应用的urls.py是主项目名下的一个子文件夹,提高简洁性和可管理性。


(2)文件路径 polls/urls.py (需要自己创建)
from django.conf.urls import urlfrom . import viewsurlpatterns = [    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'),        ]
上述代码中的views.detail,views.results等都是views.py里的视图函数(接下来就会创建)。正则表达式中的?P<question_id>作用是将数字作为参数传给视图函数。

(3)文件路径  polls/views.py
from django.http import Http404,HttpResponseRedirect, HttpResponsefrom django.shortcuts import render,get_object_or_404from django.core.urlresolvers import reversefrom .models import Question,Choice# Create your views here.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)def detail(request,question_id):    try:        question = Question.objects.get(pk=question_id)    except Question.DoesNotExist:        raise Http404("Question does not exist")    return render(request,'polls/detail.html',{'question':question})def vote(request,question_id):    p =  get_object_or_404(Question,pk=question_id)    try:        selected_choice = p.choice_set.get(pk=request.POST['choice'])    except (KeyError,Choice.DoesNotExist):        return render(request,'polls/detail.html',{            'question':p,            'error_messange':"You didn't select a choice"            })    else:        selected_choice.votes += 1        selected_choice.save()        return HttpResponseRedirect(reverse('polls:result',args=(p.id,)))def results(request,question_id):    question = get_object_or_404(Question,pk=question_id)    return render(request,'polls/results.html',{'question':question})

这些函数是视图函数,它们都接受request作为参数,并自定义是否接受其它参数。比如detail,results,vote都接受了question_id作为参数,正则表达式中的 ?P<question_id> 会将这个参数传给视图函数。接下来结合模板(html文件)来研究视图函数是怎样运作的。polls应用的html文件集合放在路径为polls/templates/polls/的文件夹(自行创建)中,Django会自动在templates文件夹中搜寻模板文件。

①首先看index函数对应的polls/index.html

文件:mysite/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 %}{% else %}     <p>No polls are avaliable.</p>{% endif %}
index视图:
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)

注意到render,第二个参数指定了模板文件的位置,第三个参数是一个字典,键值对为'latest_question_list':latest_question_list,键为字符串,值是从数据库获取的问题列表,这个键值对指导模板文件获取到问题列表。注意到模板文件开头的{% if latest_question_list %}:如果模板文件获得了问题列表,则执行if操作,否则显示 No polls are avaliable.
该模板可以正常工作了,但还有一个关于硬编码的问题,在<li><a href="/polls/{{ question.id }}/">的 /polls/ 处,这种表示方法定死了URL必须以/polls/开头,一旦URL需要作出改动,每个模板的硬编码都要重新更正一遍,所以这不是一个好的表示方法。更好的表达方法是利用上urls.py中url()函数定义的name参数和使用{% url %}模板标签,所以将
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
修改为
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
模板标签{% url %}会去urls.py里查询参数name为'detail'的url()函数。
但如果项目发展到很壮大的程度,应用数目数不胜数时,怎样更规范的管理URL名字呢?答案是命名空间,也就是上文的urls.py文件里提到的namespace(在项目的urls.py里)。需要做出的修改很简单,因为已经设定好命名空间为'polls'了,所以只要将'detail' 改为'polls:detail’就好了。

所以最终的index.html文件是这样的:
{% if latest_question_list %}    <ul>    {% for question in latest_question_list %}        <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>    {% endfor %}{% else %}     <p>No polls are avaliable.</p>{% endif %}

②detail模板与视图函数


文件: mysite/polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1><ul>{% for choice in question.choice_set.all %}    <li> {{ choice.choice_text }}</li>{% endfor %}</ul>


detail视图函数:

def detail(request,question_id):    try:        question = Question.objects.get(pk=question_id)    except Question.DoesNotExist:        raise Http404("Question does not exist")    return render(request,'polls/detail.html',{'question':question})

与index的情况类似,render的第二个参数指定了模板的位置,第三个参数字典指导模板找到它所需的question对象。特殊的是在函数首先会尝试(try)从数据库中获取question_id所对应的数据,如果数据存在,则正常返回render();若数据不存在(Question.DoesNotExist),则会引发Http404错误,并有错误信息"Question does not exist"(注意到Http404()也是一个函数)。当然这只是一个利用无序列表<ul>和循环语句来展示choice_text的简单html。下面我们来试下表单,看看表单如何在Django中是如何工作的。

更新后的detail.html

<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.id }}" />    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />{% endfor %}<input type="submit" value="vote"/></form>

解释:
(1)如果有error_message则显示error_message,erro_message由下文的vote()函数给出给出。
(2)表单的 action设置为"{% url 'polls:vote' question.id %}",用到了上文提到的命名空间。method设置为 "post"而不是"get",是因为这个表单的提交行为会改变服务器端的数据。
(3){% csrf_token %}是一个防御跨站点伪造请求的模板标签,所有针对内部URL的POST表单都应该使用{% csrf_token %}
(4)表单里的按钮。每个按钮都有一个id为"choice数字",数字由forloop.counter(指示for标签循环次数)提供,label标签里的for="choice{{ forloop.counter }}"将该label绑定在id为该字符串的按钮上。

③vote函数

def vote(request,question_id):    p =  get_object_or_404(Question,pk=question_id)    try:        selected_choice = p.choice_set.get(pk=request.POST['choice'])    except (KeyError,Choice.DoesNotExist):        return render(request,'polls/detail.html',{            'question':p,            'error_messange':"You didn't select a choice"            })    else:        selected_choice.votes += 1        selected_choice.save()        return HttpResponseRedirect(reverse('polls:results',args=(p.id,)))

p是对象问题,selected_choice = p.choice_set.get(pk=request.POST['choice'])中的request.POST是一个类似字典的对象(choice由POST数据提供),所以request.POST['choice']返回字符串形式的Choice的ID,如果POST数据没有提供choice,则会引发KeyError,然后给detail.html传递一个error_message(还记得吗?在detail.html下面有提到过)。如果selected_choice成功取得,则增加该Choice的votes(得票数),然后利用HttpResponseRedirect()返回一个重定向的URL。在这个例子中,HttpResponseRedirect()中使用了reverse()函数,reverse('polls:results;,args=(p.id,))可能会返回一个类似于 '/polls/3/results/'的字符串,其中的3由p.id提供(p是这个函数里的Question对象),格式由'polls:result'这个命名空间对应的URL格式提供。
所以,当有人对Question进行投票后,vote()视图将请求重新定向到结果页面。

④results函数

def results(request,question_id):    question = get_object_or_404(Question,pk=question_id)    return render(request,'polls/results.html',{'question':question})、

文件:mysite/polls/templates/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 %}<a href = "{% url 'polls:detail' question.id %}"> Vote Again? </a>

结果视图和模板将数据库里的问题和投票数信息显示出来。

至此,这个投票应用已经可以正常工作了,但学习任务还没有结束,我们还可以做些什么:①改良代码;②添加测试并修正错误(上面的代码包含了一些隐式的错误,我们将通过测试发现并修正这个错误);③关于静态文件。


< 改良代码 >


我们上面的视图函数的代码都很简单,但仍然存在着冗余的问题,比如detail()函数和results()函数几乎一模一样,不同的只是模板的名字。实际上这些视图反应了基本的Web开发中的一个常见的情况:根据URL中的参数从数据库中获取数据,载入模板文件,然后返回视图函数渲染后的模板。这个情况非常常见,所以Django提供了一个名为'generic views'的系统,让程序员可以方便的处理业务。下面来看看我们如何利用这个系统来改进视图文件,使其更加简洁易懂。(建议先不着急更改自己的视图文件,起码先别删了之前的函数。新的视图文件虽然简洁,但也很抽象,不适合新手去理解,这也是教程开始时用了直来直去的方式编写视图函数的原因。正如人们常说的,别过早的做优化,所以先保证自己已经了解了URL-模板-视图这一过程和这些过程的工作原理,再来优化也不迟)。

文件:polls/views.py
from django.shortcuts import get_object_or_404, renderfrom django.http import HttpResponseRedirectfrom django.core.urlresolvers import reversefrom django.views import genericfrom .models import Question,Choiceclass IndexView(generic.ListView):    template_name = 'polls/index.html'    context_object_name = 'latest_question_list'    def get_queryset(self):'''返回最新发布的五个问题'''        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):    p =  get_object_or_404(Question,pk=question_id)    try:        selected_choice = p.choice_set.get(pk=request.POST['choice'])    except (KeyError,Choice.DoesNotExist):        return render(request,'polls/detail.html',{            'question':p,            'error_messange':"You didn't select a choice"            })    else:        selected_choice.votes += 1        selected_choice.save()        return HttpResponseRedirect(reverse('polls:results',args=(p.id,)))

index(),detail(),results()三个视图函数处理的都是简单普遍的过程,因此他们的任务可以由'generic view’系统接管,所以把这些视图函数更新为generic的子类,而vote()函数比较复杂,不做更改。注意generic.ListView和generic.DetailView,这是两个'generic view'系统的通用视图,分别抽象"显示一个对象列表"和"显示一个特定类型对象的详细信息页面"。其中DetailView期望从URL捕获名为'pk’的主键值(下文的urls.py文件更改时会提到)。model为通用视图指定了模型。template_name指定了通用视图使用的模板。(通用视图有自己默认的模板,读者可以去掉template_name来体验通用视图的默认模板)

还有比较特殊的是index视图的context_object_name。对于DetailView ,question变量会自动提供—— 因为我们使用Django 的模型 (Question), Django 能够为context 变量决定一个合适的名字。然而对于ListView, 自动生成的context 变量是question_list。为了覆盖这个行为,我们提供 context_object_name 属性,表示我们想使用latest_question_list。作为一种替换方案,你可以改变你的模板来匹配新的context变量 —— 但直接告诉Django使用你想要的变量会省事很多。

同时还要对urls.py文件做出修改,因为视图函数已经不复存在,而是改成了通用视图类。

路径: polls/urls.py
from django.conf.urls import urlfrom . import viewsurlpatterns = [    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'),]

注意到只有vote()的<question_id>保持不变,而其他的视图传递参数的名字由<question_id>变成了<pk>,


<测试>
接下来是测试,测试在开发中意义重大,django应用的默认文件里也有tests.py这样的用于测试的脚本,同时django也提供了功能强大的测试模块,可见django对测试的重视。点这里查看官方文档对测试重要性的论述。下面来看看第一个测试:

文件:polls/tests.py
import datetimefrom django.utils import timezonefrom django.test import TestCasefrom .models import Questionclass QuestionMethodTests(TestCase):        def test_was_published_recently_with_future_question(self):        time = timezone.now() + datetime.timedelta(days=30)        future_question = Question(pub_date = time)        self.assertEqual(future_question.was_published_recently(),False)


解释:
TestCase来自django.test,有辅助测试的功能,它的子类会对类内以‘test_’开头的方法进行测试,就像上面的代码会对'test_was_published_recently_with_future_question’进行测试。当然前提是我们运行测试,运行方法是在命令行里输入:

$ python manage.py test polls

输入该指令后会执行polls内的测试。该测试中,'time'是一个当前时间30天后的时间,future_question是一个将在未来时间(time)发布的问题。self.assertEqual()的作用是检查方法内的两个参数是否相等。
测试的结果是 AssertionError: True != False 。 也就是说future_question.was_published_recently()返回的是真值,然而问题实际上还未发布,所以返回真值是不符合逻辑的,存在错误,需要修改。或者理解为发布时间是三十天后,不该归为近期发布,

文件:polls/models.py

def was_published_recently(self):    return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
改为
def was_published_recently(self):    now = timezone.now()    return now-datetime.timedelta(days=1) <= self.pub_date <= now
这个更改就是把近期发布的定义限制在现在到昨天之间,修改完成后再次执行测试,可以发现测试已经能通过了。

测试分为功能测试和单元测试,这只是单元测试中的简单例子,更多关于测试的内容可以查看官方文档,和一本名为《Python Web开发 测试驱动方法》的书籍。

<静态文件>

除了由服务器生成的HTML文件外,网页应用一般需要提供其它必要的文件 —— 比如图片文件、JavaScript脚本和CSS样式表 —— 来为用户呈现出一个完整的网站。 在Django中,我们将这些文件称为“静态文件”。
和防止index.html这类文件类似,我们把静态文件放入一个名为static的文件夹中,django会自动到该文件路径下寻找静态文件。下面我们来创建一个css静态文件。(图片保存路径为 polls/static/polls/image/background.PNG ,需要再创建一个image文件夹来放置图片)

文件:polls/static/polls/style.css

li a {    color: green;}body{    background: white url("image/background.PNG") no-repeat right bottom;}

然后再对index.html做出更改,使其加载style.css

文件: polls/templates/polls/index.html

{% load staticfiles %}<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />{% if latest_question_list %}    <ul>    {% for question in latest_question_list %}        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>    {% endfor %}{% else %}     <p>No polls are avaliable.</p>{% endif %}


index页面:


添加css样式表后的index页面(右下角就是background.PNG)


details页面:


results页面:





阅读全文
1 0
原创粉丝点击