美文网首页Python专辑前端Vue专辑全栈工程师
Vue 2.0 起步(6) 后台管理Flask-Admin -

Vue 2.0 起步(6) 后台管理Flask-Admin -

作者: 非梦nj | 来源:发表于2017-01-05 21:47 被阅读4486次

    上一篇: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

    相关文章

      网友评论

      本文标题:Vue 2.0 起步(6) 后台管理Flask-Admin -

      本文链接:https://www.haomeiwen.com/subject/dmmzvttx.html