[Zope3-checkins] CVS: Zope3/src/zope/app/form/browser - __init__.py:1.1 add.pt:1.1 add.py:1.1 addwizard.pt:1.1 addwizard.py:1.1 configure.zcml:1.1 display.pt:1.1 edit.pt:1.1 editview.py:1.1 editwizard.pt:1.1 editwizard.py:1.1 enumerated.py:1.1 interfaces.py:1.1 meta.zcml:1.1 metaconfigure.py:1.1 metadirectives.py:1.1 schemadisplay.py:1.1 subedit.pt:1.1 submit.py:1.1 vocabularywidget.py:1.1 vocabularywidget.zcml:1.1 widget.py:1.1 widgets.txt:1.1

Stephan Richter srichter at cosmos.phy.tufts.edu
Sat Mar 13 20:11:36 EST 2004


Update of /cvs-repository/Zope3/src/zope/app/form/browser
In directory cvs.zope.org:/tmp/cvs-serv23846/src/zope/app/form/browser

Added Files:
	__init__.py add.pt add.py addwizard.pt addwizard.py 
	configure.zcml display.pt edit.pt editview.py editwizard.pt 
	editwizard.py enumerated.py interfaces.py meta.zcml 
	metaconfigure.py metadirectives.py schemadisplay.py subedit.pt 
	submit.py vocabularywidget.py vocabularywidget.zcml widget.py 
	widgets.txt 
Log Message:
Moved zope.app.browser.form to zope.app.form.browser


=== Added File Zope3/src/zope/app/form/browser/__init__.py ===
#
# This file is necessary to make this directory a package.


=== Added File Zope3/src/zope/app/form/browser/add.pt ===
<html metal:use-macro="context/@@standard_macros/page">
  <body>
  <div metal:fill-slot="body">

  <div metal:define-macro="body">

    <form action="." tal:attributes="action request/URL" method="post"
          enctype="multipart/form-data">

      <div metal:define-macro="formbody">

        <h3 tal:condition="view/label"
            tal:content="view/label"
            metal:define-slot="heading"
            >Edit something</h3>

        <p tal:define="status view/update"
           tal:condition="status"
           tal:content="status" />

        <p tal:condition="view/errors" i18n:translate="">
          There are <strong tal:content="python:len(view.errors)"
                            i18n:name="num_errors">6</strong> input errors.
        </p>

        <div metal:define-slot="extra_info" tal:replace="nothing">
        </div>

        <div class="row" metal:define-slot="extra_top" tal:replace="nothing">
            <div class="label">Extra top</div>
            <div class="label"><input type="text" style="width:100%" /></div>
        </div>
        
        <div class="row" 
          metal:define-macro="widget_rows" tal:repeat="widget view/widgets">
          <div class="label" tal:content="structure widget/label">Name</div>
          <div class="field" tal:content="structure widget">
            <input type="text" style="width:100%"/>
          </div>
          <div class="error" tal:define="error widget/error"
            tal:condition="error" tal:content="structure error">
            The Error
          </div>
        </div>
        
        <div class="separator"></div>
        
        <div class="row"
            metal:define-slot="extra_bottom" tal:replace="nothing">
          <div class="label">Extra bottom</div>
          <div class="field"><input type="text" style="width:100%" /></div>
        </div>
        
        <div class="separator"></div>
        
      </div>
      <br/><br/>
      <div class="row">
        <div class="controls"><hr />
          <input type='submit' value='Refresh'
                 i18n:attributes='value refresh-button'>
          <input type="submit" name="UPDATE_SUBMIT" value=" Add "
                 tal:replace="structure context/renderAddButton|default"/>
        </div>
      </div>
     
      <div class="row" metal:define-slot="extra_buttons" tal:replace="nothing">
      </div>

      <div class="separator"></div>
    </form>
  </div>

  </div>
  </body>

</html>



=== Added File Zope3/src/zope/app/form/browser/add.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Add Form View class

$Id: add.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
import sys

from zope.app import zapi
from zope.app.event import publish
from zope.app.event.objectevent import ObjectCreatedEvent
from zope.app.form.utility import setUpWidgets, getWidgetsData
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.form.interfaces import IInputWidget, WidgetsError
from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.component import getAdapter
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.schema.interfaces import ValidationError
from zope.security.checker import defineChecker, NamesChecker
from zope.app.publisher.browser.globalbrowsermenuservice import \
    globalBrowserMenuService
from editview import EditView
from submit import Update

class AddView(EditView):
    """Simple edit-view base class.

    Subclasses should provide a schema attribute defining the schema
    to be edited.
    """

    def _setUpWidgets(self):
        setUpWidgets(self, self.schema, IInputWidget, names=self.fieldNames)

    def update(self):

        if self.update_status is not None:
            # We've been called before. Just return the previous result.
            return self.update_status

        if Update in self.request:

            self.update_status = ''
            try:
                data = getWidgetsData(self, self.schema, names=self.fieldNames)
                self.createAndAdd(data)
            except WidgetsError, errors:
                self.errors = errors
                self.update_status = _("An error occured.")
                return self.update_status

            self.request.response.redirect(self.nextURL())

        return self.update_status

    def create(self, *args, **kw):
        """Do the actual instantiation."""
        return self._factory(*args, **kw)

    def createAndAdd(self, data):
        """Add the desired object using the data in the data argument.

        The data argument is a dictionary with the data entered in the form.
        """

        args = []
        if self._arguments:
            for name in self._arguments:
                args.append(data[name])

        kw = {}
        if self._keyword_arguments:
            for name in self._keyword_arguments:
                if name in data:
                    kw[str(name)] = data[name]

        content = self.create(*args, **kw)
        adapted = getAdapter(content, self.schema, context=self.context)

        errors = []

        if self._set_before_add:
            for name in self._set_before_add:
                if name in data:
                    field = self.schema[name]
                    try:
                        field.set(adapted, data[name])
                    except ValidationError:
                        errors.append(sys.exc_info()[1])

        if errors:
            raise WidgetsError(*errors)

        publish(self.context, ObjectCreatedEvent(content))

        content = self.add(content)

        adapted = getAdapter(content, self.schema)

        if self._set_after_add:
            for name in self._set_after_add:
                if name in data:
                    field = self.schema[name]
                    try:
                        field.set(adapted, data[name])
                    except ValidationError:
                        errors.append(sys.exc_info()[1])

        if errors:
            raise WidgetsError(*errors)

        return content

    def add(self, content):
        return self.context.add(content)

    def nextURL(self):
        return self.context.nextURL()


def AddViewFactory(name, schema, label, permission, layer,
                   template, default_template, bases, for_,
                   fields, content_factory, arguments,
                   keyword_arguments, set_before_add, set_after_add,
                   menu=u'', usage=u''):

    s = zapi.getService(None, zapi.servicenames.Presentation)
    class_  = SimpleViewClass(
        template,
        used_for = schema, bases = bases
        )

    class_.schema = schema
    class_.label = label
    class_.fieldNames = fields
    class_._factory = content_factory
    class_._arguments = arguments
    class_._keyword_arguments = keyword_arguments
    class_._set_before_add = set_before_add
    class_._set_after_add = set_after_add

    class_.generated_form = ViewPageTemplateFile(default_template)

    if not usage and menu:
        usage = globalBrowserMenuService.getMenuUsage(menu)
    if not usage:
        # usage could be None
        usage = u''
    s.useUsage(usage)
    class_.usage = usage

    defineChecker(class_,
                  NamesChecker(
                    ("__call__", "__getitem__",
                     "browserDefault", "publishTraverse"),
                    permission,
                    )
                  )

    s.provideView(for_, name, IBrowserRequest, class_, layer)


=== Added File Zope3/src/zope/app/form/browser/addwizard.pt ===
<tal:block condition="view/update"/><html 
    metal:use-macro="context/@@standard_macros/page">

<body>
<div metal:fill-slot="body">

<div metal:define-macro="body">

  <form action="." tal:attributes="action request/URL" method="post"
        enctype="multipart/form-data">

    <div metal:define-macro="formbody">

      <h3 tal:condition="view/label"
          tal:content="view/label"
          metal:define-slot="heading"
          >Edit something</h3>

      <p tal:condition="view/feedback" tal:content="view/feedback">
        A feedback message to the user
      </p>

      <div tal:condition="view/errors">
         <ul>
            <li tal:repeat="error view/errors">
               <strong tal:content="error/__class__">
                  Error Type</strong>:
               <span tal:content="error">Error text</span>
            </li>
         </ul>
      </div>

      <div metal:define-slot="extra_info" tal:replace="nothing">
      </div>

      <div class="row" metal:define-slot="extra_top" tal:replace="nothing">
          <div class="label">Extra top</div>
          <div class="label"><input type="text" style="width:100%" /></div>
      </div>
      <div class="row"
           metal:define-macro="widget_rows" tal:repeat="widget view/widgets"
           tal:content="structure widget/row">
          <div class="label">Name</div>
          <div class="field"><input type="text" style="width:100%" /></div>
          <div class="error">Error message</div>
      </div>
      <div class="row"
           metal:define-slot="extra_bottom" tal:replace="nothing">
          <div class="label">Extra bottom</div>
          <div class="field"><input type="text" style="width:100%" /></div>
      </div>

    </div>

    <div class="row">
      <div class="controls">
        <!-- <input type="submit" value="Refresh" 
            i18n:attributes="value refresh-button" /> -->
        <input tal:condition="view/show_previous"
          type="submit" name="PREVIOUS_SUBMIT" value="Previous"
          i18n:attributes="value previous-button" /> 
        <input tal:condition="view/show_submit"
          type="submit" name="UPDATE_SUBMIT" value="Submit" 
          i18n:attributes="value submit-button"/>
        <input tal:condition="view/show_next"
          type="submit" name="NEXT_SUBMIT" value="Next"
          i18n:attributes="value next-button" /> 
      </div>
    </div>

    <div tal:replace="structure view/renderHidden">
      <!-- type=hidden input controls for passing state without session -->
      <input type="hidden" name="example" value="foo" />
    </div>

  </form>

</div>

</div>
</body>

</html>


=== Added File Zope3/src/zope/app/form/browser/addwizard.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Add Wizard View Classes

$Id: addwizard.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
import sys

from zope.app import zapi
from zope.app.event import publish
from zope.app.event.objectevent import ObjectCreatedEvent
from zope.app.form.utility import setUpWidgets
from zope.app.form.interfaces import WidgetsError, IInputWidget
from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.component import getAdapter
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.schema.interfaces import ValidationError
from zope.security.checker import defineChecker, NamesChecker
from editwizard import EditWizardView, WizardStorage

class AddWizardView(EditWizardView):
    """Multi-page add-view base class.

    Subclasses should provide a schema attribute defining the schema
    to be edited.
    """

    def _setUpWidgets(self):
        if self.use_session:
            # Need session for File upload fields
            raise NotImplementedError, 'Need a working ISessionDataManager'
        else:
            self.storage = WizardStorage(self.fieldNames, None)

        setUpWidgets(self, self.schema, IInputWidget, names=self.fieldNames)

    def create(self, *args, **kw):
        """Do the actual instantiation."""
        return self._factory(*args, **kw)

    def apply_update(self, data):
        """Add the desired object using the data in the data argument.

        The data argument is a dictionary with the data entered in the form.

        Issues a redirect to context.nextURL()

        Returns False, as per editview.apply_update
        """

        # This code originally from add.py's createAndAdd method

        args = []
        for name in self._arguments:
            args.append(data[name])

        kw = {}
        for name in self._keyword_arguments:
            if name in data:
                kw[str(name)] = data[name]

        content = self.create(*args, **kw)
        adapted = getAdapter(content, self.schema, context=self.context)

        errors = []

        for name in self._set_before_add:
            if name in data:
                field = self.schema[name]
                try:
                    field.set(adapted, data[name])
                except ValidationError:
                    errors.append(sys.exc_info()[1])

        if errors:
            raise WidgetsError(*errors)

        publish(self.context, ObjectCreatedEvent(content))

        content = self.context.add(content)

        adapted = getAdapter(content, self.schema)

        for name in self._set_after_add:
            if name in data:
                field = self.schema[name]
                try:
                    field.set(adapted, data[name])
                except ValidationError:
                    errors.append(sys.exc_info()[1])

        if errors:
            raise WidgetsError(*errors)

        self.request.response.redirect(self.context.nextURL())
        return False


def AddWizardViewFactory(
    name, schema, permission, layer, panes, fields,
    template, default_template, bases, for_, content_factory, arguments,
    keyword_arguments, set_before_add, set_after_add, use_session=True):

    class_  = SimpleViewClass(template, used_for = schema, bases = bases)

    class_.schema = schema
    class_.panes = panes
    class_.fieldNames = fields
    class_._factory = content_factory
    class_._arguments = arguments or []
    class_._keyword_arguments = keyword_arguments or []
    class_._set_before_add = set_before_add or []
    class_._set_after_add = set_after_add or []
    class_.use_session = use_session

    class_.generated_form = ViewPageTemplateFile(default_template)

    defineChecker(class_,
                  NamesChecker(
                    ("__call__", "__getitem__", "browserDefault"),
                    permission,
                    )
                  )

    s = zapi.getService(None, zapi.servicenames.Presentation)
    s.provideView(for_, name, IBrowserRequest, class_, layer)


=== Added File Zope3/src/zope/app/form/browser/configure.zcml ===
<configure xmlns="http://namespaces.zope.org/zope">
   
  <!-- Core display widgets -->

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"     
      for="zope.schema.interfaces.IField"
      provides="zope.app.form.interfaces.IDisplayWidget"
      factory=".widget.DisplayWidget"
      permission="zope.Public"
      />
      
  <!-- Core edit widgets -->
   
  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.ITextLine"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.TextWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IText"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.TextAreaWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.ISourceText"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.TextAreaWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IBytesLine"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.BytesWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IBytes"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.FileWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IASCII"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.BytesAreaWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IInt"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.IntWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IFloat"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.FloatWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IDatetime"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.DatetimeWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IDate"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.DateWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IBool"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.CheckBoxWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.ITuple"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.TupleSequenceWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IList"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.ListSequenceWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IPassword"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".widget.PasswordWidget"
      permission="zope.Public"
      />

  <!-- Widgets for enumerated field flavours -->

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IEnumeratedTextLine"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".enumerated.EnumeratedTextWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IEnumeratedInt"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".enumerated.EnumeratedIntWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IEnumeratedFloat"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".enumerated.EnumeratedFloatWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IEnumeratedDatetime"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".enumerated.EnumeratedDatetimeWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IEnumeratedDate"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".enumerated.EnumeratedDateWidget"
      permission="zope.Public"
      />
      
  <!-- Vocabulary fields share special widget factories that redirect
       to the vocabularies they reference. -->

  <!-- Single selection -->

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IVocabularyField"
      provides="zope.app.form.interfaces.IDisplayWidget"
      factory=".vocabularywidget.VocabularyFieldDisplayWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IVocabularyField"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".vocabularywidget.VocabularyFieldEditWidget"
      permission="zope.Public"
      />

  <!-- Bags -->

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IVocabularyBagField"
      provides="zope.app.form.interfaces.IDisplayWidget"
      factory=".vocabularywidget.VocabularyBagFieldDisplayWidget"
      permission="zope.Public"
      />
      
  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IVocabularyBagField"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".vocabularywidget.VocabularyBagFieldEditWidget"
      permission="zope.Public"
      />

  <!-- Lists -->

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IVocabularyListField"
      provides="zope.app.form.interfaces.IDisplayWidget"
      factory=".vocabularywidget.VocabularyListFieldDisplayWidget"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IVocabularyListField"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".vocabularywidget.VocabularyListFieldEditWidget"
      permission="zope.Public"
      />

  <!-- Sets -->

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IVocabularySetField"
      provides="zope.app.form.interfaces.IDisplayWidget"
      factory=".vocabularywidget.VocabularySetFieldDisplayWidget"
      permission="zope.Public"
      />
	  
  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IVocabularySetField"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".vocabularywidget.VocabularySetFieldEditWidget"
      permission="zope.Public"
      />

  <!-- Unique lists -->

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IVocabularyUniqueListField"
      provides="zope.app.form.interfaces.IDisplayWidget"
      factory=".vocabularywidget.VocabularyUniqueListFieldDisplayWidget"
      permission="zope.Public"
      />
      
  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      for="zope.schema.interfaces.IVocabularyUniqueListField"
      provides="zope.app.form.interfaces.IInputWidget"
      factory=".vocabularywidget.VocabularyUniqueListFieldEditWidget"
      permission="zope.Public"
      />
	  
  <!-- implementation support for vocabulary field widgets -->
  
  <include file="vocabularywidget.zcml" />  

