Flask-Themes

Flask-Themes makes it easy for your application to support a wide range of appearances.

Writing Themes

A theme is simply a folder containing static media (like CSS files, images, and JavaScript) and Jinja2 templates, with some metadata. A theme folder should look something like this:

my_theme/
    info.json
    license.txt
    templates/
        layout.html
        index.html
    static/
        style.css

The info.json file contains the theme’s metadata, so that the application can provide a nice switching interface if necessary. license.txt is optional and contains the full text of the theme’s license. static is served directly to clients, and templates contains the Jinja2 template files.

Note that exactly what templates you need to create will vary between applications. Check the application’s docs (or source code) to see what you need.

Writing Templates

Flask uses the Jinja2 template engine, so you should read its documentation to learn about the actual syntax of the templates.

All templates loaded from a theme will have a global function named theme available to look up the theme’s templates. For example, if you want to extend, import, or include another template from your theme, you can use theme(template_name), like this:

{% extends theme('layout.html') %}
{% from theme('_helpers.html') import form_field %}

If the template you requested doesn’t exist within the theme, it will fall back to using the application’s template. If you pass false as the second parameter, it will only return the theme’s template.

{% include theme('header.html', false) %}

You can still import/include templates from the application, though. Just use the tag without calling theme.

{% from '_helpers.html' import link_to %}
{% include '_jquery.html' %}

You can also get the URL for the theme’s media files with the theme_static function:

<link rel=stylesheet href="{{ theme_static('style.css') }}">

info.json Fields

application : required
This is the application’s identifier. Exactly what identifier you need to use varies between applications.
identifier : required
The theme’s identifier. It should be a Python identifier (starts with a letter or underscore, the rest can be letters, underscores, or numbers) and should match the name of the theme’s folder.
name : required
A human-readable name for the theme.
author : required
The name of the theme’s author, that is, you. It does not have to include an e-mail address, and should be displayed verbatim.
description
A description of the theme in a few sentences. If you can write multiple languages, you can include additional fields in the form description_lc, where lc is a two-letter language code like es or de. They should contain the description, but in the indicated language.
website
The URL of the theme’s Web site. This can be a Web site specifically for this theme, Web site for a collection of themes that includes this theme, or just the author’s Web site.
license
A simple phrase indicating your theme’s license, like GPL, MIT/X11, Public Domain, or Creative Commons BY-SA 3.0. You can put the full license’s text in the license.txt file.
license_url
If you don’t want to include the full text in the license.txt file, you can include a URL for a Web site where the text can be viewed. This is good for long licenses like the GPL or Creative Commons licenses.
preview
A preview image for the theme. This should be the filename for an image within the static directory.
doctype
The version of HTML used by the theme. It can be html4, html5, or xhtml. The application can use this to do things like switch the output format of a markup generator. (The default if this is left out is html5 to be safe. HTML5 is used by the majority of Flask users, so it’s best to use it.)
options
If this is given, it should be a dictionary (object in JSON parlance) containing application-specific options. You will need to check the application’s docs to see what options it uses. (For example, an application like a pastebin or wiki that highlights source code may want the theme to specify a default Pygments style in the options.)

Tips for Theme Writers

  • Always specify a doctype.
  • Remember that you have to use double-quotes with strings in JSON.
  • Look at the non-theme templates provided with the application. See how they interact.
  • Remember that most of the time, you can alter the application’s appearance completely just by changing the layout template and the style.

Using Themes in Your Application

To set up your application to use themes, you need to use the setup_themes function. It doesn’t rely on your application already being configured, so you can call it whenever is convenient. It does three things:

  • Adds a ThemeManager instance to your application as app.theme_manager.
  • Registers the theme and theme_static globals with the Jinja2 environment.
  • Registers the _themes module to your application, by default with the URL prefix /_themes (you can change it).

Theme Loaders

