更新文档
@@ -8,7 +8,7 @@
|
|||||||
开 箱 即 用 的 Flask 快 速 开 发 平 台
|
开 箱 即 用 的 Flask 快 速 开 发 平 台
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
[预览](https://pear.lovepikachu.top/) | [官网](http://www.pearadmin.com/) | [群聊](docs/assets/qqgroup.jpg) | [文档](docs/detail.md)
|
[预览](https://pear.lovepikachu.top/) | [官网](http://www.pearadmin.com/) | [群聊](docs/source/_static/qqgroup.jpg) | [文档](docs/detail.md)
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="#">
|
<a href="#">
|
||||||
@@ -221,9 +221,9 @@ docker-compose -f dockercompose.yaml down
|
|||||||
|
|
||||||
| | |
|
| | |
|
||||||
| ---------------------- | ---------------------- |
|
| ---------------------- | ---------------------- |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
|
|
||||||
# 其他说明
|
# 其他说明
|
||||||
|
|||||||
@@ -1,31 +1,39 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from marshmallow import Schema
|
from marshmallow import Schema
|
||||||
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
||||||
|
|
||||||
from applications.extensions import db, ma
|
from applications.extensions import db, ma
|
||||||
|
|
||||||
|
|
||||||
class LogicalDeleteMixin(object):
|
class LogicalDeleteMixin(object):
|
||||||
"""
|
"""
|
||||||
class Test(db.Model,LogicalDeleteMixin):
|
逻辑删除混入类,为模型提供软删除功能。
|
||||||
__tablename__ = 'admin_test'
|
|
||||||
id = db.Column(db.Integer, primary_key=True, comment='角色ID')
|
|
||||||
|
|
||||||
Test.query.filter_by(id=1).soft_delete()
|
示例:
|
||||||
Test.query.logic_all()
|
class Test(db.Model, LogicalDeleteMixin):
|
||||||
|
__tablename__ = 'admin_test'
|
||||||
|
id = db.Column(db.Integer, primary_key=True, comment='角色ID')
|
||||||
|
|
||||||
|
# 软删除
|
||||||
|
Test.query.filter_by(id=1).soft_delete()
|
||||||
|
|
||||||
|
# 查询所有未删除的记录
|
||||||
|
Test.query.logic_all()
|
||||||
"""
|
"""
|
||||||
create_at = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
|
create_at = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
|
||||||
update_at = db.Column(db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='创建时间')
|
update_at = db.Column(db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
|
||||||
delete_at = db.Column(db.DateTime, comment='删除时间')
|
delete_at = db.Column(db.DateTime, comment='删除时间')
|
||||||
|
|
||||||
|
|
||||||
def auto_model_jsonify(data, model: db.Model):
|
def auto_model_jsonify(data, model: db.Model):
|
||||||
"""
|
"""
|
||||||
不需要建立schemas,直接使用orm的定义模型进行序列化
|
自动序列化模型数据为 JSON 格式,无需手动定义 Schema。
|
||||||
基本功能,待完善
|
|
||||||
示例
|
示例:
|
||||||
power_data = curd.auto_model_jsonify(model=Dept, data=dept)
|
power_data = curd.auto_model_jsonify(model=Dept, data=dept)
|
||||||
|
|
||||||
|
:param data: 需要序列化的 SQLAlchemy 查询结果。
|
||||||
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:return: 返回序列化后的 JSON 数据。
|
||||||
"""
|
"""
|
||||||
def get_model():
|
def get_model():
|
||||||
return model
|
return model
|
||||||
@@ -33,49 +41,60 @@ def auto_model_jsonify(data, model: db.Model):
|
|||||||
class AutoSchema(SQLAlchemyAutoSchema):
|
class AutoSchema(SQLAlchemyAutoSchema):
|
||||||
class Meta(Schema):
|
class Meta(Schema):
|
||||||
model = get_model()
|
model = get_model()
|
||||||
include_fk = True
|
include_fk = True # 包含外键
|
||||||
include_relationships = True
|
include_relationships = True # 包含关联关系
|
||||||
load_instance = True
|
load_instance = True # 反序列化时加载为模型实例
|
||||||
|
|
||||||
common_schema = AutoSchema(many=True) # 用已继承ma.ModelSchema类的自定制类生成序列化类
|
common_schema = AutoSchema(many=True) # 支持序列化多个对象
|
||||||
output = common_schema.dump(data)
|
output = common_schema.dump(data)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
def model_to_dicts(schema: ma.Schema, data):
|
def model_to_dicts(schema: ma.Schema, data):
|
||||||
"""
|
"""
|
||||||
:param schema: schema类
|
使用指定的 Schema 序列化 SQLAlchemy 查询结果。
|
||||||
:param model: sqlalchemy查询结果
|
|
||||||
:return: 返回单个查询结果
|
:param schema: Marshmallow Schema 类。
|
||||||
|
:param data: SQLAlchemy 查询结果。
|
||||||
|
:return: 返回序列化后的数据,返回字典。
|
||||||
"""
|
"""
|
||||||
# 如果是分页器返回,需要传入model.items
|
common_schema = schema(many=True) # 支持序列化多个对象
|
||||||
common_schema = schema(many=True) # 用已继承ma.ModelSchema类的自定制类生成序列化类
|
output = common_schema.dump(data)
|
||||||
output = common_schema.dump(data) # 生成可序列化对象
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
def get_one_by_id(model: db.Model, id):
|
def get_one_by_id(model: db.Model, id):
|
||||||
"""
|
"""
|
||||||
:param model: 模型类
|
根据 ID 查询单个记录。
|
||||||
:param id: id
|
|
||||||
:return: 返回单个查询结果
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:param id: 记录的主键 ID。
|
||||||
|
:return: 返回查询到的记录,如果未找到则返回 None。
|
||||||
"""
|
"""
|
||||||
return model.query.filter_by(id=id).first()
|
return model.query.filter_by(id=id).first()
|
||||||
|
|
||||||
|
|
||||||
def delete_one_by_id(model: db.Model, id):
|
def delete_one_by_id(model: db.Model, id):
|
||||||
"""
|
"""
|
||||||
:param model: 模型类
|
根据 ID 删除单个记录。
|
||||||
:param id: id
|
|
||||||
:return: 返回单个查询结果
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:param id: 记录的主键 ID。
|
||||||
|
:return: 返回删除操作影响的行数。
|
||||||
"""
|
"""
|
||||||
r = model.query.filter_by(id=id).delete()
|
r = model.query.filter_by(id=id).delete()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
# 启动状态
|
|
||||||
def enable_status(model: db.Model, id):
|
def enable_status(model: db.Model, id):
|
||||||
|
"""
|
||||||
|
启用指定 ID 的记录。
|
||||||
|
|
||||||
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:param id: 记录的主键 ID。
|
||||||
|
:return: 如果操作成功返回 True,否则返回 False。
|
||||||
|
"""
|
||||||
enable = 1
|
enable = 1
|
||||||
role = model.query.filter_by(id=id).update({"enable": enable})
|
role = model.query.filter_by(id=id).update({"enable": enable})
|
||||||
if role:
|
if role:
|
||||||
@@ -84,8 +103,14 @@ def enable_status(model: db.Model, id):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# 停用状态
|
|
||||||
def disable_status(model: db.Model, id):
|
def disable_status(model: db.Model, id):
|
||||||
|
"""
|
||||||
|
停用指定 ID 的记录。
|
||||||
|
|
||||||
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:param id: 记录的主键 ID。
|
||||||
|
:return: 如果操作成功返回 True,否则返回 False。
|
||||||
|
"""
|
||||||
enable = 0
|
enable = 0
|
||||||
role = model.query.filter_by(id=id).update({"enable": enable})
|
role = model.query.filter_by(id=id).update({"enable": enable})
|
||||||
if role:
|
if role:
|
||||||
|
|||||||
@@ -1,112 +1,131 @@
|
|||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
|
|
||||||
from applications.extensions import db
|
from applications.extensions import db
|
||||||
|
|
||||||
|
|
||||||
class ModelFilter:
|
class ModelFilter:
|
||||||
"""
|
"""
|
||||||
orm多参数构造器
|
ORM 多条件查询构造器,支持多种查询条件组合。
|
||||||
"""
|
|
||||||
filter_field = {}
|
|
||||||
filter_list = []
|
|
||||||
|
|
||||||
type_exact = "exact"
|
示例:
|
||||||
type_neq = "neq"
|
mf = ModelFilter()
|
||||||
type_greater = "greater"
|
mf.exact('name', 'John')
|
||||||
type_less = "less"
|
mf.vague('email', 'example.com')
|
||||||
type_vague = "vague"
|
query = User.query.filter(mf.get_filter(User))
|
||||||
type_contains = "contains"
|
"""
|
||||||
type_between = "between"
|
filter_field = {} # 存储字段过滤条件
|
||||||
|
filter_list = [] # 存储最终的过滤条件列表
|
||||||
|
|
||||||
|
# 查询类型常量
|
||||||
|
type_exact = "exact" # 精确匹配
|
||||||
|
type_neq = "neq" # 不等于
|
||||||
|
type_greater = "greater" # 大于
|
||||||
|
type_less = "less" # 小于
|
||||||
|
type_vague = "vague" # 模糊匹配
|
||||||
|
type_contains = "contains" # 包含
|
||||||
|
type_between = "between" # 范围查询
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
"""初始化过滤条件存储字典和列表。"""
|
||||||
self.filter_field = {}
|
self.filter_field = {}
|
||||||
self.filter_list = []
|
self.filter_list = []
|
||||||
|
|
||||||
def exact(self, field_name, value):
|
def exact(self, field_name, value):
|
||||||
"""
|
"""
|
||||||
准确查询字段
|
添加精确匹配条件。
|
||||||
:param field_name: 模型字段名称
|
|
||||||
:param value: 值
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 匹配的值。
|
||||||
"""
|
"""
|
||||||
if value and value != '':
|
if value and value != '':
|
||||||
self.filter_field[field_name] = {"data": value, "type": self.type_exact}
|
self.filter_field[field_name] = {"data": value, "type": self.type_exact}
|
||||||
|
|
||||||
def neq(self, field_name, value):
|
def neq(self, field_name, value):
|
||||||
"""
|
"""
|
||||||
不等于查询字段
|
添加不等于条件。
|
||||||
:param field_name: 模型字段名称
|
|
||||||
:param value: 值
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 不匹配的值。
|
||||||
"""
|
"""
|
||||||
if value and value != '':
|
if value and value != '':
|
||||||
self.filter_field[field_name] = {"data": value, "type": self.type_neq}
|
self.filter_field[field_name] = {"data": value, "type": self.type_neq}
|
||||||
|
|
||||||
def greater(self, field_name, value):
|
def greater(self, field_name, value):
|
||||||
"""
|
"""
|
||||||
大于查询字段
|
添加大于条件。
|
||||||
:param field_name: 模型字段名称
|
|
||||||
:param value: 值
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 大于的值。
|
||||||
"""
|
"""
|
||||||
if value and value != '':
|
if value and value != '':
|
||||||
self.filter_field[field_name] = {"data": value, "type": self.type_greater}
|
self.filter_field[field_name] = {"data": value, "type": self.type_greater}
|
||||||
|
|
||||||
def less(self, field_name, value):
|
def less(self, field_name, value):
|
||||||
"""
|
"""
|
||||||
小于查询字段
|
添加小于条件。
|
||||||
:param field_name: 模型字段名称
|
|
||||||
:param value: 值
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 小于的值。
|
||||||
"""
|
"""
|
||||||
if value and value != '':
|
if value and value != '':
|
||||||
self.filter_field[field_name] = {"data": value, "type": self.type_less}
|
self.filter_field[field_name] = {"data": value, "type": self.type_less}
|
||||||
|
|
||||||
def vague(self, field_name, value: str):
|
def vague(self, field_name, value: str):
|
||||||
"""
|
"""
|
||||||
模糊查询字段
|
添加模糊匹配条件(左右模糊)。
|
||||||
:param field_name: 模型字段名称
|
|
||||||
:param value: 值
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 模糊匹配的值。
|
||||||
"""
|
"""
|
||||||
if value and value != '':
|
if value and value != '':
|
||||||
self.filter_field[field_name] = {"data": ('%' + value + '%'), "type": self.type_vague}
|
self.filter_field[field_name] = {"data": ('%' + value + '%'), "type": self.type_vague}
|
||||||
|
|
||||||
def left_vague(self, field_name, value: str):
|
def left_vague(self, field_name, value: str):
|
||||||
"""
|
"""
|
||||||
左模糊查询字段
|
添加左模糊匹配条件。
|
||||||
:param field_name: 模型字段名称
|
|
||||||
:param value: 值
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 左模糊匹配的值。
|
||||||
"""
|
"""
|
||||||
if value and value != '':
|
if value and value != '':
|
||||||
self.filter_field[field_name] = {"data": ('%' + value), "type": self.type_vague}
|
self.filter_field[field_name] = {"data": ('%' + value), "type": self.type_vague}
|
||||||
|
|
||||||
def right_vague(self, field_name, value: str):
|
def right_vague(self, field_name, value: str):
|
||||||
"""
|
"""
|
||||||
左模糊查询字段
|
添加右模糊匹配条件。
|
||||||
:param field_name: 模型字段名称
|
|
||||||
:param value: 值
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 右模糊匹配的值。
|
||||||
"""
|
"""
|
||||||
if value and value != '':
|
if value and value != '':
|
||||||
self.filter_field[field_name] = {"data": (value + '%'), "type": self.type_vague}
|
self.filter_field[field_name] = {"data": (value + '%'), "type": self.type_vague}
|
||||||
|
|
||||||
def contains(self, field_name, value: str):
|
def contains(self, field_name, value: str):
|
||||||
"""
|
"""
|
||||||
包含查询字段
|
添加包含条件。
|
||||||
:param field_name: 模型字段名称
|
|
||||||
:param value: 值
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 包含的值。
|
||||||
"""
|
"""
|
||||||
if value and value != '':
|
if value and value != '':
|
||||||
self.filter_field[field_name] = {"data": value, "type": self.type_contains}
|
self.filter_field[field_name] = {"data": value, "type": self.type_contains}
|
||||||
|
|
||||||
def between(self, field_name, value1, value2):
|
def between(self, field_name, value1, value2):
|
||||||
"""
|
"""
|
||||||
范围查询字段
|
添加范围查询条件。
|
||||||
:param field_name: 模型字段名称
|
|
||||||
:param value: 值
|
:param field_name: 模型字段名称。
|
||||||
|
:param value1: 范围起始值。
|
||||||
|
:param value2: 范围结束值。
|
||||||
"""
|
"""
|
||||||
if value1 and value2 and value1 != '' and value2 != '':
|
if value1 and value2 and value1 != '' and value2 != '':
|
||||||
self.filter_field[field_name] = {"data": [value1, value2], "type": self.type_between}
|
self.filter_field[field_name] = {"data": [value1, value2], "type": self.type_between}
|
||||||
|
|
||||||
def get_filter(self, model: db.Model):
|
def get_filter(self, model: db.Model):
|
||||||
"""
|
"""
|
||||||
获取过滤条件
|
获取最终的 SQLAlchemy 过滤条件。
|
||||||
:param model: 模型字段名称
|
|
||||||
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:return: 返回组合后的过滤条件。
|
||||||
"""
|
"""
|
||||||
for k, v in self.filter_field.items():
|
for k, v in self.filter_field.items():
|
||||||
if v.get("type") == self.type_vague:
|
if v.get("type") == self.type_vague:
|
||||||
|
|||||||
@@ -60,9 +60,13 @@ class Query(BaseQuery):
|
|||||||
def all_json(self, schema: Marshmallow().Schema):
|
def all_json(self, schema: Marshmallow().Schema):
|
||||||
return schema(many=True).dump(self.all())
|
return schema(many=True).dump(self.all())
|
||||||
|
|
||||||
def layui_paginate(self):
|
def layui_paginate(self, page=None, limit=None):
|
||||||
return self.paginate(page=request.args.get('page', type=int),
|
if page is None:
|
||||||
per_page=request.args.get('limit', type=int),
|
page = request.args.get('page', type=int)
|
||||||
|
if limit is None:
|
||||||
|
limit = request.args.get('limit', type=int)
|
||||||
|
return self.paginate(page=page,
|
||||||
|
per_page=limit,
|
||||||
error_out=False)
|
error_out=False)
|
||||||
|
|
||||||
def layui_paginate_json(self, schema: Marshmallow().Schema):
|
def layui_paginate_json(self, schema: Marshmallow().Schema):
|
||||||
|
|||||||
@@ -1,279 +0,0 @@
|
|||||||
## 用户权限判断
|
|
||||||
|
|
||||||
Pear Admin Flask 项目中集成很多实用的功能,为了便于二次开发,同样也提供了许多便于开发的自定义函数。
|
|
||||||
|
|
||||||
Pear Admin Flask 项目支持多用户,不同用户有不同的权限,此处将介绍 Pear Admin Flask 中的权限管理函数的用法。
|
|
||||||
|
|
||||||
### 函数原型
|
|
||||||
|
|
||||||
函数调用位于项目代码 ```applications/common/utils/rights.py``` 中,函数原型如下:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def authorize(power: str, log: bool = False):
|
|
||||||
"""
|
|
||||||
用户权限判断,用于判断目前会话用户是否拥有访问权限
|
|
||||||
|
|
||||||
:param power: 权限标识
|
|
||||||
:type power: str
|
|
||||||
:param log: 是否记录日志, defaults to False
|
|
||||||
:type log: bool, optional
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 基本用法
|
|
||||||
|
|
||||||
+ 后端用法
|
|
||||||
|
|
||||||
```python
|
|
||||||
from applications.common.utils.rights import authorize
|
|
||||||
|
|
||||||
@app.route("/test")
|
|
||||||
@authorize("system:power:remove", log=True)
|
|
||||||
def test_index():
|
|
||||||
return 'You are allowed.'
|
|
||||||
```
|
|
||||||
|
|
||||||
> 使用装饰器 @authorize时需要注意,该装饰器需要写在 @app.route之后
|
|
||||||
|
|
||||||
+ 前端用法
|
|
||||||
|
|
||||||
在前端中,例如增加,删除按钮,对于没有编辑权限的用户不显示的话,可以使用
|
|
||||||
|
|
||||||
`{% **if** authorize("admin:user:edit") %}`
|
|
||||||
|
|
||||||
`{% endif %}`
|
|
||||||
|
|
||||||
例如
|
|
||||||
|
|
||||||
```python
|
|
||||||
{% if authorize("system:user:edit") %}
|
|
||||||
<button class="pear-btn pear-btn-primary pear-btn-sm" lay-event="edit">
|
|
||||||
<i class="pear-icon pear-icon-edit"></i>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if authorize("system:user:remove") %}
|
|
||||||
<button class="pear-btn pear-btn-danger pear-btn-sm" lay-event="remove">
|
|
||||||
<i class="pear-icon pear-icon-ashbin"></i>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Schema 序列化
|
|
||||||
|
|
||||||
项目中时常会涉及到数据库的读写,在读入数据时可以采用SQLalchemy,将模型查询的数据对象转化为字典。
|
|
||||||
|
|
||||||
> Schema 是序列化类,我们把他放在了models文件里,因为觉得没有必要新建一个文件夹叫 Schema ,也方便看着模型写序列化类。
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 例如
|
|
||||||
class DeptSchema(ma.Schema): # 序列化类
|
|
||||||
deptId = fields.Integer(attribute="id")
|
|
||||||
parentId = fields.Integer(attribute="parent_id")
|
|
||||||
deptName = fields.Str(attribute="dept_name")
|
|
||||||
leader = fields.Str()
|
|
||||||
phone = fields.Str()
|
|
||||||
email = fields.Str()
|
|
||||||
address = fields.Str()
|
|
||||||
status = fields.Str()
|
|
||||||
sort = fields.Str()
|
|
||||||
```
|
|
||||||
|
|
||||||
> 这一部分有问题的话请看 marshmallow 文档
|
|
||||||
|
|
||||||
### 模型到字典
|
|
||||||
|
|
||||||
#### 函数原型
|
|
||||||
|
|
||||||
函数调用位于项目代码 ```applications/common/curd.py``` 中,函数原型如下:
|
|
||||||
|
|
||||||
```
|
|
||||||
def model_to_dicts(schema: ma.Schema, data):
|
|
||||||
"""
|
|
||||||
将模型查询的数据对象转化为字典
|
|
||||||
|
|
||||||
:param schema: schema类
|
|
||||||
:param model: sqlalchemy查询结果
|
|
||||||
:return: 返回单个查询结果
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 基本用法
|
|
||||||
|
|
||||||
+ model写的是查询后的对象
|
|
||||||
|
|
||||||
```python
|
|
||||||
from applications.common import curd
|
|
||||||
from applications.models import Dept
|
|
||||||
from applications.schemas import DeptOutSchema
|
|
||||||
|
|
||||||
def test(): # 某函数内
|
|
||||||
dept = Dept.query.order_by(Dept.sort).all()
|
|
||||||
res = curd.model_to_dicts(Schema=DeptOutSchema, model=dept)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 查询多字段构造器
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 准确查询字段
|
|
||||||
# 不等于查询字段
|
|
||||||
# 大于查询字段
|
|
||||||
# 小于查询字段
|
|
||||||
# 模糊查询字段(%+xxx+%)
|
|
||||||
# 左模糊 (% + xxx)
|
|
||||||
# 右模糊查询字段(xxx+ %)
|
|
||||||
# 包含查询字段
|
|
||||||
# 范围查询字段
|
|
||||||
# 查询
|
|
||||||
```
|
|
||||||
|
|
||||||
## xss过滤
|
|
||||||
|
|
||||||
### 函数原型
|
|
||||||
|
|
||||||
函数调用位于项目代码 ```applications/common/utils/validate.py``` 中,函数原型如下:
|
|
||||||
|
|
||||||
```
|
|
||||||
def str_escape(s: str) -> str:
|
|
||||||
"""
|
|
||||||
xss过滤,内部采用flask自带的过滤函数。
|
|
||||||
与原过滤函数不同的是此过滤函数将在 s 为 None 时返回 None。
|
|
||||||
|
|
||||||
:param s: 要过滤的字符串
|
|
||||||
:type s: str
|
|
||||||
:return: s 为 None 时返回 None,否则过滤字符串后返回。
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 使用方法
|
|
||||||
|
|
||||||
```python
|
|
||||||
from applications.common.utils.validate import str_escape
|
|
||||||
real_name = xss_escape(request.args.get('realName', type=str))
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## 邮件发送
|
|
||||||
|
|
||||||
+ 原邮件发送函数
|
|
||||||
|
|
||||||
### 函数原型
|
|
||||||
|
|
||||||
函数调用位于项目代码 ```applications/common/utils/mail.py``` 中,函数原型如下:
|
|
||||||
|
|
||||||
```
|
|
||||||
def send_mail(subject, recipients, content):
|
|
||||||
"""原发送邮件函数,不会记录邮件发送记录
|
|
||||||
|
|
||||||
失败报错,请注意使用 try 拦截。
|
|
||||||
|
|
||||||
:param subject: 主题
|
|
||||||
:param recipients: 接收者 多个用英文分号隔开
|
|
||||||
:param content: 邮件 html
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 示例代码
|
|
||||||
|
|
||||||
```python
|
|
||||||
#在.flaskenv中配置邮箱
|
|
||||||
from applications.common.utils import mail
|
|
||||||
|
|
||||||
mail.send_mail("subject", "test@test.com", "<h1>Hello</h1>")
|
|
||||||
```
|
|
||||||
|
|
||||||
+ 基于二次开发的邮件发送函数
|
|
||||||
|
|
||||||
### 函数原型
|
|
||||||
|
|
||||||
函数调用位于项目代码 ```applications/common/utils/mail.py``` 中,函数原型如下:
|
|
||||||
|
|
||||||
```
|
|
||||||
def add(receiver, subject, content, user_id):
|
|
||||||
"""
|
|
||||||
发送一封邮件,若发送成功立刻提交数据库。
|
|
||||||
|
|
||||||
:param receiver: 接收者 多个用英文逗号隔开
|
|
||||||
:param subject: 邮件主题
|
|
||||||
:param content: 邮件 html
|
|
||||||
:param user_id: 发送用户ID(谁发送的?) 可以用 from flask_login import current_user ; current_user.id 来表示当前登录用户
|
|
||||||
:return: 成功与否
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 示例代码
|
|
||||||
|
|
||||||
```python
|
|
||||||
#在.flaskenv中配置邮箱
|
|
||||||
from applications.common.utils import mail
|
|
||||||
|
|
||||||
mail.add("test@test.com", "subject", "<h1>Hello</h1>", current_user)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 返回格式
|
|
||||||
|
|
||||||
> 后端响应时我们推荐使用规定的API响应格式。
|
|
||||||
|
|
||||||
### 函数原型
|
|
||||||
|
|
||||||
函数调用位于项目代码 ```applications/common/utils/http.py``` 中,函数原型如下:
|
|
||||||
|
|
||||||
```
|
|
||||||
def success_api(msg: str = "成功"):
|
|
||||||
""" 成功响应 默认值“成功” """
|
|
||||||
return jsonify(success=True, msg=msg)
|
|
||||||
|
|
||||||
|
|
||||||
def fail_api(msg: str = "失败"):
|
|
||||||
""" 失败响应 默认值“失败” """
|
|
||||||
return jsonify(success=False, msg=msg)
|
|
||||||
|
|
||||||
|
|
||||||
def table_api(msg: str = "", count=0, data=None, limit=10):
|
|
||||||
""" 动态表格渲染响应 """
|
|
||||||
res = {
|
|
||||||
'msg': msg,
|
|
||||||
'code': 0,
|
|
||||||
'data': data,
|
|
||||||
'count': count,
|
|
||||||
'limit': limit
|
|
||||||
|
|
||||||
}
|
|
||||||
return jsonify(res)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 示例代码
|
|
||||||
|
|
||||||
```python
|
|
||||||
from applications.common.utils.http import success_api, fail_api, table_api
|
|
||||||
|
|
||||||
@admin_log.get('/operateLog')
|
|
||||||
@authorize("system:log:main")
|
|
||||||
def operate_log():
|
|
||||||
# orm查询
|
|
||||||
# 使用分页获取data需要.items
|
|
||||||
log = AdminLog.query.filter(
|
|
||||||
AdminLog.url != '/passport/login').order_by(
|
|
||||||
desc(AdminLog.create_time)).layui_paginate()
|
|
||||||
count = log.total
|
|
||||||
return table_api(data=model_to_dicts(schema=LogOutSchema, data=log.items), count=count)
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
from applications.common.utils.http import success_api, fail_api, table_api
|
|
||||||
|
|
||||||
@admin_power.post('/save')
|
|
||||||
@authorize("system:power:add", log=True)
|
|
||||||
def save():
|
|
||||||
... # 若干操作
|
|
||||||
if success:
|
|
||||||
return success_api(msg="成功")
|
|
||||||
return fail_api(msg="成功")
|
|
||||||
```
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
## 模型/数据库和序列化
|
|
||||||
### 数据库连接
|
|
||||||
|
|
||||||
项目采用flask-sqlalchemy,支持多数据库连接,默认sqlite
|
|
||||||
|
|
||||||
HOSTNAME: 指数据库的IP地址
|
|
||||||
USERNAME:指数据库登录的用户名
|
|
||||||
PASSWORD:指数据库登录密码
|
|
||||||
PORT:指数据库开放的端口
|
|
||||||
DATABASE:指需要连接的数据库名称
|
|
||||||
#### mssql
|
|
||||||
```
|
|
||||||
MSSQL: f"mssql+pymssql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=cp936"
|
|
||||||
```
|
|
||||||
#### msyql
|
|
||||||
```
|
|
||||||
$ pip install pymysql
|
|
||||||
|
|
||||||
# 手动在mysql中创建数据库,并将配置文件中的url配置如下示例
|
|
||||||
|
|
||||||
SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
|
|
||||||
```
|
|
||||||
#### Oracle
|
|
||||||
```
|
|
||||||
Oracle: f"oracle+cx_oracle://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}"
|
|
||||||
```
|
|
||||||
#### SQLite
|
|
||||||
```
|
|
||||||
SQLite "sqlite:/// database.db"
|
|
||||||
```
|
|
||||||
#### Postgres
|
|
||||||
```
|
|
||||||
Postgres f"postgresql+psycopg2://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}"
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### 序列化
|
|
||||||
|
|
||||||
推荐使用这种自动类型的
|
|
||||||
```python
|
|
||||||
from flask_marshmallow.sqla import SQLAlchemyAutoSchema
|
|
||||||
from applications.models import 你的模型类
|
|
||||||
class RoleOutSchema(SQLAlchemyAutoSchema):
|
|
||||||
class Meta:
|
|
||||||
model = 你的模型类 # table = models.Album.__table__
|
|
||||||
# include_relationships = True # 输出模型对象时同时对外键,是否也一并进行处理
|
|
||||||
include_fk = True # 序列化阶段是否也一并返回主键
|
|
||||||
# fields= ["id","name"] # 启动的字段列表
|
|
||||||
# exclude = ["id","name"] # 排除字段列表
|
|
||||||
```
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
### 说明
|
|
||||||
|
|
||||||
插件功能旨在最大限度不修改原框架的前提下添加新功能,我们提供了三个示例插件。
|
|
||||||
|
|
||||||
|
|
||||||
### 插件配置
|
|
||||||
|
|
||||||
将插件文件夹放置在 ```applications/config.py``` 文件夹中,并且在 .flaskenv 中配置。再配置项中填入插件的文件夹名,已 json 格式写入其中。
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 插件配置
|
|
||||||
PLUGIN_ENABLE_FOLDERS = ["helloworld"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 插件目录
|
|
||||||
|
|
||||||
```
|
|
||||||
Plugin
|
|
||||||
│ __init__.json
|
|
||||||
└─ __init__.py
|
|
||||||
```
|
|
||||||
|
|
||||||
这是一个非常简单的插件。
|
|
||||||
|
|
||||||
### 插件信息
|
|
||||||
|
|
||||||
插件信息保存在 ```__init__.py``` 中,以测试插件“helloword”为例。插件的数据应该不少于下面三项:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"plugin_name": "Hello World",
|
|
||||||
"plugin_version": "1.0.0.1",
|
|
||||||
"plugin_description": "一个测试的插件。"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 插件格式
|
|
||||||
|
|
||||||
插件的入口点为 ```__init__.py``` 文件,在插件被启用后,程序启动时此 Python 文件中的 ```event_init``` 函数。代码如下:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from flask import Flask
|
|
||||||
|
|
||||||
def event_init(app: Flask):
|
|
||||||
"""初始化完成时会调用这里"""
|
|
||||||
print("加载完毕后,我会输出一句话。")
|
|
||||||
```
|
|
||||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
@@ -1,7 +1,7 @@
|
|||||||
:mod:`admin` -- 后台函数模块
|
:mod:`admin` -- 后台函数模块
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
:mod:`admin` 模块源代码在文件夹 `applications/common/admin.py` 下,主要集结了一些常用的后台需要频繁调用的函数。
|
:mod:`admin` 模块源代码在文件 `applications/common/admin.py` 下,主要集结了一些常用的后台需要频繁调用的函数。
|
||||||
|
|
||||||
.. module:: admin
|
.. module:: admin
|
||||||
|
|
||||||
@@ -10,9 +10,21 @@
|
|||||||
|
|
||||||
.. function:: get_captcha()
|
.. function:: get_captcha()
|
||||||
|
|
||||||
生成验证码图片及其对应的验证码字符串。
|
生成验证码图片及其对应的验证码字符串。
|
||||||
|
|
||||||
:return: 返回验证码图片的响应对象和验证码字符串。
|
:return: 返回验证码图片的响应对象和验证码字符串。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from applications.common.admin import get_captcha
|
||||||
|
|
||||||
|
@bp.get('/getCaptcha')
|
||||||
|
def captcha():
|
||||||
|
resp, code = get_captcha()
|
||||||
|
session["code"] = code
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
.. function:: normal_log(method, url, ip, user_agent, desc, uid, is_access)
|
.. function:: normal_log(method, url, ip, user_agent, desc, uid, is_access)
|
||||||
@@ -31,12 +43,12 @@
|
|||||||
|
|
||||||
.. function:: login_log(request, uid, is_access)
|
.. function:: login_log(request, uid, is_access)
|
||||||
|
|
||||||
记录用户登录日志。
|
记录用户登录日志。
|
||||||
|
|
||||||
:param request: Flask 请求对象。
|
:param request: Flask 请求对象。
|
||||||
:param uid: 用户 ID。
|
:param uid: 用户 ID。
|
||||||
:param is_access: 是否成功登录(True 或 False)。
|
:param is_access: 是否成功登录(True 或 False)。
|
||||||
:return: 返回日志记录的 ID。
|
:return: 返回日志记录的 ID。
|
||||||
|
|
||||||
|
|
||||||
.. function:: admin_log(request, is_access, desc=None)
|
.. function:: admin_log(request, is_access, desc=None)
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
:mod:`curd` -- 简单增删改查模块
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
:mod:`curd` 模块源代码在文件 `applications/common/curd.py` 下,主要集结了一些简单实用的增删改查。
|
||||||
|
|
||||||
|
.. module:: curd
|
||||||
|
|
||||||
|
类
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. class:: LogicalDeleteMixin
|
||||||
|
|
||||||
|
逻辑删除混入类,为模型提供软删除功能。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Test(db.Model, LogicalDeleteMixin):
|
||||||
|
__tablename__ = 'admin_test'
|
||||||
|
id = db.Column(db.Integer, primary_key=True, comment='角色ID')
|
||||||
|
|
||||||
|
# 软删除
|
||||||
|
Test.query.filter_by(id=1).soft_delete()
|
||||||
|
|
||||||
|
# 查询所有未删除的记录
|
||||||
|
Test.query.logic_all()
|
||||||
|
|
||||||
|
|
||||||
|
函数
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. function:: auto_model_jsonify(data, model: db.Model)
|
||||||
|
|
||||||
|
自动序列化模型数据为 JSON 格式,无需手动定义 Schema。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
power_data = curd.auto_model_jsonify(model=Dept, data=dept)
|
||||||
|
|
||||||
|
:param data: 需要序列化的 SQLAlchemy 查询结果。
|
||||||
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:return: 返回序列化后的 JSON 数据。
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: model_to_dicts(schema: ma.Schema, data)
|
||||||
|
|
||||||
|
使用指定的 Schema 序列化 SQLAlchemy 查询结果。
|
||||||
|
|
||||||
|
:param schema: Marshmallow Schema 类。
|
||||||
|
:param data: SQLAlchemy 查询结果。
|
||||||
|
:return: 返回序列化后的数据,返回字典。
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: get_one_by_id(model: db.Model, id)
|
||||||
|
|
||||||
|
根据 ID 查询单个记录。
|
||||||
|
|
||||||
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:param id: 记录的主键 ID。
|
||||||
|
:return: 返回查询到的记录,如果未找到则返回 None。
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: delete_one_by_id(model: db.Model, id)
|
||||||
|
|
||||||
|
根据 ID 删除单个记录。
|
||||||
|
|
||||||
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:param id: 记录的主键 ID。
|
||||||
|
:return: 返回删除操作影响的行数。
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: enable_status(model: db.Model, id)
|
||||||
|
|
||||||
|
启用指定 ID 的记录。
|
||||||
|
|
||||||
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:param id: 记录的主键 ID。
|
||||||
|
:return: 如果操作成功返回 True,否则返回 False。
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: disable_status(model: db.Model, id)
|
||||||
|
|
||||||
|
停用指定 ID 的记录。
|
||||||
|
|
||||||
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:param id: 记录的主键 ID。
|
||||||
|
:return: 如果操作成功返回 True,否则返回 False。
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
.. _字段构造模块:
|
||||||
|
|
||||||
|
:mod:`helper` -- 字段构造模块
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
:mod:`helper` 模块源代码在文件 `applications/common/helper.py` 下,主要集结了一些常用的字段构造方法。
|
||||||
|
|
||||||
|
.. module:: helper
|
||||||
|
|
||||||
|
类
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. class:: ModelFilter
|
||||||
|
|
||||||
|
ORM 多条件查询构造器,支持多种查询条件组合。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from applications.common.helper import ModelFilter
|
||||||
|
mf = ModelFilter()
|
||||||
|
mf.exact('name', 'John') # 添加精确匹配条件
|
||||||
|
mf.vague('email', 'example.com') # 添加模糊匹配条件
|
||||||
|
query = User.query.filter(mf.get_filter(User))
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: filter_field
|
||||||
|
|
||||||
|
存储字段过滤条件的字典。
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: filter_list
|
||||||
|
|
||||||
|
存储最终的过滤条件列表。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: __init__()
|
||||||
|
|
||||||
|
初始化过滤条件存储字典和列表。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: exact(field_name, value)
|
||||||
|
|
||||||
|
添加精确匹配条件。
|
||||||
|
|
||||||
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 匹配的值。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: neq(field_name, value)
|
||||||
|
|
||||||
|
添加不等于条件。
|
||||||
|
|
||||||
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 不匹配的值。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: greater(field_name, value)
|
||||||
|
|
||||||
|
添加大于条件。
|
||||||
|
|
||||||
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 大于的值。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: less(field_name, value)
|
||||||
|
|
||||||
|
添加小于条件。
|
||||||
|
|
||||||
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 小于的值。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: vague(field_name, value: str)
|
||||||
|
|
||||||
|
添加模糊匹配条件(左右模糊)。
|
||||||
|
|
||||||
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 模糊匹配的值。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: left_vague(field_name, value: str)
|
||||||
|
|
||||||
|
添加左模糊匹配条件。
|
||||||
|
|
||||||
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 左模糊匹配的值。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: right_vague(field_name, value: str)
|
||||||
|
|
||||||
|
添加右模糊匹配条件。
|
||||||
|
|
||||||
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 右模糊匹配的值。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: contains(field_name, value: str)
|
||||||
|
|
||||||
|
添加包含条件。
|
||||||
|
|
||||||
|
:param field_name: 模型字段名称。
|
||||||
|
:param value: 包含的值。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: between(field_name, value1, value2)
|
||||||
|
|
||||||
|
添加范围查询条件。
|
||||||
|
|
||||||
|
:param field_name: 模型字段名称。
|
||||||
|
:param value1: 范围起始值。
|
||||||
|
:param value2: 范围结束值。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: get_filter(model: db.Model)
|
||||||
|
|
||||||
|
获取最终的 SQLAlchemy 过滤条件。
|
||||||
|
|
||||||
|
:param model: SQLAlchemy 模型类。
|
||||||
|
:return: 返回组合后的过滤条件。
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
admin
|
admin
|
||||||
|
curd
|
||||||
|
helper
|
||||||
|
|
||||||
辅助函数
|
辅助函数
|
||||||
------------
|
------------
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
:mod:`cache` -- 应用缓存模块
|
:mod:`cache` -- 应用缓存模块
|
||||||
================================
|
================================
|
||||||
|
|
||||||
:mod:`cache` 模块源代码在文件夹 `applications/common/utils/cache.py` 下,主要用于简单的程序数据缓存。
|
:mod:`cache` 模块源代码在文件 `applications/common/utils/cache.py` 下,主要用于简单的程序数据缓存。
|
||||||
|
|
||||||
目前此模块仅启用应用程序缓存,暂时没有联动 Redis 等数据库缓存的功能,后续有意向添加。您可以在自己的项目中添加相关的函数,当然也非常欢迎提交 PR ,一起完善项目。
|
目前此模块仅启用应用程序缓存,暂时没有联动 Redis 等数据库缓存的功能,后续有意向添加。您可以在自己的项目中添加相关的函数,当然也非常欢迎提交 PR ,一起完善项目。
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
:mod:`captcha` -- 验证码生成模块
|
:mod:`captcha` -- 验证码生成模块
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
:mod:`captcha` 模块源代码在文件夹 `applications/common/utils/captcha.py` 下,主要用于生成验证码图片。
|
:mod:`captcha` 模块源代码在文件 `applications/common/utils/captcha.py` 下,主要用于生成验证码图片。
|
||||||
|
|
||||||
.. module:: captcha
|
.. module:: captcha
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
.. _JSON 响应正文生成模块:
|
||||||
|
|
||||||
:mod:`http` -- JSON 响应正文生成模块
|
:mod:`http` -- JSON 响应正文生成模块
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
:mod:`http` 模块源代码在文件夹 `applications/common/utils/http.py` 下,主要用于生成 JSON 格式的响应正文。
|
:mod:`http` 模块源代码在文件 `applications/common/utils/http.py` 下,主要用于生成 JSON 格式的响应正文。
|
||||||
|
|
||||||
对于大部分 JSON 格式响应的数据,请尽量遵循响应格式规范。如此方便后续前后端的分离和项目的构建。
|
对于大部分 JSON 格式响应的数据,请尽量遵循响应格式规范。如此方便后续前后端的分离和项目的构建。
|
||||||
|
|
||||||
@@ -36,4 +38,22 @@
|
|||||||
:param limit: 每页数据条数,默认为 10。
|
:param limit: 每页数据条数,默认为 10。
|
||||||
:return: 返回 JSON 格式的响应,包含 `msg`、`code`、`data`、`count` 和 `limit` 字段。
|
:return: 返回 JSON 格式的响应,包含 `msg`、`code`、`data`、`count` 和 `limit` 字段。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from applications.common.utils.http import success_api, fail_api
|
||||||
|
|
||||||
|
@bp.get('/init')
|
||||||
|
def init():
|
||||||
|
if ...:
|
||||||
|
return success_api(msg="初始化成功")
|
||||||
|
return fail_api(msg="初始化失败")
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from applications.common.utils.http import table_api
|
||||||
|
|
||||||
|
@bp.get('/data')
|
||||||
|
def data():
|
||||||
|
return table_api(data=[], total=0)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
目录索引
|
目录索引
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 2
|
||||||
|
|
||||||
cache
|
cache
|
||||||
captcha
|
captcha
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
|
.. _邮件模块:
|
||||||
|
|
||||||
:mod:`mail` -- 邮件模块
|
:mod:`mail` -- 邮件模块
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
:mod:`mail` 模块源代码在文件夹 `applications/common/utils/mail.py` 下,主要用于邮件的发送。
|
:mod:`mail` 模块源代码在文件 `applications/common/utils/mail.py` 下,主要用于邮件的发送。
|
||||||
|
|
||||||
|
使用前,需要正确在 `applications/config.py` 中配置 SMTP 服务器。
|
||||||
|
|
||||||
.. module:: mail
|
.. module:: mail
|
||||||
|
|
||||||
@@ -31,14 +35,23 @@
|
|||||||
|
|
||||||
.. function:: add(receiver, subject, content, user_id)
|
.. function:: add(receiver, subject, content, user_id)
|
||||||
|
|
||||||
发送一封邮件,并将发送记录保存到数据库。 **该方法被邮件发送的视图函数调用。**
|
发送一封邮件,并将发送记录保存到数据库。 **该方法被邮件发送的视图函数调用。**
|
||||||
|
|
||||||
:param receiver: 接收者邮箱地址,多个邮箱用英文分号隔开。
|
:param receiver: 接收者邮箱地址,多个邮箱用英文分号隔开。
|
||||||
:param subject: 邮件主题。
|
:param subject: 邮件主题。
|
||||||
:param content: 邮件内容(HTML 格式)。
|
:param content: 邮件内容(HTML 格式)。
|
||||||
:param user_id: 发送者用户ID,表示谁发送了这封邮件。
|
:param user_id: 发送者用户ID,表示谁发送了这封邮件。
|
||||||
可以使用 `from flask_login import current_user; current_user.id` 获取当前登录用户的ID。
|
可以使用 `from flask_login import current_user; current_user.id` 获取当前登录用户的ID。
|
||||||
:return: 发送成功返回 True,失败报错。
|
:return: 发送成功返回 True,失败报错。
|
||||||
|
|
||||||
|
**示例**
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from flask_login import current_user
|
||||||
|
from applications.common.utils import mail
|
||||||
|
|
||||||
|
mail.add("test@test.com", "subject", "<h1>Hello</h1>", current_user.id)
|
||||||
|
|
||||||
|
|
||||||
.. function:: delete(id)
|
.. function:: delete(id)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
.. _权限验证模块:
|
||||||
|
|
||||||
:mod:`rights` -- 权限验证模块
|
:mod:`rights` -- 权限验证模块
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
:mod:`rights` 模块源代码在文件夹 `applications/common/utils/rights.py` 下,主要用于权限验证。
|
:mod:`rights` 模块源代码在文件 `applications/common/utils/rights.py` 下,主要用于权限验证。
|
||||||
|
|
||||||
.. module:: rights
|
.. module:: rights
|
||||||
|
|
||||||
@@ -23,6 +25,8 @@
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
from applications.common.utils.rights import authorize
|
||||||
|
|
||||||
@app.route("/test")
|
@app.route("/test")
|
||||||
@authorize("system:power:remove", log=True)
|
@authorize("system:power:remove", log=True)
|
||||||
def test_index():
|
def test_index():
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
:mod:`upload` -- 文件上传模块
|
:mod:`upload` -- 文件上传模块
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
:mod:`upload` 模块源代码在文件夹 `applications/common/utils/upload.py` 下,主要用于文件上传,目前主要用于图片上传。
|
:mod:`upload` 模块源代码在文件 `applications/common/utils/upload.py` 下,主要用于文件上传,目前主要用于图片上传。
|
||||||
|
|
||||||
.. module:: upload
|
.. module:: upload
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
:mod:`validate` -- 效验模块
|
:mod:`validate` -- 效验模块
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
:mod:`validate` 模块源代码在文件夹 `applications/common/utils/validate.py` 下,主要用于数据效验与过滤。
|
:mod:`validate` 模块源代码在文件 `applications/common/utils/validate.py` 下,主要用于数据效验与过滤。
|
||||||
|
|
||||||
.. module:: validate
|
.. module:: validate
|
||||||
|
|
||||||
|
|||||||
@@ -21,3 +21,9 @@
|
|||||||
|
|
||||||
function/index
|
function/index
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
:caption: 最佳实践
|
||||||
|
|
||||||
|
practices/index
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.. title:: 最佳实践
|
||||||
|
|
||||||
|
目录索引
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
plugin
|
||||||
|
trick
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
插件开发
|
||||||
|
=================
|
||||||
|
|
||||||
|
插件功能旨在最大限度不修改原框架的前提下添加新功能,并可以像程序原有框架一样进行流程注册,而且不需要修改任何程序框架原有代码(仅在配置文件中设置即可)。
|
||||||
|
|
||||||
|
所有插件放置在 `plugins` 文件夹中,项目提供了三个示例插件,分别是 `helloworld` 、 `realip` 和 `replacePage` ,分别用于示例页面的注册、修改 Flask 上下文和页面替换。
|
||||||
|
|
||||||
|
像项目自带的用户管理、部门管理等基本功能属于程序自身的“功能插件”,对于大多数衍生项目来说,多的是修改字符串和删除部分不需要的功能,
|
||||||
|
而插件开发主要可以用于添加自己的视图函数和功能,可以完美于项目融合,增加可拓展性。
|
||||||
|
|
||||||
|
插件的启用
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
插件需要在 `applications/config.py` 中配置,你会找到如下的内容:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
PLUGIN_ENABLE_FOLDERS = []
|
||||||
|
|
||||||
|
而在目录 `plugins` 中,你会发现存在 文件夹名称 为 `helloworld` 、 `realip` 和 `replacePage` 三个插件。比如我们想要启用 `helloworld` 插件,
|
||||||
|
仅需要做如下修改:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
PLUGIN_ENABLE_FOLDERS = ["helloworld"]
|
||||||
|
|
||||||
|
假设有多个插件,只要依次在列表 `PLUGIN_ENABLE_FOLDERS` 中填入插件的文件夹名称即可。**注意:填写的先后顺序会影响插件加载的前后顺序,越前面的插件越早被加载。**
|
||||||
|
|
||||||
|
假设插件启用成功,你将会在控制台收到如下的提示:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
* Plugin: Loaded plugin: Hello World .
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
|
.. image:: ../_static/plugin_run.png
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
|
`helloworld` 插件启用之后,你可以访问 `http://127.0.0.1:5000/hello_world/` 来请求到新添加的页面。你会发现添加页面变的简单,仅需要修改一下设置项就行了。
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
|
.. image:: ../_static/helloworld.png
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
|
插件的目录架构
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
插件的目录架构如下:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
Plugin
|
||||||
|
│ __init__.json
|
||||||
|
└─ __init__.py
|
||||||
|
|
||||||
|
这是一个插件基本的目录架构,插件信息保存在 `__init__.json` 中,其本质是一个包含如下 JSON 字符串的文本文件:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"plugin_name": "Hello World",
|
||||||
|
"plugin_version": "1.0.0.1",
|
||||||
|
"plugin_description": "一个测试的插件。"
|
||||||
|
}
|
||||||
|
|
||||||
|
这个 JSON 文件中,记录了基本的插件名称与插件版本,以及插件的介绍,请确保一个插件至少包含上述的三个字段,因为这三个字段会被项目所读取并在加载成功之后展示在控制台。
|
||||||
|
|
||||||
|
编写插件入口
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
插件入口位于 `__init__.py` 中,请确保 `__init__.py` 文件一定包含 `event_init(app: Flask)` 函数,如下:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def event_init(app: Flask):
|
||||||
|
pass
|
||||||
|
|
||||||
|
这个函数将会在插件加载时被调用,并传入项目的 `Flask` 对象,此后你可以像一般使用 Flask 一样添加视图函数。例如:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def event_init(app: Flask):
|
||||||
|
@app.get('/test')
|
||||||
|
def test():
|
||||||
|
return "这是测试页面"
|
||||||
|
|
||||||
|
**当然,不推荐这样直接使用 Flask 对象创建视图函数,更妥当的做法是通过注册蓝图的方式来添加视图函数。您可以这样做:**
|
||||||
|
|
||||||
|
在您编写的插件目录下建立一个 `main.py` 文件,并在该文件中添加蓝图:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from flask import render_template, Blueprint
|
||||||
|
|
||||||
|
# 创建蓝图
|
||||||
|
helloworld_blueprint = Blueprint('hello_world', __name__,
|
||||||
|
template_folder='templates',
|
||||||
|
static_folder="static",
|
||||||
|
url_prefix="/hello_world")
|
||||||
|
|
||||||
|
@helloworld_blueprint.route("/")
|
||||||
|
def index():
|
||||||
|
return render_template("helloworld_index.html")
|
||||||
|
|
||||||
|
而后在 `__init__.py` 中注册该蓝图:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
from .main import helloworld_blueprint
|
||||||
|
|
||||||
|
|
||||||
|
def event_init(app: Flask):
|
||||||
|
"""初始化完成时会调用这里"""
|
||||||
|
app.register_blueprint(helloworld_blueprint)
|
||||||
|
|
||||||
|
这样可以使目录架构更加清晰。
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
注意不要直接在 `__init__.py` 的 `event_init` 函数外直接写存在阻塞的代码,不然项目 Flask 将不能初始化完成。
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
开发技巧
|
||||||
|
===================
|
||||||
|
|
||||||
|
开发 Web 过程中需要用到许多技巧来加速开发,本章节将介绍开发的小技巧与一些需要注意的细节。
|
||||||
|
|
||||||
|
配置数据库
|
||||||
|
-----------
|
||||||
|
|
||||||
|
项目采用 flask-sqlalchemy,支持多数据库连接,默认是使用 sqlite 的,可以在 `applications/config.py` 中配置,如果需要连接其他数据库需要可以参考:
|
||||||
|
|
||||||
|
* HOSTNAME: 指数据库的IP地址
|
||||||
|
* USERNAME:指数据库登录的用户名
|
||||||
|
* PASSWORD:指数据库登录密码
|
||||||
|
* PORT:指数据库开放的端口
|
||||||
|
* DATABASE:指需要连接的数据库名称
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# MSSQL
|
||||||
|
SQLALCHEMY_DATABASE_URI = f"mssql+pymssql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=cp936"
|
||||||
|
|
||||||
|
# mysql
|
||||||
|
SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
|
||||||
|
|
||||||
|
# Oracle
|
||||||
|
SQLALCHEMY_DATABASE_URI = f"oracle+cx_oracle://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}"
|
||||||
|
|
||||||
|
# SQLite
|
||||||
|
SQLALCHEMY_DATABASE_URI = "sqlite://../database.db"
|
||||||
|
|
||||||
|
# Postgres
|
||||||
|
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}"
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
使用不同的数据库需要安装另外的库,比如 mysql 要安装 pymysql 库(不同平台可能名称不一样),请自行查询资料而后进行配置。
|
||||||
|
|
||||||
|
权限效验
|
||||||
|
------------
|
||||||
|
|
||||||
|
在开发后台管理模板的过程中会涉及到权限效验,即访问控制。Pear Admin Flask 中提供了方便的函数用于进行权限效验。详情请查看 :ref:`权限验证模块` 章节。
|
||||||
|
|
||||||
|
Schema 序列化
|
||||||
|
---------------
|
||||||
|
|
||||||
|
项目中时常会涉及到数据库的读写,在读入数据时采用 SQLAlchemy,将模型查询的数据对象转化为字典,以此方便与前端页面进行数据交换。
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
Schema 模型放在了 `applications/schemas` 文件夹中,与 `applications/models` 中的数据库模型对应(准确来说是序列化为字典的配置)。
|
||||||
|
|
||||||
|
进行序列化时,常常会用到 `applications/common/curd.py` 中的 `model_to_dicts` 函数,下面是一个常见的用法。
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from applications.models import Dept
|
||||||
|
from applications.common import curd
|
||||||
|
from applications.schemas import DeptSchema
|
||||||
|
|
||||||
|
dept = Dept.query.order_by(Dept.sort).all()
|
||||||
|
power_data = curd.model_to_dicts(schema=DeptSchema, data=dept) # 此处 power_data 将会是一个列表,存储了部门的数据字典
|
||||||
|
|
||||||
|
在自己撰写 Schema 模型时,推荐使用自动化类型转化:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from flask_marshmallow.sqla import SQLAlchemyAutoSchema
|
||||||
|
from applications.models import 你的模型类
|
||||||
|
class RoleOutSchema(SQLAlchemyAutoSchema):
|
||||||
|
class Meta:
|
||||||
|
model = 你的模型类 # table = models.Album.__table__
|
||||||
|
# include_relationships = True # 输出模型对象时同时对外键,是否也一并进行处理
|
||||||
|
include_fk = True # 序列化阶段是否也一并返回主键
|
||||||
|
# fields= ["id","name"] # 启动的字段列表
|
||||||
|
# exclude = ["id","name"] # 排除字段列表
|
||||||
|
|
||||||
|
|
||||||
|
与 layui 的数据格式同步
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
项目的前端页面基于 layui 框架,在一些数据展示页面(如:layui 动态表格)需要与 layui 框架进行快速的数据交换。比如前端会传入 limit 和 page 参数
|
||||||
|
用于限定数据展示的范围。故项目中在 SQLAlchemy 中添加了专有的查询函数。详情可以查看文件 `applications/extensions/init_sqlalchemy.py` 。
|
||||||
|
下面是对 `Query` 类的解释。
|
||||||
|
|
||||||
|
.. class:: Query(BaseQuery)
|
||||||
|
|
||||||
|
自定义查询类,扩展了 BaseQuery 的功能,支持软删除、逻辑查询、分页和序列化。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# 软删除
|
||||||
|
User.query.filter_by(id=1).soft_delete()
|
||||||
|
|
||||||
|
# 查询所有未删除的记录
|
||||||
|
users = User.query.logic_all()
|
||||||
|
|
||||||
|
# 分页查询并返回 JSON 数据
|
||||||
|
data, total, page, per_page = User.query.layui_paginate_json(UserSchema)
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: soft_delete()
|
||||||
|
|
||||||
|
软删除当前查询结果集中的记录。
|
||||||
|
|
||||||
|
:return: 返回更新操作影响的行数。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: logic_all()
|
||||||
|
|
||||||
|
查询所有未删除的记录。
|
||||||
|
|
||||||
|
:return: 返回未删除的记录列表。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: all_json(schema: Schema)
|
||||||
|
|
||||||
|
将查询结果序列化为 JSON 格式。
|
||||||
|
|
||||||
|
:param schema: Marshmallow Schema 类。
|
||||||
|
:return: 返回序列化后的 JSON 数据。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: layui_paginate(page=None, limit=None)
|
||||||
|
|
||||||
|
分页查询,适用于 Layui 表格。
|
||||||
|
|
||||||
|
**需要注意的是,如果不提供 page 和 limit 则该函数必须在视图函数中使用,该函数会自动获取 GET 请求中的 limit 和 page 参数构成查询。**
|
||||||
|
|
||||||
|
:return: 返回分页对象。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# 查询邮件数据并分页
|
||||||
|
mail = Mail.query.filter(mf.get_filter(Mail)).layui_paginate()
|
||||||
|
return model_to_dicts(schema=MailOutSchema, data=mail.items)
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: layui_paginate_json(schema: Schema)
|
||||||
|
|
||||||
|
分页查询并返回 JSON 格式数据,适用于 Layui 表格。
|
||||||
|
|
||||||
|
:param schema: Marshmallow Schema 类。
|
||||||
|
:return: 返回包含序列化数据、总数、当前页码和每页条数的元组。
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: layui_paginate_db_json()
|
||||||
|
|
||||||
|
分页查询并返回数据库原始数据的 JSON 格式,适用于 Layui 表格。
|
||||||
|
|
||||||
|
:return: 返回包含序列化数据和总数的元组。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
db.query(User.name).layui_paginate_db_json()
|
||||||
|
|
||||||
|
|
||||||
|
进行字段构造
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
提炼数据时常常会用到准确匹配或者模糊匹配,又或者是进行多条件大小比较的匹配,此时可以通过字段构造来解决。项目中提供了字段构造的类位于
|
||||||
|
`applications/common/helper.py` 。详情查看 :ref:`字段构造模块` 章节。
|
||||||
|
|
||||||
|
|
||||||
|
响应合适的响应数据
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
在进行 JSON 数据响应时,应该注重响应的 JSON 格式类型,一般情况下,项目的 JSON 响应会形如:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"msg": "请求成功",
|
||||||
|
"data": [],
|
||||||
|
"count": 0,
|
||||||
|
"limit": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
其中,`data` 、 `total` 和 `limit` 字段是可选的,仅在传输数据的时候存在。项目提供了生成统一响应格式的函数,位于 `applications/common/utils/http.py` 。
|
||||||
|
详情查看 :ref:`JSON 响应正文生成模块` 章节。
|
||||||
|
|
||||||
|
|
||||||
|
发送硬件
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
程序提供了发送邮件的模块,前提是需要正确在 `applications/config.py` 中配置 SMTP 服务器。详情查看 ref:`邮件模块` 章节。
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from flask_login import current_user
|
||||||
|
from applications.common.utils import mail
|
||||||
|
|
||||||
|
mail.add("test@test.com", "subject", "<h1>Hello</h1>", current_user.id)
|
||||||
@@ -10,6 +10,7 @@ from .main import helloworld_blueprint
|
|||||||
dir_path = os.path.dirname(__file__).replace("\\", "/")
|
dir_path = os.path.dirname(__file__).replace("\\", "/")
|
||||||
folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称
|
folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称
|
||||||
|
|
||||||
|
|
||||||
def event_init(app: Flask):
|
def event_init(app: Flask):
|
||||||
"""初始化完成时会调用这里"""
|
"""初始化完成时会调用这里"""
|
||||||
app.register_blueprint(helloworld_blueprint)
|
app.register_blueprint(helloworld_blueprint)
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
from flask import render_template, Blueprint
|
from flask import render_template, Blueprint
|
||||||
|
|
||||||
# 创建蓝图
|
# 创建蓝图
|
||||||
helloworld_blueprint = Blueprint('hello_world', __name__, template_folder='templates', static_folder="static",
|
helloworld_blueprint = Blueprint('hello_world', __name__,
|
||||||
url_prefix="/hello_world")
|
template_folder='templates',
|
||||||
|
static_folder="static",
|
||||||
|
url_prefix="/hello_world")
|
||||||
|
|
||||||
|
|
||||||
@helloworld_blueprint.route("/")
|
@helloworld_blueprint.route("/")
|
||||||
def index():
|
def index():
|
||||||
return render_template("helloworld_index.html")
|
return render_template("helloworld_index.html")
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,14 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from flask import Flask, request
|
from flask import Flask, request
|
||||||
from . import console
|
|
||||||
|
|
||||||
# 获取插件所在的目录(结尾没有分割符号)
|
# 获取插件所在的目录(结尾没有分割符号)
|
||||||
dir_path = os.path.dirname(__file__).replace("\\", "/")
|
dir_path = os.path.dirname(__file__).replace("\\", "/")
|
||||||
folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称
|
folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称
|
||||||
|
|
||||||
|
|
||||||
def event_init(app: Flask):
|
def event_init(app: Flask):
|
||||||
"""初始化完成时会调用这里"""
|
"""初始化完成时会调用这里"""
|
||||||
# 移除原有的输出日志
|
|
||||||
app.logger = None
|
|
||||||
log = logging.getLogger('werkzeug')
|
|
||||||
log.setLevel(logging.ERROR)
|
|
||||||
|
|
||||||
# 更改IP地址,只有在最新版的flask中才能生效
|
# 更改IP地址,只有在最新版的flask中才能生效
|
||||||
@app.before_request
|
@app.before_request
|
||||||
@@ -23,19 +19,6 @@ def event_init(app: Flask):
|
|||||||
request.remote_addr = get_user_ip(request)
|
request.remote_addr = get_user_ip(request)
|
||||||
|
|
||||||
|
|
||||||
# 使用自定义的日志输出
|
|
||||||
@app.after_request
|
|
||||||
def after_request(rep):
|
|
||||||
if rep.status_code == 200:
|
|
||||||
console.success(f"{request.remote_addr} -- {request.full_path} 200")
|
|
||||||
elif rep.status_code == 404:
|
|
||||||
console.error(f"{request.remote_addr} -- {request.full_path} 404")
|
|
||||||
elif rep.status_code == 500:
|
|
||||||
console.warning(f"{request.remote_addr} -- {request.full_path} 500")
|
|
||||||
else:
|
|
||||||
console.info(f"{request.remote_addr} -- {request.full_path} {rep.status_code}")
|
|
||||||
return rep
|
|
||||||
|
|
||||||
def get_user_ip(request):
|
def get_user_ip(request):
|
||||||
"""获取用户真实IP"""
|
"""获取用户真实IP"""
|
||||||
if 'HTTP_X_FORWARDED_FOR' in request.headers:
|
if 'HTTP_X_FORWARDED_FOR' in request.headers:
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
"""
|
|
||||||
输出控制台日志
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import ctypes
|
|
||||||
|
|
||||||
NONE = "\033[m"
|
|
||||||
RED = "\033[0;32;31m"
|
|
||||||
LIGHT_RED = "\033[1;31m"
|
|
||||||
GREEN = "\033[0;32;32m"
|
|
||||||
LIGHT_GREEN = "\033[1;32m"
|
|
||||||
BLUE = "\033[0;32;34m"
|
|
||||||
LIGHT_BLUE = "\033[1;34m"
|
|
||||||
DARY_GRAY = "\033[1;30m"
|
|
||||||
CYAN = "\033[0;36m"
|
|
||||||
LIGHT_CYAN = "\033[1;36m"
|
|
||||||
PURPLE = "\033[0;35m"
|
|
||||||
LIGHT_PURPLE = "\033[1;35m"
|
|
||||||
BROWN = "\033[0;33m"
|
|
||||||
YELLOW = "\033[1;33m"
|
|
||||||
LIGHT_GRAY = "\033[0;37m"
|
|
||||||
WHITE = "\033[1;37m"
|
|
||||||
|
|
||||||
# 开启 Windows 下对于 ESC控制符 的支持
|
|
||||||
if sys.platform == "win32":
|
|
||||||
kernel32 = ctypes.windll.kernel32
|
|
||||||
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
|
|
||||||
|
|
||||||
|
|
||||||
def _print(level, msg):
|
|
||||||
time_ = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
|
||||||
|
|
||||||
level_name = {10: "Plain",
|
|
||||||
11: "Log",
|
|
||||||
12: "Info",
|
|
||||||
13: "Debug",
|
|
||||||
14: "Success",
|
|
||||||
15: "Warning",
|
|
||||||
16: "Error"}
|
|
||||||
|
|
||||||
color = {10: NONE,
|
|
||||||
11: LIGHT_CYAN,
|
|
||||||
12: LIGHT_BLUE,
|
|
||||||
13: PURPLE,
|
|
||||||
14: GREEN,
|
|
||||||
15: YELLOW,
|
|
||||||
16: RED}
|
|
||||||
|
|
||||||
print(f'{color.get(level, NONE)}[{time_}]({level_name.get(level, "Plain")}):', msg, f"{NONE}")
|
|
||||||
|
|
||||||
|
|
||||||
def plain(*args, sep=' '):
|
|
||||||
msg = sep.join(str(_) for _ in args)
|
|
||||||
_print(10, msg)
|
|
||||||
|
|
||||||
|
|
||||||
def log(*args, sep=' '):
|
|
||||||
msg = sep.join(str(_) for _ in args)
|
|
||||||
_print(11, msg)
|
|
||||||
|
|
||||||
|
|
||||||
def info(*args, sep=' '):
|
|
||||||
msg = sep.join(str(_) for _ in args)
|
|
||||||
_print(12, msg)
|
|
||||||
|
|
||||||
|
|
||||||
def debug(*args, sep=' '):
|
|
||||||
msg = sep.join(str(_) for _ in args)
|
|
||||||
_print(13, msg)
|
|
||||||
|
|
||||||
|
|
||||||
def success(*args, sep=' '):
|
|
||||||
msg = sep.join(str(_) for _ in args)
|
|
||||||
_print(14, msg)
|
|
||||||
|
|
||||||
|
|
||||||
def warn(*args):
|
|
||||||
warning(*args)
|
|
||||||
|
|
||||||
|
|
||||||
def warning(*args, sep=' '):
|
|
||||||
msg = sep.join(str(_) for _ in args)
|
|
||||||
_print(15, msg)
|
|
||||||
|
|
||||||
|
|
||||||
def error(*args, sep=' '):
|
|
||||||
msg = sep.join(str(_) for _ in args)
|
|
||||||
_print(16, msg)
|
|
||||||