Version 0.1.0 0.1.0
authorThierry Florac <thierry.florac@onf.fr>
Wed, 17 Jun 2015 09:56:38 +0200
changeset 16 2f2596eee744
parent 15 6c594e84ec05
child 17 c42f7fcd6cfe
Version 0.1.0
.installed.cfg
buildout.cfg
setup.py
src/pyams_form.egg-info/SOURCES.txt
src/pyams_form.egg-info/requires.txt
src/pyams_form/form.py
src/pyams_form/interfaces/form.py
src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.mo
src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po
src/pyams_form/locales/pyams_form.pot
src/pyams_form/search.py
src/pyams_form/security.py
src/pyams_form/templates/form.pt
src/pyams_form/templates/inner-form.pt
src/pyams_form/templates/search.pt
src/pyams_form/templates/widget-form.pt
src/pyams_form/widget/__init__.py
src/pyams_form/widget/templates/html-input.pt
--- a/.installed.cfg	Wed May 20 12:24:59 2015 +0200
+++ b/.installed.cfg	Wed Jun 17 09:56:38 2015 +0200
@@ -1,16 +1,21 @@
 [buildout]
-installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-utils.egg-link
+installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-file.egg-link
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/lingua.egg-link
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-viewlet.egg-link
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-i18n.egg-link
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-catalog.egg-link
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-utils.egg-link
 parts = package i18n pyflakes test
 
 [package]
-__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pcreate
-	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/ptweens
+__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pserve
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pshell
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pcreate
 	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/prequest
 	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pdistreport
-	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pserve
-	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pshell
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pviews
 	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/proutes
-	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pviews
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/ptweens
 __buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.5-py3.4.egg zc.buildout-2.3.1-py3.4.egg
 _b = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin
 _d = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs
@@ -60,7 +65,7 @@
 [test]
 __buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_form/parts/test
 	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/test
-__buildout_signature__ = zc.recipe.testrunner-2.0.0-py3.4.egg zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.5-py3.4.egg zope.testrunner-4.4.6-py3.4.egg zc.buildout-2.3.1-py3.4.egg zope.interface-4.1.2-py3.4-linux-x86_64.egg zope.exceptions-4.0.7-py3.4.egg six-1482e89f68d85eea27f4ed7749df2819
+__buildout_signature__ = zc.recipe.testrunner-2.0.0-py3.4.egg zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.5-py3.4.egg zope.testrunner-4.4.6-py3.4.egg zc.buildout-2.3.1-py3.4.egg zope.interface-4.1.2-py3.4-linux-x86_64.egg zope.exceptions-4.0.7-py3.4.egg six-e6b62e54b4df360c40dfcbb76c1ecf1a
 _b = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin
 _d = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs
 _e = /var/local/env/pyams/eggs
@@ -71,3 +76,23 @@
 location = /home/tflorac/Dropbox/src/PyAMS/pyams_form/parts/test
 recipe = zc.recipe.testrunner
 script = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/test
+
+[buildout]
+installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-file.egg-link
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/lingua.egg-link
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-viewlet.egg-link
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-i18n.egg-link
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-catalog.egg-link
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-utils.egg-link
+
+[buildout]
+parts = i18n pyflakes test package
+
+[buildout]
+parts = pyflakes test package i18n
+
+[buildout]
+parts = test package i18n pyflakes
+
+[buildout]
+parts = package i18n pyflakes test
--- a/buildout.cfg	Wed May 20 12:24:59 2015 +0200
+++ b/buildout.cfg	Wed Jun 17 09:56:38 2015 +0200
@@ -17,9 +17,15 @@
 #allow-picked-versions = false
 
 src = src
-develop = .
-          ../pyams_skin
-          ../pyams_utils
+develop =
+    .
+    ../ext/lingua
+    ../pyams_catalog
+    ../pyams_file
+    ../pyams_i18n
+    ../pyams_skin
+    ../pyams_utils
+    ../pyams_viewlet
 
 parts =
     package
