Vue 2.0 起步(6) 后臺管理Flask-Admin - 微信公眾號RSS

上一篇:Vue 2.0 起步(5) 訂閱列表上傳和下載 - 微信公眾號RSS
本篇關鍵字:Flask-Admin 權限管理 定制顯示格式 顯示關系表外鍵內容 顯示多對多(M2M)內容

Flask-Admin

Flask-Admin是一個功能齊全、簡單易用的Flask擴展,讓你為Flask應用程序增加管理界面。它受django-admin包的影響,開箱就有所有管理功能!但開發者擁有最終應用程序的外觀、感覺和功能的全部控制權。
官網Link

Flask-Admin logo.png

本篇完成功能:

  1. 對模型model,有CRUD基本功能:
    CRUD就是Create、Read、Update、Delete
  2. 顯示數據庫內容,包括關系表Relation對應的內容
    比如:公眾號表Mp,能即時顯示每個公眾號,對應有哪些Subscriber(訂閱者)、有哪些Article(文章),有些是通過多對多(M2M)查詢得到的。
    超過20條記錄,自動分頁pagination


    Mp.png
  3. 加上權限保護,只有superuser才能訪問后臺管理
    當然,也可以定制,讓不同權限用戶,能查看不同的內容
  4. 提供搜索功能
    比如,在用戶表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風格的后臺管理頁面了?
只不過沒有關聯真實數據庫,所以內容是空的。

Get_start

下面把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
有數據顯示啦!

ModelView-default

等等!為什么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修改數據庫記錄時,一目了然,也不用面對一長串內存地址了:

create_user.png

再看一下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 customize

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語句啊??

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更新

http://www.lxweimin.com/p/ab778fde3b99

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容