flask-admin+Flask-WTF 实现实现增删改查
背景:
flask-admin+flask-wtf在网上可以搜索到很多资料,但有价值的很少,或许是太简单,或者是很少人这么用,或者。。。,本文将作者近礼拜摸索到的一点经验分享出来,给自己做个记录。
材料:
1、Flask==3.1.0
2、flask-babel==4.0.0
3、Flask-SQLAlchemy==3.1.1
4、Flask-WTF==1.2.2
5、WTForms==3.2.1
制作:
1、安装响应库:依托于pip 安装很简单,省略)
2、应用集成:Flask-WTF 无需集成,安装库文件后直接可以使用
3、flask-admin的集成与配置:
a、项目目录如下
b、在admin 目录下创建base_admin.py文件,便于工程化配置
class MyAdminIndexView(AdminIndexView):
# def is_accessible(self):
# return current_user.is_authenticated
# def inaccessible_callback(self, name, **kwargs):
# return redirect('/admin/login')
@expose("/")
def homepage(self):
isAuth = current_user.is_authenticated
return self.render("admin/index.html", isAuth=isAuth)
class BaseAdmin:
# admin视图初始化.
def init_app(app):
admin = Admin(
app,
name=u"AI阅读管理系统",
index_view=MyAdminIndexView(name="首页"),
template_mode='bootstrap3'
)
admin.add_view(MyOrganView(name='机构管理', endpoint="/org"))
admin.add_view(MyUserView(name="用户管理", endpoint="/user", category='系统管理'))
admin.add_view(MyCategoryView(name="权限管理", endpoint="/cate", category='系统管理'))
admin.add_view(MyRoleView(name="角色管理", endpoint="role", category='系统管理'))
admin.add_view(MyAiConfigView(db.session, name='Dify配置', category='Dify管理'))
admin.add_view(MyAiFlowView(db.session, name='Dify工作流', category='Dify管理'))
admin.add_view(DifyFlowTypeView(db.session, name='工作流类型', category='Dify管理'))
admin.add_view(MyUrlsView(name="网站管理", endpoint="/urls", category='AI阅读'))
admin.add_view(MyNewsView(name="今日要闻", endpoint="/news", category='AI阅读'))
return admin
c、 基于flask-admin的视图基础的有2种,自定义视图(BaseView)和模型视图(ModelView)下面分别介绍本文作者理解的2种视图及用法。
自定义视图(BaseView)
1、自定义视图需要集成baseview基类,特点是集成这个基类后增删改查都需要自己去实现
2、务必要实现 @expose('/')的函数重构(默认进入这个视图模块的路径)
3、样例代码:
from flask_admin import BaseView, expose
from flask_login import current_user
from flask import redirect, url_for
class MyUrlsView(BaseView):
def is_accessible(self):
return current_user.is_authenticated and current_user.is_pass('SYS_URLS_ITEM')
def inaccessible_callback(self, name, **kwargs):
return redirect('/admin/login')
@expose('/')
def index(self):
isAuth = current_user.is_authenticated
return self.render('/urlsSetList.html', isAuth=isAuth)
@expose('/detail/<string:news_id>')
def detail(self, news_id):
return self.render('newsDetail.html', news_id=news_id)
4、样例代码中的@expose('/detail/<string:news_id>') 只是演示传参方式 ,将业务导流到了其他的API实现
5、样例代码中的is_accessible与inaccessible_callback 函数是权限控制用的,本文不做介绍。
6、如果想把自定义的页面集成到flask-admin 中,切记自己的视图文件需要集成自{% extends 'admin/master.html' %}
{% extends 'admin/master.html' %}
{% block head %}
<script type="text/javascript" src="/static/bootstrap-3.4.1/js/jquery.min.js"></script>
<style>
.myactive{
z-index: 2;
color: #ffffff;
background-color: #2fa4e7;
border-color: #2fa4e7;
background-image: glyphicon glyphicon-ok;
}
</style>
{% endblock %}
{% block body %}
自己的html
{% endblock %}
{% block tail_js %}
<script src="/static/bootstrap-3.4.1/js/bootstrap.min.js"></script>
<script type="text/javascript">
自己的js及控制
</script>
{% endblock %}
8、效果:
9、如果实现其他的视图需要集成flask-admin响应的模板的,可以直接从flask-admin/flask_admin/templates/bootstrap3/admin/base.html at v1.6.1 · pallets-eco/flask-admin · GitHubSimple and extensible administrative interface framework for Flask - flask-admin/flask_admin/templates/bootstrap3/admin/base.html at v1.6.1 · pallets-eco/flask-adminhttps://github.com/pallets-eco/flask-admin/blob/v1.6.1/flask_admin/templates/bootstrap3/admin/base.html
10、位置
11、学习它的最好方式是抄袭它的源码
模型视图(ModelView)
1、直接上代码
a、视图代码
from flask_admin.babel import gettext
from flask_admin.contrib.sqla import ModelView
from apps.src.db.entity.dify_flow_type import DifyFlowType
from apps.src.services.vo.news_dify_flow_type_vo import NewsDifyFlowTypeVo
from flask_login import current_user
from flask_admin import expose
from flask import redirect, url_for, request, flash
from apps.src.db.entity.exts import db
from wtforms import form, fields, validators
from wtforms.widgets import TextArea
class DivyFlowTypeForm(form.Form):
status = fields.HiddenField('状态', default=0)
type_name = fields.StringField('类型名称', validators=[validators.DataRequired()])
type_code = fields.StringField('类型CODE', validators=[validators.DataRequired()])
description = fields.TextAreaField('描述', widget=TextArea())
class DifyFlowTypeView(ModelView):
def is_accessible(self):
return current_user.is_authenticated and current_user.is_admin == 1
def inaccessible_callback(self, name, **kwargs):
return redirect(url_for('admin/login'))
# 列表中文翻译
column_labels = {
'created_at': '创建时间',
'updated_at': '修改时间',
'type_name': '类型名称',
'type_code': '类型CODE',
'description': '工作流描述'
}
# 重写新增
@expose('/new/', methods=('GET', 'POST'))
def create_view(self):
bean = DifyFlowType()
form = DivyFlowTypeForm(request.form, obj=bean)
if self.validate_form(form):
model = self.create_model(form)
if model:
flash(gettext('Record was successfully created.'), 'success')
return redirect(self.get_save_return_url(model, is_created=True))
if self.create_modal and request.args.get('modal'):
template = self.create_modal_template
else:
template = self.create_template
return self.render(template, form=form)
@expose('/edit/', methods=('GET', 'POST'))
def edit_view(self):
id = request.args['id']
return_url = request.values.get('url') or self.get_url('.index_view')
model = self.get_one(id)
if model is None:
flash(gettext('Record does not exist.'), 'error')
return redirect(return_url)
form = DivyFlowTypeForm(request.form, obj=model)
if self.validate_form(form):
if self.update_model(form, model):
flash(gettext('Record was successfully saved.'), 'success')
# save button
return redirect(self.get_save_return_url(model, is_created=False))
if request.method == 'GET' or form.errors:
self.on_form_prefill(form, id)
if self.edit_modal and request.args.get('modal'):
template = self.edit_modal_template
else:
template = self.edit_template
return self.render(template, form=form)
# 隐藏主键
column_display_pk = False
# 列表排序,默认倒序
column_default_sort = [('updated_at', True), ('created_at', True)]
# 有此项会显示分页
page_size = 10
# 列表查询
column_searchable_list = ['type_name']
# 列表显示的字段
column_list = ('updated_at', 'type_name', 'description')
# 编辑页面有用
form_columns = ('type_name', 'type_code', 'description')
def __init__(self, session, **kwargs):
# You can pass name and other parameters if you want to
super(DifyFlowTypeView, self).__init__(DifyFlowType, session, **kwargs)
b、模型代码
from .exts import db
from flask_login import UserMixin
class DifyFlowType(db.Model, UserMixin):
__tablename__ = 'dify_flow_type'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
created_at = db.Column(db.TIMESTAMP)
updated_at = db.Column(db.TIMESTAMP)
status = db.Column(db.Integer)
type_name = db.Column(db.String(128))
type_code = db.Column(db.Integer)
description = db.Column(db.String(200))
c、关联类代码
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
2、直接上效果
a、列表
b、新增
c、修改
3、难点解说:
a、flask-admin 默认的表单中针对下拉框选择的支持感觉很弱,或许是我没接触好,找了很久只找到了主子表的这种内联表单和下拉框选择,效果很是不行,直接显示类名,受不了
# form_ajax_refs = {
# 'ai_config': {
# 'fields': ['ai_name', 'ai_code'],
# 'placeholder': '请输入',
# 'page_size': 10,
# 'minimum_input_length': 0,
# },'organ':{
# 'fields': ['org_name'],
# 'placeholder': '请输入',
# 'page_size': 10,
# 'minimum_input_length': 0,
# }
# }
b、使用flask-admin 内嵌wtforms 表单的方式务必处理好 实体外键和relationship 的使用
org_id = db.Column(db.Integer(), db.ForeignKey('organs.id'))
organ = db.relationship('Organ', backref=db.backref('ai_config', lazy='dynamic'))
c、自定义类中只能使用模型中的relationship 对应的实体映射名,否则很难处理编辑时的绑定
class AiConfigForm(form.Form):
def get_countries():
return Organ.query.order_by('updated_at').all()
organ = QuerySelectField('机构名称', validators=[validators.DataRequired()], query_factory=get_countries, get_label='org_name')
d、 当然可以使用自定义的方式实现,但不推荐,不太好处理。
@expose('/new/', methods=('GET', 'POST'))
def create_view(self):
# if request.method == 'POST':
# form = AiFlowForm(request.form)
# if form.validate:
# form.populate_obj(AiFlow)
# aiflow = NewsAiFlowVo.to_entity(AiFlow)
# db.session.add(aiflow)
# db.session.commit()
# return redirect('/admin/aiflow/')
# else:
# form = AiFlowForm(request.form)
下篇介绍
国际化