[Checkins] SVN: plone.z3cform/trunk/ Attempt to make plone.z3cform work in Zope 2.12 with unwrapped forms, as well as in Zope 2.10 with wraped forms

Martin Aspeli optilude at gmx.net
Mon Feb 15 00:36:00 EST 2010


Log message for revision 109071:
  Attempt to make plone.z3cform work in Zope 2.12 with unwrapped forms, as well as in Zope 2.10 with wraped forms

Changed:
  U   plone.z3cform/trunk/plone/z3cform/configure.zcml
  U   plone.z3cform/trunk/plone/z3cform/crud/crud.py
  A   plone.z3cform/trunk/plone/z3cform/demo/
  A   plone.z3cform/trunk/plone/z3cform/demo/__init__.py
  A   plone.z3cform/trunk/plone/z3cform/demo/configure.zcml
  A   plone.z3cform/trunk/plone/z3cform/demo/demoform.py
  U   plone.z3cform/trunk/plone/z3cform/interfaces.py
  U   plone.z3cform/trunk/plone/z3cform/layout.py
  U   plone.z3cform/trunk/plone/z3cform/overrides.zcml
  A   plone.z3cform/trunk/plone/z3cform/patch.py
  A   plone.z3cform/trunk/plone/z3cform/templates/
  A   plone.z3cform/trunk/plone/z3cform/templates/form.pt
  A   plone.z3cform/trunk/plone/z3cform/templates/layout.pt
  A   plone.z3cform/trunk/plone/z3cform/templates/macros.pt
  A   plone.z3cform/trunk/plone/z3cform/templates/subform.pt
  A   plone.z3cform/trunk/plone/z3cform/templates/wrappedform.pt
  A   plone.z3cform/trunk/plone/z3cform/templates/wrappedsubform.pt
  U   plone.z3cform/trunk/plone/z3cform/templates.py
  U   plone.z3cform/trunk/plone/z3cform/templates.zcml
  U   plone.z3cform/trunk/plone/z3cform/tests.py
  U   plone.z3cform/trunk/plone/z3cform/traversal.py
  U   plone.z3cform/trunk/plone/z3cform/traversal.txt
  U   plone.z3cform/trunk/plone/z3cform/z2.py
  U   plone.z3cform/trunk/setup.py

-=-
Modified: plone.z3cform/trunk/plone/z3cform/configure.zcml
===================================================================
--- plone.z3cform/trunk/plone/z3cform/configure.zcml	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/plone/z3cform/configure.zcml	2010-02-15 05:35:58 UTC (rev 109071)
@@ -2,28 +2,58 @@
     xmlns="http://namespaces.zope.org/zope"
     xmlns:zcml="http://namespaces.zope.org/zcml"
     xmlns:browser="http://namespaces.zope.org/browser"
+    xmlns:monkey="http://namespaces.plone.org/monkey"
     xmlns:i18n="http://namespaces.zope.org/i18n"
     i18n_domain="plone.z3cform">
 
   <include file="templates.zcml" />
 
+  <include package="collective.monkeypatcher" file="meta.zcml" />
   <include package="z3c.form" file="meta.zcml" />
   <include package="z3c.form" />
 
+  <i18n:registerTranslations directory="locales"/>
+  
+  <!-- TODO: Remove -->
+  <include package=".demo" />
+  
+  <!-- Monkey patch BaseForm/GroupForm's update() to apply Zope 2 input
+       parameter processing. Without this, we'll get errors because Zope is
+       sending encoded str's when z3c.form wants decoded unicode's.
+    -->
+  <monkey:patch
+      description="Zope 2 integration - decode form inputs prior to form processing"
+      class="z3c.form.form.BaseForm"
+      original="update"
+      replacement=".patch.BaseForm_update"
+      />
+  <monkey:patch
+      description="Zope 2 integration - decode form inputs prior to form processing"
+      class="z3c.form.group.GroupForm"
+      original="update"
+      replacement=".patch.GroupForm_update"
+      />
+  
+  <!-- Override the FileUploadDataConverter from z3c.form.
+       We register it for the class so that it is more specific than the one
+       registered in z3c.form. This avoids the need for a messy
+       overrides.zcml.
+     -->
+  <adapter
+      for="zope.schema.Bytes z3c.form.interfaces.IFileWidget"
+      factory=".converter.FileUploadDataConverter"
+      />
+  
   <!-- Backported from z3c.from trunk -->
   <include package=".textlines" file="textlines.zcml"
     zcml:condition="not-installed z3c.form.browser.textlines"/>
 
   <!-- These are not backported, but represent useful policy -->
-  <adapter
-      factory=".textlines.textlines.TextLinesSetConverter"
-      />
-  <adapter
-      factory=".textlines.textlines.TextLinesFrozenSetConverter"
-      />
+  <adapter factory=".textlines.textlines.TextLinesSetConverter" />
+  <adapter factory=".textlines.textlines.TextLinesFrozenSetConverter" />
+  
+  <!-- ++widget++ namespace -->
+  <adapter factory=".traversal.FormWidgetTraversal" name="widget" />
+  <adapter factory=".traversal.WrapperWidgetTraversal" name="widget" />
 
