目前我们已经建好了模型并填充了数据,现在可以将组装零件了。我们会了解如何将数据放到视图中,以及如何在模板中展示数据。
在Django中创建数据驱动的页面主要有5个步骤。
views.py
文件中导入使用的模型这几个步骤概括了Django的框架是如何分离模型,视图和模板的。
主页的需求之一就是展示访问最多的5个分类。
这个需求需要通过上面5个步骤来完成。首先,打开rango/views.py
,从Rango的models.py
文件导入模型Category
。
# Import the Category model
from rango.models import Category
在开始之前,我们需要修改我们的index()
函数。回想一下,index()
函数应该是负责主页视图。修改函数如下。
def index(request):
# Obtain the context from the HTTP request.
context = RequestContext(request)
# Query the database for a list of ALL categories currently stored.
# Order the categories by no. likes in descending order.
# Retrieve the top 5 only - or all if less than 5.
# Place the list in our context_dict dictionary which will be passed to the template engine.
category_list = Category.objects.order_by('-likes')[:5]
context_dict = {'categories': category_list}
# Render the response and send it back!
return render_to_response('rango/index.html', context_dict, context)
这里在一次执行了步骤2和步骤3。首先,我们查询Category
来获得访问最多的5个分类。这里我们使用方法order_by()
以降序排列分类的受欢迎程度——即符号-
表示降序。然后限制列表中开始的5个Category
对象。
查询结束后,我们将列表的索引(存在变量category_list
中)传递给一个字典,context_dict
。这个字典在render_to_response()
调用中作为上下文的一部分传递给模板引擎。
更新了视图后,我们还要做的就是修改位于工程template
目录的模板文件rango/index.html
。修改HTML代码如下。
<!DOCTYPE html>
<html>
<head>
<title>Rango</title>
</head>
<body>
<h1>Rango says...hello world!</h1>
{% if categories %}
<ul>
{% for category in categories %}
<li>{{ category.name }}</li>
{% endfor %}
</ul>
{% else %}
<strong>There are no categories present.</strong>
{% endif %}
<a href="/rango/about/">About</a>
</body>
</html>
这里,我们使用Django的模板语言if
和for
控制语句来展示数据。在页面的<body>
中,测试categories
——含有我们列表的上下文变量——实际上包含任何分类(例如{% if categories %}
)。
如果有,我们进而构建出一个无需的HTML列表(在标签<ul>
中)。循环{% for category in categories %}
遍历结果列表,在一对<li>
标签中作为列表元素输出每个分类的名字({{ category.name }}
)。
如果没有分类存在,会输出一条消息。
就像在Django模板语言中看到的,所有的命令都是通过{%
和%}
括起来,而变量是通过{{
和}}
括起来。
如果你现在通过地址http://127.0.0.1:8000/rango ,你就会在标题的下面看到三个分类,如图[1]
根据Rango需求,还需要展示一个与分类相关的页面列表。这里有些挑战。需要新建一个新的可参数化的视图。还需要新建URL规则和URL字符串来表示分类名。
我们首先考虑URL的问题。一种方式就是在URL中给每个分类一个唯一的ID。例如,我们可以这样创建分类/rango/category/1/
或者/rango/category/2
,这里数值分别对应ID为1和2的分类。然而,这样的分类规则不易被人们理解。虽然我们可以推断出数值是对应分类的,但是用户怎么会知道那个分类对应ID1或者2?如此一来,用户只能去尝试。
不过,我们可以将分类名作为URL的一部分。/rango/category/Python/
应该会给我们一个关于Python分类的页面列表。这是一个简单的,可读的并且有意义的URL。如果我们采用这种方法,那就必须处理分类名由多个单词组成的情形,例如“Other Frameworks”,等。
注意:
设计简洁的URL是web设计中一个非常重要的方面。细节见Wikipedia’s article on Clean URLs。
选好了URLs设计规范,可以开始后续工作了。我们要采取下面的步骤。
rango/views.py
。rango/views.py
中创建一个新视图——名为category
——这个视图会接收一个用来存放编码后的分类名的参数category_name_url
。templates/rango/category.html
urlpatterns
,将category
视图映射到rango/urls.py
的URL模式我们还要更新index()
视图和index.html
模板来提供到分类视图的链接。
在rango/views.py
中,我们首先需要导入Page
模型。这意味着,我们必须在文件的开头加入下面的导入语句。
from rango.models import Page
接下来,添加新视图,category()
。
def category(request, category_name_url):
# Request our context from the request passed to us.
context = RequestContext(request)
# Change underscores in the category name to spaces.
# URLs don't handle spaces well, so we encode them as underscores.
# We can then simply replace the underscores with spaces again to get the name.
category_name = category_name_url.replace('_', ' ')
# Create a context dictionary which we can pass to the template rendering engine.
# We start by containing the name of the category passed by the user.
context_dict = {'category_name': category_name}
try:
# Can we find a category with the given name?
# If we can't, the .get() method raises a DoesNotExist exception.
# So the .get() method returns one model instance or raises an exception.
category = Category.objects.get(name=category_name)
# Retrieve all of the associated pages.
# Note that filter returns >= 1 model instance.
pages = Page.objects.filter(category=category)
# Adds our results list to the template context under name pages.
context_dict['pages'] = pages
# We also add the category object from the database to the context dictionary.
# We'll use this in the template to verify that the category exists.
context_dict['category'] = category
except Category.DoesNotExist:
# We get here if we didn't find the specified category.
# Don't do anything - the template displays the "no category" message for us.
pass
# Go render the response and return it to the client.
return render_to_response('rango/category.html', context_dict, context)
新视图的步骤跟index()
视图一致。我们首先从request中获得context,然后构建一个上下文字典,渲染模板,发送结果。在这里,区别就是上下文字典的构造有点复杂。要构造上下文字典,我们需要根据传递给视图函数category()
的参数category_name_url
来确定哪个分类可见。一旦确定了分类,从数据库中取得相关信息,并将结果添加到上下文字典context_dict
中。后面会给出如何从URL中获得category_name_url
的值。
在视图函数category()
中,我们假设分类名category_name_url
中的空格被转成了下划线。所以我们把所有的下划线替换成了空格。不幸的是,这是一种相当原始的处理URL中分类名编解码的方式。作为后续的联系,你需要完成两个函数来完成分类名的编解码。
现在给新视图创建模板。在目录<workspace>/tango_with_django_project/templates/rango/
中,创建category.html
。在新建的文件中,添加下面的代码。
<!DOCTYPE html>
<html>
<head>
<title>Rango</title>
</head>
<body>
<h1>{{ category_name }}</h1>
{% if category %}
{% if pages %}
<ul>
{% for page in pages %}
<li><a href="{{ page.url }}">{{ page.title }}</a></li>
{% endfor %}
</ul>
{% else %}
<strong>No pages currently in category.</strong>
{% endif %}
{% else %}
The specified category {{ category_name }} does not exist!
{% endif %}
</body>
</html>
上面的HTML实例代码中,再次说明了我们是如何获得通过上下文传递给模板的数据的。我们使用了变量category_name
和category
,pages
对象。如果category
在模板上下文中没有定义,即数据库中没有这个分类,会出现一条友好的提示说明这种情况。如果情况相反,我们继续处理pages
。如果pages
没有定义或者没有任何元素,我们会给出一条消息说明没有页面。否则,会在HTML的列表中展示某个分类的页面。对于列表中的每个页面,会给出title
和url
属性。
现在,看下我们是怎么把category_name_url
传递给函数category()
的。要完成这个,我们需要修改Rango的urls.py
,更新urlpatterns
元组如下。
urlpatterns = patterns('',
url(r'^$', views.index, name='index'),
url(r'^about/$', views.about, name='about'),
url(r'^category/(?P<category_name_url>\w+)/$', views.category, name='category'),) #New!
正如你看到的,我们添加了一个相当复杂的元素,它会在正则表达式r'^(?P<category_name_url>\w+)/$'
匹配的时候调用函数view.category()
。我们的正则表达式会在删除URL的斜杠之前去匹配任意的字符序列(例如 a-z,A-Z,_,或者0-9)。匹配的值会作为强制参数request
后面的唯一参数category_name_url
传递给views.category()
。本质上说,硬编码到正则表达式中的变量名就是Django在你的视图函数定义中寻找的参数名。
我们写好了新的视图,一切就绪——不过,还需要完成一件事。主页视图需要更新以便用户可以看到列出的分类页面。更新rango/views.py
视图的index()
的如下。
def index(request):
# Obtain the context from the HTTP request.
context = RequestContext(request)
# Query for categories - add the list to our context dictionary.
category_list = Category.objects.order_by('-likes')[:5]
context_dict = {'categories': category_list}
# The following two lines are new.
# We loop through each category returned, and create a URL attribute.
# This attribute stores an encoded URL (e.g. spaces replaced with underscores).
for category in category_list:
category.url = category.name.replace(' ', '_')
# Render the response and return to the client.
return render_to_response('rango/index.html', context_dict, context)
正如行间的注释所指出的,取出数据库返回的每个分类,遍历分类表对每个分类进行友好的URL编码。这种友好的URL会作为属性保存到Category
对象的内部(例如,我们利用python的动态类型随时添加这个属性)。
然后将分类列表category_list
传递到模板的上下文,这样就可以渲染了。当每个分类的url
属性变得可用,可以更新模板index.html
如下。
<!DOCTYPE html>
<html>
<head>
<title>Rango</title>
</head>
<body>
<h1>Rango says..hello world!</h1>
{% if categories %}
<ul>
{% for category in categories %}
<!-- Following line changed to add an HTML hyperlink -->
<li><a href="/rango/category/{{ category.url }}">{{ category.name }}</a></li>
{% endfor %}
</ul>
{% else %}
<strong>There are no categories present.</strong>
{% endif %}
</body>
这里我们更新了每个列表元素(<li>
),在其中添加了一个HTML超链接(<a>
)。这个超链接有一个href
属性,我们使用这个属性来指定由{{ category.url }}
定义的目标URL。
现在访问Rango的主页。你就会看到主页列出了所有的分类。分类应该是可以点击的链接。在Python
上点击会带你到Python
细节分类的视图,如图2所示。如果你看到类似Official Python Tutorial
的链接列表,那么你已经成功的建立了新视图。试着访问并不存在的分类,例如/rango/category/computers
。你应该会看到该分类没有页面的提示消息。
20131214@南山 桃苑公寓
评论