Added "ajax_config" class decorator
authorThierry Florac <thierry.florac@onf.fr>
Thu, 07 Jun 2018 14:08:39 +0200
changeset 108 87ee710b7639
parent 107 fec6b5f9601e
child 109 48697c7b632c
Added "ajax_config" class decorator
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