Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Cleaned up Flask-Themes2, making it fully Flask extension compliant, …

…plus others. NO TESTS.
  • Loading branch information...
commit 380adc055f12c27ad40a8ac56fec9c0b69d573c6 1 parent 3d43944
@sysr-q authored
View
2  LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2013 Christopher Carter, 2010 Matthew "LeafStorm" Frazier
+Copyright (c) 2013 Christopher Carter, 2012 Drew Lustro, 2010 Matthew "LeafStorm" Frazier
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
View
6 README.rst
@@ -29,3 +29,9 @@ Once the project is worthy, I will upload it to RTD.
$ make html
The documentation will be in generated in a directory called ``_build/``
+
+Tests (a note)
+--------------
+I've yet to actually add functionality to the tests, so don't dare try them yet.
+
+To quote Dade "ZeroCool" Murphy: ``"Just give me time!"``
View
91 docs/index.rst
@@ -19,14 +19,14 @@ should look something like this:
.. sourcecode:: text
- my_theme/
- info.json
- license.txt
- templates/
- layout.html
- index.html
- static/
- style.css
+ my_theme
+ ├── info.json
+ ├── license.txt
+ ├── static
+ └── style.css
+ └── templates
+ ├── layout.html
+ └── index.html
The ``info.json`` file contains the theme's metadata, so that the application
can provide a nice switching interface if necessary. ``license.txt`` is
@@ -44,7 +44,7 @@ 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`
+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:
@@ -70,7 +70,7 @@ 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`
+You can also get the URL for the theme's media files with the ``theme_static``
function:
.. sourcecode:: html+jinja
@@ -153,21 +153,35 @@ Tips for Theme Writers
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:
+To set up your application to use themes, you need to use ``flask.ext.themes2.Themes``,
+in one of two ways:
-* Adds a `ThemeManager` instance to your application as ``app.theme_manager``.
-* Registers the `theme` and `theme_static` globals with the Jinja2
+.. code-block:: python
+
+ # The first way
+ app = Flask(__name__)
+ Themes(app, app_identifer="...")
+
+ # The second way
+ app = Flask(__name__)
+ t = Themes()
+ t.init_themes(app, app_identifer="...")
+
+The first is simply a quicker way of the second, as it will automatically call ``init_themes`` on your app.
+
+This 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 or blueprint (depending on the Flask version)
+* Registers the ``_themes`` module or blueprint (depending on the Flask version)
to your application, by default with the URL prefix ``/_themes`` (you can
change it).
.. warning::
Since the "Blueprints" mechanism of Flask 0.7 causes headaches in module
- compatibility mode, `setup_themes` will automatically register `_themes`
+ compatibility mode, ``init_themes`` will automatically register ``_themes``
as a blueprint and not as a module if possible. If this causes headaches
with your application, then you need to either (a) upgrade to Flask 0.7 or
(b) set ``Flask<0.7`` in your requirements.txt file.
@@ -175,19 +189,19 @@ configured, so you can call it whenever is convenient. It does three things:
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
+``init_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``
+* ``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
+* ``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
+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::
@@ -204,18 +218,18 @@ folder" like `Zine`_ that can have a "themes" directory::
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
+``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
+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 %}``)
+- 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,
@@ -229,7 +243,9 @@ How exactly you select the theme will vary between applications, so
Flask-Themes2 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::
+renders the template. For example:
+
+.. code-block:: python
def get_current_theme():
if g.user is not None:
@@ -244,10 +260,10 @@ renders the template. For example::
.. 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,
+ 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
+ load that theme and pass it to ``init_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.
@@ -268,10 +284,13 @@ API Documentation
=================
This API documentation is automatically generated from the source code.
-.. autoclass:: Theme
- :members:
+.. autoclass:: Themes
+ :members:
+ :special-members:
+ :exclude-members: __weakref__
-.. autofunction:: setup_themes
+.. autoclass:: Theme
+ :members:
.. autofunction:: render_theme_template
View
25 example/posts.yaml
@@ -3,6 +3,31 @@
# copyright 2013 Christopher Carter, 2010 Matthew "LeafStorm" Frazier
# licensed under the MIT/X11 license (see LICENSE for details)
+slug: additional-rewrites
+created: 2013-11-01 18:00:00
+title: Additional rewrites and forking
+body: >
+ Since LeafStorm hadn't updated Flask-Themes in several years
+ (the last update was in 2011 or so, mind you), it was picked up
+ by a guy named <a href="https://github.com/drewlustro">Drew Lustro</a>
+ under the name <a href="https://github.com/drewlustro/spirits">Spirits</a>.
+
+ Sadly, it didn't really conform to the Flask extension standards, nor was it
+ anywhere to be seen but his GitHub account. So I -- <a href="https://github.com/plausibility">
+ "plausibility"</a> -- forked Spirits, and brought it back to life,
+ cleaning up the base, making it a valid Flask extension, and putting it online, accessible via PyPi.
+
+ For obvious reasons, I renamed it from Spirits to Flask-Themes2, to honor
+ the roots of the project.
+
+ So here we are, in 2013 (or forward, if this isn't removed by someone else),
+ using an up to date theming module, making life easier for developers all over.
+
+ You probably already know this, but you can find Flask-Themes2 at my GitHub repo,
+ <a href="https://github.com/plausibility/Flask-Themes2">plausibility/Flask-Themes2</a>!
+
+---
+
slug: introduction
created: 2010-07-01 16:00:00
title: Introduction
View
2  example/themes/plain/info.json
@@ -4,5 +4,5 @@
"name": "Plain",
"author": "LeafStorm",
"license": "MIT/X11",
- "description": "An easy-to-read, green-based theme."
+ "description": "An easy-to-read, green-based theme!"
}
View
9 example/themesandbox.py
@@ -10,7 +10,8 @@
"""
import yaml
from flask import (Flask, url_for, redirect, session, Markup, abort)
-from flask.ext.themes2 import (setup_themes, render_theme_template, get_themes_list)
+#from flask.ext.themes2 import (setup_themes, render_theme_template, get_themes_list)
+from flask.ext.themes2 import (Themes, render_theme_template, get_themes_list)
from operator import attrgetter
# default settings
@@ -23,7 +24,7 @@
app = Flask(__name__)
app.config.from_object(__name__)
-setup_themes(app, app_identifier='themesandbox')
+Themes(app, app_identifier='themesandbox')
# data
@@ -114,6 +115,10 @@ def settheme(ident):
session['theme'] = ident
return redirect(url_for('themes'))
+@app.route("/refresh")
+def refresh():
+ app.theme_manager.refresh()
+ return redirect(url_for('themes'))
if __name__ == '__main__':
app.run(debug=True)
View
512 flask_themes2/__init__.py
@@ -14,12 +14,19 @@
:copyright: 2013 Christopher Carter, 2012 Drew Lustro, 2010 Matthew "LeafStorm" Frazier
:license: MIT/X11, see LICENSE for details
"""
+
from __future__ import with_statement
+
+from .version import __version__
+
+## Yarg, here be pirates!
+from operator import attrgetter
import itertools
import os
import os.path
import re
+
from flask import (Module, send_from_directory, render_template, json,
abort, url_for, Blueprint)
@@ -29,10 +36,9 @@
except ImportError:
from flask import _request_ctx_stack as stack
-
from jinja2 import contextfunction
from jinja2.loaders import FileSystemLoader, BaseLoader, TemplateNotFound
-from operator import attrgetter
+
from werkzeug import cached_property
DOCTYPES = 'html4 html5 xhtml'.split()
@@ -40,114 +46,119 @@
containable = lambda i: i if hasattr(i, '__contains__') else tuple(i)
-
def starchain(i):
return itertools.chain(*i)
-class Theme(object):
- """
- This contains a theme's metadata.
-
- :param path: The path to the theme directory.
- """
- def __init__(self, path):
- #: The theme's root path. All the files in the theme are under this
- #: path.
- self.path = os.path.abspath(path)
-
- with open(os.path.join(self.path, 'info.json')) as fd:
- self.info = i = json.load(fd)
+def active_theme(ctx):
+ if '_theme' in ctx:
+ return ctx['_theme']
+ elif ctx.name.startswith('_themes/'):
+ return ctx.name[8:].split('/', 1)[0]
+ else:
+ raise RuntimeError("Could not find the active theme")
- #: The theme's name, as given in info.json. This is the human
- #: readable name.
- self.name = i['name']
- #: The application identifier given in the theme's info.json. Your
- #: application will probably want to validate it.
- self.application = i['application']
+@contextfunction
+def global_theme_template(ctx, templatename, fallback=True):
+ theme = active_theme(ctx)
+ templatepath = '_themes/%s/%s' % (theme, templatename)
+ if (not fallback) or template_exists(templatepath):
+ return templatepath
+ else:
+ return templatename
- #: 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.
- self.identifier = i['identifier']
+@contextfunction
+def global_theme_static(ctx, filename, external=False):
+ theme = active_theme(ctx)
+ return static_file_url(theme, filename, external)
- #: The human readable description. This is the default (English)
- #: version.
- self.description = i.get('description')
+@contextfunction
+def global_theme_get_info(ctx, attribute_name, fallback=''):
+ theme = get_theme(active_theme(ctx))
+ try:
+ info = theme.getattr(attribute_name)
+ return info
+ except AttributeError:
+ pass
+ return theme.options.get(attribute_name, fallback)
- #: 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.
- self.localized_desc = dict(
- (k.split('_', 1)[1].lower(), v) for k, v in i.items()
- if k.startswith('description_')
- )
- self.localized_desc.setdefault('en', self.description)
+def static_file_url(theme, filename, external=False):
+ """
+ This is a shortcut for getting the URL of a static file in a theme.
- #: 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.
- self.author = i['author']
+ :param theme: A `Theme` instance or identifier.
+ :param filename: The name of the file.
+ :param external: Whether the link should be external or not. Defaults to
+ `False`.
+ """
+ if isinstance(theme, Theme):
+ theme = theme.identifier
+ return url_for('_themes.static', themeid=theme, filename=filename,
+ _external=external)
- #: A short phrase describing the license, like "GPL", "BSD", "Public
- #: Domain", or "Creative Commons BY-SA 3.0".
- self.license = i.get('license')
+def render_theme_template(theme, template_name, _fallback=True, **context):
+ """
+ This renders a template from the given theme. For example::
- #: A URL pointing to the license text online.
- self.license_url = i.get('license_url')
+ return render_theme_template(g.user.theme, 'index.html', posts=posts)
- #: The URL to the theme's or author's Web site.
- self.website = i.get('website')
+ If `_fallback` is True and the template 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.)
- #: The theme's preview image, within the static folder.
- self.preview = i.get('preview')
+ :param theme: Either the identifier of the theme to use, or an actual
+ `Theme` instance.
+ :param template_name: The name of the template to render.
+ :param _fallback: Whether to fall back to the default
+ """
+ if isinstance(theme, Theme):
+ theme = theme.identifier
+ context['_theme'] = theme
+ try:
+ return render_template('_themes/%s/%s' % (theme, template_name),
+ **context)
+ except TemplateNotFound:
+ if _fallback:
+ return render_template(template_name, **context)
+ else:
+ raise
- #: The theme's doctype. This can be ``html4``, ``html5``, or ``xhtml``
- #: with html5 being the default if not specified.
- self.doctype = i.get('doctype', 'html5')
+### convenience #########################################################
- #: Any additional options. These are entirely application-specific,
- #: and may determine other aspects of the application's behavior.
- self.options = i.get('options', {})
+def get_theme(ident):
+ """
+ This gets the theme with the given identifier from the current app's
+ theme manager.
- @cached_property
- def static_path(self):
- """
- The absolute path to the theme's static files directory.
- """
- return os.path.join(self.path, 'static')
+ :param ident: The theme identifier.
+ """
+ ctx = stack.top
+ return ctx.app.theme_manager.themes[ident]
- @cached_property
- def templates_path(self):
- """
- The absolute path to the theme's templates directory.
- """
- return os.path.join(self.path, 'templates')
- @cached_property
- def license_text(self):
- """
- 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.)
- """
- lt_path = os.path.join(self.path, 'license.txt')
- if os.path.exists(lt_path):
- with open(lt_path) as fd:
- return fd.read()
- else:
- return None
+def get_themes_list():
+ """
+ This returns a list of all the themes in the current app's theme manager,
+ sorted by identifier.
+ """
+ ctx = stack.top
+ return list(ctx.app.theme_manager.list_themes())
- @cached_property
- def jinja_loader(self):
- """
- This is a Jinja2 template loader that loads templates from the theme's
- ``templates`` directory.
- """
- return FileSystemLoader(self.templates_path)
+def static(themeid, filename):
+ try:
+ ctx = stack.top
+ theme = ctx.app.theme_manager.themes[themeid]
+ except KeyError:
+ abort(404)
+ return send_from_directory(theme.static_path, filename)
+def template_exists(templatename):
+ ctx = stack.top
+ return templatename in containable(ctx.app.jinja_env.list_templates())
-### theme loaders
+### loaders #############################################################
def list_folders(path):
"""
@@ -204,6 +215,89 @@ def theme_paths_loader(app):
load_themes_from(path) for path in theme_paths
)
+class ThemeTemplateLoader(BaseLoader):
+ """
+ This is a template loader that loads templates from the current app's
+ loaded themes.
+ """
+ def __init__(self, as_blueprint=False):
+ self.as_blueprint = as_blueprint
+ BaseLoader.__init__(self)
+
+ def get_source(self, environment, template):
+ if self.as_blueprint and template.startswith("_themes/"):
+ template = template[8:]
+ try:
+ themename, templatename = template.split('/', 1)
+ ctx = stack.top
+ theme = ctx.app.theme_manager.themes[themename]
+ except (ValueError, KeyError):
+ raise TemplateNotFound(template)
+ try:
+ return theme.jinja_loader.get_source(environment, templatename)
+ except TemplateNotFound:
+ raise TemplateNotFound(template)
+
+ def list_templates(self):
+ res = []
+ ctx = stack.top
+ fmt = '_themes/%s/%s'
+ for ident, theme in ctx.app.theme_manager.themes.iteritems():
+ res.extend((fmt % (ident, t)).encode("utf8")
+ for t in theme.jinja_loader.list_templates())
+ return res
+
+#########################################################################
+
+class Themes:
+ """ This is the main class you will use to interact
+ with Flask-Themes2 on your app.
+
+ It really only implements the bare minimum, the rest
+ is passed through to other methods and classes.
+ """
+
+ def __init__(self, app=None, **kwargs):
+ """ If given an app, this will simply call
+ init_themes, and pass through all kwargs
+ to init_themes, making it super easy.
+
+ :param app: the `~flask.Flask` instance to setup themes for.
+ :param \*\*kwargs: keyword args to pass through to init_themes
+ """
+ if app is not None:
+ self._app = app
+ self.init_themes(self._app, **kwargs)
+ else:
+ self._app = None
+
+ def init_themes(self, app, loaders=None, app_identifier=None,
+ manager_cls=None, theme_url_prefix="/_themes"):
+ """ This sets up the theme infrastructure by adding a `ThemeManager` to the
+ given app and registering the module/blueprint containing the views and
+ templates needed.
+
+ :param app: The `~flask.Flask` instance to set up themes for.
+ :param loaders: An iterable of loaders to use. It defaults to `packaged_themes_loader` and `theme_paths_loader`.
+ :param app_identifier: The application identifier to use. If not given, it defaults to the app's import name.
+ :param manager_cls: If you need a custom manager class, you can pass it in here.
+ :param theme_url_prefix: The prefix to use for the URLs on the themes module. (Defaults to ``/_themes``.)
+ """
+ if app_identifier is None:
+ app_identifier = app.import_name
+ if manager_cls is None:
+ manager_cls = ThemeManager
+ manager_cls(app, app_identifier, loaders=loaders)
+
+ themes_blueprint = Blueprint('_themes', __name__, url_prefix='/_themes')
+ themes_blueprint.jinja_loader
+ themes_blueprint.jinja_loader = ThemeTemplateLoader(True)
+ themes_blueprint.add_url_rule('/<themeid>/<path:filename>', 'static', view_func=static)
+
+ app.jinja_env.globals['theme'] = global_theme_template
+ app.jinja_env.globals['theme_static'] = global_theme_static
+ app.jinja_env.globals['theme_get_info'] = global_theme_get_info
+ app.register_blueprint(themes_blueprint, url_prefix=theme_url_prefix)
class ThemeManager(object):
"""
@@ -286,185 +380,103 @@ def refresh(self):
if self.valid_app_id(theme.application):
self.themes[theme.identifier] = theme
-
-def get_theme(ident):
- """
- This gets the theme with the given identifier from the current app's
- theme manager.
-
- :param ident: The theme identifier.
- """
- ctx = stack.top
- return ctx.app.theme_manager.themes[ident]
-
-
-def get_themes_list():
- """
- This returns a list of all the themes in the current app's theme manager,
- sorted by identifier.
- """
- ctx = stack.top
- return list(ctx.app.theme_manager.list_themes())
-
-
-### theme template loader
-
-class ThemeTemplateLoader(BaseLoader):
- """
- This is a template loader that loads templates from the current app's
- loaded themes.
+class Theme(object):
"""
- def __init__(self, as_blueprint=False):
- self.as_blueprint = as_blueprint
- BaseLoader.__init__(self)
-
- def get_source(self, environment, template):
- if self.as_blueprint and template.startswith("_themes/"):
- template = template[8:]
- try:
- themename, templatename = template.split('/', 1)
- ctx = stack.top
- theme = ctx.app.theme_manager.themes[themename]
- except (ValueError, KeyError):
- raise TemplateNotFound(template)
- try:
- return theme.jinja_loader.get_source(environment, templatename)
- except TemplateNotFound:
- raise TemplateNotFound(template)
-
- def list_templates(self):
- res = []
- ctx = stack.top
- fmt = '_themes/%s/%s'
- for ident, theme in ctx.app.theme_manager.themes.iteritems():
- res.extend((fmt % (ident, t)).encode("utf8")
- for t in theme.jinja_loader.list_templates())
- return res
-
-
-def template_exists(templatename):
- ctx = stack.top
- return templatename in containable(ctx.app.jinja_env.list_templates())
-
-
-def static(themeid, filename):
- try:
- ctx = stack.top
- theme = ctx.app.theme_manager.themes[themeid]
- except KeyError:
- abort(404)
- return send_from_directory(theme.static_path, filename)
-
-# Blueprint Themes Functionality
-
-themes_blueprint = Blueprint('_themes', __name__, url_prefix='/_themes')
-themes_blueprint.jinja_loader
-themes_blueprint.jinja_loader = ThemeTemplateLoader(True)
-themes_blueprint.add_url_rule('/<themeid>/<path:filename>', 'static',
- view_func=static)
-
+ This contains a theme's metadata.
-def setup_themes(app, loaders=None, app_identifier=None,
- manager_cls=ThemeManager, theme_url_prefix='/_themes'):
- """
- This sets up the theme infrastructure by adding a `ThemeManager` to the
- given app and registering the module/blueprint containing the views and
- templates needed.
-
- :param app: The `~flask.Flask` instance to set up themes for.
- :param loaders: An iterable of loaders to use. It defaults to
- `packaged_themes_loader` and `theme_paths_loader`.
- :param app_identifier: The application identifier to use. If not given,
- it defaults to the app's import name.
- :param manager_cls: If you need a custom manager class, you can pass it
- in here.
- :param theme_url_prefix: The prefix to use for the URLs on the themes
- module. (Defaults to ``/_themes``.)
+ :param path: The path to the theme directory.
"""
- if app_identifier is None:
- app_identifier = app.import_name
- manager_cls(app, app_identifier, loaders=loaders)
- app.jinja_env.globals['theme'] = global_theme_template
- app.jinja_env.globals['theme_static'] = global_theme_static
- app.jinja_env.globals['theme_get_info'] = global_theme_get_info
- app.register_blueprint(themes_blueprint, url_prefix=theme_url_prefix)
+ def __init__(self, path):
+ #: The theme's root path. All the files in the theme are under this
+ #: path.
+ self.path = os.path.abspath(path)
+ with open(os.path.join(self.path, 'info.json')) as fd:
+ self.info = i = json.load(fd)
-def active_theme(ctx):
- if '_theme' in ctx:
- return ctx['_theme']
- elif ctx.name.startswith('_themes/'):
- return ctx.name[8:].split('/', 1)[0]
- else:
- raise RuntimeError("Could not find the active theme")
+ #: The theme's name, as given in info.json. This is the human
+ #: readable name.
+ self.name = i['name']
+ #: The application identifier given in the theme's info.json. Your
+ #: application will probably want to validate it.
+ self.application = i['application']
-@contextfunction
-def global_theme_template(ctx, templatename, fallback=True):
- theme = active_theme(ctx)
- templatepath = '_themes/%s/%s' % (theme, templatename)
- if (not fallback) or template_exists(templatepath):
- return templatepath
- else:
- return templatename
+ #: 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.
+ self.identifier = i['identifier']
+ #: The human readable description. This is the default (English)
+ #: version.
+ self.description = i.get('description')
-@contextfunction
-def global_theme_static(ctx, filename, external=False):
- theme = active_theme(ctx)
- return static_file_url(theme, filename, external)
+ #: 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.
+ self.localized_desc = dict(
+ (k.split('_', 1)[1].lower(), v) for k, v in i.items()
+ if k.startswith('description_')
+ )
+ self.localized_desc.setdefault('en', self.description)
+ #: 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.
+ self.author = i['author']
-@contextfunction
-def global_theme_get_info(ctx, attribute_name, fallback=''):
- theme = get_theme(active_theme(ctx))
- try:
- info = theme.getattr(attribute_name)
- return info
- except AttributeError:
- pass
- return theme.options.get(attribute_name, fallback)
+ #: A short phrase describing the license, like "GPL", "BSD", "Public
+ #: Domain", or "Creative Commons BY-SA 3.0".
+ self.license = i.get('license')
+ #: A URL pointing to the license text online.
+ self.license_url = i.get('license_url')
-def static_file_url(theme, filename, external=False):
- """
- This is a shortcut for getting the URL of a static file in a theme.
+ #: The URL to the theme's or author's Web site.
+ self.website = i.get('website')
- :param theme: A `Theme` instance or identifier.
- :param filename: The name of the file.
- :param external: Whether the link should be external or not. Defaults to
- `False`.
- """
- if isinstance(theme, Theme):
- theme = theme.identifier
- return url_for('_themes.static', themeid=theme, filename=filename,
- _external=external)
+ #: The theme's preview image, within the static folder.
+ self.preview = i.get('preview')
+ #: The theme's doctype. This can be ``html4``, ``html5``, or ``xhtml``
+ #: with html5 being the default if not specified.
+ self.doctype = i.get('doctype', 'html5')
-def render_theme_template(theme, template_name, _fallback=True, **context):
- """
- This renders a template from the given theme. For example::
+ #: Any additional options. These are entirely application-specific,
+ #: and may determine other aspects of the application's behavior.
+ self.options = i.get('options', {})
- return render_theme_template(g.user.theme, 'index.html', posts=posts)
+ @cached_property
+ def static_path(self):
+ """
+ The absolute path to the theme's static files directory.
+ """
+ return os.path.join(self.path, 'static')
- 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.)
+ @cached_property
+ def templates_path(self):
+ """
+ The absolute path to the theme's templates directory.
+ """
+ return os.path.join(self.path, 'templates')
- :param theme: Either the identifier of the theme to use, or an actual
- `Theme` instance.
- :param template_name: The name of the template to render.
- :param _fallback: Whether to fall back to the default
- """
- if isinstance(theme, Theme):
- theme = theme.identifier
- context['_theme'] = theme
- try:
- return render_template('_themes/%s/%s' % (theme, template_name),
- **context)
- except TemplateNotFound:
- if _fallback:
- return render_template(template_name, **context)
+ @cached_property
+ def license_text(self):
+ """
+ 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.)
+ """
+ lt_path = os.path.join(self.path, 'license.txt')
+ if os.path.exists(lt_path):
+ with open(lt_path) as fd:
+ return fd.read()
else:
- raise
+ return None
+
+ @cached_property
+ def jinja_loader(self):
+ """
+ This is a Jinja2 template loader that loads templates from the theme's
+ ``templates`` directory.
+ """
+ return FileSystemLoader(self.templates_path)
View
1  flask_themes2/themes2.py
@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-
View
1  flask_themes2/version.py
@@ -0,0 +1 @@
+__version__ = "0.1.0"
View
4 setup.py
@@ -9,9 +9,11 @@ def long_desc():
with open('README.rst', 'rb') as f:
return f.read()
+execfile("flask_themes2/version.py")
+
kw = {
"name": "Flask-Themes2",
- "version": "0.1.0",
+ "version": __version__,
"url": "https://github.com/plausibility/Flask-Themes2",
"license": "MIT",
"author": "plausibility",
Please sign in to comment.
Something went wrong with that request. Please try again.