</configure>


=== Added File Zope3/src/zope/app/form/browser/display.pt ===
<html metal:use-macro="views/standard_macros/page">
<body>
<div metal:fill-slot="body">
<div metal:define-macro="body">

  <div metal:define-macro="formbody">

    <h3 tal:condition="view/label"
        tal:content="view/label"
        metal:define-slot="heading"
        >Display something</h3>

    <div metal:define-slot="extra_info" tal:replace="nothing">
    </div>

    <div class="row"
         metal:define-slot="extra_top" tal:replace="nothing">
        <div class="label">Extra top</div>
        <div class="field"><input type="text" style="width:100%" /></div>
    </div>
        <div class="row" metal:define-macro="widget_rows" 
	    		         tal:repeat="widget view/widgets">
             <div class="label" tal:content="structure widget/label">Name</div>
             <div class="field" tal:content="structure widget">
                    <input type="text" style="width:100%"/>
             </div>
             <div class="error" tal:define="error widget/error"
			 	tal:condition="error" tal:content="structure error">
				The Error
             </div>
    </div>
    <div class="row"
         metal:define-slot="extra_bottom" tal:replace="nothing">
        <div class="label">Extra bottom</div>
        <div class="field"><input type="text" style="width:100%" /></div>
    </div>

  </div>

</div>
</div>
</body>
</html>


=== Added File Zope3/src/zope/app/form/browser/edit.pt ===
<tal:tag condition="view/update"/>
<html metal:use-macro="views/standard_macros/page">
  <body>
  <div metal:fill-slot="body">

  <div metal:define-macro="body">

    <form action="." tal:attributes="action request/URL" method="POST"
          enctype="multipart/form-data">

      <div metal:define-macro="formbody">

        <h3 tal:condition="view/label"
            tal:content="view/label"
            metal:define-slot="heading"
            >Edit something</h3>

        <p tal:define="status view/update"
           tal:condition="status"
           tal:content="status" />

         <p tal:condition="view/errors" i18n:translate="">
           There are  <strong tal:content="python:len(view.errors)"
                              i18n:name="num_errors">6</strong> input errors.
        </p>

        <div metal:define-slot="extra_info" tal:replace="nothing">
        </div>

        <div class="row"
             metal:define-slot="extra_top" tal:replace="nothing">
            <div class="label">Extra top</div>
            <div class="field"><input type="text" style="width:100%" /></div>
        </div>
        <div class="row" metal:define-macro="widget_rows" 
	    		         tal:repeat="widget view/widgets">
             <div class="label" tal:content="structure widget/label">Name</div>
             <div class="field" tal:content="structure widget">
                    <input type="text" style="width:100%"/>
             </div>
             <div class="error" tal:define="error widget/error"
			 	tal:condition="error" tal:content="structure error">
				The Error
             </div>
        </div>
        <div class="separator"></div>
        <div class="row"
             metal:define-slot="extra_bottom" tal:replace="nothing">
            <div class="label">Extra bottom</div>
            <div class="field"><input type="text" style="width:100%" /></div>
        </div>
        <div class="separator"></div>
      </div>

      <div class="row">
        <div class="controls">
          <input type="submit" value="Refresh" 
              i18n:attributes="value refresh-button" />
          <input type="submit" name="UPDATE_SUBMIT" value="Change" 
              i18n:attributes="value submit-button"/>
        </div>
      </div>
      <div class="row" metal:define-slot="extra_buttons" tal:replace="nothing">
      </div>

      <div class="separator"></div>

    </form>

  </div>

  </div>
  </body>

</html>


=== Added File Zope3/src/zope/app/form/browser/editview.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Edit View Classes

$Id: editview.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
from datetime import datetime

from zope.schema import getFieldNamesInOrder
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.security.checker import defineChecker, NamesChecker
from zope.component import getAdapter

from zope.app import zapi
from zope.app.event import publish
from zope.app.event.objectevent import ObjectModifiedEvent
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.form.interfaces import WidgetsError
from zope.app.location.interfaces import ILocation
from zope.app.location import LocationProxy
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
from zope.app.publisher.browser import BrowserView
from zope.app.publisher.browser.globalbrowsermenuservice import \
    globalBrowserMenuService

from zope.app.form.utility import setUpEditWidgets, applyWidgetsChanges
from zope.app.form.browser.submit import Update


class EditView(BrowserView):
    """Simple edit-view base class

    Subclasses should provide a schema attribute defining the schema
    to be edited.
    """

    errors = ()
    update_status = None
    label = ''

    # Fall-back field names computes from schema
    fieldNames = property(lambda self: getFieldNamesInOrder(self.schema))
    # Fall-back template
    generated_form = ViewPageTemplateFile('edit.pt')

    def __init__(self, context, request):
        super(EditView, self).__init__(context, request)
        self._setUpWidgets()

    def _setUpWidgets(self):
        adapted = getAdapter(self.context, self.schema)
        if adapted is not self.context:
            if not ILocation.providedBy(adapted):
                adapted = LocationProxy(adapted)
            adapted.__parent__ = self.context
        self.adapted = adapted
        setUpEditWidgets(self, self.schema, source=self.adapted, 
                         names=self.fieldNames)

    def setPrefix(self, prefix):
        for widget in self.widgets():
            widget.setPrefix(prefix)

    def widgets(self):
        return [getattr(self, name+'_widget')
                for name in self.fieldNames]

    def changed(self):
        # This method is overridden to execute logic *after* changes
        # have been made.
        pass

    def update(self):
        if self.update_status is not None:
            # We've been called before. Just return the status we previously
            # computed.
            return self.update_status

        status = ''

        content = self.adapted

        if Update in self.request:
            changed = False
            try:
                changed = applyWidgetsChanges(self, self.schema,
                    target=content, names=self.fieldNames)
                # We should not generate events when an adapter is used.
                # That's the adapter's job.
                if changed and self.context is self.adapted:
                    publish(content, ObjectModifiedEvent(content))
            except WidgetsError, errors:
                self.errors = errors
                status = _("An error occured.")
            else:
                setUpEditWidgets(self, self.schema, source=self.adapted,
                                 ignoreStickyValues=True, 
                                 names=self.fieldNames)
                if changed:
                    self.changed()
                    formatter = self.request.locale.dates.getFormatter(
                        'dateTime', 'medium')
                    status = _("Updated on ${date_time}")
                    status.mapping = {'date_time': formatter.format(
                        datetime.utcnow())}

        self.update_status = status
        return status


def EditViewFactory(name, schema, label, permission, layer,
                    template, default_template, bases, for_, fields,
                    fulledit_path=None, fulledit_label=None, menu=u'',
                    usage=u''):
    s = zapi.getService(None, zapi.servicenames.Presentation)
    class_ = SimpleViewClass(template, used_for=schema, bases=bases)
    class_.schema = schema
    class_.label = label
    class_.fieldNames = fields

    class_.fulledit_path = fulledit_path
    if fulledit_path and (fulledit_label is None):
        fulledit_label = "Full edit"

    class_.fulledit_label = fulledit_label

    class_.generated_form = ViewPageTemplateFile(default_template)

    if not usage and menu:
        usage = globalBrowserMenuService.getMenuUsage(menu)
    if not usage:
        # usage could be None
        usage = u''
    s.useUsage(usage)
    class_.usage = usage

    defineChecker(class_,
                  NamesChecker(("__call__", "__getitem__",
                                "browserDefault", "publishTraverse"),
                               permission))

    s.provideView(for_, name, IBrowserRequest, class_, layer)


=== Added File Zope3/src/zope/app/form/browser/editwizard.pt ===
<tal:tag condition="view/update"
/><html metal:use-macro="views/standard_macros/page">
  <body>
  <div metal:fill-slot="body">

  <div metal:define-macro="body">

    <form action="." tal:attributes="action request/URL" method="POST"
          enctype="multipart/form-data" >

      <div metal:define-macro="formbody">

        <h3 tal:condition="view/label"
            tal:content="view/label"
            metal:define-slot="heading"
            >Edit something</h3>

        <p tal:condition="view/feedback" tal:content="view/feedback" />

        <div metal:define-slot="extra_info" tal:replace="nothing">
        </div>

        <div class="row"
             metal:define-slot="extra_top" tal:replace="nothing">
            <div class="label">Extra top</div>
            <div class="field"><input type="text" style="width:100%" /></div>
        </div>
        <div class="row" metal:define-macro="widget_rows" 
	    		         tal:repeat="widget view/widgets">
             <div class="label" tal:content="structure widget/label">Name</div>
             <div class="field" tal:content="structure widget">
                    <input type="text" style="width:100%"/>
             </div>
             <div class="error" tal:define="error widget/error"
			 	tal:condition="error" tal:content="structure error">
				The Error
             </div>
        </div>
        <div tal:replace="structure view/renderHidden">
          <!-- type=hidden input controls for passing state without session -->
          <input type="hidden" name="example" value="foo" />
        </div>
        <div class="row"
             metal:define-slot="extra_bottom" tal:replace="nothing">
            <div class="label">Extra bottom</div>
            <div class="field"><input type="text" style="width:100%" /></div>
        </div>

      </div>

      <div class="row">
        <div class="controls">
          <!-- <input type="submit" value="Refresh" 
              i18n:attributes="value refresh-button" /> -->
          <input tal:condition="view/show_previous"
            type="submit" name="PREVIOUS_SUBMIT" value="Previous"
            i18n:attributes="value previous-button" /> 
          <input tal:condition="view/show_submit"
            type="submit" name="UPDATE_SUBMIT" value="Submit" 
            i18n:attributes="value submit-button"/>
          <input tal:condition="view/show_next"
            type="submit" name="NEXT_SUBMIT" value="Next"
            i18n:attributes="value next-button" /> 
        </div>
      </div>

    </form>

  </div>

  </div>
  </body>

</html>


=== Added File Zope3/src/zope/app/form/browser/editwizard.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Edit Wizard View Classes

$Id: editwizard.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""

from zope.component import getAdapter
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.security.checker import defineChecker, NamesChecker

from zope.app import zapi
from zope.app.event import publish
from zope.app.event.objectevent import ObjectModifiedEvent
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.location.interfaces import ILocation
from zope.app.location import LocationProxy


from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.app.publisher.browser.globalbrowsermenuservice import \
     globalBrowserMenuService

from editview import EditView
from submit import Next, Previous, Update
from zope.app.form.interfaces import WidgetInputError, WidgetsError
from zope.app.form.utility \
        import setUpEditWidgets, getWidgetsData, applyWidgetsChanges


PaneNumber = 'CURRENT_PANE_IDX'

# TODO: Needs to be persistent aware for session (?)
class WizardStorage(dict):
    def __init__(self, fields, content):
        super(WizardStorage, self).__init__(self)
        if content:
            for k in fields:
                self[k] = getattr(content,k)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError, key

    def __setattr__(self, key, value):
        self[key] = value


class EditWizardView(EditView):

    def _setUpWidgets(self):
        adapted = getAdapter(self.context, self.schema)
        if adapted is not self.context:
            if not ILocation.providedBy(adapted):
                adapted = LocationProxy(adapted)
            adapted.__parent__ = self.context
        self.adapted = adapted

        if self.use_session:
            # Need session for File upload fields
            raise NotImplementedError, \
                'Cannot be implemented until we have an ISessionDataManager'
        else:
            self.storage = WizardStorage(self.fieldNames, adapted)

        # Add all our widgets as attributes on this view
        setUpEditWidgets(self, self.schema, source=self.storage,
                         names=self.fieldNames)

    def widgets(self):
        return [getattr(self, name+'_widget')
            for name in self.currentPane().names
            ]

    _current_pane_idx = 0

    def currentPane(self):
        return self.panes[self._current_pane_idx]

    _update_called = 0

    # Message rendered at the top of the form, probably set by update()
    feedback = u''

    def update(self):
        '''Called before rendering each pane. It is responsible
        for extracting data into temporary storage, and selecting
        which pane should be rendered.
        '''
        # Calling twice does nothing
        if self._update_called:
            return
        self._update_called = 1

        # Determine the current pane
        if PaneNumber in self.request:
            self._current_pane_idx = int(self.request[PaneNumber])
            assert self._current_pane_idx >= 0
            assert self._current_pane_idx < len(self.panes)
        else:
            # First page
            self._current_pane_idx = 0
            self.errors = {}
            self.label = self.currentPane().label
            self._choose_buttons()
            return

        # Validate the current pane, and set self.errors
        try:
            names = self.currentPane().names
            data = getWidgetsData(self, self.schema, names=names)
            self.errors = {}
        except WidgetsError, errors:
            x = {}
            for k, label, msg in errors:
                x[k] = msg
            self.errors = x
        else:

            self.storage.update(data)

            if Next in self.request:
                self._current_pane_idx += 1
                assert self._current_pane_idx < len(self.panes)
            elif Previous in self.request:
                self._current_pane_idx -= 1
                assert self._current_pane_idx >= 0
            elif Update in self.request:
                if not self.use_session:
                    # Data from panes other than the current one is still
                    # stuck in request
                    self.storage.update(getWidgetsData(
                        self, self.schema, names=self.fieldNames))
                if self.apply_update(self.storage):
                    self.feedback = _(u'No changes to save')
                else:
                    self.feedback = _(u'Changes saved')

        # Set the current label
        self.label = self.currentPane().label

        self._choose_buttons()

    def _choose_buttons(self):
        '''Determine what buttons appear when we render the current pane'''

        # The submit button appears if every field on every pane except the
        # current one has valid input or a valid default value.
        # This is almost always the case for edit forms.
        try:
            for k in self.fieldNames:
                if k not in self.currentPane().names:
                    getattr(self, k).getInputValue()
            self.show_submit = 1
        except WidgetInputError:
            self.show_submit = 0

        self.show_next = (self._current_pane_idx < len(self.panes) - 1)

        self.show_previous = self._current_pane_idx > 0

    def apply_update(self, storage):
        ''' Save changes to our content object '''
        for k,v in storage.items():
            getattr(self,k).setRenderedValue(v)
        content = self.adapted
        changed = applyWidgetsChanges(self, self.schema, target=content,
                names=self.fieldNames)
        # We should not generate events when an adapter is used.
        # That's the adapter's job
        if changed and self.context is self.adapted:
            publish(content, ObjectModifiedEvent(content))
        return not changed

    def renderHidden(self):
        ''' Render state as hidden fields. Also render hidden fields to
            propagate self.storage if we are not using the session to do this.
        '''
        olist = []
        out = olist.append

        # the index of the pane being rendered needs to be propagated
        out('<input class="hiddenType" type="hidden" name="%s" value="%d" />'%(
            PaneNumber, self._current_pane_idx
            ))

        if self.use_session:
            # Need to output a unique key as a hidden field to identity this
            # particular wizard. We use this to ensure data for this view
            # doesn't conflict with other wizards in progress in other
            # browser windows.
            # Otherwise, no more state to propagate
            raise NotImplementedError, 'use_session'

        else:
            current_fields = self.currentPane().names
            for k in self.fieldNames:
                if k not in current_fields:
                    widget = getattr(self, k)
                    out(widget.hidden())
            return ''.join(olist)