-  <adapter factory=".traversal.WidgetTraversal" name="widget" />
-
-  <i18n:registerTranslations directory="locales"/>
-
 </configure>

Modified: plone.z3cform/trunk/plone/z3cform/crud/crud.py
===================================================================
--- plone.z3cform/trunk/plone/z3cform/crud/crud.py	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/plone/z3cform/crud/crud.py	2010-02-15 05:35:58 UTC (rev 109071)
@@ -333,7 +333,6 @@
 
 class AddForm(form.Form):
     label = _(u"Add")
-    template = viewpagetemplatefile.ViewPageTemplateFile('../form.pt')
     ignoreContext = True
     ignoreRequest = True
 

Added: plone.z3cform/trunk/plone/z3cform/demo/configure.zcml
===================================================================
--- plone.z3cform/trunk/plone/z3cform/demo/configure.zcml	                        (rev 0)
+++ plone.z3cform/trunk/plone/z3cform/demo/configure.zcml	2010-02-15 05:35:58 UTC (rev 109071)
@@ -0,0 +1,27 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    xmlns:grok="http://namespaces.zope.org/grok"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    i18n_domain="plone.z3cform">
+    
+    <include package="plone.formwidget.autocomplete" />
+    <include package="plone.directives.form" file="meta.zcml" />
+    
+    <grok:grok package="." />
+    
+    <browser:view
+        name="z3c-demo-form"
+        class=".demoform.DemoForm"
+        permission="zope2.View"
+        for="*"
+        />
+
+    <browser:view
+        name="z3c-demo-form-wrapped"
+        class=".demoform.WrappedDemoForm"
+        permission="zope2.View"
+        for="*"
+        />
+
+</configure>

Added: plone.z3cform/trunk/plone/z3cform/demo/demoform.py
===================================================================
--- plone.z3cform/trunk/plone/z3cform/demo/demoform.py	                        (rev 0)
+++ plone.z3cform/trunk/plone/z3cform/demo/demoform.py	2010-02-15 05:35:58 UTC (rev 109071)
@@ -0,0 +1,65 @@
+from zope.interface import Interface
+from zope import schema
+
+from z3c.form import field, form, button
+
+from plone.z3cform import layout
+from plone.app.z3cform.wysiwyg import WysiwygFieldWidget
+
+from plone.formwidget.autocomplete import AutocompleteFieldWidget
+from plone.formwidget.autocomplete.demo import KeywordSourceBinder
+
+import plone.directives.form
+from five import grok
+
+class IDemoFormFields(plone.directives.form.Schema):
+    
+    text = schema.TextLine(title=u"Text field", description=u"With description")
+    number = schema.Int(title=u"Integer field")
+    bytes = schema.Bytes(title=u"Bytes field", required=False)
+    autocomplete = schema.Choice(title=u"Choice field", source=KeywordSourceBinder())
+    
+    plone.directives.form.widget(wysiwyg=WysiwygFieldWidget)
+    wysiwyg = schema.Text(title=u"Edit this", description=u"Stuff here")
+
+# Form class - registered directly, and used in the wrapper scenario below
+class DemoForm(form.Form):
+    
+    fields = field.Fields(IDemoFormFields)
+    fields['autocomplete'].widgetFactory = AutocompleteFieldWidget
+    fields['wysiwyg'].widgetFactory = WysiwygFieldWidget
+    
+    ignoreContext = True
+    label = u"Demo form"
+    description = u"Some description"
+
+    @button.buttonAndHandler(u'Ok')
+    def handleApply(self, action):
+        data, errors = self.extractData()
+        print data, errors
+        if errors:
+            self.status = self.formErrorsMessage
+            return
+
+# Old-style wrapped form
+WrappedDemoForm = layout.wrap_form(DemoForm)
+
+# Grokked form
+
+class SchemaForm(plone.directives.form.SchemaForm):
+    schema = IDemoFormFields
+    grok.name('z3c-demo-form-grokked')
+    grok.require('zope2.Public')
+    grok.context(Interface)
+    ignoreContext = True
+    
+    label = u"Demo form (grokked)"
+    description = u"Some description"
+    
+    @button.buttonAndHandler(u'Ok')
+    def handleApply(self, action):
+        data, errors = self.extractData()
+        print data, errors
+        if errors:
+            self.status = self.formErrorsMessage
+            return

Modified: plone.z3cform/trunk/plone/z3cform/interfaces.py
===================================================================
--- plone.z3cform/trunk/plone/z3cform/interfaces.py	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/plone/z3cform/interfaces.py	2010-02-15 05:35:58 UTC (rev 109071)
@@ -4,10 +4,19 @@
 from zope.pagetemplate.interfaces import IPageTemplate
 from z3c.form.interfaces import IForm
 