setup_themes takes a few arguments, but the one you will probably be using most is loaders, which is a list of theme loaders to use (in order) to find themes. The default theme loaders are:

  • packaged_themes_loader, which looks in your application’s themes directory for themes (you can use this to ship one or two default themes with your application)
  • theme_paths_loader, which looks at the THEME_PATHS configuration setting and loads themes from each folder therein

It’s easy to write your own loaders, though - a loader is just a callable that takes an application instance and returns an iterable of Theme instances. You can use the load_themes_from helper function to yield all the valid themes contained within a folder. For example, if your app uses an “instance folder” like Zine that can have a “themes” directory:

def instance_loader(app):
    themes_dir = os.path.join(app.instance_root, 'themes')
    if os.path.isdir(themes_dir):
        return load_themes_from(themes_dir)
    else:
        return ()

Rendering Templates

Once you have the themes set up, you can call in to the theme machinery with render_theme_template. It works like render_template, but takes a theme parameter before the template name. Also, static_file_url will generate a URL to the given static file.

When you call render_theme_template, it sets the “active template” to the given theme, even if you have to fall back to rendering the application’s template. That way, if you have a template like by_year.html that isn’t defined by the current theme, you can still

  • extend ({% extends theme('layout.html') %})
  • include ({% include theme('archive_header.html') %})
  • import ({% from theme('_helpers.html') import show_post %})

templates defined by the theme. This way, the theme author doesn’t have to implement every possible template - they can define templates like the layout, and showing posts, and things like that, and the application-provided templates can use those building blocks to form the more complicated pages.

Selecting Themes

How exactly you select the theme will vary between applications, so Flask-Themes doesn’t make the decision for you. If your app is any larger than a few views, though, you will probably want to provide a helper function that selects the theme based on whatever (settings, logged-in user, page) and renders the template. For example:

def get_current_theme():
    if g.user is not None:
        ident = g.user.theme
    else:
        ident = current_app.config.get('DEFAULT_THEME', 'plain')
    return get_theme(ident)

def render(template, **context):
    return render_theme_template(get_current_theme(), template, **context)

Warning

Make sure that you only get Theme instances from the theme manager. If you need to create a Theme instance manually outside of a theme loader, that’s a sign that you’re doing it wrong. Instead, write a loader that can load that theme and pass it to setup_themes, because if the theme is not loaded by the manager, then its templates and static files won’t be available, which will usually lead to your application breaking.

Tips for Application Programmers

  • Provide default templates, preferably for everything. Use simple, unstyled HTML.
  • If you find yourself repeating design elements, put them in a macro in a separate template. That way, theme authors can override them more easily.
  • Put class names or IDs on any elements that the theme author may want to style. (And by that I mean all of them.) That way they won’t have to override the template unnecessarily if all they want to do is right-align the meta information.

API Documentation

This API documentation is automatically generated from the source code.

class flaskext.themes.Theme(path)

This contains a theme’s metadata.

Parameters:
  • path – The path to the theme directory.
application

The application identifier given in the theme’s info.json. Your application will probably want to validate it.

author

The author’s name, as given in info.json. This may or may not include their email, so it’s best just to display it as-is.

description

The human readable description. This is the default (English) version.

doctype

The theme’s doctype. This can be html4, html5, or xhtml with html5 being the default if not specified.

identifier

The theme’s identifier. This is an actual Python identifier, and in most situations should match the name of the directory the theme is in.

jinja_loader

This is a Jinja2 template loader that loads templates from the theme’s templates directory.

license

A short phrase describing the license, like “GPL”, “BSD”, “Public Domain”, or “Creative Commons BY-SA 3.0”.

license_text

The contents of the theme’s license.txt file, if it exists. This is used to display the full license text if necessary. (It is None if there was not a license.txt.)

license_url

A URL pointing to the license text online.

localized_desc

This is a dictionary of localized versions of the description. The language codes are all lowercase, and the en key is preloaded with the base description.

name

The theme’s name, as given in info.json. This is the human readable name.

options

Any additional options. These are entirely application-specific, and may determine other aspects of the application’s behavior.

path

The theme’s root path. All the files in the theme are under this path.

preview

