这一章咱们会继续完善模板,学习几个很是实用的模板编写技巧,为下一章实现建立、编辑电影条目打下基础。css
为了引出相关知识点,咱们首先要为 Watchlist 编写一个错误页面。目前的程序中,若是你访问一个不存在的 URL,好比 /hello,Flask 会自动返回一个 404 错误响应。默认的错误页面很是简陋,以下图所示:html
在 Flask 程序中自定义错误页面很是简单,咱们先编写一个 404 错误页面模板,以下所示:git
templates/404.html:404 错误页面模板github
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ user.name }}'s Watchlist</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">
</head>
<body>
<h2>
<img alt="Avatar" class="avatar" src="{{ url_for('static', filename='images/avatar.png') }}">
{{ user.name }}'s Watchlist
</h2>
<ul class="movie-list">
<li>
Page Not Found - 404
<span class="float-right">
<a href="{{ url_for('index') }}">Go Back</a>
</span>
</li>
</ul>
<footer>
<small>© 2018 <a href="http://helloflask.com/tutorial">HelloFlask</a></small>
</footer>
</body>
</html>复制代码
接着使用 app.errorhandler()
装饰器注册一个错误处理函数,它的做用和视图函数相似,当 404 错误发生时,这个函数会被触发,返回值会做为响应主体返回给客户端:数据库
app.py:404 错误处理函数flask
@app.errorhandler(404) # 传入要处理的错误代码
def page_not_found(e): # 接受异常对象做为参数
user = User.query.first()
return render_template('404.html', user=user), 404 # 返回模板和状态码复制代码
提示 和咱们前面编写的视图函数相比,这个函数返回了状态码做为第二个参数,普通的视图函数之因此不用写出状态码,是由于默认会使用 200 状态码,表示成功。app
这个视图返回渲染好的错误模板,由于模板中使用了 user 变量,这里也要一并传入。如今访问一个不存在的 URL,会显示咱们自定义的错误页面:函数
编写完这部分代码后,你会发现两个问题:学习
<head>
标签的内容,页首的标题,页脚信息等。这种重复不只带来没必要要的工做量,并且会让修改变得更加麻烦。举例来讲,若是页脚信息须要更新,那么每一个页面都要一一进行修改。显而易见,这两个问题有更优雅的处理方法,下面咱们来一一了解。网站
对于多个模板内都须要使用的变量,咱们能够使用 app.context_processor
装饰器注册一个模板上下文处理函数,以下所示:
app.py:模板上下文处理函数
@app.context_processor
def inject_user(): # 函数名能够随意修改
user = User.query.first()
return dict(user=user) # 须要返回字典,等同于return {'user': user}复制代码
这个函数返回的变量(以字典键值对的形式)将会统一注入到每个模板的上下文环境中,所以能够直接在模板中使用。
如今咱们能够删除 404 错误处理函数和主页视图函数中的 user
变量定义,并删除在 render_template()
函数里传入的关键字参数:
@app.context_processor
def inject_user():
user = User.query.first()
return dict(user=user)
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.route('/')
def index():
movies = Movie.query.all()
return render_template('index.html', movies=movies)复制代码
一样的,后面咱们建立的任意一个模板,均可以在模板中直接使用 user
变量。
对于模板内容重复的问题,Jinja2 提供了模板继承的支持。这个机制和 Python 类继承很是相似:咱们能够定义一个父模板,通常会称之为基模板(base template)。基模板中包含完整的 HTML 结构和导航栏、页首、页脚都通用部分。在子模板里,咱们能够使用 extends
标签来声明继承自某个基模板。
基模板中须要在实际的子模板中追加或重写的部分则能够定义成块(block)。块使用 block
标签建立, {% block 块名称 %}
做为开始标记,{% endblock %}
或 {% endblock 块名称 %}
做为结束标记。经过在子模板里定义一个一样名称的块,你能够向基模板的对应块位置追加或重写内容。
下面是新编写的基模板 base.html:
templates/base.html:基模板
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ user.name }}'s Watchlist</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">
{% endblock %}
</head>
<body>
<h2>
<img alt="Avatar" class="avatar" src="{{ url_for('static', filename='images/avatar.png') }}">
{{ user.name }}'s Watchlist
</h2>
<nav>
<ul>
<li><a href="{{ url_for('index') }}">Home</a></li>
</ul>
</nav>
{% block content %}{% endblock %}
<footer>
<small>© 2018 <a href="http://helloflask.com/tutorial">HelloFlask</a></small>
</footer>
</body>
</html>复制代码
在基模板里,咱们添加了两个块,一个是包含 <head></head>
内容的 head
块,另外一个是用来在子模板中插入页面主体内容的 content
块。在复杂的项目里,你能够定义更多的块,方便在子模板中对基模板的各个部分插入内容。另外,块的名字没有特定要求,你能够自由修改。
在编写子模板以前,咱们先来看一下基模板中的两处新变化。
第一处,咱们添加了一个新的 <meta>
元素,这个元素会设置页面的视口,让页面根据设备的宽度来自动缩放页面,让移动设备拥有更好的浏览体验:
<meta name="viewport" content="width=device-width, initial-scale=1.0">复制代码
第二处,新的页面添加了一个导航栏:
<nav>
<ul>
<li><a href="{{ url_for('index') }}">Home</a></li>
</ul>
</nav>复制代码
导航栏对应的 CSS 代码以下所示:
nav ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}
nav li {
float: left;
}
nav li a {
display: block;
color: white;
text-align: center;
padding: 8px 12px;
text-decoration: none;
}
nav li a:hover {
background-color: #111;
}复制代码
建立了基模板后,子模板的编写会变得很是简单。下面是新的主页模板(index.html):
templates/index.html:继承基模板的主页模板
{% extends 'base.html' %}
{% block content %}
<p>{{ movies|length }} Titles</p>
<ul class="movie-list">
{% for movie in movies %}
<li>{{ movie.title }} - {{ movie.year }}
<span class="float-right">
<a class="imdb" href="https://www.imdb.com/find?q={{ movie.title }}" target="_blank" title="Find this movie on IMDb">IMDb</a>
</span>
</li>
{% endfor %}
</ul>
<img alt="Walking Totoro" class="totoro" src="{{ url_for('static', filename='images/totoro.gif') }}" title="to~to~ro~">
{% endblock %}复制代码
第一行使用 extends
标签声明扩展自模板 base.html,能够理解成“这个模板继承自 base.html“。接着咱们定义了 content
块,这里的内容会插入到基模板中 content
块的位置。
提示 默认的块重写行为是覆盖,若是你想向父块里追加内容,能够在子块中使用 super()
声明,即 {{ super() }}
。
404 错误页面的模板相似,以下所示:
templates/index.html:继承基模板的 404 错误页面模板
{% extends 'base.html' %}
{% block content %}
<ul class="movie-list">
<li>
Page Not Found - 404
<span class="float-right">
<a href="{{ url_for('index') }}">Go Back</a>
</span>
</li>
</ul>
{% endblock %}复制代码
在主页模板里,咱们还为每个电影条目右侧添加了一个 IMDb 连接:
<span class="float-right">
<a class="imdb" href="https://www.imdb.com/find?q={{ movie.title }}" target="_blank" title="Find this movie on IMDb">IMDb</a>
</span>复制代码
这个连接的 href
属性的值为 IMDb 搜索页面的 URL,搜索关键词经过查询参数 q
传入,这里传入了电影的标题。
对应的 CSS 定义以下所示:
.float-right {
float: right;
}
.imdb {
font-size: 12px;
font-weight: bold;
color: black;
text-decoration: none;
background: #F5C518;
border-radius: 5px;
padding: 3px 5px;
}复制代码
如今,咱们的程序主页以下所示:
本章咱们主要学习了 Jinja2 的模板继承机制,去掉了大量的重复代码,这让后续的模板编写工做变得更加轻松。结束前,让咱们提交代码:
$ git add .
$ git commit -m "Add base template and error template"
$ git push复制代码
提示 你能够在 GitHub 上查看本书示例程序的对应 commit:cfc08fa。
href
属性即 https://movie.douban.com/subject_search?search_text={{ movie.title }}
。