7. admin后台站点配置
目录
flask-Admin构建和配置后台运营站点
Flask-Admin
文档:https://flask-admin.readthedocs.io/en/latest/
安装
pip install flask-admin
模块初始化
- 项目主文件:
application/__init__.py
,代码:
import os
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis
from flask_session import Session
from flask_jsonrpc import JSONRPC
from faker import Faker
from celery import Celery
from flask_jwt_extended import JWTManager
from flask_admin import Admin
from application.utils.config import init_config
from application.utils.logger import Log
from application.utils.commands import load_commands
from application.utils.bluerprint import register_blueprint, path, include, api_rpc
from application.utils import message, code
from application.utils.unittest import BasicTestCase
from application.utils.OssStore import OssStore
# 终端脚本工具初始化
manager = Manager()
# SQLAlchemy初始化
db = SQLAlchemy()
# redis数据库初始化
# - 1.默认缓存数据库对象,配置前缀为REDIS
redis_cache = FlaskRedis(config_prefix='REDIS')
# - 2.验证相关数据库对象,配置前缀为CHECK
redis_check = FlaskRedis(config_prefix='CHECK')
# - 3.验证相关数据库对象,配置前缀为SESSION
redis_session = FlaskRedis(config_prefix='SESSION')
# session储存配置初始化
session_store = Session()
# 自定义日志初始化
logger = Log()
# 初始化jsonrpc模块
jsonrpc = JSONRPC()
# 初始化随机生成数据模块faker
faker = Faker(locale='zh-CN') # 指定中文
# 初始化异步celery
celery = Celery()
# jwt认证模块初始化
jwt = JWTManager()
# 阿里云对象存储oss初始化
oss = OssStore()
# admin后台站点初始化
admin = Admin()
# 全局初始化
def init_app(config_path):
"""全局初始化 - 需要传入加载开发或生产环境配置路径"""
# 创建app应用对象
app = Flask(__name__)
# 当前项目根目录
app.BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 开发或生产环境加载配置
init_config(app, config_path)
# SQLAlchemy加载配置
db.init_app(app)
# redis加载配置
redis_cache.init_app(app)
redis_check.init_app(app)
redis_session.init_app(app)
"""一定先加载默认配置,再传入APP加载session对象"""
# session保存数据到redis时启用的链接对象
app.config["SESSION_REDIS"] = redis_session
# session存储对象加载配置
session_store.init_app(app)
# 为日志对象加载配置
log = logger.init_app(app)
app.log = log
# json-rpc加载配置
jsonrpc.init_app(app)
# rpc访问路径入口(只有唯一一个访问路径入口),默认/api
jsonrpc.service_url = app.config.get('JSON_SERVER_URL', '/api')
jsonrpc.enable_web_browsable_api = app.config.get("ENABLE_WEB_BROWSABLE_API",False)
app.jsonrpc = jsonrpc
# 自动注册蓝图
register_blueprint(app)
# 加载celery配置
celery.main = app.name
celery.app = app
# 更新配置
celery.conf.update(app.config)
# 自动注册任务
celery.autodiscover_tasks(app.config.get('INSTALL_BLUEPRINT'))
# jwt认证加载配置
jwt.init_app(app)
# faker作为app成员属性
app.faker = faker
# 注册模型,创建表
with app.app_context():
db.create_all()
# 阿里云存储对象加载配置
oss.init_app(app)
# admin后台站点加载配置
admin.name = app.config.get('FLASK_ADMIN_NAME') # 站点名称
admin.template_mode = app.config.get('FLASK_TEMPLATE_MODE') # 使用的模板
admin.init_app(app)
# 终端脚本工具加载配置
manager.app = app
# 自动注册自定义命令
load_commands(manager)
return manager
- 项目开发配置文件
settings/dev.py
,代码:
"""admin后台站点配置"""
FLASK_ADMIN_NAME = '魔方APP' # 后台站点名称
FLASK_TEMPLATE_MODE = 'bootstrap4' # 模板(2,3,4
- 测试代码,
我们可以在users的任意一个文件中,编写一段官方提供的基于模型生成后台视图功能的代码,这里我们先写在users/urls.py
里面。
from application import path, api_rpc
# 引入当前蓝图应用视图 , 引入rpc视图
from . import views, api
# 蓝图路径与函数映射列表
urlpatterns = []
# rpc方法与函数映射列表[rpc接口列表]
apipatterns = [
api_rpc('check_mobile', api.check_mobile),
api_rpc('register', api.register),
api_rpc('login', api.login),
api_rpc('access', api.access_token),
api_rpc('refresh', api.refresh_token), # 刷新access_token值
api_rpc('update_avatar', api.update_avatar), # 更新头像
api_rpc('update_nickname', api.update_nickname), # 更新昵称
api_rpc('update_mobile', api.update_mobile), # 更新手机号
]
from application import admin, db
from flask_admin.contrib.sqla import ModelView
from .models import User # 后台站点模型类
admin.add_view(ModelView(User, db.session, name='用户')) # 把模型类添加到后台站点
运行项目, 访问后台站点 ; http://0.0.0.0:5000/admin/
模块配置
因为后台站点的相关代码肯定很多,页面也多,所以我们把每一个蓝图下关于admin站点相关配置的代码编写在每一个蓝图下的admin.py
中,并且在蓝图初始化中进行自动加载.
- 蓝图初始化自动加载admin站点配置
application/utils/blueprint.py
,代码:
from flask import Blueprint
# 引入导包函数
from importlib import import_module
# 自动注册蓝图
def register_blueprint(app):
# 从配置文件中读取总路由路径信息
app_urls_path = app.config.get('URL_PATH')
# 导包,导入总路由模块
app_urls_module = import_module(app_urls_path)
# 获取总路由列表
app_urlpatterns = app_urls_module.urlpatterns
# 从配置文件中读取需要注册到项目中的蓝图路径信息
blueprint_path_list = app.config.get('INSTALL_BLUEPRINT')
# 遍历蓝图路径列表,对每一个蓝图进行初始化
for blueprint_path in blueprint_path_list:
# 获取蓝图路径中最后一段的包名作为蓝图的名称
blueprint_name = blueprint_path.split('.')[-1]
# 创建蓝图对象
blueprint = Blueprint(blueprint_name, blueprint_path)
# 蓝图路由的前缀
url_prefix = ""
# 蓝图下的子路由列表
urlpatterns = []
# 获取蓝图的父级目录, 即application/apps
blueprint_father_path = ".".join( blueprint_path.split(".")[:-1] )
# 循环总路由列表
for item in app_urlpatterns:
# 判断当前蓝图是否在总路由列表中
if blueprint_name in item["blueprint_url_subfix"]:
# 导入蓝图下的子路由模块
urls_module = import_module(blueprint_father_path + "." + item["blueprint_url_subfix"])
# 获取urls模块下蓝图路由列表urlpatterns
urlpatterns = [] # 防止下边调用循环报错
try:
urlpatterns = urls_module.urlpatterns
except Exception:
pass
# 提取蓝图路由的前缀
url_prefix = item["url_prefix"]
# 获取urls模块下rpc接口列表apipatterns
apipatterns = [] # 防止下边调用循环报错
try:
apipatterns = urls_module.apipatterns
except Exception:
pass
# 从总路由中查到当前蓝图对象的前缀就不要继续往下遍历了
break
# 注册蓝图路由
for url in urlpatterns:
blueprint.add_url_rule(**url)
# 注册rpc接口
api_prefix_name = ''
# 判断是否开启补充api前缀
if app.config.get('JSON_PREFIX_NAME', False) == True:
api_prefix_name = blueprint_name.title() + '.'
# 循环rpc接口列表,进行注册
for api in apipatterns:
app.jsonrpc.site.register(api_prefix_name + api['name'], api['method'])
# 导入蓝图模型
import_module(blueprint_path + '.models')
try:
# 导入蓝图下的admin站点配置
import_module(blueprint_path + '.admin')
except ModuleNotFoundError:
pass
# 把蓝图对象注册到app实例对象,并添加路由前缀
app.register_blueprint(blueprint, url_prefix=url_prefix)
# 把url地址和视图方法映射关系处理成字典
def path(rule, view_func, **kwargs):
"""绑定url地址和视图的映射关系"""
data = {
'rule':rule,
'view_func':view_func,
**kwargs
}
return data
# 把rpc方法名和视图的映射关系处理成字典
def api_rpc(name, method, **kwargs):
"""
:param name: rpc方法名
:param method: 视图名称
:param kwargs: 其他参数。。
:return:
"""
data = {
'name':name,
'method':method,
**kwargs
}
return data
# 路由前缀和蓝图进行绑定映射
def include(url_prefix, blueprint_url_subfix):
"""
:param url_prefix: 路由前缀
:param blueprint_url_subfix: 蓝图路由,
格式:蓝图包名.路由模块名
例如:蓝图目录是home, 路由模块名是urls,则参数:home.urls
:return:
"""
data = {
"url_prefix": url_prefix,
"blueprint_url_subfix": blueprint_url_subfix
}
return data
- 把上面编写在
users.urls
中的admin站点配置代码,转义到users/admin.py
中,并重新启动项目。
from application import admin, db
from flask_admin.contrib.sqla import ModelView
from .models import User # 后台站点模型类
admin.add_view(ModelView(User, db.session, name='用户')) # 把模型类添加到后台站点
快速使用
修改和配置admin站点的默认首页
- 在蓝图初始化设置默认首页主体模板.
application/utils/blueprint.py
,代码:
from flask import Blueprint
# 引入导包函数
from importlib import import_module
# 自动注册蓝图
def register_blueprint(app):
"""
自动注册蓝图
:param app: 当前flask的app实例对象
:return:
"""
# 从配置文件中读取总路由路径信息
app_urls_path = app.config.get('URL_PATH')
# 导包,导入总路由模块
app_urls_module = import_module(app_urls_path)
# 获取总路由列表
app_urlpatterns = app_urls_module.urlpatterns
# 从配置文件中读取需要注册到项目中的蓝图路径信息
blueprint_path_list = app.config.get('INSTALL_BLUEPRINT')
# 遍历蓝图路径列表,对每一个蓝图进行初始化
for blueprint_path in blueprint_path_list:
# 获取蓝图路径中最后一段的包名作为蓝图的名称
blueprint_name = blueprint_path.split('.')[-1]
# 创建蓝图对象
blueprint = Blueprint(blueprint_name, blueprint_path)
# 蓝图路由的前缀
url_prefix = ""
# 蓝图下的子路由列表
urlpatterns = []
# 获取蓝图的父级目录, 即application/apps
blueprint_father_path = ".".join( blueprint_path.split(".")[:-1] )
# 循环总路由列表
for item in app_urlpatterns:
# 判断当前蓝图是否在总路由列表中
if blueprint_name in item["blueprint_url_subfix"]:
# 导入蓝图下的子路由模块
urls_module = import_module(blueprint_father_path + "." + item["blueprint_url_subfix"])
# 获取urls模块下蓝图路由列表urlpatterns
urlpatterns = [] # 防止下边调用循环报错
try:
urlpatterns = urls_module.urlpatterns
except Exception:
pass
# 提取蓝图路由的前缀
url_prefix = item["url_prefix"]
# 获取urls模块下rpc接口列表apipatterns
apipatterns = [] # 防止下边调用循环报错
try:
apipatterns = urls_module.apipatterns
except Exception:
pass
# 从总路由中查到当前蓝图对象的前缀就不要继续往下遍历了
break
# 注册蓝图路由
for url in urlpatterns:
blueprint.add_url_rule(**url)
# 注册rpc接口
api_prefix_name = ''
# 判断是否开启补充api前缀
if app.config.get('JSON_PREFIX_NAME', False) == True:
api_prefix_name = blueprint_name.title() + '.'
# 循环rpc接口列表,进行注册
for api in apipatterns:
app.jsonrpc.site.register(api_prefix_name + api['name'], api['method'])
# 导入蓝图模型
import_module(blueprint_path + '.models')
try:
# 导入蓝图下的admin站点配置
import_module(blueprint_path + '.admin')
except ModuleNotFoundError:
pass
# 把蓝图对象注册到app实例对象,并添加路由前缀
app.register_blueprint(blueprint, url_prefix=url_prefix)
# 后台站点首页相关初始化[这段代码在循环蓝图完成以后,在循环体之外]
from flask_admin import AdminIndexView # admin主视图
from application import admin
admin._set_admin_index_view(index_view=AdminIndexView(
name = app.config.get('ADMIN_HOME_NAME'), # 默认首页名称
template= app.config.get('ADMIN_HOME_TEMPLATE') # 默认首页模板路径
))
# 把url地址和视图方法映射关系处理成字典
def path(rule, view_func, **kwargs):
"""绑定url地址和视图的映射关系"""
data = {
'rule':rule,
'view_func':view_func,
**kwargs
}
return data
# 把rpc方法名和视图的映射关系处理成字典
def api_rpc(name, method, **kwargs):
"""
:param name: rpc方法名
:param method: 视图名称
:param kwargs: 其他参数。。
:return:
"""
data = {
'name':name,
'method':method,
**kwargs
}
return data
# 路由前缀和蓝图进行绑定映射
def include(url_prefix, blueprint_url_subfix):
"""
:param url_prefix: 路由前缀
:param blueprint_url_subfix: 蓝图路由,
格式:蓝图包名.路由模块名
例如:蓝图目录是home, 路由模块名是urls,则参数:home.urls
:return:
"""
data = {
"url_prefix": url_prefix,
"blueprint_url_subfix": blueprint_url_subfix
}
return data
- 在开发配置文件中
settings/dev.py
,配置admin站点的默认首页的导航名称和模板文件,代码:
"""admin后台站点配置"""
FLASK_ADMIN_NAME = '魔方APP' # 后台站点名称
FLASK_TEMPLATE_MODE = 'bootstrap4' # 模板(2,3,4)
ADMIN_HOME_NAME = 'Welcome' # 默认首页名称
ADMIN_HOME_TEMPLATE = "admin/index.html" # 默认首页模板
- 在aplication下新增模板目录及文件
application/templates/admin/index.html
{% extends 'admin/master.html' %}
{% block body %}
<h1>admin站点默认首页</h1>
{% endblock %}
运行项目, 直接访问admin站点后台查看效果。
新增自定义导航页面
- 用户导航模板
application/templates/users/index.html
, 代码:
{% extends 'admin/master.html' %}
{% block body %}
<h1>自定义导航页面</h1>
<h1>{{title}}</h1>
<table border="1" align="center">
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
</tr>
{% for user in user_list %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.age }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
- 自定义导航页面
application/apps/users/admin.py
,代码:
# from application import admin, db
# from flask_admin.contrib.sqla import ModelView
# from .models import User # 后台站点模型类
# admin.add_view(ModelView(User, db.session, name='用户')) # 把模型类添加到后台站点
# 自定义导航页面
from flask_admin import BaseView, expose
from application import admin
from .models import User
# 用户模型视图类
class UserAdmin(BaseView):
'''用户模型视图类'''
@expose('/')
def index(self, *args, **kwargs):
data = {}
data['title'] = 'admin站点用户相关的内容'
data['user_list'] = User.query.filter(User.is_deleted == False).all()
return self.render(template='users/index.html', **data)
# 注册导航页面到Admin
admin.add_view(UserAdmin(name='用户', url='users'))
基于模型自动生成导航页面
application/apps/users/admin.py
,代码:
# # 自定义导航页面
# from flask_admin import BaseView, expose
#
# from application import admin
# from .models import User
#
# # 用户模型视图类
# class UserAdmin(BaseView):
# '''用户模型视图类'''
# @expose('/')
# def index(self, *args, **kwargs):
# data = {}
# data['title'] = 'admin站点用户相关的内容'
# data['user_list'] = User.query.filter(User.is_deleted == False).all()
# return self.render(template='users/index.html', **data)
# # 注册导航页面到Admin
# admin.add_view(UserAdmin(name='用户', url='users'))
"""基于模型生成导航页面"""
# # 方式一: 直接通过ModelView来注册User模型到Admin站点中,自动生成增删查改功能
# from application import admin, db
# from flask_admin.contrib.sqla import ModelView
# from .models import User # 后台站点模型类
# admin.add_view(ModelView(User, db.session, name='用户')) # 把模型类添加到后台站点
# 方式二: 灵活配置站点需要的信息,
from application import admin, db
from flask_admin.contrib.sqla import ModelView
from .models import User # 后台站点模型类
# 自定义用户站点类(基于父类ModelView)自动生成增删查改功能
class UserAdminModel(ModelView):
# 显示字段列表
column_list = ['id', 'name', 'nickname', 'age', 'sex']
# # 显示排除字段列表,与column_list互斥,只能有一个
# column_exclude_list = ['is_deleted']
# 字段的文字描述
column_labels = dict(name='账号', nickname='昵称', age='年龄', sex='性别')
# 列表页可以直接编辑的字段列表
column_editable_list = ['name', 'age', 'sex']
# 默认排序字段
column_default_sort = 'id'
# 列表页允许点击排序的字段列表
column_sortable_list = ['id', 'age']
# 是否允许查看详情
can_view_details = True
# 显示直接可以搜索字段数据的列表
column_searchable_list = ['id', 'name', 'age']
# 可以进行滤器的字段列表
column_filters = ['sex', 'age']
# 单页显示数量
page_size = 3
# # 简单版本分页器[上一页< ,下一页>]
# simple_list_pager = True
# 高级版本的分页器(比上面简单的好用)
list_pager = True
# 字段值转换
column_choices = {
'sex':[
(0, '女'),
(1, '男'),
],
'is_deleted':[
(0, '未删除'),
(0, '已删除'),
]
}
# # 列表页模板,默认值:'admin/model/list.html'
# list_template = 'admin/model/custom_list.html'
# 修改模型钩子[添加/更新]
def on_model_change(self,form, model, is_created):
print(form) # 客户端提交的表单数据
print(model) # 本次操作的数据数据
print(self.model) # 本次操作的数据数据
print(is_created) # 本次操作是否属于添加操作,False表示修改
# 金钱不能是负数
if model.money < 0:
model.money = abs( model.money )
# 积分不能是负数
if model.credit < 0:
model.credit = abs( model.credit )
return super().on_model_change(form, model, is_created)
# 删除模型钩子
def on_model_delete(self, model):
# 删除日志的记录
# 其他数据表是否存在外在关联
return super().on_model_delete(model)
def delete_model(self,model):
if model.info:
# # 1.物理删除,慎用
# db.session.delete(model)
# db.session.delete(model.info)
# db.session.commit()
# 2.逻辑删除[修改is_deleted为True即可]
model.is_deleted = True
model.info.is_deleted = True
db.session.commit()
return True
# 把自定义模型类添加到后台站点
admin.add_view(UserAdminModel(User, db.session, name='用户'))
使用外部链接作为导航页
application/apps/users/admin.py
,
"""使用外链作为导航页"""
from flask_admin.menu import MenuLink
admin.add_link(MenuLink(name='老男孩', url='http://www.oldboyedu.com', category='用户管理')) # 把超链接作为子导航加载到顶级导航中
国际化与本地化
flask-babelex
是Flask 的翻译扩展工具,在项目安装配置,可以实现中文显示。方便我们管理admin后台站点。
安装模块
pip install flask-babelex -i https://pypi.douban.com/simple
- 模块初始化
application/__init__.py
,代码:
import os
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis
from flask_session import Session
from flask_jsonrpc import JSONRPC
from faker import Faker
from celery import Celery
from flask_jwt_extended import JWTManager
from flask_admin import Admin
from flask_babelex import Babel
from application.utils.config import init_config
from application.utils.logger import Log
from application.utils.commands import load_commands
from application.utils.bluerprint import register_blueprint, path, include, api_rpc
from application.utils import message, code
from application.utils.unittest import BasicTestCase
from application.utils.OssStore import OssStore
# 终端脚本工具初始化
manager = Manager()
# SQLAlchemy初始化
db = SQLAlchemy()
# redis数据库初始化
# - 1.默认缓存数据库对象,配置前缀为REDIS
redis_cache = FlaskRedis(config_prefix='REDIS')
# - 2.验证相关数据库对象,配置前缀为CHECK
redis_check = FlaskRedis(config_prefix='CHECK')
# - 3.验证相关数据库对象,配置前缀为SESSION
redis_session = FlaskRedis(config_prefix='SESSION')
# session储存配置初始化
session_store = Session()
# 自定义日志初始化
logger = Log()
# 初始化jsonrpc模块
jsonrpc = JSONRPC()
# 初始化随机生成数据模块faker
faker = Faker(locale='zh-CN') # 指定中文
# 初始化异步celery
celery = Celery()
# jwt认证模块初始化
jwt = JWTManager()
# 阿里云对象存储oss初始化
oss = OssStore()
# admin后台站点初始化
admin = Admin()
# 国际化和本地化的初始化
babel = Babel()
# 全局初始化
def init_app(config_path):
"""全局初始化 - 需要传入加载开发或生产环境配置路径"""
# 创建app应用对象
app = Flask(__name__)
# 当前项目根目录
app.BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 开发或生产环境加载配置
init_config(app, config_path)
# SQLAlchemy加载配置
db.init_app(app)
# redis加载配置
redis_cache.init_app(app)
redis_check.init_app(app)
redis_session.init_app(app)
"""一定先加载默认配置,再传入APP加载session对象"""
# session保存数据到redis时启用的链接对象
app.config["SESSION_REDIS"] = redis_session
# session存储对象加载配置
session_store.init_app(app)
# 为日志对象加载配置
log = logger.init_app(app)
app.log = log
# json-rpc加载配置
jsonrpc.init_app(app)
# rpc访问路径入口(只有唯一一个访问路径入口),默认/api
jsonrpc.service_url = app.config.get('JSON_SERVER_URL', '/api')
jsonrpc.enable_web_browsable_api = app.config.get("ENABLE_WEB_BROWSABLE_API",False)
app.jsonrpc = jsonrpc
# 自动注册蓝图
register_blueprint(app)
# 加载celery配置
celery.main = app.name
celery.app = app
# 更新配置
celery.conf.update(app.config)
# 自动注册任务
celery.autodiscover_tasks(app.config.get('INSTALL_BLUEPRINT'))
# jwt认证加载配置
jwt.init_app(app)
# faker作为app成员属性
app.faker = faker
# 注册模型,创建表
with app.app_context():
db.create_all()
# 阿里云存储对象加载配置
oss.init_app(app)
# admin后台站点加载配置
admin.name = app.config.get('FLASK_ADMIN_NAME') # 站点名称
admin.template_mode = app.config.get('FLASK_TEMPLATE_MODE') # 使用的模板
admin.init_app(app)
# 国际化和本地化加载配置
babel.init_app(app)
# 终端脚本工具加载配置
manager.app = app
# 自动注册自定义命令
load_commands(manager)
return manager
- 公共配置设置语言与时区,
application/settings/__init__.py
,代码:
"""公共配置"""
"""调试模式"""
DEBUG = True
"""设置加密秘钥"""
# 设置密钥,可以通过 base64.b64encode(os.urandom(48)) 来生成一个指定长度的随机字符串
SECRET_KEY = "RvDsY4u6GRJm4srpe2NUbpxS3R8+5yZRNTcYkx7NtzHpumOK4Wq/+f+qplfxgGld"
"""SQLAlchem数据库配置"""
# 数据库链接
SQLALCHEMY_DATABASE_URI = ''
# 动态追踪修改设置
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = False
"""redis数据库配置"""
# 默认缓存数据库 - 0号库
REDIS_URL = 'redis://:@127.0.0.1:6379/0'
"""总路由"""
URL_PATH = "application.urls"
"""静态文件目录存储路径"""
STATIC_DIR = 'application/static'
"""国际化和本地化"""
LANGUAGE = "zh_CN"
TIMEZONE = "Asia/Shanghai"
# 针对babel模块的语言和设置
BABEL_DEFAULT_LOCALE = LANGUAGE
BABEL_DEFAULT_TIMEZONE = TIMEZONE
- 运行项目, 查看后台站点, 英文就变成了中文显示