本篇博客会先添加一张新用户相关的报表,然后提取两次添加报表相同的模块,对代码进行重构,方便之后的报表添加
展示新用户报表
对于一个应用来说新用户也是一个十分重要的观测数据
首先在数据库中新建一张new_user表并添加一些数据
CREATE TABLE new_user(
`date` date,
`province` varchar(20),
`new_user` int,
primary key(`date`, `province`)
);
INSERT INTO new_user VALUES
('2021-01-01', 'Beijing', 10),
('2021-01-01', 'Shanghai', 20),
('2021-01-02', 'Beijing', 30);
按照日活报表相同的方法分别新增
new_user.js
$.ajax({
url: "/data/new_user",
type: "get",
success: function (data) {
let table = $('#new-user-table').DataTable();
table.clear();
let dataSet = [];
for (let i = 0; i < data.length; ++i) {
let rowSet = [];
let detail = data[i];
rowSet.push(detail["date"])
rowSet.push(detail["province"])
rowSet.push(detail["new_user"])
dataSet.push(rowSet);
}
table.rows.add(dataSet).draw();
table.columns.adjust().draw();
},
});
new_user.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>Anne</title>
<link href="https://cdn.datatables.net/1.11.2/css/jquery.dataTables.min.css" rel="stylesheet" />
<link href="{{ url_for('static', filename='css/styles.css') }}" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
</head>
<body class="sb-nav-fixed">
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
<!-- Navbar Brand-->
<a class="navbar-brand ps-3" href="/">Anne</a>
<!-- Sidebar Toggle-->
<button class="btn btn-link btn-sm order-1 order-lg-0 me-4 me-lg-0" id="sidebarToggle" href="#!"><i class="fas fa-bars"></i></button>
</nav>
<div id="layoutSidenav">
<div id="layoutSidenav_nav">
<nav class="sb-sidenav accordion sb-sidenav-dark" id="sidenavAccordion">
<div class="sb-sidenav-menu">
<div class="nav">
<div class="sb-sidenav-menu-heading">Core</div>
<a class="nav-link" href="/dau">
<div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
DAU
</a>
<a class="nav-link" href="/new_user">
<div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
New User
</a>
</div>
</div>
</nav>
</div>
<div id="layoutSidenav_content">
<main>
<div class="container-fluid px-4">
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-table me-1"></i>
New User
</div>
<div class="card-body">
<table id="new-user-table">
<thead>
<tr>
<th>Date</th>
<th>Province</th>
<th>New User</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Date</th>
<th>Province</th>
<th>New User</th>
</tr>
</tfoot>
<tbody></tbody>
</table>
</div>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="{{ url_for('static', filename='js/scripts.js') }}"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://cdn.datatables.net/1.11.2/js/jquery.dataTables.min.js"></script>
<script src="{{ url_for('static', filename='js/new_user.js') }}"></script>
</body>
</html>
以及相关db model和路由
class NewUser(db.Model):
__tablename__ = "new_user"
__table_args__ = {
"autoload": True,
"autoload_with": db.engine
}
@app.route("/data/new_user")
def data_new_user():
data = NewUser.query.all()
ret = []
for row in data:
ret.append({
"date": row.date.strftime("%Y-%m-%d"),
"province": row.province,
"new_user": row.new_user,
})
return jsonify(ret)
@app.route("/new_user")
def new_user():
return render_template("new_user.html")
这样一张新的报表就完成了
提取相同内容
观察两张报表的代码不难发现除了部分内容不同外,绝大部分内容是相同的。如果每次新增报表都需要这样写一遍代码,整个添加报表的过程显得十分的低效并且会使得代码变得臃肿。因此可以将其中共性的部分提取出来,从而简化新增报表的流程
js
js代码的差异主要是URL,table的id以及data的处理三个部分
对于table的id而言,目前一个页面只有一张报表,这个情况下,可以将table的id固定下来
对于data的处理,可以通过修改data传输的格式来解决
最终js文件为data.js,代码如下
let query_data = function(url) {
$.ajax({
url: url,
type: "get",
success: function (data) {
let table = $('#data-table').DataTable();
table.clear();
table.rows.add(data).draw();
table.columns.adjust().draw();
},
});
};
html
Flask使用jinjia2来渲染页面,因此可以做到页面内的不同信息通过参数的方式在render_template的时候传入
在jinjia2中可以会将双引号内的内容进行替换,比如下面的html
<!DOCTYPE html>
<html>
{{ message }}
</html>
通过python调用render_template
@app.route("/jinjia2")
def jinjia2():
return render_template("jinjia2.html", message = "Hello Anne")
最终会将页面渲染为
<!DOCTYPE html>
<html>
Hello Anne
</html>
通过这个方式可以将表头的标题进行替换,
但通常来说,一张表的列数是不固定的,无法预先定义,在jinjia2中可以通过{%for%}来处理循环
比如对于页面
<!DOCTYPE html>
<html>
{% for message in messages %}
<div>{{ message }}</div>
{% endfor %}
</html>
通过python调用render_template
@app.route("/jinjia2")
def jinjia2():
return render_template("jinjia2.html", messages = ["Hello Anne", "Hello Hathaway"])
最终会将页面渲染为
<!DOCTYPE html>
<html>
<div>Hello Anne</div>
<div>Hello Hathaway</div>
</html>
可以看到渲染出的html中有许多的空白行
通过jinjia2可以通过Whitespace Control,修改空行和缩进,但仍然无法完美解决问题,需要通过添加换行以及loop的检测,最终的html模板为
<!DOCTYPE html>
<html>
{% for message in messages -%}
<div>{{ message }}</div>{{"
" if not loop.last }}
{%- endfor %}
</html>
渲染出的页面为
<!DOCTYPE html>
<html>
<div>Hello Anne</div>
<div>Hello Hathaway</div>
</html>
通过使用render_template函数以及jinjia2的循环,可以实现通过通过参数动态的渲染出页面
最终的html页面代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>Anne</title>
<link href="https://cdn.datatables.net/1.11.2/css/jquery.dataTables.min.css" rel="stylesheet" />
<link href="{{ url_for('static', filename='css/styles.css') }}" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
</head>
<body class="sb-nav-fixed">
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
<!-- Navbar Brand-->
<a class="navbar-brand ps-3" href="/">Anne</a>
<!-- Sidebar Toggle-->
<button class="btn btn-link btn-sm order-1 order-lg-0 me-4 me-lg-0" id="sidebarToggle" href="#!"><i class="fas fa-bars"></i></button>
</nav>
<div id="layoutSidenav">
<div id="layoutSidenav_nav">
<nav class="sb-sidenav accordion sb-sidenav-dark" id="sidenavAccordion">
<div class="sb-sidenav-menu">
<div class="nav">
<div class="sb-sidenav-menu-heading">Core</div>
<a class="nav-link" href="/dau">
<div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
DAU
</a>
<a class="nav-link" href="/new_user">
<div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
New User
</a>
</div>
</div>
</nav>
</div>
<div id="layoutSidenav_content">
<main>
<div class="container-fluid px-4">
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-table me-1"></i>
{{ title }}
</div>
<div class="card-body">
<table id="data-table">
<thead>
<tr>
{% for col in cols -%}
<th>{{ col }}</th>{{"
" if not loop.last }}
{%- endfor %}
</tr>
</thead>
<tfoot>
<tr>
{% for col in cols -%}
<th>{{ col }}</th>{{"
" if not loop.last }}
{%- endfor %}
</tr>
</tfoot>
<tbody></tbody>
</table>
</div>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="{{ url_for('static', filename='js/scripts.js') }}"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://cdn.datatables.net/1.11.2/js/jquery.dataTables.min.js"></script>
<script src="{{ url_for('static', filename='js/data.js') }}"></script>
<script>
query_data("{{ data_url }}")
</script>
</body>
</html>
python
Flask可以通过add_url_rule的方式来添加路由,通过对python代码的抽象,选择将相同的内容抽象成一个类
class TableModel():
title = ""
cols = []
uri = ""
db_model = None
def __init__(self, title, cols, uri, db_model, app):
self.title = title
self.cols = cols
self.uri = uri
self.db_model = db_model
if app is not None:
self.register_app(app)
def register_app(self, app):
app.add_url_rule(self.url, self.endpoint, self.render)
app.add_url_rule(self.data_url, self.data_endpoint, self.data)
@property
def url(self):
return f"/{self.uri}"
@property
def endpoint(self):
return self.uri
def render(self):
return render_template("table.html", title=self.title, cols=self.cols, data_url=self.data_url)
@property
def data_url(self):
return f"/data/{self.uri}"
@property
def data_endpoint(self):
return f"data.{self.uri}"
def data(self):
data = self.db_model.query.all()
ret = []
for row in data:
ret.append(row.table_row)
return jsonify(ret)
这样新增一张报表只需要
class DAU(db.Model):
__tablename__ = "DAU"
__table_args__ = {
"autoload": True,
"autoload_with": db.engine
}
@property
def table_row(self) -> list:
return [
self.date.strftime("%Y-%m-%d"),
self.province,
self.DAU,
]
TableModel("DAU", ["Date", "Province", "DAU"], "dau", DAU, app)
大大简化了新增报表需要的代码量
本文介绍了如何在Dashboard中添加新用户报表,并通过提取共性部分进行代码重构,以提高效率。利用Flask的 Jinja2 模板引擎动态渲染HTML,简化页面生成,同时在Python中抽象路由管理,减少重复代码。

被折叠的 条评论
为什么被折叠?