-
 class IFormWrapper(Interface):
-    """Marker interface for the form wrapper
+    """Form wrapper class.
+    
+    This class allows "two-step" rendering, with an outer view rendering
+    part of the page and the form class rendering the form area.
+    
+    In Zope < 2.12, this is the only way to get z3c.form support, because
+    the view class takes care of the acquisition requirement.
+    
+    In Zope 2.12 and later, this approach is optional: you may register the
+    form class directly as a browser view.
     """
+    
     def update():
         """We use the content provider update/render couple.
         """
@@ -32,3 +41,15 @@
         required = False,
         schema = IPageTemplate
         )
+
+class IWrappedForm(Interface):
+    """Marker interface applied to wrapped forms during rendering.
+    
+    This allows different handling of templates, for example.
+    """
+
+class IWrappedSubForm(IWrappedForm):
+    """Marker interface applied to wrapped sub-forms during rendering.
+    
+    This allows different handling of templates, for example.
+    """

Modified: plone.z3cform/trunk/plone/z3cform/layout.py
===================================================================
--- plone.z3cform/trunk/plone/z3cform/layout.py	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/plone/z3cform/layout.py	2010-02-15 05:35:58 UTC (rev 109071)
@@ -46,6 +46,12 @@
 
         Override this method if you have more than one form.
         """
+        
+        if z3c.form.interfaces.ISubForm.providedBy(self.form_instance):
+            zope.interface.alsoProvides(self.form_instance, interfaces.IWrappedSubForm)
+        else:
+            zope.interface.alsoProvides(self.form_instance, interfaces.IWrappedForm)
+        
         z2.switch_on(self, request_layer=self.request_layer)
         self.form_instance.update()
         # A z3c.form.form.AddForm do a redirect in its render method.

Modified: plone.z3cform/trunk/plone/z3cform/overrides.zcml
===================================================================
--- plone.z3cform/trunk/plone/z3cform/overrides.zcml	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/plone/z3cform/overrides.zcml	2010-02-15 05:35:58 UTC (rev 109071)
@@ -1,7 +1,7 @@
 <configure xmlns="http://namespaces.zope.org/zope">
 
-  <adapter
-      factory=".converter.FileUploadDataConverter"
-      />
+    <!-- This file is no longer necessary, but left here in case people
+         reference it
+      -->
 
 </configure>

Added: plone.z3cform/trunk/plone/z3cform/patch.py
===================================================================
--- plone.z3cform/trunk/plone/z3cform/patch.py	                        (rev 0)
+++ plone.z3cform/trunk/plone/z3cform/patch.py	2010-02-15 05:35:58 UTC (rev 109071)
@@ -0,0 +1,30 @@
+"""A small monkey patch for z3c.form's BaseForm.update() and
+GroupForm.update(). We need to call z2.processInputs() before the request is
+used, because z3c.form expects them to have been converted to unicode first.
+"""
+
+from z3c.form.form import BaseForm
+from z3c.form.group import GroupForm
+
+from plone.z3cform.z2 import processInputs
+
+_original_BaseForm_update = BaseForm.update
+_original_GroupForm_update = GroupForm.update
+
+def BaseForm_update(self):
+    # This monkey patch ensures that processInputs() is called before 
+    # z3c.form does any work on the request. This is because z3c.form expects
+    # charset negotiation to have taken place in the publisher, and will
+    # complain about non-unicode strings
+    
+    processInputs(self.request)
+    _original_BaseForm_update(self)
+
+def GroupForm_update(self):
+    # This monkey patch ensures that processInputs() is called before 
+    # z3c.form does any work on the request. This is because z3c.form expects
+    # charset negotiation to have taken place in the publisher, and will
+    # complain about non-unicode strings
+    
+    processInputs(self.request)
+    _original_GroupForm_update(self)

Added: plone.z3cform/trunk/plone/z3cform/templates/form.pt
===================================================================
--- plone.z3cform/trunk/plone/z3cform/templates/form.pt	                        (rev 0)
+++ plone.z3cform/trunk/plone/z3cform/templates/form.pt	2010-02-15 05:35:58 UTC (rev 109071)
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      i18n:domain="plone.z3cform"
+      metal:use-macro="context/@@standard_macros/page">
+      
+    <body metal:fill-slot="body">
+        
+        <h1 tal:content="view/label | nothing" />
+        
+        <div class="description"
+           tal:condition="view/description | nothing"
+           tal:content="structure view/description">Form description</div>
+        
+        <metal:block use-macro="context/@@ploneform-macros/titlelessform" />
+        
+    </body>
+    
+</html>

