Commit fa757fc6 authored by Serge S. Koval's avatar Serge S. Koval

MongoEngine GridFS support

parent 9dc0af0f
......@@ -2,6 +2,7 @@
*.swo
*.pyc
*.*~
*.rdb
*.egg-info
pyenv
#*#
......
......@@ -59,6 +59,11 @@ class Post(db.Document):
lols = db.ListField(db.StringField(max_length=20))
class File(db.Document):
name = db.StringField(max_length=20)
data = db.FileField()
# Customized admin views
class UserView(ModelView):
column_filters = ['name']
......@@ -85,6 +90,7 @@ if __name__ == '__main__':
admin.add_view(TodoView(Todo))
admin.add_view(ModelView(Tag))
admin.add_view(ModelView(Post))
admin.add_view(ModelView(File))
# Start app
app.run(debug=True)
from wtforms.fields import FormField
from flask import request
from wtforms import fields
from . import widgets
class ModelFormField(FormField):
class ModelFormField(fields.FormField):
"""
Customized ModelFormField for MongoEngine EmbeddedDocuments.
"""
......@@ -17,3 +20,24 @@ class ModelFormField(FormField):
setattr(obj, name, candidate)
self.form.populate_obj(candidate)
class MongoFileField(fields.FileField):
widget = widgets.MongoFileInput()
def populate_obj(self, obj, name):
field = getattr(obj, name, None)
if field is not None:
data = request.files.get(self.name)
print data.filename
if data:
if not field.grid_id:
field.put(data.stream,
filename=data.filename,
content_type=data.content_type)
else:
field.replace(data.stream,
filename=data.filename,
content_type=data.content_type)
from operator import itemgetter
from mongoengine import ReferenceField
from mongoengine.base import BaseDocument, DocumentMetaclass
......@@ -12,7 +10,7 @@ from flask.ext.admin.model.fields import InlineFieldList
from flask.ext.admin.model.widgets import InlineFormWidget
from flask.ext.admin._compat import iteritems
from .fields import ModelFormField
from .fields import ModelFormField, MongoFileField
class CustomModelConverter(orm.ModelConverter):
......@@ -124,6 +122,10 @@ class CustomModelConverter(orm.ModelConverter):
kwargs['widget'] = form.Select2Widget()
return orm.ModelConverter.conv_Reference(self, model, field, kwargs)
@orm.converts('FileField')
def conv_File(self, model, field, kwargs):
return MongoFileField(**kwargs)
def get_form(model, converter,
base_class=form.BaseForm,
......
from flask import url_for
from jinja2 import Markup, escape
from mongoengine.base import BaseList
from mongoengine.fields import GridFSProxy
from flask.ext.admin.model.typefmt import BASE_FORMATTERS, list_formatter
def gridfs_formatter(view, model, name, value):
return Markup(
'<a href="%s" target="_blank"><i class="icon-file"></i>%s</a> %dk (%s)' % (
url_for('.api_file_view', id=model.id, name=name),
escape(value.filename),
value.length // 1024,
escape(value.content_type))
)
DEFAULT_FORMATTERS = BASE_FORMATTERS.copy()
DEFAULT_FORMATTERS.update({
BaseList: list_formatter
BaseList: list_formatter,
GridFSProxy: gridfs_formatter
})
import logging
from flask import flash
from flask import request, flash, abort, Response
from flask.ext.admin import expose
from flask.ext.admin.babel import gettext, ngettext, lazy_gettext
from flask.ext.admin.model import BaseModelView
from flask.ext.admin._compat import iteritems, string_types
import mongoengine
from mongoengine.fields import GridFSProxy
from bson.objectid import ObjectId
from flask.ext.admin.actions import action
......@@ -30,7 +32,7 @@ SORTABLE_FIELDS = set((
mongoengine.EmailField,
mongoengine.UUIDField,
mongoengine.URLField
))
))
class ModelView(BaseModelView):
......@@ -402,6 +404,32 @@ class ModelView(BaseModelView):
logging.exception('Failed to delete model')
return False
# FileField access API
@expose('/api/file/')
def api_file_view(self):
pk = request.args.get('id')
name = request.args.get('name')
if not pk or not name:
abort(404)
model = self.get_one(pk)
if model is None:
abort(404)
attr = getattr(model, name, None)
if attr is None:
abort(404)
if type(attr) != GridFSProxy:
abort(404)
return Response(attr.read(),
content_type=attr.content_type,
headers={
'Content-Length': attr.length
})
# Default model actions
def is_action_allowed(self, name):
# Check delete action permission
......
from wtforms.widgets import HTMLString, html_params
from jinja2 import escape
class MongoFileInput(object):
"""
Renders a file input chooser field.
"""
template = '<div><i class="icon-file"></i>%(name)s %(length)dk (%(content_type)s)</div>'
def __call__(self, field, **kwargs):
kwargs.setdefault('id', field.id)
placeholder = ''
if field.data:
placeholder = self.template % dict(
name=escape(field.data.filename),
content_type=escape(field.data.content_type),
length=field.data.length // 1024)
return HTMLString('%s<input %s>' % (placeholder,
html_params(name=field.name,
type='file',
**kwargs)))
......@@ -930,17 +930,7 @@ class BaseModelView(BaseView, ActionsMixin):
"""
column_fmt = self.column_formatters.get(name)
if column_fmt is not None:
try:
return column_fmt(self, context, model, name)
except TypeError:
warnings.warn(
u'Column formatter prototype was changed to accept view as first input parameter.\n'
u'Please update %s %s formatter to accept 4 parameters.' % (self.name, name),
stacklevel=2
)
self.column_formatters[name] = lambda _, c, m, n: column_fmt(c, m, n)
return column_fmt(context, model, name)
value = self._get_field_value(model, name)
......@@ -951,14 +941,14 @@ class BaseModelView(BaseView, ActionsMixin):
type_fmt = self.column_type_formatters.get(type(value))
if type_fmt is not None:
try:
value = type_fmt(self, value)
value = type_fmt(self, model, name, value)
except TypeError:
warnings.warn('Type formatter prototype was changed to accept view as first input parameter.\n' +
'Please update %s %s formatter to accept 2 parameters.' % (self.name, type(value)),
warnings.warn('Type formatter prototype was changed to accept view, model, name and value as input parameters.\n' +
'Please update %s %s formatter to accept 4 parameters.' % (self.name, type(value)),
stacklevel=2)
self.column_type_formatters[type(value)] = lambda _, value: type_fmt(value)
self.column_type_formatters[type(value)] = lambda view, _model, _name, value: type_fmt(view, value)
value = type_fmt(value)
value = type_fmt(self, value)
return value
......
......@@ -2,7 +2,7 @@ from jinja2 import Markup
from flask.ext.admin._compat import text_type
def null_formatter(view, value):
def null_formatter(view, model, name, value):
"""
Return `NULL` as the string for `None` value
......@@ -12,7 +12,7 @@ def null_formatter(view, value):
return Markup('<i>NULL</i>')
def empty_formatter(view, value):
def empty_formatter(view, model, name, value):
"""
Return empty string for `None` value
......@@ -22,7 +22,7 @@ def empty_formatter(view, value):
return ''
def bool_formatter(view, value):
def bool_formatter(view, model, name, value):
"""
Return check icon if value is `True` or empty string otherwise.
......@@ -32,7 +32,7 @@ def bool_formatter(view, value):
return Markup('<i class="icon-ok"></i>' if value else '')
def list_formatter(view, values):
def list_formatter(view, model, name, values):
"""
Return string with comma separated values
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment