Flask-Admin是基于Flask框架的,可以快速对现有数据模型进行CRUD管理。我的环境是Python 2.7,Flask-Admin 1.4.1。
参考:
- 系列博客flask-admin:
- 官方指南:https://flask-admin.readthedocs.io/en/latest/
- 官方示例:https://github.com/flask-admin/flask-admin/tree/master/examples
- flask-admin 快速打造博客:
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}
参考:
- ModelView的详细说明:https://flask-admin.readthedocs.io/en/latest/api/mod_model/?highlight=ModelView
- MongoEngine的FieldDoesNotExist错误:http://docs.mongoengine.org/apireference.html#mongoengine.FieldDoesNotExist
最终!通过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