Permalink
Browse files

Permit to TGController subclasses to keep the dececorations made on t…

…he parent class methods around
  • Loading branch information...
1 parent 4591163 commit d53e66986d9995c26dcc6fd1bb0fd5c6e1e20172 @amol- amol- committed Jun 7, 2012
@@ -1,7 +1,7 @@
"""Main Controller"""
from tg import expose, redirect, config, validate, override_template, response, render_template
-from tg.decorators import paginate, use_custom_format, with_trailing_slash, Decoration
+from tg.decorators import paginate, use_custom_format, with_trailing_slash, Decoration, before_render
from tg.controllers import TGController
from tw.forms import TableForm, TextField, CalendarDatePicker, SingleSelectField, TextArea
from tw.api import WidgetsList
@@ -51,9 +51,45 @@ def json_with_bad_object(self):
return dict(obj=BadJsonObject())
+class SubClassableController(TGController):
+ @expose('genshi:index.html')
+ def index(self):
+ return {}
+
+ @expose('genshi:index.html')
+ def index_override(self):
+ return {}
+
+ def before_render_data(remainder, params, output):
+ output['parent_value'] = 'PARENT'
+
+ @expose('json')
+ @before_render(before_render_data)
+ def data(self):
+ return {'v':5}
+
+class SubClassingController(SubClassableController):
+ @expose(inherit=True)
+ def index(self, *args, **kw):
+ return super(SubClassingController, self).index(*args, **kw)
+
+ @expose('genshi:genshi_doctype.html', inherit=True)
+ def index_override(self, *args, **kw):
+ return super(SubClassingController, self).index_override(*args, **kw)
+
+ def before_render_data(remainder, params, output):
+ output['child_value'] = 'CHILD'
+
+ @expose(inherit=True)
+ @before_render(before_render_data)
+ def data(self, *args, **kw):
+ return super(SubClassingController, self).data(*args, **kw)
+
class RootController(TGController):
j = JsonController()
+ sub1 = SubClassableController()
+ sub2 = SubClassingController()
@expose('genshi:index.html')
def index(self):
@@ -54,4 +54,24 @@ def test_json_with_bad_object(self):
def test_multiple_engines(self):
default_renderer = config['default_renderer']
resp = self.app.get('/multiple_engines')
- assert default_renderer in resp, resp
+ assert default_renderer in resp, resp
+
+class TestExposeInheritance(object):
+ def setup(self):
+ self.app = app
+
+ def test_inherited_expose_template(self):
+ resp1 = self.app.get('/sub1/index')
+ resp2 = self.app.get('/sub2/index')
+ assert resp1.body == resp2.body
+
+ def test_inherited_expose_override(self):
+ resp1 = self.app.get('/sub1/index_override')
+ resp2 = self.app.get('/sub2/index_override')
+ assert resp1.body != resp2.body
+
+ def test_inherited_expose_hooks(self):
+ resp1 = self.app.get('/sub1/data')
+ assert ('"v"' in resp1 and '"parent_value"' in resp1)
+ resp2 = self.app.get('/sub2/data')
+ assert ('"v"' in resp2 and '"parent_value"' in resp2 and '"child_value"' in resp2)
@@ -40,13 +40,25 @@ def strip_string(s):
# override pylons.request.content_type
CUSTOM_CONTENT_TYPE = 'CUSTOM/LEAVE'
+class _DecoratedControllerMeta(type):
+ def __init__(cls, name, bases, attrs):
+ super(_DecoratedControllerMeta, cls).__init__(name, bases, attrs)
+ for name, value in attrs.items():
+ #Inherit decorations for methods exposed with inherit=True
+ if hasattr(value, 'decoration') and value.decoration.inherit:
+ for pcls in reversed(bases):
+ parent_method = getattr(pcls, name, None)
+ if parent_method and hasattr(parent_method, 'decoration'):
+ value.decoration.merge(parent_method.decoration)
+
class DecoratedController(object):
"""Decorated controller object.
Creates an interface to hang decoration attributes on
controller methods for the purpose of rendering web content.
"""
+ __metaclass__ = _DecoratedControllerMeta
def _is_exposed(self, controller, name):
if hasattr(controller, name):
View
@@ -39,7 +39,7 @@ def __init__(self, controller):
self.custom_engines = {}
self.render_custom_format = None
self.validation = None
- self.error_handler = None
+ self.inherit = False
self.hooks = dict(before_validate=[],
before_call=[],
before_render=[],
@@ -55,6 +55,19 @@ def get_decoration(cls, func):
def exposed(self):
return bool(self.engines) or bool(self.custom_engines)
+ def merge(self, deco):
+ self.engines = dict(deco.engines.items() + self.engines.items())
+ self.engines_keys = sorted(self.engines, reverse=True)
+ self.custom_engines = dict(deco.custom_engines.items() + self.engines.items())
+
+ #inherit all the parent hooks
+ #parent hooks before current hooks so that they get called before
+ for hook_name, hooks in deco.hooks.items():
+ self.hooks[hook_name] = hooks + self.hooks[hook_name]
+
+ if not self.validation:
+ self.validation = deco.validation
+
def run_hooks(self, hook, *l, **kw):
for func in config.get('hooks', {}).get(hook, []):
func(*l, **kw)
@@ -212,8 +225,10 @@ class _hook_decorator(object):
hook_name = None
def __init__(self, hook_func):
- self.__name__ = hook_func.__name__
- self.__doc__ = hook_func.__doc__
+ if hasattr(hook_func, '__name__'):
+ self.__name__ = hook_func.__name__
+ if hasattr(hook_func, '__doc__'):
+ self.__doc__ = hook_func.__doc__
self.hook_func = hook_func
def __call__(self, func):
@@ -263,8 +278,16 @@ class expose(object):
The default content type is 'text/html'.
exclude_names
Assign exclude names
+ custom_format
+ Registers as a custom format which can later be activated calling
+ use_custom_format
render_params
Assign parameters that shall be passed to the rendering method.
+ inherit
+ Inherit all the decorations from the same method in the parent
+ class. This will let the exposed method expose the same template
+ as the overridden method template and keep the same hooks and
+ validation that the parent method had.
The expose decorator registers a number of attributes on the
decorated function, but does not actually wrap the function the way
@@ -319,7 +342,7 @@ def my_exposed_method(self):
"""
def __init__(self, template='', content_type=None, exclude_names=None,
- custom_format=None, render_params=None):
+ custom_format=None, render_params=None, inherit=False):
if exclude_names is None:
exclude_names = []
@@ -351,9 +374,15 @@ def __init__(self, template='', content_type=None, exclude_names=None,
self.exclude_names = exclude_names
self.custom_format = custom_format
self.render_params = render_params
+ self.inherit = inherit
def __call__(self, func):
deco = Decoration.get_decoration(func)
+ if self.inherit:
+ deco.inherit = True
+ if not self.template and not self.engine:
+ return func
+
if self.custom_format:
deco.register_custom_template_engine(
self.custom_format, self.content_type, self.engine,

0 comments on commit d53e669

Please sign in to comment.