本章介绍一些样式基础知识,包括如何集成 Bootstrap 这个 HTML/CSS 框架,还要学习 Django 处理静态文件的方式,以及如何测试静态文件。
7.1 如何在功能测试中测试布局和样式
执行命令 manage.py runserver 启动开发服务器时,可能会看到一个数据库错误:“table lists_item has no column named list_id"(lists_item 表中没有名为 list_id 的列)。此时,需要执行 manage.py migrate 命令,更新本地数据库,让 models.py 中的改动生效。
Python 世界的选丑竞赛arrow-up-right
接下来要实现如下效果:
一个精美且很大的输入框,用于新建清单,或者把待办事项添加到现有的清单中
把这个输入框放在一个更大的居中框体中,吸引用户的注意力
大多数人会说,不要测试外观。他们是对的,这就像是测量常量一样毫无意义。但可以测试装饰外观的方式,确信实现了预期的效果即可。例如,使用层叠样式表(Cascading Style Sheet,CSS)编写样式,样式表以静态文件的形式加载,而静态文件配置起来有点复杂(把静态文件移到主机上,配置起来更麻烦),因此只需做某种”冒烟测试“(smoke test),确保加载了 CSS 即可。无须测试字体、颜色以及像素级位置,而是通过简单的测试,确认重要的输入框在每个页面中都按照预期的方式对齐,由此推断页面中的其他样式或许也都正确应用了。
先在功能测试中编写一个新测试方法:
Copy class NewVisitorTest ( LiveServerTestCase ):
[ ... ]
def test_layout_and_styling ( self ):
# Y 访问首页
self . browser . get ( self . live_server_url )
self . browser . set_window_size ( 1024 , 768 )
# 她看到输入框完美地居中显示
input_box = self . browser . find_element_by_id ( " id_new_item " )
self . assertAlmostEqual (
input_box . location [ " x " ] + input_box . size [ " width " ] / 2 ,
512 ,
delta = 5
) 这里先把浏览器的窗口设为固定大小,然后找到输入框元素,获取它的大小和位置,再做些数学计算,检查输入框是否位于网页的中线上。assertAlmostEqual 的作用是帮助处理舍入误差,这里指定计算结果在正负五像素范围内可接受。
运行功能测试,会得到预料之中的失败。不过,这种功能测试很容易出错,所以要用一种快捷方法确认输入框居中时功能测试能通过。一旦确认功能测试编写正确之后,就把这些代码删掉,下面修改 home.html 文件:
修改之后测试能通过,说明功能测试起作用了。下面扩展这个测试,确保新建清单后输入框仍然居中对齐显示:
这会导致测试再次失败。
发现自己没有测试失败,寻找原因,发现是运行了另外一个路径下的 manage.py。
还有一个,自己手动打开浏览器进行测试的时候发现居然没法正常使用(已经通过的功能测试),后来发现得重建数据库,命令如下:
OK,正常了。
现在只提交功能测试:
7.2 使用 CSS 框架美化网站
使用 CSS 框架解决问题,框架有很多,不过出现最早且最受欢迎的是 Twitter 开发的 Bootstrap。就是用这个框架,Bootstraparrow-up-right 获取。
下载 Bootstrap,把它放在 lists 应用中一个新文件夹 static 里:
dist 文件夹中的内容是未经定制的原始 Bootstrap 框架,现在使用这些内容,但在真正的网站中不能这么做,因为用户能立即看出你使用 Bootstrap 时没有定制。你应该学习如何使用 LESS,至少把字体改了。Bootstrap 文档中有定制的详细说明,或者可以看这篇指南arrow-up-right 。
最终得到的 lists 文件夹结构如下:
在 Bootstrap 文档中的 "Getting Started" 部分arrow-up-right ,可以发现 Bootstrap 要求 HTML 模板中包含如下代码:
我们已经有两个 HTML 模板了,所以不想在每个模板中都添加大量的样板代码。这似乎是运用”不要自我重复“原则的好时机,可以把通用代码放在一起。Django 使用的模板语言可以轻易做到这一点,这种功能叫做”模板继承“。
7.3 Django 模板继承
看一下 home.html 和 list.html 之间的差异,使用 diff 命令。
这两个模板头部显示的文本不一样,而且表单的提交地址也不同。除此之外,list.html 还多了一个 <table> 元素。
现在弄清了两个模板之间共通以及有差异的地方,然后就可以让它们继承同一个父级模板了。先复制 home.html:
把通用的样板代码写入这个基模板中,而且标记出各个”块“,块中的内容留给子模板定制:
基模板定义了多个叫做”块“的区域,其他模板可以在这些地方插入自己所需的内容。在实际操作中看一下这种机制的用法。修改 home.html,让它继承 base.html:
可以看出,很多 HTML 样板代码都不见了,现在只需集中精力编写想定制的部分。然后对 list.html 做同样的修改:
对模板来说,这是一次重构。再次运行功能测试,确保没有破坏现有功能。
果然,结果和修改前一样。这次改动值得做一次提交:
7.4 集成 Bootstrap
现在集成 Bootstrap 所需的样板代码更容易了,不过暂时不需要 JavaScript,只加入 CSS 即可。
最后,使用 Bootstrap 中某些真正强大的功能。使用之前你得先阅读 Bootstrap 的文档。可以使用栅格系统和 text-center 类实现所需的效果。
如果你从未看过 Bootstarp 文档arrow-up-right ,花点时间浏览一下吧。文档中介绍了很多有用的工具,可以运用到你的网站中。
做了修改之后,功能测试还是通不过。
7.5 Django 中的静态文件
Django 处理静态文件时需要知道两件事(其实所有 Web 服务器都是如此)
收到指向某个 URL 的请求时,如何区分请求的是静态文件,还是需要经过视图函数处理,生成 HTML
其实,静态文件就是把 URL 映射到硬盘中文件上。
DJango 允许我们定义一个 URL 前缀,任何以这个前缀开头的 URL 都被视作针对静态文件的请求。默认的前缀是 /static/,在 settings.py 中定义:
后面在这一部分添加的设置都和第二个问题有关,即在硬盘中找到真正的静态文件。
既然用的是 Django 开发服务器 manage.py runserver,就可以把寻找静态文件的任务交给 Django 完成。Django 会在各应用中每个名为 static 的子文件夹里寻找静态文件。
为什么现在不起作用呢?因为没在 URL 中加入前缀 /static/。再看一下 base.html 中链接 CSS 的元素:
若想让这行代码起作用,要把它改成:
现在,开发服务器收到这个请求时就知道请求的是静态文件,因为 URL 以 /static/ 开头。然后,服务器在每个应用中名为 static 的子文件夹里搜寻名为 bootstrap/css/bootstrap.min.css 的文件。
换用 StaticLiveServerCase
不过,功能测试还是无法通过。
这是因为,虽然 runserver 启动的开发服务器能自动找到静态文件,但 LiveServerTestCase 找不到。不过,Django 为开发者提供了一个更神奇的测试类,叫 StaticLiveServerCasearrow-up-right 。
下面换用这个测试类:
现在测试能找到 CSS 了,因此测试也能通过了。
Windows 用户在这里可能会看到一些错误消息,这在 tearDown 方法的 self.browser.quit() 之前加上 self.browser.refresh() 就能去掉这些错误。
自己的测试并没有通过,不知道是 mac 的问题还是浏览器的问题。
自己最终作弊解决了,更改 base.html 中
强行把它偏移到中间了。
7.6 使用 Bootstrap 中的组件改进网站外观
Bootstrap 中有个类叫 jumbotron,用于特别突出地显示页面中的元素。使用这个类放大显示页面的主头部和输入表单:
Bootstrap 为表单控件提供了一个类,可以把输入框变大:
加上 Bootstrap 提供的 table 类可以改进显示效果:
7.7 使用自己编写的 CSS
现在想让输入表单离标题文字远一点。Bootstrap 没有提供现成的解决方案,那么就自己实现吧,引入一个自己编写的 CSS 文件:
新建文件 /lists/static/base.css,写入自己编写的 CSS 新规则。使用输入框的 id 定位元素,然后为其编写样式:
如果想进一步定制 Bootstrap,需要编译 LESS。LESS、SCSS 等其他伪 CSS 类工具,对普通的 CSS 做了很大改进,即便不适用 Bootstrap 也很有用。网上有很多 LESS 的参考资料,比如这个arrow-up-right 。最后再运行一次功能测试,看一切是否仍能正常运行,接着进行提交。
7.8 补遗:collectstatic 命令和其他静态目录
Django 的开发服务器会自动在应用的文件夹中查找并呈现静态文件。在开发过程中这种功能不错,但在真正的 Web 服务器中,并不需要让 Django 伺服静态内容,因为使用 Python 伺服原始文件速度慢而且效率低,Apache、Nginx 等 Web 服务器能很好地完成这项任务。或许还会把所有静态文件都上传到 CDN(Content Distribution Network,内容分发网络),不放在自己的主机中。
鉴于此,要把分散在各个应用文件夹中的所有静态文件集中起来,复制一份放在一个位置,为部署做好准备。collectstatic 命令就是用来完成这项操作的。
静态文件集中放置的位置由 settings.py 中的 STATIC_ROOT 定义。现在把 STATIC_ROOT 的值设为仓库之外的一个文件夹——使用和主源码文件夹同级的一个文件夹:
关键在于,静态文件所在的文件夹不能放在仓库中——不想把这个文件夹纳入版本控制,因为其中的文件和 lists/static 中的一样。
下面是指定这个文件夹位置的一种优雅方式,路径相对 settings.py 文件而言:
在设置文件的顶部,你会看到定义了 BASE_DIR 变量。这个变量提供了很大的帮助。下面执行 collectstatic 命令试试:
此时如果查看 ../static,会看到所有的 CSS 文件。
collectstatic 命令还收集了管理后台的所有 CSS 文件。管理后台是 Django 的强大功能之一,现在暂且禁用:
然后再执行 collectstatic:
总之,现在知道了怎么把所有静态文件都聚集到一个文件夹中,这样 Web 服务器就能轻易找到静态文件。
现在,先提交 settings.py 中的改动:
以下话题可以进一步去研究:
使用 { % static % } 模板标签,这样做更符合 DRY 原则,也不用硬编码 URL
总结:如何测试设计和布局
不应该为设计和布局编写测试。因为这太像是测试常量,所以写出的测试不太牢靠。
可以编写一些简单的“冒烟测试”,确认静态文件和 CSS 起作用即可。
但是,如果某部分样式需要很多客户端 JavaScript 代码才能使用,就必须为此编写一些测试。
所以要记住,这是一个危险地带。要试着编写最简的测试,确信设计和布局能起作用即可,不必测试具体的实现。我们的目标是能自由修改设计和布局,且无需时不时地调整测试。