Here, let's take a look at an easy way to make your application
themeable. Although you can get a lot done with just CSS, I am going to
be showing how to replace templates to accomplish theming as it is more
useful. I'll be linking to useful resources if I am covering anything
that might not be immediately easy to grasp. All of these examples come
from a webmail project I am working on (currently dubbed ThoughtMail).
The first thing we are going to do is make a change to our setup.py
file that declares (and fills) an entry point for themes. (for more
info see the first section of:
http://docs.turbogears.org/1.0/CommandPlugins)
Here is a sample entry point:
"""
[thoughtmail.themes]
default = thoughtmail.themepackages:Default
"""
Briefly what we are doing here is declaring a name (thoughtmail.themes)
that we will use later to have setuptools look for code that is
available to use. We are also informing setuptools that the "Default"
class in the "thoughtmail.themepackages" module should be included in
the list for "thoughtmail.themes" and named "default".
Now we are just a quick package registration (python setup.py develop)
away from getting started.
Since I don't want to keep a list of every template name, lets cheat a
bit. In the themepackages.py file we'll build our Default class so all
it does is put the appropriate namespace on the name it is given. Here
is what it looks like:
class Default:
desc = "Default theme for ThoughtMail" # This is not required by
setuptools, but is useful.
def __getattr__(self, item):
"""
Overload getattr so calls to Default().attr return
"kid:thoughtmail.templates.default.attr"
"""
if item[0]=='_':
return item
return "kid:thoughtmail.templates.default." + item
So a call to Default().index will return
"kid:thoughtmail.templates.default.index".
This is only half the equation though, as we still need to allow our
users to select a theme. One way of solving that is to rely on a
combination of our previously defined entry points and the TurboGears
config system. When it is started ThoughtMail will look for a
thoughtmail.theme config item grab a list of available themes through
setuptools, and use the match if it is found. Here is the code that
does that:
import turbogears.config
import pkg_resources
class ThoughtMail:
def __init__(self):
get = turbogears.config.get
# look for any provided theme libraries, if none are given use
the default
theme = get("thoughtmail.theme",None)
if not theme:
for entrypoint in
pkg_resources.iter_entry_points("thoughtmail.themes"):
if entrypoint.name == theme:
my = entrypoint.load()
else:
my =
pkg_resources.load_entry_point('thoughtmail','thoughtmail.themes','default')
self.theme = my()
Here's the important bits:
We are using turbogears.config.get to retrieve the value (if any) of
thoughtmail.theme from our application's config files. Then, if we
found a match the relevant class is imported and set to the wonderfully
descriptive (but usefully short) name 'my'. The above mentioned
'Default' theme is used if we can't find anything else.
Now that that is done we are just about home free. Since I am storing
this information as a member variable of the ThoughtMail class, it will
normally be invisible to @expose. One way around this is to set the
template in the returned dictionary through the 'tg_template' key. Here
is a sample index using this method.
@expose()
def index(self):
return dict(data="foo", tg_template=self.templates.index)
Now the template for the index page will be set as though you had used
@expose to directly reference the template library for the theme you
are using. This is kind of messy though, and I would hate to add this
tg_template key to each of my methods, so lets cheat, again.
def tdict(self, name, **kw):
"""
Create a dictionary with the given args and a tg_template key set
to 'name'
"""
return dict(tg_template=name).update(kw)
Now we can change our index to make it a little shorter:
@expose()
def index(self):
return tdict(self.templates.index, data="foo")
You can possibly get something shorter as well, if you want to spend
the effort writing a custom decorator.
Writing a new theme package now becomes a matter of creating an egg
containing your theme files, classes that point to those files, and
some entry_point listings that point to those classes.
That is pretty much it. A smattering of little bits of code in a few
places and all someone has to do is install a theme library and set a
config variable to change the look of your application. The nice part
about this solution is that, since it is all done at runtime, it is
pretty easy to allow for per-user themes as well.
Although the above code only deals with themes; plugins and extensions
are conceptually similar. You define an entry point and find a way to
choose a specific item out of the list for that entry point, and it is
pretty much strait Python classes from there.
Hope you enjoyed it,
-Adam
thanks again!
-ian
Lee
On 11/19/06, Adam Jones <ajo...@gmail.com> wrote:
>
> Yes, that should be "if theme:" I was paring down the full class into
> this example and evidently messed up the translation a bit.
>
>
> >
>
--
Lee McFadden
blog: http://www.splee.co.uk
work: http://fireflisystems.com
No, I don't mind. It could probably be less chatty, and it would be
nice to have a separate page explaining just how entry_points are (or
can be, as the case may be) used in TurboGears. I'll work on it later
today.
-Adam