--- a/setup.py	Wed May 20 12:24:59 2015 +0200
+++ b/setup.py	Wed Jun 17 09:56:38 2015 +0200
@@ -56,7 +56,9 @@
       install_requires=[
           'setuptools',
           # -*- Extra requirements: -*-
+          'pyams_i18n',
           'pyams_skin',
+          'pyams_utils',
           'pyramid',
           'pyramid_zope_request',
           'z3c.form',
--- a/src/pyams_form.egg-info/SOURCES.txt	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form.egg-info/SOURCES.txt	Wed Jun 17 09:56:38 2015 +0200
@@ -9,6 +9,7 @@
 src/pyams_form/include.py
 src/pyams_form/schema.py
 src/pyams_form/search.py
+src/pyams_form/security.py
 src/pyams_form/terms.py
 src/pyams_form/viewlet.py
 src/pyams_form.egg-info/PKG-INFO
@@ -40,8 +41,11 @@
 src/pyams_form/widget/templates/checkbox-input.pt
 src/pyams_form/widget/templates/close-display.pt
 src/pyams_form/widget/templates/close-input.pt
+src/pyams_form/widget/templates/color-input.pt
 src/pyams_form/widget/templates/date-input.pt
 src/pyams_form/widget/templates/datetime-input.pt
+src/pyams_form/widget/templates/html-input.pt
+src/pyams_form/widget/templates/orderedselect-input.pt
 src/pyams_form/widget/templates/radio-display.pt
 src/pyams_form/widget/templates/radio-input.pt
 src/pyams_form/widget/templates/reset-display.pt
--- a/src/pyams_form.egg-info/requires.txt	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form.egg-info/requires.txt	Wed Jun 17 09:56:38 2015 +0200
@@ -1,5 +1,7 @@
 setuptools
+pyams_i18n
 pyams_skin
+pyams_utils
 pyramid
 pyramid_zope_request
 z3c.form
--- a/src/pyams_form/form.py	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form/form.py	Wed Jun 17 09:56:38 2015 +0200
@@ -18,11 +18,12 @@
 
 # import interfaces
 from pyams_form.interfaces.form import IFormLayer, IForm, IAJAXForm, IInnerSubForm, IInnerTabForm, \
-    ICustomUpdateSubForm, IFormCreatedEvent, FormCreatedEvent, IInnerForm
+    ICustomUpdateSubForm, IFormCreatedEvent, FormCreatedEvent, IInnerForm, IFormContextPermissionChecker
 from pyams_form.interfaces.form import IAddFormButtons, IModalAddFormButtons, IEditFormButtons, \
     IModalEditFormButtons, IModalDisplayFormButtons
 from pyams_form.interfaces.form import FormObjectCreatedEvent, FormObjectModifiedEvent
-from pyams_skin.interfaces import IDialog, ISkinnable
+from pyams_i18n.interfaces import II18n
+from pyams_skin.interfaces import IDialog, ISkinnable, IContentTitle
 from pyams_template.interfaces import IContentTemplate, ILayoutTemplate
 from pyramid_chameleon.interfaces import IChameleonTranslate
 from z3c.form.interfaces import DISPLAY_MODE, IErrorViewSnippet
@@ -32,6 +33,8 @@
 from pyams_form.group import GroupsBasedForm
 from pyams_pagelet.interfaces import PageletCreatedEvent
 from pyams_skin.skin import apply_skin
+from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
+from pyams_utils.list import unique
 from pyams_utils.url import absolute_url
 from pyramid.decorator import reify
 from pyramid.events import subscriber
@@ -70,7 +73,6 @@
 
     edit_permission = FieldProperty(IForm['edit_permission'])
 
-    title = FieldProperty(IForm['title'])
     legend = FieldProperty(IForm['legend'])
     css_class = FieldProperty(IForm['css_class'])
     icon_css_class = FieldProperty(IForm['icon_css_class'])
@@ -93,12 +95,39 @@
             alsoProvides(req, self.layer)
         request.registry.notify(FormCreatedEvent(self))
 
+    @property
+    def title(self):
+        registry = self.request.registry
+        adapter = registry.queryMultiAdapter((self.context, self.request, self), IContentTitle)
+        if adapter is None:
+            adapter = registry.queryAdapter(self.context, IContentTitle)
+        if adapter is not None:
+            return adapter.title
+        else:
+            return II18n(self.context).query_attribute('title', request=self.request)
+
     def update(self):
+        # check form permission to get form mode
         if self.edit_permission and not self.request.has_permission(self.edit_permission, self.getContent()):
             self.mode = DISPLAY_MODE
-        Form.update(self)
+        # check form mode based on context checker
+        registry = self.request.registry
+        permission = None
+        for content in unique((self.getContent(), self.context)):
+            checker = registry.queryMultiAdapter((content, self.request, self), IFormContextPermissionChecker)
+            if checker is None:
+                checker = registry.queryAdapter(content, IFormContextPermissionChecker)
+            if checker is not None:
+                permission = checker.edit_permission
+                break
+        if permission != self.edit_permission:
+            if (permission == 'system.forbidden') or \
+               not self.request.has_permission(permission, self.getContent()):
+                self.mode = DISPLAY_MODE
+        # update form and sub-forms
         [subform.update() for subform in self.subforms]
         [tabform.update() for tabform in self.tabforms]
+        Form.update(self)
 
     def get_form_action(self):
         return self.action
@@ -278,6 +307,15 @@
         return object
 
 
+@adapter_config(context=(Interface, Interface, AddForm), provides=IFormContextPermissionChecker)
+class AddFormContextPermissionChecker(ContextRequestViewAdapter):
+    """Add form context permission checker"""
+
+    @property
+    def edit_permission(self):
+        return self.view.edit_permission
+
+
 class AJAXAddForm(AddForm):
     """AJAX add form"""
 
@@ -427,6 +465,24 @@
     dialog_class = 'modal-medium'
 
 
+@implementer(IInnerForm)
+class InnerDisplayForm(DisplayForm):
+    """Inner display form"""
+
+    @property
+    def id(self):
+        return self.__class__.__name__
+
+    buttons = Buttons(Interface)
+
+    def __init__(self, context, request, view):
+        super(InnerDisplayForm, self).__init__(context, request)
+        self.parent_form = view
+
+    def get_form_action(self):
+        return None
+
+
 #
 # Form events subscribers
 #
--- a/src/pyams_form/interfaces/form.py	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form/interfaces/form.py	Wed Jun 17 09:56:38 2015 +0200
@@ -17,10 +17,10 @@
 # import interfaces
 from pyams_viewlet.interfaces import IViewletManager
 from pyramid.interfaces import IView
-from z3c.form.interfaces import INPUT_MODE, ISubForm, IWidget, IFormLayer as IBaseFormLayer, ISubmitWidget, ITextWidget
+from z3c.form.interfaces import INPUT_MODE, ISubForm, IWidget, IFormLayer as IBaseFormLayer, ISubmitWidget, \
+    ITextWidget, ITextAreaWidget
 from zope.interface.interfaces import IObjectEvent, ObjectEvent
 from zope.lifecycleevent.interfaces import IObjectCreatedEvent, IObjectModifiedEvent
-from zope.schema.interfaces import IField
 
 # import packages
 from pyams_form.schema import ResetButton, CloseButton
@@ -41,6 +41,22 @@
     """Base PyAMS form layer"""
 
 
+class IFormSecurityContext(Interface):
+    """Interface used to get security context of a given object"""
+
+    context = Attribute("Object security context")
+
+
+class IFormContextPermissionChecker(Interface):
+    """Interface used to check form context access rights
+
+    May be implemented as a context adapter or as a
+    (context, request, form) multi-adapter
+    """
+
+    edit_permission = Attribute("Permission required to modify content")
+
+
 class IBaseForm(IView):
     """Base form interface"""
 
@@ -266,6 +282,8 @@
 class IInnerTabForm(IInnerForm, ISubForm):
     """Inner tab-form interface"""
 
+    tab_label = TextLine(title="Tab label")
+
 
 class IViewletSubForm(ISubForm):
     """Inner viewlet form interface"""
@@ -341,10 +359,11 @@
         if widget.mode == INPUT_MODE:
             return True
     if IForm.providedBy(form):
-        for subform in form.subforms:
-            for widget in subform.widgets.values():
+        for subform in (form.subforms + form.tabforms):
+            for widget in (subform.widgets or {}).values():
                 if widget.mode == INPUT_MODE:
                     return True
+    return False
 
 
 class IAddFormButtons(Interface):
@@ -409,6 +428,10 @@
     """Color widget interface"""
 
 
+class IHTMLWidget(ITextAreaWidget):
+    """HTML editor widget interface"""
+
+
 #
 # Form events
 #
Binary file src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.mo has changed
--- a/src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po	Wed Jun 17 09:56:38 2015 +0200
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-01-29 22:41+0100\n"
+"POT-Creation-Date: 2015-06-01 14:37+0200\n"
 "PO-Revision-Date: 2015-01-29 17:17+0100\n"
 "Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
 "Language-Team: French <traduc@traduc.org>\n"
@@ -16,6 +16,19 @@
 "Generated-By: Lingua 3.8\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
+#: src/pyams_form/search.py:56 src/pyams_form/search.py:49
+msgid "Search"
+msgstr "Chercher"
+
+#: src/pyams_form/search.py:41
+msgid "Search query"
+msgstr "Texte recherché"
+
+#: src/pyams_form/search.py:48 src/pyams_form/interfaces/form.py:356
+#: src/pyams_form/interfaces/form.py:370
+msgid "Reset"
+msgstr "Annuler"
+
 #: src/pyams_form/terms.py:36
 msgid "yes"
 msgstr "oui"
@@ -24,55 +37,52 @@
 msgid "no"
 msgstr "non"
 
-#: src/pyams_form/form.py:212
+#: src/pyams_form/form.py:269
 msgid "Add form"
-msgstr ""
+msgstr "Formulaire de création"
 
-#: src/pyams_form/form.py:213 src/pyams_form/form.py:264
+#: src/pyams_form/form.py:270 src/pyams_form/form.py:350
 msgid "There were some errors."
 msgstr "Des erreurs se sont produites."
 
-#: src/pyams_form/form.py:263
+#: src/pyams_form/form.py:349
 msgid "Edit form"
-msgstr ""
+msgstr "Formulaire de mise à jour"
 
-#: src/pyams_form/form.py:265
+#: src/pyams_form/form.py:351
 msgid "Data successfully updated."
 msgstr "Les modifications ont été enregistrées."
 
-#: src/pyams_form/form.py:266
+#: src/pyams_form/form.py:352
 msgid "No changes were applied."
 msgstr "Aucune modification effectuée."
 
-#: src/pyams_form/form.py:353
-msgid "My legend"
-msgstr "Ma légende"
-
-#: src/pyams_form/templates/radio-input.pt:7
+#: src/pyams_form/widget/templates/checkbox-input.pt:66
+#: src/pyams_form/widget/templates/radio-input.pt:34
+#: src/pyams_form/widget/templates/checkbox-display.pt:62
 msgid "Label"
-msgstr ""
+msgstr "Libellé"
 
-#: src/pyams_form/templates/inner-form.pt:89
-msgid "legend"
-msgstr ""
+#: src/pyams_form/widget/templates/orderedselect-input.pt:4
+msgid "Clear selected values"
+msgstr "Enlever les valeurs sélectionnées"
 
-#: src/pyams_form/templates/inner-form.pt:108
-msgid "Tab label"
-msgstr ""
+#: src/pyams_form/templates/search.pt:6
+msgid "Search results"
+msgstr "Résultats de la recherche"
 
-#: src/pyams_form/interfaces/form.py:295 src/pyams_form/interfaces/form.py:309
-msgid "Reset"
-msgstr "Annuler"
-
-#: src/pyams_form/interfaces/form.py:296 src/pyams_form/interfaces/form.py:303
+#: src/pyams_form/interfaces/form.py:357 src/pyams_form/interfaces/form.py:364
 msgid "Add"
 msgstr "Ajouter"
 
-#: src/pyams_form/interfaces/form.py:302 src/pyams_form/interfaces/form.py:316
-#: src/pyams_form/interfaces/form.py:323
+#: src/pyams_form/interfaces/form.py:363 src/pyams_form/interfaces/form.py:377
+#: src/pyams_form/interfaces/form.py:384
 msgid "Close"
 msgstr "Fermer"
 
-#: src/pyams_form/interfaces/form.py:310 src/pyams_form/interfaces/form.py:317
+#: src/pyams_form/interfaces/form.py:371 src/pyams_form/interfaces/form.py:378
 msgid "Submit"
 msgstr "Enregistrer"
+
+#~ msgid "My legend"
+#~ msgstr "Ma légende"
--- a/src/pyams_form/locales/pyams_form.pot	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form/locales/pyams_form.pot	Wed Jun 17 09:56:38 2015 +0200
@@ -6,7 +6,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-01-29 22:41+0100\n"
+"POT-Creation-Date: 2015-06-01 14:37+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -14,7 +14,20 @@
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Lingua 3.8\n"
+"Generated-By: Lingua 3.10.dev0\n"
+
+#: ./src/pyams_form/search.py:56 ./src/pyams_form/search.py:49
+msgid "Search"
+msgstr ""
+
+#: ./src/pyams_form/search.py:41
+msgid "Search query"
+msgstr ""
+
+#: ./src/pyams_form/search.py:48 ./src/pyams_form/interfaces/form.py:356
+#: ./src/pyams_form/interfaces/form.py:370
+msgid "Reset"
+msgstr ""
 
 #: ./src/pyams_form/terms.py:36
 msgid "yes"
@@ -24,59 +37,52 @@
 msgid "no"
 msgstr ""
 
-#: ./src/pyams_form/form.py:212
+#: ./src/pyams_form/form.py:269
 msgid "Add form"
 msgstr ""
 
-#: ./src/pyams_form/form.py:213 ./src/pyams_form/form.py:264
+#: ./src/pyams_form/form.py:270 ./src/pyams_form/form.py:350
 msgid "There were some errors."
 msgstr ""
 
-#: ./src/pyams_form/form.py:263
+#: ./src/pyams_form/form.py:349
 msgid "Edit form"
 msgstr ""
 
-#: ./src/pyams_form/form.py:265
+#: ./src/pyams_form/form.py:351
 msgid "Data successfully updated."
 msgstr ""
 
-#: ./src/pyams_form/form.py:266
+#: ./src/pyams_form/form.py:352
 msgid "No changes were applied."
 msgstr ""
 
-#: ./src/pyams_form/form.py:353
-msgid "My legend"
-msgstr ""
-
-#: ./src/pyams_form/templates/radio-input.pt:7
+#: ./src/pyams_form/widget/templates/checkbox-input.pt:66
+#: ./src/pyams_form/widget/templates/radio-input.pt:34
+#: ./src/pyams_form/widget/templates/checkbox-display.pt:62
 msgid "Label"
 msgstr ""
 
-#: ./src/pyams_form/templates/inner-form.pt:89
-msgid "legend"
-msgstr ""
-
-#: ./src/pyams_form/templates/inner-form.pt:108
-msgid "Tab label"
+#: ./src/pyams_form/widget/templates/orderedselect-input.pt:4
+msgid "Clear selected values"
 msgstr ""
 
-#: ./src/pyams_form/interfaces/form.py:295
-#: ./src/pyams_form/interfaces/form.py:309
-msgid "Reset"
+#: ./src/pyams_form/templates/search.pt:6
+msgid "Search results"
 msgstr ""
 
-#: ./src/pyams_form/interfaces/form.py:296
-#: ./src/pyams_form/interfaces/form.py:303
+#: ./src/pyams_form/interfaces/form.py:357
+#: ./src/pyams_form/interfaces/form.py:364
 msgid "Add"
 msgstr ""
 
-#: ./src/pyams_form/interfaces/form.py:302
-#: ./src/pyams_form/interfaces/form.py:316
-#: ./src/pyams_form/interfaces/form.py:323
+#: ./src/pyams_form/interfaces/form.py:363
+#: ./src/pyams_form/interfaces/form.py:377
+#: ./src/pyams_form/interfaces/form.py:384
 msgid "Close"
 msgstr ""
 
-#: ./src/pyams_form/interfaces/form.py:310
-#: ./src/pyams_form/interfaces/form.py:317
+#: ./src/pyams_form/interfaces/form.py:371
+#: ./src/pyams_form/interfaces/form.py:378
 msgid "Submit"
 msgstr ""
--- a/src/pyams_form/search.py	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form/search.py	Wed Jun 17 09:56:38 2015 +0200
@@ -38,7 +38,8 @@
 class ISearchFields(Interface):
     """Default search form interface"""
 
-    query = TextLine(title=_("Search query"))
+    query = TextLine(title=_("Search query"),
+                     required=False)
 
 
 class ISearchButtons(Interface):
@@ -68,7 +69,12 @@
             self.actions['search'].addClass('btn-primary')
 
     def get_search_results(self):
-        search = IContentSearch(self.context, None)
+        registry = self.request.registry
+        search = registry.queryMultiAdapter((self.context, self.request, self), IContentSearch)
+        if search is None:
+            search = registry.queryMultiAdapter((self.context, self.request), IContentSearch)
+        if search is None:
+            search = IContentSearch(self.context, None)
         if search is not None:
             self.updateWidgets()
             data, errors = self.extractData()
@@ -97,7 +103,7 @@
         return Response(self.renderTable())
 
 
-@adapter_config(context=(Interface, IPyAMSLayer, SearchResultsView), provides=IValues)
+@adapter_config(context=(IContentSearch, IPyAMSLayer, SearchResultsView), provides=IValues)
 class SearchResultsViewValuesAdapter(ContextRequestViewAdapter):
     """Search results view values adapter"""
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/security.py	Wed Jun 17 09:56:38 2015 +0200
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_form.interfaces.form import IFormSecurityContext, IFormContextPermissionChecker
+
+# import packages
+
+
+class ProtectedFormObjectMixin(object):
+    """Form object protected by a given permission"""
+
+    @property
+    def permission(self):
+        registry = self.request.registry
+        checker = None
+        context = IFormSecurityContext(self, None)
+        if context is None:
+            context = self.context
+        view = getattr(self, '__parent__', None) or getattr(self, 'view', None) or getattr(self, 'table', None)
+        if view is not None:
+            checker = registry.queryMultiAdapter((context, self.request, view), IFormContextPermissionChecker)
+        if checker is None:
+            checker = registry.queryAdapter(context, IFormContextPermissionChecker)
+        if checker is not None:
+            return checker.edit_permission
--- a/src/pyams_form/templates/form.pt	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form/templates/form.pt	Wed Jun 17 09:56:38 2015 +0200
@@ -30,7 +30,7 @@
 							  data-ams-form-download-target view.download_target | nothing;
 							  data-ams-warn-on-change view.warn_on_change;">
 			<div class="modal-viewport">
-				<fieldset>
+				<fieldset tal:attributes="class view.fieldset_class | default">
 					<legend tal:define="legend view.legend"
 							tal:condition="legend">
 						<i tal:attributes="class view.icon_css_class | nothing"></i>
@@ -120,7 +120,7 @@
 																					   errors='state-error' if tabform.widgets.errors else '')">
 								<a data-toggle="tab"
 								   tal:attributes="href string:#${tabform.id}"
