String 类

String 类表示一个字符串。 在设计上, 字符串类存储的字符串是不可变的(immutable)。想要修改一个字符串的内容只能通过新建一个新的字符串实例来实现。 且对于两个相同的字符串字面量而言, 他们指向的是同一个字符串对象。

并且String类相对于其他类, 具有以下特殊性:

  • String类是Java中唯一的具有重载运算符的类。 String重载了+运算符, 使得对于一个加号左右两边的String能够被连接起来。

成员变量

String类具有以下的关键成员变量:

final char[] value

存储组成这个字符串的字符数组。 因为是final声明, 所以在类被创建之后是不可修改的。

final int count

存储在value[]数组中的字符个数, 在大多数情况下与value.length中的值相同。

final int offset

存储在value[]数组中字符的起始位置。 此变量的创建目的是为了substring()方法提供便利

私有类

private static final class CaseInsensitiveComparator

String类私有类, 其具有compare()方法, 用于提供对于字符串之间的大小写不敏感的比较。

构造器

public String ()

默认构造器, 创建一个空的字符串, 设置value为"",offset为0,count为0。

public String (String str)

拷贝构造器, 根据传入的参数str,新建一个新的字符串实例。但由于字符串是不可变的, 所以拷贝是以浅拷贝的方式执行的。

public String (char[] data)

根据传入的data字符数组, 初始化一个新的字符串实例。

public String (char[] data, int offset, int count)

根据传入的data字符数组, offset偏移量和count数据, 来初始化一个新的字符串实例。

public String (byte[] ascii, int hibyte, int offset, int count)

使用二进制的方式初始化一个字符串, 其中ascii数组中存放每一个字符的ascii码。 并且最终每一位的值c = (char)(((hibyte & 0xff) << 8) | (b & 0xff))

即用hibyte填充最终字符的最高八位,以及用ascii数组的值填充低八位。

public String (byte[] ascii, int hibyte)

使用String (byte[] ascii, int hibyte, int offset, int count) 作为 Delegate Constructor, 默认offset为0, count为ascii.length

public String (byte[] data, int offset, String encoding)

使用encoding参数所规定的方式来解码由data数组传入的字符, 并依此新建一个字符串。

public String (StringBuffer buffer)

使用StringBuffer中存储的字符序列构造一个新的字符串。StringBuffer中字符的改动并不会影响通过此构造器构建的字符串。

public String (StringBuilder buffer)

使用StringBuilder中的字符序列构造一个新的字符串, StringBuilder中字符的改动并不会影响通过此构造器构建的字符串。

StringBuffer 类

StringBuffer类表示一个 可变的(changeable) 字符串, 它提供了一系列成员方法来对StringBuffer实例进行修改, 包括insert, replace, delete, appendreverse等。

StringBuffer实例是变长的, 所以尽管在定义时, 可能是以固定长度定义的, 但它的长度都可以进行不断正常。

出于某种特定的原因, 编译器一般使用StringBuffer来重载+运算符对于String类型的运算。 即表达式str_a+str_b, 等价于
new StringBuffer().append(str_a).append(str_b).toString()

成员变量

StringBuffer类拥有以下的关键成员变量:

int count

下一个可用buffer位置的索引, 同时也是当前字符串内容的大小。 注意此处count是public权限, 即程序可以从外部访问该变量。

char[] value

存储StringBuffer中的字符串Buffer本身。

boolean shared

标记Buffer是否与其他对象共享, 如果为真, 则在对字符串进行写操作时, 需要先将其复制一份。

构造器

public StringBuffer()

默认构造器, 在默认情况下, 会构建一个以DEFAULT_CAPACITY为大小的StringBuffer,在JDK1.2中, 这个值为16.

public StringBuffer(int capacity)

这个构造器会新建一个大小为capacitychar数组, 并且将其作为成员变量value的当前值。

public StringBuffer(String str)

这个构造器接受一个String参数,并将其值赋予countvalue,需要注意的是,通过此构造器得到的字符串Buffer的大小是原字符串的大小加上DEFAULT_CAPACITY。但是字符串的大小(length)依然是原字符串

StringBuilder类

StringBuilder类表示一个 可变的(changeable) 字符串, 与StringBuffer类似, 它也提供了诸如insert,replace,delete, append, reverse之类的方法。 但是与StringBuilder不同的是,StringBuilder是不同步的, 只能在一个线程的情况下使用。

关键成员函数

int count

下一个可用buffer位置的索引, 同时也是当前字符串内容的大小。

char[] value

存储StringBuilder中的字符串Buffer本身。

构造器

public StringBuilder()

默认构造器, 创建构建一个以DEFAULT_CAPACITY为大小的StringBuilder

public StringBuilder(int capacity)

这个构造器会新建一个大小为capacity的char数组, 并且将其作为变量value的当前值。

public StringBuilder(String str)

这个构造器会根据传入的String参数,并将其值赋予countvalue。 StringBuilder通过此构造器得到的实例对象与StringBuffer一样, value数组的大小会空余一个DEFAULT_CAPACITY

public StringBuilder(CharSequence seq)

根据传入的CharSequence参数, 新建一个StringBuilder实例。 大小同样是有一个DEFAULT_CAPACITY的空余。

比较异同

String与(StringBuffer及StringBuilder)进行比较

相同点:

  1. 都表示了一个字符串序列
  2. 都是final类, 都不允许被继承
  3. 这个字符串序列都是支持Unicode的
  4. 都支持对于字符串序列内容的部分读取
  5. 都重载了加号运算符, 能够直接使用”+”来连接两个字符串

不同点:

  1. String类型的字符串是 不可变(immutable) 的, 即一个String实例在被创建之后, 其字符串的大小, 内容不能再被改变。 如果需要进行改变的话, 实际上是新建了一个String实例。
  2. 而对于StringBuffer和StringBuilder而言, 其本质上实现了一个变长数组(VLA),所以其字符串大小,内容都是可以进行变化的。在进行修改的时候, StringBuffer和StringBuilder各自实现了一个public void ensureCapacity(int minimumCapacity)的方法, 以确保时刻Buffer数组的大小要大于minimumCapacity, 否则就将数组扩充成其原来size的两倍再加二。
  3. String类型对于字符串本身的操作更多,诸如toLowerCase, toUpperCase这类大小写转换的方法, 以及matches, split,replace这类正则表达式方法, 以及trimconcat这类操作字符串的方法,都仅在String类中实现,而在StringBuffer与StringBuilder类中并没有提供以上接口。

StringBuffer 与 StringBuilder进行比较

相同点:

  1. 前面与String类型共有的相同点不赘述
  2. StringBuffer类与StringBuilder类都实现了一个 可变的(changeable) 字符串对象, 可以通过一系列方法对字符串内容进行诸如增加,删减,插入,替换,翻转等操作。
  3. 实现的字符串buffer的本质都是一个变长数组(VLA), 在任何时刻都尽量保证数组不越界, 如果接近了其边界, 就将数组的大小扩充成原来大小的两倍加二。
  4. 默认的buffer大小DEFAULT_CAPACITY都为16

不同点:

  1. StringBuilder类相较StringBuffer, 多了一个基于CharSequence建立的构造器。
  2. StringBuffer比StringBuilder类多了一个shared指示,以标记buffer内容是否被多个对象所引用。如果被多个引用的话,在进行操作的时候就要将这个buffer再复制一份, 以保证其单引用的性质。 这一点其实是为了保证操作内容的同步化进行的。
  3. 正如之前所说, StringBuffer和StringBuilder的最大不同点在于其操作的同步性。 StringBuffer在其大多数成员方法前都加上了synchronized关键字, 以保证其线程安全。 也就是说, 在同一时间, 只有一个线程能够对同一个StringBuffer对象进行操作,这种限制避免了线程之间读写的冲突。 而对于StringBuilder来说, 就没有这一层限制。故其有可能会引起线程之间的冲突, 但从另一个角度而言, 也减少了诸如线程锁之类的可能的系统开销。 故相比StringBuffer而言, StringBuilder的速度可能会更快一些。 故两者需要权衡使用。

这章教程承接第三章结束的地方, 我们将继续那个网络民调应用, 并且将关注与简单的表单处理与精简我们的代码。

创造一个简单的表单

让我们来升级一下上个教程中创建的问题详细内容模板(polls/detail.html), 让它包含一个HTML的<form>元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--polls/templates/polls/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>

快速浏览一下上面的代码:

  • 上面的这个模板给每一个选项显示了一个单选按钮。 每一个单选按钮的值与每一个选项的id相联系。 每一个单选按钮的名字都是choice. 这意味着, 当某人选择了这些单选按钮之一然后提交这个页面的时候, 系统会使用POST传递一个数据choice=#, 此处的#,代表选中选项的id。 这是HTML表单的基本概念。

  • 我们把这个表单的动作属性设置为{ %url 'polls:vote' question.id% }, 然后我们设置动作方法为post。 此处使用method='post'(与之相对的是method='get')十分重要, 因为此处提交表单的这个动作将会出发服务器端的数据操作。 只要当你创建一个需要服务器端数据操作的表单的时候, 使用post总是最佳选择。 这个建议并不仅仅真对于Django, 这是Web开发的实际经验。

  • forloop.counter记录了for标签内循环的次数。

  • 既然我们创建了一个POST表单(拥有着操作数据的功能),我们就需要担心一下跨站请求伪造(Cross Site Request Forgeries)的问题。 但是谢天谢地,你并不需要太过于担心这个, 因为Django创造了一个非常易用的系统来避免这个问题。 简而言之, 所有使用POST提交,并且目标是一个内链URL的表单, 都需要有一个{ % CSRF token % }标签。

