Question

I'm using flask-admin with pymongo (not mongoengine) database, and I'm having serious troubles in using the new flask-admin ImageUploadField.

Basically I just can't get it working. I've spent the last two days unsuccesfully trying to replicate the examples, which work fine with sqla. This is my admin class definition:

from jinja2 import Markup
from flask_admin.contrib.pymongo import ModelView, filters
from flask_admin import form
from flask_admin.model import fields
from flask_admin.form.upload import ImageUploadField
from flask_admin.form import rules, widgets
import flask_wtf as wtf
from wtforms import validators as wtfv
from wtforms import widgets as wtfw
from wtforms import fields as wtff


class PhotoForm(wtf.Form):
    label = wtff.TextField('Label')
    file = ImageUploadField('Image', base_path=file_path, thumbnail_size=(100, 100, True))

    def __init__(self, *args, **kwargs):
        kwargs['csrf_enabled'] = True
        super(PhotoForm, self).__init__(*args, **kwargs)


class EquipmentForm(wtf.Form):
    photo = fields.InlineFieldList(fields.InlineFormField(PhotoForm))


class EquipmentViewRoot(ModelView):

    def _list_thumbnail(view, context, model, name):
        if not model['photo']:
            return ''
        return Markup('<img src="%s">' % url_for('static', filename=form.thumbgen_filename(model.path)))

    can_create = True
    column_list = ('photo')
    column_formatters = {
        'photo': _list_thumbnail
    }

    form = EquipmentForm

the form shows correctly, but as soon as I click the save button, if an image file has been selected, I get the error message that pymongo could not save the model because it includes a binary object.

According to the docs (and also the source code), the ImageUploadField should save the image in the uploads directory and replace in the model the binary object with the filename. This happens with sqla but not with pymongo. It looks like nothing is calling the populate_obj method of ImageUploadField class.

Any idea what I am misdoing ?

Was it helpful?

Solution

got it. The populate_obj method of ImageUploadField does ot have effects with pymongo backend because it works with object attributes, while pymongo uses plain dictionaries instead of objects.

I fixed it by simply subclassing ImageUploadField and overloading the populate_obj method to work with dictionaries.

I created a utils.py file including the following:

from flask_admin.form.upload import ImageUploadField as iuf
from werkzeug.datastructures import FileStorage

class ImageUploadField(iuf):
    def __init__(self, *args, **kwargs):
        super(ImageUploadField, self).__init__(*args, **kwargs)

    def populate_obj(self, obj, name):
        field = getattr(obj, name, None)
        if field:
            # If field should be deleted, clean it up
            if self._should_delete:
                self._delete_file(field)
                setattr(obj, name, None)
                return

        if self.data and isinstance(self.data, FileStorage):
            if field:
                self._delete_file(field)

            filename = self.generate_name(obj, self.data)
            filename = self._save_file(self.data, filename)
            # update filename of FileStorage to our validated name
            self.data.filename = filename

            obj[name] = filename  # this is the patched line

then in the admin class I import the new subclass instead of the original. The EquipmentViewRoot(ModelView) class now overloads the on_model_change() method where the following instruction

form.photo.populate_obj(model, 'photo')

is executed explicitly, if a photo has been uploaded.

Note: I've not yet tested the fix with the InLineFieldList, photo is now a single ImageUploadField.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top