mrzoogle
mrzoogle

Reputation: 111

Upload multiple images with Flask-Admin

I am trying to get the fileadmin to get multiple files uploaded but can't figure out how to do that.

Currently in the below reference I can only upload one file at a time.

https://github.com/flask-admin/flask-admin/blob/master/examples/file/app.py
(New Link to to file: https://github.com/mrjoes/flask-admin/blob/master/examples/file/app.py )

I tried updating the html template to have multiple="" but that didn't helped to upload multiple files.


Further looking into this I think this html file needs to have multiple=""

Python27\Lib\site-packages\flask_admin\templates\bootstrap3\adminC:\Python27\Lib\site-packages\flask_admin\templates\bootstrap3\admin\lib.html

Although I am not sure where/how to add this tag without actually overwriting the source file.

Upvotes: 2

Views: 3126

Answers (2)

Trect
Trect

Reputation: 2965

Dropzone is the best way to do this

Download with

pip install flask flask-uploads flask-dropzone

The Python app.py should look somthhing like this

# app.py

from flask import Flask, render_template
from flask_dropzone import Dropzone
from flask_uploads import UploadSet, configure_uploads, IMAGES, patch_request_class

import os

app = Flask(__name__)
dropzone = Dropzone(app)

# Dropzone settings
app.config['DROPZONE_UPLOAD_MULTIPLE'] = True
app.config['DROPZONE_ALLOWED_FILE_CUSTOM'] = True
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image/*'
app.config['DROPZONE_REDIRECT_VIEW'] = 'results'

# Uploads settings
app.config['UPLOADED_PHOTOS_DEST'] = os.getcwd() + '/uploads'

photos = UploadSet('photos', IMAGES)
configure_uploads(app, photos)
patch_request_class(app)  # set maximum file size, default is 16MB

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/results')
def results():
    return render_template('results.html')

And result.html looks as follows

# templates/results.html

<h1>Hello Results Page!</h1>
<a href="{{ url_for('index') }}">Back</a><p>

<ul>
{% for file_url in file_urls %}
    <li><img style="height: 150px" src="{{ file_url }}"></li>
{% endfor %}
</ul>

index.html looks something like this

# templates/index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Flask App</title>
        {{ dropzone.load() }}
        {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }}
    </head>
    <body>
        <h1>Hello from Flask!</h1>
        {{ dropzone.create(action_view='index') }}
    </body>
</html>

Upvotes: 1

stamaimer
stamaimer

Reputation: 6475

You should custom ImageUploadInput and ImageUploadField as follows.

import ast
from PIL import Image
from wtforms.widgets import html_params, HTMLString
from wtforms.utils import unset_value
from flask_admin.helpers import get_url
from flask_admin.form.upload import ImageUploadField
from flask_admin._compat import string_types, urljoin

class MultipleImageUploadInput(object):
    empty_template = "<input %(file)s multiple>"

    # display multiple images in edit view of flask-admin
    data_template = ("<div class='image-thumbnail'>"
                     "   %(images)s"
                     "</div>"
                     "<input %(file)s multiple>")

    def __call__(self, field, **kwargs):

        kwargs.setdefault("id", field.id)
        kwargs.setdefault("name", field.name)

        args = {
            "file": html_params(type="file", **kwargs),
        }

        if field.data and isinstance(field.data, string_types):

            attributes = self.get_attributes(field)

            args["images"] = "&emsp;".join(["<img src='{}' /><input type='checkbox' name='{}-delete'>Delete</input>"
                                            .format(src, filename) for src, filename in attributes])

            template = self.data_template

        else:
            template = self.empty_template

        return HTMLString(template % args)

    def get_attributes(self, field):

        for item in ast.literal_eval(field.data):

            filename = item

            if field.thumbnail_size:
                filename = field.thumbnail_size(filename)

            if field.url_relative_path:
                filename = urljoin(field.url_relative_path, filename)

            yield get_url(field.endpoint, filename=filename), item

class MultipleImageUploadField(ImageUploadField):

    widget = MultipleImageUploadInput()

    def process(self, formdata, data=unset_value):

        self.formdata = formdata  # get the formdata to delete images
        return super(MultipleImageUploadField, self).process(formdata, data)

    def process_formdata(self, valuelist):

        self.data = list()

        for value in valuelist:
            if self._is_uploaded_file(value):
                self.data.append(value)

    def populate_obj(self, obj, name):

        field = getattr(obj, name, None)

        if field:

            filenames = ast.literal_eval(field)

            for filename in filenames[:]:
                if filename + "-delete" in self.formdata:
                    self._delete_file(filename)
                    filenames.remove(filename)
        else:
            filenames = list()

        for data in self.data:
            if self._is_uploaded_file(data):

                self.image = Image.open(data)

                filename = self.generate_name(obj, data)
                filename = self._save_file(data, filename)

                data.filename = filename

                filenames.append(filename)

        setattr(obj, name, str(filenames))

After that, You can use them as image upload example in flask-admin

class ModelHasMultipleImages(db.Model):

    id = db.Column(db.Integer(), primary_key=1)

    # len of str representation of filename lists may exceed the len of field

    image = db.Column(db.String(128))  

class ModelViewHasMultipleImages(ModelView):

    def _list_thumbnail(view, context, model, name):

        if not model.image:
            return ''

        def gen_img(filename):
            return '<img src="{}">'.format(url_for('static', 
                   filename="images/custom/" + form.thumbgen_filename(image)))

        return Markup("<br />".join([gen_img(image) for image in ast.literal_eval(model.image)]))

    column_formatters = {'image': _list_thumbnail}

    form_extra_fields = {'image': MultipleImageUploadField("Images",
                                  base_path="app/static/images/custom",
                                  url_relative_path="images/custom/",
                                  thumbnail_size=(400, 300, 1))}

The image filed of ModelHasMultipleImages store the str represent of filename list.

Upvotes: 6

Related Questions