diff --git a/README.md b/README.md index ec8c69cc4c65455926239af4638633229e11e2c4..7954902c470866c8f8a115153e1b5dfa941b4b41 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ 开 箱 即 用 的 Flask 快 速 开 发 平 台 - [预 览](http://flask.pearadmin.com) | [官 网](http://www.pearadmin.com/) | [群聊](docs/group.md) | [文档](docs/detail.md) + [预览](http://flask.pearadmin.com) | [官网](http://www.pearadmin.com/) | [群聊](docs/group.md) | [文档](docs/detail.md)

@@ -46,8 +46,48 @@ Pear Admin Flask 基于 Flask 的后台管理系统,拥抱应用广泛的pytho #### 项目结构 +## 应用结构 +```应用结构 +Pear Admin Flask +├─applications # 应用 +│ ├─configs # 配置文件 +│ │ ├─ common.py # 普通配置 +│ │ └─ config.py # 配置文件对象 +│ ├─extensions # 注册插件 +│ ├─models # 数据模型 +│ ├─static # 静态资源文件 +│ ├─templates # 静态模板文件 +│ └─views # 视图部分 +│ ├─admin # 后台管理视图模块 +│ └─index # 前台视图模块 +├─docs # 文档说明 +├─migrations # 迁移文件记录 +├─requirement # 依赖文件 +└─.env # 项目的配置文件 ``` +## 资源结构 +```资源结构 +Pear Admin Flask +├─static # 项目设定的 Flask 资源文件夹 +│ ├─admin # pear admin flask 的后端资源文件(与 pear admin layui 同步) +│ ├─index # pear admin flask 的前端资源文件 +│ └─upload # 用户上传保存目录 +└─templates # 项目设定的 Flask 模板文件夹 + ├─admin # pear admin flask 的后端管理页面模板 + │ ├─admin_log # 日志页面 + │ ├─common # 基本模板页面(头部模板与页脚模板) + │ ├─console # 系统监控页面模板 + │ ├─dept # 部门管理页面模板 + │ ├─dict # 数据自动页面模板 + │ ├─mail # 邮件管理页面模板 + │ ├─photo # 图片上传页面模板 + │ ├─power # 权限(菜单)管理页面模板 + │ ├─role # 角色管理页面模板 + │ ├─task # 任务设置页面模板 + │ └─user # 用户管理页面模板 + ├─errors # 错误页面模板 + └─index # 主页模板 ``` #### 项目安装 diff --git a/applications/common/script/admin.py b/applications/common/script/admin.py index fec84ab203e565335bad8cc287fdeb4d93c24eac..b49920dc363077c7b9e9d3db6efcd8654def9ea8 100644 --- a/applications/common/script/admin.py +++ b/applications/common/script/admin.py @@ -602,58 +602,6 @@ powerdata = [ create_time=now_time, enable=1, - ), Power( - id=60, - name='拓展插件', - type='0', - code='', - url='', - open_type='', - parent_id='0', - icon='layui-icon layui-icon-senior', - sort=2, - create_time=now_time, - enable=1, - - ), Power( - id=61, - name='插件管理', - type='1', - code='admin:plugin:main', - url='/plugin', - open_type='_iframe', - parent_id='60', - icon='layui-icon layui-icon', - sort=2, - create_time=now_time, - enable=1, - - ), Power( - id=62, - name='启禁插件', - type='2', - code='admin:plugin:enable', - url='', - open_type='', - parent_id='61', - icon='layui-icon layui-icon', - sort=1, - create_time=now_time, - enable=1, - - ), Power( - id=63, - name='删除插件', - type='2', - code='admin:plugin:remove', - url='', - open_type='', - parent_id='61', - icon='layui-icon layui-icon', - sort=2, - create_time=now_time, - enable=1, - ) ] diff --git a/applications/config.py b/applications/config.py index 64959863dbb7639e7d886b752edd5124301374cc..15b278149117190f55bac46f229ab3708a00020e 100644 --- a/applications/config.py +++ b/applications/config.py @@ -80,8 +80,8 @@ class BaseConfig: 'max_instances': 3 } - # 插件配置 - PLUGIN_ENABLE_FOLDERS = ["helloworld"] + # 插件配置,填写插件的文件名名称,默认不启用插件。 + PLUGIN_ENABLE_FOLDERS = [] # 配置多个数据库连接的连接串写法示例 # HOSTNAME: 指数据库的IP地址、USERNAME:指数据库登录的用户名、PASSWORD:指数据库登录密码、PORT:指数据库开放的端口、DATABASE:指需要连接的数据库名称 diff --git a/applications/view/__init__.py b/applications/view/__init__.py index ef00903e369c0c1ac26ac2ce7651505f0c095eed..6b5cf78b6fe07f81df34aeba4829e5a1639a64d5 100644 --- a/applications/view/__init__.py +++ b/applications/view/__init__.py @@ -11,4 +11,4 @@ def init_view(app): register_rights_view(app) register_passport_views(app) register_dept_views(app) - # register_plugin_views(app) + register_plugin_views(app) diff --git a/applications/view/plugin/__init__.py b/applications/view/plugin/__init__.py index 4eb6c34d8dd8729e4fee5c9c341835cfa5c7ebeb..ec94ef5a48701b2da9cd308828d402de98b6dc36 100644 --- a/applications/view/plugin/__init__.py +++ b/applications/view/plugin/__init__.py @@ -18,7 +18,7 @@ def register_plugin_views(app: Flask): app.register_blueprint(plugin_bp) # 载入插件过程 # plugin_folder 配置的是插件的文件夹名 - PLUGIN_ENABLE_FOLDERS = json.loads(app.config['PLUGIN_ENABLE_FOLDERS']) + PLUGIN_ENABLE_FOLDERS = app.config['PLUGIN_ENABLE_FOLDERS'] for plugin_folder in PLUGIN_ENABLE_FOLDERS: plugin_info = {} try: @@ -39,124 +39,3 @@ def register_plugin_views(app: Flask): info += 'repr(e):\t' + repr(e) + "\n" info += 'traceback.format_exc():\n%s' + traceback.format_exc() print(info) - - -@plugin_bp.get('/') -@authorize("admin:plugin:main", log=True) -def main(): - """此处渲染管理模板""" - return render_template('admin/plugin/main.html') - - -@plugin_bp.get('/data') -@authorize("admin:plugin:main", log=True) -def data(): - """请求插件数据""" - plugin_name = escape(request.args.get("plugin_name")) - all_plugins = [] - count = 0 - for filename in os.listdir("plugins"): - try: - with open("plugins/" + filename + "/__init__.json", "r", encoding='utf-8') as f: - info = json.loads(f.read()) - - if plugin_name is None: - if info['plugin_name'].find(plugin_name) == -1: - continue - - all_plugins.append( - { - "plugin_name": info["plugin_name"], - "plugin_version": info["plugin_version"], - "plugin_description": info["plugin_description"], - "plugin_folder_name": filename, - "enable": "1" if filename in PLUGIN_ENABLE_FOLDERS else "0" - } - ) - count += 1 - except BaseException as error: - print(filename, error) - continue - return table_api(data=all_plugins, count=count) - - -@plugin_bp.put('/enable') -@authorize("admin:plugin:enable", log=True) -def enable(): - """启用插件""" - plugin_folder_name = request.get_json(force=True).get('plugin_folder_name') - if plugin_folder_name: - try: - if plugin_folder_name not in PLUGIN_ENABLE_FOLDERS: - PLUGIN_ENABLE_FOLDERS.append(plugin_folder_name) - with open(".flaskenv", "r", encoding='utf-8') as f: - flaskenv = f.read() # type: str - pos1 = flaskenv.find("PLUGIN_ENABLE_FOLDERS") - pos2 = flaskenv.find("\n", pos1) - with open(".flaskenv", "w", encoding='utf-8') as f: - if pos2 == -1: - f.write(flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS)) - else: - f.write( - flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS) + flaskenv[ - pos2:]) - # 启用插件事件 - try: - getattr(importlib.import_module('plugins.' + plugin_folder_name), "event_enable")() - except AttributeError: # 没有插件启用事件就不调用 - pass - except BaseException as error: - return fail_api(msg="Crash a error! Info: " + str(error)) - - except BaseException as error: - return fail_api(msg="Crash a error! Info: " + str(error)) - return success_api(msg="启用成功,要使修改生效需要重启程序。") - return fail_api(msg="数据错误") - - -@plugin_bp.put('/disable') -@authorize("admin:plugin:enable", log=True) -def disable(): - """禁用插件""" - plugin_folder_name = request.get_json(force=True).get('plugin_folder_name') - if plugin_folder_name: - try: - if plugin_folder_name in PLUGIN_ENABLE_FOLDERS: - PLUGIN_ENABLE_FOLDERS.remove(plugin_folder_name) - with open(".flaskenv", "r", encoding='utf-8') as f: - flaskenv = f.read() # type: str - pos1 = flaskenv.find("PLUGIN_ENABLE_FOLDERS") - pos2 = flaskenv.find("\n", pos1) - with open(".flaskenv", "w", encoding='utf-8') as f: - if pos2 == -1: - f.write(flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS)) - else: - f.write( - flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS) + flaskenv[ - pos2:]) - - # 禁用插件事件 - try: - getattr(importlib.import_module('plugins.' + plugin_folder_name), "event_disable")() - except AttributeError: # 没有插件禁用事件就不调用 - pass - except BaseException as error: - return fail_api(msg="Crash a error! Info: " + str(error)) - - except BaseException as error: - return fail_api(msg="Crash a error! Info: " + str(error)) - return success_api(msg="禁用成功,要使修改生效需要重启程序。") - return fail_api(msg="数据错误") - - -# 删除 -@plugin_bp.delete('/remove/') -@authorize("admin:mail:remove", log=True) -def delete(plugin_folder_name): - if plugin_folder_name in PLUGIN_ENABLE_FOLDERS: - return fail_api(msg="您必须先禁用插件!") - try: - shutil.rmtree(os.path.abspath("plugins/" + plugin_folder_name)) - return success_api(msg="删除成功") - except BaseException as error: - return fail_api(msg="删除失败!原因:" + str(error)) diff --git a/docs/assets/2.jpg b/docs/assets/2.jpg deleted file mode 100644 index 8673684a57e6a9eeeb52630dbe41f1efb09dc1df..0000000000000000000000000000000000000000 Binary files a/docs/assets/2.jpg and /dev/null differ diff --git a/docs/assets/3.jpg b/docs/assets/3.jpg deleted file mode 100644 index 9491d3e4fc05d6a32e7775ae683ec1fe3b476f63..0000000000000000000000000000000000000000 Binary files a/docs/assets/3.jpg and /dev/null differ diff --git a/docs/assets/4.jpg b/docs/assets/4.jpg deleted file mode 100644 index 3c0aba018ba55fd4b489eda6baf0c8dcfb34749b..0000000000000000000000000000000000000000 Binary files a/docs/assets/4.jpg and /dev/null differ diff --git a/docs/assets/5.jpg b/docs/assets/5.jpg deleted file mode 100644 index 9e644467fa9826c870304292422b024af82a5dfd..0000000000000000000000000000000000000000 Binary files a/docs/assets/5.jpg and /dev/null differ diff --git a/docs/assets/6.jpg b/docs/assets/6.jpg deleted file mode 100644 index 98a81ef51f3a6b1bdba43b0f01566a5de498c786..0000000000000000000000000000000000000000 Binary files a/docs/assets/6.jpg and /dev/null differ diff --git a/docs/assets/1.jpg "b/docs/assets/\345\256\230\347\275\221\345\234\260\345\235\200.jpg" similarity index 100% rename from docs/assets/1.jpg rename to "docs/assets/\345\256\230\347\275\221\345\234\260\345\235\200.jpg" diff --git "a/docs/assets/\346\272\220\347\240\201\344\273\223\345\272\223.jpg" "b/docs/assets/\346\272\220\347\240\201\344\273\223\345\272\223.jpg" new file mode 100644 index 0000000000000000000000000000000000000000..6d06c37f0988fd6b19730e271e6e6d3d97b95466 Binary files /dev/null and "b/docs/assets/\346\272\220\347\240\201\344\273\223\345\272\223.jpg" differ diff --git "a/docs/assets/\347\225\214\351\235\242\346\274\224\347\244\272.jpeg" "b/docs/assets/\347\225\214\351\235\242\346\274\224\347\244\272.jpeg" new file mode 100644 index 0000000000000000000000000000000000000000..f66f1b186c0154d6a592a0784c401f4a4be010a8 Binary files /dev/null and "b/docs/assets/\347\225\214\351\235\242\346\274\224\347\244\272.jpeg" differ diff --git a/docs/detail.md b/docs/detail.md index 5710ca2b217b445f86a9db1d5b737ae9888fd4f9..7d603987ed0fcd5649099ec4f99fa8e30a9d22ad 100644 --- a/docs/detail.md +++ b/docs/detail.md @@ -1,140 +1,44 @@ -### 权限管理 :id=authorize +## 项目介绍 :id=start -使用装饰器 @authorize时需要注意,该装饰器需要写在 @app.route 之后 +欢迎阅读 Pear Admin Flask 的开发文档!Pear Admin Flask 是一个基于 Flask 的后台管理系统,拥抱应用广泛的 Python 语言,通过使用本系统,即可快速构建你的功能业务。 -```python -@authorize(power: str, log: bool) -``` +项目旨在为 Python 开发者提供一个后台管理系统的模板,成为您构建信息管理系统、物联网后台等应用时灵活、简单的工具。 -第一个参数为权限 code +同时,Pear Admin Flask 项目也是一个对于 Python 初学者友好的项目。此项目处于开发初期,欢迎各位 Python 爱好者加入到 Pear Admin Flask 项目建设中来。如果您在使用此项目的过程中,发现项目代码存在问题,请在 Gitee 上提交 PR ,一起开源共建! -第二个参数为是否生成日志 +接下来,我们将会为您详细介绍该项目搭建方法与开发架构。 -```python -# 例如 -@authorize("admin:power:remove", log=True) -``` +> 如果对项目有不理解的地方欢迎加入我们的讨论群。 -在前端中,例如增加,删除按钮,对于没有编辑权限的用户不显示的话,可以使用 +![QQ群](assets/qqgroup.jpg) - `{% **if** authorize("admin:user:edit") %}` +> 微信群不定期在qq群更新二维码。 - `{% endif %}` +![开始使用](assets/界面演示.jpeg) -例如 +**[master分支版本](https://gitee.com/pear-admin/pear-admin-flask/tree/master/)** -```python - {% if authorize("admin:user:edit") %} - - {% endif %} - {% if authorize("admin:user:remove") %} - - {% endif %} -``` +flask 2.0.1 + flask-sqlalchemy + 权限验证 + Flask-APScheduler 定时任务 + marshmallow 序列化与数据验证 -## model序列化 :id=Schema +master 分支为主分支,是功能最全、页面最多的分支。 -- sqlalchemy查询的model对象转dict +![开始使用](assets/界面演示.jpeg) - -``` - model_to_dicts(Schema, model) -``` +## 下载使用 :id=download -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() -``` +#### 1. 官网地址 ->这一部分有问题的话请看marshmallow文档 +官网提供稳定版本的 Release 发行版本 [前往](http://www.pearadmin.com) -model写的是查询后的对象 +![官方网址](assets/官网地址.jpg) -```python -dept = Dept.query.order_by(Dept.sort).all() -``` +#### 2. 源码仓库 -进行序列化 +如果你需要最新代码,请前往 Gitee 仓库 [前往](https://gitee.com/pear-admin/pear-admin-flask) -```python -res = model_to_dicts(Schema=DeptSchema, model=dept) -``` +![源码仓库](assets/源码仓库.jpg) -## 构造查询过滤 -```python -# 准确查询字段 -# 不等于查询字段 -# 大于查询字段 -# 小于查询字段 -# 模糊查询字段(%+xxx+%) -# 左模糊 (% + xxx) -# 右模糊查询字段(xxx+ %) -# 包含查询字段 -#范围查询字段 -# 查询 -``` +如果您完成了这一步,请参阅[下载安装](install.md)章节。 -## xss过滤 - -```python -from applications.common.utils.validate import str_escape -details = str_escape(req.get("details")) -``` - - - -## 邮件发送 - -```python -#在.flaskenv中配置邮箱 - -from applications/common/utils/mail import send_main -send_mail(subject='title', recipients=['123@qq.com'], content='body') -``` - - - -## 返回格式 - -``` -from applications/common/utils/http import success_api,fail_api,table_api - -# 这是源代码 -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) -``` \ No newline at end of file diff --git a/docs/function.md b/docs/function.md new file mode 100644 index 0000000000000000000000000000000000000000..3d1db1c3b592a522d9d1f405eecff7941f5d79bc --- /dev/null +++ b/docs/function.md @@ -0,0 +1,279 @@ +## 用户权限判断 + +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("admin:power:remove", log=True) +def test_index(): + return 'You are allowed.' +``` + +> 使用装饰器 @authorize时需要注意,该装饰器需要写在 @app.route之后 + ++ 前端用法 + +在前端中,例如增加,删除按钮,对于没有编辑权限的用户不显示的话,可以使用 + + `{% **if** authorize("admin:user:edit") %}` + + `{% endif %}` + +例如 + +```python + {% if authorize("admin:user:edit") %} + + {% endif %} + {% if authorize("admin:user:remove") %} + + {% 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", "

Hello

") +``` + ++ 基于二次开发的邮件发送函数 + +### 函数原型 + +函数调用位于项目代码 ```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", "

Hello

", 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("admin: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("admin:power:add", log=True) +def save(): + ... # 若干操作 + if success: + return success_api(msg="成功") + return fail_api(msg="成功") +``` diff --git a/docs/group.md b/docs/group.md deleted file mode 100644 index 0da9fe9cb001905e4914ba30ba1bb095b0171858..0000000000000000000000000000000000000000 --- a/docs/group.md +++ /dev/null @@ -1,2 +0,0 @@ -![qq群](assets%2Fqqgroup.jpg) -微信群不定期在qq群更新二维码 \ No newline at end of file diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000000000000000000000000000000000000..7b54e72a0a8f1f17f325d7c741f502c78dd98258 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,113 @@ +### 环境要求 :id=install +- Python >= 3.6 +- Mysql >= 5.7.0 + +### 安装配置 + +#### 克隆远程仓库 + +您可以使用 git 来克隆远程仓库: + +```shell +# 进入项目主目录 +cd Pear Admin Flask + +# 使用 git 克隆远程仓库 +git clone https://gitee.com/pear-admin/pear-admin-flask.git + +# 切换分支 +git checkout master # master, main or mini +``` + +或者直接前往 Pear Admin Flask 项目的[Gitee 主页](https://gitee.com/pear-admin/pear-admin-flask)下载项目仓库。 + +#### 搭建开发环境 + +我们推荐使用 Python 的虚拟环境来开发该项目,这样便于项目的迁移与二次开发。当然,您也可以选择使用原 Python 环境。 + +如果你想创建 Python 虚拟环境,你可以使用下面的命令行: + +```shell +# 在当前目录的venv文件夹创建虚拟环境 +python -m venv venv + +# Windows 激活虚拟环境 +.\test_env\Scripts\Activate.ps1 + +# Linux 和 Mac 激活虚拟环境 +source ./test_env/bin/activate +``` + +**如果在创建虚拟环境时报错 “ModuleNotFoundError” ,这说明您的 Python 版本小于 3.3 。** + +#### 安装项目依赖 + +```shell +# 使用 pip 安装必要模块(对于 master 分支) +pip install -r requirement\requirement.txt + +# 使用 pip 安装必要模块(对于 mini 分支) +pip install -r requirement.txt +``` + +或者您可以尝试: + +```shell +# 使用 pip 安装必要模块 +python -m pip install -r requirement.txt +``` + +#### 配置数据库 + +在 `applications/config.py` 中配置好数据库。 + +> 由于结构与目录调整,我们简化了项目结构,废弃了文件 ```.flaskenv``` ,请使用 config.py 配置程序。 + +并使用以下命令生成数据库文件: + +```shell +# 初始化数据库 +flask db init +flask db migrate +flask db upgrade +flask admin init +``` + +#### 运行项目 + +```shell +python app.py + +# 或者可以使用 +flask run +``` + +#### 使用docker-compose运行项目 + +```shell +# 安装docker-compose +curl -L https://github.com/docker/compose/releases/download/1.26.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose +chmod +x /usr/local/bin/docker-compose +ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose + +docker-compose --version +docker-compose up -d # -d后台运行 +docker-compose stop # 停止启动 +docker-compose down # 清除容器 + +dockerdata/config.py # 配置文件 +dockerdata/mysql/initdb/ # MySQL初始化数据在 +rm -rf dockerdata/mysql/{log,data}/* # down掉容器后启动需要清除删除log,dat +``` + +### 二次开发 + +恭喜!现在您已经成功搭建并运行了 Pear Admin Flask ,是时候参与开发了: + +请阅读: + ++ Pear Admin Flask [目录结构](list.md) 章节 ++ Pear Admin Flask [开发函数](function.md) 章节 ++ Pear Admin Flask [插件开发](plugin.md) 章节 + +其它章节等待更新。 diff --git a/docs/list.md b/docs/list.md new file mode 100644 index 0000000000000000000000000000000000000000..891fa730731909584abb523fc463fda1268bf095 --- /dev/null +++ b/docs/list.md @@ -0,0 +1,43 @@ +## 应用结构 :id=config +```应用结构 +Pear Admin Flask +├─applications # 应用 +│ ├─configs # 配置文件 +│ │ ├─ common.py # 普通配置 +│ │ └─ config.py # 配置文件对象 +│ ├─extensions # 注册插件 +│ ├─models # 数据模型 +│ ├─static # 静态资源文件 +│ ├─templates # 静态模板文件 +│ └─views # 视图部分 +│ ├─admin # 后台管理视图模块 +│ └─index # 前台视图模块 +├─docs # 文档说明 +├─migrations # 迁移文件记录 +├─requirement # 依赖文件 +└─.env # 项目的配置文件 +``` + +## 资源结构 :id=static +```资源结构 +Pear Admin Flask +├─static # 项目设定的 Flask 资源文件夹 +│ ├─admin # pear admin flask 的后端资源文件(与 pear admin layui 同步) +│ ├─index # pear admin flask 的前端资源文件 +│ └─upload # 用户上传保存目录 +└─templates # 项目设定的 Flask 模板文件夹 + ├─admin # pear admin flask 的后端管理页面模板 + │ ├─admin_log # 日志页面 + │ ├─common # 基本模板页面(头部模板与页脚模板) + │ ├─console # 系统监控页面模板 + │ ├─dept # 部门管理页面模板 + │ ├─dict # 数据自动页面模板 + │ ├─mail # 邮件管理页面模板 + │ ├─photo # 图片上传页面模板 + │ ├─power # 权限(菜单)管理页面模板 + │ ├─role # 角色管理页面模板 + │ ├─task # 任务设置页面模板 + │ └─user # 用户管理页面模板 + ├─errors # 错误页面模板 + └─index # 主页模板 +``` \ No newline at end of file diff --git a/docs/plugin.md b/docs/plugin.md new file mode 100644 index 0000000000000000000000000000000000000000..9ccb1d479995e76f965e27e20ed4a1c1402432e6 --- /dev/null +++ b/docs/plugin.md @@ -0,0 +1,47 @@ +### 说明 + +插件功能旨在最大限度不修改原框架的前提下添加新功能,我们提供了三个示例插件。 + + +### 插件配置 + +将插件文件夹放置在 ```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("加载完毕后,我会输出一句话。") +``` \ No newline at end of file diff --git a/plugins/helloworld/__init__.py b/plugins/helloworld/__init__.py index efbe8aef6ebd3855f422ec6d0b3935131e3f3173..d6b218ea575edceb9d88aa2ee42363cfb2940455 100644 --- a/plugins/helloworld/__init__.py +++ b/plugins/helloworld/__init__.py @@ -10,14 +10,6 @@ from .main import helloworld_blueprint dir_path = os.path.dirname(__file__).replace("\\", "/") folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称 -def event_enable(): - """当此插件被启用时会调用此处""" - print(f"启用插件,dir_path: {dir_path} ; folder_name: {folder_name}") - -def event_disable(): - """当此插件被禁用时会调用此处""" - print(f"禁用插件,dir_path: {dir_path} ; folder_name: {folder_name}") - def event_init(app: Flask): """初始化完成时会调用这里""" app.register_blueprint(helloworld_blueprint) \ No newline at end of file diff --git a/plugins/realip/__init__.py b/plugins/realip/__init__.py index f6971fd981b0dc6f1a2cd8ad46a25bf09da1caf3..50e18b4fdaa71c5d11466c1c97c6bb59c1cfa35f 100644 --- a/plugins/realip/__init__.py +++ b/plugins/realip/__init__.py @@ -10,15 +10,6 @@ from . import console dir_path = os.path.dirname(__file__).replace("\\", "/") folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称 -def event_enable(): - """当此插件被启用时会调用此处""" - print(f"启用插件,dir_path: {dir_path} ; folder_name: {folder_name}") - - -def event_disable(): - """当此插件被禁用时会调用此处""" - print(f"禁用插件,dir_path: {dir_path} ; folder_name: {folder_name}") - def event_init(app: Flask): """初始化完成时会调用这里""" # 移除原有的输出日志 diff --git a/plugins/replacePage/__init__.py b/plugins/replacePage/__init__.py index 3756f0a66c66b50c9d6dfc0276bace98854bfdba..6e4cd7e40952ddd4c6610b6ac28b33ba36d1ce0a 100644 --- a/plugins/replacePage/__init__.py +++ b/plugins/replacePage/__init__.py @@ -8,15 +8,6 @@ from flask import Flask, render_template_string dir_path = os.path.dirname(__file__).replace("\\", "/") folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称 -def event_enable(): - """当此插件被启用时会调用此处""" - print(f"启用插件,dir_path: {dir_path} ; folder_name: {folder_name}") - - -def event_disable(): - """当此插件被禁用时会调用此处""" - print(f"禁用插件,dir_path: {dir_path} ; folder_name: {folder_name}") - def event_init(app: Flask): """初始化完成时会调用这里""" # 使用下面的代码 查看所有注册的视图函数。对于 Flask app.route 函数的实现,请参考 https://www.jianshu.com/p/dff3bc2f4836 diff --git a/templates/admin/plugin/main.html b/templates/admin/plugin/main.html deleted file mode 100644 index b0e4f181ff2731fb97f0a58851f571e121676162..0000000000000000000000000000000000000000 --- a/templates/admin/plugin/main.html +++ /dev/null @@ -1,288 +0,0 @@ - - - - 插件管理 - {% include 'admin/common/header.html' %} - - - -{# 查询表单 #} -
-
-
-
- -
- -
- - -
-
-
-
-{# 用户表格 #} -
-
-
-
-
-
-
- -{# 表格操作 #} - - -{# 用户修改操作 #} - - -{# 启动与禁用 #} - - -{# 用户注册时间 #} - - -{% include 'admin/common/footer.html' %} - - - \ No newline at end of file