-								   tal:content="tabform.tabLabel" i18n:translate="">Tab label</a>
+								   tal:content="tabform.tab_label" i18n:translate="">Tab label</a>
 							</li>
 						</ul>
 						<div class="tab-content bordered padding-x-10">
--- a/src/pyams_form/templates/inner-form.pt	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form/templates/inner-form.pt	Wed Jun 17 09:56:38 2015 +0200
@@ -1,4 +1,5 @@
-<div class="no-padding">
+<div class="no-padding"
+	 tal:attributes="class string:${view.css_class} no-padding">
 	<div tal:define="prefix provider:form_prefix"
 		 tal:replace="structure prefix">Form prefix</div>
 	<form method="post"
@@ -110,7 +111,7 @@
 																		 errors='state-error' if tabform.widgets.errors else '')">
 						<a data-toggle="tab"
 						   tal:attributes="href string:#${tabform.id}"
-						   tal:content="tabform.tabLabel" i18n:translate="">Tab label</a>
+						   tal:content="tabform.tab_label" i18n:translate="">Tab label</a>
 					</li>
 				</ul>
 				<div class="tab-content">
--- a/src/pyams_form/templates/search.pt	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form/templates/search.pt	Wed Jun 17 09:56:38 2015 +0200
@@ -1,9 +1,9 @@
-<div id="search-form" data-ams-widget-sortable="false">
+<div id="search-form" data-ams-widget-sortable="false" i18n:domain="pyams_form">
 	<tal:var content="structure view.search_form.render()" />
 	<div class="ams-widget hidden">
 		<header>
 			<span class="widget-icon"><i class="fa fa-flash"></i></span>
