[Checkins] SVN: z3c.multiform/Sandbox/src/z3c/multiform/ Started a
common place for the upcoming z3c package multiform.
Stefan Martin
s.martin at iwm-kmrc.de
Sat Apr 15 16:53:52 EDT 2006
Log message for revision 67065:
Started a common place for the upcoming z3c package multiform.
Changed:
A z3c.multiform/Sandbox/src/z3c/multiform/README.txt
A z3c.multiform/Sandbox/src/z3c/multiform/TODO.txt
A z3c.multiform/Sandbox/src/z3c/multiform/__init__.py
A z3c.multiform/Sandbox/src/z3c/multiform/actions.txt
A z3c.multiform/Sandbox/src/z3c/multiform/configure.zcml
A z3c.multiform/Sandbox/src/z3c/multiform/container/
A z3c.multiform/Sandbox/src/z3c/multiform/container/__init__.py
A z3c.multiform/Sandbox/src/z3c/multiform/container/configure.zcml
A z3c.multiform/Sandbox/src/z3c/multiform/container/container.txt
A z3c.multiform/Sandbox/src/z3c/multiform/container/grid.pt
A z3c.multiform/Sandbox/src/z3c/multiform/container/griditem.pt
A z3c.multiform/Sandbox/src/z3c/multiform/container/interfaces.py
A z3c.multiform/Sandbox/src/z3c/multiform/container/location.py
A z3c.multiform/Sandbox/src/z3c/multiform/container/tests.py
A z3c.multiform/Sandbox/src/z3c/multiform/container/views.py
A z3c.multiform/Sandbox/src/z3c/multiform/grid.pt
A z3c.multiform/Sandbox/src/z3c/multiform/gridform.py
A z3c.multiform/Sandbox/src/z3c/multiform/gridform.txt
A z3c.multiform/Sandbox/src/z3c/multiform/griditem.pt
A z3c.multiform/Sandbox/src/z3c/multiform/interfaces.py
A z3c.multiform/Sandbox/src/z3c/multiform/multiform.py
A z3c.multiform/Sandbox/src/z3c/multiform/resources/
A z3c.multiform/Sandbox/src/z3c/multiform/resources/sort_down.gif
A z3c.multiform/Sandbox/src/z3c/multiform/resources/sort_up.gif
A z3c.multiform/Sandbox/src/z3c/multiform/selection.py
A z3c.multiform/Sandbox/src/z3c/multiform/selections.txt
A z3c.multiform/Sandbox/src/z3c/multiform/sort.py
A z3c.multiform/Sandbox/src/z3c/multiform/tests.py
A z3c.multiform/Sandbox/src/z3c/multiform/z3c.multiform-configure.zcml
-=-
Added: z3c.multiform/Sandbox/src/z3c/multiform/README.txt
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/README.txt 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/README.txt 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,88 @@
+===================
+ Multiform Package
+===================
+
+This Package provides an API to handle multiple forms with matching
+form fields on multiple items. The creation of multiforms is derived
+from the Form class of the formlib package.
+
+ >>> from zope import interface, schema
+ >>> from zope.app.location.interfaces import ILocation
+
+ >>> class IOrder(interface.Interface):
+ ... identifier = schema.Int(title=u"Identifier", readonly=True)
+ ... name = schema.TextLine(title=u"Name")
+ >>> class Order:
+ ... interface.implements(IOrder,ILocation)
+ ...
+ ... def __init__(self, identifier, name=''):
+ ... self.identifier = identifier
+ ... self.name = name
+ ... self.__name__ = name
+
+ >>> orderMapping = dict([(str(k),Order(k,name='n%s'%k)) for k in range(5)])
+
+
+Let us create a new multiform class which should display IOrder objects.
+
+ >>> from zope.formlib import form
+ >>> from z3c.multiform.multiform import MultiFormBase,ItemFormBase
+
+ >>> class OrderForm(ItemFormBase):
+ ... form_fields = form.Fields(IOrder,omit_readonly=False,
+ ... render_context=True)
+ ... def __call__(self, ignore_request=False):
+ ... widgets = form.setUpWidgets(
+ ... self.form_fields, self.prefix, self.context, self.request,
+ ... ignore_request=ignore_request)
+ ... return '\n<div>%s</div>\n' % '</div><div>'.join([w() for w in
+ ... widgets])
+
+
+ >>> class OrdersForm(MultiFormBase):
+ ...
+ ... itemFormFactory = OrderForm
+ ... def __call__(self, ignore_request=False):
+ ... self.setUpWidgets()
+ ... res = u''
+ ... names = sorted(self.subForms.keys())
+ ... for name in names:
+ ... res += '<div>%s</div>\n' % self.subForms[name](
+ ... ignore_request=ignore_request)
+ ... return res
+ ...
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
+ >>> view = OrdersForm(orderMapping, request)
+ >>> print view()
+ <div>
+ <div>0</div><div><input ... name="form.0.name" ... value="n0" ...</div>
+ </div>
+ <div>
+ ...
+ </div>
+
+If the request contains any form data, that will be reflected in the
+output:
+
+ >>> request.form['form.1.name'] = u'bob'
+ >>> print OrdersForm(orderMapping,request)()
+ <div>
+ ...
+ <div>1</div><div><input ... name="form.1.name" ... value="bob" ...</div>
+ ...
+ </div>
+
+Sometimes we don't want this behavior: we want to ignore the request values,
+particularly after a form has been processed and before it is drawn again.
+This can be accomplished with the 'ignore_request' argument in
+setUpWidgets.
+
+ >>> print OrdersForm(orderMapping, request)(ignore_request=True)
+ <div>
+ ...
+ <div>1</div><div><input ... name="form.1.name" ... value="n1" ...</div>
+ ...
+ </div>
+
Added: z3c.multiform/Sandbox/src/z3c/multiform/TODO.txt
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/TODO.txt 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/TODO.txt 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,40 @@
+TODO
+====
+
+We are at the very beginning. The first concrete multiform is
+container.views.ContainerGridForm. The actions should work fine.
+
+Missing functionalities:
+------------------------
+
+ - customwidget for container.interfaces.IMovableLocation['__name__']
+
+ Returns an inputwidget if an IInputWidget is needed and returns
+ an displaywidget if an IDisplayWidget is needed.
+
+ - Size columns
+
+ Enable to register a size information field (ISized has only IMethods).
+ Pay attention to register a customwidget to display the size.
+
+ - Sort and Batch
+
+ Write sort and batch handling in the template. Show the current sort
+ column and sort direction. Show the current batch.
+
+ - Catch DuplicateError in handle_save_action
+
+ - View Errors and Status of each ItemForm
+
+ - Use namedtemplate for ContainerGridForm
+
+ - write more test in gridform.txt
+
+ Test singleedit and singlesave
+
+ - write functional tests for ContainerGridForm
+
+ - sort_down and sort_up gifs from the skin
+
+ It would be a nice feature of a skin to provides these to icons
+
Added: z3c.multiform/Sandbox/src/z3c/multiform/__init__.py
===================================================================
Added: z3c.multiform/Sandbox/src/z3c/multiform/actions.txt
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/actions.txt 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/actions.txt 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,197 @@
+===================
+ Multiform Actions
+===================
+
+The multiform package defines a new type of action called
+ParentAction. A parent action is rendered only one time in the parent
+multiform, but applied to all subforms.
+
+In this example we create a specialized item form class, which defines
+a new parent action called ``Save``. Additionally two standard actions
+are defined in the multiform (parent) class called ``Edit`` and
+``Cancel```.
+
+ >>> from zope import interface, schema
+ >>> from zope.formlib import form
+ >>> from zope.publisher.browser import TestRequest
+ >>> from zope.app.form.interfaces import IInputWidget
+ >>> from zope.app.location.interfaces import ILocation
+ >>> from z3c.multiform import multiform
+ >>> from z3c.multiform.multiform import ItemFormBase,MultiFormBase
+
+ >>> class IOrder(interface.Interface):
+ ... identifier = schema.Int(title=u"Identifier", readonly=True)
+ ... name = schema.TextLine(title=u"Name")
+ >>> class Order:
+ ... interface.implements(IOrder,ILocation)
+ ...
+ ... def __init__(self, identifier, name=''):
+ ... self.identifier = identifier
+ ... self.name = name
+ ... self.__name__= name
+
+ >>> orderMapping = dict([('n%s'%k,Order(k,name='n%s'%k)) for k in range(2)])
+
+ >>> class OrderForm(ItemFormBase):
+ ... inputMode=False
+ ... form_fields = form.Fields(IOrder,omit_readonly=False,
+ ... render_context=True)
+ ...
+ ... @multiform.parentAction(u"Save",
+ ... condition=multiform.anySubFormInputMode,inputMode=True)
+ ... def handle_save_action(self, action, data):
+ ... form.applyChanges(self.context, self.form_fields,
+ ... data, self.adapters)
+ ... self.newInputMode = False
+ ...
+ ... @form.action(u"Upper", condition=multiform.allSubFormsDisplayMode)
+ ... def handle_uppercase_action(self, action, data):
+ ... self.context.name = self.context.name.upper()
+ ...
+ ... def template(self):
+ ... return '\n<div>%s</div>\n' % '</div><div>'.join([w() for w in
+ ... self.widgets] + [action.render() for action in
+ ... self.availableActions()])
+
+
+ >>> class OrdersForm(MultiFormBase):
+ ... itemFormFactory=OrderForm
+ ... def template(self):
+ ... res = u''
+ ... names = sorted(self.subForms.keys())
+ ... for name in names:
+ ... res += '<div>%s</div>\n' % self.subForms[name].render()
+ ... for action in self.availableActions():
+ ... res += '<div>%s</div>\n' % action.render()
+ ... for action in self.availableSubActions():
+ ... res += '<div>%s</div>\n' % action.render()
+ ... return res
+ ...
+ ... @form.action('Edit',condition=multiform.allSubFormsDisplayMode)
+ ... def handle_edit_action(self, action, data):
+ ... for form in self.subForms.values():
+ ... form.newInputMode = True
+ ...
+ ... @form.action('Cancel',condition=multiform.anySubFormInputMode)
+ ... def handle_cancel_action(self, action, data):
+ ... for form in self.subForms.values():
+ ... form.newInputMode = False
+
+
+So in our new form all widgets are display widgets per default
+
+ >>> request = TestRequest()
+ >>> pf = OrdersForm(orderMapping,request)
+ >>> print pf()
+ <div>
+ <div>0</div><div>n0</div>...<input...name="form.n0.actions.upper"...
+ </div>
+ <div>
+ <div>1</div><div>n1</div>...<input...name="form.n1.actions.upper"...
+ </div>
+ <div><input...name="form.actions.edit"...</div>
+
+And the save action should not be available, due to the reason that there
+are no input widgets in the sub forms.
+
+ >>> pf.subActionNames
+ []
+ >>> [action.__name__ for action in pf.availableActions()]
+ [u'form.actions.edit']
+
+Now let's call the edit action to set the widgets to input widgets.
+
+ >>> request.form['form.actions.edit']=u''
+ >>> pf = OrdersForm(orderMapping,request)
+ >>> print pf()
+ <div>
+ <div...<input class="textType" ... value="n0"...
+ </div>
+ <div>
+ <div...<input class="textType" ... value="n1"...
+ <div><input...name="form.actions.cancel"...</div>
+ <div><input...name="form.actions.save"...</div>
+
+Now the save action should be available in the subActionNames and
+the cancel action in the multiform actions.
+
+ >>> pf.subActionNames
+ [u'form.actions.save']
+ >>> [a.__name__ for a in pf.availableActions()]
+ [u'form.actions.cancel']
+
+
+Now Let us save some data.
+
+ >>> request = TestRequest()
+ >>> request.form['form.actions.save']=u''
+ >>> for i in range(2):
+ ... request.form['form.n%s.name' % i]='newer name %s' % i
+ ... request.form['form.n%s.identifier' % i]= i
+ >>> pf = OrdersForm(orderMapping,request)
+ >>> result = pf()
+
+After the form is called, the changes are applied to the objects.
+ >>> sorted([obj.name for obj in orderMapping.values()])
+ [u'newer name 0', u'newer name 1']
+
+Due to the reason the save handler sets the inputMode to False,
+only display widgets are rendered in the results
+
+ >>> print result
+ <div>
+ <div>0</div><div>newer name 0</div>...
+ </div>
+ <div>
+ <div>1</div><div>newer name 1</div>...
+ </div>
+ ...
+
+Now we should only have the edit action be available, which is a
+multiform action, therefore not contained in the subActionNames
+
+ >>> pf.subActionNames
+ []
+ >>> [a.__name__ for a in pf.availableActions()]
+ [u'form.actions.edit']
+
+Now Let us cancel the edit mode.
+
+ >>> request = TestRequest()
+ >>> request.form['form.actions.cancel']=u''
+ >>> for i in range(2):
+ ... request.form['form.n%s.name' % i]='next name %s' % i
+ ... request.form['form.n%s.identifier' % i]= i
+ >>> pf = OrdersForm(orderMapping,request)
+ >>> result = pf()
+
+After the form is called, the objects are left unchanged. And the form
+should be in display mode again.
+
+ >>> sorted([obj.name for obj in orderMapping.values()])
+ [u'newer name 0', u'newer name 1']
+
+ >>> print result
+ <div>
+ <div>0</div><div>newer name 0</div>...
+ </div>
+ <div>
+ <div>1</div><div>newer name 1</div>...
+ </div>...
+
+Now let us call the upper action, which should uppercase the name
+attributes of the items. This action is an action on the items itself,
+so we have a unique prefix with the key of the item in the parent
+mapping.
+
+ >>> request = TestRequest()
+ >>> request.form['form.n1.actions.upper']=u''
+ >>> pf = OrdersForm(orderMapping,request)
+ >>> result = pf()
+ >>> print result
+ <div>
+ <div>0</div><div>newer name 0</div>...
+ </div>
+ <div>
+ <div>1</div><div>NEWER NAME 1</div>...
+ </div>...
Added: z3c.multiform/Sandbox/src/z3c/multiform/configure.zcml
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/configure.zcml 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/configure.zcml 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<configure xmlns="http://namespaces.zope.org/zope"
+ xmlns:i18n="http://namespaces.zope.org/i18n"
+ i18n_domain="zope">
+
+ <adapter for="zope.app.location.interfaces.ILocation
+ zope.formlib.interfaces.IForm"
+ provides=".interfaces.IFormLocation"
+ factory=".selection.FormLocationProxy"
+ trusted="true"
+ />
+
+ <adapter for=".interfaces.IFormLocation"
+ provides=".interfaces.ISelection"
+ factory=".selection.FormLocationSelection"
+ />
+
+ <adapter for="zope.interface.Interface
+ zope.schema.interfaces.IField"
+ factory=".sort.SchemaSorter"
+ provides=".interfaces.ISorter"
+ />
+
+ <!-- namedtemplate for IParentAction -->
+ <adapter factory=".multiform.render_submit_button" name="render" />
+
+ <include package=".container"/>
+
+</configure>
\ No newline at end of file
Added: z3c.multiform/Sandbox/src/z3c/multiform/container/__init__.py
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/container/__init__.py 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/container/__init__.py 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,3 @@
+# container
+
+
Added: z3c.multiform/Sandbox/src/z3c/multiform/container/configure.zcml
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/container/configure.zcml 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/container/configure.zcml 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<configure xmlns:zope="http://namespaces.zope.org/zope"
+ xmlns="http://namespaces.zope.org/browser"
+ xmlns:i18n="http://namespaces.zope.org/i18n"
+ i18n_domain="zope">
+
+ <page for="zope.app.container.interfaces.IContainer"
+ name="grid.html"
+ permission="zope.View"
+ class=".views.ContainerGridForm"/>
+
+ <zope:adapter for="zope.app.location.interfaces.ILocation"
+ factory=".location.MovableLocation"
+ provides=".interfaces.IMovableLocation"/>
+
+</configure>
+
Added: z3c.multiform/Sandbox/src/z3c/multiform/container/container.txt
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/container/container.txt 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/container/container.txt 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,46 @@
+=====================
+ Container Grid Form
+=====================
+
+A container contents view implementation using multiform. This view
+provides cut, copy, paste, edit and save actions. Additionally the
+__name__ attribute of the items can be transparently changed.
+
+In order to test the behaviour of the ``ContainerGridForm`` we
+subclass it and override the template method.
+
+ >>> from zope import interface
+ >>> from zope.publisher.browser import TestRequest
+ >>> from zope.app.container.sample import SampleContainer
+ >>> from zope.app.annotation.interfaces import IAttributeAnnotatable
+ >>> from zope.app.dublincore.interfaces import IZopeDublinCore
+ >>> from zope.app.dublincore.interfaces import IDCDescriptiveProperties
+ >>> from z3c.multiform.container.views import ContainerGridForm
+
+ >>> class ContainerTest(ContainerGridForm):
+ ... actions = ContainerGridForm.actions.copy()
+ ... def template(self):
+ ... res = u''
+ ... forms = list(self.getForms())
+ ... forms.sort(lambda x,y: cmp(x.prefix,y.prefix))
+ ... for form in forms:
+ ... res += '<div>%s</div>\n' % form.render()
+ ... return res
+
+ >>> c = SampleContainer()
+ >>> for i in range(2):
+ ... o = SampleContainer()
+ ... interface.directlyProvides(o,IAttributeAnnotatable)
+ ... c[u'name of %s' % i]=o
+
+ >>> request = TestRequest()
+ >>> view = ContainerTest(c,request)
+ >>> print view()
+ <div...<td>name of 0</td>...<td>name of 1</td>...
+
+
+
+
+
+
+
Added: z3c.multiform/Sandbox/src/z3c/multiform/container/grid.pt
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/container/grid.pt 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/container/grid.pt 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,195 @@
+<html metal:extend-macro="context/@@standard_macros/view"
+ metal:define-macro="main">
+<head>
+</head>
+
+<body>
+<div metal:fill-slot="body">
+
+<div metal:define-macro="form">
+
+<form action="." metal:define-macro="master"
+ tal:attributes="action request/URL" method="post"
+ class="edit-form" enctype="multipart/form-data"
+ id="zc.page.browser_form">
+
+<script type="text/javascript"><!--
+
+function toggleFormFieldHelp(ob,state) {
+ // ob is the label element
+ var field = ob.form[ob.htmlFor];
+ if (field) {
+ var viz = state && 'hidden' || 'visible';
+ if (field.length == null) {
+ field.style.visibility = viz;
+ }
+ else {
+ for (var i = 0; i < field.length; ++i) {
+ var e = field.item(i);
+ e.style.visibility = viz;
+ }
+ }
+ var help = document.getElementById("field-help-for-" + ob.htmlFor);
+ if (help) {
+ help.style.visibility = state && 'visible' || 'hidden';
+ }
+ }
+}
+
+//-->
+</script>
+
+<div id="viewspace" metal:define-slot="viewspace">
+
+ <h1 i18n:translate=""
+ tal:condition="view/label"
+ tal:content="view/label"
+ metal:define-slot="heading"
+ >Do something</h1>
+
+ <metal:block define-macro="header">
+
+ <div class="form-status"
+ tal:define="status view/status"
+ tal:condition="status">
+
+ <div class="summary"
+ i18n:translate=""
+ tal:content="view/status">
+ Form status summary
+ </div>
+
+ <ul class="errors" tal:condition="view/errors">
+ <li tal:repeat="error view/error_views">
+ <span tal:replace="structure error">Error Type</span>
+ </li>
+ </ul>
+ </div>
+
+ </metal:block>
+
+ <div metal:define-slot="extra_info" tal:replace="nothing">
+ </div>
+
+ <div metal:define-slot="main_form">
+ <table class="form-fields" metal:define-macro="formtable">
+ <tr class="row" metal:define-slot="extra_top" tal:replace="nothing">
+ <td class="label">Extra top</td>
+ <td class="field"><input type="text" /></td>
+ </tr>
+ <tbody metal:define-slot="formbody" tal:omit-tag="">
+ <tal:block omit-tag="" repeat="widget view/widgets">
+ <tr metal:define-macro="formrow">
+ <td class="label" tal:define="hint widget/hint"
+ metal:define-macro="labelcell">
+ <label tal:condition="python:hint"
+ tal:attributes="for widget/name"
+ onmousedown="toggleFormFieldHelp(this,1)"
+ onmouseup="toggleFormFieldHelp(this,0)"
+ onmouseout="toggleFormFieldHelp(this,0)"
+ style="cursor: help">
+ <span class="required" tal:condition="widget/required"
+ >*</span><span i18n:translate=""
+ tal:content="widget/label">label</span>
+ </label>
+ <label tal:condition="python:not hint"
+ tal:attributes="for widget/name">
+ <span class="required" tal:condition="widget/required"
+ >*</span><span i18n:translate=""
+ tal:content="widget/label">label</span>
+ </label>
+ </td>
+ <td class="field" tal:define="hint widget/hint"
+ metal:define-macro="widgetcell">
+ <div class="form-fields-help"
+ i18n:translate=""
+ tal:content="hint"
+ tal:condition="hint"
+ tal:attributes="id string:field-help-for-${widget/name}"
+ onclick="this.style.visibility='hidden';"
+ style="visibility: hidden; position: absolute;"
+ >Title of this content object.</div>
+ <div class="widget" tal:content="structure widget">
+ <input type="text" /></div>
+ <div class="error"
+ tal:condition="widget/error"
+ >
+ <!-- TODO Put this back, the Zope3 way.
+ <img src="alert.gif" alt="Error"
+ tal:replace="structure context/alert.gif" />
+ -->
+ <span tal:replace="structure widget/error">error</span>
+ </div>
+ </td>
+ </tr>
+ </tal:block>
+ </tbody>
+ <tr class="row" metal:define-slot="extra_bottom" tal:replace="nothing">
+ <td class="label">Extra bottom</td>
+ <td class="label"><input type="text" /></td>
+ </tr>
+ </table>
+ </div>
+ <!-- start: subforms -->
+ <table id="sortable" class="listing"
+ summary="Content listing">
+ <thead>
+ <tr>
+ <th tal:repeat="field view/itemFormFactory/form_fields"
+ tal:content="field/field/title" i18n:translate="">
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr tal:repeat="subForm view/getForms"
+ tal:attributes="class python:repeat['subForm'].even and 'even' or 'odd'"
+ tal:content="structure subForm"/>
+ </tbody>
+ </table>
+
+ <!-- end: subforms -->
+
+ <!-- start: parentActions -->
+
+ <div id="parentActionsView"
+ metal:define-macro="form_parent_actions">
+ <span class="actionButtons"
+ tal:condition="view/availableSubActions"
+ metal:define-slot="bottom_buttons">
+ <input tal:repeat="action view/availableSubActions"
+ tal:replace="structure action/render"
+ />
+ </span>
+</div>
+
+ <!-- end: parentActions -->
+
+
+
+ <metal:block define-slot="above_buttons" />
+</div>
+<div id="actionsView"
+ metal:define-macro="form_actions">
+ <span class="actionButtons"
+ tal:condition="view/availableActions"
+ metal:define-slot="bottom_buttons">
+ <input tal:repeat="action view/actions"
+ tal:replace="structure action/render"
+ />
+ </span>
+</div>
+
+</form>
+
+<div tal:content="view/request/form"/>
+
+<script type="text/javascript" metal:define-slot="trackChanges">
+ zc_trackChanges(document.getElementById('zc.page.browser_form'));
+</script>
+
+<script type="text/javascript"
+ tal:define="extra_script view/extra_script | nothing"
+ tal:condition="extra_script"
+ tal:content="structure extra_script" />
+
+</div></div></body></html>
Added: z3c.multiform/Sandbox/src/z3c/multiform/container/griditem.pt
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/container/griditem.pt 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/container/griditem.pt 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,2 @@
+<td tal:repeat="widget view/widgets"
+ tal:content="structure widget"/>
Added: z3c.multiform/Sandbox/src/z3c/multiform/container/interfaces.py
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/container/interfaces.py 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/container/interfaces.py 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,16 @@
+from zope import schema
+from zope.interface import Interface, Attribute
+from zope.app.location.interfaces import ILocation
+
+
+class IMovableLocation(ILocation):
+
+ """a located object that can change its __name__ attribute by
+ itself"""
+
+ __name__ = schema.TextLine(
+ title=u"The name within the parent",
+ description=u"Traverse the parent with this name to get the object.",
+ required=False,
+ default=None)
+
Added: z3c.multiform/Sandbox/src/z3c/multiform/container/location.py
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/container/location.py 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/container/location.py 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,37 @@
+from zope.interface import implements
+from zope.security.proxy import removeSecurityProxy
+from zope.app.copypastemove.interfaces import IContainerItemRenamer
+
+from z3c.multiform.interfaces import IFormLocation
+from interfaces import IMovableLocation
+
+
+class MovableLocation(object):
+
+ implements(IMovableLocation)
+
+ def __init__(self,context):
+ self.context = context
+ self.__parent__ = self.context.__parent__
+
+ def _setName(self,v):
+ old = self.context.__name__
+ if v != old:
+ renamer = IContainerItemRenamer(self.__parent__)
+ renamer.renameItem(old, v)
+ if IFormLocation.providedBy(self.context):
+ # if we are in a multiform, we have to add the request
+ # data to our prefix
+ form = removeSecurityProxy(self.context.__form__)
+ oldPrefix = form.prefix
+ newPrefix = form.prefix[:-len(old)] + v
+ for oldKey in list(form.request.form.keys()):
+ if oldKey.startswith(oldPrefix):
+ newKey = newPrefix + oldKey[len(oldPrefix):]
+ form.request.form[newKey]=form.request.form[oldKey]
+
+ def _getName(self):
+ return self.context.__name__
+
+ __name__ = property(_getName,_setName)
+
Added: z3c.multiform/Sandbox/src/z3c/multiform/container/tests.py
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/container/tests.py 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/container/tests.py 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,115 @@
+import doctest
+import unittest
+import location
+
+from zope import component
+from zope.testing.doctestunit import DocFileSuite, DocTestSuite
+from zope.app.testing import setup
+import zope.schema.interfaces
+import zope.app.form.browser
+import zope.publisher.interfaces.browser
+import zope.app.form.interfaces
+from zope.app.dublincore.annotatableadapter import ZDCAnnotatableAdapter
+from zope.app.dublincore.interfaces import IWriteZopeDublinCore
+from zope.app.annotation.interfaces import IAnnotatable
+from zope.app.location.interfaces import ILocation
+from zope.formlib import form
+
+from z3c.multiform import gridform,multiform,selection
+from z3c.multiform.interfaces import IFormLocation,ISelection
+from interfaces import IMovableLocation
+
+
+def setUp(test):
+ setup.placefulSetUp()
+
+ component.provideAdapter(
+ zope.app.form.browser.TextWidget,
+ [zope.schema.interfaces.ITextLine,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IInputWidget,
+ )
+ component.provideAdapter(
+ zope.app.form.browser.UnicodeDisplayWidget,
+ [zope.schema.interfaces.ITextLine,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IDisplayWidget,
+ )
+ component.provideAdapter(
+ zope.app.form.browser.boolwidgets.BooleanDisplayWidget,
+ [zope.schema.interfaces.IBool,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IDisplayWidget,
+ )
+ component.provideAdapter(
+ zope.app.form.browser.CheckBoxWidget,
+ [zope.schema.interfaces.IBool,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IInputWidget,
+ )
+ component.provideAdapter(
+ zope.app.form.browser.UnicodeDisplayWidget,
+ [zope.schema.interfaces.IInt,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IDisplayWidget,
+ )
+ component.provideAdapter(
+ zope.app.form.browser.IntWidget,
+ [zope.schema.interfaces.IInt,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IInputWidget,
+ )
+ component.provideAdapter(
+ selection.FormLocationProxy,
+ [zope.app.location.interfaces.ILocation,
+ zope.formlib.interfaces.IForm
+ ],
+ IFormLocation
+ )
+ component.provideAdapter(
+ selection.FormLocationSelection,
+ [IFormLocation],
+ ISelection
+ )
+ component.provideAdapter(
+ location.MovableLocation,
+ [ILocation],
+ IMovableLocation
+ )
+ component.provideAdapter(
+ ZDCAnnotatableAdapter,
+ [IAnnotatable],
+ IWriteZopeDublinCore
+ )
+
+
+ component.provideAdapter(gridform.default_grid_template,
+ name="default")
+ component.provideAdapter(gridform.default_griditem_template,
+ name="default")
+ component.provideAdapter(form.render_submit_button, name='render')
+ component.provideAdapter(multiform.render_submit_button, name='render')
+
+def tearDown(test):
+ setup.placefulTearDown()
+
+
+def test_suite():
+
+ return unittest.TestSuite(
+ (
+ DocFileSuite('container.txt',
+ setUp=setUp, tearDown=tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ ))
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Added: z3c.multiform/Sandbox/src/z3c/multiform/container/views.py
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/container/views.py 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/container/views.py 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,302 @@
+import datetime
+import pytz
+from zope.interface.common import idatetime
+from zope.formlib.i18n import _
+from zope.formlib import form
+from zope.app.dublincore.interfaces import IWriteZopeDublinCore
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.event import notify
+from zope.app.event.objectevent import ObjectModifiedEvent
+from zope.app import zapi
+from zope.app.copypastemove.interfaces import IPrincipalClipboard
+from zope.app.copypastemove.interfaces import IObjectCopier
+from zope.app.copypastemove.interfaces import IObjectMover
+from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
+from zope.app.container.interfaces import DuplicateIDError
+from zope.security.interfaces import Unauthorized
+from zope.app.traversing.interfaces import TraversalError
+
+from z3c.multiform import multiform, gridform
+from z3c.multiform.interfaces import ISelection
+from interfaces import IMovableLocation
+
+
+def isSelected(form,action):
+ return ISelection(form.context).selected
+
+
+def isSelectedInput(form,action):
+ print "isSelectedInput",form,form.inputMode,action.__name__,isSelected(form,action)
+ if not form.inputMode:
+ return False
+ return isSelected(form,action)
+
+
+def isSelectedOrDisplay(form,action):
+ print "isSelectedOrDisplay",form.inputMode,isSelected(form,action)
+ return not form.inputMode
+
+
+def pasteable(containerForm):
+ """Decide if there is anything to paste."""
+ target = containerForm.context
+ clipboard = getPrincipalClipboard(containerForm.request)
+ items = clipboard.getContents()
+ for item in items:
+ try:
+ obj = zapi.traverse(target, item['target'])
+ except TraversalError:
+ pass
+ else:
+ if item['action'] == 'cut':
+ mover = IObjectMover(obj)
+ moveableTo = safe_getattr(mover, 'moveableTo', None)
+ if moveableTo is None or not moveableTo(target):
+ return False
+ elif item['action'] == 'copy':
+ copier = IObjectCopier(obj)
+ copyableTo = safe_getattr(copier, 'copyableTo', None)
+ if copyableTo is None or not copyableTo(target):
+ return False
+ else:
+ raise
+
+ return True
+
+
+def safe_getattr(obj, attr, default):
+ """Attempts to read the attr, returning default if Unauthorized."""
+ try:
+ return getattr(obj, attr, default)
+ except Unauthorized:
+ return default
+
+
+def hasClipboardContents(form, action):
+ """ interogates the `PrinicipalAnnotation` to see if
+ clipboard contents exist """
+
+ if multiform.anySubFormInputMode(form,action):
+ return False
+
+ if not pasteable(form):
+ return False
+
+ # touch at least one item to in clipboard confirm contents
+ clipboard = getPrincipalClipboard(form.request)
+ items = clipboard.getContents()
+ for item in items:
+ try:
+ zapi.traverse(form.context, item['target'])
+ except TraversalError:
+ pass
+ else:
+ return True
+
+ return False
+
+
+class ContainerItemForm(multiform.ItemFormBase):
+
+ inputMode=False
+ forceInput=['selected']
+ template = ViewPageTemplateFile('griditem.pt')
+ form_fields = form.Fields(ISelection['selected'],
+ IMovableLocation['__name__'],
+ IWriteZopeDublinCore['title'],
+ omit_readonly=False,render_context=True)
+
+ @multiform.parentAction('Edit',
+ condition=multiform.allSubFormsDisplayMode)
+ def handle_edit_action(self, action, data):
+ #print "handle_edit_action",action,data,isSelected(self,action)
+ if isSelected(self,action):
+ self.newInputMode = True
+
+ @multiform.parentAction("Save", inputMode=True,
+ condition=multiform.anySubFormInputMode)
+ def handle_save_action(self, action, data):
+
+ if not isSelected(self,action):
+ return
+ if form.applyChanges(self.context, self.form_fields,
+ data, self.adapters):
+ notify(ObjectModifiedEvent(self.context))
+ formatter = self.request.locale.dates.getFormatter(
+ 'dateTime', 'medium')
+ try:
+ time_zone = idatetime.ITZInfo(self.request)
+ except TypeError:
+ time_zone = pytz.UTC
+ m = {'date_time':formatter.format(datetime.datetime.now(time_zone))}
+ self.status = (_("Updated on ${date_time}", mapping=m),)
+ else:
+ self.status = (_('No changes'),)
+ ISelection(self.context).selected=False
+ self.newInputMode = False
+
+
+class ContainerGridForm(multiform.MultiFormBase):
+
+ itemFormFactory=ContainerItemForm
+
+ template = ViewPageTemplateFile('grid.pt')
+
+ @form.action('Cancel',condition=multiform.anySubFormInputMode)
+ def handle_cancel_action(self, action, data):
+ for form in self.subForms.values():
+ form.newInputMode = False
+
+ @form.action("Paste", condition=hasClipboardContents)
+ def handle_paste_action(self, action, data):
+ """Paste ojects in the user clipboard to the container"""
+ self.form_reset = True
+ target = self.context
+ clipboard = getPrincipalClipboard(self.request)
+ items = clipboard.getContents()
+ moved = False
+ not_pasteable_ids = []
+ for item in items:
+ duplicated_id = False
+ try:
+ obj = zapi.traverse(target, item['target'])
+ except TraversalError:
+ pass
+ else:
+ if item['action'] == 'cut':
+ mover = IObjectMover(obj)
+ try:
+ mover.moveTo(target)
+ moved = True
+ except DuplicateIDError:
+ duplicated_id = True
+ elif item['action'] == 'copy':
+ copier = IObjectCopier(obj)
+ try:
+ copier.copyTo(target)
+ except DuplicateIDError:
+ duplicated_id = True
+ else:
+ raise
+
+ if duplicated_id:
+ not_pasteable_ids.append(zapi.getName(obj))
+
+ if moved:
+ # Clear the clipboard if we do a move, but not if we only do a copy
+ clipboard.clearContents()
+
+ if not_pasteable_ids != []:
+ # Show the ids of objects that can't be pasted because
+ # their ids are already taken.
+ # TODO Can't we add a 'copy_of' or something as a prefix
+ # instead of raising an exception ?
+ self.errors = (
+ _("The given name(s) %s is / are already being used" %(
+ str(not_pasteable_ids))),)
+
+ @form.action("Cut", condition=multiform.allSubFormsDisplayMode)
+ def handle_cut_action(self, action, data):
+ """move objects specified in a list of object ids"""
+
+ container_path = zapi.getPath(self.context)
+
+ # For each item, check that it can be moved; if so, save the
+ # path of the object for later moving when a destination has
+ # been selected; if not movable, provide an error message
+ # explaining that the object can't be moved.
+ items = []
+ for form in self.getForms():
+ ob = form.context
+ name = ob.__name__
+ selection = ISelection(ob)
+ if not selection.selected:
+ continue
+ selection.selected=False
+ mover = IObjectMover(ob)
+ if not mover.moveable():
+ m = {"name": name}
+ title = getDCTitle(ob)
+ if title:
+ m["title"] = title
+ self.errors = (_(
+ "Object '${name}' (${title}) cannot be moved",
+ mapping=m),)
+ else:
+ self.errors = (_("Object '${name}' cannot be moved",
+ mapping=m),)
+ return
+ items.append(zapi.joinPath(container_path, name))
+ if len(items) == 0:
+ self.errors = (_("You didn't specify any ids to cut."),)
+ else:
+ # store the requested operation in the principal annotations:
+ clipboard = getPrincipalClipboard(self.request)
+ clipboard.clearContents()
+ clipboard.addItems('cut', items)
+
+ @form.action("Copy", condition=multiform.allSubFormsDisplayMode)
+ def handle_copy_action(self, action, data):
+ """Copy objects specified in a list of object ids"""
+
+ container_path = zapi.getPath(self.context)
+
+ # For each item, check that it can be copied; if so, save the
+ # path of the object for later copying when a destination has
+ # been selected; if not copyable, provide an error message
+ # explaining that the object can't be copied.
+
+ items = []
+ for form in self.getForms():
+ ob = form.context
+ name = ob.__name__
+ selection = ISelection(ob)
+ if not selection.selected:
+ continue
+ selection.selected=False
+ copier = IObjectCopier(ob)
+ if not copier.copyable():
+ m = {"name": name}
+ title = getDCTitle(ob)
+ if title:
+ m["title"] = title
+ self.errors = (_(
+ "Object '${name}' (${title}) cannot be copied",
+ mapping=m),)
+ else:
+ self.errors = (_("Object '${name}' cannot be copied",
+ mapping=m),)
+ return
+ items.append(zapi.joinPath(container_path, name))
+ if len(items) == 0:
+ self.errors = (_("You didn't specify any ids to copy."),)
+ else:
+ # store the requested operation in the principal annotations:
+ clipboard = getPrincipalClipboard(self.request)
+ clipboard.clearContents()
+ clipboard.addItems('copy', items)
+
+ @form.action("Delete", condition=multiform.allSubFormsDisplayMode)
+ def handle_delete_action(self, action, data):
+ """Delete objects specified in a list of object ids"""
+ container = self.context
+ toDelete = []
+ for form in self.getForms():
+ if not ISelection(form.context).selected:
+ continue
+ toDelete.append(form.context.__name__)
+ if toDelete:
+ for name in toDelete:
+ del(container[name])
+ self.form_reset = True
+ else:
+ self.errors = (_("You didn't specify any ids to delete."),)
+
+
+def getPrincipalClipboard(request):
+ """Return the clipboard based on the request."""
+ user = request.principal
+ annotationutil = zapi.getUtility(IPrincipalAnnotationUtility)
+ annotations = annotationutil.getAnnotations(user)
+ return IPrincipalClipboard(annotations)
+
Added: z3c.multiform/Sandbox/src/z3c/multiform/grid.pt
===================================================================
Added: z3c.multiform/Sandbox/src/z3c/multiform/gridform.py
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/gridform.py 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/gridform.py 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,149 @@
+from zope.component import getMultiAdapter
+from zope.interface import implements
+from zope.formlib import namedtemplate
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.app.location.interfaces import ILocation
+from interfaces import IGridForm, IGridItemForm, ISorter
+import multiform
+
+
+default_grid_template = namedtemplate.NamedTemplateImplementation(
+ ViewPageTemplateFile('grid.pt'), IGridForm)
+
+default_griditem_template = namedtemplate.NamedTemplateImplementation(
+ ViewPageTemplateFile('griditem.pt'), IGridItemForm)
+
+
+class GridItemFormBase(multiform.ItemFormBase):
+ implements(IGridItemForm)
+ template = namedtemplate.NamedTemplate('default')
+
+
+class GridFormBase(multiform.MultiFormBase):
+
+ implements(IGridForm)
+
+ default_batch_size = None
+ default_sort_on = None
+ default_sort_reverse = None
+
+ template = namedtemplate.NamedTemplate('default')
+
+ def __init__(self, context, request):
+ context = FilterMapping(context, request, self)
+ super(GridFormBase,self).__init__(context, request)
+
+
+class FilterMapping(object):
+
+ implements(ILocation)
+
+ def __init__(self, context, request, form):
+ self.context = context
+ self.request = request
+ if ILocation.providedBy(context):
+ self.__parent__ = context.__parent__
+ self.__name__ = context.__name__
+
+ else:
+ self.__parent__ = None
+ self.__name__ = u""
+ self.form = form
+ self.batch_start = request.form.get(
+ '%s.handle.batch_start' % form.prefix,0)
+ self.batch_size = request.form.get(
+ '%s.handle.batch_size' % form.prefix,
+ form.default_batch_size)
+ self.sort_on = request.form.get(
+ '%s.handle.sort_on' % form.prefix,
+ form.default_sort_on)
+ self.sort_reverse = request.form.get(
+ '%s.handle.sort_reverse' % form.prefix,
+ form.default_sort_reverse)
+
+ def sortAllKeys(self):
+ sorter = None
+ if self.sort_on:
+ sortName = self.sort_on
+ sortField = None
+ for field in self.form.itemFormFactory.form_fields:
+ if field.__name__ == sortName:
+ sortField = field
+ break
+ if sortField:
+ sorter = getMultiAdapter((sortField.field.interface,
+ sortField.field),ISorter)
+ if sorter:
+ items = sorter.sort(self.context.items())
+ if self.sort_reverse:
+ items.reverse()
+ keys = []
+ for key, value in items:
+ yield key
+ else:
+ for key in self.context.keys():
+ yield key
+
+ def keys(self):
+ sortKeys = self.sortAllKeys()
+ batch_start = self.batch_start or 0
+ batch_size = self.batch_size or 0
+ if not self.batch_size:
+ if not batch_start:
+ for k in sortKeys:
+ yield k
+ raise StopIteration
+ batch_end = None
+ else:
+ batch_end = batch_start + batch_size
+ for i, key in enumerate(sortKeys):
+ if batch_end is not None and i >= batch_end:
+ return
+ if i >= batch_start:
+ yield key
+
+ def values(self):
+ for k in self.keys():
+ yield self.context[k]
+
+ def items(self):
+ for k in self.keys():
+ yield k, self.context[k]
+
+ def __iter__(self):
+ return iter(self.keys())
+
+ def __getitem__(self, key):
+ '''See interface `IReadContainer`'''
+ if key in self.keys():
+ return self.context[key]
+ else:
+ raise KeyError, key
+
+ def get(self, key, default=None):
+ '''See interface `IReadContainer`'''
+ try:
+ return self.__getitem__(key)
+ except KeyError:
+ return default
+
+ def __len__(self):
+ '''See interface `IReadContainer`'''
+ return len(self.keys())
+
+ def __contains__(self, key):
+ '''See interface `IReadContainer`'''
+ return key in self.keys()
+
+ has_key = __contains__
+
+ def __setitem__(self, key, object):
+ '''See interface `IWriteContainer`'''
+ self.context.__setitem__(key, object)
+
+ def __delitem__(self, key):
+ '''See interface `IWriteContainer`'''
+ if key in self.keys():
+ self.context.__delitem__(key)
+ else:
+ raise KeyError, key
Added: z3c.multiform/Sandbox/src/z3c/multiform/gridform.txt
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/gridform.txt 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/gridform.txt 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,312 @@
+===========
+ Gridforms
+===========
+
+Gridforms are specialized multiforms which are supposed to be
+displayed as grids.
+
+Let's define a simple content object
+
+ >>> from zope import interface, schema
+ >>> from zope.formlib import form
+ >>> from zope.publisher.browser import TestRequest
+ >>> from zope.app.location.interfaces import ILocation
+ >>> from z3c.multiform.interfaces import ISelection
+ >>> from z3c.multiform import multiform, gridform
+
+ >>> class IOrder(interface.Interface):
+ ... identifier = schema.Int(title=u"Identifier", readonly=True)
+ ... name = schema.TextLine(title=u"Name")
+
+ >>> class Order:
+ ... interface.implements(IOrder,ILocation)
+ ...
+ ... def __init__(self, identifier, name=''):
+ ... self.identifier = identifier
+ ... self.name = name
+ ... self.__name__= name
+
+ >>> orderMapping = dict([('n%s'%k,Order(k,name='n%s'%k)) for k in range(4)])
+
+Now we use the ``GridForm`` as a base class to display orders in
+tabular form. Additionally to the IOrder schema the ISelection schema
+is added to the ``form_fields`` of the ``OrderForm`` in order to get
+selectable items in our order grid.
+
+
+ >>> def isSelected(form,action):
+ ... return ISelection(form.context).selected
+
+ >>> def isSelectedInput(form,action):
+ ... if not form.inputMode:
+ ... return False
+ ... return ISelection(form.context).selected
+
+ >>> def isSelectedDisplay(form,action):
+ ... if not form.inputMode:
+ ... return False
+ ... return ISelection(form.context).selected
+
+
+ >>> class OrderForm(gridform.GridItemFormBase):
+ ... inputMode=False
+ ... forceInput=['selected']
+ ... form_fields = form.Fields(ISelection,IOrder,
+ ... omit_readonly=False,render_context=True)
+ ...
+ ... def template(self):
+ ... return '\n<div>%s</div>\n' % '</div>\n<div>'.join([w() for w in
+ ... self.widgets])
+ ...
+ ... @multiform.parentAction(u"Save",
+ ... condition=isSelectedInput,inputMode=True)
+ ... def handle_save_action(self, action, data):
+ ... form.applyChanges(self.context, self.form_fields,
+ ... data, self.adapters)
+ ... self.newInputMode = False
+ ...
+ ... @multiform.parentAction('Edit',
+ ... condition=multiform.allSubFormsDisplayMode)
+ ... def handle_edit_action(self, action, data):
+ ... if isSelected(self,action):
+ ... self.newInputMode = True
+ ...
+ ... @multiform.itemAction(u"SingleSave",
+ ... condition=multiform.isFormInputMode,inputMode=True)
+ ... def handle_singlesave_action(self, action, data):
+ ... form.applyChanges(self.context, self.form_fields,
+ ... data, self.adapters)
+ ... self.newInputMode = False
+ ...
+ ... @multiform.itemAction('SingleEdit',
+ ... condition=multiform.isFormDisplayMode)
+ ... def handle_singleedit_action(self, action, data):
+ ... self.newInputMode = True
+
+
+ >>> class OrdersForm(gridform.GridFormBase):
+ ... itemFormFactory=OrderForm
+ ... def template(self):
+ ... res = u''
+ ... names = sorted(self.subForms.keys())
+ ... for name in names:
+ ... res += '<div>%s</div>\n' % self.subForms[name].render()
+ ... return res
+ ...
+ ... @form.action('Cancel',condition=multiform.isFormInputMode)
+ ... def handle_cancel_action(self, action, data):
+ ... for form in self.subForms.values():
+ ... form.newInputMode = False
+
+Due to the reason that the ``inputMode`` attribute is ``False`` we now
+get DisplayWidgets for all fields, except the ``selected`` field
+which's name is defined in the ``forceInput`` attribute of the class,
+because we always want checkboxes to select some items.
+
+ >>> request = TestRequest()
+ >>> gf = OrdersForm(orderMapping,request)
+ >>> gf.update()
+ >>> print gf.render()
+ <div>
+ <div><input ... name="form.n0.selected" type="checkbox" ... /></div>
+ <div>0</div>
+ <div>n0</div>
+ </div>
+ <div>
+ <div><input ... name="form.n1.selected" type="checkbox" ... /></div>
+ <div>1</div>
+ <div>n1</div>
+ ...
+ </div>
+
+Also the edit action should be available.
+
+ >>> print gf.subActionNames
+ [u'form.actions.edit']
+ >>> [a.__name__ for a in gf.availableSubActions()]
+ [u'form.actions.edit']
+
+Now we are going to select some items. And the checkbox of the ``selected``
+
+ >>> request = TestRequest()
+ >>> request.form['form.n1.selected']=u'on'
+ >>> request.form['form.n0.selected.used']=u''
+ >>> gf = OrdersForm(orderMapping,request)
+ >>> gf.update()
+ >>> print gf.render()
+ <div>
+ <div><input... name="form.n0.selected" type="checkbox" value="on" /></div>
+ <div>0</div>
+ <div>n0</div>
+ </div>
+ <div>
+ <div><input... checked="checked" ... name="form.n1.selected" ... /></div>
+ <div>1</div>
+ <div>n1</div>
+ ...
+ </div>
+
+By using the Edit action we can now switch all selected item forms to
+input mode.
+
+ >>> request = TestRequest()
+ >>> request.form['form.actions.edit']=u''
+ >>> request.form['form.n0.selected.used']=u''
+ >>> request.form['form.n1.selected']=u'on'
+ >>> request.form['form.n2.selected.used']=u''
+ >>> request.form['form.n3.selected.used']=u''
+ >>> gf = OrdersForm(orderMapping,request)
+ >>> res = gf()
+ >>> ISelection(gf.subForms['n1'].context).selected
+ True
+ >>> print res
+ <div>
+ <div><input... name="form.n0.selected" type="checkbox" value="on" /></div>
+ <div>0</div>
+ <div>n0</div>
+ </div>
+ <div>
+ <div><input... checked="checked" ... name="form.n1.selected" ... /></div>
+ <div>1</div>
+ <div><input... name="form.n1.name" ...</div>
+ ...
+ </div>
+
+Also the edit action should be disabled now and the save action should
+be enabled.
+
+ >>> print gf.subActionNames
+ [u'form.actions.save']
+
+Now we test the actions which are displayed in each row.
+
+ >>> request = TestRequest()
+ >>> #request.form['form.n1.actions.singleedit']=u''
+ >>> gf = OrdersForm(orderMapping,request)
+ >>> res = gf()
+ >>> sorted([action.__name__ for action in
+ ... gf.subForms['n1'].availableActions()])
+ [u'form.n1.actions.singleedit']
+
+We call the singleedit to edit a single row.
+
+ >>> request = TestRequest()
+ >>> request.form['form.n0.selected.used']=u''
+ >>> request.form['form.n1.selected.used']=u''
+ >>> request.form['form.n1.actions.singleedit']=u''
+ >>> gf = OrdersForm(orderMapping,request)
+ >>> res = gf()
+
+Batching
+--------
+The gridform class is able to handle batch_start and batch_size. Let us reduce
+the output.
+
+ >>> request = TestRequest()
+ >>> request.form['form.handle.batch_start']=1
+ >>> request.form['form.handle.batch_size']=2
+ >>> gf = OrdersForm(orderMapping,request)
+ >>> gf.update()
+ >>> print gf.render()
+ <div>
+ <div><input ... name="form.n1.selected" type="checkbox" ... /></div>
+ <div>1</div>
+ <div>n1</div>
+ </div>
+ <div>
+ <div><input ... name="form.n2.selected" type="checkbox" ... /></div>
+ <div>2</div>
+ <div>n2</div>
+ </div>
+
+Check the results of fraktal informations about batch_size and batch_start:
+
+ >>> request = TestRequest()
+ >>> request.form['form.handle.batch_start']=1
+ >>> gf = OrdersForm(orderMapping,request)
+ >>> gf.update()
+ >>> sorted([name for name in gf.subForms])
+ ['n1', 'n2', 'n3']
+ >>> request = TestRequest()
+ >>> request.form['form.handle.batch_size']=2
+ >>> gf = OrdersForm(orderMapping,request)
+ >>> gf.update()
+ >>> sorted([name for name in gf.subForms])
+ ['n0', 'n1']
+
+We can have a default value for batch size. Here we just set the attribute
+default_batch_size by hand.
+
+ >>> request = TestRequest()
+ >>> gf = OrdersForm(orderMapping,request)
+ >>> gf.default_batch_size = 3
+ >>> gf.update()
+ >>> print gf.render()
+ <div>
+ <div><input ... name="form.n0.selected" ...
+ <div>1</div>
+ <div>n1</div>
+ ...
+ <div>2</div>
+ <div>n2</div>
+ ...
+ <div>3</div>
+ <div>n3</div>
+ </div>
+
+Sorting
+-------
+We can sort the grid with the informations field name (string) and
+reverse (bool).
+
+ >>> class Orders2Form(gridform.GridFormBase):
+ ... itemFormFactory=OrderForm
+ ... def template(self):
+ ... res = u''
+ ... for form in self.getForms():
+ ... res += '<div>%s</div>\n' % form.render()
+ ... return res
+ ...
+ ... @form.action('Cancel',condition=multiform.isFormInputMode)
+ ... def handle_cancel_action(self, action, data):
+ ... for form in self.subForms.values():
+ ... form.newInputMode = False
+
+ >>> orderMapping2 = dict([('n%s'%k,Order(4-k,name='n%s'%k)) for k
+ ... in range(4)])
+
+We sort the forms by the value of the column name
+
+ >>> request = TestRequest()
+ >>> request.form['form.handle.sort_on']=u'name'
+ >>> gf = Orders2Form(orderMapping2,request)
+ >>> gf.update()
+ >>> [(form.context.identifier,
+ ... form.context.name) for form in gf.getForms()]
+ [(4, 'n0'), (3, 'n1'), (2, 'n2'), (1, 'n3')]
+
+and reserve.
+
+ >>> request = TestRequest()
+ >>> request.form['form.handle.sort_on']=u'name'
+ >>> request.form['form.handle.sort_reverse']=u'on'
+ >>> gf = Orders2Form(orderMapping2,request)
+ >>> gf.update()
+ >>> [(form.context.identifier,
+ ... form.context.name) for form in gf.getForms()]
+ [(1, 'n3'), (2, 'n2'), (3, 'n1'), (4, 'n0')]
+
+We can have default values for sort field and sort direction. Here we just set
+the attributes default_sort_on or default_sort_reverse by hand.
+
+ >>> request = TestRequest()
+ >>> gf = Orders2Form(orderMapping2,request)
+ >>> gf.default_sort_on = u'name'
+ >>> gf.update()
+ >>> [(form.context.identifier,
+ ... form.context.name) for form in gf.getForms()]
+ [(4, 'n0'), (3, 'n1'), (2, 'n2'), (1, 'n3')]
+
+TODO:
+-----
+- test singleedit, singlesave
Added: z3c.multiform/Sandbox/src/z3c/multiform/griditem.pt
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/griditem.pt 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/griditem.pt 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,3 @@
+<div>
+ griditem
+</div>
\ No newline at end of file
Added: z3c.multiform/Sandbox/src/z3c/multiform/interfaces.py
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/interfaces.py 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/interfaces.py 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,49 @@
+from zope import schema
+from zope.interface import Interface,Attribute
+from zope.formlib.interfaces import IAction
+from zope.formlib.i18n import _
+
+
+class IMultiForm(Interface):
+
+ """multiform"""
+
+class IItemForm(Interface):
+
+ """a sub form for an item of a multiform"""
+
+
+class IGridItemForm(IItemForm):
+
+ """an form for an item of a grid form"""
+
+
+class IGridForm(IMultiForm):
+
+ """a special grid multiform"""
+
+
+class IItemAction(IAction):
+ """a item action"""
+
+
+class IParentAction(IAction):
+ """a parent action"""
+
+
+class ISelection(Interface):
+
+ """Provides information about the selection state of an object"""
+
+ selected = schema.Bool(title=_(u'Selected'),default=False)
+
+
+class IFormLocation(Interface):
+
+ __form_name__ = Attribute('The unique name of the item in a multiform')
+
+
+class ISorter(Interface):
+
+ def sort(items):
+ """return the items sorted. items are (key,value) tuples"""
Added: z3c.multiform/Sandbox/src/z3c/multiform/multiform.py
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/multiform.py 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/multiform.py 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,322 @@
+import copy
+from zope import interface
+from zope.component import getMultiAdapter
+from zope.interface import implements
+import zope.i18n
+from zope.app.publisher.browser import BrowserView
+from zope.app import zapi
+from zope.app.form.browser.interfaces import IWidgetInputErrorView
+from zope.formlib import form
+from zope.formlib import namedtemplate
+from zope.formlib.interfaces import IBoundAction
+from zope.formlib.i18n import _
+
+from interfaces import IMultiForm, IParentAction, IItemAction, ISelection
+from interfaces import IFormLocation,IItemForm
+
+
+def isFormDisplayMode(f,action):
+ return not f.inputMode
+
+def isFormInputMode(f,action):
+ return f.inputMode
+
+def anySubFormInputMode(form,action):
+ if not IMultiForm.providedBy(form):
+ form = form.parentForm
+ for f in form.subForms.values():
+ if f.inputMode:
+ return True
+
+def allSubFormsDisplayMode(form,action):
+ if not IMultiForm.providedBy(form):
+ form = form.parentForm
+ for f in form.subForms.values():
+ if f.inputMode:
+ return False
+ return True
+
+
+class ItemAction(form.Action):
+
+ """an action that is rendered in the itemform object and can
+ handle the toggle between input and display."""
+
+ implements(IItemAction)
+ def __init__(self, label, **options):
+ self.inputMode = options.pop('inputMode',None)
+ super(ItemAction,self).__init__(label,**options)
+
+class ParentAction(form.Action):
+
+ """an action that is rendered in the parent multiform object and
+ is applied to all subForms"""
+
+ implements(IParentAction)
+ def __init__(self, label, **options):
+ self.inputMode = options.pop('inputMode',None)
+ super(ParentAction,self).__init__(label,**options)
+
+ def __get__(self, form, class_=None):
+ if form is None:
+ return self
+ result = self.__class__.__new__(self.__class__)
+ result.__dict__.update(self.__dict__)
+ result.form = form
+ result.__name__ = form.parentForm.prefix + '.' + result.__name__
+ interface.alsoProvides(result, IBoundAction)
+ return result
+
+ def submitted(self):
+ # override to find the matching prefix
+ if not self.available():
+ res = False
+ else:
+ res = self.__name__ in self.form.request
+ return res
+
+ at namedtemplate.implementation(IParentAction)
+def render_submit_button(self):
+ if IBoundAction.providedBy(self) and not self.available():
+ return ''
+ label = self.label
+ if isinstance(label, (zope.i18n.Message, zope.i18n.MessageID)):
+ label = zope.i18n.translate(self.label, context=self.form.request)
+ return ('<input type="submit" id="%s" name="%s" value="%s"'
+ ' class="button" />' %
+ (self.__name__, self.__name__, label)
+ )
+
+
+class itemAction(form.action):
+
+ def __call__(self, success):
+ action = ItemAction(self.label, success=success, **self.options)
+ self.actions.append(action)
+ return action
+
+
+class parentAction(form.action):
+
+ def __call__(self, success):
+ action = ParentAction(self.label, success=success, **self.options)
+ self.actions.append(action)
+ return action
+
+
+class ItemFormBase(form.FormBase):
+
+ implements(IItemForm)
+ forceInput = []
+ parentForm = None
+ inputMode = None
+ newInputMode = None
+ form_fields=[]
+ actions = []
+
+ def __init__(self,context,request,parentForm):
+ # we have to copy the default fields, so that we can mutate
+ # them in our instance
+ self.form_fields = copy.deepcopy(self.__class__.form_fields)
+ self.request = request
+ self.context = getMultiAdapter([context,self],IFormLocation)
+ self.parentForm = parentForm
+
+ def update(self):
+ self.form_reset = False
+
+ data = {}
+ errors, action = form.handleSubmit(self.actions, data, self.validate)
+ self.errors = errors
+
+ if errors:
+ self.status = _('There were errors')
+ result = action.failure(data, errors)
+ elif errors is not None:
+ self.form_reset = True
+ result = action.success(data)
+ else:
+ result = None
+
+ self.form_result = result
+
+ def setUpWidgets(self,ignore_request=False):
+ super(ItemFormBase,self).setUpWidgets(ignore_request)
+ # XXX how to check for the field
+ if self.widgets.get('selected'):
+ widget = self.widgets['selected']
+ widget.setRenderedValue(ISelection(self.context).selected)
+
+ def availableActions(self):
+ # we need to override this, because we should not return the
+ # parentActions
+ if not hasattr(self,'actions'):
+ return []
+ actions = [action for action in self.actions
+ if not IParentAction.providedBy(action)]
+ return form.availableActions(self, actions)
+
+ def availableParentActions(self):
+ if not hasattr(self,'actions'):
+ return []
+ actions = [action for action in self.actions
+ if IParentAction.providedBy(action)]
+ return form.availableActions(self, actions)
+
+
+class MultiFormBase(form.FormBase):
+
+ implements(IMultiForm)
+ itemFormFactory = ItemFormBase
+ subForms={}
+ form_fields = []
+ actions = []
+ subActionNames = []
+ subFormInputMode = {}
+ selection = []
+ actions = []
+
+ def update(self):
+ self.checkInputMode()
+ self.updateSelection()
+ super(MultiFormBase,self).update()
+ preForms = list(self.getForms())
+ for form in preForms:
+ form.update()
+ postForms = list(self.getForms())
+ refresh = postForms != preForms
+ for form in postForms:
+ if form.newInputMode is not None:
+ newInputMode = form.newInputMode
+ context = self.context[form.context.__name__]
+ name = context.__name__
+ self.setUpForm(name, context, newInputMode,
+ ignore_request=True)
+ self.subFormInputMode[name] = newInputMode
+ refresh = True
+ if refresh:
+ self.refreshSubActionNames()
+
+ def setUpWidgets(self, *args, **kw):
+ super(MultiFormBase,self).setUpWidgets(*args,**kw)
+ self.setUpForms(*args, **kw)
+
+ def setUpForm(self, name, item, inputMode, *args, **kw):
+ prefix = (self.prefix and self.prefix+'.' or '') + name
+ subForm = self.newSubForm(item)
+ if inputMode is not None and not inputMode:
+ forceInput = self.itemFormFactory.forceInput
+ for field in subForm.form_fields:
+ if field.__name__ not in forceInput:
+ field.for_display=True
+ subForm.inputMode = inputMode
+ subForm.setPrefix(prefix)
+ subForm.setUpWidgets(*args, **kw)
+ self.subForms[name] = subForm
+
+ def setUpForms(self, *args, **kw):
+ self.subForms = {}
+ for name, item in self.context.items():
+ inputMode = self.subFormInputMode.get(name,self.itemFormFactory.inputMode)
+ self.setUpForm(name, item, inputMode)
+ self.refreshSubActionNames()
+
+ def getForms(self):
+ # we have to use the keys here to support all actions that
+ # modifies the keys of our mapping, e.g. rename, delete
+ keys = list(self.context.keys())
+ deleted = filter(lambda x: x not in keys,self.subForms.keys())
+ for k in deleted:
+ del(self.subForms[k])
+ try:
+ del(self.subFormInputMode[k])
+ except KeyError:
+ pass
+ for name in self.context.keys():
+ if not self.subForms.has_key(name):
+ self.setUpForm(name,self.context[name],
+ self.itemFormFactory.inputMode,
+ ignore_request=True)
+ yield self.subForms[name]
+
+
+ def refreshSubActionNames(self):
+ availableActions = set()
+ for subForm in self.getForms():
+ availableActions.update([action.__name__ for action in \
+ subForm.availableParentActions()])
+ self.subActionNames = []
+ if hasattr(self.itemFormFactory,'actions'):
+ for action in self.itemFormFactory.actions:
+ name = '%s.%s' % (self.prefix,action.__name__)
+ if name in availableActions:
+ self.subActionNames.append(name)
+
+
+ def checkInputMode(self):
+ self.subFormInputMode = {}
+ inputMode = None
+ for action in self.itemFormFactory.actions:
+ name = '%s.%s' % (self.prefix,action.__name__)
+ if name in self.request.form and getattr(action,
+ 'inputMode', None) is not None:
+ inputMode = action.inputMode
+ break
+ if inputMode is None:
+ return
+ inputField = None
+ if len(self.context) > 0:
+ for name, item in self.context.items():
+ break
+ tmpForm = self.newSubForm(item)
+ for field in tmpForm.form_fields:
+ if not field.for_display and field.__name__ not in tmpForm.forceInput:
+ inputField = field
+ break
+ for name in self.context.keys():
+ prefix = self.prefix + '.' + name + '.' + field.__name__
+ self.subFormInputMode[name] = (prefix in self.request.form)
+
+ def updateSelection(self):
+ for field in self.itemFormFactory.form_fields:
+ if issubclass(field.field.interface,ISelection):
+ form_fields = form.Fields(field)
+ for name,item in self.context.items():
+ sForm = SelectionForm(item, self.request, form_fields)
+ prefix = (self.prefix and self.prefix+'.' or '') + name
+ sForm.setPrefix(prefix)
+ sForm.form_fields = form_fields
+ sForm.setUpWidgets()
+ data = {}
+ try:
+ form.getWidgetsData(sForm.widgets, sForm.prefix, data)
+ except:
+ pass
+ form.applyChanges(sForm.context, sForm.form_fields, data)
+ return
+
+ def newSubForm(self,item):
+
+ """creates a new instance from the itemFormFactory for
+ temporary usage"""
+
+ return self.itemFormFactory(item,self.request,self)
+
+ def availableSubActions(self):
+ if self.subActionNames:
+ for name in self.subActionNames:
+ # remove the prefix of our form because, the actions in
+ # the class variable have no prefix in their name
+ actionName = name[len(self.prefix)+1:]
+ action = self.itemFormFactory.actions.byname[actionName]
+ action = copy.copy(action)
+ action.__name__ = name
+ yield action
+
+
+class SelectionForm(form.FormBase):
+
+ def __init__(self, context, request, form_fields):
+ self.form_fields = form_fields
+ self.request = request
+ self.context = getMultiAdapter([context,self],IFormLocation)
Added: z3c.multiform/Sandbox/src/z3c/multiform/resources/sort_down.gif
===================================================================
(Binary files differ)
Property changes on: z3c.multiform/Sandbox/src/z3c/multiform/resources/sort_down.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: z3c.multiform/Sandbox/src/z3c/multiform/resources/sort_up.gif
===================================================================
(Binary files differ)
Property changes on: z3c.multiform/Sandbox/src/z3c/multiform/resources/sort_up.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: z3c.multiform/Sandbox/src/z3c/multiform/selection.py
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/selection.py 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/selection.py 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,138 @@
+from zope.interface import implements
+from zope.proxy import ProxyBase, getProxiedObject
+from zope.security.checker import NamesChecker,defineChecker
+from zope.security.proxy import removeSecurityProxy
+from zope.app.decorator import DecoratorSpecificationDescriptor
+from zope.app.decorator import DecoratedSecurityCheckerDescriptor
+from zope.app.location import location
+
+from interfaces import ISelection,IFormLocation
+
+
+class FormLocationSelection(object):
+
+ __doc__ = """Form Location-object proxy
+
+ This is a non-picklable proxy that can be put around objects that
+ implement `ILocation`.
+ >>> from zope.publisher.browser import TestRequest
+ >>> from zope import interface
+ >>> class L(object):
+ ... __name__ = u'name'
+ >>> l= L()
+ >>> class F(object):
+ ... request = TestRequest()
+ ... context = 1
+ ... prefix = u"form.n1"
+ >>> f = F()
+ >>> p = FormLocationProxy(l, f)
+ >>> p = FormLocationSelection(p)
+ >>> p.selected
+ False
+ >>> p.selected = True
+ >>> p.selected
+ True
+ >>> p.selected = False
+ >>> p.selected
+ False
+ """
+
+ implements(ISelection)
+
+ def __init__(self,context):
+ self.context = context
+
+ def _setSelected(self,v):
+ key = '_mf_selection.' + removeSecurityProxy(
+ self.context.__form__).prefix
+ form = removeSecurityProxy(self.context.__form__)
+ form.request.form[key] = v
+
+ def _getSelected(self):
+ key = '_mf_selection.' + removeSecurityProxy(
+ self.context.__form__).prefix
+ res = removeSecurityProxy(
+ self.context.__form__).request.form.get(key,False)
+ return res
+
+ selected = property(_getSelected ,_setSelected)
+
+
+class FormLocationProxy(ProxyBase):
+
+ __doc__ = """Form Location-object proxy
+
+ XXX the attributes of the form are not available because of a
+ security proxy issue
+
+ This is a non-picklable proxy that can be put around objects that
+ implement `ILocation`.
+
+ >>> from zope import interface
+ >>> class IMarker(interface.Interface): pass
+ >>> class L(object):
+ ... x = 1
+ >>> l = L()
+ >>> interface.directlyProvides(l,IMarker)
+ >>> p = FormLocationProxy(l, "Form")
+ >>> p.x
+ 1
+ >>> p.x=2
+ >>> p.x
+ 2
+ >>> l.x
+ 2
+ >>> p.__form__
+ 'Form'
+
+ >>> IFormLocation.providedBy(p)
+ True
+
+ >>> IMarker.providedBy(p)
+ True
+
+ >>> import pickle
+ >>> p2 = pickle.dumps(p)
+ Traceback (most recent call last):
+ ...
+ TypeError: Not picklable
+
+ Proxies should get their doc strings from the object they proxy:
+
+ >>> p.__doc__ == l.__doc__
+ True
+
+ """
+
+ implements(IFormLocation)
+
+ __slots__ = '__form__'
+ __safe_for_unpickling__ = True
+
+ def __new__(self, ob, form):
+ return ProxyBase.__new__(self, ob)
+
+ def __init__(self, ob, form):
+ ProxyBase.__init__(self, ob)
+ self.__form__ = form
+
+ def __reduce__(self, proto=None):
+ raise TypeError("Not picklable")
+
+
+ __doc__ = location.ClassAndInstanceDescr(
+ lambda inst: getProxiedObject(inst).__doc__,
+ lambda cls, __doc__ = __doc__: __doc__,
+ )
+
+ __reduce_ex__ = __reduce__
+
+ __providedBy__ = DecoratorSpecificationDescriptor()
+
+ __Security_checker__ = DecoratedSecurityCheckerDescriptor()
+
+formLocationChecker = NamesChecker(['__form__'])
+defineChecker(FormLocationProxy, formLocationChecker)
+
+
+
Added: z3c.multiform/Sandbox/src/z3c/multiform/selections.txt
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/selections.txt 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/selections.txt 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,14 @@
+======================
+ Multiform Selections
+======================
+
+Selecting subforms of a multiform is a common task which is supported
+by the multiform package. Selections are implemented by the ISelection
+interface. This interface can be adapted to from objects which
+implement IFormLocation which in turn can be adapted to from ILocation
+and IForm. The IFormLocation adpater is a proxy which provides the
+``__form_name__`` attribute.
+
+The ISelection implementation in this package uses the request to
+store the selection state, by using the ILocationForm interface.
+
Added: z3c.multiform/Sandbox/src/z3c/multiform/sort.py
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/sort.py 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/sort.py 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,35 @@
+from zope.interface import implements
+
+from interfaces import ISorter
+
+
+class SchemaSorter(object):
+
+ implements(ISorter)
+
+ def __init__(self,schema,field):
+ self.schema = schema
+ self.name = field.__name__
+ self.field = field
+
+ def sort(self,seq):
+
+ l = []
+ def _v(o):
+ o = self.schema(o)
+ field=self.field.bind(o)
+ v = field.get(o)
+ if hasattr(field,'vocabulary'):
+ # XXX maybe this is an adapter issue
+ v = field.vocabulary.getTerm(v).token
+ return v
+ def _cmp(a,b):
+ # none comparison
+ a,b = a[0],b[0]
+ if a==None and b: return -1
+ if b==None and a: return 1
+ return cmp(a,b)
+ for item in seq:
+ l.append((_v(item[1]),item))
+ l.sort(_cmp)
+ return map(lambda i:i[1],l)
Added: z3c.multiform/Sandbox/src/z3c/multiform/tests.py
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/tests.py 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/tests.py 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1,117 @@
+import doctest
+import unittest
+
+from zope import component
+from zope.testing.doctestunit import DocFileSuite, DocTestSuite
+from zope.app.testing import setup
+import zope.schema.interfaces
+import zope.app.form.browser
+import zope.publisher.interfaces.browser
+import zope.app.form.interfaces
+from zope.formlib import form
+
+import interfaces
+import gridform
+import multiform
+import selection
+import sort
+
+
+def setUp(test):
+ setup.placefulSetUp()
+
+ component.provideAdapter(
+ zope.app.form.browser.TextWidget,
+ [zope.schema.interfaces.ITextLine,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IInputWidget,
+ )
+ component.provideAdapter(
+ zope.app.form.browser.UnicodeDisplayWidget,
+ [zope.schema.interfaces.ITextLine,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IDisplayWidget,
+ )
+ component.provideAdapter(
+ zope.app.form.browser.boolwidgets.BooleanDisplayWidget,
+ [zope.schema.interfaces.IBool,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IDisplayWidget,
+ )
+ component.provideAdapter(
+ zope.app.form.browser.CheckBoxWidget,
+ [zope.schema.interfaces.IBool,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IInputWidget,
+ )
+ component.provideAdapter(
+ zope.app.form.browser.UnicodeDisplayWidget,
+ [zope.schema.interfaces.IInt,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IDisplayWidget,
+ )
+ component.provideAdapter(
+ zope.app.form.browser.IntWidget,
+ [zope.schema.interfaces.IInt,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ],
+ zope.app.form.interfaces.IInputWidget,
+ )
+ component.provideAdapter(
+ selection.FormLocationProxy,
+ [zope.app.location.interfaces.ILocation,
+ zope.formlib.interfaces.IForm
+ ],
+ interfaces.IFormLocation
+ )
+ component.provideAdapter(
+ selection.FormLocationSelection,
+ [interfaces.IFormLocation],
+ interfaces.ISelection
+ )
+ component.provideAdapter(
+ sort.SchemaSorter,
+ [zope.interface.Interface,
+ zope.schema.interfaces.IField],
+ interfaces.ISorter
+ )
+ component.provideAdapter(gridform.default_grid_template,
+ name="default")
+ component.provideAdapter(gridform.default_griditem_template,
+ name="default")
+ component.provideAdapter(form.render_submit_button, name='render')
+ component.provideAdapter(multiform.render_submit_button, name='render')
+
+def tearDown(test):
+ setup.placefulTearDown()
+
+
+def test_suite():
+
+ return unittest.TestSuite(
+ (
+ DocTestSuite('multiform.selection',
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ DocFileSuite('README.txt',
+ setUp=setUp, tearDown=tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ DocFileSuite('actions.txt',
+ setUp=setUp, tearDown=tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ DocFileSuite('gridform.txt',
+ setUp=setUp, tearDown=tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ ))
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Added: z3c.multiform/Sandbox/src/z3c/multiform/z3c.multiform-configure.zcml
===================================================================
--- z3c.multiform/Sandbox/src/z3c/multiform/z3c.multiform-configure.zcml 2006-04-15 20:45:33 UTC (rev 67064)
+++ z3c.multiform/Sandbox/src/z3c/multiform/z3c.multiform-configure.zcml 2006-04-15 20:53:48 UTC (rev 67065)
@@ -0,0 +1 @@
+<include package="z3c.multiform"/>
\ No newline at end of file
More information about the Checkins
mailing list