现在, 让我们来创建一个能够处理提交的数据的Django视图吧。 别忘了, 在第三章教程中, 我们为这个民调应用创建了一个URL设置, 包含着如下代码:

url(r'^(?P<question_id>d+)/vote/$', views.vote, name='vote')

我们之前也为vote视图创造了一个简单版本, 现在让我们来写一个完整版的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#polls/views.py

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse

form polls.models import Choice, 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_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
#当成功提交一个POST数据之后, 要记得返回一个
#HttpResponseRedirect, 不然当用户点击后退
#按钮的时候, 同样的数据会被提交多次。
return HttpResponseRedirect(reverse('polls:results', args=(p.id, )))

以上的代码包含一些我们之前没有提过的东西:

  • request.POST是一个字典型对象。 它允许你通过键值对来访问POST传递的数据。 在上文的代码中, request.POST['choice']以字符串的形式,返回了选中的选项的ID。 request.POST的值总是以字符串表示的。

    Django同样提供request.GET来以同样的方式访问GET数据。 在我们的代码中,我们只使用request.POST, 来确保数据只由POST请求来操作。

  • choice没有提供一个POST数据的话,request.POST['choice']会引发一个KeyError错误。 上面的代码检查了KeyError错误发生的可能性, 并在choice没有提供POST数据的时候重新显示表单页面,并且返回一个错误信息。
  • 在添加了投票统计之后, 代码返回了一个HttpResponseRedirect而非一个普通的HttpResponseHttpResponseRedirect只需要一个参数: 被重定向的目标URL。

    正如代码中的注释提到的那样, 你应当总是返回一个HttpResponseRedirect, 这不仅仅是对于Django而言。 这是一个优秀的编程实践经验。

  • 我们在本例的HttpResponseRedirect构造器中使用了一个reverse()函数。 这个函数帮助我们避免了在视图函数中硬编码写入一个URL。 给定我们想要传递到的那个视图的名字与这个URL模式所需的参数,在这个例子中, 使用我们在第三章教程中创建的URL设置, 这个reverse()函数会返回如下字符串:

    '/polls/3/results/'
    

    这里的3是p.id的值。 这个重定向的URL会调用result视图来显示最终的页面。

正如我们在第三章教程中提到的那样, request是一个HttpRequest对象。

在某人为某个问题投票之后, vote()视图重定向到这个问题的最终视图中, 它的视图代码如下:

1
2
3
4
5
6
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})

它与我们在第三章写的detail视图几乎完全一样, 仅有的不同之处在于模板的名字。 我们将会在稍后修改这处冗余。

现在, 创建一个polls/results.html模板

1
2
3
4
5
6
7
8
9
10
<!--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 % }
</ul>

<a href="{ % url 'polls:detail' question.id % }">Vote again?</a>

现在在你的浏览器中访问/polls/1来为这个问题投票。 你将见到一个随着你每次投票都有变化的结果页面。 如果你没有选择一个选项就提交了的话, 你还将见到一个错误页面。

使用基础视图: 代码越少越好

之前的detail()result()视图都极其简单, 以及, 像之前提过的那样—— 冗赘。 而那个显示着问题列表的index()视图, 也是一样。

这些视图体现着网页开发的一个基本模式:根据URL中传递的一个参数来从数据库中获取数据, 读取一个模板然后返回渲染之后的页面。 因为这实在是太平常了, Django提供了一个快捷表示, 叫做基础视图(generic views)(或者叫泛型视图).

基础视图将程序抽象表示, 以至于你甚至不需要书写Python代码就能够创建一个应用。

然我们把民调应用转化成使用基础视图系统, 所以我们就可以删去一大堆无用的代码。 我们只需做一下这几步就可以完成转化:

  • 改变URL设置
  • 删除一些旧的,不需要的视图
  • 引入基于基础视图系统的新的视图

修改URL设置

首先, 打开polls/urls.py, 并作如下修改:

1
2
3
4
5
6
7
8
9
10
11
12
#polls/urls.py

from django.conf.urls import patterns, url

from polls import views

urlpatterns = patterns('',
url(r'^$',views.IndexView.as_view(), name ='index'),
url(r'^(?P<pk>d+)/$', views.DetailView.as_view(), name='detail'),
url(r'^(?P<pk>d+)/results/$', views.ResultsView.as_view(), name='results'),
url(r'^(?P<question_id>d+)/vote/$', views.vote, name='vote'),
)

注意第二个和第三个正则表达式匹配参数名已经从<question_id>变成了<pk>

修改视图

接下来, 我们要对旧的index, detailresult视图做一些修改, 并且使用之前提到的Django的基础视图。 打开polls/views.py, 并作如下修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#polls/views.py

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic

from polls.models import Choice, Question

class 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/result.html'

def vote(request, question_id):
...#与之前相同

我们使用了两个基础视图: ListViewDetailView。 这两个视图分别抽象了“显示一列对象”和“显示某个特定对象的详细内容”两个概念。

  • 每一个抽象视图都需要直到它所作用的数据模型。 这个信息由model属性传递
  • DetailView基础视图需要由URL中匹配得到被称为pk的主键键值, 所以我们在URL设置中把question_id改名作pk

默认情况下, DetailView基础视图使用叫做<app name>/<model name>_detail.html的模板。 在本例中, 它使用的是polls/question_detail.html。 这里的template_name属性被用来告诉Django去使用一个特定的模板名称, 而非Django自动默认的模板。 我们还为result视图指定了一个模板名称 – 这确保了result视图和detail视图在渲染时会有不同的显示, 尽管他们都是基于DetailView

同样的, ListView使用默认的<app name>/<model name>_list.html 我们使用template_name来告诉ListView使用我们存在的polls/index.html模板

在之前的教程中, 我们将一个包含着questionlatest_question_list的内容对象传递到模板中。 对于DetailView来说, 这些信息都是自动传递的。 因为我们使用了一个Django的视图。 Django能够自己为这个内容决定一个合适的名字。 但是, 对于ListView,自动生成的内容变量是question_list。 为了覆盖这个, 我们提供了context_object_name属性, 特殊指定了我们想要替代性地使用latest_question_list。 作为一个可选的方法, 你应该更改你的模板来匹配新的默认内容变量 – 但是让Django使用你想要的变量会更简单。

运行服务器,你就会看见基于基础视图的全新的民意调查应用。

这章教程将继续第二章结束的地方, 我们将继续编写我们的网络民调应用,并切将专注于创建一个界面 – 视图(view)

思想

视图, 是在你的Django应用中具有一个特定模板的特定的函数, 它生成某“种” 特定的Web页面。

举例来说, 在一个博客应用之中, 你可能会有如下的视图:

  • 博客主页, 显示最新的几篇博文
  • 博文的“详细” 页面, 完整展示某一篇博文的详细信息
  • 以年为基础的分类页面 ,以某一年的月份为基础分类博文
  • 以月为基础的分类页面, 以某一月的日子为基础分类博文
  • 以日为基础的分类界面, 显示某一天发布的所有博文
  • 评论动作, 处理对某一篇博文的评论功能

在我们的民意调查应用中, 我们有如下四个视图:

  • 问题“索引”视图: 显示最新的几个问题
  • 问题“详细”视图: 显示一个问题的文本, 以及一个投票的表单
  • 问题“结果”视图: 显示一个特定问题的投票结果
  • 投票动作: 处理对某个特定问题的特定选项的投票操作

在Django中, 网页和其它的内容是由视图所呈现的。 每个视图由一个单独的Python函数所表示(或者在以类为基础的视图中, 以方法来表示)。 Django会通过审视被要求访问的URL(准确地说, 域名之后的那部分URL)来选择一个特定的视图。

你可能经常会遇见像 “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B” 这样的URL, 你会十分欣慰Django有着一个比这要优雅的多的URL系统。

一个URL模式字符串其实就是URL的一般形式, 例如: /newsarchive/<year>/<month>

要从一个URL中获得一个视图, Django使用被称为URLconfs的方法。 URLconfs适配URL模式字符串(用正则表达式的形式描述),并选择相对应的视图。

编写你的第一个视图

让我们来着手编写第一个视图, 打开polls/views.py文件, 然后输入如下的Python代码:

1
2
3
4
5
6
7
#polls/views.py

from django.http import HttpResponse


def index(request):
return HttpResponse("Hello, world. You're at the polls index.")

这是Django能够提供的最基础的视图。 要访问这个视图, 我们需要将其映射到URL上 – 所以我们需要一个URL配置。

若想在应用目录下设置一个URL配置, 创建一个叫做urls.py的文件。 你的应用目录现在应该长这个样子:

1
2
3
4
5
6
7
polls/
__init__.py
admin.py
models.py
tests.py
urls.py
views.py

polls/urls.py文件中应当包含如下内容:

