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 :
Let's also modify our view, to be able to upload images and display flashed messages:
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=)
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>).
- Reading file from request.
- 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.
- Creating Photo model.
- Assigning photo property to newly created Blob.
- 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:
Now you may check, if something goes wrong, here is the full source code for this tutorial - link
<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
Since you like gae and Flask and also uploading stuff.. you might also like this project: gae-init-upload
ReplyDeleteThe full source for it can be found on BitBucket: bitbucket.org/lipis/gae-init-upload
You can find more info regarding gae-init on the main page where there is also some documentation for it. Any feedback is more than welcome..!
Awesome tutorial man,
ReplyDeleteI was struggling with uploading images using google apps.
Thanks for sharing.
thanks)
Delete