-			<h2>Search results</h2>
+			<h2 i18n:translate="">Search results</h2>
 		</header>
 		<div class="widget-body no-padding alerts-container">
 			<div class="widget-body-toolbar"></div>
--- a/src/pyams_form/templates/widget-form.pt	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form/templates/widget-form.pt	Wed Jun 17 09:56:38 2015 +0200
@@ -4,6 +4,7 @@
 			  class="widget-icon"><i tal:attributes="class view.widget_icon_class"></i>
 		</span>
 		<h2 tal:content="view.legend"></h2>
+		<tal:var content="structure provider:pyams.widget_title" />
 		<tal:var content="structure provider:pyams.toolbar" />
 	</header>
 	<div class="widget-body no-padding">
@@ -112,7 +113,7 @@
 																					   errors='state-error' if tabform.widgets.errors else '')">
 								<a data-toggle="tab"
 								   tal:attributes="href string:#${tabform.id}"
-								   tal:content="tabform.tabLabel" i18n:translate="">Tab label</a>
+								   tal:content="tabform.tab_label" i18n:translate="">Tab label</a>
 							</li>
 						</ul>
 						<div class="tab-content">
--- a/src/pyams_form/widget/__init__.py	Wed May 20 12:24:59 2015 +0200
+++ b/src/pyams_form/widget/__init__.py	Wed Jun 17 09:56:38 2015 +0200
@@ -15,14 +15,16 @@
 
 # import standard library
 import inspect