1
2
3
4
5
6
7
8
9
10
#polls/urls.py

from django.conf.urls import patterns, url

from polls import views


urlpatterns = patterns('',
url(r'^$',views.index, name='index'),
)

下一步是把URL设置的根节点指向polls.urls模块。 在mysite/urls中添加一个include()方法:

1
2
3
4
5
6
7
8
9
#mysite/urls.py

from django.conf.urls import patterns, include, url
from django.contrib import admin

urlpatterns = patterns('',
url(r'^polls/', include('polls.urls')),
url(r'^admin/', include(admin.site.urls)),
)

你现在就已经把一个index视图添加进了URL设置之中。 在你的浏览器中访问 http://localhost:8000/polls/ , 你就将见到你在index视图中定义的那行文字: Hello, world. You’re at the polls index

url函数传递四个参数, 两个是必须参数: regex正则表达式 和 view视图。 以及两个可选参数: kwargs关键字参数,name名称。

现在让我们一个一个介绍一下他们各自分别代表着什么:

url()参数: regex 正则表达式

术语’regex’ 是 正则表达式’regular expression’ 的缩写, 代表着一种对字符串中特定模式进行匹配的语法。 Django从第一个正则表达式开始, 并按照列表依次对要求访问的URL进行匹配, 直到找到所需的URL。

请注意, 这些URL的正则匹配并不会对POST和GET的传递参量进行匹配。 如在一个http://www.example.com/myapp的URL请求中, URL设置会匹配 myapp/。 而在一个http://www.example.com/myapp/?page=3的请求中, URL设置仍然只会匹配myapp/

如果在正则表达式方面需要任何帮助, 请访问Wikipedia的正则表达式相关页面,与Python的re模块的相关官方文档。 以及由O’Relly公司出版的由Jeffrey Friedl所著的《精通正则表达式》也是相当不错的选择。 但实际上, 你并不需要成为一名正则表达式的专家, 你仅仅需要知道怎么去对一些简单的模式进行匹配就好了。 事实上, 十分复杂的正则表达式有着很惨的使用表现,所以你最好还是不要太过依赖于正则表达式。

最终, 一个性能方面的提醒: 这些正则表达式在URL设置模块被加载的时候就被编译好了, 他们运行的速度超级快(如果这个匹配并非像前文所讲的那么的复杂的话)。


url()参数: view 视图

当Django找到了一个正则表达式的匹配, 它会调用相对应的视图函数, 并传递一个HttpRequest作为第一参数,被正则表达式匹配的其它内容作为剩余参数。 如果正则表达式仅作简单的匹配, 匹配值将作为普通参数(positional arguments)进行传递, 如果使用了名称匹配, 那么匹配值将作为键值对来传递。 我们将会在稍后提供一个小小的例子。


url()参数: kwargs 关键字

任意的关键字都可以被以字典的形式作为参数传递给目标视图。 在此教程中我们将不会使用这个功能。


url()参数: name 名称

为你的URL起一个名字, 使得你可以在Django项目的其他地方, 尤其是模板中明确地调用它。 这个强大的功能使得你可以在只用在一个文件中就可以对url模式匹配做一个全局的修改。


编写更多的视图

现在让我们对polls/views.py 文件添加更多的视图。 这些视图有着些许的差别, 因为他们都接受了一个参数:

1
2
3
4
5
6
7
8
9
10
11
#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)

对这些新增的视图添加url链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#polls/urls.py

from django.conf.urls import patterns, url
from polls import views

urlpatterns = patterns('',
#ex: /polls/
url(r'^$', views.index, name='index'),
#ex: /polls/5/
url(r'^(?P<question_id>d+)/$', views.detail, name='detail'),
#ex: /polls/5/results/
url(r'^(?P<question_id>d+)/results/$', views.results, name = 'results'),
#ex: /polls/5/vote/
url(r'^(?P<question_id>d+)/vote/$', views.vote, name='vote'),
)

现在在你的浏览器中实验一下: 如果输入/polls/34的话。 它将会运行detail()函数, 并且会显示你所输入的问题id所对应的那个问题。 试试/polls/34/results//polls/34/vote/也将出现相对应的结果

当某人对你的服务器请求一个网页: 比如说/polls/34的时候, Django会读取mysite.urlsPython模块, 因为ROOT_URLCONF设置指向的这里。 它寻找到名为urlpatterns的变量并且逐一匹配其中的正则表达式。 这里我们使用的include()函数是对其它的URL设置的简单引用。 注意这里的include()函数中的正则表达式并没有一个$(标记字符串末尾)字符,而是一个下划线。 当Django遇到include()的时候, 它会将当前部分已经被匹配的URL部分删去, 然后把剩余部分交予include的URL设置去做进一步处理。

include()函数背后的思想是把URL变得“即插即用”。 在这里我们的民调应用是被放在polls/urls.py, 他们还可以被放在polls/下, 或者在/fun_polls/下, 或者在/content/polls/下, 他们还可以被放在其它目录下, 而这个应用仍然可以工作。

当用户访问/polls/34/的时候,Django会做如下操作:

  • Django会找到与这个URL匹配的正则表达式^polls/
  • 然后Django会截去匹配的字符串polls/, 然后把剩下的文本34/传递到polls.urlsURL设置中, 来做进一步操作。
  • 此时这个字符串与r'^(?P<question_id>d+)/$'相匹配, 最终映射到一个对detail()函数的调用上:

    detail(request = <HttpRequest object>, question_id='34')
    

上面的question_id='34'这一部分来自于(?P<question_id>d+)。 这里使用一个括号来把一个模式“包裹”起来, 它“捕捉”其匹配的那个字符串, 然后把它作为一个参数传递给视图。 这里?P<question_id>定义了这个匹配内容的名字。 d+是正则表达式语法中对一个数字序列的匹配定义。

因为URL模式是一系列正则表达式, 实际上他们能做的事情是没有限制的。 并没有需要添加一个类似于.html的后缀 —— 除非你真的想这么做, 在这种情况下你可以写如下的代码:

(r'^polls/latest.html$','polls.views.index'),

但是千万别, 这样真的太傻了。

写真正能够做一些事的视图

每个视图都承担着一个或两个责任: 返回一个HttpResponse对象, 包含着这个被请求的页面的信息, 或者引发一个诸如Http404的异常——这都由你决定。

你的视图可以从一个数据库中读取记录。 它可以使用一个模板系统——Django默认的, 或者是第三方的Python模板——或者不使用。 它可以生成一个PDF文件, 输出XML文件, 实时生成zip文件…… 做一切你想的事情, 只要使用相对应的Python支持库就可以。

而Django所需要的仅仅是一个HttpResponse, 或者是一个异常。

出于方便起见, 让我们使用Django自带的数据库API。 以下是我们的新的index()视图, 它显示了数据库中最新的五个问题, 用逗号连接, 并根据发布时间排序。

1
2
3
4
5
6
7
8
9
#polls/views.py

from django.http import HttpResponse
from polls.models import Question

def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ','.join([p.question_text for p in latest_question_list])
return HttpResponse(output)

这里有一个问题: 这个页面的内容是被直接编码在视图源代码中的, 如果你想要修改的话。 需要修改这个视图的Python代码。 所以在这里我们使用Django的模板系统创建一个模板, 来把设计与视图分割开来。

首先, 在你的polls目录下创建一个templates文件夹。 Django将在这里寻找网页模板。

Django的TEMPLATE_LOADERS设置包含了一个指示从不同的源头调用模板的列表。 其中的一个默认选项是django.template.loaders.app_directories.Loader, 它自动在每一个INSTALLED_APPS中寻找一个’templates’文件夹。 这就是在第二章教程的时候,在我们没有修改 TEMPLATE_DIRS的情况下Django依然能够找到模板的原因。

安排模板
我们可以把我们所有的模板都放在同一个地方, 比如一个巨大的模板目录下, 此时它依然可以工作得十分完美。 但是,这个模板属于那个民调应用, 所以与我们创建的管理后台的页面模板不同, 我们把这个模板放在应用的模板目录下, 而不是放在项目的模板目录下。

在你刚刚创建的这个templates文件夹中, 再创建另一个叫做polls的文件夹。 并且在这个文件夹中, 创建一个叫做index.html的文件。 你的模板此时的目录应该是polls/templates/polls/index.html。 因为app_directories模板加载器如之前我们所描述的那样工作, 所以此时你可以简单地在Django中使用polls/index.html就可以调用这个模板。

模板命名空间
因为我们可能会直接把文件放在 polls/templates之中(而不是另外新建一个polls文件夹), 但是这确实是一个坏主意。 Django会选择与这个名字相匹配的第一个模板。 而如果此时你有一个与其同名而在不同应用下的模板的话。 Django将不能区分它们两个。 我们需要能够让Django准确无误地找到正确的那一个。 此时最简单的方式就是为其确定命名空间, 也就是把每个模板都放进用每个应用的名字命名的文件夹中

在这个模板中输入如下内容:

1
2
3
4
5
6
7
8
9
10
11
<!--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 % }

然后升级我们的index视图, 来使用这个模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#polls/views.py

from django.http import HttpResponse
from django.template import RequestContext, loader

from polls.models import Qeustion


