merge default doc-dc
authorDamien Correia
Fri, 08 Jun 2018 10:35:42 +0200
branchdoc-dc
changeset 659 8ba76f7719b4
parent 658 b0bfac7e3160 (current diff)
parent 591 b694d5667d17 (diff)
child 660 b977c7da3074
merge default
--- a/src/pyams_content/component/association/zmi/paragraph.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/association/zmi/paragraph.py	Fri Jun 08 10:35:42 2018 +0200
@@ -33,13 +33,13 @@
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
     BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_widget_refresh_event
 from pyams_utils.adapter import adapter_config
 from pyams_utils.traversing import get_parent
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer
 
@@ -59,6 +59,8 @@
 
 @pagelet_config(name='add-association-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-association-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class AssociationParagraphAddForm(AdminDialogAddForm):
     """Association paragraph add form"""
 
@@ -66,7 +68,6 @@
     icon_css_class = 'fa fa-fw fa-link'
 
     fields = field.Fields(IAssociationParagraph).select('title', 'renderer')
-    ajax_handler = 'add-association-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def create(self, data):
@@ -76,14 +77,9 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-association-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class AssociationParagraphAJAXAddForm(BaseParagraphAJAXAddForm, AssociationParagraphAddForm):
-    """Association paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IAssociationParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IAssociationParagraph, layer=IPyAMSLayer, base=BaseParagraphAJAXEditForm)
 class AssociationParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Association paragraph properties edit form"""
 
@@ -100,23 +96,17 @@
     fields = field.Fields(IAssociationParagraph).select('title', 'renderer')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
 
-@view_config(name='properties.json', context=IAssociationParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class AssociationParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, AssociationParagraphPropertiesEditForm):
-    """Association paragraph properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IAssociationParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IAssociationParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm, IAssociationsParentForm)
 class AssociationParagraphInnerEditForm(AssociationParagraphPropertiesEditForm):
     """Association paragraph inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -125,14 +115,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IAssociationParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class AssociationParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, AssociationParagraphInnerEditForm):