Added: plone.z3cform/trunk/plone/z3cform/templates/layout.pt
===================================================================
--- plone.z3cform/trunk/plone/z3cform/templates/layout.pt	                        (rev 0)
+++ plone.z3cform/trunk/plone/z3cform/templates/layout.pt	2010-02-15 05:35:58 UTC (rev 109071)
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n" 
+      metal:use-macro="context/main_template/macros/master">
+<body>
+
+<metal:slot metal:fill-slot="header" i18n:domain="cmf_default">
+  <h1 tal:content="view/label">View Title</h1>
+</metal:slot>
+
+<metal:slot metal:fill-slot="main" i18n:domain="cmf_default">
+  <div id="layout-contents">
+    <span tal:replace="structure view/contents" />
+  </div>
+</metal:slot>
+</body>
+</html>

Added: plone.z3cform/trunk/plone/z3cform/templates/macros.pt
===================================================================
--- plone.z3cform/trunk/plone/z3cform/templates/macros.pt	                        (rev 0)
+++ plone.z3cform/trunk/plone/z3cform/templates/macros.pt	2010-02-15 05:35:58 UTC (rev 109071)
@@ -0,0 +1,89 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      i18n:domain="plone.z3cform"
+      tal:omit-tag="">
+
+  <head></head>
+
+  <body>
+
+    <div class="form" metal:define-macro="form">
+
+      <h3 tal:condition="view/label | nothing">
+          <span tal:replace="view/label">Form title</span>:</h3>
+
+      <div class="description"
+           tal:condition="view/description | nothing"
+           tal:content="structure view/description">Form description</div>
+      <metal:define define-macro="titlelessform">
+	<div class="portalMessage"
+             tal:condition="view/status" tal:content="view/status">
+	</div>
+
+
+	<form action="." method="post"
+              tal:attributes="action request/getURL; enctype view/enctype">
+
+          <metal:define define-macro="fields">
+
+            <tal:widgets repeat="widget view/widgets/values">
+              <div class="row"
+                   tal:define="hidden python:widget.mode == 'hidden'"
+                   tal:omit-tag="hidden">
+
+		<metal:field define-macro="field">
+                  <div class="field"
+                       tal:define="error widget/error"
+                       tal:attributes="class python:'field' + (error and ' error' or '')">
+                    <label for=""
+                           tal:attributes="for widget/id"
+                           tal:condition="not:hidden">
+                      <span i18n:translate=""
+                            tal:content="widget/label">label</span>
+                    </label>
+
+                    <span class="fieldRequired" title="Required"
+                          tal:condition="python:widget.required and not hidden"
+                          i18n:translate="label_required"
+                          i18n:attributes="title title_required;">
+                      (Required)
+                    </span>
+
+                    <div class="formHelp"
+			 tal:define="description widget/field/description"
+			 i18n:translate=""
+			 tal:content="description"
+			 tal:condition="python:description and not hidden"
+			 >field description</div>
+
+                    <div tal:condition="error"
+			 tal:content="structure error/render">
+                      Error
+                    </div>
+
+                    <div class="widget">
+                      <input type="text" tal:replace="structure widget/render" />
+                    </div>
+                  </div>
+		</metal:field>
+
+              </div>
+            </tal:widgets>
+
+          </metal:define>
+
+          <metal:define define-macro="actions">
+            <div class="action" tal:repeat="action view/actions/values|nothing">
+              <input type="submit" tal:replace="structure action/render" />
+            </div>
+          </metal:define>
+
+	</form>
+      </metal:define>
+      </div>
+
+
+  </body>
+</html>

Added: plone.z3cform/trunk/plone/z3cform/templates/subform.pt
===================================================================
--- plone.z3cform/trunk/plone/z3cform/templates/subform.pt	                        (rev 0)
+++ plone.z3cform/trunk/plone/z3cform/templates/subform.pt	2010-02-15 05:35:58 UTC (rev 109071)
@@ -0,0 +1,10 @@
+<div xmlns="http://www.w3.org/1999/xhtml"
+     xmlns:tal="http://xml.zope.org/namespaces/tal"
+     xmlns:metal="http://xml.zope.org/namespaces/metal"
+     tal:attributes="class view/css_class|nothing">
+  <h3 tal:replace="structure view/heading|nothing" />
+  <div tal:replace="structure view/contents_top|nothing" />
+  <metal:use use-macro="context/@@ploneform-macros/fields" />
+  <metal:use use-macro="context/@@ploneform-macros/actions" />
+  <div tal:replace="structure view/contents_bottom|nothing" />
+</div>

Added: plone.z3cform/trunk/plone/z3cform/templates/wrappedform.pt
===================================================================
--- plone.z3cform/trunk/plone/z3cform/templates/wrappedform.pt	                        (rev 0)
+++ plone.z3cform/trunk/plone/z3cform/templates/wrappedform.pt	2010-02-15 05:35:58 UTC (rev 109071)
@@ -0,0 +1 @@
+<metal:use use-macro="context/@@ploneform-macros/titlelessform" />