def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = Requestcontext(request, {
'latest_question_list': latest_question_list,
})
return HttpResponse(template.render(context))

这个代码从叫做polls/index.html的文档中读取了模板, 并且把它传递给一个内容对象。 内容对象是一个将模板变量名与Python对象一一映射的字典对象。

通过你的浏览器访问polls/来读取这个页面, 此时你将能够看见一个列表显示着我们在第一章中创建的”What’s up”问题。 这个链接指向这个问题的详细页面。

一个快捷表示: render()

读取一个模板, 填入内容, 再用HttpResponse返回处理后的模板, 这一系列操作经常被执行。 所以Django提供了一个快捷表示, 我们用它重写一下index()视图:

1
2
3
4
5
6
7
8
9
10
#polls/views.py
from django.shortcuts import render

from polls.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)

这里我们可以注意到, 如果我们使用了render的话, 我们就不再需要导入loader, RequestContextHttpResponse了。

render()方法把request对象作为第一参数, 然后把模板名字作为第二参数, 然后一个可选的字典变量作为第三参数。 它返回一个给定的模板通过给定的内容渲染之后得到的HttpResponse对象。

引发一个404错误

现在, 让我们改动一下detail视图 – 那个显示给定问题的详细内容的视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#polls/views.py

from django.http import Http404
from django.shortcuts import render

from polls.models import Question
#...

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})

这里有一个全新的概念: 如果所要求的ID不存在的话, 这个视图引发了一个Http404异常。

我们将在稍后讨论你应该在polls/detail.html 模板中放一些什么。 但如果你想尽快地完成这个教程的话, 一个包含如下内容的模板就够了:

1
2
<!--polls/templates/polls/detail.html-->
{ { question } }

一个快捷表示: get_object_or_404()

调用get()函数, 并在对象不存在的时候引发Http404异常,也是一个十分常见的操作。 Django提供了一个快捷表示。 根据其重写detail()视图,如下所示:

1
2
3
4
5
6
7
8
9
#polls/views.py

from django.shortcuts import get_object_or_404, render

from polls.models import Question
#...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html',{'question': question})

这个get_object_or_404()函数把一个Django数据模型作为它的第一参数, 以及任选的键值对作为其它参数。 这个函数把参数传递给get()函数, 如果对象不存在的话, 引发404异常

思想
为什么我们使用一个函数get_object_or_404(), 而不是自动使用更高层面的ObjectDoesNotExist异常, 或者用一个API来引发Http404异常呢?

因为这样的话就会把数据模型与视图联系在一起。 而Django的一个哲学就是低耦合度。 一些系统自带的合作函数存放于django.shortcuts模型之中

同样也有一个get_list_or_404()函数, 使用方法和get_object_or_404()一样,只不过使用的是filter()函数, 如果列表是空的就返回404异常

使用模板系统

回到我们的民调应用的detail()视图中。 给定一个内容变量question, 我们的polls/detail.html应该长这个样子:

1
2
3
4
5
6
7
<!--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>

模板系统使用 点 来访问一个变量的属性。 在上面的{ { question.question_text } }的例子中, 首先Django在question对象中做一次字典查找。 如果没有找到, 他做一次属性查找——在这个例子中成功找到了—— 如果还是失败的话, 他就会做一次列表查找。

对方法的调用发生在{ % for % }循环之中: question.choice_set.all 被解析为Python的代码question.choice_set.all(), 将会返回一个可迭代的Choice对象并且适于在{ % for % }标签内使用。

删去在模板中硬编码的URL

别忘了, 在之前我们写那个polls/index.html模板中的链接的时候, 我们用了一些硬编码的风格:

<li><a href="/polls/{ {question.id} }/">{ {question.question_text} }</a></li>

在这里使用硬编码,紧耦合方法的弊端在于, 如果要修改链接的话, 会造成相当大的修改模板的工作量。 既然你在polls.urls模块中的url()方法里定义了正则表达式的名字参数, 你就可以使用{ %url% }标签来代替这里特定的url地址。

<li><a href = "{ % url 'detail' question.id % }">{ { question.question_text } }</a></li>

这个函数的工作原理是查询polls.urls模块中的URL定义。 你可以清除地看见名称’detail’被做了如下定义:

url(r'^(?P<question_id>d+)/$', views.detail, name='detail'),

如果你想要把问题的详细页面转换到别的URL上, 也只用修改polls/urls.py就好了。

为URL名称确定命名空间

这个教程项目只包含一个应用:polls。 在真实的Django应用环境中, 可能在同一个项目下有着五个, 十个, 乃至20个或更多不同的应用。 Django是怎样区别其中不同的URL的? 举例来说, polls应用有一个detail视图, 同样一个博客应用也可能有一个相同的视图。 Django使怎样确保在使用{ % url % }模板标签的时候不至于混乱呢?

这个问题的答案就是为你的根URL配置添加命名空间。 在mysite/urls.py文件中,添加命名空间的定义。

1
2
3
4
5
6
7
8
9
#mysite/urls.py

from django.conf.urls import patterns, include, url
from django.contrib import admin

urlpatterns = patterns('',
url(r'^polls/', include('polls.urls', namespace="polls")),
url(r'^admin/', include(admin.site.urls)),
)

然后把具体模板中的代码从:

<li><a href = "{ % url 'detail' question.id % }">{ { question.question_text } }</a></li>

改成:

<li><a href = "{ % url 'polls:detail' question.id % }">{ { question.question_text } }</a></li>

这章教程从教程一结束的地方开始。 我们继续编写这个网络民调应用, 并且将关注于Django自动生成的后台站点。

思想
为你的同事或者客户生成一个自动站点,以供他们添加,修改和删除内容是一件多么无趣而又重复,缺乏创造的事情。 出于这个原因, Django提供了完全自动化的基于数据模型的后台站点的生成功能。
Django最初是作为一个新闻站点框架使用的, 有着非常清晰的内容提供者和公众的角色区分。 网站管理员操作这个系统并且添加新的新闻, 专访, 体育比赛的得分等等。 这些内容将在公共站点显示出来。 Django通过创建一个统一的编辑后台来解决了这个问题。
编辑后台并非是为网站访问者所准备的, 他们是为了网站管理员设计的。

创建一个管理员用户

首先我们需要创建一个能够登录进入管理员后台的用户。 运行如下代码:

$ python manage.py createsuperuser

输入你想要取的用户名后按下回车

Username: admin

然后输入邮箱

Email address: admin@example.com

最后一步是输入密码, 你将会被要求输入两次密码, 第二次是对第一次输入的密码的确认。

Password: ********
Password(again): ********
Superuser created successfully.

启动开发服务器

Django管理员后台将会默认自动运行。 让我们启动管理员后台然后探索一下它吧。

回忆一下在第一章教程中学到的如何启动开发服务器的命令:

$ python manage.py runserver

现在, 启动一个网页浏览器, 然后访问你的根域名的”/admin/“, 比如http://127.0.0.1:8000 你将会看见如下的管理员登录界面:
管理员登陆界面

因为翻译功能会被自动启动, 所以很有可能你会看见这个登录界面的语言将会与你的浏览器的默认语言保持一致。

和你所看见的不一样?
如果这时候,你看见的是如下的错误页面而非如上的登录界面的话:

ImportError at /admin
   cannot import name patterns
   ...

这说明你很有可能使用着一个与此教程不相符的Django版本。 你将需要一个更新的Django或者一个更老的教程

进入管理员后台

现在试着使用你在之前的步骤中创建的超级用户账号登录进去。 你应该能够看见如下的Django管理员主页:
Django管理员主页

你将看见几种可以修改的内容: 用户组(Groups)和用户(Users)。 这些功能由django.contrib.auth, Django的授权功能框架提供。

让我们的民调应用能够在后台系统中进行修改

但是我们写的应用在哪里? 它并没有显示在后台系统里。

其实只要做一件事情就够了: 我们需要告诉管理员后台Question对象具有一个管理员借口。 要完成这件事, 打开polls/admin.py 文件, 然后作如下修改:

1
2
3
4
5
#polls/admin.py
from django.contrib import admin
from polls.models import Question

admin.site.register(Question)

探索一下免费(free)的管理员功能

现在我们已经把Question对象在管理员后台进行了注册, Django现在明白它应该被显示在后台系统的主页中:
后台系统

点击”Questions”, 你就进入了问题列表的 “Change list”(修改列表)之中。 这个页面显示在数据库之中的所有问题, 并允许你选择一个来对其进行修改。 这里有我们在先前的教程之中创建的”What’s up”问题

What's up

点击”What’s up”来对其进行修改

enter image description here

此处需要注意:

  • 这个表格由Question数据模型自动生成
  • 不同的域类型(DateTimeField, CharField)对应于不同的HTML输入空间。 每一种域类型都明白其应当如何在Django后台中展现/
  • 每一个DateTimeField 都有其自己的JavaScript快捷方式。 日期表示有一个“今天”快捷方式, 并且有一个日历图标。 时间表示有一个“现在”快捷方式并且有一个钟表图标,弹出常用时间的列表。

