Search code examples
pythonamazon-s3flaskflask-admin

Flask-Admin: displaying images from S3


I created a custom ImageUploadField in Flask-Admin for uploading images to S3. It's working great, except when I select a single record - the img src tag now prepends /static/ to all of my file routes and I can't see the image. This is the generated tag from Flask-Admin:

<div class="image-thumbnail"> <img src="/static/https://mybucket.s3.amazonaws.com/mypicture.jpg"> </div><input class="form-control" id="image1" name="image1" type="file"></div>

I also serve static files from my app, so globally changing the static directory wouldn't be a good idea.

There appears to be several properties on ImageUploadField that can be modified for this need (endpoint, url_relative_path, relative_path and base_path) but I was still unable to fix it.


Solution

  • It turns out that the widget was responsible for generating the URL. It also looks for the image in the static endpoint (hence why endpoint defaults to static when initializing the ImageUploadField class). To solve this, I created a subclass of this widget and modified the HTML to point directly to the S3 URL, which get's stored on the form.

    Although widgets aren't talked about in depth in the Flask-Admin documentation, you can see how they work from pieces of the source code.

    Below is the full code for the widget, and part of my custom ImageUploadField.

    class S3ImageUploadInput(ImageUploadInput):
    
        def __call__(self, field, **kwargs):
            kwargs.setdefault('id', field.id)
            kwargs.setdefault('name', field.name)
    
            args = {
                'file': html_params(type='file',
                                    **kwargs),
                'marker': '_%s-delete' % field.name
            }
    
            if field.data and isinstance(field.data, string_types):
                # calling field.data to directly return S3 URL
                url = field.data
                args['image'] = html_params(src=url)
    
                template = self.data_template
            else:
                template = self.empty_template
    
            return HTMLString(template % args)
    
    
    class S3ImageUploadField(ImageUploadField):
    
        widget = S3ImageUploadInput()
    
        def __init__(self, *args, **kwargs):
            super(S3ImageUploadField, self).__init__(*args, **kwargs)