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

MongoEngine ImageField support. Fixes #257.

parent 125196d3
......@@ -64,6 +64,11 @@ class File(db.Document):
data = db.FileField()
class Image(db.Document):
name = db.StringField(max_length=20)
image = db.ImageField(thumbnail_size=(100, 100, True))
# Customized admin views
class UserView(ModelView):
column_filters = ['name']
......@@ -91,6 +96,7 @@ if __name__ == '__main__':
admin.add_view(ModelView(Tag))
admin.add_view(ModelView(Post))
admin.add_view(ModelView(File))
admin.add_view(ModelView(Image))
# Start app
app.run(debug=True)
......@@ -30,8 +30,6 @@ class MongoFileField(fields.FileField):
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,
......@@ -41,3 +39,7 @@ class MongoFileField(fields.FileField):
field.replace(data.stream,
filename=data.filename,
content_type=data.content_type)
class MongoImageField(MongoFileField):
widget = widgets.MongoImageInput()
......@@ -10,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, MongoFileField
from .fields import ModelFormField, MongoFileField, MongoImageField
class CustomModelConverter(orm.ModelConverter):
......@@ -126,6 +126,10 @@ class CustomModelConverter(orm.ModelConverter):
def conv_File(self, model, field, kwargs):
return MongoFileField(**kwargs)
@orm.converts('ImageField')
def conv_image(self, model, field, kwargs):
return MongoImageField(**kwargs)
def get_form(model, converter,
base_class=form.BaseForm,
......
......@@ -2,23 +2,42 @@ from flask import url_for
from jinja2 import Markup, escape
from mongoengine.base import BaseList
from mongoengine.fields import GridFSProxy
from mongoengine.fields import GridFSProxy, ImageGridFsProxy
from flask.ext.admin.model.typefmt import BASE_FORMATTERS, list_formatter
from . import helpers
def gridfs_formatter(view, model, name, value):
def grid_formatter(view, value):
args = helpers.make_gridfs_args(value)
return Markup(
('<a href="%(url)s" target="_blank">' +
'<i class="icon-file"></i>%(name)s' +
'</a> %(size)dk (%(content_type)s)') %
{
'url': url_for('.api_file_view', **args),
'name': escape(value.filename),
'size': value.length // 1024,
'content_type': escape(value.content_type)
})
def grid_image_formatter(view, 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))
)
('<div class="image-thumbnail">' +
'<a href="%(url)s" target="_blank"><img src="%(thumb)s"/></a>' +
'</div>') %
{
'url': url_for('.api_file_view', **helpers.make_gridfs_args(value)),
'thumb': url_for('.api_file_view', **helpers.make_thumb_args(value)),
})
DEFAULT_FORMATTERS = BASE_FORMATTERS.copy()
DEFAULT_FORMATTERS.update({
BaseList: list_formatter,
GridFSProxy: gridfs_formatter
GridFSProxy: grid_formatter,
ImageGridFsProxy: grid_image_formatter
})
......@@ -8,7 +8,9 @@ from flask.ext.admin.model import BaseModelView
from flask.ext.admin._compat import iteritems, string_types
import mongoengine
from mongoengine.fields import GridFSProxy
import gridfs
from mongoengine.fields import GridFSProxy, ImageGridFsProxy
from mongoengine.connection import get_db
from bson.objectid import ObjectId
from flask.ext.admin.actions import action
......@@ -408,26 +410,22 @@ class ModelView(BaseModelView):
@expose('/api/file/')
def api_file_view(self):
pk = request.args.get('id')
name = request.args.get('name')
coll = request.args.get('coll')
db = request.args.get('db', 'default')
if not pk or not name:
if not pk or not coll or not db:
abort(404)
model = self.get_one(pk)
if model is None:
abort(404)
attr = getattr(model, name, None)
if attr is None:
abort(404)
fs = gridfs.GridFS(get_db(db), coll)
if type(attr) != GridFSProxy:
data = fs.get(ObjectId(pk))
if not data:
abort(404)
return Response(attr.read(),
content_type=attr.content_type,
return Response(data.read(),
content_type=data.content_type,
headers={
'Content-Length': attr.length
'Content-Length': data.length
})
# Default model actions
......
from wtforms.widgets import HTMLString, html_params
from jinja2 import escape
from flask import url_for
from . import helpers
class MongoFileInput(object):
"""
Renders a file input chooser field.
"""
template = '<div><i class="icon-file"></i>%(name)s %(length)dk (%(content_type)s)</div>'
template = '<div><i class="icon-file"></i>%(name)s %(size)dk (%(content_type)s)</div>'
def __call__(self, field, **kwargs):
kwargs.setdefault('id', field.id)
placeholder = ''
if field.data:
data = field.data.fs
placeholder = self.template % {
'name': escape(data.filename),
'content_type': escape(data.content_type),
'size': data.length // 1024
}
return HTMLString('%s<input %s>' % (placeholder,
html_params(name=field.name,
type='file',
**kwargs)))
class MongoImageInput(object):
"""
Renders a file input chooser field.
"""
template = '<div class="image-thumbnail"><img src="%(thumb)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)
args = helpers.make_thumb_args(field.data)
placeholder = self.template % {
'thumb': url_for('.api_file_view', **args)
}
return HTMLString('%s<input %s>' % (placeholder,
html_params(name=field.name,
......
......@@ -940,15 +940,7 @@ class BaseModelView(BaseView, ActionsMixin):
type_fmt = self.column_type_formatters.get(type(value))
if type_fmt is not None:
try:
value = type_fmt(self, model, name, value)
except TypeError:
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 view, _model, _name, value: type_fmt(view, value)
value = type_fmt(self, 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, model, name, value):
def null_formatter(view, value):
"""
Return `NULL` as the string for `None` value
......@@ -12,7 +12,7 @@ def null_formatter(view, model, name, value):
return Markup('<i>NULL</i>')
def empty_formatter(view, model, name, value):
def empty_formatter(view, value):
"""
Return empty string for `None` value
......@@ -22,7 +22,7 @@ def empty_formatter(view, model, name, value):
return ''
def bool_formatter(view, model, name, value):
def bool_formatter(view, value):
"""
Return check icon if value is `True` or empty string otherwise.
......@@ -32,7 +32,7 @@ def bool_formatter(view, model, name, value):
return Markup('<i class="icon-ok"></i>' if value else '')
def list_formatter(view, model, name, values):
def list_formatter(view, values):
"""
Return string with comma separated values
......
......@@ -77,6 +77,11 @@ table.filters {
float: right;
}
/* Image thumbnails */
.image-thumbnail img {
max-width: 100px;
max-height: 100px;
}
/* Patch Select2 */
.select2-results li {
......
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