页面的末尾给你一些选项:

  • 保存 – 保存更改并且返回这个对象的修改列表页面
  • 保存并继续 – 保存更改并重新读取到这个对象的管理页面
  • 保存并添加下一个 – 保存更改并且从这个对象读取一个新的创建表单
  • 删除 – 显示一个删除确认表单

如果”发布日期”的值与你在之前创建这个问题的时间并不相同的话。这可能意味着你忘记了修正TIME_ZONE设定的值。 改正它, 并重新读取这个界面, 检查一下是否显示正确的值。

点击“今天”或者“现在“ 快捷方式来修改“发布日期”。 然后点击”保存并继续“, 然后点击右上方的“历史” , 你会看见一个列举着通过Django管理后台对这个对象进行修改的记录的页面, 时间戳显示在左侧, 修改者显示在中间。
TimeStamp

自定义管理员表单

给你一点时间来为你省下不用写的那么一大堆代码感到惊讶。 通过把Question对象利用admin.site.register(Qustion)来注册到管理后台, Django能够通过其自己的理解创建一个默认的表单。 一般来说, 你可能会希望自定义这个表单的样子以及工作方式。 你可以在通过注册这个对象时告诉Django这些选项来做到这一点。

然我们看看它是如何做到把编辑表单的两个域的顺序调换过来把。 把admin.site.register(Question)一行做如下替换:

1
2
3
4
5
6
7
8
#polls/admin.py
from django.contrib import admin
from polls.models import Question

class QuestionAdmin(admin.ModelAdmin):
fields = ['pub_date', 'question_text']

admin.site.register(Question, QusestionAdmin)

你只需照着这个模式来就可以: 创建一个数据模型的管理员对象, 然后把它作为一个第二变量传递给admin.site.register() —— 在任何你想要为一个对象改变其管理员后台选项的时候

上面的这个改动把”发布日期“ 提前到了 “问题内容”之前
Before

对于两个域来说, 这并不引人惊讶。 但是对于一个有着很多个域的管理员表单来说, 为这些域给定一个确定的顺序就显得至关重要了。

说到有很多个域的表单, 你可能会希望把这些域分成不同的类别:

1
2
3
4
5
6
7
8
9
10
11
12
#polls/admin.py

from django.contrib import admin
from polls.models import Question

class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields':['question_text']}),
('Date information', {'fields':['pub_date']}),
]

admin.site.register(Question, QuestionAdmin)

fieldsets内的每个元组的第一个元素,就是每一个不同的类别的标题。 我们的表格现在长这个样子:

form

你可以为每个分类任意制定HTML类。 Django提供了一个collapse类, 在默认情况下, 会把这个分类给折叠起来。 这在你有一个很长的表单的时候十分重要。

1
2
3
4
5
6
7
8
9
10
11
#polls/admin.py

from django.contrib import admin
from polls.models import Question

class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields':['question_text']}),
('Date information',{'fields':['pub_date'], 'classes':['collapse']}),
]
admin.site.register(Question, QuestionAdmin)

Collapse

添加相关的数据对象

好了, 我们有了问题对象的后台界面了。 但是一个问题有多个选项,而目前的后台系统并不能显示选项。

仅仅是目前来说。

有两个方式能够解决这个问题。 第一种方式是就像我们之前注册问题对象那样注册一个选项对象。

1
2
3
4
5
6
#polls/admin.py

from django.contrib import admin
from polls.models import Choice, Question
# ...
admin.site.register(Choice)

现在”Choice”也可以在后台系统被访问到了。 “添加选项” 的表单像这个样子:
Choice

在这个表单中, Question域作为一个选项框存在。 它包含着数据库中的所有问题。 Django懂得一个“外键”意味着这个域应当在后台表单中被表达为一个选项框。 在现在, 选项框中只有一个问题。

注意在Question旁边的那个“添加新问题”链接。 每一个有着外键关系的域都有这么一个选项。 当你点击它的时候, 有一个带着“添加问题”表单的窗口会弹出来。 如果你在这个窗口之中添加了一个新的问题并点击保存。 DJango会将其存进数据库中并且会动态地在你所浏览的这个页面的选项框中添加你刚刚添加的这个问题。

但是, 实际上, 这是一个十分低效的添加问题的方法。 更好的方法是在你创建一个新问题的时候就创建相对应的一族选项。 让我们开始做吧。

删除Choice模型调用的register函数, 然后依照如下代码进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#polls/admin.py

from django.contrib import admin
from polls.models import Choice, Question

class ChoiceInline(admin.StackedInline):
model = Choice
extra = 3

class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields':['question_text']}),
('Date information',{'fields':['pub_date'], 'classes':['collapse']}),
]
inlines = [ChoiceInline]

admin.site.register(Question, QuestionAdmin)

以上代码告诉Django, Choice选项由Question管理主页进行编辑。 默认情况下, 提供3个空白的选项以供选择。

读取“添加问题”页面来看看它长什么样子。

ADD Question

工作原理如下: 这里有三个空白的选项 —— 由extra定义的—— 每一次你访问这个已经存在的类的时候, 你都会得到另外三个空白的选项。

在这三个空白选项的最后, 你会看见一个“添加另一个新的选项”链接, 如果你点击了它, 就会有一个更新的选项被添加进去。 你这时可以点击新的选项的右上角的那个“叉”来删除他。 但是你不能删除默认的那三个选项:

Add

有一个小小的问题就是,这么多选项占据了满满一个屏幕。 出于这个原因, Django提供了一个更为灵活的方式将每一个选项极其相关内容在同一行进行显示。 你只需要依照如下修改ChoiceInline就可以:

1
2
3
#polls/admin.py
class ChoiceInline(admin.TabularInline):
#...

继承了admin.TabularInline类, (而非StackedInline), 相关的对象就会以一个更为紧凑的方式进行排列:

compact

注意到那个多出来的”Delete?”列了么? 那使得你可以删掉后面再新增的选项。

自定义修改列表

现在问题的管理界面看上去挺不错,让我们对“修改列表”页面——那个显示着 数据库中所有问题的页面做一些小小的修改吧。

现在这个页面长这个样子:

enter image description here

默认情况下,Django会显示每个对象的str()方法的返回值。 但是在某些时候如果只显示个别的域会更有帮助。 出于这个目的, 我们可以使用list_display管理选项。 这个选项划定了一个显示对象的元组, 元组内的元素作为最终显示表单的列进行表示。

1
2
3
4
#polls/admin.py
class QuestionAdmin(admin.ModelAdmin):
#...
list_display = ('question_text', 'pub_date')

为了更好的显示, 我们把在第一章教程中创建的自定义方法was_published_recently也包括进来

1
2
3
4
#polls/admin.py
class QuestionAdmin(admin.ModelAdmin):
#...
list_display = ('question_text', 'pub_date', 'was_published_recently')

现在问题的修改列表长这个样子:

enter image description here

你可以通过点击列表的表头来对其中的值进行排序–除了was_published_recently之外。 因为目前还不支持对一个自建方法进行排序。 同时还要注意的是, was_published_recently方法的表头名字是将方法名的下划线用空格代替, 之后把首字母大写得到。 并且这一列的每一行都包括着这个方法的出输出值的字符串表示。

你可以通过给模型方法一些新的属性改正这个列表的显示:

1
2
3
4
5
6
7
8
9
#polls/models.py

class Question(models.Model):
#...
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
was_published_recently.admin_order_field = 'pub_date'
was_published_recently.boolean = True
was_published_recently.short_description = 'Published recently?'

阅读“list_display”文档来获得更多关于这些方法属性的信息

再一次修改你的 polls/admin.py 文件来添加一个对于Question修改列表页面的改进 : 一个使用list_filter方法的过滤器。 在QuestionAdmin方法中添加如下一行:

list_filter = ['pub_date']

这一行代码添加了一个允许人们按照pub_date域来过滤表内信息的侧边栏:
"filter"

这一种类型的过滤器的显示取决于它所基于的域的数据类型。 因为pub_dateDateTimeField类型的。 Django懂得应该给予其如下的过滤选项:任何时候、今天、七天内、这个月、这年

到目前为止, 一切看上去都还好。 让我们再添加一点搜索功能:

search_fields = ['question_text']

这在修改列表界面的顶端添加了一个搜索框。 当某个人输入一个搜索词的时候, Django会搜索question_text 域。 你想包括多少个域都可以 – 因为这个搜索背后使用的是SQL的LIKE 命令, 所以限制一下你的搜索域可能会对搜索速度等有更大的帮助。

这个界面还提供了换页的连接。 默认可以在每一个页显示100个元素。 如果你喜欢的话,换页连接, 搜索框, 过滤器, 日期层次和列表头排序这几项功能是可以工作得很顺畅的。

自定义管理后台的外观

很明显, 在管理后台每一页的顶端有一个”Django 管理后台”的字是很蠢的。 这只是一个文字占位符而已。

通过Django的模板系统,他们是很容易改变的。 Django的管理后台是依靠Django自身搭建的, 它的接口也使用的Django自身的模板系统。

自定义你的项目的模板

在你的项目目录下创建一个templates文件夹。 模板可以存放在任何可以被Django访问到的地方。 (Django和你的服务器运行在同一个地方)。 不管怎么说, 把你的模板与项目放在同一个地方总是一个很好的习惯。

