Flask中的模板可以继承,把模板中重复出现的元素抽取出来放在父模板中,子模板再根据自己的需要进行改写。
通常,在父模板中定义公用的部分,通过定义block给子模板开一个口,子模板从父模板中继承并做改动,从而提高了代码的复用性。
采用原始的简单复制的方法测试:
创建视图函数Python文件:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/list/')
def course_list():
return render_template('list.html')
if __name__ == '__main__':
app.run(debug=True)
创建templates目录,在其下创建index.html和list.html,index.html如下:
首页
这是首页
- 课程列表
- 课程详情
- 视频教程
- 关于我们
list.html如下:
课程列表
这是课程列表页面
- Python
- Java
- PHP
- C++
显示:
显然,两个网页结构相同,并且在模板代码中元素基本相同,有大量的代码重复,可以进行优化。
现使用模板继承进行测试:
from flask import Flask, render_template
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
@app.route('/')
def index():
return render_template('index.html')
@app.route('/list/')
def course_list():
return render_template('list.html')
if __name__ == '__main__':
app.run(debug=True)
新建base.html即父模板:
{% block title %}
{% endblock %}
{% block header %}
这是首页
{% endblock %}
{% block content %}
- 课程列表
- 课程详情
- 视频教程
- 关于我们
{% endblock %}
{% block footer %}
{% endblock %}
base.html抽取了所有模板都需要用到的元素html、body等公共部分,并且对于所有模板都要用到的样式文件也进行了抽取,同时对于子模板需要重写的地方,比如title、head、body都定义成了block,子模板继承自base.html后可以根据自己的需要,再具体实现。
让index.html和list.html继承自base.html,index.html如下:
{% extends 'base.html' %}
{% block title %}
这是首页
{% endblock %}
{# 重写父模板中的block #}
{% block header %}
这是子模版首页
{% endblock %}
list.html如下:
{% extends 'base.html' %}
{% block title %}
这是首页
{% endblock %}
{# 重写父模板中的block #}
{% block header %}
这是子模版课程列表页面
{% endblock %}
{% block content %}
- Python
- Java
- PHP
- C++
{% endblock %}
显示:
显然,在使用模板继承后,代码的复用性明显提高、代码量显著减少;
{% extends 'base.html' %}
定义了该模板继承的父模板,然后在相应的block中重写所需内容;
在父模板中放入block中的代码子模板继承后可以重写,而父模板中未放入block中的代码子模板只能继承不能重写;
模板中的block不能重名。
在父模板和子模板中block还可以嵌套,测试如下:
base.html:
{% block title %}
{% endblock %}
{% block header %}
这是首页
{% endblock %}
{% block content %}
- 课程列表
- 课程详情
- 视频教程
- 关于我们
{% endblock %}
{% block footer %}
{% block footer-link %}
{% endblock %}
{% endblock %}
index.html:
{% extends 'base.html' %}
{% block title %}
这是首页
{% endblock %}
{# 重写父模板中的block #}
{% block header %}
这是子模版首页
{% endblock %}
{% block footer %}
{% block footerlink %}
{% endblock %}
{% endblock %}
list.html:
{% extends 'base.html' %}
{% block title %}
这是首页
{% endblock %}
{# 重写父模板中的block #}
{% block header %}
这是子模版课程列表页面
{% endblock %}
{% block content %}
- Python
- Java
- PHP
- C++
{% endblock %}
{% block footerlink %}
{% endblock %}
显示:
显然,在父模板中block定义嵌套后,在子模板中重写block时,可以将两层嵌套都引用再重写,如index.html,也可以只引用内部block嵌套并重写,可根据需要选择。
注意:
父模板中的block相当于给子模板提供了一个接口,只有父模板中已经定义了,才能在父模板中实现;
在子模板中不能扩展代码,即自己在block之外添加的代码是无效的,子模版中的所有代码都必须在父模板及其定义的block中实现,测试如下:
index.html中添加代码:
{% extends 'base.html' %}
{% block title %}
这是首页
{% endblock %}
{% block header %}
这是子模版首页
{% endblock %}
{% block footer %}
{% block footerlink %}
{% endblock %}
{% endblock %}
这是在block之外的代码
显示:
显然,在block之外的代码并没有被渲染出来。
同时,一个子模板只能继承自一个父模板,不能多继承。
如果在一个地方需要用到另一个block的内容,需要使用self.block名字
来引用,测试(index.html代码)如下:
{% extends 'base.html' %}
{% block title %}
这是首页
{% endblock %}
{% block header %}
这是子模版首页
{% endblock %}
{% block footer %}
这是本网页标题---{{ self.title() }}---
{% block footerlink %}
{% endblock %}
{% endblock %}
显示:
显然,在footer中引用了title中的内容。
调用父模板中的block时,在改写父模板的同时还想得到父模板中的代码时,可以类比Python中类的继承,使用super()
函数即调用了父模板中的代码,把父模板中的内容添加到子模板中,测试如下:
index.html代码修改如下:
{% extends 'base.html' %}
{% block title %}
这是首页
{% endblock %}
{% block header %}
{{ super() }}
这是子模版首页
{% endblock %}
{% block footer %}
{% block footerlink %}
{% endblock %}
{% endblock %}
显示:
显然,是可以继承同时重写父模板的。
静态文件包括包括CSS样式文件、JavaScript脚本文件、图片文件、字体文件等;
实际的前端开发中会使用大量的静态文件来使得网页更加生动美观,在Jinja2中加载静态文件通过url_for()
函数来实现,例如
url_for()
函数默认会在项目根目录下的static文件夹的css目录下中寻找test.css文件,如果文件存在,则会生成一个相对于项目根目录的/static/about.css
路径。
如果不想放在static目录、自己指定目录,可以在初始化Flask时,设置static_folder参数,例如app = Flask(__name__,static_folder='static_files')
,此时会到static_files目录下寻找静态文件。
加载静态文件测试:
在模板文件夹下创建books.html如下:
图书列表
这是图书列表页面
- Python
- Java
- PHP
- C++
在项目目录下创建static目录,下面创建css、js、images三个文件夹、分别用于保存CSS、JavaScript和图片文件,用于保存资源文件,css目录下创建books.css如下:
body {
background: #4eb8ff;
}
js目录下创建books.js如下:
alert(document.domain);
找一张图片如monalisa.jpg放在images目录下,进行测试:
flask模板静态文件加载
显然,此时已加载到css、js、images静态文件。
考虑模板继承,css和JavaScript一般在父模板中直接在头部加入,而图片在子模板的对应位置的block中加入,如下:
base.html:
{% block title %}
{% endblock %}
{% block header %}
这是首页
{% endblock %}
{% block content %}
- 课程列表
- 课程详情
- 视频教程
- 关于我们
{% endblock %}
{% block footer %}
{% block footerlink %}
{% endblock %}
{% endblock %}
index.html:
{% extends 'base.html' %}
{% block title %}
这是首页
{% endblock %}
{% block header %}
这是子模版首页
{% endblock %}
{% block footer %}
{% block footerlink %}
{% endblock %}
{% endblock %}
list.html:
{% extends 'base.html' %}
{% block title %}
这是首页
{% endblock %}
{# 重写父模板中的block #}
{% block header %}
这是子模版课程列表页面
{% endblock %}
{% block content %}
- Python
- Java
- PHP
- C++
{% endblock %}
{% block footerlink %}
{% endblock %}
显示:
显然,在父模板中定义的CSS样式和JavaScript在子模板中全部继承并渲染。
目标是做一个豆瓣电影的电影、电视剧评分页面图,预期效果如下:
本项目用到的素材可点击https://download.csdn.net/download/CUFEECR/12327362下载测试。
先初步测试:
建立视图函数Python文件:
from flask import Flask, render_template
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
再创建templates文件夹并在其下创建index.html:
豆瓣
豆瓣影视评分
电影
更多
绅士们
{% set rating = 8.4 %}
{% set rating_light = ((rating|int)/2)|int %}
{% set rating_half = (rating|int)%2 %}
{% set rating_gray = 5 - rating_light - rating_half %}
{% for foo in range(0, rating_light) %}
{% endfor %}
{% for foo in range(0, rating_half) %}
{% endfor %}
{% for foo in range(0, rating_gray) %}
{% endfor %}
{{ rating }}
饥饿站台
{% set rating = 7.8 %}
{% set rating_light = ((rating|int)/2)|int %}
{% set rating_half = (rating|int)%2 %}
{% set rating_gray = 5 - rating_light - rating_half %}
{% for foo in range(0, rating_light) %}
{% endfor %}
{% for foo in range(0, rating_half) %}
{% endfor %}
{% for foo in range(0, rating_gray) %}
{% endfor %}
{{ rating }}
消失吧,群青
{% set rating = 6.7 %}
{% set rating_light = ((rating|int)/2)|int %}
{% set rating_half = (rating|int)%2 %}
{% set rating_gray = 5 - rating_light - rating_half %}
{% for foo in range(0, rating_light) %}
{% endfor %}
{% for foo in range(0, rating_half) %}
{% endfor %}
{% for foo in range(0, rating_gray) %}
{% endfor %}
{{ rating }}
创建static目录,在其下创建css目录和images目录,再在css目录下创建base.css和item.css,base.css如下:
*{
margin: 0;
padding: 0;
list-style: none;
text-decoration: none;
}
.container{
width: 375px;
height: 600px;
background: pink;
}
.search-group{
padding: 14px 8px;
background: #41be57;
}
.search-group .search-input{
background: #fff;
display: block;
width: 100%;
height: 30px;
border-radius: 5px;
outline: none;
border: none;
}
item.css如下:
.item-list-group .item-list-top{
overflow: hidden;
padding: 10px;
}
.item-list-top .module-title{
float: left;
}
.item-list-top .more-btn{
float: right;
}
.list-group{
overflow: hidden;
padding: 0 10px;
}
.item-group{
float: left;
margin-right: 10px;
}
.item-group .thumbnail{
display: block;
width: 100px;
height: 150px;
}
.item-group .item-title{
font-size: 12px;
text-align: center;
}
.item-group .item-rating{
font-size: 12px;
text-align: center;
}
.item-rating img{
width: 10px;
height: 10px;
}
在images目录下放入所需图片文件,测试如下:
显然,达到了预期效果,但是在index.hml代码中重复代码很多,复用性较低,可以进行优化。
现使用宏将显示一部电影信息的HTML代码抽象出来,再传入所需参数,如下:
豆瓣
{% macro itemGroup(thumbnail, title, rating) %}
{{ title }}
{% set rating_light = ((rating|int)/2)|int %}
{% set rating_half = (rating|int)%2 %}
{% set rating_gray = 5 - rating_light - rating_half %}
{% for foo in range(0, rating_light) %}
{% endfor %}
{% for foo in range(0, rating_half) %}
{% endfor %}
{% for foo in range(0, rating_gray) %}
{% endfor %}
{{ rating }}
{% endmacro %}
豆瓣影视评分
电影
更多
{{ itemGroup('static/images/绅士们.jpg', '绅士们', 8.4) }}
{{ itemGroup('static/images/饥饿站台.jpg', '饥饿站台', 7.8) }}
{{ itemGroup('static/images/消失吧,群青.jpg', '消失吧,群青', 6.7) }}
测试效果与之前相同,此时显然代码缩短了很多,提高了代码的复用性。
但是上面是直接将数据写入HTML中的,真实环境中一般是通过请求数据库获得的,现在模板函数代码中模拟数据的查询与渲染:
from flask import Flask, render_template
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
# 电影
movies = [
{
'title': u'绅士们',
'thumbnail': 'static/images/绅士们.jpg',
'rating': u'8.4',
},
{
'title': u'饥饿站台',
'thumbnail': u'static/images/饥饿站台.jpg',
'rating': u'7.8',
},
{
'title': u'消失吧,群青',
'thumbnail': u'static/images/消失吧,群青.jpg',
'rating': u'6.7',
},
{
'title': u'再见,妈妈',
'thumbnail': u'static/images/再见,妈妈.jpg',
'rating': u'8.1',
},
{
'title': u'狩猎',
'thumbnail': u'static/images/狩猎.jpg',
'rating': u'7.3',
},
{
'title': u'隐形人',
'thumbnail': u'static/images/隐形人.jpg',
'rating': u'7.3',
}
]
# 电视剧
tvs = [
{
'title': u'清平乐',
'thumbnail': 'static/images/清平乐.jpg',
'rating': u'8.1',
},
{
'title': u'民国神探',
'thumbnail': u'static/images/民国神探.jpg',
'rating': u'7.1',
},
{
'title': u'叹息桥',
'thumbnail': u'static/images/叹息桥.jpg',
'rating': u'8.8',
},
{
'title': u'重生',
'thumbnail': u'static/images/重生.jpg',
'rating': u'6.6',
},
{
'title': u'陈情令',
'thumbnail': u'static/images/陈情令.jpg',
'rating': u'7.7',
},
{
'title': u'锦衣之下',
'thumbnail': u'static/images/锦衣之下.jpg',
'rating': u'7.6',
}
]
@app.route('/')
def index():
context = {
'movies': movies,
'tvs': tvs
}
return render_template('index.html', **context)
if __name__ == '__main__':
app.run(debug=True)
模板index.html代码:
豆瓣
{% macro itemGroup(thumbnail, title, rating) %}
{{ title }}
{% set rating_light = ((rating|int)/2)|int %}
{% set rating_half = (rating|int)%2 %}
{% set rating_gray = 5 - rating_light - rating_half %}
{% for foo in range(0, rating_light) %}
{% endfor %}
{% for foo in range(0, rating_half) %}
{% endfor %}
{% for foo in range(0, rating_gray) %}
{% endfor %}
{{ rating }}
{% endmacro %}
豆瓣影视评分
显示:
显然,已经达到了初步效果。
可以看到,在展示电影和电视剧的模板代码部分,还是有部分重复的代码,因此可以再定义一个宏,如下:
豆瓣
{% macro itemGroup(thumbnail, title, rating) %}
{{ title }}
{% set rating_light = ((rating|int)/2)|int %}
{% set rating_half = (rating|int)%2 %}
{% set rating_gray = 5 - rating_light - rating_half %}
{% for foo in range(0, rating_light) %}
{% endfor %}
{% for foo in range(0, rating_half) %}
{% endfor %}
{% for foo in range(0, rating_gray) %}
{% endfor %}
{{ rating }}
{% endmacro %}
{% macro listGroup(type, items) %}
{{ type }}
更多
{% for item in items[:3] %}
{{ itemGroup(item.thumbnail, item.title, item.rating) }}
{% endfor %}
{% endmacro %}
豆瓣影视评分
{{ listGroup('电影', movies) }}
{{ listGroup('电视剧', tvs) }}
效果与前者相同。
现进行进一步扩展,将公共部分代码抽象到父模板中,重复代码定义到宏中,进而来实现电影和电视剧详情列表。
视图函数代码如下:
from flask import Flask, render_template, request
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
# 电影
movies = [
{
'title': u'绅士们',
'thumbnail': '../static/images/绅士们.jpg',
'rating': u'8.4',
},
{
'title': u'饥饿站台',
'thumbnail': u'../static/images/饥饿站台.jpg',
'rating': u'7.8',
},
{
'title': u'消失吧,群青',
'thumbnail': u'../static/images/消失吧,群青.jpg',
'rating': u'6.7',
},
{
'title': u'再见,妈妈',
'thumbnail': u'../static/images/再见,妈妈.jpg',
'rating': u'8.1',
},
{
'title': u'狩猎',
'thumbnail': u'../static/images/狩猎.jpg',
'rating': u'7.3',
},
{
'title': u'隐形人',
'thumbnail': u'../static/images/隐形人.jpg',
'rating': u'7.3',
}
]
# 电视剧
tvs = [
{
'title': u'清平乐',
'thumbnail': '../static/images/清平乐.jpg',
'rating': u'8.1',
},
{
'title': u'民国神探',
'thumbnail': u'../static/images/民国神探.jpg',
'rating': u'7.1',
},
{
'title': u'叹息桥',
'thumbnail': u'../static/images/叹息桥.jpg',
'rating': u'8.8',
},
{
'title': u'重生',
'thumbnail': u'../static/images/重生.jpg',
'rating': u'6.6',
},
{
'title': u'陈情令',
'thumbnail': u'../static/images/陈情令.jpg',
'rating': u'7.7',
},
{
'title': u'锦衣之下',
'thumbnail': u'../static/images/锦衣之下.jpg',
'rating': u'7.6',
}
]
@app.route('/')
def index():
context = {
'movies': movies,
'tvs': tvs
}
return render_template('index.html', **context)
@app.route('/list/')
def show_list():
category = int(request.args.get('category'))
if category == 1:
items = movies
mtype = '电影'
else:
items = tvs
mtype = '电视剧'
return render_template('list.html', items = items, mtype = mtype)
if __name__ == '__main__':
app.run(debug=True)
创建base.html如下:
{% from 'macros.html' import itemGroup, listGroup %}
{% block title %}
{% endblock %}
{% block body_title %}
豆瓣影视评分
{% endblock %}
{% block content %}
{% endblock %}
创建macro.html保存宏:
{% macro itemGroup(thumbnail, title, rating) %}
{{ title }}
{% set rating_light = ((rating|int)/2)|int %}
{% set rating_half = (rating|int)%2 %}
{% set rating_gray = 5 - rating_light - rating_half %}
{% for foo in range(0, rating_light) %}
{% endfor %}
{% for foo in range(0, rating_half) %}
{% endfor %}
{% for foo in range(0, rating_gray) %}
{% endfor %}
{{ rating }}
{% endmacro %}
{% macro listGroup(type, items, category=category) %}
{{ type }}
更多
{% for item in items[:3] %}
{{ itemGroup(item.thumbnail, item.title, item.rating) }}
{% endfor %}
{% endmacro %}
index.html代码为:
{% extends 'base.html' %}
{% block title %}
首页
{% endblock %}
{% block body_title %}
豆瓣影视评分
{% endblock %}
{% block content %}
{{ listGroup('电影', movies, 1) }}
{{ listGroup('电视剧', tvs, 2) }}
{% endblock %}
创建list.html代码为:
{% extends 'base.html' %}
{% block title %}
{{ mtype }}
{% endblock %}
{% block body_title %}
豆瓣{{ mtype }}评分
{% endblock %}
{% block content %}
{% for item in items %}
{{ itemGroup(item.thumbnail, item.title, item.rating) }}
{% endfor %}
{% endblock %}
显示:
显然,已经达到了基本的预期效果。
在实现的过程中有关键的几点需要注意:
在定义宏时要注意代码重复的范围,并设置恰当的参数; 在设计模板继承时,要考虑到各个页面公共的代码,同时要注意在父模板中引用宏,在子模版中直接继承父模板,而不需要再引用宏; 在定义宏时,在listGroup()
部分需要传入参数来判断在主页点击的是电影还是电视剧,从而返回相应的数据,这主要是使用url_for()
函数进行映射,因为在show_list()
函数中不存在category参数,所以在请求时会将参数体现到URL中,再在视图函数中通过request.args.get()
方法来得到选择的category来传递数据。