earn the White PyBites Ninja earn the Yellow PyBites Ninja earn the Orange PyBites Ninja right arrow earn more PyBites Ninja belts and certificates
The best way to learn to code in Python is to actually use the language.

Our platform offers effective Test Driven Learning which will be key to your progress.

Join thousands of Pythonistas and start coding!

Join us on our PyBites Platform
Click here to code!

Making a Banner Generator With Pillow and Flask

Posted by Bob on Sat 19 August 2017 in Tools • 4 min read

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:

home logged out

This results in:

logged out banner

Upon login it also caches the form input parameters to easily recreate the banners:

home logged in

Logged it uses PyBites logos. Here is the same banner logged in:

banner with pybites article logo

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:

another banner with background


banner of pybites twitter digest

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 validators.

The form is diplayed in imageform.html and _formhelpers.html in the templates dir which I copied from this wtforms pattern.


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.


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 set cache_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 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.html template for rendering.

  • Use of logging and namedtuples.


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

Keep Calm and Code in Python!

-- Bob

See an error in this post? Please submit a pull request on Github.