打开你的设置文件, 添加一个TEMPLATE_DIRS设置选项:

TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')]

TEMPLATE_DIRS 是一个当Django读取模板文件时查询的地址列表。

现在在templates里创建一个admin目录, 把Django框架源代码(django/contrib/admin/templates)中的admin/base_site.html文件拷进该文件夹中。

Django源代码放在哪里?
如果你在寻找Django框架源代码的时候出现了问题, 请运行如下代码:

$ python -c "
    import sys
    sys.path = sys.path[1:]
    import django
    print(django.__path__)"

然后就只需打开文件,把{ {site_header|default:_('Django administration')} }(包括大括号)给替换成你想要写的站点名字就可以了。 修改完的代码如下所示:

1
2
3
{ % block crading % }
<h1 id="site-name"><a href="{ % url 'admin:index' % }">Polls Administration</a></h1>
{ % endblock % }

我们通过这种方法来教你如何重写模板。 在实际的项目中, 你更有可能使用django.contrib.admin.AdminSite.site_header属性来更简单地做这个自定义的操作。

这个模板文件包含了许许多多类似于{ %block brading % }{ {title} }的文字。 这里的{ %{ {标签都属于Django的模板语言的一部分。 当Django对 admin/base_site.html进行渲染的时候, 这个模板语言将会被作为基准来生成最终的HTML页面。 如果你现在对于Django的模板语言一无所知, 也不需要担心。 我们将会在第三章的教程中仔细介绍它的。

请记住, 任一个Django的默认后台系统都是可以被修改的。 要想修改一个模板, 只需做如同你对base_site.html做的事情一样 – 把它从默认的目录拷贝出来, 然后对其做修改。

自定义你的应用模板

聪明的读者会问: 既然默认情况下, TEMPLATE_DIRS是空的, 那么Django是怎样招到默认的后台系统模板的? 这个问题的答案是: 在默认情况下, Django自动地查找每一个应用包的templates/子目录, 作为一个备用方法(别忘了django.contrib.admin 也是一个应用)

我们的民调应用并非十分复杂, 所以我们不需要自定义一个后台模板。 但是如果它逐渐发展至更加复杂并需要修改以增加部分功能的时候, 修改这个应用的模板,而非修改整个项目的通用模板, 可能会是一个更好的办法。 在这种情况下, 你可以把这个民调应用使用与任一个新的应用中, 并且确保它能够自己找到其需要的自定义模板。

自定义后台主页

同样的, 你可能希望要自定义Django后台主页的样子。

默认情况下, 它以字母序显示INSTALLED_APPS中所有在管理员应用中注册的应用。 你可能希望能够对整体页面布局做一个大的调整。 毕竟后台主页是整个后台系统最重要的页面, 它应当做到尽可能的易用。

被提供来自定义的模板是 admin/index.html (做你之前对base_site.html相同的事——把它从默认文件夹拷出来, 然后再做修改)。 修改这个文件, 你会看见它使用了一个模板变量叫做app_list。 这个变量包含了所有安装的Django应用。 你也可以不使用这个默认的变量, 反而直接引用一个确定的应用的管理界面。 再说一遍, 不用担心你现在不能理解模板语言, 我们将会在第三章中仔细介绍的。

让我们通过示例来进行学习吧。
通过这个教程, 我们将带领你一步一步的创建一个基础的进行社会调查的网络应用。

它将包含两个部分:

  • 一个公共的网站允许人们进行访问并通过其进行投票
  • 一个后台的管理员应用, 来允许你添加, 修改, 以及删除调查的项目

我们假设你已经安装完成了所有的Django程序。 你可以通过以下命令来检测你的Django程序的安装是否完好以及其版本号:

$python -c "import django; print(django.get_version())"

如果Django被完整安装, 你将得到你所安装的Django的版本号。 如果没有的话, 你将得到一个表示为“No module named django”的错误。

这个教程为Django 1.7, Python 3.2或之后的版本书写。

点击如何安装 Django 来移除老版本的Django并安装一个更新的版本。

在哪里能够获得帮助?
如果你在进行这个教程的过程中遇到了某些问题, 请向django-users发送信息, 或者访问#django on irc.freenode.net来和其它可能能够提供帮助的的Django用户进行交流。

##创建一个新的项目

如果这是你第一次使用Django, 你将需要进行一次初始化安装的过程。 顾名思义, 你需要自动生成一些代码,来组建一个Django 项目 – 也就是为了创建一个Django实例而进行的一系列设置的集合,包括数据库设置(database configuration), Django提供的一些可选功能的设置(Django-specific options)和应用指向的设置(application-specific settings)等。

在命令行窗口下, cd 进入一个你想要你的项目代码存放的文件夹, 然后运行一下命令:

$ django-admin.py startproject mysite

这个命令将会在当前目录下创建一个mysite文件夹。 如果这个命令并没有正常运行, 请参见 运行 django-admin.py 时出现的问题

提示
你需要避免项目名称与Python或者Django的一些内置组件重名, 如你不得不避免使用诸如”django“(与Django本身重名) 或 “test“(与Python的一个内置包重名)

这些代码应该存放在哪里?
如果你之前仅有纯PHP的背景(没有用过任何现代框架)的话, 你很可能会将项目的代码放在网页服务器的根目录(如/var/www)下。 对于Django, 你最好别这么做。 把这些Python代码存放在你的网页服务器的根目录下可能并非是一个很好的注意。 因为这样有着被他人通过网页访问到你的代码的风险。 这对于安全来说有着很大的隐患。
把你的代码放在根目录之外的任何地方把, 比如/home/mycode之类。

让我们看看startproject命令创建了什么东西

1
2
3
4
5
6
7
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py

和你所见的文件结构不一样?
默认的项目文件结构可能已经发生了变化。 如果你看见了一个“扁平化“的架构(没有里面的那一层mysite/目录), 你很可能使用着一个与这个教程并不相同的Django版本。 你需要切换到更老版本的Django教程, 或者是一个更新版本的Django程序

这些文件是:

  • 最外一层mysite/根目录就是一个存放你的项目文件的地方。 它的名字对于Django来说并不重要, 你可以将其重命名为你喜欢的任何东西。

  • manage.py: 一个命令行程序, 使得你能够通过许多方式来与这个Django项目进行交互。 你可以在django-admin.py 与 manage.py读到关于这个文件的更多细节。

  • 里面的一层mysite/目录是你的项目的实际上的Python包文件。 它的名字就是你在导入其中任何东西时所需要使用的包名。(例如: mysite.urls)

  • mysite/__init__.py:一个空的文件, 用来告诉Python这个目录应当被识别为一个Python包 (如果你是一个Python新手的话, 请阅读在Python官方文档中关于包的更多介绍)

  • mysite/settings.py : 这个Django项目的配置文档。 Django设置文档会让你懂得配置文档是如何工作的

  • mysite/urls.py:存放这个Django项目的url路由声明。 一个你的Django网站的内容表(table of contents)。 你可以在URL分发文档处获得关于URL的更多信息。

  • mysite/wsgi.py:一个支持WSGI的服务器运行你的项目时的访问入口。 参阅如何通过WSGI部署以获得更多信息

##配置数据库

现在对 mysite/settings.py进行修改。 这是一个通过代表着不同Django设置的模块变量(module-level variables)来对Django项目进行设置的Python模块。

默认情况下, 配置文件使用的数据库是SQLite。 如果你对于数据库一无所知, 或者你只是想要尝试一下Django, 这是你的最佳选择。 SQLite已经被包括在Python之中, 所以你不需要再安装任何东西以获取对于数据库的支持。

如果你想要设置对于别的数据库的支持, 请安装相对应的数据库适配程序(database bingings), 并且把DATABASES键的下列属性从defalut修改为与你链接的数据库相匹配的属性:

  • ENGINE: django.db.backends.sqlite3或者django.db.backends.postgresql_psycopg2, django.db.backends.mysql, 或django.db.backends.oracle
  • NAME: 你的数据库文件的名字。 如果你使用的是SQLite的话, 数据库将以一个文件的形式存放于电脑中。 在这种情况下, NAME属性将是完整的绝对路径, 包括这个文件的文件名。

如果你并非使用SQLite作为你的数据库引擎, 类似于USER, PASSWORD, HOST 这样的属性必须被添加。 更多资料, 请参考关于数据库设置的官方文档

提示
如果你正在使用 PostgreSQL 或者 MySQL, 请确保在这个时候你已经创建了一个数据库。 通过在你的数据库交互界面中使用CREATE DATABASE database_name命令可以创建一个数据库

如果你正在使用SQLite, 那么你不需要在之前创建任何的数据库 – 数据库将会在需要的时候自动创建。

当你在配置mysite/setting.py文件的时候, 把TIME_ZONE配置成你所在的时区

并且, 请确保INSTALLED_APPS配置选项位于文件的开始。 这个选项列举了所有在此Django项目中运行的Django应用的名字。 应用可以在多个项目中使用, 并且你可以将其封包并分发给他人以供其在自己的项目中使用。

默认情况下, INSTALLED_APPS包含一下应用, 他们都是Django的内置应用:

  • django.contrib.admin: 管理员界面, 你可以在这个教程的第二部分见到他
  • django.contrib.auth: 一个授权系统(用户系统)
  • django.contrib.contenttypes:一个内容类型(content types)框架
  • django.contrib.sessions:一个会话(session)框架
  • django.contrib.messages: 一个消息(message)框架
  • django.contrib.staticfiles :一个用于管理静态文件的框架

这些应用在默认情况下, 出于方便考虑, 都默认被包括在项目之中。

这些应用的某一些需要使用至少一个数据表, 所以我们在使用他们之前, 需要先创建一些数据表。 出于这个目的, 运行以下命令:

$python manage.py migrate

migrate命令查看INSTALLED_APPS配置, 并且根据mysite/settings.py中关于数据库的设置,创建运行必须的数据表, 并且将应用运行所需的数据转移进去。 每一个转移发生时你将会看见一个反馈的消息。 如果你感兴趣的话, 运行数据库程序的命令行工具并且键入dt(PostgreSQL) SHOW TABLES;(MySQL) .schema(SQLite)来显示Django创建的数据表。

对于极简主义者(minimalists)来说:
就像我们之前说过的那样, 这些应用都是为了应付常见情况而被包括进去的, 并非所有人都需要他们。 所以当你不需要他们其中的任何一个的时候, 在运行migrate命令之前,删除它吧。 migrate命令只会遍历INSTALLED_APPS选项并运行其转移操作。

##开发服务器(The development server)

让我们确认一下你的Django项目能否正常运行: 进入mysite目录, 运行如下命令:

$python manage.py runserver

你将会看见如下输出行:

1
2
3
4
5
6
7
Performing system checks...

0 errors found
February 02, 2015 - 15:50:33
Django version 1.7, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

此时你就已经启动了Django开发使用的服务器, 一个完全使用Python写成的轻型的Web服务器。 我们已经将其包括在Django之中, 所以你可以很快速地开发程序, 而不需要再去配置一个产品级的服务器 – 例如Apache – 指导你准备好进行工作。

但是同时也要记住: 不要将这个服务器用于任何实际的产品当中, 这只是一个供开发测试使用的轻型服务器软件。

现在服务器就已经开始运行了, 使用你的网页浏览器访问 http://127.0.0.1:8000/。 你将会看见一个有着淡蓝色优雅色调的”Welcome to Django” 界面。 它成功运行了!

修改端口
默认情况下, runserver命令把开发服务器假设在内网的8000端口上。
如果你想要更改这个端口的话, 把它作为一个命令行参数进行传递。 举例来说, 如下的命令在8080端口启动了一个开发服务器。

$python manage.py runserver 8080

如果你想要修改这个服务器的IP地址, 也把它与端口号一起作为参数传递。 如下的命令将会使服务器监听所有的公有IP地址:

$python manage.py runserver 0.0.0.0:8000

完整的文档请参阅runserver文档

Runserver的自动重载
在需要的时候, 开发服务器会自动重载。 你不需要在对代码进行改动之后重新加载这个服务器。 但是诸如像添加文件这样的操作并不会触发服务器的自动重载, 所以在这种情况下你需要手动重新启动服务器。

##创建数据模型(models)

现在你的运行环境——项目——已经创建起来了, 你可以开始正式的工作了。

每一个你使用Django书写的应用都包含着一个遵守一定约定的Python包。 Django有着自动生成一个app的基础目录结构的功能, 所以你可以更专心于书写代码而非创建文件目录。

项目(Projects) 与 应用(Apps)
一个“项目”和一个“应用”之间究竟有什么差别? 一个应用是一个能够做一些实际的事情的网络应用。 诸如:博客系统, 公共记录的数据库,或者一个简单的民意调查的应用。 一个项目是一个网站的配置文件与应用的集合。 一个项目可以包含许多应用, 一个应用可以在多个项目中使用。

你的应用可以存放在你的Python路径(Python进行包搜索的路径)下的任何地方。 在这个教程之中, 我们将把应用创建在manage.py文件同目录下, 这样拿就可以被作为其自身的一级模块(top-level module)所引用, 而不是mysite的子模块(submodule)。

要创建一个应用, 确保你和manage.py在同一个文件夹下, 并输入如下命令:

$python manage.py startapp polls

这个命令会创建一个polls目录, 其结构如下:

1
2
3
4
5
6
7
8
polls/
__init__.py
admin.py
migrations/
__init__.py
models.py
tests.py
views.py

这个文件结构是我们要编写的民调应用的基础。

使用Django创建一个基于数据库的网页应用的第一步是定义你的数据模型——必须的数据库结构以及一些附加的元数据(metadata)。

思想
模型是你的数据的唯一, 明确的数据源。它包含了你想要存储的数据的必要的类型(fields)以及表现(behaviors)。 Django遵循DRY(Don’t Repeat Yourself)原则。 目的就是让你的数据模型能够定义一次并且能多自动从其中生发出许多东西。
这包括了数据转移(migrations), 不像Ruby on Rails那样, 数据转移完全是由你的数据模型中生发出来的, 并且其仅仅是一个历史记录, 以供Django能够升级你的数据库结构, 以满足你现在的数据模型。

在我们的简单的民调应用之中, 我们将创建两种数据模型: 问题与选项。 一个问题的模型包含一个问题,与一个发布日期。 一个选项的模型有两个域, 选项的文字以及一个点票结果。 每个选项都和一个问题相链接:

这些概念都可以由简单的Python类所表示。 依照如下代码修改polls/models.py文件:

1
2
3
4
5
6
7
8
9
10
11
#polls/models.py
from django.db import models

class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')

class Choice(models.Model):
question = models.ForeignKey(Question)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(defalut=0)

这段代码是十分简单直接的。 每个数据模型由一个继承django.db.models.Model的类表示。 每个数据模型有着一系列的类变量, 其每一个都表示着数据模型在数据库中的一个域。

每个域都由一个Field类的示例所表示——举例来说,CharField表示字符域而DateTimeField表示时间域。 这告诉了Django每一个域其内的数据类型。

每一个Field类的示例的名字(如question_text)这个域的名字——以一种电脑能够读懂的形式。 你将会在你的Python代码中使用这个值, 并且你的数据库将会将其作为其对应的列的名称。

你可以使用一个可选的置于首位的参数来指定一个人能够读得懂的名称。 这个用法常见于Django的一些内联(introspective)的部分之中, 并且它在参考文档之中出现的频率很可能翻倍。 如果这个参数并没有被指定, Django会使用机器能够读得懂的名字。 在这个例子中, 我们只为Question.pub_date指定了一个人能够读懂的名称, 对于其它的数据域, Django将默认使用其变量名称。

某些Field的类有着必填的参数。 例如CharField, 要求你必须提供一个max_length参数。 这不仅仅在数据库结构之中使用到。 也在数据验证之中发挥着作用, 我们将会很快见到。

一个Field也可以有着许多可选的参数, 在这个例子中, 我们将votes域的default参数设置为0

最后, 请注意通过使用ForeignKey, 我们定义了一个数据表之间的关系。这个操作告诉Django每一个Choice与一个单独的Question相对应。 Django支持所有常见的数据库关系, 如:多对一(many-to-one),多对多(many-to-many), 一对一(one-to-one)

##激活数据模型

前面那简短的代码给了Django许多有用的信息。 有了他, Django可以:

  • 为这个应用创建一个数据库结构
  • 为操作Question和Choice对象创建一个Python接入数据库的API

但是首先我们需要告诉我们项目,polls应用已经被安装了。

思想
Django应用都是“即插即用”的: 你可以把一个应用在多个项目中应用, 你也可以将应用分发给他人使用。 因为他们不必与特定的Django环境相绑定。

再次修改mysite/settings.py文件, 修改INSTALLED_APPS设置, 把’polls’加进去。 所以它看上去应该是这个样子的:

1
2
3
4
5
6
7
8
9
10
#mysite/settings.py
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'polls',
)

现在Django懂得应当把polls应用给包括进来了。 运行另外一个命令:

$python manage.py makemigrations polls

你将看见与下面类似的一些信息:

1
2
3
4
5
Migrations for 'polls':
0001_initial.py:
- Create model Question
- Create model Choice
- Add field question to choice

通过运行makemigrations, 你告诉Django你对于你的数据模型做了一些变化, 并且你需要将这些变化被作为一个数据转移(migration)存储起来

Django通过储存数据转移的方式将你的数据模型存储起来——他们就是你的磁盘上的一些文件。 如果你想的话, 你可以为你的新的数据模型去阅读这个数据转移文件。 他就是polls/migrations/0001_initial.py。 别担心, 并不是每次django创建一个新的文件的时候你都需要去读它, 但是它被设计为可以被人所直接编辑的, 以适应你想要适当人为调整Django的工作机理的情况。

有一个命令, 能够自动的运行数据转移并且处理好你的数据库结构 – 它叫做 migrate, 我们稍后将会提到。 但是首先让我们看看数据转移执行了哪些SQL操作。 sqlmigrate 命令接收数据转移文件的名称并且返回其SQL语言:

$ python manage.py sqlmigrate polls 0001

你将看见与如下相似的一些东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
BEGIN;
CREATE TABLE polls_question (
"id" serial NOT NULL PRIMARY KEY,
"question_text" varchar(200) NOT NULL,
"pub_date" timestamp with time zone NOT NULL
);

CREATE TABLE polls_choice (
"id" serial NOT NULL PRIMARY KEY,
"question_id" integer NOT NULL,
"choice_text" varchar(200) NOT NULL,
"votes" integer NOT NULL
);

CREATE INDEX polls_choice_7aa0f6ee ON "polls_choice" ("question_id");

ALTER TABLE "polls_choice"
ADD CONSTRAINT polls_choice_question_id_246c99a640fbbd72_fk_polls_question_id
FOREIGN KEY ("question_id")
REFERENCES "polls_question" ("id")
DEFERRABLE INITIALLY DEFERRED;
COMMIT;

请注意:

  • 确切的输出将会取决于你正在使用的数据库引擎。 如上的例子是为PostgreSQL所生成的

  • 数据表的名字将有你的app的名字与模块名字的小写自动组合而成。 (你可以通过设置来覆盖这个选项)

  • 主键是自动被添加的(你也可以覆盖它)

  • 依照惯例, Django在外键域的名字后添加一个 ‘_id’ (你同样也可以覆盖它)

  • 外键关系由FOREIGN_KEY确定。不用太担心 DEFERRABLE部分, 那只是告诉PostgreSQL直到操作结束不要增加外键的值。

  • Django自动适配你正在使用的数据库。 所以各数据库专有的特性如auto_increment(MySQL), serial(PostgreSQL),integer primary key autoincrement(SQLite) 都被自动设置好了。

  • sqlmigrate命令并不会实际上去运行一个数据转移工作。 它仅仅是把SQL命令显示出来, 让你可以直到实际上发生了什么。 如果你有一个数据库管理员要求知道你对于数据库的操作命令的化, 这将派上不小的用处。

如果你感兴趣的话, 你可以运行python manage.py check 命令, 这个命令将会检查你的项目中的问题而不实际去运行数据迁移或者操作数据库。

现在再运行一次 migrate 命令, 来为你的数据模型创建数据表

1
2
3
4
5
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
Applying <migration name>... OK

migrate命令把所有未进行的数据转移操作进行完成。 (Django 通过数据库中一个叫做django_migrations的数据表来进行跟踪哪一个数据转移操作未被进行)。

数据转移是十分强大的。 它允许你能够编写项目的时候适时地更新你的数据模型,而不需要删除你之前的数据库。 这尤其能够帮助你实时更新你的数据库, 而不用失去之前所有的数据。 我们将在之后更深一层的教学之中解释这个问题, 但是现在, 记住一下进行数据模型改动的三部曲:

  • 改动你的数据模型(在models.py之中)
  • 运行 python manage.py makemigrations来为这些改动创建数据转移文件
  • 运行 python manage.py migrage 来应用这些数据转移

把创建文件与应用更改分开的主要原因是你有可能将数据转移文件上传至你的版本控制系统并且与你的应用一起移动。 这样不仅仅使得你的开发更为简单, 还对于其它的开发者更加方便。

你可以参考 django-admin.py 文档来获得关于manage.py的完整功能的介绍。

##试试API

现在让我们跳入Python Shell的环境中, 试试Django给你的哪些免费(且自由)的API吧。 若要运行Python Shell, 输入如下命令:

$python manage.py shell

我们使用这个命令, 而非简单地输入 python, 原因是manage.py 设置了 DJANGO_SETTINGS_MODULE选项, 允许了Python能够直接获得Django的导入地址。

除了 manage.py
如果你不想使用manage.py, 也没有问题。 只需设置DJANGO_SETTINGS_MODULE 环境变量指向mysite.settings,启动一个纯的Python Shell 环境,然后手动设置Django:

1
2
>>>import django
>>>django.setup()

如果这引发了一个AttibuteError, 你很有可能用着一个与此教程不相符的Django版本。 你需要一个更老的教程或者是切换到一个更新的Django。
你必须在与manage.py 相同的目录下运行python, 或者确保这个目录在python的默认目录下, 在这种情况下, import mysite才能正常工作。
关于此的更多信息, 请参见django-admin.py 的官方文档

现在你就已经在Shell环境中了, 来体验一番数据库API把

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
>>> from polls.model import Question, Choice 
# 导入我们刚刚写的数据模型类
# 目前系统还没有发生任何问题0u0
>>> Questions.objects.all()
[]
#目前Questions中还没有问题
#创建一个新的问题
#关于时差的支持已经在默认的配置文件中, 所以Django需要为pub_date有一个带有时区信息(tzinfo)的时间属性。 使用 timezone.now()而非datetime.datetime.now()将会得到现在正确的时间。
>>> from django.utils import timezone
>>> q = Question(question_text = "What's new?", pub_date = timezone.now())
#将对象保存到数据库中。 你只需要简单明了地调用save()函数
>>> q.save()
#现在这个问题有了它自己的id。 请注意这个id的值有可能是1L或者1, 这取决于你使用的数据库——其实这并不是什么大问题, 它仅仅表明了你的数据库引擎更喜欢使用python的长整形数
>>> q.id
1

#通过Python的属性来访问数据的值
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2015,2,2,13,0,0,775217,tzinfo = <UTC>)

#通过直接改动数据对象的属性来改动值, 然后调用save()函数
>>> q.question_text = "What's up?"
>>> q.save()

#objects.all() 显示数据库中所有的问题
>>> Question.objects.all()
[<Question: Question object>]

等等。 <Question: Question object> 是一个很难看,很没有帮助的显示。 然后我们通过修改Question模型来改正它:

1
2
3
4
5
6
7
8
9
10
11
12
#polls/models.py
from django.db import models

class Question(models.Model):
#...
def __str__(self): #在Python2 中是__unicode__
return self.question_text

class Choice(models.Model):
#...
def __str__(self): #在Python2 中是__unicode__
return self.choice_text

给你的数据模型添加__str__()方法至关重要。 不仅仅是为了你自己的方便, 更是因为在Django自动生成的管理员后台中, 这个将作为数据模型的自动表示方式。

这些都只是普通的Python方法。 出于演示考虑,让我们添加一个特殊的方法:

1
2
3
4
5
6
7
8
9
#polls/models.py
import datetime
from django.db import models
from django.utils import timezone

class Question(models.Model):
#...
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days-1)

