一、Flask-SQLAlchemy
1、ORM 框架
Web开发中,一个重要的组成部分便是数据库了。Web程序中最常用的莫过于关系型数据库了,也称SQL数据库。另外,文档数据库(如 mongodb)、键值对数据库(如redis)近几年也逐渐在 web开发中流行起来,我们习惯把这两种数据库称为NoSQL数据库。
大多数的关系型数据库引擎(比如MySQL、Postgres和SQLite)都有对应的Python包。在这里,我们不直接使用这些数据库引擎提供的 Python包,而是使用对象关系映射(Object-Relational Mapper,ORM)框架,它将低层的数据库操作指令抽象成高层的面向对象操作。也就是说,如果我们直接使用数据库引擎,我们就要写SQL操作语句,但是,如果我们使用了ORM框架,我们对诸如表、文档此类的数据库实体就可以简化成对Python对象的操作。Python中最广泛使用的ORM框架是SQLAlchemy,它是一个很强大的关系型数据库框架,不仅支持高层的ORM,也支持使用低层的SQL操作,另外,它也支持多种数据库引擎,如MySQL、Postgres和SQLite等。
2、Flask-SQLAlchemy
在Flask中,为了简化配置和操作,我们使用的ORM框架是Flask-SQLAlchemy,这个Flask扩展封装了SQLAlchemy框架。在Flask-SQLAlchemy中,数据库使用URL指定,下表列出了常见的数据库引擎和对应的 URL。
3、安装
首先,我们使用pip安装Flask-SQLAlchemy,安装在你所需要的环境中,本作者使用的是虚拟环境,虚拟环境能比较好的解决依赖问题,
pip install flask-sqlalchemy
4、应用
1)链接数据库
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+pymysql://root:yutao@127.0.0.1/TodoProject"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
# app.config['SECRET_KEY'] = 'westos'
app.config['SECRET_KEY'] = os.urandom(24)
Bootstrap(app)
# 实例化db对象
db = SQLAlchemy(app)
这里有几点需要注意的是:
1、app应用配置项SQLALCHEMY_DATABASE_URI指定了SQLAlchemy所要操作的数据库,这里我们使用的是mysql数据库,引号里是你需要链接的数据库://用户名和密码@本机localhost或远程链接某个数据库,/后面是你要链接数据库的名称;这里特别说一点就是数据库的编码格式问题,当你在创建数据库时可以使用下面这个命令来解决你的编码问题;
create database 数据库名称 default charset utf8;
'SQLALCHEMY_DATABASE_URI' =====》链接 数据库;
'SQLALCHEMY_TRACK_MODIFICATIONS' ======》Ture
'SECRET_KEY' =====》加密
2、db 对象是 SQLAlchemy 类的实例,表示程序使用的数据库。
3、我们定义的User模型必须继承自db.Model,这里的模型其实就对应着数据库中的表。其中,类变量 __tablename__ 定义了在数据库中使用的表名,如果该变量没有被定义,Flask-SQLAlchemy会使用一个默认名字。
2)建表
在这里我们的建表有了突破性的进展,前面我们一直说mysql是一个关系型数据库,但是没有体现出“关系”二字,在这里我们就要建立有关系的数据表;
我们设想这样的一个场景:我们在公司的内部系统上每一天都会有任务发布,有任务本身和它相关的执行部门,他们两个关系在任务产生的时候就被绑定在了一起,这样也就完成了我们所说的关系,那么我们在数据库中怎样建立数据表之间的关系呢?
外键约束是在两张表中建立的。两张表存在父子关系,即:子表中的某个字段的取值有父表决定的。
结论:在一对多关系中额,外键建立在多的一方
例如:简单的例子在很多的电影网站上都有会员,超级会员,普通会员这三种会员身份,我们需要对普通用户的身份进行关联
# 创建user表
class Users(db.Model):
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
# unique: 指定该列信息是唯一的;
name = db.Column(db.String(50), unique=True)
passwd = db.Column(db.String(50))
# default是默认值l
add_time = db.Column(db.DateTime, default=datetime.now())
role_id = db.Column(db.Integer, db.ForeignKey('role.id')) #和哪张表的哪个表头进行关联
# 创建用户角色(超级会员, 会员, 普通会员)
class Role(db.Model):
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.String(50), unique=True)
# 'Users代表的是Role表关联的数据库表'
# backref=“role”: 反向引用;让Users中有role属性, user.role返回的是role对象。
users = db.relationship('Users', backref='role') #上表和这里的进行相关联
#
# def __repr__(self):
# """如果查询数据表内容, 需要特色化设置时, 使用__repr__"""
# return "<Role %s>" %(self.name)
# 1. 创建表
# db.create_all()
下面的是我们更为复杂的例子:
class Todo(db.Model):
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
# unique: 指定该列信息是唯一的;
name = db.Column(db.String(50))
# default是默认值l
add_time = db.Column(db.DateTime, default=datetime.now())
status = db.Column(db.Boolean, default=False)
department_id = db.Column(db.Integer, db.ForeignKey('department.id'))
class Department(db.Model):
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.String(50), unique=True)
todos = db.relationship('Todo', backref='department')
users = db.relationship('User', backref='department')
class User(db.Model):
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
# unique: 指定该列信息是唯一的;
name = db.Column(db.String(50), unique=True)
# 此处为了用户帐号的安全性, 必须对密码进行加密;
pwd = db.Column(db.String(100))
email = db.Column(db.String(20), unique=True)
phone = db.Column(db.String(20), unique=True)
info = db.Column(db.Text) # 个性简介
addtime = db.Column(db.DateTime, default=datetime.now())
department_id = db.Column(db.Integer, db.ForeignKey('department.id'))
userlogs = db.relationship('UserLog', backref="user")
def check_pwd(self, pwd):
return check_password_hash(self.pwd, 'westos1')
# 用户登录日志
class UserLog(db.Model):
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
ip = db.Column(db.String(100)) # 登录的IP
addtime = db.Column(db.DateTime, default=datetime.now())
area = db.Column(db.String(100)) # 登录的城市
3)给表中添加信息
1、建表
# 1). 创建表
# db.create_all()
2、给表内添加信息
2). 初始化数据
parts = ['开发部', '运维部', '人事部']
partObj = [Department(name=part) for part in parts]
db.session.add_all(partObj)
db.session.commit()
todos = ['打扫卫生', '开发微电影管理系统', '招聘人才']
todoObj = [Todo(name=todo, department_id=random.choice([1,2,3]) ) for todo in todos]
db.session.add_all(todoObj)
db.session.commit()
3、添加用户的测试数据
u1 = User(name="westos1", pwd=generate_password_hash('westos'), department_id=1)
u2 = User(name="westos2", pwd=generate_password_hash('westos'), department_id=2)
u3 = User(name="westos3", pwd=generate_password_hash('westos'), department_id=1)
db.session.add_all([u1, u2, u3])
db.session.commit()
注意:这里我们用到了哈希加密
什么是哈希加密:HASH主要用于信息安全领域中加密算法,他把一些不同长度的信息转化成杂乱的128位的编码里,叫做HASH值. 也可以说,hash就是找到一种数据内容和数据存放地址之间的映射关系
这里我们用到的加密模块为:
from werkzeug.security import generate_password_hash
相对应的也就有解密模块:
from werkzeug.security import check_password_hash
解密的时候我们需要两个参数:分别是哈希加密后的一串代码,再其次是我们的明文密码,返回值为Ture 或 False
4)查询数据信息
# 查询所有的角色;
print(Role.query.all())
1、 如何显示会员类型与该会员类型包含的所有用户;
roles为对象。对象对应的属性我们都可以看到
roles = Role.query.all()
for role in roles:
print(role.id, role.name,role.users)
2、显示所有的用户以及用户类别
users = Users.query.all()
for user in users:
print(user.id, user.name, user.passwd, user.role)
3、根据条件查找符和条件的数据和更新数据;(filter_by(常用), filter)
# 1). 找到所有超级会员的用户;
users = Users.query.filter_by(role_id=1).all()
# 2). 更新所有超级会员的密码为0000000;
for user in users:
# print(user.passwd)
user.passwd = "000000"
print("更新用户%s的密码成功!" %(user.name))
db.session.add(user)
db.session.commit()
4、其他的查询信息显示限制条件
users = Users.query.filter_by(role_id=1).limit(10).all()
print(users, len(users))
5、排序: 根据用户创建的时间进行排序(默认情况正序, 如果要逆序desc())
users = Users.query.order_by(desc(Users.add_time)).all()
for u in users[:10]:
print(u.add_time)
6、limit可以限制查询数据的数量;
users =Users.query.order_by(desc(Users.add_time)).limit(5).all()
print(users)
7、offset: 偏移量, 可以设置查询偏移量,也就是数据的起始位置, 一般与limit结合使用;
print(Users.query.order_by(desc(Users.add_time)).limit(5).offset(2).all())
8、slice: 切片
print(Users.query.order_by(desc(Users.add_time)).slice(1,5).all())
9、分页:第一个参数为页码,第二个参数为每一页显示的内容
page_u = Users.query.paginate(1, 3)
print(page_u.items)
print(dir(page_u))
page_u = Users.query.paginate(2, 3)
print(page_u.items)
10、用户数据更新;
u = Users.query.filter_by(id=1).first()
print(u.id, u.name, u.add_time)
u.name = "西部开源1"
db.session.add(u)
db.session.commit()
u = Users.query.filter_by(id=1).first()
print(u.id, u.name, u.add_time)
11、用户信息删除;
u = Users.query.filter_by(id=1).first()
db.session.delete(u)
db.session.commit()
u = Users.query.filter_by(id=1).first()
print(u.id, u.name, u.add_time)
二、Flask-Bootstrap
Bootstrap是Twitter开源的一个CSS/HTML框架,它让Web开发变得更加迅速,简单。要想在我们的Flask应用中使用Boostrap,有两种方案可供选择:
第1种,在我们的Jinja2模板中直接引入Bootstrap层叠样式表(CSS)和JavaScript文件,比如bootstrap.min.css,bootstrap.min.js;
第2种,也是更简单的方法,就是使用一个 Flask-Bootstrap 的扩展,它简化了集成Bootstrap 的过程;
1、安装
pip install flask-bootstrap
2、使用
from werkzeug.security import check_password_hash
from models import app, Todo, db, Department, User, UserLog
from flask import render_template, redirect, url_for, request, flash, session
from functools import wraps
from urllib.request import urlopen
import json
# 通过ip获取该IP的所在城市和国家;
def get_ip_area(ip):
# 构造url地址, 使用淘宝的API接口
url = 'http://ip.taobao.com/service/getIpInfo.php?ip=%s' %(ip)
# 获取页面返回的内容()
json_data = urlopen(url).read().decode('utf-8')
# 将json格式的数据转换为字典格式;
s_data = json.loads(json_data)
country = s_data['data']['country']
if country == 'XX':
country = ''
city = s_data['data']['city']
if city == 'XX':
city = ''
return country+city
# 编写一个判断用户是否登录的装饰器
def is_login(f):
@wraps(f)
def wrapper(*args, **kwargs):
# 判断不在session(会话)中, 跳转到登录页面;
if not "user" in session:
return redirect(url_for('login'))
return f(*args, **kwargs)
return wrapper
@app.route('/list/<int:page>/')
@app.route('/list/')
@is_login
def list(page=1):
todos = Todo.query.paginate(page, per_page=5)
parts = Department.query.all()
return render_template('list.html', todos=todos, parts=parts)
@app.route('/done/<int:todo_id>/')
@is_login
def done(todo_id):
todo = Todo.query.filter_by(id=todo_id).first()
todo.status = True
try:
db.session.add(todo)
db.session.commit()
except Exception as e:
db.session.rollback()
return e
return redirect(url_for('list', page=1))
@app.route('/undone/<int:todo_id>/')
@is_login
def undone(todo_id):
todo = Todo.query.filter_by(id=todo_id).first()
todo.status = False
try:
db.session.add(todo)
db.session.commit()
except Exception as e:
db.session.rollback()
return e
return redirect(url_for('list', page=1))
@app.route('/delete/<int:todo_id>/<int:page>/')
@is_login
def delete(todo_id, page=1):
u = Todo.query.filter_by(id=todo_id).first()
db.session.delete(u)
db.session.commit()
return redirect(url_for('list', page=page))
@app.route('/add/', methods=['POST'])
@is_login
def add():
# 通过request获取 表单提交的内容;
data = request.form
name = data['todo_name']
part = data['part']
# 实例化对象
todo = Todo(name=name, department_id=part)
db.session.add(todo)
db.session.commit()
return redirect(url_for('list'))
@app.route('/login/', methods=['POST', 'GET'])
def login():
# 判断用户的HTTP请求方法
if request.method == 'POST':
# 1). 接收提交的数据;
data = request.form
name = data['name']
pwd = data['pwd']
# 2). 判断用户名是否存在, 密码是否正确?
u = User.query.filter_by(name=name).first()
if u and check_password_hash(u.pwd, pwd):
session['user'] = u.name
session['user_id'] = u.id
userlog = UserLog(
user_id=u.id,
ip=request.remote_addr,
area=get_ip_area(request.remote_addr)
)
db.session.add(userlog)
db.session.commit()
return redirect(url_for('list'))
else:
# flash: 消息闪现,
# 如何在前端html页面显示? Jinja2 get_flash_messages()
# 返回的是一个列表, 想依次显示内容, 需要用到for循环;
flash('用户或者密码不正确!')
return redirect(url_for('login'))
else:
return render_template('login.html')
@app.route('/logout/')
def logout():
# 将会话中的key值弹出;
session.pop('user', None)
session.pop('user_id', None)
# 注销跳转到登录页面, 或者公共首页
return redirect(url_for('login'))
@app.route('/<int:page>')
@app.route('/')
def index(page=1):
parts = Department.query.all()
untodos = Todo.query.filter_by(status=False).paginate(page, per_page=5)
return render_template('index.html', parts=parts, untodos=untodos)
#
#
# #
# @app.route('/add/', methods=['POST'])
# def add():
# form = request.form
# print(form)
# name = form['content']
# part = form['part']
# todo = Todo(name = name, department_id=part)
# db.session.add(todo)
# db.session.commit()
# todos = Todo.query.all()
# return redirect(url_for('list'))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5008)
初始化Flask-Bootstrap之后,我们的模板通过继承一个基模板,即bootstrap/base.html ,就可以使用Bootstrap了。
新建一个templates文件夹,在该文件夹中添加一个base.html的文件,代码如下:
{% extends 'bootstrap/base.html' %}
{% block title %}
首页
{% endblock %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" href="{{ url_for('static', filename='css/main6.css') }}" type="text/css">
<style>
.navbar-default {
background-color: white;
border: 0;
box-shadow: 0px 2px 8px 0px rgba(50, 50, 50, 0.2);
}
.navbar-default .navbar-brand {
font-size: 30px;
color: #40D2B1;
height: 70px;
line-height: 35px;
}
.navbar-default .navbar-nav li a {
font-size: 16px;
color: #666;
height: 70px;
line-height: 35px;
}
.navbar-nav li .active {
background-color: #666;
}
.navbar-default .navbar-left {
padding-top: 10px;
}
</style>
{% endblock %}
<!--导航栏内容-->
{% block navbar %}
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">任务管理</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
{# <li><a href="#"> </a></li>#}
{# <li><a href="/add/"><span class="glyphicon glyphicon-plus"></span> 增加用户 <span#}
{# class="sr-only">(current)</span></a></li>#}
{# <li><a href="/delete/"><span class="glyphicon glyphicon-minus"></span> 删除用户</a></li>#}
{# <li><a href="/list/"><span class="glyphicon glyphicon-hdd"></span> 查看用户</a></li>#}
{# <li><a href="/query/username/"><span class="glyphicon glyphicon-hdd"></span> 用户查询</a>#}
{# </li>#}
</ul>
<ul class="nav navbar-nav navbar-right">
<form class="navbar-form navbar-left" action="/query/username/" method="post">
<div class="form-group">
<input type="text" class="form-control" name='value' placeholder="Search">
<input type="submit" class="form-control" value="查询">
</div>
{# <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-search"></span>#}
{# </button>#}
</form>
{% block logout %}
<li><a href="/login/">登录</a></li>
{% endblock %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{% endblock %}
{% block content %}
{% endblock %}
index.html
{% extends 'base.html' %}
{% block content %}
<div class="col-md-6 col-md-offset-3">
<table class="table table-bordered">
<tr>
<td>部门</td>
<td>
{% for part in parts %}
<a href="#" class="btn btn-sm btn-danger" role="button"><span
class="glyphicon glyphicon-tags"></span> {{ part.name }}</a>
{% endfor %}
</td>
</tr>
<tr>
<td>任务类型</td>
<td>
<a href="#" class="btn btn-sm btn-success" role="button"><span
class="glyphicon glyphicon-remove"></span> 已完成</a>
<button type="button" class="btn btn-sm btn-info"><span
class="glyphicon glyphicon-ok"></span> 未完成
</button>
</td>
</tr>
</table>
<h3>紧急任务</h3>
<span>注意: 登录后进行操作</span>
<br/>
<table class="table table-bordered">
<thead>
<th>编号</th>
<th>任务内容</th>
<th>创建时间</th>
<th>状态</th>
<th>所属部门</th>
<th>删除</th>
</thead>
{% for todo in untodos.items %}
<tr>
<td>{{ todo.id }}</td>
<td>{{ todo.name }}</td>
<td>{{ todo.add_time }}</td>
<td>
{# 有按钮 :
如果状态为True: 则返回完成的按钮;
#}
{% if todo.status %}
<a href="{{ url_for('undone', todo_id=todo.id) }}" class="btn btn-success"
role="button">已完成</a>
{% else %}
{# {{ url_for('done', todo_id=todo.id) }} ======== '/done/1/' #}
<a href="{{ url_for('done', todo_id=todo.id) }}" class="btn btn-warning"
role="button">未完成</a>
{% endif %}
</td>
<td>{{ todo.department.name }}</td>
<td>
<a class="btn btn-danger"
href="{{ url_for('delete', todo_id=todo.id, page=untodos.page) }}"
role="button">删除</a>
</td>
</tr>
{% endfor %}
</table>
{# 分页信息#}
{% from 'macro/page.html' import paginate %}
{{ paginate('list', untodos) }}
</div>
{% endblock %}
list.html
{% extends 'base.html' %}
{% block title %}用户显示{% endblock %}
{% block logout %} <li><a href="/logout/">登出</a></li>{% endblock %}
{% block content %}
<div class="col-md-8 col-md-offset-2">
<br/>
<br/>
<br/>
<br/>
<br/>
<form class="form-horizontal" action="{{ url_for('add') }}" method="post">
<div class="form-group">
{# 添加框 #}
<div class="col-sm-9">
<input type="text" class="form-control" placeholder="请添加任务" required="required" name="todo_name">
</div>
{# 选择框 #}
<div class="col-sm-2">
{#
select标签的name属性是为了将用户选择的数据传递给后台;作为key值;
option的value属性, 是传递用户选择的值;
#}
<select class="form-control" name="part">
{% for part in parts %}
<option value="{{ part.id }}">{{ part.name }}</option>
{% endfor %}
</select>
</div>
{# 添加的按钮 #}
<div class="col-sm-1">
<input type="submit" class="btn btn-success" value="添加任务">
</div>
</div>
</form>
{# <br/>#}
{# <br/>#}
{# <br/>#}
{# <form class="form-horizontal" method="post" action="/add/">#}
{# <div class="form-group">#}
{# <div class="col-md-9">#}
{# <input type="text" class="form-control" required="required" name="content" placeholder="添加任务">#}
{# </div>#}
{##}
{# <div class="col-md-2">#}
{##}
{# <select class="form-control" name="part">#}
{# {% for part in parts %}#}
{# <option value="{{ part.id }}">{{ part.name }}</option>#}
{# {% endfor %}#}
{##}
{# </select>#}
{##}
{# </div>#}
{# <button type="submit" class="btn btn-default col-md-1 btn-success ">添加</button>#}
{# </div>#}
{##}
{# </form>#}
<h1>任务显示</h1>
<table class="table table-bordered">
<thead>
<th>编号</th>
<th>任务内容</th>
<th>创建时间</th>
<th>状态</th>
<th>所属部门</th>
<th>删除</th>
</thead>
{% for todo in todos.items %}
<tr>
<td>{{ todo.id }}</td>
<td>{{ todo.name }}</td>
<td>{{ todo.add_time }}</td>
<td>
{# 有按钮 :
如果状态为True: 则返回完成的按钮;
#}
{% if todo.status %}
<a href="{{ url_for('undone', todo_id=todo.id) }}" class="btn btn-success"
role="button">已完成</a>
{% else %}
{# {{ url_for('done', todo_id=todo.id) }} ======== '/done/1/' #}
<a href="{{ url_for('done', todo_id=todo.id) }}" class="btn btn-warning"
role="button">未完成</a>
{% endif %}
</td>
<td>{{ todo.department.name }}</td>
<td>
<a class="btn btn-danger"
href="{{ url_for('delete', todo_id=todo.id, page=todos.page) }}"
role="button">删除</a>
</td>
</tr>
{% endfor %}
</table>
{# 分页信息#}
{% from 'macro/page.html' import paginate %}
{{ paginate('list', todos) }}
</div>
{% endblock %}
login.html
{% extends 'base.html' %}
{% block title %}用户登录{% endblock %}
{% block content %}
<div class="col-md-8 col-md-offset-2">
<form class="form-horizontal" action="{{ url_for('login') }}" method="post">
<h2>用户登录</h2>
{% for error in get_flashed_messages() %}
<p style=" color:red;">{{ error }}</p>
{% endfor %}
<div class="form-group">
<label class="col-sm-2 control-label">用户名</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" placeholder="请输入用户名">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" name="pwd" placeholder="请输入密码">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox"> Remember me
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">登录</button>
</div>
</div>
</form>
</div>
{% endblock %}
上面的所有的页面都继承与base.html页面,而base.html就是继承于'bootstrap/base.html',
'bootstrap/base.html'就是官方为我们提供的模板,这个模板中有帮我们“挖的坑“我们只需要将其填补上就可以比如
{% extends 'bootstrap/base.html' %} 继承于哪里
{% block title %} 首页 {% endblock %} 浏览器上的头部信息
{% block content %}...... {% endblock %} 正文部分的样式
我们也可以自己刨坑和填坑前面的文章中我们也提到过相关内容:
模板继承语法: 1. 如何继承某个模板? {% extends "模板名称" %} 2. 如何挖坑和填坑? 挖坑: {% block 名称 %} 默认值 {% endblock %} 填坑: {% block 名称 %} {% endblock %} 3. 如何调用/继承被替代的模板? 挖坑: {% block 名称 %} 默认值 {% endblock %} 填坑: {% block 名称 %} #如何继承挖坑时的默认值? {{ super() }} # 后面写新加的方法. ........ {% endblock %}