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.jianshu.com/p/ab778fde3b99

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容