diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..84da1b5 --- /dev/null +++ b/setup.py @@ -0,0 +1,44 @@ +from setuptools import setup, find_packages +import sys, os + +version = '1.0' +shortdesc = 'Handle web application parts as tiles.' +longdesc = open(os.path.join(os.path.dirname(__file__), 'src', 'bda', 'bfg', + 'tile', '_api.txt')).read() + +setup(name='bda.bfg.tile', + version=version, + description=shortdesc, + long_description=longdesc, + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Web Environment', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + ], + keywords='', + author='BlueDynamics Alliance', + author_email='dev@bluedynamics.com', + url=u'https://svn.bluedynamics.net/svn/internal/bda.tile', + license='GNU General Public Licence', + packages=find_packages('src'), + package_dir = {'': 'src'}, + namespace_packages=['bda', 'bda.bfg'], + include_package_data=True, + zip_safe=False, + install_requires=[ + 'setuptools', + 'repoze.bfg', + ], + dependency_links = [ + "http://dist.repoze.org/bfg/1.0/", + ], + extras_require = dict( + test=[ + 'interlude', + ] + ), + tests_require=['interlude'], + test_suite="bda.bfg.tile.tests.test_suite", + ) \ No newline at end of file diff --git a/src/bda/__init__.py b/src/bda/__init__.py new file mode 100644 index 0000000..b0d6433 --- /dev/null +++ b/src/bda/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file diff --git a/src/bda/bfg/__init__.py b/src/bda/bfg/__init__.py new file mode 100644 index 0000000..b0d6433 --- /dev/null +++ b/src/bda/bfg/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file diff --git a/src/bda/bfg/tile/__init__.py b/src/bda/bfg/tile/__init__.py new file mode 100644 index 0000000..f521de3 --- /dev/null +++ b/src/bda/bfg/tile/__init__.py @@ -0,0 +1,7 @@ +from _api import render_template +from _api import render_template_to_response +from _api import ITile +from _api import TileRenderer +from _api import Tile +from _api import registerTile +from _api import tile diff --git a/src/bda/bfg/tile/_api.py b/src/bda/bfg/tile/_api.py new file mode 100644 index 0000000..80df1c0 --- /dev/null +++ b/src/bda/bfg/tile/_api.py @@ -0,0 +1,176 @@ +import os +from zope.interface import Interface, Attribute, implements +from zope.component import queryUtility +from zope.component import getMultiAdapter +from zope.component import ComponentLookupError +from repoze.bfg.threadlocal import get_current_registry +from webob import Response +from webob.exc import HTTPFound +from repoze.bfg.interfaces import IRequest +from repoze.bfg.interfaces import IResponseFactory +from repoze.bfg.interfaces import IAuthenticationPolicy +from repoze.bfg.interfaces import IViewPermission +from repoze.bfg.path import caller_package +from repoze.bfg.renderers import template_renderer_factory +from repoze.bfg.chameleon_zpt import ZPTTemplateRenderer +#from repoze.bfg.security import ViewPermissionFactory +from repoze.bfg.security import has_permission + +class ITile(Interface): + """returns on call some HTML snippet.""" + + def __call__(model, request): + """Renders the tile. + + It's intended to work this way: First it calls its own prepare method, + then it checks its own show attribute. If this returns True it renders + the template in the context of the ITile implementing class instance. + """ + + def prepare(): + """Prepares the tile. + + I.e. fetch data to display ... + """ + + show = Attribute("""Render this tile?""") + +def _update_kw(**kw): + if not ('request' in kw and 'model' in kw): + raise ValueError, "Eexpected kwargs missing: model, request." + kw.update({'tile': TileRenderer(kw['model'], kw['request'])}) + return kw + +def _redirect(kw): + if kw['request'].environ.get('redirect'): + return True + return False + +def render_template(path, **kw): + kw = _update_kw(**kw) + if _redirect(kw): + return u'' + renderer = template_renderer_factory(path, ZPTTemplateRenderer) + return renderer(kw, {}) + +def render_template_to_response(path, **kw): + kw = _update_kw(**kw) + kw['request'].environ['redirect'] = None + renderer = template_renderer_factory(path, ZPTTemplateRenderer) + result = renderer(kw, {}) + if _redirect(kw): + return HTTPFound(location=kw['request'].environ['redirect']) + response_factory = queryUtility(IResponseFactory, default=Response) + return response_factory(result) + +class Tile(object): + implements(ITile) + + def __init__(self, path, attribute): + self.path = path + self.attribute = attribute + + def __call__(self, model, request): + self.model = model + self.request = request + self.prepare() # XXX maybe remove. + if not self.show: + return u'' + if self.path: + try: + # XXX: do not catch exception. + return render_template(self.path, request=request, + model=model, context=self) + except Exception, e: + return u"Error:
%s
" % e + renderer = getattr(self, self.attribute) + result = renderer() + return result + + @property + def show(self): + return True + + def prepare(self): + pass + + def render(self): + return u'' + + def redirect(self, url): + self.request.environ['redirect'] = url + + @property + def nodeurl(self): + relpath = [p for p in self.model.path if p is not None] + return '/'.join([self.request.application_url] + relpath) + +class TileRenderer(object): + + def __init__(self, model, request): + self.model, self.request = model, request + + def __call__(self, name): + registry = get_current_registry() + # XXX fix me. new API in repoze.bfg + #secured = not not registry.queryUtility(IAuthenticationPolicy) + #if secured: + # permitted = registry.getMultiAdapter((self.model, self.request), + # IViewPermission, + # name=name) + # if not permitted: + # return u'permission denied' + try: + tile = getMultiAdapter((self.model, self.request), ITile, name=name) + except ComponentLookupError, e: + return u"Tile with name '%s' not found:
%s
" % \ + (name, e) + return tile + +# Registration +def registerTile(name, path=None, attribute='render', + interface=Interface, _class=Tile, permission='view'): + if isinstance(interface, basestring): + pass # XXX: lookup + if path: + if not (':' in path or os.path.isabs(path)): + caller = caller_package(level=1) + path = '%s:%s' % (caller.__name__, path) + factory = _class(path, attribute) + registry = get_current_registry() + registry.registerAdapter(factory, [interface, IRequest], + ITile, name, event=False) + # XXX fix me. new API in repoze.bfg + #if permission: + # factory = ViewPermissionFactory(permission) + # registry.registerAdapter(factory, [interface, IRequest], + # IViewPermission, name) + +class tile(object): + """Tile decorator. + """ + + def __init__(self, name, path=None, attribute='render', + interface=Interface, permission='view', level=2): + """name to register as, path to template, interface adapting to. + level is a special to make doctests pass the magic path-detection. + you should never need latter. + """ + self.name = name + self.path = path + if path: + if not (':' in path or os.path.isabs(path)): + caller = caller_package(level) + self.path = '%s:%s' % (caller.__name__, path) + self.attribute = attribute + self.interface = interface + self.permission = permission + + def __call__(self, ob): + registerTile(self.name, + self.path, + self.attribute, + interface=self.interface, + _class=ob, + permission=self.permission) + return ob \ No newline at end of file diff --git a/src/bda/bfg/tile/_api.txt b/src/bda/bfg/tile/_api.txt new file mode 100644 index 0000000..e44436a --- /dev/null +++ b/src/bda/bfg/tile/_api.txt @@ -0,0 +1,63 @@ +A tile is a piece of web application, i.e. a form, a navigation, etc. + +Splitting your application in such small and logic application parts makes it +easy to re-use this application, simplifies application ajaxification and +the use of same application parts in different manners. + +Imports.: + + >>> from bda.bfg.tile import Tile + >>> from bda.bfg.tile import TileRenderer + >>> from bda.bfg.tile import registerTile + >>> from bda.bfg.tile import tile + +We need some dummies as model and request.: + + >>> class Model(object): pass + >>> model = Model() + >>> from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES + >>> request = DEFAULT_REQUEST_FACTORIES[None]['factory'](environ={}) + +The pure Tile itself. Normally you do not create this directly, this is done +due registration, see below.: + + >>> mytile = Tile('testdata/tile1.pt', None) + >>> mytile(model, request) + u'Tile One' + +Register a tile using the prior template testtemplate. When no object is given, +the default tile is instanciated as above.: + + >>> registerTile('tileone', 'testdata/tile1.pt') + +Render the already registered tile.: + + >>> TileRenderer(model, request)('tileone') + u'Tile One' + +Now the decorator - level=1 is needed for the doctest only to reduce the module +level.: + + >>> @tile('tiletwo', 'testdata/tile2.pt', level=1) + ... class Tile2(Tile): + ... data = u'custom' + >>> TileRenderer(model, request)('tiletwo') + u'Tile Two: Tile One' + +You can define an attribute which is responsible to render the tile instead of +defining a template. By default ``render`` is taken. With the keyword argument +``attribute`` you can point to a different attribute.: + + >>> @tile('attrtile') + ... class Tile2(Tile): + ... def render(self): + ... return u'

Rendered via attribute call

' + >>> TileRenderer(model, request)('attrtile') + u'

Rendered via attribute call

' + + >>> @tile('attrtile', attribute='foobar') + ... class Tile2(Tile): + ... def foobar(self): + ... return u'

Rendered via attribute foobar call

' + >>> TileRenderer(model, request)('attrtile') + u'

Rendered via attribute foobar call

' diff --git a/src/bda/bfg/tile/testdata/main1.pt b/src/bda/bfg/tile/testdata/main1.pt new file mode 100644 index 0000000..79e83d0 --- /dev/null +++ b/src/bda/bfg/tile/testdata/main1.pt @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/bda/bfg/tile/testdata/main2.pt b/src/bda/bfg/tile/testdata/main2.pt new file mode 100644 index 0000000..c44fc94 --- /dev/null +++ b/src/bda/bfg/tile/testdata/main2.pt @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/bda/bfg/tile/testdata/main3.pt b/src/bda/bfg/tile/testdata/main3.pt new file mode 100644 index 0000000..06c70ab --- /dev/null +++ b/src/bda/bfg/tile/testdata/main3.pt @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/bda/bfg/tile/testdata/tile1.pt b/src/bda/bfg/tile/testdata/tile1.pt new file mode 100644 index 0000000..d6e54b2 --- /dev/null +++ b/src/bda/bfg/tile/testdata/tile1.pt @@ -0,0 +1 @@ +Tile One \ No newline at end of file diff --git a/src/bda/bfg/tile/testdata/tile2.pt b/src/bda/bfg/tile/testdata/tile2.pt new file mode 100644 index 0000000..c9fa9cb --- /dev/null +++ b/src/bda/bfg/tile/testdata/tile2.pt @@ -0,0 +1 @@ +Tile Two: \ No newline at end of file diff --git a/src/bda/bfg/tile/testdata/tile2_1.pt b/src/bda/bfg/tile/testdata/tile2_1.pt new file mode 100644 index 0000000..eb711e1 --- /dev/null +++ b/src/bda/bfg/tile/testdata/tile2_1.pt @@ -0,0 +1 @@ +second level \ No newline at end of file diff --git a/src/bda/bfg/tile/testdata/tile3.pt b/src/bda/bfg/tile/testdata/tile3.pt new file mode 100644 index 0000000..de2d0bc --- /dev/null +++ b/src/bda/bfg/tile/testdata/tile3.pt @@ -0,0 +1 @@ +Tile Three: \ No newline at end of file diff --git a/src/bda/bfg/tile/tests.py b/src/bda/bfg/tile/tests.py new file mode 100644 index 0000000..a4c9ecf --- /dev/null +++ b/src/bda/bfg/tile/tests.py @@ -0,0 +1,25 @@ +import unittest +import doctest +from pprint import pprint +from interlude import interact + +optionflags = doctest.NORMALIZE_WHITESPACE | \ + doctest.ELLIPSIS #| \ + #doctest.REPORT_ONLY_FIRST_FAILURE + +TESTFILES = [ + '_api.txt', +] + +def test_suite(): + return unittest.TestSuite([ + doctest.DocFileSuite( + file, + optionflags=optionflags, + globs={'interact': interact, + 'pprint': pprint}, + ) for file in TESTFILES + ]) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') \ No newline at end of file