--- 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