Added: plone.z3cform/trunk/plone/z3cform/templates/wrappedsubform.pt
===================================================================
--- plone.z3cform/trunk/plone/z3cform/templates/wrappedsubform.pt	                        (rev 0)
+++ plone.z3cform/trunk/plone/z3cform/templates/wrappedsubform.pt	2010-02-15 05:35:58 UTC (rev 109071)
@@ -0,0 +1,10 @@
+<div xmlns="http://www.w3.org/1999/xhtml"
+     xmlns:tal="http://xml.zope.org/namespaces/tal"
+     xmlns:metal="http://xml.zope.org/namespaces/metal"
+     tal:attributes="class view/css_class|nothing">
+  <h3 tal:replace="structure view/heading|nothing" />
+  <div tal:replace="structure view/contents_top|nothing" />
+  <metal:use use-macro="context/@@ploneform-macros/fields" />
+  <metal:use use-macro="context/@@ploneform-macros/actions" />
+  <div tal:replace="structure view/contents_bottom|nothing" />
+</div>

Modified: plone.z3cform/trunk/plone/z3cform/templates.py
===================================================================
--- plone.z3cform/trunk/plone/z3cform/templates.py	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/plone/z3cform/templates.py	2010-02-15 05:35:58 UTC (rev 109071)
@@ -1,7 +1,8 @@
 """This module provides "form template factories" that can be
-registered to provide default form templates for forms and subforms
-that have a Plone style.  These default templates draw from a macro
-page template which you can use by itself to render parts of it.
+registered to provide default form templates for forms and subforms. 
+
+The default templates draw from a macro page template which you can use by
+itself to render parts of it.
 """
 
 import os
@@ -9,10 +10,11 @@
 import zope.app.pagetemplate.viewpagetemplatefile
 
 import z3c.form.interfaces
+import z3c.form.form
+import z3c.form.widget
+
 from z3c.form import util
 from zope.pagetemplate.interfaces import IPageTemplate
-from z3c.form.form import FormTemplateFactory
-from z3c.form.widget import WidgetTemplateFactory
 
 try:
     # chameleon-compatible page templates
@@ -26,42 +28,49 @@
 import plone.z3cform
 import plone.z3cform.layout
 
-path = lambda p: os.path.join(os.path.dirname(plone.z3cform.__file__), p)
+path = lambda p: os.path.join(os.path.dirname(plone.z3cform.__file__), 'templates', p)
 
-class FormTemplateFactory(FormTemplateFactory):
-    """Form template factory that maybe uses chameleon"""
+# Zope 2-compatible form and widget template factory classes.
 
-    def __init__(self, filename, contentType='text/html', form=None,
-        request=None):
+class FormTemplateFactory(z3c.form.form.FormTemplateFactory):
+    """Form template factory that will use Chameleon if installed (via
+    five.pt), or the Zope 2 ViewPageTemplateFile from Products.Five if not.
+    
+    You can use this for a wrapped form, but not for a form that is going
+    to be rendered as a standalone view. Use ZopeTwoFormTemplateFactory for
+    that instead.
+    """
+
+    def __init__(self, filename, contentType='text/html', form=None, request=None):
         self.template = ViewPageTemplateFile(filename, content_type=contentType)
         zope.component.adapter(
             util.getSpecification(form),
             util.getSpecification(request))(self)
         zope.interface.implementer(IPageTemplate)(self)
-z3c.form.form.FormTemplateFactory = FormTemplateFactory
 
-class ZopeTwoFormTemplateFactory(FormTemplateFactory):
-    """Form template factory for Zope 2 page templates"""
+class ZopeTwoFormTemplateFactory(z3c.form.form.FormTemplateFactory):
+    """Form template factory for Zope 2 page templates.
+    
+    Use this for any form which is going to be rendered as a view, or any
+    form wrapper view.
+    """
 
-    def __init__(self, filename, contentType='text/html', form=None,
-        request=None):
+    def __init__(self, filename, contentType='text/html', form=None, request=None):
         self.template = ZopeTwoPageTemplateFile(filename, content_type=contentType)
         zope.component.adapter(
             util.getSpecification(form),
             util.getSpecification(request))(self)
         zope.interface.implementer(IPageTemplate)(self)
 
-layout_factory = ZopeTwoFormTemplateFactory(
-    path('layout.pt'), form=plone.z3cform.interfaces.IFormWrapper)
-form_factory = FormTemplateFactory(
-    path('form.pt'), form=z3c.form.interfaces.IForm)
-subform_factory = FormTemplateFactory(
-    path('subform.pt'), form=z3c.form.interfaces.ISubForm)
-
-class ZopeTwoWidgetTemplateFactory(WidgetTemplateFactory):
-    def __init__(self, filename, contentType='text/html',
-                 context=None, request=None, view=None,
-                 field=None, widget=None):
+class ZopeTwoWidgetTemplateFactory(z3c.form.widget.WidgetTemplateFactory):
+    """A variant of z3c.form's widget.WidgetTemplateFactory which uses Zope 2
+    page templates. This should only be necessary if you strictly need the
+    extra Zope 2-isms of Five's ViewPageTemplateFile.
+    """
+    
+    def __init__(self, filename, contentType='text/html', context=None,
+        request=None, view=None, field=None, widget=None
+    ):
         self.template = ViewPageTemplateFile(filename, content_type=contentType)
         zope.component.adapter(
             util.getSpecification(context),
@@ -70,10 +79,33 @@
             util.getSpecification(field),
             util.getSpecification(widget))(self)
         zope.interface.implementer(IPageTemplate)(self)
