# -*- test-case-name: xmantissa.test.test_theme -*-
import os, sys
from zope.interface import implements
from twisted.python import reflect
from twisted.python.util import sibpath
from nevow.loaders import xmlfile
from nevow import inevow, tags, athena, page, stan
from nevow.url import URL
from xmantissa import ixmantissa
from xmantissa.offering import getInstalledOfferings, getOfferings
def getAllThemes():
l = []
for offering in getOfferings():
l.extend(offering.themes)
l.sort(key=lambda o: o.priority)
l.reverse()
return l
def getInstalledThemes(store):
l = []
for offering in getInstalledOfferings(store).itervalues():
l.extend(offering.themes)
l.sort(key=lambda o: o.priority)
l.reverse()
return l
_marker = object()
def getLoader(n, default=_marker):
"""
Deprecated. Don't call this.
"""
for t in getAllThemes():
fact = t.getDocFactory(n, None)
if fact is not None:
return fact
if default is _marker:
raise RuntimeError("No loader for %r anywhere" % (n,))
return default
class XHTMLDirectoryTheme(object):
"""
I am a theme made up of a directory full of XHTML templates.
The suggested use for this class is to make a subclass,
C{YourThemeSubclass}, in a module in your Mantissa package, create a
directory in your package called 'yourpackage/themes/<your theme name>',
and then pass <your theme name> as the constructor to C{YourThemeSubclass}
when passing it to the constructor of L{xmantissa.offering.Offering}. You
can then avoid calculating the path name in the constructor, since it will
be calculated based on where your subclass was defined.
@ivar directoryName: the name of the directory containing the appropriate
template files.
@ivar themeName: the name of the theme that this directory represents.
This will be displayed to the user.
"""
implements(ixmantissa.ITemplateNameResolver)
def __init__(self, themeName, priority=0, directoryName=None):
"""
Create a theme based off of a directory full of XHTML templates.
@param themeName: sets my themeName
@param priority: an integer that affects the ordering of themes
returned from L{getAllThemes}.
@param directoryName: If None, calculates the directory name based on
the module the class is defined in and the given theme name. For a
subclass C{bar.baz.FooTheme} defined in C{bar/baz.py} the instance
C{FooTheme('qux')}, regardless of where it is created, will have a
default directoryName of {bar/themes/qux/}.
"""
self.themeName = themeName
self.priority = priority
self.cachedLoaders = {}
if directoryName is None:
directoryName = os.path.join(
sibpath(sys.modules[self.__class__.__module__].__file__,
'themes'),
self.themeName)
self.directoryName = directoryName
def head(self, request, website):
"""
Provide content to include in the head of the document. Override this.
@type request: L{inevow.IRequest} provider
@param request: The request object for which this is a response.
@param website: The site-wide L{xmantissa.website.WebSite} instance.
Primarily of interest for its C{cleartextRoot} and C{encryptedRoot}
methods.
@return: Anything providing or adaptable to L{nevow.inevow.IRenderer},
or C{None} to include nothing.
"""
# IThemePreferrer
def getDocFactory(self, fragmentName, default=None):
if fragmentName in self.cachedLoaders:
return self.cachedLoaders[fragmentName]
p = os.path.join(self.directoryName, fragmentName + '.html')
if os.path.exists(p):
loader = xmlfile(p)
self.cachedLoaders[fragmentName] = loader
return loader
return default
class MantissaTheme(XHTMLDirectoryTheme):
def head(self, request, website):
root = website.maybeEncryptedRoot(request.getHeader('host'))
return tags.link(
rel='stylesheet',
type='text/css',
href=root.child('Mantissa').child('mantissa.css'))
class _ThemedMixin(object):
"""
Mixin for L{nevow.inevow.IRenderer} implementations which want to use the
theme system.
"""
implements(ixmantissa.ITemplateNameResolver)
def __init__(self, fragmentParent=None):
"""
Create a themed fragment with the given parent.
@param fragmentParent: An object to pass to C{setFragmentParent}. If
not None, C{self.setFragmentParent} is called immediately. It is
suggested but not required that you set this here; if not, the
resulting fragment will be initialized in an inconsistent state. You
must call setFragmentParent to correct this before this fragment is
rendered.
"""
super(_ThemedMixin, self).__init__()
if fragmentParent is not None:
self.setFragmentParent(fragmentParent)
def head(self):
"""
Don't do anything.
"""
def rend(self, context, data):
"""
Automatically retrieve my C{docFactory} based on C{self.fragmentName}
before invoking L{athena.LiveElement.rend}.
"""
if self.docFactory is None:
self.docFactory = self.getDocFactory(self.fragmentName)
return super(_ThemedMixin, self).rend(context, data)
def pythonClass(self, request, tag):
"""
This renderer is available on all themed fragments. It returns the fully
qualified python name of the class of the fragment being rendered.
"""
return reflect.qual(self.__class__)
page.renderer(pythonClass)
def render_pythonClass(self, ctx, data):
return self.pythonClass(inevow.IRequest(ctx), ctx.tag)
_website = None
def getWebSite(self):
"""
Retrieve the L{xmantissa.website.WebSite} instance installed on the
site-store.
"""
if self._website is None:
siteStore = self.store
if siteStore.parent is not None:
siteStore = siteStore.parent
self._website = inevow.IResource(siteStore)
return self._website
# ITemplateNameResolver
def getDocFactory(self, fragmentName, default=None):
f = getattr(self.page, "getDocFactory", getLoader)
return f(fragmentName, default)
class ThemedFragment(_ThemedMixin, athena.LiveFragment):
"""
Subclass me to create a LiveFragment which supports automatic
theming. (Deprecated)
@ivar fragmentName: A short string naming the template from which the
docFactory for this fragment should be loaded.
@see ThemedElement
"""
fragmentName = 'fragment-no-fragment-name-specified'
class ThemedElement(_ThemedMixin, athena.LiveElement):
"""
Subclass me to create a LiveElement which supports automatic theming.
@ivar fragmentName: A short string naming the template from which the
docFactory for this fragment should be loaded.
"""
fragmentName = 'element-no-fragment-name-specified'
syntax highlighted by Code2HTML, v. 0.9.1