def EditWizardViewFactory(name, schema, permission, layer,
                    panes, fields, template, default_template, bases, for_,
                    menu=u'', usage=u'', use_session=True):
    # XXX What about the __implements__ of the bases?
    class_ = SimpleViewClass(template, used_for=schema, bases=bases)
    class_.schema = schema
    class_.panes = panes
    class_.fieldNames = fields
    class_.use_session = use_session

    class_.generated_form = ViewPageTemplateFile(default_template)

    class_.usage = usage or (
        menu and globalBrowserMenuService.getMenuUsage(menu))

    defineChecker(
        class_,
        NamesChecker(("__call__", "__getitem__", "browserDefault"), permission)
        )

    s = zapi.getService(None, zapi.servicenames.Presentation)
    s.provideView(for_, name, IBrowserRequest, class_, layer)




=== Added File Zope3/src/zope/app/form/browser/enumerated.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Widgets for enumerated field flavours.

$Id: enumerated.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""

from widget import TextWidget, IntWidget, FloatWidget, \
     DatetimeWidget, DateWidget

__metaclass__ = type

class Enumerated:
    """Mixin for enumerated field widgets
    """
    def __init__(self, *args):
        super(Enumerated, self).__init__(*args)
        field = self.context
        if field.allowed_values is not None:
            values = []
            # if field is optional and missing_value isn't in
            # allowed_values, add an additional option at top to represent
            # field.missing_value
            if not field.required and \
                field.missing_value not in field.allowed_values:
                values.append(field.missing_value)
            values += list(field.allowed_values)
            self.__values = values

    def __call__(self):
        selected = self._showData()
        result = ['<select id="%s" name="%s">' % (self.name, self.name)]
        for value in self.__values:
            unconverted = self._unconvert(value)
            selectedStr = unconverted == selected and ' selected' or ''
            result.append('<option value="%s"%s>%s</option>' % \
                   (unconverted, selectedStr, unconverted))
        result.append('</select>')
        return '\n\t'.join(result)

class EnumeratedTextWidget(Enumerated, TextWidget):
    """EnumeratedText widget (for TextLines)
    """

class EnumeratedIntWidget(Enumerated, IntWidget):
    """EnumeratedInt widget
    """

class EnumeratedFloatWidget(Enumerated, FloatWidget):
    """EnumeratedFloat widget
    """

class EnumeratedDatetimeWidget(Enumerated, DatetimeWidget):
    """EnumeratedDatetime widget
    """

class EnumeratedDateWidget(Enumerated, DateWidget):
    """EnumeratedDate widget
    """


=== Added File Zope3/src/zope/app/form/browser/interfaces.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""
$Id: interfaces.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
from zope.interface import Interface
from zope.app.form.interfaces import IWidget

class IAddFormCustomization(Interface):
    """This interface defined methods of add forms that can be overridden

    Classes supplied when defining add forms may need to override some
    of these methods.

    In particular, when the context of an add form is not an IAdding,
    a subclass needs to override ``nextURL`` and one of ``add`` or
    ``createAndAdd``.

    To see how all this fits together, here's pseudo code for the
    update() method of the form:

    def update(self):
        data = <get data from widgets> # a dict
        self.createAndAdd(data)
        self.request.response.redirect(self.nextURL())

    def createAndAdd(self, data):
        content = <create the content from the data>
        content = self.add(content) # content wrapped in some context
        <set after-add attributes on content>

    """

    def createAndAdd(data):
        """Create a new object from the given data and the resulting object.

        The data argument is a dictionary with values supplied by the form.

        If any user errors occur, they should be collected into a list
        and raised as a WidgetsError.

        (For the default implementation, see pseudo-code in class docs.)
        """

    def add(content):
        """Add the given content

        This method is overridden when the context of the add form is
        not an IAdding.  In this case, the class that customizes the
        form must take over adding the object.

        The content should be returned wrapped in the context of the
        object that it was added to.

        The default implementation returns self.context.add(content),
        i.e. it delegates to the IAdding view.
        """

    def nextURL():
        """Return the URL to be displayed after the add operation.

        This can be relative to the view's context.

        The default implementation returns self.context.nextURL(),
        i.e. it delegates to the IAdding view.
        """


class IBrowserWidget(IWidget):
    """A field widget contains all the properties that are required
       to represent a field. Properties include css_sheet,
       default value and so on.
    """

    def __call__(): # XXX promote to IWidget?
        """Render the widget
        """

    def hidden():
        """Render the widget as a hidden field
        """

    def label():
        """Render a label tag"""

    def error(): # XXX promote to IWidget?
        """Render the validation error for the widget, or return
        an empty string if no error"""

    def row():
        """Render the widget as two or three div elements,
           for the label, the field and possibly the validation error

        For example:
          <div class="label">label</div><div class="field">field</div>
          <div class="error">Validation error message</div>
        """


class IFormCollaborationView(Interface):
    """Views that collaborate to create a single form

    When a form is applied, the changes in the form need to
    be applied to individual views, which update objects as
    necessary.
    """

    def __call__():
        """Render the view as a part of a larger form.

        Form input elements should be included, prefixed with the
        prefix given to setPrefix.

        'form' and 'submit' elements should not be included. They
        will be provided for the larger form.
        """

    def setPrefix(prefix):
        """Set the prefix used for names of input elements

        Element names should begin with the given prefix,
        followed by a dot.
        """

    def update():
        """Update the form with data from the request.
        """


class IVocabularyQueryView(Interface):
    """View support for IVocabularyQuery objects.

    Implementations of this interface are used by vocabulary field
    edit widgets to support query and result presentations.
    """

    def setName(name):
        """Set the name used to compute the form field names.

        Form field names should be the given name, or additional name
        components separated by dots may be appended if multiple form
        fields are needed.

        This method will be called after the IVocabularyQueryView has
        been created and before performAction() is called.
        """

    def setWidget(widget):
        """Set the widget using this query view.

        This allows the query view to take advantage of rendering
        helper methods made available by the widget.

        This method will be called after the IVocabularyQueryView has
        been created and before performAction() is called.
        """

    def performAction(value):
        """Perform any action indicated by any submit buttons in the
        sub-widget.

        'value' is the current value of the field.  Submit actions may
        cause the value to be modified.  If so, the new value should
        be returned; otherwise the old value should be returned.

        Actions should only be performed if a submit button provided
        by the view was selected.

        This method will be called after setName() and setWidget() and
        before renderInput() or renderResults().
        """

    def renderInput():
        """Return a rendering of the input portion of the widget."""

    def renderResults(value):
        """Return a rendering of the results portion of the widget.

        'value' is the current value represented by the widget.
        """


=== Added File Zope3/src/zope/app/form/browser/meta.zcml ===
<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:meta="http://namespaces.zope.org/meta"
    >

  <meta:directives namespace="http://namespaces.zope.org/browser">

    <meta:complexDirective
        name="addwizard"
        schema=".metadirectives.IAddWizardDirective"
        handler=".metaconfigure.AddWizardDirective"
        >

      <meta:subdirective
          name="pane"
          schema=".metadirectives.IPaneSubdirective"
          />

    </meta:complexDirective>

    <meta:complexDirective
        name="editwizard"
        schema=".metadirectives.IEditWizardDirective"
        handler=".metaconfigure.EditWizardDirective"
        >

      <meta:subdirective
          name="pane"
          schema=".metadirectives.IPaneSubdirective"
          />

    </meta:complexDirective>

    <meta:complexDirective
        name="editform"
        schema=".metadirectives.IEditFormDirective"
        handler=".metaconfigure.EditFormDirective"
        />

    <meta:complexDirective
        name="subeditform"
        schema=".metadirectives.ISubeditFormDirective"
        handler=".metaconfigure.SubeditFormDirective"
        />

    <meta:complexDirective
        name="addform"
        schema=".metadirectives.IAddFormDirective"
        handler=".metaconfigure.AddFormDirective"
        />

    <meta:complexDirective
        name="schemadisplay"
        schema=".metadirectives.ISchemaDisplayDirective"
        handler=".metaconfigure.SchemaDisplayDirective"
        />

  </meta:directives>

</configure>


=== Added File Zope3/src/zope/app/form/browser/metaconfigure.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""
$Id: metaconfigure.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""

__metaclass__ = type

import os

from zope.configuration.exceptions import ConfigurationError

from zope.schema import getFieldNamesInOrder
from zope.app.container.interfaces import IAdding
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.publisher.browser.globalbrowsermenuservice import \
     menuItemDirective

from add import AddView, AddViewFactory
from editview import EditView, EditViewFactory
from addwizard import AddWizardView, AddWizardViewFactory
from editwizard import EditWizardView, EditWizardViewFactory
from schemadisplay import DisplayView, DisplayViewFactory

class BaseFormDirective:

    # to be overriden by the subclasses
    view = None
    default_template = None

    # default basic information
    for_ = None
    layer = 'default'
    permission = 'zope.Public'
    template = None
    class_ = None

    # default form information
    title = None
    label = None
    menu = None
    fields = None

    def __init__(self, _context, **kwargs):
        self._context = _context
        for key, value in kwargs.items():
            if not (value is None and hasattr(self, key)):
                setattr(self, key, value)
        self._normalize()

    def _normalize(self):
        if self.for_ is None:
            self.for_ = self.schema

        if self.class_ is None:
            self.bases = (self.view,)
        else:
            self.bases = (self.class_, self.view)

        if self.template is not None:
            self.template = os.path.abspath(str(self.template))
            if not os.path.isfile(self.template):
                raise ConfigurationError("No such file", self.template)
        else:
            self.template = self.default_template

        self.names = getFieldNamesInOrder(self.schema)

        if self.fields:
            for name in self.fields:
                if name not in self.names:
                    raise ValueError("Field name is not in schema",
                                     name, self.schema)
        else:
            self.fields = self.names

    def _args(self):
        return (self.name, self.schema, self.label, self.permission,
                self.layer, self.template, self.default_template,
                self.bases, self.for_, self.fields)

    def _discriminator(self):
        return ('view', self.for_, self.name, IBrowserRequest,
                self.layer)

class Pane:
    ''' Holder for information about a Pane of a wizard '''
    # TODO: Add more funky stuff to each pane, such as a validator
    def __init__(self, field_names, label):
        self.names = field_names
        self.label = label


class BaseWizardDirective(BaseFormDirective):

    # default wizard information
    description = None
    use_sessions = True

    def __init__(self, _context, **kwargs):
        super(BaseWizardDirective, self).__init__(_context, **kwargs)
        self.panes = []

    def _args(self):
        return (self.name, self.schema, self.permission, self.layer,
                self.panes, self.fields, self.template, self.default_template,
                self.bases, self.for_)

    def pane(self, _context, fields, label=''):
        for f in fields:
            if f not in self.fields:
                raise ValueError(
                    'Field name is not in schema',
                    f, self.schema
                    )
        self.panes.append(Pane(fields, label))

class AddFormDirective(BaseFormDirective):

    view = AddView
    default_template = 'add.pt'
    usage = None
    for_ = IAdding

    # default add form information
    description = None
    content_factory = None
    arguments = None
    keyword_arguments = None
    set_before_add = None
    set_after_add = None

    def _handle_menu(self):
        if self.menu or self.title:
            if (not self.menu) or (not self.title):
                raise ValueError("If either menu or title are specified, "
                                 "they must both be specified")
            # XXX why no self.schema in for as in EditFormDirective
            menuItemDirective(
                self._context, self.menu, self.for_, '@@' + self.name,
                self.title, permission=self.permission,
                description=self.description)

    def _handle_arguments(self, leftover=None):
        schema = self.schema
        fields = self.fields
        arguments = self.arguments
        keyword_arguments = self.keyword_arguments
        set_before_add = self.set_before_add
        set_after_add = self.set_after_add

        if leftover is None:
            leftover = fields

        if arguments:
            missing = [n for n in arguments if n not in fields]
            if missing:
                raise ValueError("Some arguments are not included in the form",
                                 missing)
            optional = [n for n in arguments if not schema[n].required]
            if optional:
                raise ValueError("Some arguments are optional, use"
                                 " keyword_arguments for them",
                                 optional)
            leftover = [n for n in leftover if n not in arguments]

        if keyword_arguments:
            missing = [n for n in keyword_arguments if n not in fields]
            if missing:
                raise ValueError(
                    "Some keyword_arguments are not included in the form",
                    missing)
            leftover = [n for n in leftover if n not in keyword_arguments]

        if set_before_add:
            missing = [n for n in set_before_add if n not in fields]
            if missing:
                raise ValueError(
                    "Some set_before_add are not included in the form",
                    missing)
            leftover = [n for n in leftover if n not in set_before_add]

        if set_after_add:
            missing = [n for n in set_after_add if n not in fields]
            if missing:
                raise ValueError(
                    "Some set_after_add are not included in the form",
                    missing)
            leftover = [n for n in leftover if n not in set_after_add]

            self.set_after_add += leftover

        else:
            self.set_after_add = leftover

    def __call__(self):
        self._handle_menu()
        self._handle_arguments()

        self._context.action(
            discriminator=self._discriminator(),
            callable=AddViewFactory,
            args=self._args()+(self.content_factory, self.arguments,
                                 self.keyword_arguments,
                                 self.set_before_add, self.set_after_add),
            kw={'menu': self.menu, 'usage': self.usage},
            )

class EditFormDirective(BaseFormDirective):

    view = EditView
    default_template = 'edit.pt'
    usage = None
    title = 'Edit'

    def _handle_menu(self):
        if self.menu:
            menuItemDirective(
                self._context, self.menu, self.for_ or self.schema,
                '@@' + self.name, self.title, permission=self.permission)

    def __call__(self):
        self._handle_menu()
        self._context.action(
            discriminator=self._discriminator(),
            callable=EditViewFactory,
            args=self._args(),
            kw={'menu': self.menu, 'usage': self.usage},
        )

class SubeditFormDirective(BaseFormDirective):

    view = EditView
    default_template = 'subedit.pt'

    # default subedit form directive
    fulledit_path = None
    fulledit_label = None

    def __call__(self):
        self._context.action(
            discriminator = self._discriminator(),
            callable = EditViewFactory,
            args = self._args()+(self.fulledit_path, self.fulledit_label),
            )

class AddWizardDirective(BaseWizardDirective, AddFormDirective):

    view = AddWizardView
    default_template = 'addwizard.pt'
    use_session = False

    def __call__(self):
        self._handle_menu()

        all_fields = self.fields
        leftover = []
        for pane in self.panes:
            leftover.extend(pane.names)
        self.fields = leftover[:]
        self._handle_arguments(leftover)
        self.fields = all_fields

        self._context.action(
            discriminator = self._discriminator(),
            callable = AddWizardViewFactory,
            args = self._args()+(self.content_factory, self.arguments,
                                 self.keyword_arguments, self.set_before_add,
                                 self.set_after_add, self.use_session)
            )

class EditWizardDirective(BaseWizardDirective, EditFormDirective):

    view = EditWizardView
    default_template = 'editwizard.pt'

    def __call__(self):
        self._handle_menu()
        self._context.action(
            discriminator = self._discriminator(),
            callable = EditWizardViewFactory,
            args = self._args()+(self.menu, self.usage, self.use_session)
            )

