My new blog here at www.mostovenko.com

Sunday, January 20, 2013

Upload images to Google App Engine(GAE) with Flask

As you understood, this article is about uploading images to GAE. Actually, this article is mostly for beginners and covers only basic concepts, but when i started to learn GAE platform i missed such kind of tutorial a lot. So, to get started, let's take app from my previous GAE tutorial from here.
I decided to add our photo upload functionality as a separate python module and wrap it in flask blueprint. Let's create it inside of gae_flask_app dir and name it upload_photo. It would be great for you to get familiar with my previous article to deal with gae_flask_app structure.






1. Include new module to old application

Let's include our newly created module to gae_flask_app, for this let's add this to __init__.py in upload_photo package :

# -*- coding: utf-8 -*-

from flask import Blueprint

upload_photo = Blueprint('upload_photo', __name__)

import handlers

This will create new blueprint in our app, after that let's add handlers.py to the package, with such initial content:
# -*- coding: utf-8 -*-
from flask import render_template
from ..upload_photo import upload_photo

@upload_photo.route('/', methods=["GET"])
def upload_view():
    return render_template("photo.html")


Let's also add photo.html template to our templates folder
<html>
<head>
    <title>Upload images</title>
</head>
<body>
    <form method="POST" action="" enctype="multipart/form-data">
        <input name="photo" type="file" />
        <input type="submit" />
    </form>
</body>
</html>

And the last step for including our new module - is the registration for blueprint. Let's do it in gae_flask_app/app.py file - add this code to the create_app function:
# ...
from gae_flask_app.upload_photo import upload_photo
app.register_blueprint(upload_photo, url_prefix="/photo")
# ...

For now we are ready to launch our app, and navigate to localhost:8080/photo url - we must see our form for photo uploading.

2. Creating model for storing photo

Google App Engine uses BlobProperty for storing binary data. Let's add models.py module to our upload_photo package and declare our simple Photo model with BlobProperty:
# -*- coding: utf-8 -*-

from google.appengine.ext import db

class Photo(db.Model):
    photo = db.BlobProperty()

In realtime webapp you will also add some name, thumbnails, date of creation fields to your model, but let's concentrate on the main target of this tutorial and omit those fields.

3. Upload handler

Let's add our upload handler to upload_photo/handlers.py 
# ... adding Photo model import at the top
from google.appengine.ext import db
from .models import Photo
# ...

MAX_IMAGE_SIZE = 1024 * 1024

@upload_photo.route('/upload', methods=["POST"])
def upload():
    file = request.files['photo']

    if file:
        filestream = file.read()

        if len(filestream) > MAX_IMAGE_SIZE:
            flash("Too large image size (must be less than 1 MB)")
            return redirect(url_for("upload_photo.upload_view"))
        photo = Photo()
        photo.photo = db.Blob(filestream)
        photo.put()
        flash("Photo added")
 
    return render_template("photo.html")

As you see everything is clear :

  1. Reading file from request.
  2. Validating file size (GAE has a restriction on a max request size - 1 MB, so we must handle this in some way). Also i may note that in realtime app you also need to check file type extension.
  3. Creating Photo model. 
  4. Assigning photo property to newly created Blob.
  5. Calling put method to save our new object to datastore.

Let's also modify our view, to be able to upload images and display flashed messages:

<html>
<head>
    <title>Upload images</title>
</head>
<body>
    {% if get_flashed_messages() %}
        {%  for message in get_flashed_messages() %}
            <p style="color: brown; font-weight:bold">{{ message }}</p>
        {% endfor %}
    {%  endif %}
    <form method="POST" action="{{ url_for("photo_url.upload") }}" enctype="multipart/form-data">
        <input name="photo" type="file" />
        <input type="submit" />
    </form>
</body>
</html>

For now, before testing upload, let's look at our local datastore by accessing localhost:8080/_ah/admin/datastore. There must be one entity kind - Photo, and empty list when pressing "List Entities". Let's change this situation ) Go to localhost:8080/photo, select some image and submit form to upload. If everything was done right - you must see now one new entity in EntitiesList in _ah/admin/datastore . Nice=)

4. Image get handler

Now, after we can upload images to datastore, it would be great to learn how get them from there and show to our end user. This can be done by implementing new handler in handlers.py 

@upload_photo.route('/show/', methods=["GET"])
def show(key):
    photo = db.get(key)
    if not photo:
        return abort(404)
    else:
        mimetype = "image/png"
        return current_app.response_class(photo.photo, mimetype=mimetype)

Here we returning image by it's key. Every GAE database  model has it's own unique key. So we are using show handler to return image.
For now you can copy your uploaded photo key from _ah/admin/datastore and access url localhost:8080/photo/show/<entity key> - you must see your image in browser. So every time when you need to display image by url - you may get get it from url_for("upload_photo.show", key=<entity key>).

5. Last steps

As for the last steps let's display our uploaded image to user after load, for this we must modify our upload handler :

@upload_photo.route('/upload', methods=["POST"])
def upload():
    file = request.files['photo']

    if file:
        filestream = file.read()

        if len(filestream) > MAX_IMAGE_SIZE:
            flash("Too large image size (must be less than 1 MB)")
            return redirect(url_for("upload_photo.upload_view"))

        photo = Photo()
        photo.photo = db.Blob(filestream)
        photo.put()
        photo_url = url_for(".show", key=photo.key())

    return render_template("photo.html", photo_url=photo_url)

As you can see we added redirect functionality if image size is more than 1 MB, else we are passing uploaded photo url to template context. And now let's modify our template:

<body>
{% if get_flashed_messages() %}
    {%  for message in get_flashed_messages() %}
        <p style="color: brown; font-weight:bold">{{ message }}</p>
    {% endfor %}
{%  endif %}

{% if photo_url %}
    <p>Congratulations!!! image was successfully aploaded</p>
    <img src="{{ photo_url }}"/>
    <a href="{{ url_for("upload_photo.upload_view") }}">Back</a>
{%  else %}
    <form method="POST" action="{{ url_for("upload_photo.upload") }}" enctype="multipart/form-data">
        <input type="file" name="photo"/>
        <input type="submit"/>
    </form>
{%  endif %}
</body>

Now you may check, if something goes wrong, here is the full source code for this tutorial - link