In this article I will take last week’s banner.py Pillow script and integrate it into a Flask app.
I ended up creating our PyBites Banner Generator. Want to try it? The app is on Heroku. Want to fork it? For example to use it with your own brand logos? The code is on Github.
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:
Result:
What’s under the hood?
Here are the pieces that make up this app:
Pillow
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
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 validators
.
The form is diplayed in imageform.html
and _formhelpers.html
in the templates dir which I copied from this wtforms pattern.
Flask-SQLAlchemy
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 SQLAlchemy model code is in model.py including code under main to recreate the DB. Obviously I need to look at a tool like Alembic to properly handle future DB migrations.
Flask
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_required
decorator (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_banner
shows how easy it is to interface with SQLAlchemy. -
The
_get_form
helper swaps out the default logos (currently just one Python logo) with PyBites logos when logged in. Flask-WTF made this effortless. -
The
index
route 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 setcache_timeout=1
the browser would stubbornly show previous banners, probably due to its own caching policy. I ended up giving the output file name a unique string withstr(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.html
template for rendering. -
Use of logging and namedtuples.
Resources
Code Challenge
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.
Further reading
-
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.
-
Want to learn more Flask? Check out our Flask category or tag.
Keep Calm and Code in Python!
— Bob