class SchemaDisplayDirective(EditFormDirective):

    view = DisplayView
    default_template = 'display.pt'

    def __call__(self):
        self._handle_menu()
        self._context.action(
            discriminator = self._discriminator(),
            callable = DisplayViewFactory,
            args = self._args()+(self.menu, self.usage)
            )


=== Added File Zope3/src/zope/app/form/browser/metadirectives.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""
$Id: metadirectives.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""

from zope.interface import Interface
from zope.configuration.fields import GlobalObject, Tokens, Path, \
     Bool, PythonIdentifier, MessageID
from zope.schema import Text, TextLine, Id
from zope.app.publisher.interfaces.browser import IUsage

class ICommonInformation(IUsage):
    """
    Common information for all successive directives
    """

    name = TextLine(
        title=u"Name",
        description=u"The name of the generated view.",
        required=True
        )

    schema = GlobalObject(
        title=u"Schema",
        description=u"The schema from which the form is generated.",
        required=True
        )

    for_ = GlobalObject(
        title=u"Interface",
        description=u"""
        The interface this page (view) applies to.

        The view will be for all objects that implement this
        interface. The schema is used if the for attribute is not
        specified.

        If the for attribute is specified, then the objects views must
        implement or be adaptable to the schema.""",
        required=False
        )

    permission = Id(
        title=u"Permission",
        description=u"The permission needed to use the view.",
        required=True
        )

    layer = TextLine(
        title=u"Layer",
        description=u"The later the view is in. Default: 'default'",
        required=False
        )

    template = Path(
        title=u"Template",
        description=u"An alternate template to use for the form.",
        required=False
        )

    class_ = GlobalObject(
        title=u"Class",
        description=u"""
        A class to provide custom widget definitions or methods to be
        used by a custom template.

        This class is used as a mix-in class. As a result, it needn't
        subclass any special classes, such as BrowserView.""",
        required=False
        )


class ICommonFormInformation(ICommonInformation):
    """
    Common information for browser forms
    """

    label = MessageID(
        title=u"Label",
        description=u"A label to be used as the heading for the form.",
        required=False
        )

    menu = TextLine(
        title=u"The browser menu to include the form in.",
        description=u"""
        Many views are included in menus. It's convenient to name the
        menu in the page directive, rather than having to give a
        separate menuItem directive.""",
        required=False
        )

    title = MessageID(
        title=u"Menu title",
        description=u"The browser menu label for the form.",
        required=False
        )

    fields = Tokens(
        title=u"Fields",
        description=u"""
        The fields and the order in which to display them.  If this is
        not specified, all schema fields will be displayed in the
        order specified in the schema itself.""",
        required=False,
        value_type=PythonIdentifier()
        )

class ICommonWizardInformation(ICommonInformation):
    """
    Common information for browser wizards
    """

    menu = TextLine(
        title=u"The browser menu to include the form in.",
        description=u"""
        Many views are included in menus. It's convenient to name the
        menu in the page directive, rather than having to give a
        separate menuItem directive.""",
        required=False
        )

    title = MessageID(
        title=u"Menu title",
        description=u"The browser menu label for the form.",
        required=False
        )

    description = MessageID(
        title=u"A longer description of the add form.",
        description=u"""
        A UI may display this with the item or display it when the
        user requests more assistance.""",
        required=False
        )

    use_session = Bool(
        title=u"Use session",
        description=u"""
        If 'no', hidden input controls are used to maintain state
        between panes in the wizard. Only simple data types can
        be propagated with this method.

        Defaults to 'yes'.""",
        required=False
        )

class ICommonAddInformation(Interface):
    """
    Common information for add forms/wizards
    """

    content_factory = GlobalObject(
        title=u"Content factory",
        description=u"""
        An object to call to create new content objects.

        This attribute isn't used if a class is specified that
        implements createAndAdd.""",
        required=False
        )

    arguments = Tokens(
        title=u"Arguments",
        description=u"""
        A list of field names to supply as positional arguments to the
        factory.""",
        required=False,
        value_type=PythonIdentifier()
        )

    keyword_arguments = Tokens(
        title=u"Keyword arguments",
        description=u"""
        A list of field names to supply as keyword arguments to the
        factory.""",
        required=False,
        value_type=PythonIdentifier()
        )

    set_before_add = Tokens(
        title=u"Set before add",
        description=u"""
        A list of fields to be assigned to the newly created object
        before it is added.""",
        required=False,
        value_type=PythonIdentifier(),
        )

    set_after_add = Tokens(
        title=u"Set after add",
        description=u"""
        A list of fields to be assigned to the newly created object
        after it is added.""",
        required=False,
        value_type=PythonIdentifier()
        )

class IAddWizardDirective(ICommonWizardInformation, ICommonAddInformation):
    """
    Define an automatically generated add wizard (multi-page form)

    The addwizard directive creates and registers a view for adding an
    object based on a schema.

    Adding an object is a bit trickier than editing an object, because
    the object the schema applies to isn't available when forms are
    being rendered.  The addwizard directive provides an customization
    interface to overcome this difficulty.

    See zope.app.browser.form.interfaces.IAddFormCustomization.
    """

class IEditWizardDirective(ICommonWizardInformation):
    """
    Define an automatically generated edit wizard (multi-page form).

    The editwizard directive creates and register's a view for editing
    an object based on a schema.
    """

    title = MessageID(
        title=u"The browser menu label for the edit form",
        description=u"This attribute defaults to 'Edit'.",
        required=False
        )

class IPaneSubdirective(Interface):
    """
    Define a Pane (page) of the wizard
    """

    label = MessageID(
        title=u"Label",
        description=u"The label used as the heading on this pane",
        required=False,
        )

    fields = Tokens(
        title=u"Fields",
        description=u"The fields to display on this pane of the wizard",
        required=True,
        value_type=PythonIdentifier()
        )

class IEditFormDirective(ICommonFormInformation):
    """
    Define an automatically generated edit form

    The editform directive creates and register's a view for editing
    an object based on a schema.
    """

class ISubeditFormDirective(ICommonInformation):
    """
    Define a subedit form
    """

    label = TextLine(
        title=u"Label",
        description=u"A label to be used as the heading for the form.",
        required=False
        )

    fulledit_path = TextLine(
        title=u"Path (relative URL) to the full edit form",
        required=False
        )

    fulledit_label = MessageID(
        title=u"Label of the full edit form",
        required=False
        )

class IAddFormDirective(ICommonFormInformation, ICommonAddInformation):
    """
    Define an automatically generated add form

    The addform directive creates and registers a view for adding an
    object based on a schema.

    Adding an object is a bit trickier than editing an object, because
    the object the schema applies to isn't available when forms are
    being rendered.  The addform directive provides an customization
    interface to overcome this difficulty.

    See zope.app.browser.form.interfaces.IAddFormCustomization.
    """

    description = Text(
        title=u"A longer description of the add form.",
        description=u"""
        A UI may display this with the item or display it when the
        user requests more assistance.""",
        required=False
        )

class ISchemaDisplayDirective(ICommonFormInformation):
    """
    Define an automatically generated display form.

    The schemadisplay directive creates and register's a view for
    displaying an object based on a schema.
    """

    title = MessageID(
        title=u"The browser menu label for the edit form",
        description=u"This attribute defaults to 'Edit'.",
        required=False
        )


=== Added File Zope3/src/zope/app/form/browser/schemadisplay.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Support for display-only pages based on schema.

$Id: schemadisplay.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
from zope.app import zapi

from zope.schema import getFieldNamesInOrder

from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.location.interfaces import ILocation
from zope.app.location import LocationProxy
from zope.app.publisher.browser import BrowserView
from zope.security.checker import defineChecker, NamesChecker
from zope.component import getAdapter

from zope.app.form.utility import setUpDisplayWidgets
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.app.pagetemplate.simpleviewclass import SimpleViewClass

class DisplayView(BrowserView):
    """Simple display-view base class.

    Subclasses should provide a schema attribute defining the schema
    to be displayed.
    """

    errors = ()
    update_status = ''
    label = ''

    # Fall-back field names computes from schema
    fieldNames = property(lambda self: getFieldNamesInOrder(self.schema))

    def __init__(self, context, request):
        super(DisplayView, self).__init__(context, request)
        self._setUpWidgets()

    def _setUpWidgets(self):
        adapted = getAdapter(self.context, self.schema)
        if adapted is not self.context:
            if not ILocation.providedBy(adapted):
                adapted = LocationProxy(adapted)
            adapted.__parent__ = self.context
        self.adapted = adapted
        setUpDisplayWidgets(self, self.schema, source=adapted,
                            names=self.fieldNames)

    def setPrefix(self, prefix):
        for widget in self.widgets():
            widget.setPrefix(prefix)

    def widgets(self):
        return [getattr(self, name+'_widget')
                for name in self.fieldNames]


def DisplayViewFactory(name, schema, label, permission, layer,
                       template, default_template, bases, for_, fields,
                       fulledit_path=None, fulledit_label=None, menu=u'',
                       usage=u''):
    # XXX What about the __implements__ of the bases?
    class_ = SimpleViewClass(template, used_for=schema, bases=bases)
    class_.schema = schema
    class_.label = label
    class_.fieldNames = fields
    class_.fulledit_path = fulledit_path
    if fulledit_path and (fulledit_label is None):
        fulledit_label = "Full display"
    class_.fulledit_label = fulledit_label
    class_.generated_form = ViewPageTemplateFile(default_template)
    class_.usage = usage or (
        menu and globalBrowserMenuService.getMenuUsage(menu)
        )
    defineChecker(class_,
                  NamesChecker(("__call__", "__getitem__", "browserDefault"),
                               permission))
    s = zapi.getService(None, zapi.servicenames.Presentation)
    s.provideView(for_, name, IBrowserRequest, class_, layer)


=== Added File Zope3/src/zope/app/form/browser/subedit.pt ===
<div metal:define-macro="formbody">
  <h5 metal:define-slot="heading">
    <a tal:attributes="href context/@@absolute_url"
       tal:condition="view/label"
       tal:content="view/label">
         Edit something
    </a>
  </h5>
  <p tal:condition="view/fulledit_label">
    <a tal:attributes="href
         string:${context/@@absolute_url}/${view/fulledit_path}"
       tal:content="view/fulledit_label">Full edit</a>
  </p>
  <p tal:define="status view/update"
     tal:condition="status"
     tal:content="status" />

  <div tal:condition="view/errors">
     <ul>
        <li tal:repeat="error view/errors">
           <strong tal:content="error/__class__">
              Error Type</strong>:
           <span tal:content="error">Error text</span>
        </li>
     </ul>
  </div>

  <div metal:define-slot="extra_info" tal:replace="nothing">
  </div>

  <div class="row" metal:define-slot="extra_top" tal:replace="nothing">
      <div class="label">Extra top</div>
      <div class="field"><input type="text" style="width:100%" /></div>
  </div>
    <div class="row" metal:define-macro="widget_rows" 
                     tal:repeat="widget view/widgets">
         <div class="label" tal:content="structure widget/label">Name</div>
         <div class="field" tal:content="structure widget">
                <input type="text" style="width:100%"/>
         </div>
		 <div class="error" tal:define="error widget/error"
			tal:condition="error" tal:content="structure error">
			The Error
		 </div>
  </div>
  <div class="row"
       metal:define-slot="extra_bottom" tal:replace="nothing">
      <div class="label">Extra bottom</div>
      <div class="field"><input type="text" style="width:100%" /></div>
  </div>

</div>


=== Added File Zope3/src/zope/app/form/browser/submit.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Standard submit button names

Update -- Name of the standard update submit button


$Id: submit.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""

Next = "NEXT_SUBMIT"
Previous = "PREVIOUS_SUBMIT"
Update = "UPDATE_SUBMIT"



=== Added File Zope3/src/zope/app/form/browser/vocabularywidget.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Vocabulary widget support.

This includes support for vocabulary fields' use of the vocabulary to
determine the actual widget to display, and support for supplemental
query objects and helper views.

$Id: vocabularywidget.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
from xml.sax.saxutils import quoteattr

from zope.interface import implements, implementedBy
from zope.interface.declarations import directlyProvides
from zope.publisher.browser import BrowserView
from zope.security.proxy import trustedRemoveSecurityProxy
from zope.schema.interfaces import IIterableVocabularyQuery
from zope.schema.interfaces import ValidationError
from zope.i18n import translate

from zope.app import zapi
from zope.app.form.browser import widget
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.form.browser.interfaces import IVocabularyQueryView
from zope.app.form.interfaces import WidgetInputError


# These widget factories delegate to the vocabulary on the field.

# Display

def VocabularyFieldDisplayWidget(field, request):
    """Return a display widget based on a vocabulary field."""
    return _get_vocabulary_widget(field, request, "display")

def VocabularyBagFieldDisplayWidget(field, request):
    """Return a display widget based on a vocabulary field."""
    return _get_vocabulary_widget(field, request, "display-bag")

def VocabularyListFieldDisplayWidget(field, request):
    """Return a display widget based on a vocabulary field."""
    return _get_vocabulary_widget(field, request, "display-list")

def VocabularySetFieldDisplayWidget(field, request):
    """Return a display widget based on a vocabulary field."""
    return _get_vocabulary_widget(field, request, "display-set")

def VocabularyUniqueListFieldDisplayWidget(field, request):
    """Return a display widget based on a vocabulary field."""
    return _get_vocabulary_widget(field, request, "display-unique-list")

# Edit

def VocabularyFieldEditWidget(field, request):
    """Return a value-selection widget based on a vocabulary field."""
    return _get_vocabulary_edit_widget(field, request)

def VocabularyBagFieldEditWidget(field, request):
    """Return a value-selection widget based on a vocabulary field."""
    return _get_vocabulary_edit_widget(field, request, "bag")

def VocabularyListFieldEditWidget(field, request):
    """Return a value-selection widget based on a vocabulary field."""
    return _get_vocabulary_edit_widget(field, request, "list")

def VocabularySetFieldEditWidget(field, request):
    """Return a value-selection widget based on a vocabulary field."""
    return _get_vocabulary_edit_widget(field, request, "set")

def VocabularyUniqueListFieldEditWidget(field, request):
    """Return a value-selection widget based on a vocabulary field."""
    return _get_vocabulary_edit_widget(field, request, "unique-list")


# Helper functions for the factories:

def _get_vocabulary_widget(field, request, viewname):
    view = zapi.getView(field.vocabulary,
                        "field-%s-widget" % viewname, request)
    view = trustedRemoveSecurityProxy(view)
    view.setField(field)
    return view

def _get_vocabulary_edit_widget(field, request, modifier=''):
    if modifier:
        modifier = "-" + modifier
    viewname = "edit" + modifier
    view = _get_vocabulary_widget(field, request, viewname)
    query = field.vocabulary.getQuery()
    if query is not None:
        queryname = "widget-query%s-helper" % modifier
        queryview = zapi.getView(query, queryname, request)
        view.setQuery(query, queryview)
        queryview.setWidget(view)
    return view


class IterableVocabularyQuery(object):
    """Simple query object used to invoke the simple selection mechanism."""

    implements(IIterableVocabularyQuery)

    def __init__(self, vocabulary, *interfaces):
        self.vocabulary = vocabulary
        if interfaces:
            directlyProvides(self, *interfaces)


class TranslationHook:

    def translate(self, msgid):
        return translate(self.context, msgid, context=self.request,
                         default=msgid)

def message(msgid, default):
    msgid.default = default
    return msgid


# Widget implementation:

