赞
踩
全栈工程师开发手册 (作者:栾鹏)
python教程全解
官方:https://flask-appbuilder.readthedocs.io/en/latest/cli.html#create-app-create-new-applications
pip install flask-appbuilder
fabmanager 命令不再使用,新版本使用Flask cli集成的新命令行管理器。不过命令形式和参数是一样的。也就是使用flask fab代替原来命令中的fabmanager
mac os先要安装证书的包
cd "/Applications/Python 3.6/"
sudo "./Install Certificates.command"
开始使用命令行flask fab的参数如下(可以通过flask fab --help
查看)
babel-compile-Babel,编译所有翻译 babel-extract-Babel,提取和更新所有消息。 create-admin-创建一个管理员用户 create-user-创建具有任意角色的用户 create-app-创建一个骨架应用程序(SQLAlchemy或MongoEngine)。 create-addon-创建骨架插件。 create-db-创建所有数据库对象(仅适用于SQLAlchemy) collect-static-将静态文件从flask-appbuilder复制到您的静态文件夹。很高兴在某些部署上 list-users-列出数据库中的所有用户。 list-views-列出所有注册的视图。 reset-password-重置用户密码。 security-cleanup -从视图和角色清除未使用的权限。安全 security-converge-融合来自所有角色的所有安全性视图和权限名称。安全 upgrade-db-在FAB升级后升级数据库。 version -Flask-AppBuilder软件包版本。
使用命令行快速生成项目结构
flask fab create-app
创建admin用户
cd myapp
flask fab create-admin # 会在ab_user中添加用户并绑定权限
现在,我们已经有一个基本的web后台了,下面我们来运行一下,进行查看效果。
启动
flask run --with-threads --reload # 如果使用 flask run 命令启动,将忽视.py文件里配置8080,而采用默认的5000端口
或者
python run.py
之后我们在浏览器上输入127.0.0.1:8080就可以看到生成的页面。
model 修改后需要 升级数据库
flask db migrate
flask db update
config.py文件时fab的公共配置。
配置参考官网:https://flask-appbuilder.readthedocs.io/en/latest/config.html
其中系统配置
app的名字 APP_NAME = "My App Name" # 配置连接数据库 配置SQLALCHEMY_DATABASE_URI的值来指定数据库连接。如果使用Mongdb可以配置MONGODB_SETTINGS的值。默认使用Sqlite数据库,SQLALCHEMY_DATABASE_URI的值为'sqlite:///' + os.path.join(basedir, 'app.db')。 设置为mysql SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:admin@localhost:3306/myapp' # 通过配置AUTH_TYPE来指定应用使用的认证方式。 AUTH_TYPE = 0 | 1 | 2 | 3 | 4或AUTH_TYPE = AUTH_OID, AUTH_DB,AUTH_LDAP, AUTH_REMOTE AUTH_OAUTH。默认使用AUTH_DB的认证方式。 # 配置主题风格 Flask-AppBuilder集成了bootwatch,只需要配置APP_THEME的值就可以改变应用的主题风格。 配置主题风格 APP_THEME = "spacelab.css" # 配置身份验证类型 AUTH_TYPE = AUTH_DB #AUTH_OID,AUTH_DB,AUTH_LDAP,AUTH_REMOTE AUTH_OAUTH
我们在/app下创建文件index.py,并且输入下面内容:
from flask_appbuilder import IndexView
class MyIndexView(IndexView):
index_template = 'index.html'
接着在templates中index.html文件,在其中定义你想要的首页的内容。
{% extends "appbuilder/base.html" %}
{% block content %}
<div class="jumbotron">
<div class="container">
<h1>{{_("My App on F.A.B.")}}</h1>
<p>{{_("My first app using F.A.B, bla, bla, bla")}}</p>
</div>
</div>
{% endblock %}
最后修改app/__iniy__.py
的内容,在AppBuilder初始化的时候,指定indexview的值
from app.index import MyIndexView
appbuilder = AppBuilder(app, db.session, indexview=MyIndexView) # 替换原来的初始化语句
AppBuilder类可以指定数据库,模板目录,静态文件目录,静态文件url,安全管理,菜单等
BaseView是视图中的基类,所有的视图都继承自它。当我们定义一个继承自BaseView的子类时,BaseView自动将我们用@exposed修饰的urls注册为Flask中的蓝图。我们可以通过BaseView来实现自定义页面,并添加到菜单上或者通过一个连接来访问它。这里需要注意,作为路由的方法一定要用@exposed修饰,如果需要保护的路由则需要额外使用@has_access修饰。 现在我们来看一个小例子,通常我们使用F.A.B.框架都使用自动生成的项目结构,这样我们只需要在app目录下views.py文件中修改代码即可。下面我们来看一个简单例子:
from flask_appbuilder import ModelView,AppBuilder,expose,BaseView,has_access # 一个蓝图 class indexView(BaseView): # 相对路径的url route_base = '/index' # 蓝图前缀路由 default_view = 'hello' # 设置进入蓝图的默认访问视图(没有设置网址的情况下) # 新视图,蓝图url+路由 @expose('/hello') def hello(self): return 'Hello World' # 新视图,蓝图url+路由 @expose('/message/<string:msg>') @has_access def message(self, msg): msg = 'Hello' + msg # 返回模板 return self.render_template('hello.html',msg=msg) # 后端添加视图,同时前端添加菜单 appbuilder.add_view(indexView,'Hello',category='My View') # 使用默认视图 # 前端,向菜单栏添加子菜单,绑定链接 appbuilder.add_link('message',href='/index/message/user',category='My View') # 前端,向菜单栏添加子菜单,绑定链接 appbuilder.add_link('welcome',href='/index/hello',category='My View') # 添加一个后端视图,不添加前端菜单 # appbuilder.add_view_no_menu(indexView())
如果想渲染的html模板跟默认模板一样需要继承appbuilder/base.html
在’/app/templates/'目录中,创建一个hello.html,并添加下面的代码
{% extends "appbuilder/base.html" %}
{% block content %}
<h1>Welcome</h1>
<h2>Hello World</h2>
<h3>{{ msg }}</h3>
{% endblock %}
此时我们再访问http://127.0.0.1:8080就需要登录之后才可以进行访问
定义表的时候需要继承Model类。
修改/app/models.py这个文件,添加
from sqlalchemy import Column, Integer, String, ForeignKey ,Date from flask_appbuilder.models.decorators import renders from flask import Markup #定义联系人分组,如家人、同学 class ContactGroup(Model): id = Column(Integer, primary_key=True) name = Column(String(50), unique = True, nullable=False) def __repr__(self): return self.name # 定义联系人 class Contact(Model): id = Column(Integer, primary_key=True) name = Column(String(150), unique = True, nullable=False) address = Column(String(564)) birthday = Column(Date) personal_phone = Column(String(20)) personal_cellphone = Column(String(20)) contact_group_id = Column(Integer, ForeignKey('contact_group.id')) # 定义外键,绑定联系人所属分组 contact_group = relationship("ContactGroup") def __repr__(self): return self.name # 自定义一个函数字段和渲染样式,供前端显示 @renders('name') def my_name(self): return Markup('<b style="color:red">' + self.name + '</b>')
通过继承ModelView类可以实现我们自定义的视图,F.A.B.可以针对我们定义好的数据库模型生成创建、删除、更新和显示的功能。
每个model视图,在前端会包含添加新记录,list,查看单条详情,修改几个界面。我们可以配置下面几个参数来实现配置这几个界面。
编辑 /app/views.py 文件
# 将model添加成视图,并控制在前端的显示 from .models import Contact,ContactGroup from flask_appbuilder.actions import action from flask import redirect from flask_appbuilder.models.sqla.filters import FilterEqualFunction, FilterStartsWith,FilterEqual,FilterNotEqual from wtforms.validators import EqualTo,Length from flask_babel import lazy_gettext,gettext from flask_appbuilder.security.decorators import has_access # 定义数据库视图 class ContactModelView(ModelView): datamodel = SQLAInterface(Contact) # 定义创建页面要填写的字段 add_fieldsets=[ ( 'Summary', {'fields': ['name','personal_phone','contact_group']} ) ] # 定义编辑页面要填写的字段 edit_fieldsets = [ ( 'Summary', {'fields': ['name', 'address','birthday','personal_phone','personal_cellphone', 'contact_group']} ) ] # 定义在前端显示时,model的列,显示成一个新别名 label_columns = {'name':'姓名','my_name':"姓名",'contact_group':'分组'} #定义前端model list页面显示的列。my_name为自定义样式的一列 list_columns = ['my_name','personal_cellphone','birthday','contact_group'] # 定义单条model记录详情显示的列 show_fieldsets = [ ( 'Summary', {'fields':['name','address','contact_group']} ), ( 'Personal Info', {'fields':['birthday','personal_phone','personal_cellphone'],'expanded':False} ), ] # 定义list页面的默认筛选条件的配置 base_filters = [['name', FilterNotEqual, ''],] # 筛选出名称为''的 # 定义list页面的排序方法 base_order = ('id', 'dasc') # 使用自定义模板配置详情页面 # extra_args = {'name': 'SOMEVALUE'} # show_template = 'my_show_template.html' # 自定义add/update页面时表单提交自动校验 validators_columns = { 'personal_phone': [Length(min=11,max=11, message=gettext('fields length mush 11'))] # message 为错误时的提示消息 } # 为关联字段做自定义查询过滤器。add_form_quey_rel_fields、edit_form_query_rel_fields、search_form_query_rel_fields add_form_query_rel_fields = {'contact_group': [['name', FilterStartsWith, '家']]} # 仅能选择 name字段的值以'家'开头的contact_group。 # 自定义 页面模板 # show_template = 'appbuilder/general/model/show_cascade.html' # edit_template = 'appbuilder/general/model/edit_cascade.html' # 在联系人组视图中,我们使用related_views来关联联系人视图,F.A.B.将自动处理他们之间的关系。包含列表显示列,修改列 class GroupModelView(ModelView): datamodel = SQLAInterface(ContactGroup) related_views = [ContactModelView] base_permissions = ['can_add','can_edit', 'can_delete','can_list','can_show'] # 默认为这些 # 自定义model视图中的操作按钮。会在数据库添加权限,和视图-权限绑定。 # 默认在model的list页面以list形式调用该函数,在详情页面以单记录形式调用该函数。single=False固定仅在list页面显示该按钮。 @action("muldelete", "Delete", "Delete all Really?", "fa-rocket", single=False) @has_access # 为方法启动权限保护 def muldelete(self, items): if isinstance(items, list): self.datamodel.delete_all(items) self.update_redirect() else: self.datamodel.delete(items) return redirect(self.get_redirect()) # 首先使用db.create_all()根据数据库模型创建表,然后再将视图添加到菜单。 db.create_all() appbuilder.add_view(GroupModelView,"List Groups",icon = 'fa-address-book-o',category = 'Contacts',category_icon = 'fa-envelope') appbuilder.add_separator("Contacts") # 在指定菜单栏下面的每个子菜单中间添加一个分割线的显示。 appbuilder.add_view(ContactModelView,'List Contacts',icon = 'fa-address-card-o',category = 'Contacts')
在view.py中添加自定义的后端接口
# 添加自定义后端接口 from flask import request from flask_appbuilder.api import BaseApi, expose, rison, safe # rison 是和json类似更简单的map数据格式 from flask_appbuilder.security.decorators import protect,permission_name from . import appbuilder # BaseApi 类包含了所有的公开方法 class ExampleApi(BaseApi): resource_name='example' version='v1' base_permissions=['can_get'] # 该视图类会自动添加的权限绑定 class_permission_name=['ExampleApi'] # 该视图类被添加为视图时的名称 # route_base = '/newapi/v2/nice' # 覆盖基础路由/api/{version}/{resource_name},默认是api/v1/$classname_lower # expose 注册为蓝图,url为http://localhost:5000/newapi/v2/nice/greeting @expose('/greeting', methods=['POST', 'GET']) def greeting(self): if request.method == 'GET': return self.response(200, message="Hello (GET)") return self.response(201, message="Hello (POST)") # 编写受权限控制的视图 # 访问该接口,需要携带token. -H "Authorization: Bearer $TOKEN". # token获取: # curl -XPOST 'http://localhost:8080/api/v1/security/login' # -d '{"username": "root", "password": "admin", "provider": "db"}' # -H "Content-Type: application/json" @expose('/private') @protect() # 为api启动权限保护。 会在数据库中新建一个can_rison_json的权限,并在视图上建一个这样的视图-权限绑定 @permission_name('my_Permission') # 自定义权限名称,而不使用默认的函数名为权限名 def rison_json(self): return self.response(200, message="This is private") @expose('/error') @safe # 使用safe装饰器正确处理所有可能的异常,它将为您捕获所有未捕获的异常并返回正确的错误响应 def error(self): raise Exception appbuilder.add_api(ExampleApi)
from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder.api import ModelRestApi
from . import appbuilder
class GroupModelApi(ModelRestApi):
resource_name = 'group'
datamodel = SQLAInterface(ContactGroup)
# 该api自动生成,下面的api和绑定权限,需要有此权限的角色才能调用api
# /_info get can_info
# / get,post创建记录 can_get,can_post,can_put
# /<id> get读取记录,delete删除记录,put修改记录 can_get,can_delete
appbuilder.add_api(GroupModelApi)
BaseChartView为谷歌图表,前端页面显示时需要连接谷歌。
DirectChartView,GroupByChartView,ChartView,TimeChartView 均继承自BaseChartView
定义一个model
# charts视图的model from sqlalchemy import Column, Integer, String, ForeignKey,Float import datetime # insert into country (name) values ('china') # insert into country_stats (stat_date,population,unemployed_perc,poor_perc,college,country_id) values ('2019-10-11',100,10,10,30,1), ('2019-11-11',110,9,10,30,1), ('2019-12-11',120,8,10,30,1) class Country(Model): id = Column(Integer, primary_key=True) name = Column(String(50), unique = True, nullable=False) def __repr__(self): return self.name class CountryStats(Model): id = Column(Integer, primary_key=True) # 国家历史状态id stat_date = Column(Date, nullable=True) # 当时的时间 population = Column(Float) # 当时的人口总数 unemployed_perc = Column(Float) # 失业人口比例 poor_perc = Column(Float) # 当时的贫困者比例 college = Column(Float) # 当时的大学生总数 country_id = Column(Integer, ForeignKey('country.id'), nullable=False) # 国家id country = relationship("Country") # 定义函数计算全国失业人数/全国大学生 def college_perc(self): # 函数可以作为一个字段对后端接口使用,像查询列一样获取数据 if self.population != 0: return (self.college * 100) / self.population else: return 0.0 # 定义时间新字段 def month_year(self): return datetime.datetime(self.stat_date.year, self.stat_date.month, 1)
添加视图代码
from flask_appbuilder.charts.views import DirectByChartView from flask_appbuilder.models.sqla.interface import SQLAInterface from .models import CountryStats class CountryDirectChartView(DirectByChartView): datamodel = SQLAInterface(CountryStats) # 每个chart包含过滤(查询数据源)和报表可视化显示两部分 chart_title = 'Direct Data Example' chart_type = 'ColumnChart' # 图表类型 PieChart,ColumnChart或LineChart # 定义报表部分前端页面的相关显示。多个图表,每个图表包含多个曲线数据。 definitions = [ { 'label': 'Unemployment', # 当前图表的标题 'group': 'stat_date', # 用来作为x的字段。 'series': ['unemployed_perc','college_perc'] # 用来作为y的字段,包含多个y值 }, { 'label': 'Poor', 'group': 'stat_date', 'series': ['poor_perc','college_perc'] } ] appbuilder.add_view(CountryDirectChartView, "Show Country Chart", icon="fa-dashboard", category="Statistics")
# 分组图表的使用 from flask_appbuilder.charts.views import GroupByChartView from flask_appbuilder.models.group import aggregate_count, aggregate_sum, aggregate_avg # 分组计算函数 from flask_appbuilder.models.sqla.interface import SQLAInterface import calendar from .models import CountryStats from .models import Country class CountryGroupByChartView(GroupByChartView): datamodel = SQLAInterface(CountryStats) chart_title = 'Statistics' definitions = [ { 'label': 'Country Stat', 'group': 'month_year', # x的取值字段 'formatter': lambda value:calendar.month_name[value.month] + ' ' + str(value.year), # x值的显示格式 'series': [(aggregate_avg, 'unemployed_perc'), # 分组后,指定字段值集合进行计算后再显示 (aggregate_avg, 'population'), (aggregate_avg, 'college_perc') ] } ] appbuilder.add_view(CountryGroupByChartView, "Show group Country Chart", icon="fa-dashboard", category="Statistics")
@protect()
和@has_access
修饰的视图函数,或在ab_permission中生成can_viewname的权限,并且创建在该视图上的该权限的绑定。只有有此权限绑定的角色才能调用这个视图。您可以添加自己的js,css,导航栏模板,list,add,edit,show,edit-related级联页面模板,参考:https://flask-appbuilder.readthedocs.io/en/latest/templates.html#css-and-javascript
# 在配置文件中配置身份验证类型
AUTH_TYPE = AUTH_DB #AUTH_OID,AUTH_DB,AUTH_LDAP,AUTH_REMOTE AUTH_OAUTH
每种认证方式的配置参考:https://flask-appbuilder.readthedocs.io/en/latest/security.html#authentication-methods
由于user不仅牵扯到认证授权,还有model的增删改查,所以如果我们自定义认证方式,需要和系统自带的user-model联系在一起,既能保证自定义认证,又能保证原有的user在数据库中的增删改查。
认证和user-model是两个模块组成的。
在config.py文件中添加
# 自定义认证方式的user model。继承usermodel,因为也是需要在数据库中存储和查询的。
class MyUserDBView(UserDBModelView):
@action("muldelete", "Delete", "Delete all Really?", "fa-rocket", single=False)
def muldelete(self, items):
self.datamodel.delete_all(items)
self.update_redirect()
return redirect(self.get_redirect())
# 自定义认证策略
class MySecurityManager(SecurityManager):
userdbmodelview = MyUserDBView
FAB_SECURITY_MANAGER_CLASS=MySecurityManager
FAB为每种身份验证方法使用不同的用户视图
当然我们可以扩展系统自带的user model
一种方式是直接在config.py文件中
# 自定义扩展系统自带的user class MyUser(User): __tablename__ = 'ab_user' extra = Column(String(256)) # 新增的属性 # 自定义认证方式的user model。继承usermodel,因为也是需要在数据库中存储和查询的。 class MyUserDBView(UserDBModelView): @action("muldelete", "Delete", "Delete all Really?", "fa-rocket", single=False) def muldelete(self, items): self.datamodel.delete_all(items) self.update_redirect() return redirect(self.get_redirect()) # 自定义认证策略 class MySecurityManager(SecurityManager): user_model = MyUser userdbmodelview = MyUserDBView FAB_SECURITY_MANAGER_CLASS=MySecurityManager
一种方式是单独定义扩展model的py文件,单独定义扩展view的文件,然后在__init__.py
文件中添加自定义认证方法
from flask_appbuilder.security.sqla.manager import SecurityManager
from .sec_models import MyUser
from .sec_views import MyUserDBModelView
class MySecurityManager(SecurityManager):
user_model = MyUser
userdbmodelview = MyUserDBModelView
# appbuilder = AppBuilder(app, db.session, indexview=MyIndexView,menu=Menu(reverse=False),base_template='mybase.html',security_manager_class=MySecurityManager)
每个用户可能具有多个角色,并且一个角色拥有对视图/ API和菜单的权限,因此产生了用户对视图/ API和菜单具有权限。
格式为 FAB_ROLES = { "<ROLE NAME>": [ ["<VIEW/MENU/API NAME>", "PERMISSION NAME"], .... ], ... } 示例 # 添加自带角色和具有的视图-权限 FAB_ROLES = { "ReadOnly": [ [".*", "can_list"], [".*", "can_show"], [".*", "menu_access"], [".*", "can_get"], [".*", "can_info"] ] } # 修改之前的角色id,为新的角色名称。自带Admin和Public不能替换名称。 # FAB_ROLES_MAPPING = { # 3: "role1" # }
Flask AppBuilder 自动为视图、API或菜单,创建所有可能的现有权限。
每次基于模型(从ModelView继承)创建新视图时,都会创建以下权限:
对于CRUD REST API:
除了创建权限,还会创建在这些自定义视图上的权限绑定记录。因为权限都是在视图上才有意义。
如果我们想在视图类上创建访问视图函数的权限。使用@has_access
修饰我们的视图函数(在api类上使用@protect()
修饰)。那么框架将根据您的视图函数名称创建权限,并添加MyModelView上的mymethod,这个视图-权限绑定对。
可以使用@permission_name
自定义权限名称,而采用默认的视图函数名作为权限名称。
上面的权限过于精细化,当视图过多时非常麻烦,可以将权限进行合并。
class OneApi(ModelRestApi): datamodel = SQLAInterface(Contact) class_permission_name = "api" method_permission_name = { "get_list": "access", "get": "access", "post": "access", "put": "access", "delete": "access", "info": "access" } # 修改之前的权限名称。如果之前的名称是get,就换成最新的access previous_method_permission_name = { "get_list": "get", "get": "get", "post": "get", "put": "get", "delete": "get", "info": "get" } 获取的视图和权限为 can access on api
清理之前的垃圾视图和权限
# 修改代码后,视图/权限名称变更,会在数据库中生成新的记录,旧记录删除需要下面的语句。
appbuilder.security_cleanup()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。