赵走x博客
网站访问量:151916
首页
书籍
软件
工具
古诗词
搜索
登录
17、部署 Django 项目
16、自动化测试
14、jQuery 和 Django
13、Webhose 搜索
12、集成 Bootstrap
11、使用 Django-Registration-Redux
10、cookie 和会话
9、用户身份验证
8、模板进阶
7、表单
6、模型、模板和视图
5、模型与数据库
4、模板和媒体文件
3、Django 基础
2、前期准备工作
1、导言
6、模型、模板和视图
资源编号:76309
Tango with Django
Python Web
热度:71
现在,我们定义了两个模型,还在数据库中填充了一些示例数据,接下来可以连接模型、视图和 模板,伺服动态内容了。本章说明在首页显示分类的过程,并为各分类创建专属页面,显示分类 下的链接。
现在,我们定义了两个模型,还在数据库中填充了一些示例数据,接下来可以连接模型、视图和 模板,伺服动态内容了。本章说明在首页显示分类的过程,并为各分类创建专属页面,显示分类 下的链接。 # 6.1 创建数据驱动页面的流程 在 Django 中创建数据驱动页面主要分为 5 步: ➊ 在 views.py 文件中导入想使用的模型。 ➋ 在视图函数中查询模型,获取想呈现的数据。 ➌ 把从模型获取的数据传给模板上下文。 ➍ 创建或修改模板,显示上下文中的数据。 ➎ 把 URL 映射到视图上(如果还未做的话)。 以上就是在 Django 框架中把模型、视图和模板连接在一起的步骤。 # 6.2 在首页显示分类 客户对主页的要求之一是显示最受欢迎的 5 个分类。下面根据上述步骤实现这一要求。 ### 导入所需的模型 首先完成第一步。打开 rango/views.py 文件,在顶部其他导入语句之后从 Rango 应用的 models.py 文件中导入 Category 模型: ``` # 导入 Category 模型 from rango.models import Category ``` ### 修改 index 视图 在此之前务为 Category 模型添加 likes 字段。 下面完成第 2 步和第 3 步。我们要修改主页的视图函数,即 index()。根据下述代码修改。 ``` def index(request): # 查询数据库,获取目前存储的所有分类 # 按点赞次数倒序排列分类 # 获取前 5 个分类(如果分类数少于 5 个,那就获取全部) # 把分类列表放入 context_dict 字典 # 稍后传给模板引擎 category_list=Category.objects.order_by('-likes')[:5] context_dict={ 'categories':category_list } # 渲染响应,发给客户端 return render(request,'rango/index.html',context_dict) ``` 这里的 Category.objects.order_by('-likes')[:5] 从 Category 模型中查询最受欢迎的前 5 个分 类。order_by() 方法的作用是排序,这里我们根据 likes 字段的值倒序排列。-likes 中的 - 号表 示倒序(如果没有 - 号,返回的结果是升序排列的)。因为我们想获得一个分类对象列表,所以 使用 Python 的列表运算符从列表中获取前 5 个对象([:5]),返回一个 Category 对象子集。 查询结束后,把列表的引用(category_list 变量)传给 context_dict 字典。最后把这个字典作 为模板上下文传给 render() 函数。 ### 修改 index 模板 更新视图之后,接下来要做第 4 步,更新项目根目录中 templates 目录里的 rango/index.html 模 板。根据下述代码片段修改模板中的 HTML。 ``` {% load staticfiles %}
Rango
Rango says...
hey there partner!
{% if categories %}
{% for category in categories %}
{{ category.name }}
{% endfor %}
{% else %}
There are no categories present.
{% endif %}
About Rango
``` 这里,我们使用 Django 模板语言提供的 if 和 for 控制语句呈现数据。在页面的 `` 元素中, 我们判断 categories(包含分类列表的上下文变量)中有没有分类({% if categories %})。 如果有分类,构建一个 HTML 无序列表(`
`标签)。for 循环迭代分类列表({% for category in categories %}),在列表项目(`
` 标签)中输出各分类的名称({{ category.name }})。 如果没有分类,显示一个消息,指明没有分类。 从上述代码片段可以看出,Django 模板语言中的命令放在 {% 和 %} 之间,而变量放在 {{ 和 }} 之 间。 现在访问 Rango 的主页应该会看到页面标题下方显示着分类列表,如图 6-1 所示。  # 6.3 创建详情页面 根据设计要求,每个分类还有对应的详情页面。这里有些挑战要克服。我们要定义一个参数化视 图,而且 URL 模式中要编码分类名称。 ### URL 设计和映射 先解决 URL 问题。我们可以在 URL 中使用分类的唯一 ID,例如 /rango/category/1/ 或 /rango/category/2/,其中的数字 1 和 2 是分类的唯一 ID。可是从 ID 上看不出到底是哪个分类。 更好的方法是在 URL 中使用分类的名称。例如,可以通过 URL /rango/category/python/ 访问与 Python 相关的网页列表。这样的 URL 简单、可读性高,而且具有一定的语义。如果采用这种形 式,还要处理有多个单词的分类名,例如“Other Frameworks”。 为此,我们要使用 Django 提供的 slugify 函数。 ### 为分类添加 slug 字段 为了得到可读性高的 URL,我们要为 Category 模型添加一个别名(slug)字段。然后使用 Django 提供的 slugify 函数把空白替换为连字符,例如 "how do i create a slug in django" 将变成 "how-do-i-create-a-slug-in-django"。 >不安全的 URL URL 中虽然可以有空格,但是却会导致安全问题。详情参见因特网工程任务组(Internet Engineering Task Force)发布的 [URL RFC](http://www.ietf.org/rfc/rfc1738.txt "URL RFC")。 此外,还要覆盖 Category 模型的 save() 方法,调用 slugify() 函数更新 slug 字段。注意,只要 分类名称变了,别名就随之改变。参照下述代码片段更新模型,别忘了导入 slugify() 函数。 ``` from django.template.defaultfilters import slugify class Category(models.Model): name = models.CharField(max_length=128, unique=True) likes=models.IntegerField(default=0) views=models.IntegerField(default=0) slug=models.SlugField() def save(self, *args,**kwargs): self.slug=slugify(self.name) super(Category,self).save(*args,**kwargs) class Meta: verbose_name_plural='Categories' def __str__(self): # Python 2 还要定义 __unicode__ return self.name ``` 更新模型之后,接下来要把变动应用到数据库上。然而,数据库中已经有数据了,因此我们必须 考虑改动产生的影响。其实我们想做的很简单,就是从分类名称中得到别名(此项操作在初次保 存记录时执行)。通过迁移工具能把 slug 字段添加到数据库中,而且可以为该字段指定默认值。 可是,每个分类的别名应该是不同的。因此,我们将先执行迁移,然后重新运行填充脚本。之所 以这么做,是因为填充脚本会在分类上调用 save() 方法,从而触发上面实现的 save() 方法,更新各分类的别名。 执行下述命令,执行迁移: ``` $ python manage.py makemigrations rango $ python manage.py migrate ``` 我们没有为 slug 字段指定默认值,而且数据库中有数据,因此 migrate 命令会给你两个选择。选 择提供默认值的选项,输入一个空字符串(两个引号,即 '')。然后运行填充脚本,更新 slug 字段。 ``` $ python populate_rango.py ``` 执行 python manage.py runserver 命令,启动 Django 开发服务器,在管理界面中查看模型中的数 据。 如果此时通过管理界面添加分类,可能会遇到一两个问题。 ➊ 假设要添加的分类名是“Python User Groups”。保存时 Django 会阻止你,让你填写别名。虽 然可以自己动手输入“python-user-groups”,但是这样容易出错。如果能自动生成别名就好 了。 ➋ 如果有个分类名为“Django”,还有个名为“django”,又会遇到一个问题。因为 slugify() 函 数生成的别名是小写形式,所以无法区分别名“django”对应于哪个分类。 第一个问题的解决方法有两个。其一,更新模型,把 slug 字段设为允许空值: ``` slug = models.SlugField(blank=True) ``` 其二,定制管理界面,在输入分类名称时自动填写别名。请参照下述代码更新 rango/admin.py。 ``` from django.contrib import admin from rango.models import Category, Page ... # 添加这个类,定制管理界面 class CategoryAdmin(admin.ModelAdmin): prepopulated_fields = {'slug':('name',)} # 注册定制界面的类 admin.site.register(Category, CategoryAdmin) ... ``` 在管理界面中添加一个分类试试。 第二个问题也不难解决,只需把 slug 字段设为唯一的。为 slug 字段添加约束: ``` slug = models.SlugField(unique=True) ``` 添加这个约束之后便可以通过别名唯一标识分类。你可能想在之前就添加唯一性约束,但是这样 一来执行迁移时把别名都设为空字符串就会遇到问题,因为违背了唯一性约束。此外也可以删除 数据库,然后重建,但这并不适合所有情况。 >迁移混乱 事先最好规划好数据库,尽量不修改。填充脚本的作用是便于删除数据库后重建。 有时,删除数据库后重建比找出问题后再设法解决要方便。你可以练习编写一个脚本,输出 数据库中的数据。这样每次修改数据库都可以把数据输出到一个文件中保存起来,供以后查 看。 ### 创建分类页面的步骤 为了实现可通过 `/rango/category/
/` 访问的分类页面,我们要做几处修改。基 本步骤如下: ➊ 把 Page 模型导入 rango/views.py 模块。 ➋ 在 rango/views.py 模块中定义一个新视图,命名为 show_category()。这个视图有个额外的参数,category_name_slug,用于传入编码后的分类名称。为了编码和解码category_name_slug,要定义两个辅助函数。 ➌ 创建一个模板,templates/rango/category.html。 ➍ 更新 rango/urls.py. 中的 urlpatterns,把这个新视图映射到 URL 模式上。 此外还要更新 index() 视图和 index.html 模板,添加指向分类页面的链接。 ### 分类视图 在 rango/views.py 中,首先导入 Page 模型,即把下述导入语句添加到文件顶部: ``` from rango.models import Page ``` 然后定义视图 show_category()。 ``` def show_category(request, category_name_slug): # 创建上下文字典,稍后传给模板渲染引擎 context_dict = {} try: # 能通过传入的分类别名找到对应的分类吗? # 如果找不到,.get() 方法抛出 DoesNotExist 异常 # 因此 .get() 方法返回一个模型实例或抛出异常 category = Category.objects.get(slug=category_name_slug) # 检索关联的所有网页 # 注意,filter() 返回一个网页对象列表或空列表 pages = Page.objects.filter(category=category) # 把得到的列表赋值给模板上下文中名为 pages 的键 context_dict['pages'] = pages # 也把从数据库中获取的 category 对象添加到上下文字典中 # 我们将在模板中通过这个变量确认分类是否存在 context_dict['category'] = category except Category.DoesNotExist: # 没找到指定的分类时执行这里 # 什么也不做 # 模板会显示消息,指明分类不存在 context_dict['category'] = None context_dict['pages'] = None # 渲染响应,返回给客户端 return render(request, 'rango/category.html', context_dict) ``` 这个视图的基本步骤与 index() 视图一样。首先定义上下文字典,然后尝试从模型中提取数据, 并把相关数据添加到上下文字典中。我们通过传给 show_category() 视图函数的 category_name_slug 参数确认要查看的是哪个分类。如果通过别名找到了分类,获取与之关联的 网页,并将其添加到上下文字典 context_dict 中。 ### 分类模板 下面为这个新视图创建模板。在 `
/tango_with_django_project/templates/rango/` 目录中 新建 category.html 文件,写入下述代码。 ```
Rango
{% if category %}
{{ category.name }}
{% if pages %}
{% for page in pages %}
{{ page.title }}
{% endfor %}
{% else %}
No pages currently in category.
{% endif %} {% else %} The specified category does not exist! {% endif %}
``` 上述 HTML 代码再次展示了如何通过 {{ }} 标签使用模板上下文中的数据。我们访问了 category 和 pages 对象,以及它们的字段,例如 category.name 和 page.url。 如果 category 存在,再检查分类下有没有网页。如果有,使用模板标签 {% for page in pages %} 迭代网页,显示 pages 列表中各网页的 title 和 url 属性。网页的信息在一个 HTML 无序列表 (`
` 标签)中显示。如果你不熟悉 HTML,可以查看 W3Schools.com 网站中的 [HTML 教程](http://www.w3schools.com/html/ "HTML 教程"), 学习不同的标签。 使用 Django 模板的条件标签 {% if %} 判断上下文中有没有某个对象十分方便。为了避免出 错,应该检查对象是否存在。 在模板中检查条件(例如上例中的 {% if category %})也更符合语义。条件判断的结果直 接影响呈献给用户的页面。记住,Django 应用表现层的逻辑应该封装在模板中。 ### 带参数的 URL 映射 下面看看 category_name_slug 参数的值是如何传给 show_category() 视图函数的。打开 Rango 应 用的 urls.py 文件,把 urlpatterns 改成下面这样: ``` urlpatterns = [ url(r'^$', views.index, name='index'), url(r'^about$', views.about, name='about'), url(r'^category/(?P
[\w\-]+)/$', views.show_category, name='show_category'), ] ``` 我们添加了一个相当复杂的 URL 模式,当 URL 匹配` r'^category/(?P
[\w\-]+)/$' `时调用 views.show_category() 函数。 这里有两点要注意。首先,URL 模式中有个参数,即`
`,在视图中可以访 问。声明带参数的 URL 时,要确保对应的视图中有那个参数。其次,正则表达式 `[\w\-]+` 匹配连 续的数字字母(即 a-z、A-Z 或 0-9,正则表达式中的 `\w`)和连字符(正则表达式中的 `\-`),而且 可以匹配任意个(正则表达式中的 `[ ]+`)。 新增的 URL 模型匹配 rango/category/ 和末尾的 / 之间的数字字母和连字符序列。匹配的序列存 储在参数 category_name_slug 中,传给 views.show_category() 函数。例如,对 rango/category/ python-books/ 这个 URL 来说,category_name_slug 参数的值是 python-books。然而,如果 URL 是 rango/category/££££-$/,那么 rango/category/ 和末尾的 / 之间的字符与正则表达式不匹配,此 时将得到“404 not found”错误,因为没有与之匹配的 URL 模式。 Django 应用中的视图函数必须至少有一个参数。这个参数通常命名为 request,通过它获取与 HTTP 请求有关的信息。如果 URL 中带有参数,必须为对应的视图函数声明额外的具名参数。鉴 于此,show_category() 视图才定义为 `def show_category(request, category_name_slug)`。 正则表达式看似令人恐惧,晦涩难懂,但是网上有大量资源能助你一臂之力。遇到问题时, 可以使用这个[速查表](http://cheatography.com/davechild/cheat-sheets/regular-expressions/ "速查表")。 ### 修改 index 模板 新视图可用了,但是还有一件事要做。我们要修改首页的模板,为列出的分类添加链接,指向分 类页面。更新 index.html 模板,加入指向分类页面的链接。 ``` {% load staticfiles %}
Rango
Rango says...
hey the partner!
{{ boldmessage }}
{% if categories %}
{% for category in categories %}
{{ category.name }}
{% endfor %}
{% endif %}
About
``` 这里也是使用 HTML`
`标签定义一个无序列表,里面有一系列列表项目(`
`),其中有一个 HTML 超链接(`
`)。超链接有个 href 属性,其值为` /rango/category/{{ category.slug }}`。 例如,“Python Books”分类对应的 URL 是` /rango/category/python-books/`。 ### 检验结果 访问 Rango 首页,检查一下劳动成果。你应该会看到首页最多显示 5 个分类,而且都带超链接。 点击“Django”会转到 Django 分类的页面,如图 6-2 所示。如果你看到“Official Django Tutorial”链 接,说明你做的没错。 访问不存在的分类情况如何呢?访问一个不存在的分类试试,例如直接在浏览器的地址栏中输入 /rango/category/computers/。你应该会看到一个消息,提示要查看的分类不存在。  图 6-2:Django 分类页面显示的链接。注意,鼠标悬停在第一个链接上,在 Google Chrome 浏览 器窗口的底部能看到对应的 URL。 如果想深入了解模型,可以阅读下面两篇博客文章。 ➊ Kostantin Moiseenko 写的 [Django 模型最佳实践](https://steelkiwi.com/blog/best-practices-working-django-models-python/ "Django 模型最佳实践")。这篇文章给出了很多处理模型的技 巧。 ➋ Robert Roskam 写的 [Django 模型去除重复指南](https://medium.com/@raiderrobert/make-your-django-models-dryer-4b8d0f3453dd#.ozrdt3rsm "Django 模型去除重复指南")。这篇文章介绍如何通过类的 property 方法减少访问关联模型的代码量。