class ViewSupport(object, TranslationHook):
    # This is mixed into the vocabulary widget base classes.

    """Helper class for vocabulary and vocabulary-query widgets."""

    def textForValue(self, term):
        # Extract a string from the term.  This can be overridden to
        # support more complex term objects.  The token is returned
        # here since it's the only thing known to be a string, or
        # str()able.
        return term.token

    def mkselectionlist(self, type, info, name):
        L = [self.mkselectionitem(type, name, *item) for item in info]
        return widget.renderElement("table",
                                    contents="\n%s\n" % "\n".join(L))

    def mkselectionitem(self, type, name, term, selected, disabled):
        flag = ""
        if selected:
            flag = " checked='checked'"
        if disabled:
            flag += " disabled='disabled'"
        if flag:
            flag = "\n      " + flag
        return ("<tr><td>"
                "<input type='%s' value='%s' name='%s'%s />"
                "</td>\n    <td>%s</td></tr>"
                % (type, term.token, name, flag, self.textForValue(term)))


class VocabularyWidgetBase(ViewSupport, widget.BrowserWidget):
    """Convenience base class for vocabulary-based widgets."""

    extra = ""
    type = "vocabulary"
    context = None

    def __init__(self, context, request):
        self.request = request
        self.vocabulary = context
        # self.context is set to the field in setField below

    def setField(self, field):
        assert self.context is None
        # only allow this to happen for a bound field
        assert field.context is not None
        self.context = field
        self.setPrefix(self._prefix)
        assert self.name

    def setPrefix(self, prefix):
        super(VocabularyWidgetBase, self).setPrefix(prefix)
        # names for other information from the form
        self.empty_marker_name = self.name + "-empty-marker"

    def __call__(self):
        if self._data is self._data_marker:
            if self.hasInput():
                try:
                    value = self.getInputValue()
                except WidgetInputError:
                    value = self.request.form.get(self.name, self._missing)
            else:
                value = self._getDefault()
        else:
            value = self._data
        return self.render(value)

    def render(self, value):
        raise NotImplementedError(
            "render() must be implemented by a subclass")

    def convertTokensToValues(self, tokens):
        L = []
        for token in tokens:
            try:
                term = self.context.vocabulary.getTermByToken(token)
            except LookupError:
                raise WidgetInputError(
                    self.context.__name__,
                    self.context.title,
                    "token %r not found in vocabulary" % token)
            else:
                L.append(term.value)
        return L

    _have_field_data = False

    def getInputValue(self):
        value = self._compute_data()
        field = self.context

        # missing value is okay if field is not required
        if value == field.missing_value and not field.required:
            return value

        # all other values must be valid
        try:
            field.validate(value)
        except ValidationError, v:
            self._error = WidgetInputError(
                self.context.__name__, self.title, v)
            raise self._error

        return value

    def _emptyMarker(self):
        return "<input name='%s' type='hidden' value='1' />" % (
            self.empty_marker_name)

    def hasInput(self):
        return (self.name in self.request.form or
                self.empty_marker_name in self.request.form)

    def setRenderedValue(self, value):
        self._data = value

    def _compute_data(self):
        raise NotImplementedError(
            "_compute_data() must be implemented by a subclass\n"
            "It may be inherited from the mix-in classes SingleDataHelper\n"
            "or MultiDataHelper (from zope.app.form.browser.vocabularywidget)")

    def _showData(self):
        raise NotImplementedError(
            "vocabulary-based widgets don't use the _showData() method")

    def _convert(self, value):
        raise NotImplementedError(
            "vocabulary-based widgets don't use the _convert() method")

    def _unconvert(self, value):
        raise NotImplementedError(
            "vocabulary-based widgets don't use the _unconvert() method")




_msg_missing_single_value_display = message(
    _("vocabulary-missing-single-value-for-display"),   "")
_msg_missing_multiple_value_display = message(
    _("vocabulary-missing-multiple-value-for-display"), "")

_msg_missing_single_value_edit = message(
    _("vocabulary-missing-single-value-for-edit"),   "(no value)")
_msg_missing_multiple_value_edit = message(
    _("vocabulary-missing-multiple-value-for-edit"), "(no values)")

class SingleDataHelper(object):

    def _compute_data(self):
        token = self.request.form.get(self.name)
        if token:
            return self.convertTokensToValues([token])[0]
        else:
            return None

class MultiDataHelper(object):

    def _compute_data(self):
        if self.name in self.request.form:
            tokens = self.request.form[self.name]
            if not isinstance(tokens, list):
                tokens = [tokens]
            return self.convertTokensToValues(tokens)
        return []

    def _getDefault(self):
        # Return the default value for this widget;
        # may be overridden by subclasses.
        val = self.context.default
        if val is None:
            val = []
        return val

class VocabularyDisplayWidget(SingleDataHelper, VocabularyWidgetBase):
    """Simple single-selection display that can be used in many cases."""

    _msg_no_value = _msg_missing_single_value_display

    def render(self, value):
        if value is None:
            return self.translate(self._msg_no_value)
        else:
            term = self.context.vocabulary.getTerm(value)
            return self.textForValue(term)


class VocabularyMultiDisplayWidget(MultiDataHelper, VocabularyWidgetBase):

    itemTag = 'li'
    tag = 'ol'

    def render(self, value):
        if value:
            rendered_items = self.renderItems(value)
            return widget.renderElement(self.tag,
                                        type=self.type,
                                        name=self.name,
                                        id=self.name,
                                        cssClass=self.cssClass,
                                        contents="\n".join(rendered_items),
                                        extra=self.extra)
        else:
            return self.translate(_msg_missing_multiple_value_display)

    def renderItems(self, value):
        L = []
        vocabulary = self.context.vocabulary
        cssClass = self.cssClass or ''
        if cssClass:
            cssClass += "-item"
        tag = self.itemTag
        for v in value:
            term = vocabulary.getTerm(v)
            L.append(widget.renderElement(tag,
                                          cssClass=cssClass,
                                          contents=self.textForValue(term)))
        return L


class VocabularyListDisplayWidget(VocabularyMultiDisplayWidget):
    """Display widget for ordered multi-selection fields.

    This can be used for both VocabularyListField and
    VocabularyUniqueListField fields.
    """
    tag = 'ol'


class VocabularyBagDisplayWidget(VocabularyMultiDisplayWidget):
    """Display widget for unordered multi-selection fields.

    This can be used for both VocabularyBagField and
    VocabularySetField fields.
    """
    tag = 'ul'


class ActionHelper(object, TranslationHook):
    __actions = None

    def addAction(self, action, msgid):
        if self.__actions is None:
            self.__actions = {}
        assert action not in self.__actions
        self.__actions[action] = msgid

    def getAction(self):
        assert self.__actions is not None, \
               "getAction() called on %r with no actions defined" % self
        get = self.request.form.get
        for action in self.__actions.iterkeys():
            name = "%s.action-%s" % (self.name, action)
            if get(name):
                return action
        return None

    def renderAction(self, action, disabled=False):
        msgid = self.__actions[action]
        return ("<input type='submit' name='%s.action-%s' value=%s %s />"
                % (self.name, action, quoteattr(self.translate(msgid)),
                   disabled and "\n       disabled='disabled' " or ""))


class VocabularyEditWidgetBase(VocabularyWidgetBase):
    
    size = 5
    tag = 'select'

    query = None
    queryview = None

    def setQuery(self, query, queryview):
        assert self.query is None
        assert self.queryview is None
        if query is None:
            assert queryview is None
        else:
            assert queryview is not None
            self.query = query
            self.queryview = queryview
            # Use of a hyphen to form the name for the query widget
            # ensures that it won't clash with anything else, since
            # field names are normally Python identifiers.
            queryview.setName(self.name + "-query")

    def setPrefix(self, prefix):
        super(VocabularyEditWidgetBase, self).setPrefix(prefix)
        if self.queryview is not None:
            self.queryview.setName(self.name + "-query")

    def render(self, value):
        contents = []
        have_results = False
        if self.queryview is not None:
            s = self.queryview.renderResults(value)
            if s:
                contents.append(self._div('queryresults', s))
                s = self.queryview.renderInput()
                contents.append(self._div('queryinput', s))
                have_results = True
        contents.append(self._div('value', self.renderValue(value)))
        contents.append(self._emptyMarker())
        if self.queryview is not None and not have_results:
            s = self.queryview.renderInput()
            if s:
                contents.append(self._div('queryinput', s))
        return self._div(self.cssClass, "\n".join(contents),
                         id=self.name)

    def _div(self, cssClass, contents, **kw):
        if contents:
            return widget.renderElement('div',
                                        cssClass=cssClass,
                                        contents="\n%s\n" % contents,
                                        **kw)
        return ""

    def renderItemsWithValues(self, values):
        """Render the list of possible values, with those found in
        'values' being marked as selected."""

        cssClass = self.cssClass

        # multiple items with the same value are not allowed from a
        # vocabulary, so that need not be considered here
        rendered_items = []
        count = 0
        for term in self.context.vocabulary:
            item_text = self.textForValue(term)

            if term.value in values:
                rendered_item = self.renderSelectedItem(count,
                                                        item_text,
                                                        term.token,
                                                        self.name,
                                                        cssClass)
            else:
                rendered_item = self.renderItem(count,
                                                item_text,
                                                term.token,
                                                self.name,
                                                cssClass)

            rendered_items.append(rendered_item)
            count += 1

        return rendered_items

    def renderItem(self, index, text, value, name, cssClass):
        return widget.renderElement('option',
                                    contents=text,
                                    value=value,
                                    cssClass=cssClass)

    def renderSelectedItem(self, index, text, value, name, cssClass):
        return widget.renderElement('option',
                                    contents=text,
                                    value=value,
                                    cssClass=cssClass,
                                    selected='selected')

class RadioWidget(SingleDataHelper, VocabularyEditWidgetBase):
    """Vocabulary-backed single-selection edit widget.

    This widget can be used when the number of selections is going
    to be small.
    """
    implements(implementedBy(widget.SingleItemsWidget))
    firstItem = False

    _msg_no_value = _msg_missing_single_value_edit
    
    _join_button_to_message_template = u"%s&nbsp;%s"
    _join_messages_template = u"<br />\n"

    def renderItem(self, index, text, value, name, cssClass):
        elem = widget.renderElement('input',
                                    value=value,
                                    name=name,
                                    cssClass=cssClass,
                                    type='radio')
        return self._join_button_to_message_template % (elem, text)

    def renderSelectedItem(self, index, text, value, name, cssClass):
        elem = widget.renderElement('input',
                                    value=value,
                                    name=name,
                                    cssClass=cssClass,
                                    checked=None,
                                    type='radio')
        return self._join_button_to_message_template % (elem, text)

    def renderValue(self, value):
        return "\n%s\n" % self._join_messages_template.join(
            self.renderItems(value))
    
    def renderItems(self, value):
        # XXX this should be rolled into renderValue; separate only
        # for the convenience of leveraging the already existing test
        # framework
        vocabulary = self.context.vocabulary
        # check if we want to select first item
        no_value = None
        if (value == self.context.missing_value
            and getattr(self.context, 'firstItem', False)
            and len(vocabulary) > 0):
            if self.context.required:
                # Grab the first item from the iterator:
                values = [iter(vocabulary).next().value]
            else:
                # the "no value" option will be checked
                no_value = 'checked'
        elif value != self.context.missing_value:
            values = [value]
        else:
            values = ()
        L = self.renderItemsWithValues(values)
        if not self.context.required:
            cssClass = self.cssClass
            kwargs = {
                'value':'',
                'name':self.name,
                'cssClass':cssClass,
                'type':'radio'}
            if no_value:
                kwargs['checked']=None
            option = widget.renderElement('input', **kwargs)
            option = self._join_button_to_message_template % (
                option, self.translate(self._msg_no_value))
            L.insert(0, option)
        return L

class SelectListWidget(SingleDataHelper, VocabularyEditWidgetBase):
    """Vocabulary-backed single-selection edit widget.

    This widget can be used when the number of selections isn't going
    to be very large.
    """
    implements(implementedBy(widget.SingleItemsWidget))
    firstItem = False

    _msg_no_value = _msg_missing_single_value_edit

    def renderValue(self, value):
        rendered_items = self.renderItems(value)
        contents = "\n%s\n" % "\n".join(rendered_items)
        return widget.renderElement('select',
                                    name=self.name,
                                    contents=contents,
                                    size=self.size,
                                    extra=self.extra)

    def renderItems(self, value):
        vocabulary = self.context.vocabulary
        # check if we want to select first item
        if (value == self.context.missing_value
            and getattr(self.context, 'firstItem', False)
            and len(vocabulary) > 0):
            # Grab the first item from the iterator:
            values = [iter(vocabulary).next().value]
        elif value != self.context.missing_value:
            values = [value]
        else:
            values = ()
        L = self.renderItemsWithValues(values)
        if not self.context.required:
            option = ("<option name='%s' value=''>%s</option>"
                      % (self.name, self.translate(self._msg_no_value)))
            L.insert(0, option)
        return L

# more general alias
VocabularyEditWidget = SelectListWidget

class DropdownListWidget(SelectListWidget):
    """Variation of the SelectListWidget that uses a drop-down list."""

    size = 1

class VocabularyMultiEditWidget(MultiDataHelper, VocabularyEditWidgetBase):
    """Vocabulary-backed widget supporting multiple selections."""

    _msg_no_value = _msg_missing_multiple_value_edit

    def renderItems(self, value):
        if value == self.context.missing_value:
            values = ()
        else:
            values = list(value)
        return self.renderItemsWithValues(values)

    def renderValue(self, value):
        # All we really add here is the ':list' in the name argument
        # to widget.renderElement().
        rendered_items = self.renderItems(value)
        return widget.renderElement(self.tag,
                                    name=self.name + ':list',
                                    multiple=None,
                                    size=self.size,
                                    contents="\n".join(rendered_items),
                                    extra=self.extra)


class VocabularyQueryViewBase(ActionHelper, ViewSupport, BrowserView):
    """Vocabulary query support base class."""

    implements(IVocabularyQueryView)

    # This specifically isn't a widget in it's own right, but is a
    # form of BrowserView (at least conceptually).

    widget = None

    def __init__(self, context, request):
        self.vocabulary = context.vocabulary
        self.context = context
        self.request = request
        super(VocabularyQueryViewBase, self).__init__(context, request)

    def setName(self, name):
        assert not name.endswith(".")
        self.name = name

    def setWidget(self, widget):
        assert self.widget is None
        assert widget is not None
        self.widget = widget

    def renderInput(self):
        return self.renderQueryInput()

    def renderResults(self, value):
        results = self.getResults()
        if results is not None:
            return self.renderQueryResults(results, value)
        else:
            return ""

    def renderQueryResults(self, results, value):
        raise NotImplementedError(
            "renderQueryResults() must be implemented by a subclass")

    def renderQueryInput(self):
        raise NotImplementedError(
            "renderQueryInput() must be implemented by a subclass")

    def getResults(self):
        # This is responsible for running the query against the query
        # object (self.context), and returning a results object.  If
        # there isn't a query in the form, returns None.
        return None

    def performAction(self, value):
        return value


ADD_DONE = "adddone"
ADD_MORE = "addmore"
MORE = "more"