注意新增的import datetimefrom django.utils import timezone选项, 分别参见Python标准的datetime模块和Django的时区相关的django.utils.timezone功能。 如果你对于时区功能不够了解的话, 请参考时区支持相关的官方文档

保存这些更改, 并且重新启动一个Python Shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
>>>from polls.models import Question, Choice
#确保我们新增的__str__()工作正常
>>> Questions.objects.all()
[<Question: Whats up>]
#Django提供了一个功能齐全的基于键值的数据库查找API
>>> Question.objects.filter(id=1)
[<Question: Whats up>]
>>> Question.objects.filter(question_text__startswith='What')
[<Question: Whats up>]

#得到当年发布的问题
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
[<Question: Whats up>]

#查找一个并不存在的ID, 将会引发一个异常
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.

#通过主键进行查找是一个极其常用的操作, 所以Django提供通了一个主键查找的捷径, 以下命令与Question.objects.get(id=1)作用等同
>>>Question.objects.get(pk=1)
[<Question: Whats up>]

#确保我们自己写的方法成功运行

>>>q = Question.objects.get(pk=1)
>>>q.was_published_recently()
True

#给某个问题制定多个选择。 构造器将会创建一个新的Choice对象, 进行INSERT操作。 把选项加入进入并返回新的Choise对象。 Django创建了一个集合去管理外键关系的“另外一边”(例如一个问题的选项), 它们可以通过API进行访问
>>> q = Question.objects.get(pk=1)