+import json
 import os
 import venusian
 
 # import interfaces
 from pyams_form.interfaces.form import IFormLayer, IResetWidget, ICloseWidget, IDateWidget, IDatetimeWidget, ITimeWidget, \
-    IColorWidget
+    IColorWidget, IHTMLWidget
 from pyams_form.schema import IResetButton, ICloseButton
-from pyams_utils.schema import IColorField
+from pyams_skin.interfaces.tinymce import ITinyMCEConfiguration
+from pyams_utils.schema import IColorField, IHTMLField
 from pyramid.interfaces import IRequest
 from z3c.form.interfaces import INPUT_MODE, IFieldWidget, IButtonAction, IWidgetLayoutTemplate, IDataConverter
 from zope.pagetemplate.interfaces import IPageTemplate
@@ -34,6 +36,7 @@
 from z3c.form.action import Action
 from z3c.form.browser.submit import SubmitWidget
 from z3c.form.browser.text import TextWidget
+from z3c.form.browser.textarea import TextAreaWidget
 from z3c.form.button import ButtonAction
 from z3c.form.converter import BaseDataConverter
 from z3c.form.widget import FieldWidget, WidgetTemplateFactory, WidgetLayoutFactory
@@ -276,3 +279,34 @@
 @adapter_config(context=(IColorField, IFormLayer), provides=IFieldWidget)
 def ColorFieldWidget(field, request):
     return FieldWidget(field, ColorWidget(request))