class IterableVocabularyQueryViewBase(VocabularyQueryViewBase):
    """Query view for IIterableVocabulary objects without more
    specific query views.

    This should only be used (directly) for vocabularies for which
    getQuery() returns None.
    """

    implements(IVocabularyQueryView)

    queryResultBatchSize = 8

    _msg_add_done   = message(_("vocabulary-query-button-add-done"),
                              "Add+Done")
    _msg_add_more   = message(_("vocabulary-query-button-add-more"),
                              "Add+More")
    _msg_more       = message(_("vocabulary-query-button-more"),
                              "More")
    _msg_no_results = message(_("vocabulary-query-message-no-results"),
                              "No Results")
    _msg_results_header = message(_("vocabulary-query-header-results"),
                                 "Search results")

    def __init__(self, *args, **kw):
        super(IterableVocabularyQueryViewBase, self).__init__(*args, **kw)
        self.addAction(ADD_DONE, self._msg_add_done)
        self.addAction(ADD_MORE, self._msg_add_more)
        self.addAction(MORE,     self._msg_more)

    def setName(self, name):
        super(IterableVocabularyQueryViewBase, self).setName(name)
        name = self.name
        self.query_index_name = name + ".start"
        self.query_selections_name = name + ".picks"
        #
        get = self.request.form.get
        self.action = self.getAction()
        self.query_index = None
        if self.query_index_name in self.request.form:
            try:
                index = int(self.request.form[self.query_index_name])
            except ValueError:
                pass
            else:
                if index >= 0:
                    self.query_index = index
        QS = get(self.query_selections_name, [])
        if not isinstance(QS, list):
            QS = [QS]
        self.query_selections = []
        for token in QS:
            try:
                term = self.vocabulary.getTermByToken(token)
            except LookupError:
                # XXX unsure what to pass to exception constructor
                raise WidgetInputError(
                    "(query view for %s)" % self.context,
                    "(query view for %s)" % self.context,
                    "token %r not in vocabulary" % token)
            else:
                self.query_selections.append(term.value)

    def renderQueryInput(self):
        # There's no query support, so we can't actually have input.
        return ""

    def getResults(self):
        if self.query_index is not None:
            return self.vocabulary
        else:
            return None

    def renderQueryResults(self, results, value):
        # display query results batch
        it = iter(results)
        qi = self.query_index
        have_more = True
        try:
            for xxx in range(qi):
                it.next()
        except StopIteration:
            # we should only get here with a botched request; ADD_MORE
            # and MORE will normally be disabled if there are no results
            # (see below)
            have_more = False
        items = []
        QS = []
        try:
            for i in range(qi, qi + self.queryResultBatchSize):
                term = it.next()
                disabled = term.value in value
                selected = disabled
                if term.value in self.query_selections:
                    QS.append(term.value)
                    selected = True
                items.append((term, selected, disabled))
            else:
                # see if there's anything else:
                it.next()
        except StopIteration:
            if not items:
                return "<div class='results'>%s</div>" % (
                    self.translate(self._msg_no_results))
            have_more = False
        self.query_selections = QS
        return ''.join(
            ["<div class='results'>\n",
             "<h4>%s</h4>\n" % (
                 self.translate(self._msg_results_header)),
             self.makeSelectionList(items, self.query_selections_name),
             "\n",
             self.renderAction(ADD_DONE), "\n",
             self.renderAction(ADD_MORE, not have_more), "\n",
             self.renderAction(MORE, not have_more), "\n"
             "<input type='hidden' name='%s' value='%d' />\n"
             % (self.query_index_name, qi),
             "</div>"])

    def performAction(self, value):
        if self.action == ADD_DONE:
            value = self.addSelections(value)
            self.query_index = None
            self.query_selections = []
        elif self.action == ADD_MORE:
            value = self.addSelections(value)
            self.query_index += self.queryResultBatchSize
        elif self.action == MORE:
            self.query_index += self.queryResultBatchSize
        elif self.action:
            raise ValueError("unknown action in request: %r" % self.action)
        return value

    def addSelections(self, value):
        for item in self.query_selections:
            if item not in value and item in self.context.vocabulary:
                value.append(item)
        return value

class IterableVocabularyQueryView(IterableVocabularyQueryViewBase):

    def makeSelectionList(self, items, name):
        return self.mkselectionlist("radio", items, name)

    def renderQueryResults(self, results, value):
        return super(IterableVocabularyQueryView, self).renderQueryResults(
            results, [value])

class IterableVocabularyQueryMultiView(IterableVocabularyQueryViewBase):

    def makeSelectionList(self, items, name):
        return self.mkselectionlist("checkbox", items, name)


=== Added File Zope3/src/zope/app/form/browser/vocabularywidget.zcml ===
<configure xmlns="http://namespaces.zope.org/zope">

  <!-- Query view helpers -->
  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      allowed_interface="zope.app.form.browser.interfaces.IVocabularyQueryView"
      for="zope.schema.interfaces.IIterableVocabularyQuery"
      name="widget-query-helper"
      factory=".vocabularywidget.IterableVocabularyQueryView"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      allowed_interface="zope.app.form.browser.interfaces.IVocabularyQueryView"
      for="zope.schema.interfaces.IIterableVocabularyQuery"
      name="widget-query-list-helper"
      factory=".vocabularywidget.IterableVocabularyQueryMultiView"
     permission="zope.Public"
      />

  <!-- Vocabulary field display widgets -->
  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      factory=".vocabularywidget.VocabularyDisplayWidget"
      name="field-display-widget"
      for="zope.schema.interfaces.IVocabulary"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      factory=".vocabularywidget.VocabularyBagDisplayWidget"
      name="field-display-bag-widget"
      for="zope.schema.interfaces.IVocabulary"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      factory=".vocabularywidget.VocabularyListDisplayWidget"
      name="field-display-list-widget"
      for="zope.schema.interfaces.IVocabulary"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      factory=".vocabularywidget.VocabularyBagDisplayWidget"
      name="field-display-set-widget"
      for="zope.schema.interfaces.IVocabulary"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      factory=".vocabularywidget.VocabularyListDisplayWidget"
      name="field-display-unique-list-widget"
      for="zope.schema.interfaces.IVocabulary"
      permission="zope.Public"
      />

  <!-- Vocabulary edit widgets -->
  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      factory=".vocabularywidget.VocabularyEditWidget"
      name="field-edit-widget"
      for="zope.schema.interfaces.IVocabulary"
      permission="zope.Public"
      />

  <view
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      factory=".vocabularywidget.VocabularyMultiEditWidget"
      name="field-edit-list-widget"
      for="zope.schema.interfaces.IVocabulary"
      permission="zope.Public"
      />

      
</configure>


=== Added File Zope3/src/zope/app/form/browser/widget.py ===
##############################################################################
#
# Copyright (c) 2001-2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Browser Widget Definitions

$Id: widget.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
import re, cgi
import traceback
from warnings import warn
from xml.sax.saxutils import quoteattr

from zope.interface import implements
from zope.proxy import removeAllProxies
from zope.schema import getFieldNamesInOrder
from zope.schema.interfaces import ValidationError
from zope.publisher.browser import BrowserView
from zope.i18n import translate

from zope.app import zapi
from zope.app.tests import ztapi
from zope.app.form.interfaces import IInputWidget
from zope.app.form.browser.interfaces import IBrowserWidget
from zope.app.form.widget import Widget
from zope.app.form.utility import setUpEditWidgets, applyWidgetsChanges
from zope.app.form.interfaces import ConversionError, WidgetInputError
from zope.app.form.interfaces import MissingInputError
from zope.app.datetimeutils import parseDatetimetz
from zope.app.datetimeutils import DateTimeError
from zope.app.i18n import ZopeMessageIDFactory as _

ListTypes = list, tuple

class BrowserWidget(Widget, BrowserView):
    """A field widget that knows how to display itself as HTML.

    When we generate labels, titles, descriptions, and errors, the
    labels, titles, and descriptions are translated and the
    errors are rendered with the view machinery, so we need to set up
    a lot of machinery to support translation and views:
        
    >>> setUp() # now we have to set up an error view...
    >>> from zope.app.form.interfaces import IWidgetInputError
    >>> from zope.app.publisher.browser import BrowserView
    >>> from cgi import escape
    >>> class SnippetErrorView(BrowserView):
    ...     def __call__(self):
    ...         return escape(self.context.errors[0])
    ...
    >>> ztapi.browserView(IWidgetInputError, 'snippet', SnippetErrorView)
    >>> from zope.publisher.browser import TestRequest

    And now the tests proper...

    >>> from zope.schema import Field
    >>> import re
    >>> isFriendly=re.compile(".*hello.*").match
    >>> field = Field(__name__='foo', title=u'Foo', constraint=isFriendly)
    >>> request = TestRequest(form={
    ... 'field.foo': u'hello\\r\\nworld',
    ... 'baz.foo': u'bye world'})
    >>> widget = BrowserWidget(field, request)
    >>> widget.name
    'field.foo'
    >>> widget.title
    u'Foo'
    >>> widget.hasInput()
    True
    >>> widget.getInputValue()
    u'hello\\r\\nworld'
    >>> widget.required
    True
    >>> widget._error is None
    True
    >>> widget.error()
    ''
    >>> widget.setRenderedValue('Hey\\nfolks')
    >>> widget.getInputValue()
    u'hello\\r\\nworld'
    >>> widget._error is None
    True
    >>> widget.error()
    ''

    >>> widget.setPrefix('baz')
    >>> widget.name
    'baz.foo'
    >>> widget.error()
    ''
    >>> try:
    ...     widget.getInputValue()
    ... except WidgetInputError:
    ...     print widget._error.errors
    (u'Constraint not satisfied', u'bye world')
    >>> widget.error()
    u'Constraint not satisfied'
    >>> widget._error = None # clean up for next round of tests

    >>> widget.setPrefix('test')
    >>> widget.name
    'test.foo'
    >>> widget._error is None
    True
    >>> widget.error()
    ''
    >>> widget.hasInput()
    False
    >>> widget.getInputValue()
    Traceback (most recent call last):
    ...
    MissingInputError: ('test.foo', u'Foo', None)
    >>> field.required = False
    >>> widget.request.form['test.foo'] = u''
    >>> widget.required
    False
    >>> widget.getInputValue() == field.missing_value
    True
    >>> widget._error is None
    True
    >>> widget.error()
    ''

    >>> print widget.label()
    <label for="test.foo">Foo</label>

    Now we clean up.

    >>> tearDown()
    """

    implements(IBrowserWidget)

    tag = 'input'
    type = 'text'
    cssClass = ''
    extra = ''
    _missing = ''
    _error = None
    
    required = property(lambda self: self.context.required)

    def hasInput(self):
        """See IWidget.hasInput.

        Returns True if the submitted request form contains a value for
        the widget, otherwise returns False.

        Some browser widgets may need to implement a more sophisticated test
        for input. E.g. checkbox values are not supplied in submitted
        forms when their value is 'off' -- in this case the widget will
        need to add a hidden element to signal its presence in the form.
        """
        return self.name in self.request.form

    def hasValidInput(self):
        try:
            self.getInputValue()
            return True
        except WidgetInputError:
            return False

    def getInputValue(self):
        self._error = None
        field = self.context

        # form input is required, otherwise raise an error
        input = self.request.form.get(self.name)
        if input is None:
            raise MissingInputError(self.name, self.title, None)

        # convert input to suitable value - may raise conversion error
        value = self._convert(input)

        # allow missing values only for non-required fields
        if value == field.missing_value and not field.required:
            return value

        # value must be valid per the field constraints
        try:
            field.validate(value)
        except ValidationError, v:
            self._error = WidgetInputError(
                self.context.__name__, self.title, v)
            raise self._error
        return value

    def validate(self):
        self.getInputValue()

    def applyChanges(self, content):
        field = self.context
        value = self.getInputValue()
        if field.query(content, self) != value:
            field.set(content, value)
            return True
        else:
            return False

    def _convert(self, input):
        """Converts input to a value appropriate for the field type.

        Widgets for non-string fields should override this method to
        perform an appropriate conversion.

        This method is used by getInputValue to perform the conversion
        of a form input value (keyed by the widget's name) to an appropriate
        field value. Widgets that require a more complex conversion process
        (e.g. utilize more than one form field) should override getInputValue
        and disregard this method.
        """
        if input == self._missing:
            return self.context.missing_value
        else:
            return input

    def _unconvert(self, value):
        """Converts a field value to a string used as an HTML form value.

        This method is used in the default rendering of widgets that can
        represent their values in a single HTML form value. Widgets whose
        fields have more complex data structures should disregard this
        method and override the default rendering method (__call__).
        """
        if value == self.context.missing_value:
            return self._missing
        else:
            return value

    def _showData(self):
        """Returns a value suitable for use as an HTML form value."""

        if not self._renderedValueSet():
            if self.hasInput():
                try:
                    value = self.getInputValue()
                except WidgetInputError:
                    return self.request.form.get(self.name, self._missing)
            else:
                value = self._getDefault()
        else:
            value = self._data

        return self._unconvert(value)

    def _getDefault(self):
        # Return the default value for this widget;
        # may be overridden by subclasses.
        return self.context.default

    def __call__(self):
        return renderElement(self.tag,
                             type=self.type,
                             name=self.name,
                             id=self.name,
                             value=self._showData(),
                             cssClass=self.cssClass,
                             extra=self.extra)

    def hidden(self):
        return renderElement(self.tag,
                             type='hidden',
                             name=self.name,
                             id=self.name,
                             value=self._showData(),
                             cssClass=self.cssClass,
                             extra=self.extra)

    def render(self, value):
        warn("The widget render method is deprecated",
            DeprecationWarning, 2)

        self.setRenderedValue(value)
        return self()

    def renderHidden(self, value):
        warn("The widget render method is deprecated",
            DeprecationWarning, 2)
        self.setRenderedValue(value)
        return self.hidden()

    def label(self):
        kw = {"for": self.name,
              "contents": cgi.escape(self.title)}
        if self.context.description:
            kw["title"] = self.context.description
        return renderElement("label", **kw)

    def error(self):
        if self._error:
            return zapi.getView(self._error, 'snippet', self.request)()
        return ""

    def labelClass(self):
        return self.context.required and "label required" or "label"

    def row(self):
        if self._error:
            return '<div class="%s">%s</div><div class="field">%s</div>' \
                '<div class="error">%s</div>' % (self.labelClass(),
                                                 self.label(), self(),
                                                 self.error())
        else:
            return '<div class="%s">%s</div><div class="field">%s</div>' % (
                self.labelClass(), self.label(), self())
                

class DisplayWidget(BrowserWidget):

    def __call__(self):
        return self._showData()
        

class CheckBoxWidget(BrowserWidget):
    """A checkbox widget used to display Bool fields.
    
    For more detailed documentation, including sample code, see
    tests/test_checkboxwidget.py.
    """
    
    implements(IInputWidget)
    
    type = 'checkbox'
    default = 0
    extra = ''

    def __call__(self):
        data = self._showData()
        if data:
            kw = {'checked': None}
        else:
            kw = {}
        return "%s %s" % (
            renderElement(self.tag,
                          type='hidden',
                          name=self.name+".used",
                          id=self.name+".used",
                          value=""
                          ),
            renderElement(self.tag,
                             type=self.type,
                             name=self.name,
                             id=self.name,
                             cssClass=self.cssClass,
                             extra=self.extra,
                             **kw),
            )

    def _convert(self, value):
        return value == 'on'

    def _unconvert(self, value):
        return value and "on" or ""
        return value == 'on'

    def hasInput(self):
        return self.name + ".used" in self.request.form or \
            super(CheckBoxWidget, self).hasInput()

    def getInputValue(self):
        # When it's checked, its value is 'on'.
        # When a checkbox is unchecked, it does not appear in the form data.
        value = self.request.form.get(self.name, 'off')
        return value == 'on'