-z3c.form.widget.WidgetTemplateFactory = ZopeTwoWidgetTemplateFactory
 
+# View containing common macros
+
 class Macros(zope.publisher.browser.BrowserView):
-    template = ViewPageTemplateFile('macros.pt')
+    
+    def __getitem__(self, key):
+        return self.index.macros[key]
 
-    def __getitem__(self, key):
-        return self.template.macros[key]
+# Default templates for the wrapped layout view use case
+
+layout_factory = ZopeTwoFormTemplateFactory(path('layout.pt'),
+        form=plone.z3cform.interfaces.IFormWrapper
+    )
+
+wrapped_form_factory = FormTemplateFactory(path('wrappedform.pt'),
+        form=plone.z3cform.interfaces.IWrappedForm,
+    )
+wrapped_subform_factory = FormTemplateFactory(path('wrappedsubform.pt'),
+        form=plone.z3cform.interfaces.IWrappedSubForm,
+    )
+
+# Default templates for the standalone form use case
+
+standalone_form_factory = ZopeTwoFormTemplateFactory(path('form.pt'),
+        form=z3c.form.interfaces.IForm
+    )
+
+standalone_subform_factory = ZopeTwoFormTemplateFactory(path('subform.pt'),
+        form=z3c.form.interfaces.ISubForm
+    )

Modified: plone.z3cform/trunk/plone/z3cform/templates.zcml
===================================================================
--- plone.z3cform/trunk/plone/z3cform/templates.zcml	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/plone/z3cform/templates.zcml	2010-02-15 05:35:58 UTC (rev 109071)
@@ -13,13 +13,18 @@
       name="ploneform-macros"
       for="*"
       class=".templates.Macros"
-      template="macros.pt"
+      template="templates/macros.pt"
       allowed_interface="zope.interface.common.mapping.IItemMapping"
       permission="zope.Public"
       />
 
-  <adapter factory=".templates.form_factory" />
-  <adapter factory=".templates.subform_factory" />
+  <!-- Form templates for wrapped layout use case -->
   <adapter factory=".templates.layout_factory" />
-
+  <adapter factory=".templates.wrapped_form_factory" />
+  <adapter factory=".templates.wrapped_subform_factory" />
+  
+  <!-- Form templates for standalone form use case -->
+  <adapter factory=".templates.standalone_form_factory" />
+  <adapter factory=".templates.standalone_subform_factory" />
+  
 </configure>

Modified: plone.z3cform/trunk/plone/z3cform/tests.py
===================================================================
--- plone.z3cform/trunk/plone/z3cform/tests.py	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/plone/z3cform/tests.py	2010-02-15 05:35:58 UTC (rev 109071)
@@ -13,6 +13,8 @@
 
 import plone.z3cform.templates
 
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+
 def create_eventlog(event=interface.Interface):
     value = []
     @component.adapter(event)
@@ -32,6 +34,9 @@
         zope.traversing.namespace.view, (None, None), name='view')
 
     # Setup ploneform macros