+
+
+#
+# HTML widget
+#
+
+@widgettemplate_config(mode='input', template='templates/html-input.pt', layer=IFormLayer)
+@implementer_only(IHTMLWidget)
+class HTMLWidget(TextAreaWidget):
+    """HTML editor widget"""
+
+    label_css_class = 'textarea'
+
+    @property
+    def editor_data(self):
+        registry = self.request.registry
+        config = registry.queryMultiAdapter((self.form, self.request), ITinyMCEConfiguration, name=self.name)
+        if config is None:
+            config = registry.queryMultiAdapter((self.form, self.request), ITinyMCEConfiguration)
+        if config is None:
+            config = getattr(self, 'editor_config_data', None)
+            if config is not None:
+                return json.dumps(config)
+        else:
+            return json.dumps(config.configuration)
+
+
+@adapter_config(context=(IHTMLField, IFormLayer), provides=IFieldWidget)
+def HTMLFieldWidget(field, request):
+    return FieldWidget(field, HTMLWidget(request))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/templates/html-input.pt	Wed Jun 17 09:56:38 2015 +0200
@@ -0,0 +1,31 @@
+<textarea id="" name="" class="" cols="" rows=""
+		  tabindex="" disabled="" readonly="" accesskey=""
+		  i18n:domain=""
+		  tal:attributes="id view/id;
+						  name view/name;
+						  class string:${view/klass} tinymce;
+						  style view/style;
+						  title view/title;
+						  lang view/lang;
+						  onclick view/onclick;
+						  ondblclick view/ondblclick;
+						  onmousedown view/onmousedown;
+						  onmouseup view/onmouseup;
+						  onmouseover view/onmouseover;
+						  onmousemove view/onmousemove;
+						  onmouseout view/onmouseout;
+						  onkeypress view/onkeypress;
+						  onkeydown view/onkeydown;
+						  onkeyup view/onkeyup;
+						  disabled view/disabled;
+						  tabindex view/tabindex;
+						  onfocus view/onfocus;
+						  onblur view/onblur;
+						  onchange view/onchange;
+						  cols view/cols;
+						  rows view/rows;
+						  readonly view/readonly;
+						  accesskey view/accesskey;
+						  onselect view/onselect;
+						  data-ams-data view/editor_data;"
+		  tal:content="view/value"></textarea>