上一篇: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/, 哈哈,清楚地顯示出對象了吧?不再是一長串內存地址了
現在,給其它的模型Role, Subscription, Article都加上__repr__()
吧!
加上之后,另一個好處是:使用Create創建/Modify修改數據庫記錄時,一目了然,也不用面對一長串內存地址了:
再看一下User頁面:
我的天,密碼都顯示出來啦!幸好是加密過的!
一長串看著有些礙眼,如何修改?而且萬一有些字段我們想保密,怎么辦呢?
解決:
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))
再試試看,這下順眼多了吧。
2. 顯示數據庫內容,包括關系表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語句啊??
如果把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了,按我們的期望顯示了:
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更新