In my last post Checking out: Flask-Admin extension I gave a short introduction to the Flask-Admin extension. I also built a small example to show how easy it is to get a basic admin interface for your data. But how does Flask-Admin work if we have more advanced requirements? For example what if we want to provide an image for each user? In this case we will have to expand our recent example by an Image model.

class Image(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))
    path = db.Column(db.String, unique=True)

    def __repr__(self):
        return self.name

To administer the new Image model we need to create a ModelView for it. This time it won' t be enough to just use the ModelView provided by Flask-admin. We want to be able to upload a new image via an upload button and get a nice preview of our image. We need to create our own ImageView class here. I basically used the Flask-Admin example Files, images & custom forms here.

basedir = os.path.abspath(os.path.dirname(__file__))
file_path = os.path.join(basedir, 'files')

Alright, that' s it. You can find the whole example on https://github.com/MrLeeh/flask-admin-examples-images.

class ImageView(ModelView):
    def _list_thumbnail(view, context, model, name):
        if not model.path:
            return ''

        return Markup(
            '<img src="%s">' %
            url_for('static',
                    filename=form.thumbgen_filename(model.path))
        )

    column_formatters = {
        'path': _list_thumbnail
    }

    form_extra_fields = {
        'path': form.ImageUploadField(
            'Image', base_path=file_path, thumbnail_size=(100, 100, True))
    }

admin.add_view(ImageView(Image, db.session))

That's great, we can upload images. Now we want to use them to give our users a face. For this we need to modify our User model and create a UserView class.

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True)
    image_id = db.Column(db.Integer, db.ForeignKey('image.id'))
    image = db.relationship('Image')

class UserView(ModelView):
    def _list_thumbnail(view, context, model, name):
        if not model.image or not model.image.path:
            return ''

        return Markup(
            '<img src="%s">' %
            url_for('static',
                    filename=form.thumbgen_filename(model.image.path))
        )

    column_formatters = {
        'image': _list_thumbnail
    }

admin.add_view(UserView(User, db.session))

Now this will show the image for each user in the user list. That is very nice. Also when we edit a user we magically get a nice Select2 field that let's us choose our user image. Only we don't get a preview of this image. To include a preview we need to provide our own edit template edit_user.html:

{% extends 'admin/model/edit.html' %}

{% block head %}
{{ super() }}
<script   src="https://code.jquery.com/jquery-2.2.4.min.js"   integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="   crossorigin="anonymous"></script>
<script>
  $(document).ready(function() {
    $('select').on('change', function(event) {
      var img_id = $(this).val();
      $.getJSON(
        '{{ url_for('_get_image_url') }}',
        {img_id: img_id}, 
        function(data) {
          if (data.status == 'ok') {
            $('img').prop('src', {{ url_for('static', filename='') }} + data.img_path);
          }
          else {
          }
        });
    });
  });
</script>
{% endblock %}

{% block edit_form %}
{{ super() }}
<img src="{{ url_for('static', filename=model.image.path) }}" style="max-height:200px;">
{% endblock %}

We tell Flask-Admin to use this template by adding the following line to our UserView class .

edit_template = 'edit_user.html'

Now what is it doing. Actually we keep the default template but only add an image tag at the bottom. We use jQuery to listen to the change event of our image select field. If it is changed we will send an ajax call to our server, ask for the image path of the currently selected image and load it in our img element.

Of course for this to work we still need to implement the endpoint get_image_url in our application.

@app.route('/_image-url')
def _get_image_url():
    img_id = request.args.get('img_id')
    img = Image.query.get(img_id)
    if img is None:
        response = jsonify(status='not found')
        return response
    return jsonify(img_path=img.path, status='ok')

Alright, that' s it. Now you can choose and preview the images of your user. You can find the whole example on https://github.com/MrLeeh/flask-admin-examples-images.

Links