面向大众
至今为止我们做了一个不错的后端系统,但普通用户却看不见网站的内容。终端用户目前只会得到404错误页或者管理员登录界面(如果用户聪明到能发现它)。现在,让我们来添加一些东西。
首先,我们需要编辑urls.py文件来让我们的urls与我们之后要创建的函数联系起来。为了区别特定应用中的url细节,我们可以在posts中创建一个新的urls.py。这样做的好处在于,当我们想快速地向另一个工程中插入一个应用时,我们能简单地在urls.py中添加一行来把这个应用链接起来。那么,我们需要修改在工程文件夹内的urls.py中的urlpattern一项:
urlpatterns = patterns(”,
(r’^', include(‘posts.urls’)),
(r’^admin/’, include(admin.site.urls)),
)
像管理员url系统一样,我们利用项目中的urls.py来做url映射。由于项目中的urls.py是我们blog的主要特征,我们将其设为总url。
为什么在每个应用中都要添加urls.py呢?
这样做是为了让开发者自己定义应用被分配的层次。项目可以通过使用其中应用提供的urls.py或者自己实现urls.py来进行url映射。另外,项目自身可能含有其他特定的应用,用来向某个应用提供可选的view,它们使用的是相同的model,这种情况你需要更多的手工调整。尽管如此,应用中的view应当更加灵活,尽量避免不必要的重复。urls.py文件只是提供了简单化的项目实现以及有着默认url映射的未来的项目。
在你的posts应用目录下创建一个新的urls.py文件。应用中的urls.py和在项目中的urls.py作用一样。现在,让我们来实现两个前端的view:
1. 一个用来取得最近的帖子并展示出来的view
2. 一个用来展示特定的帖子及其评论的view
这里我们会使用到Django中内置的评论系统来实现我们需要的评论功能。但首先我们需要展示blog中的帖子。
Generic views
回到我们新建的urls.py。在解释之前,先加入下列代码。
from django.conf.urls.defaults import *
from models import Post
queryset = {‘queryset’: Post.objects.all()}
urlpatterns = patterns(‘django.views.generic.list_detail’,
url(‘^$’, ‘object_list’, queryset, name=”posts”),
url(‘^(?P<object_id>\d+)/$’, ‘object_detail’, queryset, name=”post”),
)
这里我们用到了Django提供的generic views,它帮我们省去了自己写函数代码的麻烦:增删改查操作。所以我们在这里使用它。url的前缀用来清楚地映射每个url。
我们import了自己定义的Post 模型,这样做是为了从数据库取得数据并传递到我们的view中。Generic views 接受一个称做queryset的附加参数,在其中包含着view用来取得数据库结果的对象查询。因为model查询是通过对象允许多种方式地取得model实例的属性。queryset是懒惰的求值程序不会有什么问题,于是在第二个url模式的view中会自动的将queryset中的数据缩小范围到某个特定的id而不用从数据库中取出所有的帖子。
Querysets 是懒惰的
Queryset会尽量避免数据库查询。这意味着,如下:
myqs = Post.objects.all()
其实并没有操作数据库。只在当结果中确切的数据被请求时,如:
myqs.count() # uses a COUNT query
myqs.get(id=1) # uses WHERE id=1, but expects to only fetch 1 record
print myqs # executes the current query
当这样时,才会执行数据库查询。访问一个特定下标的值时也会自动执行queryset,但只执行一次:
myqs[0] # fetches all the query values, returns the first result
myqs[1] # doesn’t query since above did, returns the second result
你还可以建立一些基于queryset中数据的新的queryset,这么做也不会进行数据库操作:
import datetime
newqs = myqs.filter(pub_date__lte=datetime.datetime.now()) # no database call
newqs = newqs.exclude(id=1) # no database call
for post in newqs: # hits the database once and cached for iterating
print post.title
如果你想了解更多的信息,可以参考Django官方文档“Querysets are Lazy” 与"When QuerySets are evaluated"
Url 函数
另一个新东西是我们用普通元组实现的url 函数。它提供了与我们之前用到的添加了一些特征的3元组一样的功能。其中最值得注意的特征叫做名称关键字。它允许开发者用一个自定义的名字来引用一个特定的url。这在我们写自己的模板是很有帮助。
最后如果你想知道post应用的url模式,可以参考官方文档regular expression named capture。如果你对正则表达式不太熟悉的话,它是用来抓取括号内的值,并将其命名为object_id后传到view中。在我们的例子中,我们匹配至少一位数字(\d+),这个值在generic view中用作id,
Order By
如果仔细看我们上面的urls.py中的代码,你会注意到我们并没有对帖子做任何排序通过用Post.objects.order_by('-pub_date')(它对所有的帖子按发布日期降序排列)替换掉Post.objects.all可以做到对帖子的简单排序。当然,你也可以自己添加更多的参数进行排序避免某个参数相等的情况。
Templates
现在让我们来为我们的两个公共view页做html模板。Django有一套定制模板语言,它大致可分为以下两种东西:1. 变量输出用{{ variable }} 加上可选过滤器用{{ variable|filter_name }}
2.模板标签或函数调用通过 {% if variable_exists_or_not %}{% endif %},等。
尽管if语句看起来不像是个函数调用,但它确实是的。这里一个函数被简化为用一个关键字来代替函数调用,并且之后的值都被传入函数。
当然这里我们要介绍的不是好的html设计,所以就让html看起来越简单越好。现在,在你的项目目录中创建一个称做templates的文件夹(名字取决于你在settings.py中的命名)。
Base.html
Django模板系统的中心思想是扩展的使用。一个父模板用来定义一系列可以被子模板重载的块。这一点在实际使用后会变得很有体会。那么,让我们创建一个所有其他模板的基础模板,它的作用是统一网站的外观,就让我们命名为base.html。下面是代码:
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml” xml:lang=”en” lang=”en”>
<head>
<meta http-equiv=”Content-type” content=”text/html; charset=utf-8″ />
<title>My Blog</title>
<link rel=”stylesheet” href=”{{ MEDIA_URL}}css/style.css” type=”text/css” media=”screen,projection” />
</head>
<body>
<h1><a href=”/”>My Blog</a></h1>
{% block content %}{% endblock %}
</body>
</html>
在样式表中,我们输出在settings.py中定义的MEDIA_URL用来指明样式表的路径(这有一点超出了我们访问MEDIA_URL的范围,可以看官方文档RequestContext)。我们还没有将这些资源映射到开发服务器上的url,所以这些东西暂时没有作用,但我们之后会使用的到。
之后我们定义了这个模板中的一个称做content的块。当然,我们能在这个块不被重载的情况下向其中输入一些默认的html,但作为主要的内容,这部分位置是用来做替换的。
为了使MEDIA_URL正确工作,我们需要对我们的媒体文件做url映射。可以这样修改urls.py。(将下面代码加在你的文件底部)
from django.conf import settings
if settings.DEBUG:
urlpatterns += patterns(‘’,
(r‘^rsrc/(? .*)$’, ‘django.views.static.serve’, {
‘document_root’: settings.MEDIA_ROOT})
)
关于django.views.static.serve的注意
这个view不是用来做成型产品这一点值得注意。这在Django的官方文档中清楚的描述了,这也是为什么我将其置于settings中的原因。如果出问题了就DEBUG。
上面的代码import了settings配置并且如果它是在DEBUG模式下,就会添加static serve content到url模式。你可以将rsrc改成任意你个人的MEDIA_URL(注意前面没有'/')。其他的东西都应当是可解释的。
Listing page
现在我们要实现generic view使用的模板。在urls.py定义的generic view会默认的到某些地方查找模板(你可以通过template_name参数定制这个行为)。当默认情况下,它会去为object_list查找<app_name>/<model_name>_list.html 以及为object_detail查找<app_name>/<model_name>_detail.html 。所以对于我们特定的应用,我们可以用include标签来包含另一个位于posts/_post.html的额外html文件。
首先是post_list.html:
{% extends ‘base.html’ %}
{% block content %}
{% for object in object_list %}
<hr/>
{% include ‘posts/_post.html’ %}
{% endfor %}
{% endblock %}
在这里显得相当简单。我们告诉Django我们继承所有base.html的内容并扩展它。我们重载content块,循环输出每一条帖子,并在帖子顶端加一条水平线做分隔,这就做到通过包含_post.html文件来展示帖子内容的目的。不知你是否注意到,所有模板路径是相对于基本模板目录而言的(所以我们要在前面加上'posts')。
你也许想知道我们如何获得object_list变量。object_list由object_list generic view 提供(在Django官方文档中有描述),在object_list中会包含我们帖子的列表。所以用python的方式,我们重复所有的帖子(post),并且为每个html块生成各自的对象。_post.html会继承所有在模板中可访问的变量,它就像被插入模板一样被包含其中。在这里我们保留帖子的命名object来区别于post,因为object_detail generic view 使用object变量来代表一个被取到的post。使用同样的变量使使用者能反复使用这个包含方式。
Detail page
我们的posts/post_detail.html 会看起来更简单:
{% extends ‘base.html’ %}
{% block content %}
{% include ‘posts/_post.html’ %}
{% endblock %}
没什么新东西需要解释。但我们同时需要实现_post.html模板:
<div id=”object_{{ object.id }}”>
<h2>
<a href=”{% url post object.id %}”>{{ object.title }}</a>
<small>{{ object.pub_date|timesince }} ago</small>
</h2>
{{ object.body }}
</div>
object变量代表了我们与之交互的post模型。所以 object.id, object.title, object.body都是我们之前定义的post模型中的字段,同样,他们还可以是字段,函数调用(无参数)。
另外我们能添加一个’返回‘链接。在 post_detail.html 中加上:
<a href=”{% url posts %}”>Back to posts</a>
由于posts的url模式不需要任何参数,我们可以空着那个位置
在这个模板中,我们还使用了一个模板过滤器,timesince,来将datetime字段转成距当前的时间。模板过滤器只是用在变量输出中(即在{{ }}中),作用是改变输出的方式。这些过滤器是从左边接受参数的函数,并且是栈式的。所以我们可以这么做:{{ object.pub_date|timesince|upper }} 来使输出均为大写字母。
PS:
感觉这次翻译得比较糟,有些东西我能理解,但翻译过来发现看不大懂了。这里面比较值得注意的应该是url pattern 与 generic view,具体可参阅官方文档