-    """Associations paragraph inner edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(AssociationParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IAssociationParagraph, ())
         if 'renderer' in updated:
             output.setdefault('events', []).append(get_json_widget_refresh_event(self.context, self.request,
--- a/src/pyams_content/component/extfile/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/extfile/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -31,12 +31,12 @@
 from pyams_content.component.extfile import EXTERNAL_FILES_FACTORIES
 from pyams_content.component.paragraph.zmi import get_json_paragraph_markers_refresh_event
 from pyams_content.component.paragraph.zmi.container import ParagraphContainerCounterBase
+from pyams_form.form import ajax_config
 from pyams_form.security import ProtectedFormObjectMixin
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.toolbar import ToolbarMenuItem, ToolbarMenuDivider
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
-from pyramid.view import view_config
 from z3c.form import field
 from zope.interface import Interface
 from zope.schema import Choice
@@ -95,6 +95,8 @@
 
 @pagelet_config(name='add-extfile.html', context=IExtFileContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-extfile.json', context=IExtFileContainerTarget, layer=IPyAMSLayer,
+             base=AssociationItemAJAXAddForm)
 class ExtFileAddForm(AdminDialogAddForm):
     """External file add form"""
 
@@ -102,7 +104,6 @@
     icon_css_class = 'fa fa-fw fa-file-text-o'
 
     fields = field.Fields(IExtFile).select('data', 'filename', 'title', 'description', 'author', 'language')
-    ajax_handler = 'add-extfile.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -118,14 +119,8 @@
     def add(self, object):
         IAssociationContainer(self.context).append(object)
 
-
-@view_config(name='add-extfile.json', context=IExtFileContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExtFileAJAXAddForm(AssociationItemAJAXAddForm, ExtFileAddForm):
-    """External file add form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(ExtFileAJAXAddForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if output:
             output.setdefault('events', []).append(get_json_paragraph_markers_refresh_event(self.context, self.request,
                                                                                             self, ExtFilesCounter))
@@ -133,6 +128,8 @@
 
 
 @pagelet_config(name='properties.html', context=IExtFile, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IExtFile, layer=IPyAMSLayer,
+             base=AssociationItemAJAXEditForm)
 class ExtFilePropertiesEditForm(AdminDialogEditForm):
     """External file properties edit form"""
 
@@ -143,7 +140,6 @@
     dialog_class = 'modal-large'
 
     fields = field.Fields(IExtFile).select('data', 'filename', 'title', 'description', 'author', 'language')
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -151,19 +147,13 @@
         if 'description' in self.widgets:
             self.widgets['description'].widget_css_class = 'textarea'
 
-
-@view_config(name='properties.json', context=IExtFile, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExtFilePropertiesAJAXEditForm(AssociationItemAJAXEditForm, ExtFilePropertiesEditForm):
-    """External file properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         if ('title' in changes.get(IBaseExtFile, ())) or \
            ('filename' in changes.get(IBaseExtFile, ())) or \
            ('data' in changes.get(IExtFile, ())):
             return self.get_associations_table()
         else:
-            return super(ExtFilePropertiesAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
 
 
 #
@@ -202,6 +192,8 @@
 
 @pagelet_config(name='add-extimage.html', context=IExtFileContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-extimage.json', context=IExtFileContainerTarget, layer=IPyAMSLayer,
+             base=AssociationItemAJAXAddForm)
 class ExtImageAddForm(ExtFileAddForm):
     """External image add form"""
 
@@ -209,7 +201,6 @@
     icon_css_class = 'fa fa-fw fa-file-image-o'
 
     fields = field.Fields(IExtImage).select('data', 'filename', 'title', 'description', 'author')
-    ajax_handler = 'add-extimage.json'
 
     def updateWidgets(self, prefix=None):
         super(ExtImageAddForm, self).updateWidgets(prefix)
@@ -223,14 +214,8 @@
         if factory is not None:
             return factory[0]()
 
-
-@view_config(name='add-extimage.json', context=IExtFileContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExtImageAJAXAddForm(AssociationItemAJAXAddForm, ExtImageAddForm):
-    """External image add form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(ExtImageAJAXAddForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if output:
             output.setdefault('events', []).append(get_json_paragraph_markers_refresh_event(self.context, self.request,
                                                                                             self, ExtImagesCounter))
@@ -238,6 +223,8 @@
 
 
 @pagelet_config(name='properties.html', context=IExtImage, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IExtImage, layer=IPyAMSLayer,
+             base=AssociationItemAJAXEditForm)
 class ExtImagePropertiesEditForm(ExtFilePropertiesEditForm):
     """External image properties edit form"""
 
@@ -253,19 +240,13 @@
         if 'description' in self.widgets:
             self.widgets['description'].description = None
 
-
-@view_config(name='properties.json', context=IExtImage, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExtImagePropertiesAJAXEditForm(AssociationItemAJAXEditForm, ExtImagePropertiesEditForm):
-    """External image properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         if ('title' in changes.get(IBaseExtFile, ())) or \
            ('filename' in changes.get(IBaseExtFile, ())) or \
            ('data' in changes.get(IExtFile, ())):
             return self.get_associations_table()
         else:
-            return super(ExtImagePropertiesAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
 
 
 #
@@ -304,6 +285,8 @@
 
 @pagelet_config(name='add-extvideo.html', context=IExtFileContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-extvideo.json', context=IExtFileContainerTarget, layer=IPyAMSLayer,
+             base=AssociationItemAJAXAddForm)
 class ExtVideoAddForm(ExtFileAddForm):
     """External video add form"""
 
@@ -311,21 +294,14 @@
     icon_css_class = 'fa fa-fw fa-file-video-o'
 
     fields = field.Fields(IExtVideo).select('data', 'filename', 'title', 'description', 'author', 'language')
-    ajax_handler = 'add-extvideo.json'
 
     def create(self, data):
         factory = EXTERNAL_FILES_FACTORIES.get('video')
         if factory is not None:
             return factory[0]()
 
-
-@view_config(name='add-extvideo.json', context=IExtFileContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExtVideoAJAXAddForm(AssociationItemAJAXAddForm, ExtVideoAddForm):
-    """External video add form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(ExtVideoAJAXAddForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if output:
             output.setdefault('events', []).append(get_json_paragraph_markers_refresh_event(self.context, self.request,
                                                                                             self, ExtVideosCounter))
@@ -333,6 +309,8 @@
 
 
 @pagelet_config(name='properties.html', context=IExtVideo, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IExtVideo, layer=IPyAMSLayer,
+             base=AssociationItemAJAXEditForm)
 class ExtVideoPropertiesEditForm(ExtFilePropertiesEditForm):
     """External video properties edit form"""
 
@@ -341,19 +319,13 @@
 
     fields = field.Fields(IExtVideo).select('data', 'filename', 'title', 'description', 'author', 'language')
 
-
-@view_config(name='properties.json', context=IExtVideo, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExtVideoPropertiesAJAXEditForm(AssociationItemAJAXEditForm, ExtVideoPropertiesEditForm):
-    """External video properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         if ('title' in changes.get(IBaseExtFile, ())) or \
            ('filename' in changes.get(IBaseExtFile, ())) or \
            ('data' in changes.get(IExtFile, ())):
             return self.get_associations_table()
         else:
-            return super(ExtVideoPropertiesAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
 
 
 #
@@ -392,6 +364,8 @@
 
 @pagelet_config(name='add-extaudio.html', context=IExtFileContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-extaudio.json', context=IExtFileContainerTarget, layer=IPyAMSLayer,
+             base=AssociationItemAJAXAddForm)
 class ExtAudioAddForm(ExtFileAddForm):
     """External audio file add form"""
 
@@ -399,21 +373,14 @@
     icon_css_class = 'fa fa-fw fa-file-audio-o'
 
     fields = field.Fields(IExtAudio).select('data', 'filename', 'title', 'description', 'author', 'language')
-    ajax_handler = 'add-extaudio.json'
 
     def create(self, data):
         factory = EXTERNAL_FILES_FACTORIES.get('audio')
         if factory is not None:
             return factory[0]()
 
-
-@view_config(name='add-extaudio.json', context=IExtFileContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExtAudioAJAXAddForm(AssociationItemAJAXAddForm, ExtAudioAddForm):
-    """External audio file add form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(ExtAudioAJAXAddForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if output:
             output.setdefault('events', []).append(get_json_paragraph_markers_refresh_event(self.context, self.request,
                                                                                             self, ExtAudiosCounter))
@@ -421,6 +388,8 @@
 
 
 @pagelet_config(name='properties.html', context=IExtAudio, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IExtAudio, layer=IPyAMSLayer,
+             base=AssociationItemAJAXEditForm)
 class ExtAudioPropertiesEditForm(ExtFilePropertiesEditForm):
     """External audio file properties edit form"""
 
@@ -429,16 +398,10 @@
 
     fields = field.Fields(IExtAudio).select('data', 'filename', 'title', 'description', 'author', 'language')
 
-
-@view_config(name='properties.json', context=IExtAudio, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExtAudioPropertiesAJAXEditForm(AssociationItemAJAXEditForm, ExtAudioPropertiesEditForm):
-    """External audio file properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         if ('title' in changes.get(IBaseExtFile, ())) or \
            ('filename' in changes.get(IBaseExtFile, ())) or \
            ('data' in changes.get(IExtFile, ())):
             return self.get_associations_table()
         else:
-            return super(ExtAudioPropertiesAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
--- a/src/pyams_content/component/gallery/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/gallery/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -33,7 +33,7 @@
 # import packages
 from pyams_content.shared.common.zmi import WfSharedContentPermissionMixin
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_template.template import template_config
 from pyams_utils.url import absolute_url
@@ -54,6 +54,7 @@
 #
 
 @pagelet_config(name='properties.html', context=IGallery, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IGallery, layer=IPyAMSLayer)
 class GalleryPropertiesEditForm(AdminDialogEditForm):
     """Gallery properties edit form"""
 
@@ -65,7 +66,6 @@
     fields = field.Fields(IGallery).omit('__parent__', '__file__')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -73,12 +73,6 @@
         if 'description' in self.widgets:
             self.widgets['description'].widget_css_class = 'textarea'
 
-
-@view_config(name='properties.json', context=IGallery, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class GalleryPropertiesAJAXEditForm(AJAXEditForm, GalleryPropertiesEditForm):
-    """Gallery properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         if 'title' in changes.get(IGallery, ()):
             return {
@@ -86,7 +80,7 @@
                 'location': '#external-files.html'
             }
         else:
-            return super(GalleryPropertiesAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
 
 
 #
--- a/src/pyams_content/component/gallery/zmi/file.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/gallery/zmi/file.py	Fri Jun 08 10:35:42 2018 +0200
@@ -34,7 +34,7 @@
 from pyams_content.shared.common.zmi import WfSharedContentPermissionMixin
 from pyams_file.file import get_magic_content_type
 from pyams_file.zmi.file import FilePropertiesAction
-from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_form.group import NamedWidgetsGroup
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.toolbar import ToolbarAction, JsToolbarActionItem
@@ -43,7 +43,6 @@
 from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogEditForm, AdminDialogAddForm
-from pyramid.view import view_config
 from z3c.form import field
 from zope.lifecycleevent import ObjectCreatedEvent
 from zope.location import locate
@@ -63,6 +62,7 @@
 
 
 @pagelet_config(name='add-media.html', context=IGallery, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-media.json', context=IGallery, layer=IPyAMSLayer, base=AJAXAddForm)
 class GalleryMediaAddForm(AdminDialogAddForm):
     """Gallery media add form"""
 
@@ -70,7 +70,7 @@
     icon_css_class = 'fa -fa-fw fa-picture-o'
 
     fields = field.Fields(IGalleryMediasAddFields)
-    ajax_handler = 'add-media.json'
+    edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
         super(GalleryMediaAddForm, self).updateWidgets(prefix)
@@ -121,12 +121,6 @@
                 self.context.append(media)
         return None
 
-
-@view_config(name='add-media.json', context=IGallery, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class GalleryMediaAJAXAddForm(AJAXAddForm, GalleryMediaAddForm):
-    """Gallery media add form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         return {
             'status': 'reload',
@@ -181,6 +175,7 @@
 
 @pagelet_config(name='gallery-file-properties.html', context=IGalleryFile, layer=IPyAMSLayer,
                 permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='gallery-file-properties.html', context=IGalleryFile, layer=IPyAMSLayer)
 class GalleryFilePropertiesEditForm(AdminDialogEditForm):
     """Gallery file properties edit form"""
 
@@ -191,7 +186,7 @@
     dialog_class = 'modal-large'
 
     fields = field.Fields(IGalleryFile).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'gallery-file-properties.json'
+    edit_permission = MANAGE_CONTENT_PERMISSION
 
     @property
     def title(self):
@@ -219,14 +214,8 @@
                                          display_mode='auto'))
         super(GalleryFilePropertiesEditForm, self).updateGroups()
 
-
-@view_config(name='gallery-file-properties.json', context=IGalleryFile, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class GalleryFileInfoPropertiesAJAXEditForm(AJAXEditForm, GalleryFilePropertiesEditForm):
-    """Gallery file properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(GalleryFileInfoPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if 'title' in changes.get(IGalleryFile, ()):
             gallery = get_parent(self.context, IGallery)
             if gallery is not None:
--- a/src/pyams_content/component/gallery/zmi/paragraph.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/gallery/zmi/paragraph.py	Fri Jun 08 10:35:42 2018 +0200
@@ -34,6 +34,7 @@
     BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_content.shared.common.zmi import WfSharedContentPermissionMixin
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.toolbar import ToolbarAction
 from pyams_template.template import template_config
@@ -41,7 +42,6 @@
 from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm, InnerAdminDisplayForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer, Interface
 
@@ -61,6 +61,8 @@
 
 @pagelet_config(name='add-gallery.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-gallery.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class GalleryAddForm(AdminDialogAddForm):
     """Gallery add form"""
 
@@ -68,7 +70,6 @@
     icon_css_class = 'fa fa-fw fa-picture-o'
 
     fields = field.Fields(IGalleryParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-gallery.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -83,14 +84,10 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-gallery.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class GalleryAJAXAddForm(BaseParagraphAJAXAddForm, GalleryAddForm):
-    """Gallery paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IGalleryParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IGalleryParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class GalleryPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Gallery properties edit form"""
 
@@ -102,7 +99,6 @@
     fields = field.Fields(IGalleryParagraph).omit('__parent__', '__name__', 'visible')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -110,12 +106,6 @@
         if 'description' in self.widgets:
             self.widgets['description'].widget_css_class = 'textarea'
 
-
-@view_config(name='properties.json', context=IGalleryParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class GalleryPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, GalleryPropertiesEditForm):
-    """Gallery paragraph properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         updated = changes.get(IBaseGallery, ())
         if 'title' in updated:
@@ -124,7 +114,7 @@
                 'events': [get_json_paragraph_refresh_event(self.context, self.request), ]
             }
         else:
-            return super(GalleryPropertiesAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
 
 
 @adapter_config(context=(IGalleryParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
--- a/src/pyams_content/component/illustration/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/illustration/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -32,7 +32,7 @@
 from pyams_i18n.property import I18nFileProperty
 from pyams_utils.adapter import adapter_config, ContextAdapter, get_annotation_adapter
 from pyams_utils.factory import factory_config
-from pyams_utils.registry import query_utility, get_utility
+from pyams_utils.registry import query_utility, get_utility, get_global_registry
 from pyams_utils.request import check_request
 from pyams_utils.traversing import get_parent
 from pyams_utils.vocabulary import vocabulary_config
@@ -114,7 +114,8 @@
     """++illustration++ namespace adapter"""
 
     def traverse(self, name, furtherpath=None):
-        return IIllustration(self.context)
+        registry = get_global_registry()
+        return registry.queryAdapter(self.context, IIllustration, name=name)
 
 
 @adapter_config(name='illustration', context=IIllustrationTarget, provides=ISublocations)
--- a/src/pyams_content/component/illustration/zmi/paragraph.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/illustration/zmi/paragraph.py	Fri Jun 08 10:35:42 2018 +0200
@@ -33,12 +33,12 @@
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
     BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_form_refresh_event
 from pyams_utils.adapter import adapter_config
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer
 
@@ -62,6 +62,8 @@
 
 @pagelet_config(name='add-illustration.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-illustration.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class IllustrationAddForm(AdminDialogAddForm):
     """Illustration add form"""
 
@@ -71,7 +73,7 @@
 
     fields = field.Fields(IIllustrationParagraph).select('data', 'title', 'alt_title', 'description',
                                                          'author', 'renderer')
-    ajax_handler = 'add-illustration.json'
+
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -86,14 +88,10 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-illustration.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class IllustrationAJAXAddForm(BaseParagraphAJAXAddForm, IllustrationAddForm):
-    """HTML paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IIllustrationParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IIllustrationParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class IllustrationPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Illustration properties edit form"""
 
@@ -107,7 +105,6 @@
                                                          'author', 'renderer')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -115,26 +112,21 @@
         if 'description' in self.widgets:
             self.widgets['description'].widget_css_class = 'textarea'
 
-
-@view_config(name='properties.json', context=IIllustrationParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class IllustrationPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, IllustrationPropertiesEditForm):
-    """Illustration properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(IllustrationPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if 'title' in changes.get(IIllustration, ()):
             output.setdefault('events', []).append(get_json_paragraph_refresh_event(self.context, self.request))
         return output
 
 
 @adapter_config(context=(IIllustrationParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IIllustrationParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm)
 class IllustrationInnerEditForm(IllustrationPropertiesEditForm):
     """Illustration inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -143,14 +135,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IIllustrationParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class IllustrationInnerAJAXEditForm(BaseParagraphAJAXEditForm, IllustrationInnerEditForm):
-    """Illustration paragraph inner edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(IllustrationInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IIllustration, ())
         if 'title' in updated:
             output.setdefault('events', []).append(get_json_paragraph_refresh_event(self.context, self.request))
--- a/src/pyams_content/component/links/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/links/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -31,12 +31,12 @@
 from pyams_content.component.links import InternalLink, ExternalLink, MailtoLink
 from pyams_content.component.paragraph.zmi import get_json_paragraph_markers_refresh_event
 from pyams_content.component.paragraph.zmi.container import ParagraphContainerCounterBase
+from pyams_form.form import ajax_config
 from pyams_form.security import ProtectedFormObjectMixin
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.toolbar import ToolbarMenuItem
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
-from pyramid.view import view_config
 from z3c.form import field
 
 from pyams_content import _
@@ -78,6 +78,8 @@
 
 @pagelet_config(name='add-internal-link.html', context=ILinkContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-internal-link.json', context=ILinkContainerTarget, layer=IPyAMSLayer,
+             base=AssociationItemAJAXAddForm)
 class InternalLinkAddForm(AdminDialogAddForm):
     """Internal link add form"""
 
@@ -85,7 +87,6 @@
     icon_css_class = 'fa fa-fw fa-external-link-square fa-rotate-90'
 
     fields = field.Fields(IInternalLink).select('reference', 'title', 'description')
-    ajax_handler = 'add-internal-link.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -99,14 +100,8 @@
     def add(self, object):
         IAssociationContainer(self.context).append(object)
 
-
-@view_config(name='add-internal-link.json', context=ILinkContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class InternalLinkAJAXAddForm(AssociationItemAJAXAddForm, InternalLinkAddForm):
-    """Internal link add form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(InternalLinkAJAXAddForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if output:
             output.setdefault('events', []).append(get_json_paragraph_markers_refresh_event(self.context, self.request,
                                                                                             self, InternalLinksCounter))
@@ -114,6 +109,8 @@
 
 
 @pagelet_config(name='properties.html', context=IInternalLink, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IInternalLink, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, base=AssociationItemAJAXEditForm)
 class InternalLinkPropertiesEditForm(AdminDialogEditForm):
     """Internal link properties edit form"""
 
@@ -123,7 +120,6 @@
     icon_css_class = 'fa fa-fw fa-external-link-square fa-rotate-90'
 
     fields = field.Fields(IInternalLink).select('reference', 'title', 'description')
-    ajax_handler = 'properties.json'
     edit_permission = None  # defined by IFormContextPermissionChecker adapter
 
     def updateWidgets(self, prefix=None):
@@ -131,18 +127,12 @@
         if 'description' in self.widgets:
             self.widgets['description'].widget_css_class = 'textarea'
 
-
-@view_config(name='properties.json', context=IInternalLink, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class InternalLinkPropertiesAJAXEditForm(AssociationItemAJAXEditForm, InternalLinkPropertiesEditForm):
-    """Internal link properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         if ('title' in changes.get(IBaseLink, ())) or \
            ('reference' in changes.get(IInternalLink, ())):
             return self.get_associations_table()
         else:
-            return super(InternalLinkPropertiesAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
 
 
 #
@@ -181,6 +171,8 @@
 
 @pagelet_config(name='add-external-link.html', context=ILinkContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-external-link.json', context=ILinkContainerTarget, layer=IPyAMSLayer,
+             base=AssociationItemAJAXAddForm)
 class ExternalLinkAddForm(AdminDialogAddForm):
     """External link add form"""
 
@@ -188,7 +180,6 @@
     icon_css_class = 'fa fa-fw fa-external-link'
 
     fields = field.Fields(IExternalLink).select('url', 'title', 'description', 'language')
-    ajax_handler = 'add-external-link.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -202,14 +193,8 @@
     def add(self, object):
         IAssociationContainer(self.context).append(object)
 
-
-@view_config(name='add-external-link.json', context=ILinkContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExternalLinkAJAXAddForm(AssociationItemAJAXAddForm, ExternalLinkAddForm):
-    """External link add form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(ExternalLinkAJAXAddForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if output:
             output.setdefault('events', []).append(get_json_paragraph_markers_refresh_event(self.context, self.request,
                                                                                             self, ExternalLinksCounter))
@@ -217,6 +202,8 @@
 
 
 @pagelet_config(name='properties.html', context=IExternalLink, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IExternalLink, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, base=AssociationItemAJAXEditForm)
 class ExternalLinkPropertiesEditForm(AdminDialogEditForm):
     """External link properties edit form"""
 
@@ -226,7 +213,6 @@
     icon_css_class = 'fa fa-fw fa-external-link'
 
     fields = field.Fields(IExternalLink).select('url', 'title', 'description', 'language')
-    ajax_handler = 'properties.json'
     edit_permission = None  # defined by IFormContextPermissionChecker adapter
 
     def updateWidgets(self, prefix=None):
@@ -234,18 +220,12 @@
         if 'description' in self.widgets:
             self.widgets['description'].widget_css_class = 'textarea'
 
-
-@view_config(name='properties.json', context=IExternalLink, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExternalLinkPropertiesAJAXEditForm(AssociationItemAJAXEditForm, ExternalLinkPropertiesEditForm):
-    """External link properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         if ('title' in changes.get(IBaseLink, ())) or \
            ('url' in changes.get(IExternalLink, ())):
             return self.get_associations_table()
         else:
-            return super(ExternalLinkPropertiesAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
 
 
 #
@@ -284,6 +264,8 @@
 
 @pagelet_config(name='add-mailto-link.html', context=ILinkContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-mailto-link.json', context=ILinkContainerTarget, layer=IPyAMSLayer,
+             base=AssociationItemAJAXAddForm)
 class MailtoLinkAddForm(AdminDialogAddForm):
     """Mailto link add form"""
 
@@ -291,7 +273,6 @@
     icon_css_class = 'fa fa-fw fa-envelope-o'
 
     fields = field.Fields(IMailtoLink).select('address', 'address_name', 'title', 'description')
-    ajax_handler = 'add-mailto-link.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -305,14 +286,8 @@
     def add(self, object):
         IAssociationContainer(self.context).append(object)
 
-
-@view_config(name='add-mailto-link.json', context=ILinkContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class MailtoLinkAJAXAddForm(AssociationItemAJAXAddForm, MailtoLinkAddForm):
-    """Mailto link add form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(MailtoLinkAJAXAddForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if output:
             output.setdefault('events', []).append(get_json_paragraph_markers_refresh_event(self.context, self.request,
                                                                                             self, MailtoLinksCounter))
@@ -320,6 +295,8 @@
 
 
 @pagelet_config(name='properties.html', context=IMailtoLink, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IMailtoLink, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, base=AssociationItemAJAXEditForm)
 class MailtoLinkPropertiesEditForm(AdminDialogEditForm):
     """Mailto link properties edit form"""
 
@@ -329,7 +306,6 @@
     icon_css_class = 'fa fa-fw fa-envelope-o'
 
     fields = field.Fields(IMailtoLink).select('address', 'address_name', 'title', 'description')
-    ajax_handler = 'properties.json'
     edit_permission = None  # defined by IFormContextPermissionChecker adapter
 
     def updateWidgets(self, prefix=None):
@@ -337,14 +313,8 @@
         if 'description' in self.widgets:
             self.widgets['description'].widget_css_class = 'textarea'
 
-
-@view_config(name='properties.json', context=IMailtoLink, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class MailtoLinkPropertiesAJAXEditForm(AssociationItemAJAXEditForm, MailtoLinkPropertiesEditForm):
-    """Mailto link properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         if ('title' in changes.get(IBaseLink, ())) or changes.get(IMailtoLink, ()):
             return self.get_associations_table()
         else:
-            return super(MailtoLinkPropertiesAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
--- a/src/pyams_content/component/paragraph/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -32,7 +32,7 @@
 # import packages
 from pyams_content.component.paragraph.zmi.container import ParagraphContainerTable, ParagraphContainerBaseTable
 from pyams_content.features.renderer.zmi import BaseRenderedContentRenderer
-from pyams_form.form import AJAXEditForm, AJAXAddForm
+from pyams_form.form import AJAXEditForm, AJAXAddForm, ajax_config
 from pyams_form.help import FormHelp
 from pyams_form.schema import ActionButton, CloseButton
 from pyams_form.security import ProtectedFormObjectMixin
@@ -47,7 +47,6 @@
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogEditForm
 from pyramid.location import lineage
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import Interface
 
@@ -71,6 +70,7 @@
 
 @pagelet_config(name='default-paragraphs.html', context=IParagraphFactorySettings, layer=IPyAMSLayer,
                 permission=MANAGE_TOOL_PERMISSION)
+@ajax_config(name='default-paragraphs.json', context=IParagraphFactorySettings, layer=IPyAMSLayer)
 class DefaultParagraphsEditForm(AdminDialogEditForm):
     """Default paragraphs edit form"""
 
@@ -79,16 +79,9 @@
     legend = _("Content block types")
 
     fields = field.Fields(IParagraphFactorySettings)
-    ajax_handler = 'default-paragraphs.json'
     edit_permission = MANAGE_TOOL_PERMISSION
 
 
-@view_config(name='default-paragraphs.json', context=IParagraphFactorySettings, request_type=IPyAMSLayer,
-             permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
-class DefaultParagraphAJAXEditForm(AJAXEditForm, DefaultParagraphsEditForm):
-    """Default paragraphs edit form, JSON renderer"""
-
-
 @adapter_config(context=(IParagraphFactorySettings, IPyAMSLayer, DefaultParagraphsEditForm), provides=IFormHelp)
 class DefaultParagraphsEditFormHelp(FormHelp):
     """Default paragraphs edit form help"""
--- a/src/pyams_content/component/paragraph/zmi/audio.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/audio.py	Fri Jun 08 10:35:42 2018 +0200
@@ -33,13 +33,13 @@
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
     BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_form.form import ajax_config
 from pyams_form.group import NamedWidgetsGroup
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_form_refresh_event, get_json_widget_refresh_event
 from pyams_utils.adapter import adapter_config
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer
 
@@ -59,6 +59,8 @@
 
 @pagelet_config(name='add-audio-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-audio-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class AudioParagraphAddForm(AdminDialogAddForm):
     """Audio paragraph add form"""
 
@@ -67,7 +69,6 @@
     icon_css_class = 'fa fa-fw fa-volume-up'
 
     fields = field.Fields(IAudioParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-audio-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -96,14 +97,9 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-audio-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class AudioParagraphAJAXAddForm(BaseParagraphAJAXAddForm, AudioParagraphAddForm):
-    """Audio paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IAudioParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IAudioParagraph, layer=IPyAMSLayer, base=BaseParagraphAJAXEditForm)
 class AudioParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Audio paragraph properties edit form"""
 
@@ -116,7 +112,6 @@
     fields = field.Fields(IAudioParagraph).omit('__parent__', '__name__', 'visible')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -140,19 +135,14 @@
         super(AudioParagraphPropertiesEditForm, self).updateGroups()
 
 
-@view_config(name='properties.json', context=IAudioParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class AudioParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, AudioParagraphPropertiesEditForm):
-    """Audio paragraph properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IAudioParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IAudioParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm, IPropertiesEditForm, IAssociationsParentForm)
 class AudioParagraphPropertiesInnerEditForm(AudioParagraphPropertiesEditForm):
     """Audio paragraph properties inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -161,14 +151,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IAudioParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class AudioParagraphPropertiesInnerAJAXEditForm(BaseParagraphAJAXEditForm, AudioParagraphPropertiesInnerEditForm):
-    """Audio paragraph properties inner deit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(AudioParagraphPropertiesInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IAudioParagraph, ())
         if 'data' in updated:
             # we have to commit transaction to be able to handle blobs...
--- a/src/pyams_content/component/paragraph/zmi/contact.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/contact.py	Fri Jun 08 10:35:42 2018 +0200
@@ -33,12 +33,12 @@
     BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm, get_json_paragraph_refresh_event, \
     IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_form_refresh_event
 from pyams_utils.adapter import adapter_config
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer
 
@@ -58,6 +58,8 @@
 
 @pagelet_config(name='add-contact-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-contact-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class ContactParagraphAddForm(AdminDialogAddForm):
     """Contact paragraph add form"""
 
@@ -66,7 +68,6 @@
     icon_css_class = 'fa fa-fw fa-id-card-o'
 
     fields = field.Fields(IContactParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-contact-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -81,14 +82,10 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-contact-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ContactParagraphAJAXAddForm(BaseParagraphAJAXAddForm, ContactParagraphAddForm):
-    """Contact paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IContactParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IContactParagraph, request_type=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class ContactParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Contact paragraph properties edit form"""
 
@@ -100,7 +97,6 @@
     fields = field.Fields(IContactParagraph).omit('__parent__', '__name__', 'visible')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -109,19 +105,14 @@
             self.widgets['address'].widget_css_class = 'textarea'
 
 
-@view_config(name='properties.json', context=IContactParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ContactParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, ContactParagraphPropertiesEditForm):
-    """Contact paragraph properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IContactParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IContactParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm)
 class ContactParagraphInnerEditForm(ContactParagraphPropertiesEditForm):
     """Contact paragraph inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -130,14 +121,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IContactParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ContactParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, ContactParagraphInnerEditForm):
-    """Contact paragraph inner edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(ContactParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IBaseParagraph, ())
         if 'title' in updated:
             output.setdefault('events', []).append(get_json_paragraph_refresh_event(self.context, self.request))
--- a/src/pyams_content/component/paragraph/zmi/frame.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/frame.py	Fri Jun 08 10:35:42 2018 +0200
@@ -38,13 +38,13 @@
 from pyams_content.component.paragraph.zmi.container import ParagraphContainerTable, \
     ParagraphTitleToolbarViewletManager
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_switched_table_refresh_event, get_json_widget_refresh_event
 from pyams_utils.adapter import adapter_config, ContextRequestAdapter
 from pyams_utils.traversing import get_parent
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer, Interface
 
@@ -89,6 +89,8 @@
 
 @pagelet_config(name='add-frame-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-frame-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 @implementer(IFrameParagraphForm)
 class FrameParagraphAddForm(AdminDialogAddForm):
     """Framed text paragraph add form"""
@@ -100,7 +102,6 @@
     input_css_class = 'col-md-10'
 
     fields = field.Fields(IFrameParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-frame-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -115,14 +116,10 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-frame-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class FrameParagraphAJAXAddForm(BaseParagraphAJAXAddForm, FrameParagraphAddForm):
-    """Framed text paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IFrameParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IFrameParagraph, request_type=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IFrameParagraphForm)
 class FrameParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Framed text paragraph properties edit form"""
@@ -138,7 +135,6 @@
     fields = field.Fields(IFrameParagraph).omit('__parent__', '__name__', 'visible')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -150,14 +146,8 @@
                 widget.id = '{id}_{name}'.format(id=widget.id, name=self.context.__name__)
             body_widget.widget_css_class = 'textarea'
 
-
-@view_config(name='properties.json', context=IFrameParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class FrameParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, FrameParagraphPropertiesEditForm):
-    """Framed text paragraph properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(FrameParagraphPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if 'body' in changes.get(IFrameParagraph, ()):
             # refresh associations count markers
             parent = get_parent(self.context, IAssociationTarget)
@@ -171,12 +161,13 @@
 
 
 @adapter_config(context=(IFrameParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IFrameParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm, IPropertiesEditForm, IAssociationsParentForm)
 class FrameParagraphInnerEditForm(FrameParagraphPropertiesEditForm):
     """Framed text paragraph inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -185,14 +176,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IFrameParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class FrameParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, FrameParagraphInnerEditForm):
-    """Framed text paragraph inner edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(FrameParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IFrameParagraph, ())
         if 'renderer' in updated:
             output.setdefault('events', []).append(
--- a/src/pyams_content/component/paragraph/zmi/header.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/header.py	Fri Jun 08 10:35:42 2018 +0200
@@ -30,12 +30,12 @@
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
     BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_widget_refresh_event
 from pyams_utils.adapter import adapter_config
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer
 
@@ -55,6 +55,8 @@
 
 @pagelet_config(name='add-header-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-header-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class HeaderParagraphAddForm(AdminDialogAddForm):
     """Header paragraph add form"""
 
@@ -62,7 +64,6 @@
     icon_css_class = 'fa fa-fw fa-download fa-rotate-180'
 
     fields = field.Fields(IHeaderParagraph).select('header', 'renderer')
-    ajax_handler = 'add-header-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -77,14 +78,10 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-header-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class HeaderParagraphAJAXAddForm(BaseParagraphAJAXAddForm, HeaderParagraphAddForm):
-    """Header paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IHeaderParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IHeaderParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class HeaderParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Header paragraph properties edit form"""
 
@@ -96,7 +93,6 @@
     fields = field.Fields(IHeaderParagraph).select('header', 'renderer')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -104,20 +100,16 @@
         if 'header' in self.widgets:
             self.widgets['header'].widget_css_class = 'textarea height-100'
 
-
-@view_config(name='properties.json', context=IHeaderParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class HeaderParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, HeaderParagraphPropertiesEditForm):
-    """Header paragraph properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(HeaderParagraphPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if 'header' in changes.get(IHeaderParagraph, ()):
             output.setdefault('events', []).append(get_json_paragraph_refresh_event(self.context, self.request))
         return output
 
 
 @adapter_config(context=(IHeaderParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IHeaderParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm)
 class HeaderParagraphInnerEditForm(HeaderParagraphPropertiesEditForm):
     """Header paragraph inner edit form"""
@@ -126,8 +118,6 @@
     label_css_class = 'control-label col-md-2'
     input_css_class = 'col-md-10'
 
-    ajax_handler = 'inner-properties.json'
-
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
@@ -135,14 +125,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IHeaderParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class HeaderParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, HeaderParagraphInnerEditForm):
-    """Header paragraph inner edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(HeaderParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IHeaderParagraph, ())
         if 'header' in updated:
             output.setdefault('events', []).append(get_json_paragraph_refresh_event(self.context, self.request))
--- a/src/pyams_content/component/paragraph/zmi/html.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/html.py	Fri Jun 08 10:35:42 2018 +0200
@@ -37,6 +37,7 @@
     BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_toolbar_refresh_event, \
     IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_form.form import ajax_config
 from pyams_form.security import ProtectedFormObjectMixin
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_switched_table_refresh_event
@@ -45,7 +46,6 @@
 from pyams_utils.traversing import get_parent
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer
 
@@ -81,6 +81,8 @@
 
 @pagelet_config(name='add-raw-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-raw-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class RawParagraphAddForm(AdminDialogAddForm):
     """Raw HTML paragraph add form"""
 
@@ -91,7 +93,6 @@
     input_css_class = 'col-md-10'
 
     fields = field.Fields(IRawParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-raw-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -106,14 +107,10 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-raw-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class RawParagraphAJAXAddForm(BaseParagraphAJAXAddForm, RawParagraphAddForm):
-    """Raw HTML paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IRawParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IRawParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class RawParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Raw HTML paragraph properties edit form"""
 
@@ -125,7 +122,6 @@
     fields = field.Fields(IRawParagraph).omit('__parent__', '__name__', 'visible')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -134,19 +130,14 @@
             self.widgets['body'].widget_css_class = 'textarea height-100'
 
 
-@view_config(name='properties.json', context=IRawParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class RawParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, RawParagraphPropertiesEditForm):
-    """Raw HTML paragraph properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IRawParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IRawParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm)
 class RawParagraphInnerEditForm(RawParagraphPropertiesEditForm):
     """Raw HTML paragraph inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -156,12 +147,6 @@
             return button.Buttons()
 
 
-@view_config(name='inner-properties.json', context=IRawParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class RawParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, RawParagraphInnerEditForm):
-    """Raw HTML paragraph inner edit form, JSON renderer"""
-
-
 #
 # Rich text paragraph
 #
@@ -179,6 +164,8 @@
 
 @pagelet_config(name='add-html-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-html-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class HTMLParagraphAddForm(AdminDialogAddForm):
     """Rich text paragraph add form"""
 
@@ -189,7 +176,6 @@
     input_css_class = 'col-md-10'
 
     fields = field.Fields(IHTMLParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-html-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -204,14 +190,10 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-html-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class HTMLParagraphAJAXAddForm(BaseParagraphAJAXAddForm, HTMLParagraphAddForm):
-    """Rich text paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IHTMLParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IHTMLParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class HTMLParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Rich text paragraph properties edit form"""
 
@@ -226,7 +208,6 @@
     fields = field.Fields(IHTMLParagraph).omit('__parent__', '__name__', 'visible')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -238,14 +219,8 @@
                 widget.id = '{id}_{name}'.format(id=widget.id, name=self.context.__name__)
             body_widget.widget_css_class = 'textarea'
 
-
-@view_config(name='properties.json', context=IHTMLParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class HTMLParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, HTMLParagraphPropertiesEditForm):
-    """Rich text paragraph properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(HTMLParagraphPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if 'body' in changes.get(IHTMLParagraph, ()):
             # refresh associations count markers
             parent = get_parent(self.context, IAssociationTarget)
@@ -258,12 +233,13 @@
 
 
 @adapter_config(context=(IHTMLParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IHTMLParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm, IPropertiesEditForm, IAssociationsParentForm)
 class HTMLParagraphInnerEditForm(HTMLParagraphPropertiesEditForm):
     """Rich text paragraph inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -272,14 +248,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IHTMLParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class HTMLParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, HTMLParagraphInnerEditForm):
-    """Rich text paragraph inner edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(HTMLParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if 'body' in changes.get(IHTMLParagraph, ()):
             # refresh associations count markers
             parent = get_parent(self.context, IAssociationTarget)
--- a/src/pyams_content/component/paragraph/zmi/keynumber.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/keynumber.py	Fri Jun 08 10:35:42 2018 +0200
@@ -36,7 +36,7 @@
 from pyams_content.component.paragraph.zmi import BaseParagraphAddMenu, BaseParagraphAJAXAddForm, \
     BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
-from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_form.security import ProtectedFormObjectMixin
 from pyams_i18n.column import I18nAttrColumn
 from pyams_pagelet.pagelet import pagelet_config
@@ -81,6 +81,8 @@
     
 @pagelet_config(name='add-keynumber-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-keynumber-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class KeyNumberParagraphAddForm(AdminDialogAddForm):
     """Key number paragraph add form"""
     
@@ -88,7 +90,6 @@
     icon_css_class = 'fa fa-fw fa-dashboard'
     
     fields = field.Fields(IKeyNumberParagraph).select('title', 'renderer')
-    ajax_handler = 'add-keynumber-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
     
     def create(self, data):
@@ -96,16 +97,12 @@
     
     def add(self, object):
         IParagraphContainer(self.context).append(object)
-        
-        
-@view_config(name='add-keynumber-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class KeyNumberParagraphAJAXAddForm(BaseParagraphAJAXAddForm, KeyNumberParagraphAddForm):
-    """Key number paragraph add form, JSON renderer"""
-    
+
     
 @pagelet_config(name='properties.html', context=IKeyNumberParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IKeyNumberParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class KeyNumberParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Key number paragraph properties edit form"""
 
@@ -122,23 +119,17 @@
     fields = field.Fields(IKeyNumberParagraph).select('title', 'renderer')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
 
-@view_config(name='properties.json', context=IKeyNumberParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class KeyNumberParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, KeyNumberParagraphPropertiesEditForm):
-    """Key number paragraph properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IKeyNumberParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IKeyNumberParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm, IKeyNumbersParentForm)
 class KeyNumberParagraphInnerEditForm(KeyNumberParagraphPropertiesEditForm):
     """Key number paragraph inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -147,14 +138,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IKeyNumberParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class KeyNumberParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, KeyNumberParagraphInnerEditForm):
-    """Key numbers paragraph inner edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(KeyNumberParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IKeyNumberParagraph, ())
         if 'renderer' in updated:
             output.setdefault('events', []).append(
@@ -302,6 +287,8 @@
 
 @pagelet_config(name='add-keynumber.html', context=IKeyNumberContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-keynumber.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=AJAXAddForm)
 class KeyNumberAddForm(AdminDialogAddForm):
     """Key number add form"""
 
@@ -309,7 +296,6 @@
     icon_css_class = 'fa fa-fw fa-dashboard'
 
     fields = field.Fields(IKeyNumber).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-keynumber.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def create(self, data):
@@ -318,12 +304,6 @@
     def add(self, object):
         IKeyNumberContainer(self.context).append(object)
 
-
-@view_config(name='add-keynumber.json', context=IKeyNumberContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class KeyNumberAJAXAddForm(AJAXAddForm, KeyNumberAddForm):
-    """Key number add form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         return {
             'status': 'success',
@@ -333,6 +313,7 @@
 
 
 @pagelet_config(name='properties.html', context=IKeyNumber, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IKeyNumber, layer=IPyAMSLayer)
 class KeyNumberPropertiesEditForm(AdminDialogEditForm):
     """Key number properties edit form"""
 
@@ -342,17 +323,10 @@
     icon_css_class = 'fa fa-fw fa-dashboard'
 
     fields = field.Fields(IKeyNumber).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
-
-@view_config(name='properties.json', context=IKeyNumber, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class KeyNumberPropertiesAJAXEditForm(AJAXEditForm, KeyNumberPropertiesEditForm):
-    """Key number properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(KeyNumberPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IKeyNumber, ())
         if updated:
             target = get_parent(self.context, IKeyNumberContainerTarget)
--- a/src/pyams_content/component/paragraph/zmi/keypoint.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/keypoint.py	Fri Jun 08 10:35:42 2018 +0200
@@ -28,8 +28,9 @@
 # import packages
 from pyams_content.component.paragraph.keypoint import KeypointsParagraph
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
-    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event, IParagraphEditFormButtons
+    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_widget_refresh_event
 from pyams_utils.adapter import adapter_config
@@ -55,6 +56,8 @@
 
 @pagelet_config(name='add-keypoints-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-keypoints-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class KeypointsParagraphAddForm(AdminDialogAddForm):
     """Key points paragraph add form"""
 
@@ -62,7 +65,6 @@
     icon_css_class = 'fa fa-fw fa-list-ol'
 
     fields = field.Fields(IKeypointsParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-keypoints-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -77,14 +79,10 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-keypoints-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class KeypointsParagraphAJAXAddForm(BaseParagraphAJAXAddForm, KeypointsParagraphAddForm):
-    """Key points paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IKeypointsParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IKeypointsParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class KeypointsParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Key points paragraph properties edit form"""
 
@@ -96,7 +94,6 @@
     fields = field.Fields(IKeypointsParagraph).omit('__parent__', '__name__', 'visible')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -105,13 +102,9 @@
             self.widgets['body'].widget_css_class = 'textarea height-100'
 
 
-@view_config(name='properties.json', context=IKeypointsParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class KeypointsParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, KeypointsParagraphPropertiesEditForm):
-    """Key points paragraph properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IKeypointsParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IKeypointsParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm)
 class KeypointsParagraphInnerEditForm(KeypointsParagraphPropertiesEditForm):
     """Key points paragraph inner edit form"""
@@ -120,8 +113,6 @@
     label_css_class = 'control-label col-md-2'
     input_css_class = 'col-md-10'
 
-    ajax_handler = 'inner-properties.json'
-
     @property
     def buttons(self):
         if self.mode == INPUT_MODE:
@@ -129,14 +120,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IKeypointsParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class KeypointsParagraphInnerAJAXEditForm(KeypointsParagraphPropertiesAJAXEditForm, KeypointsParagraphInnerEditForm):
-    """Key points paragraph inner edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(KeypointsParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IKeypointsParagraph, ())
         if 'renderer' in updated:
             form = KeypointsParagraphInnerEditForm(self.context, self.request)
--- a/src/pyams_content/component/paragraph/zmi/milestone.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/milestone.py	Fri Jun 08 10:35:42 2018 +0200
@@ -36,7 +36,7 @@
 from pyams_content.component.paragraph.zmi import BaseParagraphAddMenu, BaseParagraphAJAXAddForm, \
     BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
-from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_form.security import ProtectedFormObjectMixin
 from pyams_i18n.column import I18nAttrColumn
 from pyams_pagelet.pagelet import pagelet_config
@@ -82,31 +82,28 @@
     
 @pagelet_config(name='add-milestone-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-milestone-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class MilestoneParagraphAddForm(AdminDialogAddForm):
     """Milestone paragraph add form"""
     
     legend = _("Add new milestone paragraph")
     icon_css_class = 'fa fa-fw fa-arrows-h'
-    
+
     fields = field.Fields(IMilestoneParagraph).select('title', 'renderer')
-    ajax_handler = 'add-milestone-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
-    
+
     def create(self, data):
         return MilestoneParagraph()
     
     def add(self, object):
         IParagraphContainer(self.context).append(object)
-        
-        
-@view_config(name='add-milestone-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class MilestoneParagraphAJAXAddForm(BaseParagraphAJAXAddForm, MilestoneParagraphAddForm):
-    """Milestone paragraph add form, JSON renderer"""
-    
-    
+
+
 @pagelet_config(name='properties.html', context=IMilestoneParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IMilestoneParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class MilestoneParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Milestone paragraph properties edit form"""
 
@@ -123,23 +120,17 @@
     fields = field.Fields(IMilestoneParagraph).select('title', 'renderer')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
 
-@view_config(name='properties.json', context=IMilestoneParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class MilestoneParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, MilestoneParagraphPropertiesEditForm):
-    """Milestone paragraph properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IMilestoneParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IMilestoneParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm, IMilestonesParentForm)
 class MilestoneParagraphInnerEditForm(MilestoneParagraphPropertiesEditForm):
     """Milestone paragraph inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -148,14 +139,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IMilestoneParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class MilestoneParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, MilestoneParagraphInnerEditForm):
-    """Milestones paragraph inner edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(MilestoneParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IMilestoneParagraph, ())
         if 'renderer' in updated:
             form = MilestoneParagraphInnerEditForm(self.context, self.request)
@@ -319,6 +304,8 @@
 
 @pagelet_config(name='add-milestone.html', context=IMilestoneContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-milestone.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=AJAXAddForm)
 class MilestoneAddForm(AdminDialogAddForm):
     """Milestone add form"""
 
@@ -326,7 +313,6 @@
     icon_css_class = 'fa fa-fw fa-arrow-h'
 
     fields = field.Fields(IMilestone).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-milestone.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def create(self, data):
@@ -335,12 +321,6 @@
     def add(self, object):
         IMilestoneContainer(self.context).append(object)
 
-
-@view_config(name='add-milestone.json', context=IMilestoneContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class MilestoneAJAXAddForm(AJAXAddForm, MilestoneAddForm):
-    """Milestone add form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         return {
             'status': 'success',
@@ -350,6 +330,7 @@
 
 
 @pagelet_config(name='properties.html', context=IMilestone, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IMilestone, layer=IPyAMSLayer)
 class MilestonePropertiesEditForm(AdminDialogEditForm):
     """Milestone properties edit form"""
 
@@ -359,17 +340,10 @@
     icon_css_class = 'fa fa-fw fa-arrows-h'
 
     fields = field.Fields(IMilestone).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
-
-@view_config(name='properties.json', context=IMilestone, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class MilestonePropertiesAJAXEditForm(AJAXEditForm, MilestonePropertiesEditForm):
-    """Milestone properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(MilestonePropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IMilestone, ())
         if ('title' in updated) or ('anchor' in updated):
             target = get_parent(self.context, IMilestoneContainerTarget)
--- a/src/pyams_content/component/paragraph/zmi/pictogram.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/pictogram.py	Fri Jun 08 10:35:42 2018 +0200
@@ -40,7 +40,7 @@
 from pyams_content.component.paragraph.zmi import BaseParagraphAddMenu, BaseParagraphAJAXAddForm, \
     BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
-from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_form.security import ProtectedFormObjectMixin
 from pyams_i18n.column import I18nAttrColumn
 from pyams_pagelet.pagelet import pagelet_config
@@ -88,6 +88,8 @@
     
 @pagelet_config(name='add-pictogram-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-pictogram-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class PictogramParagraphAddForm(AdminDialogAddForm):
     """Pictogram paragraph add form"""
     
@@ -95,7 +97,6 @@
     icon_css_class = 'fa fa-fw fa-linode'
     
     fields = field.Fields(IPictogramParagraph).select('title', 'renderer')
-    ajax_handler = 'add-pictogram-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
     
     def create(self, data):
@@ -103,16 +104,12 @@
     
     def add(self, object):
         IParagraphContainer(self.context).append(object)
-        
-        
-@view_config(name='add-pictogram-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PictogramParagraphAJAXAddForm(BaseParagraphAJAXAddForm, PictogramParagraphAddForm):
-    """Pictogram paragraph add form, JSON renderer"""
-    
-    
+
+
 @pagelet_config(name='properties.html', context=IPictogramParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IPictogramParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class PictogramParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Pictogram paragraph properties edit form"""
 
@@ -129,23 +126,17 @@
     fields = field.Fields(IPictogramParagraph).select('title', 'renderer')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
 
-@view_config(name='properties.json', context=IPictogramParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PictogramParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, PictogramParagraphPropertiesEditForm):
-    """Pictogram paragraph properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IPictogramParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IPictogramParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm, IPropertiesEditForm, IPictogramsParentForm)
 class PictogramParagraphInnerEditForm(PictogramParagraphPropertiesEditForm):
     """Pictogram paragraph inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -154,14 +145,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IPictogramParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PictogramParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, PictogramParagraphInnerEditForm):
-    """Pictograms paragraph inner edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(PictogramParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IPictogramParagraph, ())
         if 'renderer' in updated:
             output.setdefault('events', []).append(
@@ -325,6 +310,8 @@
 
 @pagelet_config(name='add-pictogram.html', context=IPictogramContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-pictogram.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=AJAXAddForm)
 class PictogramAddForm(AdminDialogAddForm):
     """Pictogram add form"""
 
@@ -332,7 +319,6 @@
     icon_css_class = 'fa fa-fw fa-arrow-h'
 
     fields = field.Fields(IPictogramItem).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-pictogram.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -362,12 +348,6 @@
     def add(self, object):
         IPictogramContainer(self.context).append(object)
 
-
-@view_config(name='add-pictogram.json', context=IPictogramContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PictogramAJAXAddForm(AJAXAddForm, PictogramAddForm):
-    """Pictogram add form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         return {
             'status': 'success',
@@ -385,6 +365,7 @@
 
 
 @pagelet_config(name='properties.html', context=IPictogramItem, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IPictogramItem, layer=IPyAMSLayer)
 class PictogramPropertiesEditForm(AdminDialogEditForm):
     """Pictogram properties edit form"""
 
@@ -394,7 +375,6 @@
     icon_css_class = 'fa fa-fw fa-linode'
 
     fields = field.Fields(IPictogramItem).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -424,14 +404,8 @@
         if 'body' in self.widgets:
             self.widgets['body'].widget_css_class = 'textarea height-100'
 
-
-@view_config(name='properties.json', context=IPictogramItem, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PictogramPropertiesAJAXEditForm(AJAXEditForm, PictogramPropertiesEditForm):
-    """Pictogram properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(PictogramPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IPictogramItem, ())
         if updated:
             target = get_parent(self.context, IPictogramContainerTarget)
--- a/src/pyams_content/component/paragraph/zmi/verbatim.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/verbatim.py	Fri Jun 08 10:35:42 2018 +0200
@@ -32,12 +32,12 @@
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
     BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_widget_refresh_event
 from pyams_utils.adapter import adapter_config
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer
 
@@ -61,6 +61,8 @@
 
 @pagelet_config(name='add-verbatim-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-verbatim-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class VerbatimParagraphAddForm(AdminDialogAddForm):
     """Verbatim paragraph add form"""
 
@@ -68,7 +70,6 @@
     icon_css_class = 'fa fa-fw fa-quote-right'
 
     fields = field.Fields(IVerbatimParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-verbatim-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -83,14 +84,10 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-verbatim-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class VerbatimParagraphAJAXAddForm(BaseParagraphAJAXAddForm, VerbatimParagraphAddForm):
-    """Verbatim paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IVerbatimParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IVerbatimParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class VerbatimParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Verbatim paragraph properties edit form"""
 
@@ -102,7 +99,6 @@
     fields = field.Fields(IVerbatimParagraph).omit('__parent__', '__name__', 'visible')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -111,19 +107,14 @@
             self.widgets['quote'].widget_css_class = 'textarea'
 
 
-@view_config(name='properties.json', context=IVerbatimParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class VerbatimParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, VerbatimParagraphPropertiesEditForm):
-    """Verbatim paragraph properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IVerbatimParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IVerbatimParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm, IPropertiesEditForm, IAssociationsParentForm)
 class VerbatimParagraphInnerEditForm(VerbatimParagraphPropertiesEditForm):
     """Verbatim paragraph inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -132,14 +123,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IVerbatimParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class VerbatimParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, VerbatimParagraphInnerEditForm):
-    """Verbatim paragraph inner edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(VerbatimParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IVerbatimParagraph, ())
         if 'renderer' in updated:
             output.setdefault('events', []).append(
--- a/src/pyams_content/component/paragraph/zmi/video.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/paragraph/zmi/video.py	Fri Jun 08 10:35:42 2018 +0200
@@ -33,6 +33,7 @@
 from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
     BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_form.form import ajax_config
 from pyams_form.group import NamedWidgetsGroup
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_form_refresh_event, get_json_widget_refresh_event
@@ -59,6 +60,8 @@
 
 @pagelet_config(name='add-video-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-video-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class VideoParagraphAddForm(AdminDialogAddForm):
     """Video paragraph add form"""
 
@@ -67,7 +70,6 @@
     icon_css_class = 'fa fa-fw fa-film'
 
     fields = field.Fields(IVideoParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-video-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -96,14 +98,10 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-video-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class VideoParagraphAJAXAddForm(BaseParagraphAJAXAddForm, VideoParagraphAddForm):
-    """Video paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IVideoParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IVideoParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class VideoParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Video paragraph properties edit form"""
 
@@ -116,7 +114,6 @@
     fields = field.Fields(IVideoParagraph).omit('__parent__', '__name__', 'visible')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -140,19 +137,14 @@
         super(VideoParagraphPropertiesEditForm, self).updateGroups()
 
 
-@view_config(name='properties.json', context=IVideoParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class VideoParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, VideoParagraphPropertiesEditForm):
-    """Video paragraph properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IVideoParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IVideoParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm, IPropertiesEditForm, IAssociationsParentForm)
 class VideoParagraphPropertiesInnerEditForm(VideoParagraphPropertiesEditForm):
     """Video paragraph properties inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -161,14 +153,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IVideoParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class VideoParagraphPropertiesInnerAJAXEditForm(BaseParagraphAJAXEditForm, VideoParagraphPropertiesInnerEditForm):
-    """Video paragraph properties inner deit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(VideoParagraphPropertiesInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IVideoParagraph, ())
         if 'data' in updated:
             # we have to commit transaction to be able to handle blobs...
--- a/src/pyams_content/component/theme/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/theme/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -28,7 +28,7 @@
 
 # import packages
 from pyams_content.shared.common.zmi import WfSharedContentHeaderAdapter
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_template.template import template_config
@@ -37,7 +37,6 @@
 from pyams_utils.traversing import get_parent
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminEditForm
-from pyramid.view import view_config
 from z3c.form import field
 from zope.interface import implementer
 
@@ -56,6 +55,7 @@
 
 @pagelet_config(name='themes.html', context=IThemesTarget, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
 @template_config(template='templates/themes-info.pt', layer=IPyAMSLayer)
+@ajax_config(name='themes.json', context=IThemesTarget, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
 @implementer(IWidgetForm, IInnerPage)
 class ThemesEditForm(AdminEditForm):
     """Themes edit form"""
@@ -64,8 +64,6 @@
 
     fields = field.Fields(IThemesInfo)
 
-    ajax_handler = 'themes.json'
-
     def __init__(self, context, request):
         super(ThemesEditForm, self).__init__(context, request)
         target = get_parent(self.context, IThemesManagerTarget)
@@ -96,12 +94,6 @@
                     yield another
 
 
-@view_config(name='themes.json', context=IThemesTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ThemesAJAXEditForm(AJAXEditForm, ThemesEditForm):
-    """Themes edit form, JSON renderer"""
-
-
 @adapter_config(context=(IThemesTarget, IAdminLayer, ThemesEditForm), provides=IPageHeader)
 class ThemesHeaderAdapter(WfSharedContentHeaderAdapter):
     """Shared content themes header adapter"""
--- a/src/pyams_content/component/theme/zmi/manager.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/theme/zmi/manager.py	Fri Jun 08 10:35:42 2018 +0200
@@ -25,13 +25,12 @@
 
 # import packages
 from pyams_content.skin import pyams_content
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_utils.fanstatic import get_resource_path
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogEditForm
-from pyramid.view import view_config
 from z3c.form import field
 from zope.interface import alsoProvides
 
@@ -51,6 +50,7 @@
 
 @pagelet_config(name='themes.html', context=IThemesManagerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_TOOL_PERMISSION)
+@ajax_config(name='themes.json', context=IThemesManagerTarget, layer=IPyAMSLayer)
 class ThemesManagerEditForm(AdminDialogEditForm):
     """Themes manager edit form"""
 
@@ -59,7 +59,6 @@
     legend = _("Selected themes")
 
     fields = field.Fields(IThemesManager)
-    ajax_handler = 'themes.json'
     edit_permission = MANAGE_TOOL_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -73,9 +72,3 @@
                 'ams-plugin-pyams_content-async': 'false'
             }
             alsoProvides(widget, IObjectData)
-
-
-@view_config(name='themes.json', context=IThemesManagerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
-class ThemesManagerAJAXEditForm(AJAXEditForm, ThemesManagerEditForm):
-    """Themes manager edit form, JSON renderer"""
--- a/src/pyams_content/component/video/zmi/paragraph.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/component/video/zmi/paragraph.py	Fri Jun 08 10:35:42 2018 +0200
@@ -34,6 +34,7 @@
     IParagraphEditFormButtons
 from pyams_content.component.video.paragraph import ExternalVideoParagraph
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_form.form import ajax_config
 from pyams_form.group import NamedWidgetsGroup
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_template.template import template_config
@@ -66,6 +67,8 @@
 
 @pagelet_config(name='add-external-video.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-external-video.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class ExternalVideoParagraphAddForm(AdminDialogAddForm):
     """External video paragraph add form"""
 
@@ -74,7 +77,6 @@
     icon_css_class = 'fa fa-fw fa-youtube-play'
 
     fields = field.Fields(IExternalVideoParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-external-video.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -131,12 +133,6 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-external-video.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExternalVideoParagraphAJAXAddForm(BaseParagraphAJAXAddForm, ExternalVideoParagraphAddForm):
-    """External video paragraph add form, JSON renderer"""
-
-
 @subscriber(IDataExtractedEvent, form_selector=ExternalVideoParagraphAddForm)
 def handle_video_paragraph_add_form_data_extraction(event):
     """Handle provider name data extraction"""
@@ -201,6 +197,8 @@
 
 @pagelet_config(name='properties.html', context=IExternalVideoParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IExternalVideoParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class ExternalVideoParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """External video paragraph properties edit form"""
 
@@ -209,7 +207,6 @@
     legend = _("Edit video properties")
     icon_css_class = 'fa fa-fw fa-youtube-play'
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     @property
@@ -262,14 +259,8 @@
                                                      switch=True,
                                                      display_mode='never'))
 
-
-@view_config(name='properties.json', context=IExternalVideoParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExternalVideoParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, ExternalVideoParagraphPropertiesEditForm):
-    """External video paragraph properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(ExternalVideoParagraphPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if 'title' in changes.get(IBaseParagraph, ()):
             output.setdefault('events', []).append(
                 get_json_paragraph_refresh_event(self.context, self.request))
@@ -277,12 +268,13 @@
 
 
 @adapter_config(context=(IExternalVideoParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IExternalVideoParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm)
 class ExternalVideoParagraphInnerEditForm(ExternalVideoParagraphPropertiesEditForm):
     """External video paragraph properties inner deit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -290,9 +282,3 @@
             return button.Buttons(IParagraphEditFormButtons)
         else:
             return button.Buttons()
-
-
-@view_config(name='inner-properties.json', context=IExternalVideoParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ExternalVideoParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, ExternalVideoParagraphInnerEditForm):
-    """External video paragraph inner edit form, JSON renderer"""
--- a/src/pyams_content/features/alert/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/features/alert/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -24,7 +24,7 @@
 # import packages
 from pyams_content.features.alert import AlertItem
 from pyams_content.features.alert.zmi.container import AlertContainerView, AlertContainerTable
-from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_table_row_refresh_event
 from pyams_skin.viewlet.toolbar import ToolbarAction
@@ -32,7 +32,6 @@
 from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
-from pyramid.view import view_config
 from z3c.form import field
 
 from pyams_content import _
@@ -50,6 +49,7 @@
 
 
 @pagelet_config(name='add-alert.html', context=IAlertTarget, layer=IPyAMSLayer, permission=MANAGE_SITE_ROOT_PERMISSION)
+@ajax_config(name='add-alert.json', context=IAlertTarget, layer=IPyAMSLayer, base=AJAXAddForm)
 class AlertItemAddForm(AdminDialogAddForm):
     """Alert item add form"""
 
@@ -57,7 +57,6 @@
     icon_css_class = 'fa fa-fw fa-exclamation-triangle'
 
     fields = field.Fields(IAlertItem).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-alert.json'
     edit_permission = MANAGE_SITE_ROOT_PERMISSION
 
     def create(self, data):
@@ -70,13 +69,8 @@
         return absolute_url(self.context, self.request, 'alerts.html')
 
 
-@view_config(name='add-alert.json', context=IAlertTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
-class AlertItemAJAXAddForm(AJAXAddForm, AlertItemAddForm):
-    """Alert item add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IAlertItem, layer=IPyAMSLayer, permission=MANAGE_SITE_ROOT_PERMISSION)
+@ajax_config(name='properties.json', context=IAlertItem, layer=IPyAMSLayer)
 class AlertItemPropertiesEditForm(AdminDialogEditForm):
     """Alert item properties edit form"""
 
@@ -86,17 +80,10 @@
     icon_css_class = 'fa fa-fw fa-exclamation-triangle'
 
     fields = field.Fields(IAlertItem).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_SITE_ROOT_PERMISSION
 
-
-@view_config(name='properties.json', context=IAlertItem, request_type=IPyAMSLayer,
-             permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
-class AlertItemPropertiesAJAXEditForm(AJAXEditForm, AlertItemPropertiesEditForm):
-    """Alert item properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(AlertItemPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         updated = changes.get(IAlertItem, ())
         if updated:
             target = get_parent(self.context, IAlertTarget)
--- a/src/pyams_content/features/footer/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/features/footer/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -29,10 +29,10 @@
 from z3c.form.interfaces import INPUT_MODE
 
 # import packages
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_form.group import NamedWidgetsGroup
 from pyams_pagelet.pagelet import pagelet_config
-from pyams_skin.viewlet.menu import MenuItem, MenuDivider
+from pyams_skin.viewlet.menu import MenuItem
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 from pyams_utils.url import absolute_url
@@ -63,6 +63,8 @@
 
 @pagelet_config(name='footer-settings.html', context=IFooterTarget, layer=IPyAMSLayer,
                 permission=MANAGE_TEMPLATE_PERMISSION)
+@ajax_config(name='footer-settings.json', context=IFooterTarget, layer=IPyAMSLayer,
+             permission=MANAGE_TEMPLATE_PERMISSION)
 @implementer(IWidgetForm, IInnerPage)
 class FooterSettingsEditForm(AdminEditForm):
     """Footer settings edit form"""
@@ -92,8 +94,6 @@
         else:
             return button.Buttons(Interface)
 
-    ajax_handler = 'footer-settings.json'
-
     def updateGroups(self):
         if self.getContent().can_inherit:
             group = NamedWidgetsGroup(self, 'footer', self.widgets,
@@ -110,14 +110,8 @@
         self.add_group(group)
         super(FooterSettingsEditForm, self).updateGroups()
 
-
-@view_config(name='footer-settings.json', context=IFooterTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
-class FooterSettingsAJAXEditForm(AJAXEditForm, FooterSettingsEditForm):
-    """Footer settings edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(FooterSettingsAJAXEditForm, self).get_ajax_output(changes) or {}
+        output = super(self.__class__, self).get_ajax_output(changes) or {}
         if 'no_inherit' in changes.get(IInheritInfo, ()):
             output['status'] = 'reload'
         return output
@@ -164,7 +158,7 @@
                 context=(IFooterRendererSettings, IPyAMSLayer, FooterSettingsRendererEditSubform),
                 provides=IInnerSubForm)
 @adapter_config(name='footer-renderer-settings-form',
-                context=(IFooterTarget, IPyAMSLayer, FooterSettingsAJAXEditForm),
+                context=(IFooterTarget, IPyAMSLayer, FooterSettingsEditForm),
                 provides=IInnerSubForm)
 class FooterSettingsRendererSettingsEditForm(InnerAdminEditForm):
     """Footer settings renderer settings edit form"""
--- a/src/pyams_content/features/header/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/features/header/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -29,7 +29,7 @@
 from z3c.form.interfaces import INPUT_MODE
 
 # import packages
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_form.group import NamedWidgetsGroup
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.menu import MenuItem, MenuDivider
@@ -69,6 +69,8 @@
 
 @pagelet_config(name='header-settings.html', context=IHeaderTarget, layer=IPyAMSLayer,
                 permission=MANAGE_TEMPLATE_PERMISSION)
+@ajax_config(name='header-settings.json', context=IHeaderTarget, layer=IPyAMSLayer,
+             permission=MANAGE_TEMPLATE_PERMISSION)
 @implementer(IWidgetForm, IInnerPage)
 class HeaderSettingsEditForm(AdminEditForm):
     """Header settings edit form"""
@@ -98,8 +100,6 @@
         else:
             return button.Buttons(Interface)
 
-    ajax_handler = 'header-settings.json'
-
     def updateGroups(self):
         if self.getContent().can_inherit:
             group = NamedWidgetsGroup(self, 'header', self.widgets,
@@ -116,14 +116,8 @@
         self.add_group(group)
         super(HeaderSettingsEditForm, self).updateGroups()
 
-
-@view_config(name='header-settings.json', context=IHeaderTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
-class HeaderSettingsAJAXEditForm(AJAXEditForm, HeaderSettingsEditForm):
-    """Header settings edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(HeaderSettingsAJAXEditForm, self).get_ajax_output(changes) or {}
+        output = super(self.__class__, self).get_ajax_output(changes) or {}
         if 'no_inherit' in changes.get(IInheritInfo, ()):
             output['status'] = 'reload'
         return output
@@ -170,7 +164,7 @@
                 context=(IHeaderRendererSettings, IPyAMSLayer, HeaderSettingsRendererEditSubform),
                 provides=IInnerSubForm)
 @adapter_config(name='header-renderer-settings-form',
-                context=(IHeaderTarget, IPyAMSLayer, HeaderSettingsAJAXEditForm),
+                context=(IHeaderTarget, IPyAMSLayer, HeaderSettingsEditForm),
                 provides=IInnerSubForm)
 class HeaderSettingsRendererSettingsEditForm(InnerAdminEditForm):
     """Header settings renderer settings edit form"""
--- a/src/pyams_content/features/renderer/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/features/renderer/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -21,11 +21,10 @@
 from pyams_skin.layer import IPyAMSLayer
 
 # import packages
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_viewlet.viewlet import BaseContentProvider
 from pyams_zmi.form import AdminDialogEditForm
-from pyramid.view import view_config
 from z3c.form import field
 from zope.interface import Interface
 
@@ -64,6 +63,7 @@
 
 @pagelet_config(name='renderer-properties.html', context=IRenderedContent, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='renderer-properties.json', context=IRenderedContent, layer=IPyAMSLayer)
 class RendererPropertiesEditForm(AdminDialogEditForm):
     """Renderer properties edit form"""
 
@@ -77,14 +77,7 @@
         renderer = IContentRenderer(self.context)
         return field.Fields(renderer.settings_interface or Interface)
 
-    ajax_handler = 'renderer-properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def getContent(self):
         return IRendererSettings(self.context)
-
-
-@view_config(name='renderer-properties.json', context=IRenderedContent, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class RendererPropertiesAJAXEditForm(AJAXEditForm, RendererPropertiesEditForm):
-    """Renderer properties edit form, JSON renderer"""
--- a/src/pyams_content/features/review/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/features/review/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -29,7 +29,7 @@
 
 # import packages
 from pyams_content.features.review import ReviewComment
-from pyams_form.form import AJAXAddForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_form.schema import CloseButton
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_security.schema import PrincipalsSet
@@ -93,6 +93,7 @@
 
 @pagelet_config(name='ask-review.html', context=IReviewTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='ask-review.json', context=IReviewTarget, layer=IPyAMSLayer, base=AJAXAddForm)
 class WfSharedContentReviewForm(AdminDialogAddForm):
     """Shared content review form"""
 
@@ -102,7 +103,6 @@
     fields = field.Fields(ISharedContentReviewInfo)
     buttons = button.Buttons(ISharedContentReviewButtons)
 
-    ajax_handler = 'ask-review.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     label_css_class = 'control-label col-md-4'
@@ -127,12 +127,6 @@
                                       data.get('comment'),
                                       data.get('notify_all'))
 
-
-@view_config(name='ask-review.json', context=IReviewTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class WfSharedContentReviewAJAXForm(AJAXAddForm, WfSharedContentReviewForm):
-    """Shared content review form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         translate = self.request.localizer.translate
         if changes:
--- a/src/pyams_content/reference/pictograms/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/reference/pictograms/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -9,7 +9,6 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-from z3c.form.interfaces import NOVALUE, NO_VALUE
 
 __docformat__ = 'restructuredtext'
 
@@ -24,7 +23,6 @@
 from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION, IBaseContent
 from pyams_content.reference.pictograms.interfaces import IPictogramTable, IPictogram
 from pyams_content.reference.zmi.table import ReferenceTableContentsTable, ReferenceTableContentsView
-from pyams_form.form import AJAXAddForm, AJAXEditForm
 from pyams_i18n.interfaces import II18n
 from pyams_skin.interfaces.viewlet import IWidgetTitleViewletManager
 from pyams_skin.layer import IPyAMSLayer
@@ -34,6 +32,7 @@
 
 # import packages
 from pyams_content.reference.pictograms import Pictogram
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_i18n.column import I18nAttrColumn
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_table_row_refresh_event
@@ -67,6 +66,7 @@
 
 @pagelet_config(name='add-pictogram.html', context=IPictogramTable, layer=IPyAMSLayer,
                 permission=MANAGE_SITE_ROOT_PERMISSION)
+@ajax_config(name='add-pictogram.json', context=IPictogramTable, layer=IPyAMSLayer, base=AJAXAddForm)
 class PictogramAddForm(AdminDialogAddForm):
     """Pictogram add form"""
 
@@ -74,7 +74,6 @@
     dialog_class = 'modal-large'
 
     fields = field.Fields(IPictogram).omit('__parent__', '__name__')
-    ajax_handler = 'add-pictogram.json'
     edit_permission = MANAGE_SITE_ROOT_PERMISSION
 
     def create(self, data):
@@ -88,13 +87,8 @@
         return 'contents.html'
 
 
-@view_config(name='add-pictogram.json', context=IPictogramTable, request_type=IPyAMSLayer,
-             permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
-class PictogramAJAXAddForm(AJAXAddForm, PictogramAddForm):
-    """Pictogram add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IPictogram, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IPictogram, layer=IPyAMSLayer)
 class PictogramEditForm(AdminDialogEditForm):
     """Pictogram properties edit form"""
 
@@ -104,17 +98,10 @@
     dialog_class = 'modal-large'
 
     fields = field.Fields(IPictogram).omit('__parent__', '__name__')
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_SITE_ROOT_PERMISSION
 
-
-@view_config(name='properties.json', context=IPictogram, request_type=IPyAMSLayer,
-             permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
-class PictogramAJAXEditForm(AJAXEditForm, PictogramEditForm):
-    """Pictogram edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(PictogramAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if ('image' in changes.get(IPictogram, ())) or \
            ('title' in changes.get(IBaseContent, ())):
             parent = get_parent(self.context, IPictogramTable)
--- a/src/pyams_content/reference/pictograms/zmi/manager.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/reference/pictograms/zmi/manager.py	Fri Jun 08 10:35:42 2018 +0200
@@ -27,7 +27,7 @@
 from pyams_zmi.layer import IAdminLayer
 
 # import packages
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import AJAXEditForm, ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_template.template import template_config
@@ -55,6 +55,7 @@
 
 @pagelet_config(name='pictograms-selection.html', context=IPictogramManagerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_TOOL_PERMISSION)
+@ajax_config(name='pictograms-selection.json', context=IPictogramManagerTarget, layer=IPyAMSLayer)
 @implementer(IWidgetForm, IInnerPage, IObjectData)
 class PictogramManagerEditForm(AdminEditForm):
     """Pictogram manager selection form"""
@@ -66,7 +67,6 @@
 
     object_data = {'ams-form-data-init-callback': 'PyAMS_content.pictograms.initManagerSelection'}
 
-    ajax_handler = 'pictograms-selection.json'
     edit_permission = MANAGE_TOOL_PERMISSION
 
     def update_content(self, content, data):
@@ -80,12 +80,6 @@
         return changes
 
 
-@view_config(name='pictograms-selection.json', context=IPictogramManagerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
-class PictogramManagerAJAXEditForm(AJAXEditForm, PictogramManagerEditForm):
-    """Pictogram manager selection form, JSON renderer"""
-
-
 @viewlet_config(name='pictogram-selection.subform', context=IPictogramManagerTarget, layer=IAdminLayer,
                 view=PictogramManagerEditForm, manager=IWidgetsSuffixViewletsManager, permission=MANAGE_TOOL_PERMISSION)
 @template_config(template='templates/manager-selection.pt', layer=IAdminLayer)
--- a/src/pyams_content/reference/zmi/table.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/reference/zmi/table.py	Fri Jun 08 10:35:42 2018 +0200
@@ -28,7 +28,7 @@
 from z3c.table.interfaces import IValues, IColumn
 
 # import packages
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import AJAXEditForm, ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.container import ContainerView, delete_container_element
 from pyams_skin.page import DefaultPageHeaderAdapter
@@ -148,6 +148,7 @@
 
 
 @pagelet_config(name='properties.html', context=IReferenceTable, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IReferenceTable, layer=IPyAMSLayer)
 class ReferenceTablePropertiesEditForm(AdminDialogEditForm):
     """Reference table properties edit form"""
 
@@ -156,16 +157,9 @@
     legend = _("Edit table properties")
 
     fields = field.Fields(IReferenceTable).omit('__parent__', '__name__')
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_SITE_ROOT_PERMISSION
 
 
-@view_config(name='properties.json', context=IReferenceTable, request_type=IPyAMSLayer,
-             permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
-class ReferenceTablePropertiesAJAXEditForm(AJAXEditForm, ReferenceTablePropertiesEditForm):
-    """Reference table properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IReferenceTable, ISiteManagementMenu), provides=IMenuHeader)
 class ReferenceTableSiteManagementMenuHeader(ContextRequestAdapter):
     """Reference table site management menu header adapter"""
--- a/src/pyams_content/shared/blog/zmi/manager.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/blog/zmi/manager.py	Fri Jun 08 10:35:42 2018 +0200
@@ -32,7 +32,7 @@
 
 # import packages
 from pyams_content.shared.blog.manager import BlogManager
-from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.table import DefaultElementEditorAdapter
 from pyams_skin.viewlet.menu import MenuItem
@@ -88,6 +88,8 @@
 
 @pagelet_config(name='add-blog-manager.html', context=ISiteRoot, layer=IPyAMSLayer,
                 permission=MANAGE_SITE_ROOT_PERMISSION)
+@ajax_config(name='add-blog-manager.json', context=ISiteRoot, layer=IPyAMSLayer,
+             permission=MANAGE_SITE_ROOT_PERMISSION, base=AJAXAddForm)
 class BlogManagerAddForm(AdminDialogAddForm):
     """Blog manager add form"""
 
@@ -96,7 +98,6 @@
     icon_css_class = 'fa fa-fw fa-tags'
 
     fields = field.Fields(IBlogManager).select('title', 'short_name')
-    ajax_handler = 'add-blog-manager.json'
     edit_permission = None
 
     def create(self, data):
@@ -134,12 +135,6 @@
         event.form.widgets.errors += (Invalid(_("A blog manager is already registered with this name!!")),)
 
 
-@view_config(name='add-blog-manager.json', context=ISiteRoot, request_type=IPyAMSLayer,
-             permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
-class BlogManagerAJAXAddForm(AJAXAddForm, BlogManagerAddForm):
-    """Blog manager add form, JSON renderer"""
-
-
 @adapter_config(context=(IBlogManager, IAdminLayer, ISiteTreeTable), provides=ITableElementEditor)
 class BlogManagerTableElementEditor(DefaultElementEditorAdapter):
     """Blog manager table element editor"""
@@ -177,6 +172,7 @@
 
 @pagelet_config(name='workflow-publication.html', context=IBlogManager, layer=IPyAMSLayer,
                 permission=MANAGE_SITE_PERMISSION)
+@ajax_config(name='workflow-publication.json', context=IBlogManager, layer=IPyAMSLayer)
 class BlogManagerWorkflowPublicationEditForm(AdminDialogEditForm):
     """Blog manager workflow publication edit form"""
 
@@ -185,7 +181,6 @@
     legend = _("Update publication dates")
 
     fields = field.Fields(IWorkflowPublicationInfo).select('publication_effective_date', 'publication_expiration_date')
-    ajax_handler = 'workflow-publication.json'
     edit_permission = MANAGE_SITE_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -194,9 +189,3 @@
             widget = self.widgets['publication_effective_date']
             if not widget.value:
                 widget.value = tztime(datetime.utcnow()).strftime('%d/%m/%y %H:%M')
-
-
-@view_config(name='workflow-publication.json', context=IBlogManager, request_type=IPyAMSLayer,
-             permission=MANAGE_SITE_PERMISSION, renderer='json', xhr=True)
-class BlogManagerWorkflowPublicationAJAXEditForm(AJAXEditForm, BlogManagerWorkflowPublicationEditForm):
-    """Blog manager workflow publication edit form, JSON renderer"""
--- a/src/pyams_content/shared/common/security.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/common/security.py	Fri Jun 08 10:35:42 2018 +0200
@@ -71,9 +71,10 @@
         if IPrincipalInfo.providedBy(principal):
             principal = principal.id
         restrictions_folder = get_annotation_adapter(self.context, self.restrictions_key, Folder)
-        if restrictions is None:
-            restrictions = self.new_restrictions(principal)
-        restrictions_folder[principal] = restrictions
+        if principal not in restrictions_folder:
+            if restrictions is None:
+                restrictions = self.new_restrictions(principal)
+            restrictions_folder[principal] = restrictions
 
     def drop_restrictions(self, principal):
         restrictions_folder = get_annotation_adapter(self.context, self.restrictions_key)
--- a/src/pyams_content/shared/common/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/common/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -37,7 +37,7 @@
 from zope.dublincore.interfaces import IZopeDublinCore
 
 # import packages
-from pyams_form.form import AJAXAddForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_form.schema import CloseButton
 from pyams_i18n.widget import I18nSEOTextLineFieldWidget
 from pyams_pagelet.pagelet import pagelet_config
@@ -55,7 +55,6 @@
 from pyams_workflow.versions import WorkflowHistoryItem
 from pyams_zmi.form import AdminDialogAddForm
 from pyramid.location import lineage
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.copy import copy
 from zope.interface import Interface
@@ -279,6 +278,7 @@
 
 @pagelet_config(name='duplicate.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=CREATE_CONTENT_PERMISSION)
+@ajax_config(name='duplicate.json', context=IWfSharedContent, layer=IPyAMSLayer, base=AJAXAddForm)
 class WfSharedContentDuplicateForm(AdminDialogAddForm):
     """Shared content duplicate form"""
 
@@ -288,7 +288,6 @@
     fields = field.Fields(IWorkflowCommentInfo)
     buttons = button.Buttons(ISharedContentDuplicateButtons)
 
-    ajax_handler = 'duplicate.json'
     edit_permission = CREATE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -349,12 +348,6 @@
         state.history.append(history)
         return new_version
 
-
-@view_config(name='duplicate.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=CREATE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class WfSharedContentDuplicateAJAXForm(AJAXAddForm, WfSharedContentDuplicateForm):
-    """Shared content duplicate form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         return {
             'status': 'redirect',
--- a/src/pyams_content/shared/common/zmi/manager.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/common/zmi/manager.py	Fri Jun 08 10:35:42 2018 +0200
@@ -30,7 +30,7 @@
 from z3c.form.interfaces import DISPLAY_MODE
 
 # import packages
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_form.help import FormHelp
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.page import DefaultPageHeaderAdapter
@@ -40,7 +40,6 @@
 from pyams_viewlet.manager import viewletmanager_config
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminEditForm
-from pyramid.view import view_config
 from z3c.form import field
 from zope.interface import implementer, Interface
 
@@ -100,6 +99,7 @@
 
 
 @pagelet_config(name='properties.html', context=IBaseSharedTool, layer=IPyAMSLayer, permission=MANAGE_TOOL_PERMISSION)
+@ajax_config(name='properties.json', context=IBaseSharedTool, layer=IPyAMSLayer)
 @implementer(IWidgetForm, IInnerPage, IPropertiesEditForm)
 class SharedToolPropertiesEditForm(AdminEditForm):
     """Shared tool properties edit form"""
@@ -108,7 +108,6 @@
 
     fields = field.Fields(IBaseSharedTool).omit('__parent__', '__name__')
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_TOOL_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -117,12 +116,6 @@
             self.widgets['shared_content_workflow'].mode = DISPLAY_MODE
 
 
-@view_config(name='properties.json', context=IBaseSharedTool, request_type=IPyAMSLayer,
-             permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
-class SharedToolPropertiesAJAXEditForm(AJAXEditForm, SharedToolPropertiesEditForm):
-    """Shared tool properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IBaseSharedTool, IPyAMSLayer, SharedToolPropertiesEditForm), provides=IFormHelp)
 class SharedToolPropertiesHelpAdapter(FormHelp):
     """Shared tool properties help adapter"""
@@ -161,6 +154,7 @@
 
 
 @pagelet_config(name='languages.html', context=IBaseSharedTool, layer=IPyAMSLayer, permission=MANAGE_TOOL_PERMISSION)
+@ajax_config(name='languages.json', context=IBaseSharedTool, layer=IPyAMSLayer)
 @implementer(IInnerPage, IWidgetForm)
 class SharedToolLanguagesEditForm(AdminEditForm):
     """Shared tool languages edit form"""
@@ -168,16 +162,9 @@
     legend = _("Content languages")
 
     fields = field.Fields(II18nManager)
-    ajax_handler = 'languages.json'
     edit_permission = MANAGE_TOOL_PERMISSION
 
 
-@view_config(name='languages.json', context=IBaseSharedTool, request_type=IPyAMSLayer,
-             permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
-class SharedToolLanguagesAJAXEditForm(AJAXEditForm, SharedToolLanguagesEditForm):
-    """Shared tool languages edit form, JSON renderer"""
-
-
 @adapter_config(context=(IBaseSharedTool, IPyAMSLayer, SharedToolLanguagesEditForm), provides=IFormHelp)
 class SharedToolLanguagesEditFormHelp(FormHelp):
     """Shared tool languages edit form help"""
--- a/src/pyams_content/shared/common/zmi/owner.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/common/zmi/owner.py	Fri Jun 08 10:35:42 2018 +0200
@@ -24,7 +24,7 @@
 from pyams_workflow.interfaces import IWorkflowVersions, IWorkflow, IWorkflowState
 
 # import packages
-from pyams_form.form import AJAXAddForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_form.help import FormHelp
 from pyams_form.schema import CloseButton
 from pyams_pagelet.pagelet import pagelet_config
@@ -33,7 +33,6 @@
 from pyams_utils.adapter import adapter_config
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
 from zope.interface import Interface
@@ -77,6 +76,7 @@
 
 @pagelet_config(name='change-owner.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=MANAGE_SITE_PERMISSION)
+@ajax_config(name='change-owner.json', context=IWfSharedContent, layer=IPyAMSLayer, base=AJAXAddForm)
 class WfSharedContentOwnerChangeForm(AdminDialogAddForm):
     """Shared content owner change form"""
 
@@ -86,7 +86,6 @@
     fields['keep_owner_as_contributor'].widgetFactory = SingleCheckBoxFieldWidget
     buttons = button.Buttons(IWfSharedContentOwnerChangeButtons)
 
-    ajax_handler = 'change-owner.json'
     edit_permission = MANAGE_SITE_PERMISSION
 
     def updateActions(self):
@@ -115,12 +114,6 @@
             roles.contributors = contributors
             self.request.registry.notify(ObjectModifiedEvent(version))
 
-
-@view_config(name='change-owner.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=MANAGE_SITE_PERMISSION, renderer='json', xhr=True)
-class WfSharedContentOwnerChangeAJAXForm(AJAXAddForm,WfSharedContentOwnerChangeForm):
-    """Shared content owner change form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         return {'status': 'reload'}
 
--- a/src/pyams_content/shared/common/zmi/properties.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/common/zmi/properties.py	Fri Jun 08 10:35:42 2018 +0200
@@ -9,11 +9,6 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-from pyramid.events import subscriber
-from z3c.form.interfaces import IDataExtractedEvent, HIDDEN_MODE
-
-from pyams_skin.event import get_json_widget_refresh_event
-from pyams_utils.url import generate_url
 
 __docformat__ = 'restructuredtext'
 
@@ -30,18 +25,21 @@
 from pyams_zmi.interfaces import IPropertiesEditForm
 from pyams_zmi.interfaces.menu import IContentManagementMenu, IPropertiesMenu
 from pyams_zmi.layer import IAdminLayer
+from z3c.form.interfaces import IDataExtractedEvent, HIDDEN_MODE
 
 # import packages
 from pyams_content.shared.common.zmi import WfSharedContentHeaderAdapter
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_i18n.widget import I18nSEOTextLineFieldWidget
 from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.event import get_json_widget_refresh_event
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_utils.adapter import adapter_config
+from pyams_utils.url import generate_url
 from pyams_viewlet.manager import viewletmanager_config
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminEditForm
-from pyramid.view import view_config
+from pyramid.events import subscriber
 from z3c.form import field
 from zope.interface import implementer
 
@@ -76,6 +74,7 @@
 
 @pagelet_config(name='properties.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IWfSharedContent, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
 @implementer(IPropertiesEditForm, IWidgetForm, IInnerPage)
 class SharedContentPropertiesEditForm(AdminEditForm):
     """Shared content properties edit form"""
@@ -86,8 +85,6 @@
                                                    'description', 'notepad')
     fields['title'].widgetFactory = I18nSEOTextLineFieldWidget
 
-    ajax_handler = 'properties.json'
-
     def updateWidgets(self, prefix=None):
         super(SharedContentPropertiesEditForm, self).updateWidgets(prefix)
         if 'short_name' in self.widgets:
@@ -97,6 +94,22 @@
         if 'notepad' in self.widgets:
             self.widgets['notepad'].widget_css_class = 'textarea'
 
+    def get_ajax_output(self, changes):
+        updated = changes.get(IBaseContent, ())
+        if 'title' in updated:
+            return {
+                'status': 'reload',
+                'message': self.request.localizer.translate(self.successMessage)
+            }
+        else:
+            output = super(self.__class__, self).get_ajax_output(changes)
+            updated = changes.get(IWfSharedContent, ())
+            if 'content_url' in updated:
+                output.setdefault('events', []).append(
+                    get_json_widget_refresh_event(self.context, self.request,
+                                                  SharedContentPropertiesEditForm, 'content_url'))
+            return output
+
 
 @subscriber(IDataExtractedEvent, form_selector=SharedContentPropertiesEditForm)
 def handle_content_properties_data_extraction(event):
@@ -106,28 +119,6 @@
     data['content_url'] = generate_url(data['content_url'])
 
 
-@view_config(name='properties.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class SharedContentPropertiesAJAXEditForm(AJAXEditForm, SharedContentPropertiesEditForm):
-    """Shared content properties edit form, JSON renderer"""
-
-    def get_ajax_output(self, changes):
-        updated = changes.get(IBaseContent, ())
-        if 'title' in updated:
-            return {
-                'status': 'reload',
-                'message': self.request.localizer.translate(self.successMessage)
-            }
-        else:
-            output = super(SharedContentPropertiesAJAXEditForm, self).get_ajax_output(changes)
-            updated = changes.get(IWfSharedContent, ())
-            if 'content_url' in updated:
-                output.setdefault('events', []).append(
-                    get_json_widget_refresh_event(self.context, self.request,
-                                                  SharedContentPropertiesEditForm, 'content_url'))
-            return output
-
-
 @adapter_config(context=(IWfSharedContent, IAdminLayer, SharedContentPropertiesEditForm), provides=IPageHeader)
 class SharedContentPropertiesHeaderAdapter(WfSharedContentHeaderAdapter):
     """Shared content properties header adapter"""
--- a/src/pyams_content/shared/common/zmi/security.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/common/zmi/security.py	Fri Jun 08 10:35:42 2018 +0200
@@ -27,7 +27,7 @@
 from z3c.table.interfaces import IValues, IColumn
 
 # import packages
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_form.group import NamedWidgetsGroup
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.container import ContainerView
@@ -42,7 +42,6 @@
 from pyams_zmi.view import AdminView
 from pyramid.exceptions import NotFound
 from pyramid.url import resource_url
-from pyramid.view import view_config
 from z3c.form import field
 from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
 from z3c.form.interfaces import HIDDEN_MODE
@@ -153,6 +152,7 @@
 
 @pagelet_config(name='contributor-restrictions.html', context=IBaseSharedTool, layer=IPyAMSLayer,
                 permission=MANAGE_TOOL_PERMISSION)
+@ajax_config(name='contributor-restrictions.json', context=IBaseSharedTool, layer=IPyAMSLayer)
 class SharedToolContributorRestrictionsEditForm(AdminDialogEditForm):
     """Shared tool contributor restrictions edit form"""
 
@@ -160,7 +160,6 @@
 
     icon_css_class = 'fa fa-fw fa-lock'
 
-    ajax_handler = 'contributor-restrictions.json'
     edit_permission = MANAGE_TOOL_PERMISSION
 
     @property
@@ -200,17 +199,11 @@
         self.widgets['principal_id'].value = self.principal
         self.widgets['principal_id'].mode = HIDDEN_MODE
 
-
-@view_config(name='contributor-restrictions.json', context=IBaseSharedTool, request_type=IPyAMSLayer,
-             permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
-class SharedToolContributorRestrictionsAJAXEditForm(AJAXEditForm, SharedToolContributorRestrictionsEditForm):
-    """Shared tool contributor restrictions edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         if changes:
             return {'status': 'reload'}
         else:
-            return super(SharedToolContributorRestrictionsAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
 
 
 #
@@ -345,6 +338,7 @@
 
 @pagelet_config(name='manager-restrictions.html', context=IBaseSharedTool, layer=IPyAMSLayer,
                 permission=MANAGE_TOOL_PERMISSION)
+@ajax_config(name='manager-restrictions.json', context=IBaseSharedTool, layer=IPyAMSLayer)
 class SharedToolManagerRestrictionsEditForm(AdminDialogEditForm):
     """Shared tool manager restrictions edit form"""
 
@@ -352,7 +346,6 @@
 
     icon_css_class = 'fa fa-fw fa-lock'
 
-    ajax_handler = 'manager-restrictions.json'
     edit_permission = MANAGE_TOOL_PERMISSION
 
     @property
@@ -414,14 +407,8 @@
                                          checkbox_field=self.interface['restricted_contents']))
         super(SharedToolManagerRestrictionsEditForm, self).updateGroups()
 
-
-@view_config(name='manager-restrictions.json', context=IBaseSharedTool, request_type=IPyAMSLayer,
-             permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
-class SharedToolManagerRestrictionsAJAXEditForm(AJAXEditForm, SharedToolManagerRestrictionsEditForm):
-    """Shared tool manager restrictions edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         if changes:
             return {'status': 'reload'}
         else:
-            return super(SharedToolManagerRestrictionsAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
--- a/src/pyams_content/shared/common/zmi/types.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/common/zmi/types.py	Fri Jun 08 10:35:42 2018 +0200
@@ -33,7 +33,7 @@
 
 # import packages
 from pyams_content.shared.common.types import DataType, SubType
-from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_form.security import ProtectedFormObjectMixin
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.container import delete_container_element
@@ -239,6 +239,7 @@
 
 @pagelet_config(name='add-data-type.html', context=ITypedSharedTool, layer=IPyAMSLayer,
                 permission=MANAGE_TOOL_PERMISSION)
+@ajax_config(name='add-data-type.json', context=ITypedSharedTool, layer=IPyAMSLayer, base=AJAXAddForm)
 class DataTypeAddForm(AdminDialogAddForm):
     """Data type add form"""
 
@@ -249,7 +250,6 @@
 
     fields = field.Fields(IDataType).omit('__parent__', '__name__')
 
-    ajax_handler = 'add-data-type.json'
     edit_permission = MANAGE_TOOL_PERMISSION
 
     def create(self, data):
@@ -260,7 +260,7 @@
         ITypedDataManager(self.context)[name] = object
 
     def nextURL(self):
-        return absolute_url(self.context, self.request, 'admin#data-types.html')
+        return '#data-types.html'
 
 
 @subscriber(IDataExtractedEvent, form_selector=DataTypeAddForm)
@@ -273,16 +273,8 @@
         event.form.widgets.errors += (Invalid(_("Specified type name is already used!")),)
 
 
-@view_config(name='add-data-type.json', context=ITypedSharedTool, request_type=IPyAMSLayer,
-             permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
-class DataTypeAJAXAddForm(AJAXAddForm, DataTypeAddForm):
-    """Data type add form, JSON renderer"""
-
-    def nextURL(self):
-        return '#data-types.html'
-
-
 @pagelet_config(name='properties.html', context=IDataType, layer=IPyAMSLayer, permission=MANAGE_TOOL_PERMISSION)
+@ajax_config(name='properties.json', context=IDataType, layer=IPyAMSLayer)
 class DataTypeEditForm(AdminDialogEditForm):
     """Data type edit form"""
 
@@ -295,7 +287,6 @@
 
     fields = field.Fields(IDataType).omit('__parent__', '__name__')
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_TOOL_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -304,12 +295,6 @@
             self.widgets['name'].mode = DISPLAY_MODE
 
 
-@view_config(name='properties.json', context=IDataType, request_type=IPyAMSLayer,
-             permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
-class DataTypeAJAXEditForm(AJAXEditForm, DataTypeEditForm):
-    """Data type edit form, JSON renderer"""
-
-
 #
 # Subtypes views
 #
@@ -460,6 +445,7 @@
 
 @pagelet_config(name='add-data-subtype.html', context=IDataType, layer=IPyAMSLayer,
                 permission=MANAGE_TOOL_PERMISSION)
+@ajax_config(name='add-data-subtype.json', context=IDataType, layer=IPyAMSLayer, base=AJAXAddForm)
 class DataSubtypeAddForm(AdminDialogAddForm):
     """Data subtype add form"""
 
@@ -470,7 +456,6 @@
 
     fields = field.Fields(ISubType).omit('__parent__', '__name__')
 
-    ajax_handler = 'add-data-subtype.json'
     edit_permission = MANAGE_TOOL_PERMISSION
 
     def create(self, data):
@@ -483,6 +468,15 @@
     def nextURL(self):
         return absolute_url(self.context, self.request, 'admin#data-types.html')
 
+    def get_ajax_output(self, changes):
+        return {
+            'status': 'success',
+            'message': self.request.localizer.translate(_("Subtype was correctly added.")),
+            'events': [
+                get_json_table_refresh_event(self.context, self.request, DatatypeSubtypesTable)
+            ]
+        }
+
 
 @subscriber(IDataExtractedEvent, form_selector=DataSubtypeAddForm)
 def handle_subtype_add_form_data_extraction(event):
@@ -494,22 +488,8 @@
         event.form.widgets.errors += (Invalid(_("Specified subtype name is already used!")),)
 
 
-@view_config(name='add-data-subtype.json', context=IDataType, request_type=IPyAMSLayer,
-             permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
-class DataSubtypeAJAXAddForm(AJAXAddForm, DataSubtypeAddForm):
-    """Data subtype add form, JSON renderer"""
-
-    def get_ajax_output(self, changes):
-        return {
-            'status': 'success',
-            'message': self.request.localizer.translate(_("Subtype was correctly added.")),
-            'events': [
-                get_json_table_refresh_event(self.context, self.request, DatatypeSubtypesTable)
-            ]
-        }
-
-
 @pagelet_config(name='properties.html', context=ISubType, layer=IPyAMSLayer, permission=MANAGE_TOOL_PERMISSION)
+@ajax_config(name='properties.json', context=ISubType, layer=IPyAMSLayer)
 class DataSubtypeEditForm(AdminDialogEditForm):
     """Data subtype edit form"""
 
@@ -522,7 +502,6 @@
 
     fields = field.Fields(ISubType).omit('__parent__', '__name__')
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_TOOL_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -530,12 +509,6 @@
         if 'name' in self.widgets:
             self.widgets['name'].mode = DISPLAY_MODE
 
-
-@view_config(name='properties.json', context=ISubType, request_type=IPyAMSLayer,
-             permission=MANAGE_TOOL_PERMISSION, renderer='json', xhr=True)
-class DataSubtypeAJAXEditForm(AJAXEditForm, DataSubtypeEditForm):
-    """Data subtype edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
         if 'label' in changes.get(IBaseDataType, ()):
             target = get_parent(self.context, IDataType)
@@ -547,4 +520,4 @@
                 ]
             }
         else:
-            return super(DataSubtypeAJAXEditForm, self).get_ajax_output(changes)
+            return super(self.__class__, self).get_ajax_output(changes)
--- a/src/pyams_content/shared/common/zmi/workflow.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/common/zmi/workflow.py	Fri Jun 08 10:35:42 2018 +0200
@@ -36,7 +36,7 @@
 
 # import packages
 from pyams_content.workflow import DRAFT, DELETED
-from pyams_form.form import AJAXAddForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_form.schema import CloseButton
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_template.template import template_config
@@ -128,6 +128,8 @@
 
 @pagelet_config(name='wf-propose.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='wf-propose.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, base=WorkflowContentTransitionAJAXForm)
 class PublicationRequestForm(WorkflowContentTransitionForm):
     """Shared content publication request form"""
 
@@ -146,7 +148,6 @@
                field.Fields(IWorkflowCommentInfo)
 
     buttons = button.Buttons(IPublicationRequestButtons)
-    ajax_handler = 'wf-propose.json'
 
     def updateWidgets(self, prefix=None):
         super(PublicationRequestForm, self).updateWidgets(prefix)
@@ -174,12 +175,6 @@
         return super(PublicationRequestForm, self).createAndAdd(data)
 
 
-@view_config(name='wf-propose.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PublicationRequestAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRequestForm):
-    """Shared content publication request form, JSON renderer"""
-
-
 @subscriber(IDataExtractedEvent, form_selector=PublicationRequestForm)
 def handle_publication_request_form_data_extraction(event):
     """Handle publication request form data extraction"""
@@ -220,17 +215,12 @@
 
 @pagelet_config(name='wf-cancel-propose.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='wf-cancel-propose.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, base=WorkflowContentTransitionAJAXForm)
 class PublicationRequestCancelForm(WorkflowContentTransitionForm):
     """Shared content publication request cancel form"""
 
     buttons = button.Buttons(IPublicationRequestCancelButtons)
-    ajax_handler = 'wf-cancel-propose.json'
-
-
-@view_config(name='wf-cancel-propose.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PublicationRequestCancelAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRequestCancelForm):
-    """Shared content publication request cancel form, JSON renderer"""
 
 
 @viewlet_config(name='wf-cancel-propose-message', context=IWfSharedContent, layer=IPyAMSLayer,
@@ -265,11 +255,12 @@
 
 @pagelet_config(name='wf-refuse.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=PUBLISH_CONTENT_PERMISSION)
+@ajax_config(name='wf-refuse.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=PUBLISH_CONTENT_PERMISSION, base=WorkflowContentTransitionAJAXForm)
 class PublicationRequestRefuseForm(WorkflowContentTransitionForm):
     """Shared content publication request refuse form"""
 
     buttons = button.Buttons(IPublicationRequestRefuseButtons)
-    ajax_handler = 'wf-refuse.json'
 
     def updateWidgets(self, prefix=None):
         super(PublicationRequestRefuseForm, self).updateWidgets(prefix)
@@ -277,12 +268,6 @@
             self.widgets['comment'].required = True
 
 
-@view_config(name='wf-refuse.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=PUBLISH_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PublicationRequestRefuseAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRequestRefuseForm):
-    """Shared content publication request refuse form, JSON renderer"""
-
-
 @subscriber(IDataExtractedEvent, form_selector=PublicationRequestRefuseForm)
 def handle_publication_request_refuse_form_data_extraction(event):
     """Handle publication request refuse form data extraction"""
@@ -324,6 +309,8 @@
 
 @pagelet_config(name='wf-publish.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=PUBLISH_CONTENT_PERMISSION)
+@ajax_config(name='wf-publish.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=PUBLISH_CONTENT_PERMISSION, base=WorkflowContentTransitionAJAXForm)
 class PublicationForm(WorkflowContentTransitionForm):
     """Shared content publication form"""
 
@@ -341,7 +328,6 @@
                field.Fields(IWorkflowCommentInfo)
 
     buttons = button.Buttons(IPublicationButtons)
-    ajax_handler = 'wf-publish.json'
 
     def updateWidgets(self, prefix=None):
         super(PublicationForm, self).updateWidgets(prefix)
@@ -371,12 +357,6 @@
         return super(PublicationForm, self).createAndAdd(data)
 
 
-@view_config(name='wf-publish.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=PUBLISH_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PublicationAJAXForm(WorkflowContentTransitionAJAXForm, PublicationForm):
-    """Shared content publication form, JSON renderer"""
-
-
 @subscriber(IDataExtractedEvent, form_selector=PublicationForm)
 def handle_publication_form_data_extraction(event):
     """Handle publication form data extraction"""
@@ -418,6 +398,8 @@
 
 @pagelet_config(name='wf-retiring.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='wf-retiring.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, base=WorkflowContentTransitionAJAXForm)
 class PublicationRetireRequestForm(WorkflowContentTransitionForm):
     """Shared content publication request refuse form"""
 
@@ -425,7 +407,6 @@
              field.Fields(IWorkflowRequestUrgencyInfo) + \
              field.Fields(IWorkflowCommentInfo)
     buttons = button.Buttons(IPublicationRetireRequestButtons)
-    ajax_handler = 'wf-retiring.json'
 
     def updateWidgets(self, prefix=None):
         super(PublicationRetireRequestForm, self).updateWidgets(prefix)
@@ -433,12 +414,6 @@
             self.widgets['comment'].required = True
 
 
-@view_config(name='wf-retiring.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PublicationRetireRequestAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRetireRequestForm):
-    """Shared content publication retire request form, JSON renderer"""
-
-
 @subscriber(IDataExtractedEvent, form_selector=PublicationRetireRequestForm)
 def handle_publication_retire_request_form_data_extraction(event):
     """Handle publication retire request form data extraction"""
@@ -479,17 +454,12 @@
 
 @pagelet_config(name='wf-cancel-retiring.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='wf-cancel-retiring.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, base=WorkflowContentTransitionAJAXForm)
 class PublicationRetireCancelForm(WorkflowContentTransitionForm):
     """Shared content publication retire request cancel form"""
 
     buttons = button.Buttons(IPublicationRetireCancelButtons)
-    ajax_handler = 'wf-cancel-retiring.json'
-
-
-@view_config(name='wf-cancel-retiring.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PublicationRetireCancelAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRetireCancelForm):
-    """Shared content publication retire request cancel form, JSON renderer"""
 
 
 @viewlet_config(name='wf-cancel-retiring-message', context=IWfSharedContent, layer=IPyAMSLayer,
@@ -524,17 +494,12 @@
 
 @pagelet_config(name='wf-retire.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=PUBLISH_CONTENT_PERMISSION)
+@ajax_config(name='wf-retire.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=PUBLISH_CONTENT_PERMISSION, base=WorkflowContentTransitionAJAXForm)
 class PublicationRetireForm(WorkflowContentTransitionForm):
     """Shared content publication retire form"""
 
     buttons = button.Buttons(IPublicationRetireButtons)
-    ajax_handler = 'wf-retire.json'
-
-
-@view_config(name='wf-retire.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=PUBLISH_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PublicationRetireAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRetireForm):
-    """Shared content publication retire form, JSON renderer"""
 
 
 @viewlet_config(name='wf-retire-message', context=IWfSharedContent, layer=IPyAMSLayer,
@@ -570,6 +535,8 @@
 
 @pagelet_config(name='wf-archiving.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='wf-archiving.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, base=WorkflowContentTransitionAJAXForm)
 class PublicationArchiveRequestForm(WorkflowContentTransitionForm):
     """Shared content publication request archive form"""
 
@@ -577,13 +544,6 @@
              field.Fields(IWorkflowRequestUrgencyInfo) + \
              field.Fields(IWorkflowCommentInfo)
     buttons = button.Buttons(IPublicationArchiveRequestButtons)
-    ajax_handler = 'wf-archiving.json'
-
-
-@view_config(name='wf-archiving.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PublicationArchiveRequestAJAXForm(WorkflowContentTransitionAJAXForm, PublicationArchiveRequestForm):
-    """Shared content publication archive request form, JSON renderer"""
 
 
 @viewlet_config(name='wf-archiving-message', context=IWfSharedContent, layer=IPyAMSLayer,
@@ -618,17 +578,12 @@
 
 @pagelet_config(name='wf-cancel-archiving.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='wf-cancel-archiving.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, base=WorkflowContentTransitionAJAXForm)
 class PublicationArchiveCancelForm(WorkflowContentTransitionForm):
     """Shared content publication archive request cancel form"""
 
     buttons = button.Buttons(IPublicationArchiveCancelButtons)
-    ajax_handler = 'wf-cancel-archiving.json'
-
-
-@view_config(name='wf-cancel-archiving.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PublicationArchiveCancelAJAXForm(WorkflowContentTransitionAJAXForm, PublicationArchiveCancelForm):
-    """Shared content publication archive request cancel form, JSON renderer"""
 
 
 @viewlet_config(name='wf-cancel-archiving-message', context=IWfSharedContent, layer=IPyAMSLayer,
@@ -663,17 +618,12 @@
 
 @pagelet_config(name='wf-archive.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=PUBLISH_CONTENT_PERMISSION)
+@ajax_config(name='wf-archive.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=PUBLISH_CONTENT_PERMISSION, base=WorkflowContentTransitionAJAXForm)
 class PublicationArchiveForm(WorkflowContentTransitionForm):
     """Shared content publication archive form"""
 
     buttons = button.Buttons(IPublicationArchiveButtons)
-    ajax_handler = 'wf-archive.json'
-
-
-@view_config(name='wf-archive.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=PUBLISH_CONTENT_PERMISSION, renderer='json', xhr=True)
-class PublicationArchiveAJAXForm(WorkflowContentTransitionAJAXForm, PublicationArchiveForm):
-    """Shared content publication archive form, JSON renderer"""
 
 
 @viewlet_config(name='wf-archive-message', context=IWfSharedContent, layer=IPyAMSLayer,
@@ -709,23 +659,18 @@
 
 @pagelet_config(name='wf-clone.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=CREATE_CONTENT_PERMISSION)
+@ajax_config(name='wf-clone.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=CREATE_CONTENT_PERMISSION, base=AJAXAddForm)
 class SharedContentCloneForm(WorkflowContentTransitionForm):
     """Shared content clone form"""
 
     buttons = button.Buttons(ISharedContentCloneButtons)
-    ajax_handler = 'wf-clone.json'
 
     def createAndAdd(self, data):
         data = data.get(self, data)
         info = IWorkflowInfo(self.context)
         return info.fire_transition_toward(DRAFT, comment=data.get('comment'))
 
-
-@view_config(name='wf-clone.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=CREATE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class SharedContentCloneAJAXForm(AJAXAddForm, SharedContentCloneForm):
-    """Shared content clone form, JSON rendener"""
-
     def get_ajax_output(self, changes):
         return {
             'status': 'redirect',
@@ -765,11 +710,12 @@
 
 @pagelet_config(name='wf-delete.html', context=IWfSharedContent, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='wf-delete.json', context=IWfSharedContent, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION, base=WorkflowContentTransitionAJAXForm)
 class SharedContentDeleteForm(WorkflowContentTransitionForm):
     """Shared content delete form"""
 
     buttons = button.Buttons(ISharedContentDeleteButtons)
-    ajax_handler = 'wf-delete.json'
 
     @property
     def fields(self):
@@ -806,16 +752,10 @@
             versions.remove_version(state.version_id, state=DELETED, comment=data.get('comment'))
             self.__target = versions.get_last_versions(count=1)[0]
 
-
-@view_config(name='wf-delete.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class SharedContentDeleteAJAXForm(AJAXAddForm, SharedContentDeleteForm):
-    """Shared content delete form, JSON rendener"""
-
     def get_ajax_output(self, changes):
         return {
             'status': 'redirect',
-            'location': absolute_url(self._SharedContentDeleteForm__target, self.request, 'admin')
+            'location': absolute_url(self.__target, self.request, 'admin')
         }
 
 
--- a/src/pyams_content/shared/form/zmi/field.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/form/zmi/field.py	Fri Jun 08 10:35:42 2018 +0200
@@ -35,7 +35,7 @@
 # import packages
 from pyams_content.shared.common.zmi import WfModifiedContentColumnMixin
 from pyams_content.shared.form.field import FormField
-from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_form.security import ProtectedFormObjectMixin
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.container import switch_element_visibility
@@ -231,6 +231,8 @@
 
 @pagelet_config(name='add-form-field.html', context=IFormFieldContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-form-field.json', context=IFormFieldContainerTarget, layer=IPyAMSLayer,
+             base=AJAXAddForm)
 class FormFieldAddForm(AdminDialogAddForm):
     """Form field add form"""
 
@@ -238,7 +240,6 @@
     icon_css_class = 'fa fa-fw fa-pencil-square-o'
 
     fields = field.Fields(IFormField).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-form-field.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -252,6 +253,9 @@
     def add(self, object):
         IFormFieldContainer(self.context)[object.name] = object
 
+    def nextURL(self):
+        return '#form-fields.html'
+
 
 @subscriber(IDataExtractedEvent, form_selector=FormFieldAddForm)
 def handle_new_form_field_data_extraction(event):
@@ -262,16 +266,8 @@
         event.form.widgets.errors += (Invalid(_("Specified name is already used!")),)
 
 
-@view_config(name='add-form-field.json', context=IFormFieldContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class FormFieldAJAXAddForm(AJAXAddForm, FormFieldAddForm):
-    """Form field add form, JSON renderer"""
-
-    def nextURL(self):
-        return '#form-fields.html'
-
-
 @pagelet_config(name='properties.html', context=IFormField, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IFormField, layer=IPyAMSLayer)
 class FormFieldPropertiesEditForm(AdminDialogEditForm):
     """Form field properties edit form"""
 
@@ -286,7 +282,6 @@
     icon_class = 'fa fa-fw fa-pencil-square-o'
 
     fields = field.Fields(IFormField).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -296,14 +291,8 @@
         if 'description' in self.widgets:
             self.widgets['description'].widget_css_class = 'textarea'
 
-
-@view_config(name='properties.json', context=IFormField, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class FormFieldPropertiesAJAXEditForm(AJAXEditForm, FormFieldPropertiesEditForm):
-    """Form field properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(FormFieldPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if 'label' in changes.get(IFormField, ()):
             output.setdefault('events', []).append({
                 'event': 'myams.refresh',
--- a/src/pyams_content/shared/imagemap/zmi/area.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/imagemap/zmi/area.py	Fri Jun 08 10:35:42 2018 +0200
@@ -26,12 +26,11 @@
 from pyams_content.shared.imagemap import ImageMapArea
 from pyams_content.shared.imagemap.zmi.container import ImagemapAreasContainerView
 from pyams_content.shared.imagemap.zmi.widget import ImgareaInputFieldWidget
-from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.toolbar import ToolbarAction
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
-from pyramid.view import view_config
 from z3c.form import field
 
 from pyams_content import _
@@ -58,6 +57,7 @@
 
 
 @pagelet_config(name='add-area.html', context=IWfImageMap, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-area.json', context=IWfImageMap, layer=IPyAMSLayer, base=AJAXAddForm)
 class ImagemapAreaAddForm(AdminDialogAddForm):
     """Imagemap area add form"""
 
@@ -80,7 +80,6 @@
     fields = field.Fields(IImageMapArea)
     fields['area'].widgetFactory = ImgareaInputFieldWidget
 
-    ajax_handler = 'add-area.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def create(self, data):
@@ -93,13 +92,8 @@
         return 'areas.html'
 
 
-@view_config(name='add-area.json', context=IWfImageMap, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ImagemapAreaAJAXAddForm(AJAXAddForm, ImagemapAreaAddForm):
-    """Image map area add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IImageMapArea, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IImageMapArea, layer=IPyAMSLayer)
 class ImagemapAreaPropertiesEditForm(AdminDialogEditForm):
     """Image map area properties edit form"""
 
@@ -124,11 +118,4 @@
     fields = field.Fields(IImageMapArea)
     fields['area'].widgetFactory = ImgareaInputFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
-
-
-@view_config(name='properties.json', context=IImageMapArea, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ImagemapAreaPropertiesAJAXEditForm(AJAXEditForm, ImagemapAreaPropertiesEditForm):
-    """Image map properties edit form, JSON rendener"""
--- a/src/pyams_content/shared/imagemap/zmi/paragraph.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/imagemap/zmi/paragraph.py	Fri Jun 08 10:35:42 2018 +0200
@@ -32,6 +32,7 @@
     BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_content.shared.imagemap.paragraph import ImageMapParagraph
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_form_refresh_event
 from pyams_template.template import template_config
@@ -39,7 +40,6 @@
 from pyams_utils.traversing import get_parent
 from pyams_viewlet.viewlet import viewlet_config, Viewlet
 from pyams_zmi.form import AdminDialogAddForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer
 
@@ -59,6 +59,8 @@
 
 @pagelet_config(name='add-imagemap-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-imagemap-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class ImagemapParagraphAddForm(AdminDialogAddForm):
     """Image map paragraph add form"""
 
@@ -66,7 +68,6 @@
     icon_css_class = 'fa fa-fw fa-location-arrow'
 
     fields = field.Fields(IImageMapParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-imagemap-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def create(self, data):
@@ -76,14 +77,10 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-imagemap-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ImagemapParagraphAJAXAddForm(BaseParagraphAJAXAddForm, ImagemapParagraphAddForm):
-    """Image map paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=IImageMapParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=IImageMapParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 class ImagemapParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Image map paragraph edit form"""
 
@@ -100,23 +97,17 @@
     fields = field.Fields(IImageMapParagraph).omit('__parent__', '__name__', 'visible')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
 
-@view_config(name='properties.json', context=IImageMapParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ImagemapParagrahPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, ImagemapParagraphPropertiesEditForm):
-    """Image map properties edit form, JSON renderer"""
-
-
 @adapter_config(context=(IImageMapParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=IImageMapParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm)
 class ImagemapParagraphInnerEditForm(ImagemapParagraphPropertiesEditForm):
     """Image map properties inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
@@ -125,14 +116,8 @@
         else:
             return button.Buttons()
 
-
-@view_config(name='inner-properties.json', context=IImageMapParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ImagemapParagraphInnerAJAXEditForm(BaseParagraphAJAXEditForm, ImagemapParagraphInnerEditForm):
-    """Image map paragraph properties inner edit form, JSON rendener"""
-
     def get_ajax_output(self, changes):
-        output = super(ImagemapParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if 'reference' in changes.get(IImageMapParagraph, ()):
             output.setdefault('events', []).append(
                 get_json_form_refresh_event(self.context, self.request, ImagemapParagraphInnerEditForm))
--- a/src/pyams_content/shared/logo/zmi/paragraph.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/logo/zmi/paragraph.py	Fri Jun 08 10:35:42 2018 +0200
@@ -32,6 +32,7 @@
     BaseParagraphAJAXAddForm, BaseParagraphPropertiesEditForm, BaseParagraphAJAXEditForm, IParagraphEditFormButtons
 from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_content.shared.logo.paragraph import LogosParagraph
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.event import get_json_widget_refresh_event
 from pyams_utils.adapter import adapter_config
@@ -58,6 +59,8 @@
 
 @pagelet_config(name='add-logos-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-logos-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXAddForm)
 class LogosParagraphAddForm(AdminDialogAddForm):
     """Logos paragraph add form"""
 
@@ -65,7 +68,6 @@
     icon_css_class = 'fa fa-fw fa-th-large'
 
     fields = field.Fields(ILogosParagraph).omit('__parent__', '__name__', 'visible')
-    ajax_handler = 'add-logos-paragraph.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def create(self, data):
@@ -75,14 +77,9 @@
         IParagraphContainer(self.context).append(object)
 
 
-@view_config(name='add-logos-paragraph.json', context=IParagraphContainerTarget, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class LogosParagraphAJAXAddForm(BaseParagraphAJAXAddForm, LogosParagraphAddForm):
-    """Logos paragraph add form, JSON renderer"""
-
-
 @pagelet_config(name='properties.html', context=ILogosParagraph, layer=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='properties.json', context=ILogosParagraph, layer=IPyAMSLayer)
 class LogosParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
     """Logos paragraph properties edit form"""
 
@@ -99,23 +96,17 @@
     fields = field.Fields(ILogosParagraph).omit('__parent__', '__name__', 'visible')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
 
-@view_config(name='properties.json', context=ILogosParagraph, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class LogosParagraphPropertiesAJAXEditForm(BaseParagraphAJAXEditForm, LogosParagraphPropertiesEditForm):
-    """Logos paragraph properteis edit form, JSOn renderer"""
-
-
 @adapter_config(context=(ILogosParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+@ajax_config(name='inner-properties.json', context=ILogosParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
 @implementer(IInnerForm)
 class LogosParagraphInnerEditForm(LogosParagraphPropertiesEditForm):
     """Logos paragraph properties inner edit form"""
 
     legend = None
-    ajax_handler = 'inner-properties.json'
 
     @property
     def buttons(self):
--- a/src/pyams_content/shared/site/zmi/__init__.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/site/zmi/__init__.py	Fri Jun 08 10:35:42 2018 +0200
@@ -31,6 +31,7 @@
 # import packages
 from pyams_content.shared.common.zmi import SharedContentAddForm, SharedContentAJAXAddForm
 from pyams_content.shared.site.zmi.widget import SiteManagerFoldersSelectorFieldWidget
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.toolbar import ToolbarMenuItem
 from pyams_utils.adapter import adapter_config, ContextRequestAdapter
@@ -40,7 +41,6 @@
 from pyams_viewlet.viewlet import viewlet_config
 from pyramid.decorator import reify
 from pyramid.path import DottedNameResolver
-from pyramid.view import view_config
 from z3c.form import field
 from zope.interface import Interface
 from zope.lifecycleevent import ObjectCreatedEvent
@@ -78,6 +78,8 @@
                 permission=CREATE_CONTENT_PERMISSION)
 @pagelet_config(name='add-shared-content.html', context=ISiteContainer, layer=IPyAMSLayer,
                 permission=CREATE_CONTENT_PERMISSION)
+@ajax_config(name='add-topic.json', context=ISiteContainer, layer=IPyAMSLayer,
+             base=SharedContentAJAXAddForm)
 class TopicAddForm(SharedContentAddForm):
     """Topic add form"""
 
@@ -86,7 +88,6 @@
     fields = field.Fields(ITopicAddFormFields).select('title', 'parent', 'notepad')
     fields['parent'].widgetFactory = SiteManagerFoldersSelectorFieldWidget
 
-    ajax_handler = 'add-topic.json'
     edit_permission = CREATE_CONTENT_PERMISSION
 
     __target = None
@@ -138,9 +139,3 @@
 
     def nextURL(self):
         return absolute_url(self.__target, self.request, 'admin')
-
-
-@view_config(name='add-topic.json', context=ISiteContainer, request_type=IPyAMSLayer,
-             permission=CREATE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class TopicAJAXAddForm(SharedContentAJAXAddForm, TopicAddForm):
-    """Topic add form, JSON renderer"""
--- a/src/pyams_content/shared/site/zmi/container.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/site/zmi/container.py	Fri Jun 08 10:35:42 2018 +0200
@@ -43,7 +43,7 @@
     SharedToolDashboardStatusColumn, SharedToolDashboardVersionColumn, SharedToolDashboardStatusDateColumn, \
     SharedToolDashboardStatusPrincipalColumn, SharedToolDashboardOwnerColumn
 from pyams_content.skin import pyams_content
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.container import ContainerView, delete_container_element
 from pyams_skin.event import get_json_table_cell_refresh_event
@@ -123,6 +123,7 @@
 
 @pagelet_config(name='workflow-publication.html', context=ISiteContainer, layer=IPyAMSLayer,
                 permission=MANAGE_SITE_PERMISSION)
+@ajax_config(name='workflow-publication.json', context=ISiteContainer, layer=IPyAMSLayer)
 class SiteContainerWorkflowPublicationEditForm(AdminDialogEditForm):
     """Site container workflow publication edit form"""
 
@@ -131,7 +132,6 @@
     legend = _("Update publication dates")
 
     fields = field.Fields(IWorkflowPublicationInfo).select('publication_effective_date', 'publication_expiration_date')
-    ajax_handler = 'workflow-publication.json'
     edit_permission = MANAGE_SITE_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -141,14 +141,8 @@
             if not widget.value:
                 widget.value = tztime(datetime.utcnow()).strftime('%d/%m/%y %H:%M')
 
-
-@view_config(name='workflow-publication.json', context=ISiteContainer, request_type=IPyAMSLayer,
-             permission=MANAGE_SITE_PERMISSION, renderer='json', xhr=True)
-class SiteContainerWorkflowPublicationAJAXEditForm(AJAXEditForm, SiteContainerWorkflowPublicationEditForm):
-    """Site container workflow publication edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(SiteContainerWorkflowPublicationAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         if changes:
             info = IWorkflowPublicationInfo(self.context, None)
             if info is not None:
@@ -163,7 +157,8 @@
                 intids = get_utility(IIntIds)
                 output.setdefault('events', []).append(
                     get_json_table_cell_refresh_event(self.context, self.request,
-                                                      '{0}::{1}'.format(SiteContainerTreeTable.id, intids.queryId(self.context)),
+                                                      '{0}::{1}'.format(SiteContainerTreeTable.id,
+                                                                        intids.queryId(self.context)),
                                                       'visible',
                                                       value))
         return output
--- a/src/pyams_content/shared/site/zmi/folder.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/site/zmi/folder.py	Fri Jun 08 10:35:42 2018 +0200
@@ -9,10 +9,6 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-from pyams_content.shared.common import IBaseSharedTool
-from pyams_content.shared.common.zmi.manager import SharedToolPropertiesEditForm
-from pyams_utils.adapter import adapter_config, ContextRequestAdapter
-from pyams_zmi.interfaces.menu import ISiteManagementMenu
 
 __docformat__ = 'restructuredtext'
 
@@ -21,20 +17,24 @@
 
 # import interfaces
 from pyams_content.interfaces import MANAGE_SITE_PERMISSION, MANAGE_TOOL_PERMISSION
+from pyams_content.shared.common.interfaces import IBaseSharedTool
 from pyams_content.shared.site.interfaces import ISiteContainer, ISiteManager, ISiteFolder
 from pyams_i18n.interfaces import INegotiator, II18n
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IMenuHeader
 from pyams_skin.layer import IPyAMSLayer
+from pyams_zmi.interfaces.menu import ISiteManagementMenu
 from pyams_zmi.layer import IAdminLayer
 from z3c.form.interfaces import IDataExtractedEvent
 from zope.intid.interfaces import IIntIds
 
 # import packages
+from pyams_content.shared.common.zmi.manager import SharedToolPropertiesEditForm
 from pyams_content.shared.site.zmi.widget import SiteManagerFoldersSelectorFieldWidget
-from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.form import AJAXAddForm, AJAXEditForm, ajax_config
 from pyams_i18n.schema import I18nTextLineField
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.toolbar import ToolbarMenuItem
+from pyams_utils.adapter import adapter_config, ContextRequestAdapter
 from pyams_utils.registry import get_utility
 from pyams_utils.traversing import get_parent
 from pyams_utils.unicode import translate_string
@@ -80,6 +80,7 @@
 
 @pagelet_config(name='add-site-folder.html', context=ISiteContainer, layer=IPyAMSLayer,
                 permission=MANAGE_SITE_PERMISSION)
+@ajax_config(name='add-site-folder.json', context=ISiteContainer, layer=IPyAMSLayer, base=AJAXAddForm)
 class SiteFolderAddForm(AdminDialogAddForm):
     """Site folder add form"""
 
@@ -93,7 +94,6 @@
     fields = field.Fields(ISiteFolderAddFormFields)
     fields['parent'].widgetFactory = SiteManagerFoldersSelectorFieldWidget
 
-    ajax_handler = 'add-site-folder.json'
     edit_permission = MANAGE_SITE_PERMISSION
 
     def updateWidgets(self, prefix=None):
@@ -140,6 +140,9 @@
     def nextURL(self):
         return absolute_url(self.context, self.request, 'admin#site-tree.html')
 
+    def get_ajax_output(self, changes):
+        return {'status': 'reload'}
+
 
 @subscriber(IDataExtractedEvent, form_selector=SiteFolderAddForm)
 def handle_site_folder_add_form_data_extraction(event):
@@ -150,15 +153,6 @@
         event.form.widgets.errors += (Invalid(_("You must provide a folder name for default server language!")),)
 
 
-@view_config(name='add-site-folder.json', context=ISiteContainer, request_type=IPyAMSLayer,
-             permission=MANAGE_SITE_PERMISSION, renderer='json', xhr=True)
-class SiteFolderAJAXAddForm(AJAXAddForm, SiteFolderAddForm):
-    """Site folder add form, JSON renderer"""
-
-    def get_ajax_output(self, changes):
-        return {'status': 'reload'}
-
-
 @adapter_config(context=(ISiteFolder, ISiteManagementMenu), provides=IMenuHeader)
 class SiteFolderSiteManagementMenuHeader(ContextRequestAdapter):
     """Site folder site management menu header adapter"""
--- a/src/pyams_content/shared/site/zmi/link.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/site/zmi/link.py	Fri Jun 08 10:35:42 2018 +0200
@@ -33,7 +33,7 @@
 from pyams_content.shared.site.link import ContentLink
 from pyams_content.shared.site.zmi.container import SiteContainerTreeTable, SiteContainerTreeNameColumn
 from pyams_content.shared.site.zmi.widget import SiteManagerFoldersSelectorFieldWidget
-from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.table import get_object_name
 from pyams_skin.viewlet.toolbar import ToolbarMenuItem
@@ -42,7 +42,6 @@
 from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
-from pyramid.view import view_config
 from z3c.form import field
 from zope.interface import Interface
 from zope.schema import Int
@@ -71,6 +70,7 @@
 
 @pagelet_config(name='add-content-link.html', context=ISiteContainer, layer=IPyAMSLayer,
                 permission=CREATE_CONTENT_PERMISSION)
+@ajax_config(name='add-content-link.json', context=ISiteContainer, layer=IPyAMSLayer, base=AJAXAddForm)
 class ContentLinkAddForm(AdminDialogAddForm):
     """Content link add form"""
 
@@ -79,7 +79,6 @@
     fields = field.Fields(IContentLinkAddFormFields).select('reference', 'alt_title', 'parent')
     fields['parent'].widgetFactory = SiteManagerFoldersSelectorFieldWidget
 
-    ajax_handler = 'add-content-link.json'
     edit_permission = CREATE_CONTENT_PERMISSION
 
     __target = None
@@ -110,12 +109,6 @@
         return absolute_url(self.__target, self.request, 'admin#site-tree.html')
 
 
-@view_config(name='add-content-link.json', context=ISiteContainer, request_type=IPyAMSLayer,
-             permission=CREATE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ContentLinkAJAXAddForm(AJAXAddForm, ContentLinkAddForm):
-    """Content link add form, JSOn renderer"""
-
-
 @adapter_config(context=(IContentLink, IPyAMSLayer, ISiteTreeTable), provides=ITableElementName)
 class ContentLinkTableElementName(ContextRequestViewAdapter):
     """Content link table element name"""
@@ -133,6 +126,7 @@
 
 @pagelet_config(name='properties.html', context=IContentLink, layer=IPyAMSLayer,
                 permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='properties.json', context=IContentLink, layer=IPyAMSLayer)
 class ContentLinkPropertiesEditForm(AdminDialogEditForm):
     """Content link properties edit form"""
 
@@ -141,17 +135,10 @@
     legend = _("Edit content link properties")
 
     fields = field.Fields(IContentLink).omit('__parent__', '__name__')
-    ajax_handler = 'properties.json'
     edit_permission = MANAGE_CONTENT_PERMISSION
 
-
-@view_config(name='properties.json', context=IContentLink, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ContentLinkPropertiesAJAXEditForm(AJAXEditForm, ContentLinkPropertiesEditForm):
-    """Content link properties edit form, JSON renderer"""
-
     def get_ajax_output(self, changes):
-        output = super(ContentLinkPropertiesAJAXEditForm, self).get_ajax_output(changes)
+        output = super(self.__class__, self).get_ajax_output(changes)
         intids = get_utility(IIntIds)
         if 'reference' in changes.get(IInternalReference, ()):
             table = SiteContainerTreeTable(self.context.__parent__, self.request)
--- a/src/pyams_content/shared/site/zmi/manager.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/site/zmi/manager.py	Fri Jun 08 10:35:42 2018 +0200
@@ -31,7 +31,7 @@
 # import packages
 from pyams_content.shared.site import WfTopic
 from pyams_content.shared.site.manager import SiteManager
-from pyams_form.form import AJAXAddForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.table import DefaultElementEditorAdapter
 from pyams_skin.viewlet.breadcrumb import BreadcrumbItem
@@ -44,7 +44,6 @@
 from pyams_zmi.form import AdminDialogAddForm
 from pyramid.events import subscriber
 from pyramid.path import DottedNameResolver
-from pyramid.view import view_config
 from z3c.form import field
 from zope.interface import Invalid
 
@@ -93,6 +92,8 @@
 
 @pagelet_config(name='add-site-manager.html', context=ISiteRoot, layer=IPyAMSLayer,
                 permission=MANAGE_SITE_ROOT_PERMISSION)
+@ajax_config(name='add-site-manager.json', context=ISiteRoot, layer=IPyAMSLayer,
+             permission=MANAGE_SITE_ROOT_PERMISSION, base=AJAXAddForm)
 class SiteManagerAddForm(AdminDialogAddForm):
     """Site manager add form"""
 
@@ -101,7 +102,6 @@
     icon_css_class = 'fa fa-fw fa-sitemap'
 
     fields = field.Fields(ISiteManager).select('title', 'short_name')
-    ajax_handler = 'add-site-manager.json'
     edit_permission = None
 
     def create(self, data):
@@ -139,12 +139,6 @@
         event.form.widgets.errors += (Invalid(_("A site manager is already registered with this name!!")),)
 
 
-@view_config(name='add-site-manager.json', context=ISiteRoot, request_type=IPyAMSLayer,
-             permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
-class SiteManagerAJAXAddForm(AJAXAddForm, SiteManagerAddForm):
-    """Site manager add form, JSOn renderer"""
-
-
 @adapter_config(context=(ISiteManager, IAdminLayer, ISiteTreeTable), provides=ITableElementEditor)
 class SiteManagerTableElementEditor(DefaultElementEditorAdapter):
     """Site tree table element editor"""
--- a/src/pyams_content/shared/view/zmi/reference.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/view/zmi/reference.py	Fri Jun 08 10:35:42 2018 +0200
@@ -27,12 +27,11 @@
 from z3c.form.interfaces import INPUT_MODE
 
 # import packages
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.menu import MenuItem, MenuDivider
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminEditForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import Interface, implementer
 
@@ -56,6 +55,7 @@
 
 
 @pagelet_config(name='references.html', context=IWfView, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='references.json', context=IWfView, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
 @implementer(IWidgetForm, IInnerPage)
 class ViewReferencesEditForm(AdminEditForm):
     """View references settings edit form"""
@@ -70,11 +70,3 @@
             return button.Buttons(IUncheckedEditFormButtons)
         else:
             return button.Buttons(Interface)
-
-    ajax_handler = 'references.json'
-
-
-@view_config(name='references.json', context=IWfView, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ViewReferencesAJAXEditForm(AJAXEditForm, ViewReferencesEditForm):
-    """References settings edit form, JSON renderer"""
--- a/src/pyams_content/shared/view/zmi/theme.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/shared/view/zmi/theme.py	Fri Jun 08 10:35:42 2018 +0200
@@ -28,13 +28,12 @@
 from z3c.form.interfaces import INPUT_MODE
 
 # import packages
-from pyams_form.form import AJAXEditForm
+from pyams_form.form import ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_utils.registry import get_utility
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminEditForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer, Interface
 
@@ -52,6 +51,7 @@
 
 
 @pagelet_config(name='themes.html', context=IWfView, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='themes.json', context=IWfView, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
 @implementer(IWidgetForm, IInnerPage)
 class ViewThemesEditForm(AdminEditForm):
     """View themes settings edit form"""
@@ -67,16 +67,8 @@
         else:
             return button.Buttons(Interface)
 
-    ajax_handler = 'themes.json'
-
     def updateWidgets(self, prefix=None):
         super(ViewThemesEditForm, self).updateWidgets(prefix)
         if 'themes' in self.widgets:
             manager = get_utility(IViewsManager)
             self.widgets['themes'].thesaurus_name = IThesaurusContextManager(manager).thesaurus_name
-
-
-@view_config(name='themes.json', context=IWfView, request_type=IPyAMSLayer,
-             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
-class ViewThemesAJAXEditForm(AJAXEditForm, ViewThemesEditForm):
-    """View themes settings edit form, JSON renderer"""
--- a/src/pyams_content/workflow/zmi/task.py	Wed Jun 06 13:36:56 2018 +0200
+++ b/src/pyams_content/workflow/zmi/task.py	Fri Jun 08 10:35:42 2018 +0200
@@ -23,13 +23,12 @@
 
 # import packages
 from pyams_content.workflow.task import ContentArchiverTask
-from pyams_form.form import AJAXAddForm
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_scheduler.zmi.scheduler import SchedulerTasksTable
 from pyams_scheduler.zmi.task import TaskBaseAddForm
 from pyams_skin.viewlet.toolbar import ToolbarMenuItem
 from pyams_viewlet.viewlet import viewlet_config
-from pyramid.view import view_config
 
 from pyams_content import _
 
@@ -48,17 +47,12 @@
 
 @pagelet_config(name='add-content-archiver-task.html', context=ISite, layer=IPyAMSLayer,
                 permission=MANAGE_SYSTEM_PERMISSION)
+@ajax_config(name='add-content-archiver-task.json', context=ISite, layer=IPyAMSLayer,
+             permission=MANAGE_SYSTEM_PERMISSION, base=AJAXAddForm)
 class ContentArchiverTaskAddForm(TaskBaseAddForm):
     """Content archiver task add form"""
 
     legend = _("Add automatic content archiver")
     icon_css_class = 'fa fa-fw fa-archive'
 
-    ajax_handler = 'add-content-archiver-task.json'
     task_factory = ContentArchiverTask
-
-
-@view_config(name='add-content-archiver-task.json', context=ISite, request_type=IPyAMSLayer,
-             permission=MANAGE_SYSTEM_PERMISSION, renderer='json', xhr=True)
-class ContentArchiverTaskAJAXAddForm(AJAXAddForm, ContentArchiverTaskAddForm):
-    """Content archiver task add form, JSON renderer"""