#显示问题相关的所有的选项——目前还一个都没有
>>> q.choice_set.all()
[]

#创建三个选项
>>>q.choice_set.create(choice_text = "Not much", votes=0)
<Choice: Not much>
>>>q.choice_set.create(choice_text = "The sky", votes=0)
<Choice: The sky>
>>>c = q.choice_set.create(choice_text = "Just hacking again", votes=0)

#Choice对象有API接口来访问它们相关的问题
>>> c.question
<Question: Whats up?>

#反之亦然: Question对象也可以访问其相关的选项
>>> q.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> q.choice_set.count()
3

#API按你的需要自动维护数据之间的关系。
#使用双下划线来分割关系
#这在多深的层级都可以使用, 没有限制
#下面的代码寻找所有今年发布的问题的所有选项
>>>Choice.objects.filter(question__pub_date__year=current_year)
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]

#让我们使用delete()函数删除其中的一个选项
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

总算是settle了, 这个VPS在Vultr 的日本节点。 5刀的plan。 1CPU+700M RAM。 似乎不是很够用。。
开了2G 的SWAP才勉强跑起来gitlab。。 等回来看要不再升级成10刀的吧。


2016/5/28 5:34

居然折腾了一个通宵。。 不过看起来整个网站的基本架构已经都搭起来了。 也是没白费功夫。

P.S. 这个Hexo Theme叫做Jane。 Pretty nice, isn’t it?


2016/5/28 6:12

HTTPS也配好啦, 自从有了Let’s encrypt. 配置https都用不了10分钟wwwwww