# HG changeset patch # User Thierry Florac # Date 1528373319 -7200 # Node ID 87ee710b7639655eb022394c8f49eff8e0a0f3a6 # Parent fec6b5f9601ee7df70ff170a76e451d02d594961 Added "ajax_config" class decorator diff -r fec6b5f9601e -r 87ee710b7639 src/pyams_form/form.py --- a/src/pyams_form/form.py Wed May 30 16:26:00 2018 +0200 +++ b/src/pyams_form/form.py Thu Jun 07 14:08:39 2018 +0200 @@ -9,12 +9,17 @@ # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # +from zope.interface.declarations import classImplements __docformat__ = 'restructuredtext' # import standard library import json +import logging +logger = logging.getLogger('PyAMS (form)') + +import venusian # import interfaces from pyams_form.interfaces.form import IFormLayer, IForm, IAJAXForm, IInnerSubForm, IInnerTabForm, \ @@ -657,3 +662,88 @@ if isinstance(event.form, intf): return True return False + + +class ajax_config(object): + """Class decorator used to declare AJAX settings for a form. + + When decorating a form class, this decorator create a new subclass which will handle AJAX queries + executed when submitting the form, and register this class as Pyramid's view. + + Decorator arguments (all optional) are: + + - **name**: AJAX view name + - **context** (or **for_**): view context type + - **layer** (or **request_type**): request type for which view is registered + - **permission**: permission required to call the view; if not set, permission is extracted from form's + "edit_permission" attribute + - **base**: base class for newly created AJAX form; if not set, inherits from :py:class:`AJAXEditForm` + - **implementer**: list of interfaces implemented by the new class + - **method** (or **request_method**): HTTP method name; if not defined, view is restricted to "POST" requests + - **renderer**: name of Pyramid renderer used to return view output; defaults to 'json' + - **xhr**: Pyramid's view's "xhr" predicate; **True** by default + """ + + venusian = venusian # for testing injection + + def __init__(self, **settings): + if 'for_' in settings: + if settings.get('context') is None: + settings['context'] = settings.pop('for_') + if 'layer' in settings: + settings['request_type'] = settings.pop('layer') + if 'base' not in settings: + settings['base'] = AJAXEditForm + if 'renderer' not in settings: + settings['renderer'] = 'json' + if 'xhr' not in settings: + settings['xhr'] = True + if 'method' in settings: + settings['request_method'] = settings.pop('method') + else: + settings['request_method'] = 'POST' + self.__dict__.update(settings) + + def __call__(self, wrapped): + settings = self.__dict__.copy() + depth = settings.pop('_depth', 0) + + def callback(context, name, ob): + cdict = { + '__name__': settings.get('name'), + '__module__': ob.__module__, + 'permission': settings.get('permission') or ob.edit_permission + } + + # Set form's AJAX handler + ob.ajax_handler = settings.get('name') + + # Create new AJAX form and register view + base = settings.pop('base') + new_class = type('AJAX' + ob.__name__, (base, ob), cdict) + + if 'implementer' in settings: + implementer = settings.pop('implementer') + if not isinstance(implementer, (list, tuple, set)): + implementer = (implementer, ) + classImplements(new_class, *implementer) + + logger.debug('Registering AJAX view "{0}" for {1} ({2})'.format(settings.get('name'), + str(settings.get('context', Interface)), + str(new_class))) + + config = context.config.with_package(info.module) + config.add_view(view=new_class, **settings) + + info = self.venusian.attach(wrapped, callback, category='pyams_form', + depth=depth + 1) + + if info.scope == 'class': + # if the decorator was attached to a method in a class, or + # otherwise executed at class scope, we need to set an + # 'attr' into the settings if one isn't already in there + if settings.get('attr') is None: + settings['attr'] = wrapped.__name__ + + settings['_info'] = info.codeinfo # fbo "action_method" + return wrapped