In this article I will take last week's banner.py Pillow script and integrate it into a Flask app.
How it works
Give your banner a name, a background URL and text. We use PyBites logos upon login. Logged out state has a Python logo but I probably make this a free field so you can input any URL. Leaving "Use Second Image as Background" unchecked aligns the second image to the right:
This results in:
Upon login it also caches the form input parameters to easily recreate the banners:
Logged it uses PyBites logos. Here is the same banner logged in:
And the banner's form data can be retrieved again by clicking its name in the right "Cached Banners" list.
Let's make a Twitter digest banner. Ticking "Use Second Image as Background" turns it into background image:
What's under the hood?
Here are the pieces that make up this app:
Last week's article detailed the Pillow script
banner.py which is in the banner package. The
generate_banner takes a
img_banner named tuple, instantiates a
Banner object, and creates and saves the image.
Since last time I added a
add_background method which you saw in the 3rd example above. I also made
add_text smarter about aligning text: if background is ticked it uses the extra free space to the right and if the text is less than 2 lines long (using Python's textwrap), it adds more top padding to it.
Flask-WTF integrates Flask and WTForms making working with forms a joy.
In forms.py I subclass wtforms's Form class, read in the logos for the dropdown and add some validations using wtform's
We have covered Flask-SQLAlchemy before. I use it here to store the image parameters in a DB when logged in. Why not the images? Heroku has an ephemeral filesystem so they would be lost after a dyno restart (which happens often because I am using the hobby version now). For this same reason Heroku provides production grade PostgreSQL databases as a service.
The core logic is in app.py. It started simple with 57 lOC, growing to 139 LOC as of this writing. Not bad considering that it does form handling, image generation, caching and handling a simple login session.
Some interesting things:
login_requireddecorator (RealPython's Flask material). This login implementation simply verifies against env variables and keeps state in Flask's session. For multiple users you really would use a User model and a plugin like Flask-Login.
_store_bannershows how easy it is to interface with SQLAlchemy.
_get_formhelper swaps out the default logos (currently just one Python logo) with PyBites logos when logged in. Flask-WTF made this effortless.
indexroute is still a bit too long. This would be a good candidate for refactoring. It retrieves cached image objects (basically the corresponding form inputs) from the DB and generates the banner upon POST request, displaying it in the browser.
The way to send a banner to the browser is via Flask's
send_file. This was a bit tricky. Although I set
cache_timeout=1the browser would stubbornly show previous banners, probably due to its own caching policy. I ended up giving the output file name a unique string with
str(time.time()), so the browser sees it as a brand new file each time. Tricking the browser for fun and profit ;)
Form and cached banners are passed to the
imageform.htmltemplate for rendering.
Use of logging and namedtuples.
This project was part of Code Challenge 31 - Image Manipulation With Pillow - if you want to play with Pillow and potentially Flask and Heroku, follow the instructions there and start coding and PR your code to our challenges repo. I hope this article inspired you to give it a try yourself.
Flask vs Django
Yeah I know what you are thinking: "But it's 100 days of Django, why not a Django app?" Glad you asked. For this case I think Flask was the right choice. Julian shared some more thoughts about when to use one or the other, you can check it out here.
You can read more about the Pillow code in Part 1 of this tutorial.
You can read more how to automatically generate banners with Requests and Selenium in part 3.
If you take the challenge and want to deploy your app to Heroku, check out Julian's nice tutorial on the subject.
Keep Calm and Code in Python!
See an error in this post? Please submit a pull request on Github.