Vue 2.0 起步(6) 后台管理Flask-Admin -
上一篇:Vue 2.0 起步(5) 订阅列表上传和下载 - 微信公众号RSS
本篇关键字:Flask-Admin 权限管理 定制显示格式 显示关系表外键内容 显示多对多(M2M)内容
Flask-Admin
Flask-Admin是一个功能齐全、简单易用的Flask扩展,让你为Flask应用程序增加管理界面。它受django-admin包的影响,开箱就有所有管理功能!但开发者拥有最终应用程序的外观、感觉和功能的全部控制权。
官网Link
本篇完成功能:
- 对模型model,有CRUD基本功能:
CRUD就是Create、Read、Update、Delete
-
显示数据库内容,包括关系表Relation对应的内容
比如:公众号表Mp,能即时显示每个公众号,对应有哪些Subscriber(订阅者)、有哪些Article(文章),有些是通过多对多(M2M)查询得到的。
超过20条记录,自动分页pagination
Mp.png - 加上权限保护,只有superuser才能访问后台管理
当然,也可以定制,让不同权限用户,能查看不同的内容 -
提供搜索功能
比如,在用户表User,我想把某个用户加入黑名单,设想一下,如果有成千上万的用户,不可能一页页找吧?所以加入搜索框:
User-search.png
1. 对模型model,有CRUD基本功能
我们先看一下Get_started最简例子:
admin.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
app = Flask(__name__)
admin = Admin(app, name='MyAdmin', template_mode='bootstrap3')
# Flask-SQLAlchemy initialization here
db = SQLAlchemy(app)
# Create models
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64))
class Role(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Role, db.session))
app.run(debug=True)
去掉注释、import,才短短12行代码?!
保存为admin.py,运行python admin.py
打开浏览器:http://localhost:5000/admin
是不是立马显示出Bootstrap风格的后台管理页面了?
只不过没有关联真实数据库,所以内容是空的。
下面把Flask-Admin整合到我们app应用,并且关联微信公众号RSS的真实数据库:
- Flask-Admin初始化
/app/_init_.py
# encoding: utf-8
from flask import Flask, abort, redirect, url_for, request
from flask_sqlalchemy import SQLAlchemy
from config import config
from flask_admin.contrib import sqla
from flask_admin import Admin, helpers as admin_helpers
db = SQLAlchemy()
# models引用必须在 db/login_manager之后,不然会循环引用
from .models import User, Role
# Create admin
admin = Admin(name=u'简读Admin')
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
db.init_app(app)
admin.init_app(app)
return app
- 自定义后台管理页面的首页
/app/templates/admin/index.html
{% extends 'admin/master.html' %}
{% block body %}
<div class="container" align="right">
<h5 align="center">Welcome to 后台管理!</h5>
<br>
<p>管理员<a href="/login">登录</a></p>
<br>
Back to <a href="/">首页 - 简读RSS</a>
<div>
{% endblock %}
- 在View视图里,为Admin引入我们应用里的数据模型
/app/main/views.py
from flask import render_template, redirect, url_for, abort, flash, request,\
current_app, make_response, jsonify
from .. import db, admin
from flask_admin import Admin, BaseView, expose
from flask_admin.contrib.sqla import ModelView
from ..models import User, Mp, Article, Role, Subscription
# 后台管理页面的首页
class MyView(BaseView):
@expose('/')
def index(self):
return self.render('admin/index.html')
admin.add_view(ModelView(Role, db.session))
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Mp, db.session))
admin.add_view(ModelView(Subscription, db.session))
admin.add_view(ModelView(Article, db.session))
运行应用:python manage.py runserver
有数据显示啦!
等等!为什么Subscriber、Mp显示的是<models.XXX object>??
看一下 /app/models.py
里Subscription定义,原来这两个字段,是外键ForeignKey,查到当然是对象了
# 订阅公众号和User是多对多关系
class Subscription(db.Model):
__tablename__ = 'subscriptions'
id = db.Column(db.Integer(), primary_key=True)
subscriber_id = db.Column(db.Integer, db.ForeignKey('users.id'))
mp_id = db.Column(db.Integer, db.ForeignKey('mps.id'))
# primary_key=True)
解决:
Python对象有个_repr方法,可以方便地改变打印对象时的输出
我们来改一下User、Mp对象的_repr方法:
class User(db.Model):
...
def __repr__(self):
return '<User-%d %r>' % (self.id, self.email)
class Mp(db.Model):
...
def __repr__(self):
return '<Mp-%d %s>' % (self.id, self.mpName)
这时,再访问一下http://localhost:5000/admin/subscription/, 哈哈,清楚地显示出对象了吧?不再是一长串内存地址了
Class_repr现在,给其它的模型Role, Subscription, Article都加上__repr__()
吧!
加上之后,另一个好处是:使用Create创建/Modify修改数据库记录时,一目了然,也不用面对一长串内存地址了:
再看一下User页面:
我的天,密码都显示出来啦!幸好是加密过的!
User-default
一长串看着有些碍眼,如何修改?而且万一有些字段我们想保密,怎么办呢?
解决:
Flask-Admin提供全方位的定制服务,除了完善的默认显示,另外你想怎么显示就怎么显示。
/app/main/views.py
创建一个自定义ModelView,User.password字段,比如,我只想显示后6位。然后添加给User:
class MyModelViewUser(ModelView):
# 字段(列)格式化
# `view` is current administrative view
# `context` is instance of jinja2.runtime.Context
# `model` is model instance
# `name` is property name
column_formatters = dict(
password=lambda v, c, m, p: '**'+m.password[-6:],
)
admin.add_view(MyModelViewUser(User, db.session))
再试试看,这下顺眼多了吧。
User-password customize2. 显示数据库内容,包括关系表Relation对应的内容
上一节,最后一张图,缺省情况下不显示Roles, Mps,因为它们是关系表Relationship。id,默认也是不显示的,因为是主键primary_key:
# /app/models.py
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
。。。
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users'), lazy='dynamic')
mps = db.relationship('Subscription',
foreign_keys=[Subscription.subscriber_id],
backref=db.backref('subscriber', lazy='joined'),
lazy='dynamic', # select dynamic subquery
cascade='all, delete-orphan')
那如何显示呢?
继续在/app/main/views.py
里配置:
class MyModelViewUser(ModelView):
column_display_pk = True # optional, but I like to see the IDs in the list
column_display_all_relations = True
咦,报错了:
sqlalchemy.exc.InvalidRequestError
InvalidRequestError: 'User.roles' does not support object population - eager loading cannot be applied.
原来,User模型里,roles relationship没有定义外键,而且是用了lazy='dynamic'
查询方式,每次动态查询,但不会立即返回数据。
解决:
把User--roles字段,lazy='dynamic'注释掉!
这下,没有报错了,主键id、关系Relationship都显示了。但Mps怎么是SQL语句啊??
Relationship如果把User--mps字段,lazy='dynamic'
注释掉,会怎么样呢?
答案是:Mps会显示内容了!但是,因为mps字段是多对多关系的外键,这样Flask后台查询语句会报错,比如Mp.to_json()!
解决:
User模型保留lazy='dynamic'
,添加subscribed_mps_str
属性方法,供ModelView里自定义显示Mps字段使用
# /app/models.py
class User(UserMixin, db.Model):
...
@property
def subscribed_mps_str(self):
mplist = []
i = 1
# SQLAlchemy 过滤器和联结
mps = Mp.query.join(Subscription, Subscription.mp_id == Mp.id)\
.filter(Subscription.subscriber_id == self.id)
for mp in mps:
mplist.append('<Mp-%d %s_%s>' % (mp.id, mp.weixinhao, mp.mpName) )
i+=1
return mplist
Admin ModelView里自定义显示Mps字段,使用subscribed_mps_str方法。
顺便重构一下,把公用的ModelView定义到MyModelViewBase
# /app/main/views.py
class MyModelViewBase(ModelView):
column_display_pk = True # optional, but I like to see the IDs in the list
column_display_all_relations = True
class MyModelViewUser(MyModelViewBase):
column_formatters = dict(
password=lambda v, c, m, p: '**'+m.password[-6:],
mps=lambda v, c, m, p: (m.subscribed_mps_str),
)
admin.add_view(MyModelViewUser(User, db.session))
OK了,按我们的期望显示了:
User.png
3. 加上权限保护,只有superuser才能访问后台管理
这就用到我们上一篇的Flask-Security里,session管理的功能了
在自定义ModelView里,添加权限检查就行:
# /app/main/views.py
class MyModelViewBase(ModelView):
def is_accessible(self):
if not current_user.is_active or not current_user.is_authenticated:
return False
if current_user.has_role('superuser'):
return True
return False
def _handle_view(self, name, **kwargs):
"""
Override builtin _handle_view in order to redirect users when a view is not accessible.
"""
if not self.is_accessible():
if current_user.is_authenticated:
# permission denied
abort(403)
else:
# login
return redirect(url_for('security.login', next=request.url))
试一下,是不是没权限访问啦?乖乖地用admin登录吧~
记得先创建superuser用户,用户名admin,密码自己定:
python manage.py initrole
4. 提供搜索功能
其实很简单,ModelView里添加column_searchable_list就行。
注意,不同的模型,search字段要分开来。如果放在一起,会查询错误,因为不同模型(比如User, Article),不一定定义了Relationship关系。
# /app/main/views.py
class MyModelViewUser(MyModelViewBase):
column_formatters = dict(
password=lambda v, c, m, p: '**'+m.password[-6:],
mps=lambda v, c, m, p: (m.subscribed_mps_str),
)
column_searchable_list = (User.email, )
class MyModelViewMp(MyModelViewBase):
column_formatters = dict(
subscribers=lambda v, c, m, p: (m.subscribers_str), # '\n\p'.join
articles=lambda v, c, m, p: (m.articles_str),
)
column_searchable_list = (Mp.weixinhao, Mp.mpName, )
admin.add_view(MyModelViewBase(Role, db.session))
admin.add_view(MyModelViewUser(User, db.session))
admin.add_view(MyModelViewMp(Mp, db.session))
admin.add_view(MyModelViewBase(Subscription, db.session))
admin.add_view(MyModelViewBase(Article, db.session))
Demo:http://vue2.heroku.com
源码:https://code.csdn.net/Kevin_QQ/vue-tutorial/tree/master
敬请关注第7篇!
Vue 2.0 起步(7) 大结局:公众号文章抓取 - 微信公众号RSS
Vue 2.0 起步(6) 后台管理Flask-Admin更新