+    
+    plone.z3cform.templates.Macros.index = ViewPageTemplateFile(plone.z3cform.templates.path('macros.pt'))
+    
     component.provideAdapter(
         plone.z3cform.templates.Macros,
         (None, None),
@@ -41,7 +46,7 @@
     from zope.pagetemplate.interfaces import IPageTemplate
 
     component.provideAdapter(
-        plone.z3cform.templates.form_factory,
+        plone.z3cform.templates.wrapped_form_factory,
         (None, None),
         IPageTemplate)
 

Modified: plone.z3cform/trunk/plone/z3cform/traversal.py
===================================================================
--- plone.z3cform/trunk/plone/z3cform/traversal.py	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/plone/z3cform/traversal.py	2010-02-15 05:35:58 UTC (rev 109071)
@@ -4,14 +4,16 @@
 from zope.traversing.interfaces import ITraversable
 from zope.publisher.interfaces.browser import IBrowserRequest
 
+from z3c.form.interfaces import IForm
+
 from plone.z3cform.interfaces import IFormWrapper
 from plone.z3cform import z2
 
 from Acquisition import aq_inner
 
-class WidgetTraversal(object):
+class FormWidgetTraversal(object):
     """Allow traversal to widgets via the ++widget++ namespace. The context
-    is the from layout wrapper.
+    is the from itself (used when the layout wrapper view is not used).
     
     Note that to support security in Zope 2.10, the widget being traversed to
     must have an __of__ method, i.e. it must support acquisition. The easiest
@@ -31,16 +33,18 @@
     """
     
     implements(ITraversable)
-    adapts(IFormWrapper, IBrowserRequest)
+    adapts(IForm, IBrowserRequest)
     
     def __init__(self, context, request=None):
         self.context = context
         self.request = request
-
+    
+    def _prepareForm(self):
+        return self.context
+    
     def traverse(self, name, ignored):
         
-        form = self.context.form_instance
-        z2.switch_on(self.context, request_layer=self.context.request_layer)
+        form = self._prepareForm()
         
         form.update()
         
@@ -59,3 +63,17 @@
             return widget
         
         return None
+
+class WrapperWidgetTraversal(FormWidgetTraversal):
+    """Allow traversal to widgets via the ++widget++ namespace. The context
+    is the from layout wrapper.
+    
+    The caveat about security above still applies!
+    """
+    
+    adapts(IFormWrapper, IBrowserRequest)
+    
+    def _prepareForm(self):
+        form = self.context.form_instance
+        z2.switch_on(self.context, request_layer=self.context.request_layer)
+        return form

Modified: plone.z3cform/trunk/plone/z3cform/traversal.txt
===================================================================
--- plone.z3cform/trunk/plone/z3cform/traversal.txt	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/plone/z3cform/traversal.txt	2010-02-15 05:35:58 UTC (rev 109071)
@@ -1,10 +1,31 @@
 Traversal
 =========
 
-plone.z3cform allows you to traverse to a widget using the ++wiget++ namespace
-adapter on a form view. Note that widgets may need to mix in
-Acquisition.Explicit to be truly traversable in Zope 2.
+plone.z3cform allows you to traverse to a widget using the ++widget++
+namespace adapter on a form view.
 
+Note that widgets may need to mix in Acquisition.Explicit to be truly
+traversable in Zope 2.10. In Zope 2.12, that is not required. However, you
+may get an error if you mix in Explicit in Zope 2.12 and the parent view
+(normally the form or the form layout wrapper) does not, as is likely to be
+the case. To be compatible with both Zope 2.10 and 2.12, you'll need to
+mix in Acquisition.Explicit, but make sure the widget does *not* provide the
+IAcquirer interface. One way to do that is by using implementsOnly(), e.g.::
+    
+    class MyWidget(Aquisition.Explicit):
+        implementsOnly(IMyWidget)
+        
+        ...
+
+If you are only targeting Zope 2.12 and later, you can avoid mixing in any
+kind of acquisition altogether.
+
+The ++widget++ namespace works both in the case of a layout wrapper view,
+and in the case of a form used directly (in Zope 2.12 or later).
+
+Traversal on a standalone form
+------------------------------
+
 First, we create a simple form and context.
 
     >>> from zope.interface import alsoProvides
@@ -21,6 +42,72 @@
 
     >>> from zope import interface, schema
     >>> from z3c.form import form, field, button
+
+    >>> class MySchema(interface.Interface):
+    ...     age = schema.Int(title=u"Age")
+
+    >>> from z3c.form.interfaces import IFieldsForm
+    >>> from zope.interface import implements
+    >>> class MyForm(form.Form):
+    ...     implements(IFieldsForm)
+    ...     fields = field.Fields(MySchema)
+    ...     ignoreContext = True # don't use context to get widget data
+    ...     
+    ...     def update(self):
+    ...         print "Updating test form"
+    ...         super(MyForm, self).update()
+
+    >>> from zope.component import provideAdapter
+    >>> from zope.publisher.interfaces.browser import IBrowserRequest
+    >>> from zope.interface import Interface
+
+    >>> provideAdapter(adapts=(Interface, IBrowserRequest),
+    ...                provides=Interface,
+    ...                factory=MyForm,
+    ...                name=u"test-form")
+
+    >>> from Acquisition import Implicit
+    >>> class Bar(Implicit):
+    ...     __allow_access_to_unprotected_subobjects__ = 1
+    ...     implements(Interface)
+
+    >>> from zope.component import getMultiAdapter
+    >>> context = Bar()
+    >>> request = make_request()
+
+Now, let's emulate the publisher and look up the namespace traversal
+adapter. For example, let's say we'd traversed to
+../@@test-form/++widget++age. The publisher would then do:
+
+    >>> form = getMultiAdapter((context, request), name=u"test-form")
+
+    >>> from zope.traversing.interfaces import ITraversable
+    >>> traverser = getMultiAdapter((form, request), name=u"widget")
+    >>> traverser.traverse('age', [])
+    Updating test form
+    <TextWidget 'form.widgets.age'>
+
+Please note that this point, the form has been updated, but not rendered.
+
+Traversal on a layout wrapper view
+-----------------------------------
+
+Again, we create a simple form and context.
+
+    >>> from zope.interface import alsoProvides
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.annotation.interfaces import IAttributeAnnotatable
+    >>> from z3c.form.interfaces import IFormLayer
+
+    >>> def make_request(form={}):
+    ...     request = TestRequest()
+    ...     request.form.update(form)
+    ...     alsoProvides(request, IFormLayer)
+    ...     alsoProvides(request, IAttributeAnnotatable)
+    ...     return request
+
+    >>> from zope import interface, schema
+    >>> from z3c.form import form, field, button
     >>> from plone.z3cform.layout import FormWrapper
 
     >>> class MySchema(interface.Interface):

Modified: plone.z3cform/trunk/plone/z3cform/z2.py
===================================================================
--- plone.z3cform/trunk/plone/z3cform/z2.py	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/plone/z3cform/z2.py	2010-02-15 05:35:58 UTC (rev 109071)
@@ -1,14 +1,69 @@
 from zope import interface
-from zope.i18n.interfaces import IUserPreferredLanguages
+
+from zope.publisher.browser import isCGI_NAME
+from zope.publisher.interfaces.browser import IBrowserApplicationRequest
+
+from zope.i18n.interfaces import IUserPreferredLanguages, IUserPreferredCharsets
 from zope.i18n.locales import locales, LoadLocaleError
-from Products.Five.browser import decode
+
 import z3c.form.interfaces
 
 class IFixedUpRequest(interface.Interface):
-    pass
+    """Marker interface used to ensure we don't fix up the request twice
+    """
 
-# XXX This is ripped from zope.publisher.http.HTTPRequest; we should
-# move this into Five
+class IProcessedRequest(interface.Interface):
+    """Marker interface used to ensure we don't process the request inputs
+    twice.
+    """
+
+# Safer versions of the functions in Products.Five.browser.decode
+
+def processInputs(request, charsets=None):
+    """Process the values in request.form to decode strings to unicode, using
+    the passed-in list of charsets. If none are passed in, look up the user's
+    preferred charsets. The default is to use utf-8.
+    """
+    
+    if IProcessedRequest.providedBy(request):
+        return
+    
+    if charsets is None:
+        envadapter = IUserPreferredCharsets(request, None)
+        if envadapter is None:
+            charsets = ['utf-8']
+        else:
+            charsets = envadapter.getPreferredCharsets() or ['utf-8']
+    
+    for name, value in request.form.items():
+        if not (isCGI_NAME(name) or name.startswith('HTTP_')):
+            if isinstance(value, str):
+                request.form[name] = _decode(value, charsets)
+            elif isinstance(value, (list, tuple,)):
+                newValue = []
+                for val in value:
+                    if isinstance(val, str):
+                        val = _decode(val, charsets)
+                    newValue.append(val)
+                
+                if isinstance(value, tuple):
+                    newValue = tuple(value)
+                
+                request.form[name] = newValue
+    
+    interface.alsoProvides(request, IProcessedRequest)
+
+def _decode(text, charsets):
+    for charset in charsets:
+        try:
+            text = unicode(text, charset)
+            break
+        except UnicodeError:
+            pass
+    return text
+
+# This is ripped from zope.publisher.http.HTTPRequest; it is only 
+# necessary in Zope < 2.11
 def setup_locale(request):
     envadapter = IUserPreferredLanguages(request, None)
     if envadapter is None:
@@ -27,8 +82,8 @@
         # which is guaranteed to exist
         return locales.getLocale(None, None, None)
 
-# XXX Add a getURL method on the request object; we should move this
-# into Five
+# XXX Add a getURL method on the request object; this is only necessary in
+# Zope < 2.11
 def add_getURL(request):
     def getURL(level=0, path_only=False):
         assert level == 0 and path_only == False
@@ -36,12 +91,21 @@
     request.getURL = getURL
 
 def switch_on(view, request_layer=z3c.form.interfaces.IFormLayer):
+    """Fix up the request and apply the given layer. This is mainly useful
+    in Zope < 2.10 when using a wrapper layout view.
+    """
+    
     request = view.request
-    from zope.publisher.interfaces.browser import IBrowserApplicationRequest
-    if not IFixedUpRequest.providedBy(request) and \
-       not IBrowserApplicationRequest.providedBy(request):
+    
+    if (not IFixedUpRequest.providedBy(request) and
+        not IBrowserApplicationRequest.providedBy(request)
+    ):
+        
         interface.alsoProvides(request, IFixedUpRequest)
         interface.alsoProvides(request, request_layer)
-        request.locale = setup_locale(request)
-        add_getURL(request)
-        decode.processInputs(request)
+        
+        if getattr(request, 'locale', None) is None:
+            request.locale = setup_locale(request)
+        
+        if not hasattr(request, 'getURL'):
+            add_getURL(request)

Modified: plone.z3cform/trunk/setup.py
===================================================================
--- plone.z3cform/trunk/setup.py	2010-02-14 22:24:30 UTC (rev 109070)
+++ plone.z3cform/trunk/setup.py	2010-02-15 05:35:58 UTC (rev 109071)
@@ -42,6 +42,7 @@
           'z3c.batching',
           'zope.i18n>=3.4',
           'zope.component',
+          'collective.monkeypatcher',
       ],
       extras_require = {
         'test': ['lxml']



More information about the checkins mailing list