Flask-Admin是基于Flask框架的,可以快速对现有数据模型进行CRUD管理。我的环境是Python 2.7,Flask-Admin 1.4.1。

参考:

MongoDB是一个面向文档存储的数据库,它将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

PyMongo是一个低级的MongoDB的Python驱动器,它封装了 MongoDB API,并通过JSON与MongoDB通信。

MongoEngine是一个Document-Object Mapper (类似ORM, 但它是针对文档型数据库),它将MongoDB的数据映射成自定义类实例,且基于PyMongo。

Flask-Admin对存储于MongoDB的数据模型,也提供了PyMongo和MongoEngine两种访问方式,详见上面官方示例。

我的需求比较特别,既想充分利用Flask-Admin的CRUD管理功能进行快速开发,也想利用MongoDB支持动态增加模型属性的特性,在后台修改数据模型结构后,前端能直接CRUD。

通过PyMongo访问

要预先写数据模型,并指定给视图的form属性,column_filters的写法与普通ModelView不同,根据文档写法也报错,对筛选和原地修改支持都不好,大概是因为PyMongo的ModelView源码里有三处NotImplementedError,整体结构跟普通ModelView差别较大,不推荐使用:

# 数据模型
from wtforms import form, fields
class NodeForm(form.Form):
    _id = fields.StringField('_id')
    value = fields.StringField('value')
    from_slice = fields.StringField('from_slice')
    
# 视图
import flask_admin.contrib.pymongo as admin_py
class NodeView(admin_py.ModelView):
    column_list = ('_id', 'value', 'from_slice')
    form = NodeForm  # 指定数据模型
   
    column_searchable_list = ['_id', 'value', 'from_slice'] # 正常
    # column_filters = ['_id', 'value', 'from_slice'] # 与普通ModelView写法不同!
    column_filters = (FilterLike(column=NodeForm.value, name='Value'),) # 文档写法,依然报错

如果多个数据模型的结构相同,就可以直接添加视图。

mongo = PyMongo()
admin = Admin()
def create_app(config_name):
    app = Flask(__name__)
    mongo.init_app(app)
	admin.init_app(app)
	from .views import NodeView
    admin.add_view(NodeView(mongo.db['nodeskeys']))
    admin.add_view(NodeView(mongo.db['attrskeys']))

PyMongo的配置是MONGO_URI = 'mongodb://127.0.0.1:27017/mydb'

通过MongoEngine访问

MongoEngine类似Sqlachemy的用法,筛选等功能支持也比较完整。但是,即使是相同结构的数据模型,也得一个个写。当数据模型频繁增加属性的时候,这种方式就过于麻烦了:

# 数据模型
from . import mge
class Nodeskeys(mge.Document):
    value = mge.StringField('value')
    from_slice = mge.StringField('from_slice')
class Attrskeys(mge.Document):
    value = mge.StringField('value')
    from_slice = mge.StringField('from_slice')

# 视图
import flask_admin.contrib.mongoengine as admin_mge
class MyView(admin_mge.ModelView):
    column_list = ( 'value', 'from_slice')
    column_searchable_list = ['value', 'from_slice'] #正常
    column_filters = [ 'value', 'from_slice'] # 与普通ModelView写法一致!
mge= MongoEngine()
admin = Admin()
def create_app(config_name):
    app = Flask(__name__)
    mongo.init_app(app)
	admin.init_app(app)
	from .views import MyView
	from .models import Nodes_Keys,Attrs_Keys
    admin.add_view(MyView(Nodeskeys))
    admin.add_view(MyView(Attrskeys))

MongoEngine的配置是

MONGODB_SETTINGS = {
        'db': 'mydb',
        'host': 'localhost',
        'port': 27017
    }

MongoEngine注意事项

  • 类名Nodeskeys对应集合nodeskeys,实测首字母的要大写,不支持下划线等,参考此文
  • _id只能是ObjectId,不如PyMongo灵活
  • 属性名不能是_id、id、pk等!
  • 集合的列名一个都不能缺,不然会报错,除非设置meta = {'strict': False}

参考:

最终!通过MongoEngine+动态生成类访问

由于我的Mongo数据模型的结构容易变化,Flask-Admin的视图又不支持动态修改,我用MongoEngine+动态生成数据模型类来解决,MyView不用变动,集合映射的数据模型类根据预先从Mongo里获得的集合键名来动态生成。唯一的弊端是,由于Flask-Admin不支持动态增删视图,每次需要修改数据模型结构后,Web也需要重启才能显示新的视图。考虑到修改结构后自动重启的频率可以接受,MongoEngine又特别省事,最终采用此方案!(MongoEngine的DynamicDocument并不满足此场景)

from app.views import get_keys
    import flask_admin.contrib.mongoengine as admin_mge
    
    # 获得nodes_keys集合的所有键名
    keys = get_keys('nodeskeys')
    attrs = {}
    for key in keys:
        if key['_id'] != '_id':
            attrs[key['_id']] = mge.StringField(key['_id'])
    # 动态生成nodes_keys集合映射的数据模型类
    Item= type('Nodeskeys', (mge.Document,), attrs)
    admin.add_view(MyView (Item))
    
    # 获得attrs_keys集合的所有键名
    keys = get_keys('attrskeys')
    attrs = {}
    for key in keys:
        if key['_id'] != '_id':
            attrs[key['_id']] = mge.StringField(key['_id'])
    # 动态生成aodes_keys集合映射的数据模型类
    Item= type('Attrskeys', (mge.Document,), attrs)
    admin.add_view(MyView (Item))

获取集合的所有键名用map_reduce函数:

def get_keys(coll):
    mapper = Code("""function() {for (var key in this) { emit(key, null); }}""")
    reducer = Code("""function(key, stuff) { return null; }""")
    # 用PyMongo去取,集合coll得预先建好……
    result = mongo.db[coll].map_reduce(mapper, reducer, coll + 'keys')  # fix
    keys = mongo.db[coll + 'keys'].find()
    return keys

参考:https://stackoverflow.com/questions/2298870/mongodb-get-names-of-all-keys-in-collection