The theme’s preview image, within the static folder.

static_path

The absolute path to the theme’s static files directory.

templates_path

The absolute path to the theme’s templates directory.

website

The URL to the theme’s or author’s Web site.

flaskext.themes.setup_themes(app, loaders=None, app_identifier=None, manager_cls=<class 'flaskext.themes.ThemeManager'>, theme_url_prefix='/_themes')

This sets up the theme infrastructure by adding a ThemeManager to the given app and registering the module containing the views and templates needed.

Parameters:
  • app – The Flask instance to set up themes for.
  • loaders – An iterable of loaders to use. It defaults to packaged_themes_loader and theme_paths_loader.
  • app_identifier – The application identifier to use. If not given, it defaults to the app’s import name.
  • manager_cls – If you need a custom manager class, you can pass it in here.
  • theme_url_prefix – The prefix to use for the URLs on the themes module. (Defaults to /_themes.)
flaskext.themes.render_theme_template(theme, template_name, _fallback=True, **context)

This renders a template from the given theme. For example:

return render_theme_template(g.user.theme, 'index.html', posts=posts)

If _fallback is True and the themplate does not exist within the theme, it will fall back on trying to render the template using the application’s normal templates. (The “active theme” will still be set, though, so you can try to extend or include other templates from the theme.)

Parameters:
  • theme – Either the identifier of the theme to use, or an actual Theme instance.
  • template_name – The name of the template to render.
  • _fallback – Whether to fall back to the default
flaskext.themes.static_file_url(theme, filename, external=False)

This is a shortcut for getting the URL of a static file in a theme.

Parameters:
  • theme – A Theme instance or identifier.
  • filename – The name of the file.
  • external – Whether the link should be external or not. Defaults to False.
flaskext.themes.get_theme(ident)

This gets the theme with the given identifier from the current app’s theme manager.

Parameters:
  • ident – The theme identifier.
flaskext.themes.get_themes_list()

This returns a list of all the themes in the current app’s theme manager, sorted by identifier.

Loading Themes

class flaskext.themes.ThemeManager(app, app_identifier, loaders=None)

This is responsible for loading and storing all the themes for an application. Calling refresh will cause it to invoke all of the theme loaders.

A theme loader is simply a callable that takes an app and returns an iterable of Theme instances. You can implement your own loaders if your app has another way to load themes.

Parameters:
  • app – The app to bind to. (Each instance is only usable for one app.)
  • app_identifier – The value that the info.json’s application key is required to have. If you require a more complex check, you can subclass and override the valid_app_id method.
  • loaders – An iterable of loaders to use. The defaults are packaged_themes_loader and theme_paths_loader, in that order.
bind_app(app)

If an app wasn’t bound when the manager was created, this will bind it. The app must be bound for the loaders to work.

Parameters:
  • app – A Flask instance.
list_themes()

This yields all the Theme objects, in sorted order.

loaders

This is a list of the loaders that will be used to load the themes.

refresh()

This loads all of the themes into the themes dictionary. The loaders are invoked in the order they are given, so later themes will override earlier ones. Any invalid themes found (for example, if the application identifier is incorrect) will be skipped.

themes

This is a dictionary of all the themes that have been loaded. The keys are the identifiers and the values are Theme objects.

valid_app_id(app_identifier)

This checks whether the application identifier given will work with this application. The default implementation checks whether the given identifier matches the one given at initialization.

Parameters:
  • app_identifier – The application identifier to check.
flaskext.themes.packaged_themes_loader(app)

This theme will find themes that are shipped with the application. It will look in the application’s root path for a themes directory - for example, the someapp package can ship themes in the directory someapp/themes/.

flaskext.themes.theme_paths_loader(app)

This checks the app’s THEME_PATHS configuration variable to find directories that contain themes. The theme’s identifier must match the name of its directory.

flaskext.themes.load_themes_from(path)

This is used by the default loaders. You give it a path, and it will find valid themes and yield them one by one.

Parameters:
  • path – The path to search for themes in.