class TextWidget(BrowserWidget):
    """Text widget.

    Single-line text (unicode) input

    >>> from zope.publisher.browser import TestRequest
    >>> from zope.schema import TextLine
    >>> field = TextLine(__name__='foo', title=u'on')
    >>> request = TestRequest(form={'field.foo': u'Bob'})
    >>> widget = TextWidget(field, request)
    >>> widget.hasInput()
    True
    >>> widget.getInputValue()
    u'Bob'

    >>> def normalize(s):
    ...   return '\\n  '.join(filter(None, s.split(' ')))

    >>> print normalize( widget() )
    <input
      class="textType"
      id="field.foo"
      name="field.foo"
      size="20"
      type="text"
      value="Bob"
      />

    >>> print normalize( widget.hidden() )
    <input
      class="hiddenType"
      id="field.foo"
      name="field.foo"
      type="hidden"
      value="Bob"
      />

    Calling setRenderedValue will change what gets output:

    >>> widget.setRenderedValue("Barry")
    >>> print normalize( widget() )
    <input
      class="textType"
      id="field.foo"
      name="field.foo"
      size="20"
      type="text"
      value="Barry"
      />

    """
    
    implements(IInputWidget)

    default = ''
    displayWidth = 20
    displayMaxWidth = ""
    extra = ''
    # XXX Alex Limi doesn't like this!
    # style = "width:100%"
    style = ''
    __values = None

    def __init__(self, *args):
        super(TextWidget, self).__init__(*args)

    def __call__(self):
        displayMaxWidth = self.displayMaxWidth or 0
        if displayMaxWidth > 0:
            return renderElement(self.tag,
                                 type=self.type,
                                 name=self.name,
                                 id=self.name,
                                 value=self._showData(),
                                 cssClass=self.cssClass,
                                 style=self.style,
                                 size=self.displayWidth,
                                 maxlength=displayMaxWidth,
                                 extra=self.extra)
        else:
            return renderElement(self.tag,
                                 type=self.type,
                                 name=self.name,
                                 id=self.name,
                                 value=self._showData(),
                                 cssClass=self.cssClass,
                                 style=self.style,
                                 size=self.displayWidth,
                                 extra=self.extra)

class Bytes(BrowserWidget):

    def _convert(self, value):
        value = super(Bytes, self)._convert(value)
        if type(value) is unicode:
            try:
                value = value.encode('ascii')
            except UnicodeError, v:
                raise ConversionError("Invalid textual data", v)

        return value
        

class BytesWidget(Bytes, TextWidget):
    """Bytes widget.

    Single-line data (string) input

    >>> from zope.publisher.browser import TestRequest
    >>> from zope.schema import BytesLine
    >>> field = BytesLine(__name__='foo', title=u'on')
    >>> request = TestRequest(form={'field.foo': u'Bob'})
    >>> widget = BytesWidget(field, request)
    >>> widget.hasInput()
    True
    >>> widget.getInputValue()
    'Bob'
    """    

class ASCII(Bytes):
    """ASCII"""
    

class ASCIIWidget(BytesWidget):
    """ASCII widget.

    Single-line data (string) input
    """

class IntWidget(TextWidget):

    displayWidth = 10

    def _convert(self, value):
        if value == self._missing:
            return self.context.missing_value
        else:
            try:
                return int(value)
            except ValueError, v:
                raise ConversionError("Invalid integer data", v)
                

class FloatWidget(TextWidget):
    
    implements(IInputWidget)
    displayWidth = 10

    def _convert(self, value):
        if value == self._missing:
            return self.context.missing_value
        else:
            try:
                return float(value)
            except ValueError, v:
                raise ConversionError("Invalid floating point data", v)
                

class DatetimeWidget(TextWidget):
    """Datetime entry widget."""

    displayWidth = 20

    def _convert(self, value):
        if value == self._missing:
            return self.context.missing_value
        else:
            try:
                return parseDatetimetz(value)
            except (DateTimeError, ValueError, IndexError), v:
                raise ConversionError("Invalid datetime data", v)
                

class DateWidget(TextWidget):
    """Date entry widget.
    """

    displayWidth = 20

    def _convert(self, value):
        if value == self._missing:
            return self.context.missing_value
        else:
            try:
                return parseDatetimetz(value).date()
            except (DateTimeError, ValueError, IndexError), v:
                raise ConversionError("Invalid datetime data", v)
                

class TextAreaWidget(BrowserWidget):
    """TextArea widget.

    Multi-line text (unicode) input.

    >>> from zope.publisher.browser import TestRequest
    >>> from zope.schema import Text
    >>> field = Text(__name__='foo', title=u'on')
    >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
    >>> widget = TextAreaWidget(field, request)
    >>> widget.hasInput()
    True
    >>> widget.getInputValue()
    u'Hello\\nworld!'

    >>> def normalize(s):
    ...   return '\\n  '.join(filter(None, s.split(' ')))

    >>> print normalize( widget() )
    <textarea
      cols="60"
      id="field.foo"
      name="field.foo"
      rows="15"
      >Hello\r
    world!</textarea>

    >>> print normalize( widget.hidden() )
    <input
      class="hiddenType"
      id="field.foo"
      name="field.foo"
      type="hidden"
      value="Hello\r
    world!"
      />

    Calling setRenderedValue will change what gets output:

    >>> widget.setRenderedValue("Hey\\ndude!")
    >>> print normalize( widget() )
    <textarea
      cols="60"
      id="field.foo"
      name="field.foo"
      rows="15"
      >Hey\r
    dude!</textarea>

    """

    implements(IInputWidget)

    default = ""
    width = 60
    height = 15
    extra = ""
    style = ''

    def _convert(self, value):
        value = super(TextAreaWidget, self)._convert(value)
        if value:
            value = value.replace("\r\n", "\n")
        return value

    def _unconvert(self, value):
        value = super(TextAreaWidget, self)._unconvert(value)
        if value:
            value = value.replace("\n", "\r\n")
        return value

    def __call__(self):
        return renderElement("textarea",
                             name=self.name,
                             id=self.name,
                             cssClass=self.cssClass,
                             rows=self.height,
                             cols=self.width,
                             style=self.style,
                             contents=self._showData(),
                             extra=self.extra)

class BytesAreaWidget(Bytes, TextAreaWidget):
    """BytesArea widget.

    Multi-line string input.

    >>> from zope.publisher.browser import TestRequest
    >>> from zope.schema import Bytes
    >>> field = Bytes(__name__='foo', title=u'on')
    >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
    >>> widget = BytesAreaWidget(field, request)
    >>> widget.hasInput()
    True
    >>> widget.getInputValue()
    'Hello\\nworld!'
    """    

class PasswordWidget(TextWidget):
    """Password Widget"""
    
    type = 'password'

    def __call__(self):
        displayMaxWidth = self.displayMaxWidth or 0
        if displayMaxWidth > 0:
            return renderElement(self.tag,
                                 type=self.type,
                                 name=self.name,
                                 id=self.name,
                                 value='',
                                 cssClass=self.cssClass,
                                 style=self.style,
                                 size=self.displayWidth,
                                 maxlength=displayMaxWidth,
                                 extra=self.extra)
        else:
            return renderElement(self.tag,
                                 type=self.type,
                                 name=self.name,
                                 id=self.name,
                                 value='',
                                 cssClass=self.cssClass,
                                 style=self.style,
                                 size=self.displayWidth,
                                 extra=self.extra)

    def hidden(self):
        raise NotImplementedError(
            'Cannot get a hidden tag for a password field')
            

class FileWidget(TextWidget):
    """File Widget"""
    
    type = 'file'

    def __call__(self):
        displayMaxWidth = self.displayMaxWidth or 0
        if displayMaxWidth > 0:
            return renderElement(self.tag,
                                 type=self.type,
                                 name=self.name,
                                 id=self.name,
                                 cssClass=self.cssClass,
                                 size=self.displayWidth,
                                 maxlength=displayMaxWidth,
                                 extra=self.extra)
        else:
            return renderElement(self.tag,
                                 type=self.type,
                                 name=self.name,
                                 id=self.name,
                                 cssClass=self.cssClass,
                                 size=self.displayWidth,
                                 extra=self.extra)

    def hasInput(self):
        file = self.request.form.get(self.name)
        if file is None:
            return False

        if getattr(file, 'filename', ''):
            return True

        try:
            seek = file.seek
            read = file.read
        except AttributeError:
            return False

        seek(0)
        if read(1):
            return True

        return False

    def _convert(self, value):
        try:
            seek = value.seek
            read = value.read
        except AttributeError, e:
            raise ConversionError('Value is not a file object', e)
        else:
            seek(0)
            data = read()
            if data or getattr(value, 'filename', ''):
                return data
            else:
                return self.context.missing_value


class ItemsWidget(BrowserWidget):
    """A widget that has a number of items in it."""
    # What the heck is this for?
    
    implements(IInputWidget)
    

class SingleItemsWidget(ItemsWidget):
    """A widget with a number of items that has only a single
    selectable item."""
    
    default = ""
    firstItem = False

    def textForValue(self, value):
        '''Returns the text for the given value.

        Override this in subclasses.'''
        # The text could be a MessageID, in which case we should try to
        # translate it.
        return translate(self.context, value, context=self.request,
                         default=value)

    def renderItems(self, value):
        name = self.name
        # get items
        items = self.context.allowed_values

        # check if we want to select first item
        if (not value and getattr(self.context, 'firstItem', False)
            and len(items) > 0):
            value = items[0]

        cssClass = self.cssClass

        # FIXME: what if we run into multiple items with same value?
        rendered_items = []
        count = 0
        for item_value in items:
            item_text = self.textForValue(item_value)

            if item_value == value:
                rendered_item = self.renderSelectedItem(count,
                                                        item_text,
                                                        item_value,
                                                        name,
                                                        cssClass)
            else:
                rendered_item = self.renderItem(count,
                                                item_text,
                                                item_value,
                                                name,
                                                cssClass)

            rendered_items.append(rendered_item)
            count += 1

        return rendered_items


class ListWidget(SingleItemsWidget):
    """List widget."""
    
    size = 5

    def __call__(self):
        renderedItems = self.renderItems(self._showData())
        return renderElement('select',
                              name=self.name,
                              id=self.name,
                              cssClass=self.cssClass,
                              size=self.size,
                              contents="\n".join(renderedItems),
                              extra=self.extra)

    def renderItem(self, index, text, value, name, cssClass):
        return renderElement('option', contents=text, value=value,
                              cssClass=cssClass)

    def renderSelectedItem(self, index, text, value, name, cssClass):
        return renderElement('option', contents=text, value=value,
                              cssClass=cssClass, selected=None)


class RadioWidget(SingleItemsWidget):
    """Radio buttons widget."""
    
    orientation = "vertical"

    def __call__(self):
        rendered_items = self.renderItems(self._showData())
        orientation = self.orientation
        if orientation == 'horizontal':
            return "&nbsp;&nbsp;".join(rendered_items)
        else:
            return '<br />'.join(rendered_items)

    def _renderItem(self, index, text, value, name, cssClass, checked):
        id = '%s.%s' % (name, index)
        if checked:
            element = renderElement('input',
                                    type="radio",
                                    cssClass=cssClass,
                                    name=name,
                                    id=id,
                                    value=value,
                                    checked=None)
        else:
            element = renderElement('input',
                                    type="radio",
                                    cssClass=cssClass,
                                    name=name,
                                    id=id,
                                    value=value)

        return '%s<label for="%s">%s</label>' % (element, id, text)

    def renderItem(self, index, text, value, name, cssClass):
        return self._renderItem(index, text, value, name, cssClass, False)

    def renderSelectedItem(self, index, text, value, name, cssClass):
        return self._renderItem(index, text, value, name, cssClass, True)

    def label(self):
        return translate(self.context, self.title, context=self.request,
                         default=self.title)

    def row(self):
        return ('<div class="%s"><label for="%s">%s</label></div>'
                '<div class="field" id="%s">%s</div>' % (
                self.labelClass(), self.name, self.label(), self.name, self()))
                

class MultiItemsWidget(ItemsWidget):
    """A widget with a number of items that has multiple selectable items."""
        
    default = []

    def _convert(self, value):
        if not value:
            return []
        if isinstance(value, ListTypes):
            return value
        return [value]

    def renderItems(self, value):
        # need to deal with single item selects
        value = removeAllProxies(value)

        if not isinstance(value, ListTypes):
            value = [value]
        name = self.name
        items = self.context.allowed_values
        cssClass = self.cssClass
        rendered_items = []
        count = 0
        for item in items:
            try:
                item_value, item_text = item
            except ValueError:
                item_value = item
                item_text = item

            if item_value in value:
                rendered_item = self.renderSelectedItem(count,
                                                        item_text,
                                                        item_value,
                                                        name,
                                                        cssClass)
            else:
                rendered_item = self.renderItem(count,
                                                item_text,
                                                item_value,
                                                name,
                                                cssClass)

            rendered_items.append(rendered_item)
            count += 1

        return rendered_items


class MultiListWidget(MultiItemsWidget):
    """List widget with multiple select."""

    size = 5

    def __call__(self):
        rendered_items = self.renderItems(self._showData())
        return renderElement('select',
                              name=self.name,
                              id=self.name,
                              multiple=None,
                              cssClass=self.cssClass,
                              size=self.size,
                              contents="\n".join(rendered_items),
                              extra=self.extra)

    def renderItem(self, index, text, value, name, cssClass):
        return renderElement('option', contents=text, value=value)

    def renderSelectedItem(self, index, text, value, name, cssClass):
        return renderElement('option', contents=text, value=value,
                              selected=None)


class MultiCheckBoxWidget(MultiItemsWidget):
    """Multiple checkbox widget."""

    orientation = "vertical"

    def __call__(self):
        rendered_items = self.renderItems(self._showData())
        orientation = self.orientation
        if orientation == 'horizontal':
            return "&nbsp;&nbsp;".join(rendered_items)
        else:
            return "<br />".join(rendered_items)

    def renderItem(self, index, text, value, name, cssClass):
        return renderElement('input',
                              type="checkbox",
                              cssClass=cssClass,
                              name=name,
                              id=name,
                              value=value) + text

    def renderSelectedItem(self, index, text, value, name, cssClass):
        return renderElement('input',
                              type="checkbox",
                              cssClass=cssClass,
                              name=name,
                              id=name,
                              value=value,
                              checked=None) + text
                              

