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