class SequenceWidget(BrowserWidget):
    """A widget baseclass for a sequence of fields.

    subwidget  - Optional CustomWidget used to generate widgets for the
                 items in the sequence
    """

    implements(IInputWidget)

    _type = tuple    
    _data = () # pre-existing sequence items (from setRenderedValue)

    def __init__(self, context, request, subwidget=None):
        super(SequenceWidget, self).__init__(context, request)

        self.subwidget = None

    def __call__(self):
        """Render the widget
        """
        # XXX we really shouldn't allow value_type of None
        if self.context.value_type is None:
            return ''

        render = []

        # length of sequence info
        sequence = list(self._generateSequence())
        num_items = len(sequence)
        min_length = self.context.min_length
        max_length = self.context.max_length

        # ensure minimum number of items in the form
        if num_items < min_length:
            for i in range(min_length - num_items):
                sequence.append(None)
        num_items = len(sequence)

        # generate each widget from items in the sequence - adding a
        # "remove" button for each one
        for i in range(num_items):
            value = sequence[i]
            render.append('<tr><td>')
            if num_items > min_length:
                render.append(
                    '<input type="checkbox" name="%s.remove_%d" />' % (
                    self.name, i)
                    )
            widget = self._getWidget(i)
            widget.setRenderedValue(value)
            render.append(widget() + '</td></tr>')

        # possibly generate the "remove" and "add" buttons
        buttons = ''
        if render and num_items > min_length:
            button_label = _('remove-selected-items', "Remove selected items")
            button_label = translate(self.context, button_label,
                                     context=self.request, default=button_label)
            buttons += '<input type="submit" value="%s" />' % button_label
        if max_length is None or num_items < max_length:
            field = self.context.value_type
            button_label = _('Add %s')
            button_label = translate(self.context, button_label,
                                     context=self.request, default=button_label)
            button_label = button_label % (field.title or field.__name__)
            buttons += '<input type="submit" name="%s.add" value="%s" />' % (
                self.name, button_label)
        if buttons:
            render.append('<tr><td>%s</td></tr>' % buttons)

        return '<table border="0">' + ''.join(render) + '</table>'

    def _getWidget(self, i):
        field = self.context.value_type
        if self.subwidget:
            widget = self.subwidget(field, self.request)
        else:
            widget = zapi.getViewProviding(field, IInputWidget, self.request,
                                           context=self.context)
        widget.setPrefix('%s.%d.'%(self.name, i))
        return widget

    def hidden(self):
        ''' Render the list as hidden fields '''
        # length of sequence info
        sequence = self._generateSequence()
        num_items = len(sequence)
        min_length = self.context.min_length

        # ensure minimum number of items in the form
        if num_items < min_length:
            for i in range(min_length - num_items):
                sequence.append(None)
        num_items = len(sequence)

        # generate hidden fields for each value
        s = ''
        for i in range(num_items):
            value = sequence[i]
            widget = self._getWidget(i)
            widget.setRenderedValue(value)
            s += widget.hidden()
        return s

    def getInputValue(self):
        """Return converted and validated widget data.

        If there is no user input and the field is required, then a
        MissingInputError will be raised.

        If there is no user input and the field is not required, then
        the field default value will be returned.

        A WidgetInputError is returned in the case of one or more
        errors encountered, inputting, converting, or validating the data.
        """
        sequence = self._generateSequence()
        # validate the input values
        for value in sequence:
            self.context.value_type.validate(value)
        return self._type(sequence)

    # XXX applyChanges isn't reporting "change" correctly (we're
    # re-generating the sequence with every edit, and need to be smarter)
    def applyChanges(self, content):
        field = self.context
        value = self.getInputValue()
        change = field.query(content, self) != value
        if change:
            field.set(content, value)
        return change

    def hasInput(self):
        """Is there input data for the field

        Return True if there is data and False otherwise.
        """
        return len(self._generateSequence()) != 0

    def setRenderedValue(self, value):
        """Set the default data for the widget.

        The given value should be used even if the user has entered
        data.
        """
        # the current list of values derived from the "value" parameter
        self._data = value

    def _generateSequence(self):
        """Take sequence info in the self.request and _data.
        """
        len_prefix = len(self.name)
        adding = False
        removing = []
        subprefix = re.compile(r'(\d+)\.(.*)$')
        if self.context.value_type is None:
            return []

        # pre-populate
        found = {}
        if self._data is not None:
            found = dict(enumerate(self._data))

        # now look through the request for interesting values
        for key in self.request.keys():
            if not key.startswith(self.name):
                continue
            token = key[len_prefix+1:]        # skip the '.'
            if token == 'add':
                # append a new blank field to the sequence
                adding = True
            elif token.startswith('remove_'):
                # remove the index indicated
                removing.append(int(token[7:]))
            else:
                match = subprefix.match(token)
                if match is None:
                    continue
                # key refers to a sub field
                i = int(match.group(1))

                # find a widget for the sub-field and use that to parse the
                # request data
                widget = self._getWidget(i)
                value = widget.getInputValue()
                found[i] = value

        # remove the indicated indexes
        for i in removing:
            del found[i]

        # generate the list, sorting the dict's contents by key
        items = found.items()
        items.sort()
        sequence = [value for key, value in items]

        # add an entry to the list if the add button has been pressed
        if adding:
            sequence.append(None)

        return sequence


class TupleSequenceWidget(SequenceWidget):
    pass

class ListSequenceWidget(SequenceWidget):
    
    _type = list


class ObjectWidget(BrowserWidget):
    """A widget over an Interface that contains Fields.

    "factory"  - factory used to create content that this widget (field)
                 represents
    *_widget   - Optional CustomWidgets used to generate widgets for the
                 fields in this widget
    """

    implements(IInputWidget)
    
    _object = None      # the object value (from setRenderedValue & request)
    _request_parsed = False

    def __init__(self, context, request, factory, **kw):
        super(ObjectWidget, self).__init__(context, request)

        # factory used to create content that this widget (field)
        # represents
        self.factory = factory

        # handle foo_widget specs being passed in
        self.names = getFieldNamesInOrder(self.context.schema)
        for k, v in kw.items():
            if k.endswith('_widget'):
                setattr(self, k, v)

        # set up my subwidgets
        self._setUpEditWidgets()

    def setPrefix(self, prefix):
        super(ObjectWidget, self).setPrefix(prefix)
        self._setUpEditWidgets()

    def _setUpEditWidgets(self):
        # subwidgets need a new name
        setUpEditWidgets(self, self.context.schema, source=self.context,
                         prefix=self.name, names=self.names, 
                         context=self.context)

    def __call__(self):
        """Render the widget
        """
        render = []

        # XXX see if there's some widget layout already

        # generate each widget from fields in the schema
        field = self.context
        title = field.title or field.__name__
        render.append('<fieldset><legend>%s</legend>'%title)
        for name, widget in self.getSubWidgets():
            render.append(widget.row())
        render.append('</fieldset>')

        return '\n'.join(render)

    def getSubWidgets(self):
        l = []
        for name in self.names:
            l.append((name, getattr(self, '%s_widget'%name)))
        return l

    def hidden(self):
        ''' Render the list as hidden fields '''
        for name, widget in self.getSubWidgets():
            s += widget.hidden()
        return s

    def getInputValue(self):
        """Return converted and validated widget data.

        The value for this field will be represented as an ObjectStorage
        instance which holds the subfield values as attributes. It will
        need to be converted by higher-level code into some more useful
        object (note that the default EditView calls applyChanges, which
        does this).
        """
        content = self.factory()
        for name, widget in self.getSubWidgets():
            setattr(content, name, widget.getInputValue())
        return content

    def applyChanges(self, content):
        field = self.context

        # create our new object value
        value = field.query(content, None)
        if value is None:
            # XXX ObjectCreatedEvent here would be nice
            value = self.factory()

        # apply sub changes, see if there *are* any changes
        # XXX ObjectModifiedEvent here would be nice
        changes = applyWidgetsChanges(self, field.schema, target=value,
                                      names=self.names)

        # if there's changes, then store the new value on the content
        if changes:
            field.set(content, value)

        return changes

    def hasInput(self):
        """Is there input data for the field

        Return True if there is data and False otherwise.
        """
        for name, widget in self.getSubWidgets():
            if widget.hasInput():
                return True
        return False

    def setRenderedValue(self, value):
        """Set the default data for the widget.

        The given value should be used even if the user has entered
        data.
        """
        # re-call setupwidgets with the content
        self._setUpEditWidgets()
        for name, widget in self.getSubWidgets():
            widget.setRenderedValue(getattr(value, name, None))
            

# XXX Note, some HTML quoting is needed in renderTag and renderElement.

def renderTag(tag, **kw):
    """Render the tag. Well, not all of it, as we may want to / it."""
    attr_list = []

    # special case handling for cssClass
    cssClass = ''
    if 'cssClass' in kw:
        if kw['cssClass']:
            cssClass = kw['cssClass']
        del kw['cssClass']

    # If the 'type' attribute is given, append this plus 'Type' as a
    # css class. This allows us to do subselector stuff in css without
    # necessarily having a browser that supports css subselectors.
    # This is important if you want to style radio inputs differently than
    # text inputs.
    cssWidgetType = kw.get('type')
    if cssWidgetType:
        cssWidgetType += 'Type'
    else:
        cssWidgetType = ''
    if cssWidgetType or cssClass:
        names = filter(None, (cssClass, cssWidgetType))
        attr_list.append('class="%s"' % ' '.join(names))

    if 'style' in kw:
        if kw['style'] != '':
            attr_list.append('style=%s' % quoteattr(kw['style']))
        del kw['style']

    # special case handling for extra 'raw' code
    if 'extra' in kw:
        extra = " " + kw['extra'] # could be empty string but we don't care
        del kw['extra']
    else:
        extra = ""

    # handle other attributes
    if kw:
        items = kw.items()
        items.sort()
        for key, value in items:
            if value == None:
                value = key
            attr_list.append('%s=%s' % (key, quoteattr(unicode(value))))

    if attr_list:
        attr_str = " ".join(attr_list)
        return "<%s %s%s" % (tag, attr_str, extra)
    else:
        return "<%s%s" % (tag, extra)


def renderElement(tag, **kw):
    if 'contents' in kw:
        contents = kw['contents']
        del kw['contents']
        return "%s>%s</%s>" % (renderTag(tag, **kw), contents, tag)
    else:
        return renderTag(tag, **kw) + " />"
        

def setUp():
    import zope.app.tests.placelesssetup
    global setUp
    setUp = zope.app.tests.placelesssetup.setUp
    setUp()
    

def tearDown():
    import zope.app.tests.placelesssetup
    global tearDown
    tearDown = zope.app.tests.placelesssetup.tearDown
    tearDown()


=== Added File Zope3/src/zope/app/form/browser/widgets.txt ===
Simple example showing ObjectWidget and SequenceWidget usage
============================================================

The following implements a Poll product (add it as
zope/products/demo/poll) which has poll options defined as:

label
  A TextLine holding the label of the option
description
  Another TextLine holding the description of the option

Simple stuff.

Our Poll product holds an editable list of the PollOption instances. This
is shown in the ``poll.py`` source below::

    from persistence import Persistent
    from interfaces import IPoll, IPollOption
    from zope.interface import implements, classImplements

    class PollOption(Persistent, object):
        implements(IPollOption)

    class Poll(Persistent, object):
        implements(IPoll)

        def getResponse(self, option):
            return self._responses[option]

        def choose(self, option):
            self._responses[option] += 1
            self._p_changed = 1

        def get_options(self):
            return self._options

        def set_options(self, options):
            self._options = options
            self._responses = {}
            for option in self._options:
                self._responses[option.label] = 0

        options = property(get_options, set_options, None, 'fiddle options')

And the Schemas are define in the ``interfaces.py`` file below::

    from zope.interface import Interface
    from zope.schema import Object, Tuple, TextLine
    from zope.schema.interfaces import ITextLine
    from zope.i18n import MessageIDFactory

    _ = MessageIDFactory("poll")

    class IPollOption(Interface):
        label = TextLine(title=u'Label', min_length=1)
        description = TextLine(title=u'Description', min_length=1)

    class IPoll(Interface):
        options = Tuple(title=u'Options',
            value_type=Object(IPollOption, title=u'Poll Option'))

        def getResponse(option): "get the response for an option"

        def choose(option): 'user chooses an option'

Note the use of the Tuple and Object schema fields above. The Tuple
could optionally have restrictions on the min or max number of items -
these will be enforce by the SequenceWidget form handling code. The Object
must specify the schema that is used to generate its data.

Now we have to specify the actual add and edit views. We use the existing
AddView and EditView, but we pre-define the widget for the sequence because
we need to pass in additional information. This is given in the
``browser.py`` file::

    from zope.app.container.interfaces import IAdding

    from zope.app.event import publish
    from zope.app.event.objectevent import ObjectModifiedEvent
    from zope.app.event.objectevent import ObjectCreatedEvent

    from zope.app.form.browser.editview import EditView
    from zope.app.form.browser.add import AddView
    from zope.app.form.widget import CustomWidgetFactory
    from zope.app.form.browser.widget import SequenceWidget, ObjectWidget

    from interfaces import IPoll
    from poll import Poll, PollOption

    class PollVoteView:
        __used_for__ = IPoll

        def choose(self, option):
            self.context.choose(option)
            self.request.response.redirect('.')

    ow = CustomWidgetFactory(ObjectWidget, PollOption)
    sw = CustomWidgetFactory(SequenceWidget, subwidget=ow)

    class PollEditView(EditView):
        __used_for__ = IPoll

        options_widget = sw

    class PollAddView(AddView):
        __used_for__ = IPoll

        options_widget = sw

Note the creation of the widget via a CustomWidgetFactory. So,
whenever the options_widget is used, a new
``SequenceWidget(subwidget=CustomWidgetFactory(ObjectWidget,
PollOption))`` is created. The subwidget argument indicates that each
item in the sequence should be represented by the indicated widget
instead of their default. If the contents of the sequence were just
Text Fields, then the default would be just fine - the only odd cases
are Sequence and Object Widgets because they need additional arguments
when they're created.

Each item in the sequence will be represented by a
``CustomWidgetFactory(ObjectWidget, PollOption)`` - thus a new
``ObjectWidget(context, request, PollOption)`` is created for each
one. The PollOption class ("factory") is used to create new instances
when new data is created in add forms (or edit forms when we're adding
new items to a Sequence).

Tying all this together is the ``configure.zcml``::

    <configure xmlns='http://namespaces.zope.org/zope'
        xmlns:browser='http://namespaces.zope.org/browser'>

    <content class=".poll.Poll">
    <factory id="zope.products.demo.poll"
             permission="zope.ManageContent" />

    <implements 
        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
        />

    <require
        permission="zope.View"
        interface=".interfaces.IPoll"
        />

    <require
        permission="zope.ManageContent"
        set_schema=".interfaces.IPoll"
        />
    </content>

    <content class=".poll.PollOption">
    <require
        permission="zope.View"
        interface=".interfaces.IPollOption"
        />
    </content>

    <browser:page for=".interfaces.IPoll"
        name="index.html"
        template="results.zpt"
        permission="zope.View"
        menu="zmi_views" title="View"
        />

    <browser:pages
        for=".interfaces.IPoll"
        class=".browser.PollVoteView"
        permission="zope.ManageContent">
        <browser:page name="vote.html" template="vote.zpt"
            menu="zmi_views" title="Vote" />
        <browser:page name="choose" attribute="choose" />
    </browser:pages>

    <browser:addform
        schema=".interfaces.IPoll"
        label="Add a Poll"
        content_factory=".poll.Poll"
        name="zope.products.demo.poll"
        class=".browser.PollAddView"
        menu="add_content" title="Poll"
        permission="zope.ManageContent" />

    <browser:editform
        schema=".interfaces.IPoll"
        class=".browser.PollEditView"
        label="Change a Poll"
        name="edit.html"
        menu="zmi_views" title="Edit"
        permission="zope.ManageContent" />

    </configure>

Note the use of the "class" argument to the addform and editform
directives. Otherwise, nothing much exciting here.

Finally, we have some additiona views...

``results.zpt``::

    <html metal:use-macro="context/@@standard_macros/page">
    <title metal:fill-slot="title">Poll results</title>
    <div metal:fill-slot="body">
    <table border="1">
    <caption>Poll results</caption>
    <thead>
        <tr><th>Option</th><th>Results</th><th>Description</th></tr>
    </thead>
    <tbody>
        <tr tal:repeat="option context/options">
        <td tal:content="option/label">Option</td>
        <td tal:content="python:context.getResponse(option.label)">Result</td>
        <td tal:content="option/description">Option</td>
        </tr>
    </tbody>
    </table>
    </div>
    </html>

``vote.zpt``::

    <html metal:use-macro="context/@@standard_macros/page">
    <title metal:fill-slot="title">Poll voting</title>
    <div metal:fill-slot="body">
    <form action="choose">
    <table border="1">
    <caption>Poll voting</caption>
    <tbody>
        <tr tal:repeat="option context/options">
        <td><input type="radio" name="option"
                    tal:attributes="value option/label"></td>
        <td tal:content="option/label">Option</td>
        <td tal:content="option/description">Option</td>
        </tr>
    </tbody>
    </table>
    <input type="submit">
    </form>
    </div>
    </html>





More information about the Zope3-Checkins mailing list