The ObjectWidget widget is about rendering a schema’s fields as a single widget.
There are way too many preconditions to exercise the ObjectWidget as it relies heavily on the from and widget framework. It renders the sub-widgets in a sub-form.
In order to not overwhelm you with our set of well-chosen defaults, all the default component registrations have been made prior to doing those examples:
>>> from z3c.form import testing >>> testing.setupFormDefaults()>>> import zope.component>>> from z3c.form import datamanager >>> zope.component.provideAdapter(datamanager.DictionaryField)>>> from z3c.form.testing import IMySubObject >>> from z3c.form.testing import IMySecond >>> from z3c.form.testing import MySubObject >>> from z3c.form.testing import MySecond>>> import z3c.form.object >>> zope.component.provideAdapter(z3c.form.object.SubformAdapter) >>> zope.component.provideAdapter(z3c.form.object.ObjectConverter)>>> from z3c.form.error import MultipleErrorViewSnippet >>> zope.component.provideAdapter(MultipleErrorViewSnippet)
As for all widgets, the objectwidget must provide the new IWidget interface:
>>> from zope.interface.verify import verifyClass >>> from z3c.form import interfaces >>> import z3c.form.browser.object>>> verifyClass(interfaces.IWidget, z3c.form.browser.object.ObjectWidget) True
The widget can be instantiated only using the request:
>>> from z3c.form.testing import TestRequest
>>> request = TestRequest()
>>> widget = z3c.form.browser.object.ObjectWidget(request)
Before rendering the widget, one has to set the name and id of the widget:
>>> widget.id = 'widget-id'
>>> widget.name = 'widget.name'
Also, stand-alone widgets need to ignore the context:
>>> widget.ignoreContext = True
There is no life for ObjectWidget without a schema to render:
>>> widget.update()
Traceback (most recent call last):
...
ValueError: <ObjectWidget 'widget.name'> .field is None,
that's a blocking point
This schema is specified by the field:
>>> from z3c.form.widget import FieldWidget
>>> from z3c.form.testing import IMySubObject
>>> import zope.schema
>>> field = zope.schema.Object(
... __name__='subobject',
... title=u'my object widget',
... schema=IMySubObject)
Apply the field nicely with the helper:
>>> widget = FieldWidget(field, widget)
We also need to register the templates for the widget and request:
>>> import zope.component >>> from zope.pagetemplate.interfaces import IPageTemplate >>> from z3c.form.testing import getPath >>> from z3c.form.widget import WidgetTemplateFactory>>> zope.component.provideAdapter( ... WidgetTemplateFactory(getPath('object_input.pt'), 'text/html'), ... (None, None, None, None, interfaces.IObjectWidget), ... IPageTemplate, name=interfaces.INPUT_MODE)>>> zope.component.provideAdapter( ... WidgetTemplateFactory(getPath('object_display.pt'), 'text/html'), ... (None, None, None, None, interfaces.IObjectWidget), ... IPageTemplate, name=interfaces.DISPLAY_MODE)
We can now render the widget:
>>> widget.update()
>>> print widget.render()
<html>
<body>
<div class="object-widget required">
<div class="label">
<label for="subobject-widgets-foofield">
<span>My foo field</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<input class="text-widget required int-field"
id="subobject-widgets-foofield"
name="subobject.widgets.foofield"
type="text" value="1,111">
</div>
<div class="label">
<label for="subobject-widgets-barfield">
<span>My dear bar</span>
</label>
</div>
<div class="widget">
<input class="text-widget int-field"
id="subobject-widgets-barfield"
name="subobject.widgets.barfield"
type="text" value="2,222">
</div>
<input name="subobject-empty-marker" type="hidden" value="1">
</div>
</body>
</html>
As you see all sort of default values are rendered.
Let’s provide a more meaningful value:
>>> from z3c.form.testing import MySubObject >>> v = MySubObject() >>> v.foofield = 42 >>> v.barfield = 666 >>> v.__marker__ = "ThisMustStayTheSame">>> widget.ignoreContext = False >>> widget.value = dict(foofield=42, barfield=666)>>> widget.update()>>> print widget.render() <html> <body> <div class="object-widget required"> <div class="label"> <label for="subobject-widgets-foofield"> <span>My foo field</span> <span class="required">*</span> </label> </div> <div class="widget"> <input class="text-widget required int-field" id="subobject-widgets-foofield" name="subobject.widgets.foofield" type="text" value="42"> </div> <div class="label"> <label for="subobject-widgets-barfield"> <span>My dear bar</span> </label> </div> <div class="widget"> <input class="text-widget int-field" id="subobject-widgets-barfield" name="subobject.widgets.barfield" type="text" value="666"> </div> <input name="subobject-empty-marker" type="hidden" value="1"> </div> </body> </html>
The widget’s value is NO_VALUE until it gets a request:
>>> widget.value
<NO_VALUE>
Let’s fill in some values via the request:
>>> widget.request = TestRequest(form={'subobject.widgets.foofield':u'2',
... 'subobject.widgets.barfield':u'999',
... 'subobject-empty-marker':u'1'})
>>> widget.update()
>>> print widget.render()
<html>
<body>
<div class="object-widget required">
<div class="label">
<label for="subobject-widgets-foofield">
<span>My foo field</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<input class="text-widget required int-field"
id="subobject-widgets-foofield"
name="subobject.widgets.foofield"
type="text" value="2">
</div>
<div class="label">
<label for="subobject-widgets-barfield">
<span>My dear bar</span>
</label>
</div>
<div class="widget">
<input class="text-widget int-field"
id="subobject-widgets-barfield"
name="subobject.widgets.barfield"
type="text" value="999">
</div>
<input name="subobject-empty-marker" type="hidden" value="1">
</div>
</body>
</html>
Widget value comes from the request:
>>> wv = widget.value
>>> wv
{'foofield': 2, 'barfield': 999}
But our object will not be modified, since there was no “apply”-like control.
>>> v
<z3c.form.testing.MySubObject object at ...>
>>> v.foofield
42
>>> v.barfield
666
The marker must stay (we have to modify the same object):
>>> v.__marker__ 'ThisMustStayTheSame'>>> converter = interfaces.IDataConverter(widget)>>> value = converter.toFieldValue(wv) Traceback (most recent call last): ... ValueError: No IObjectFactory adapter registered for z3c.form.testing.IMySubObject
We have to register object factory adapters to allow the objectwidget to create objects:
>>> from z3c.form.object import registerFactoryAdapter >>> registerFactoryAdapter(IMySubObject, MySubObject) >>> registerFactoryAdapter(IMySecond, MySecond)>>> value = converter.toFieldValue(wv) >>> value <z3c.form.testing.MySubObject object at ...> >>> value.foofield 2 >>> value.barfield 999
This is a different object:
>>> value.__marker__
Traceback (most recent call last):
...
AttributeError: 'MySubObject' object has no attribute '__marker__'
Setting missing values on the widget works too:
>>> widget.value = converter.toWidgetValue(field.missing_value)>>> widget.update()
Default values get rendered:
>>> print widget.render()
<div class="object-widget required">
<div class="label">
<label for="subobject-widgets-foofield">
<span>My foo field</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<input id="subobject-widgets-foofield"
name="subobject.widgets.foofield"
class="text-widget required int-field" value="2" type="text" />
</div>
<div class="label">
<label for="subobject-widgets-barfield">
<span>My dear bar</span>
</label>
</div>
<div class="widget">
<input id="subobject-widgets-barfield"
name="subobject.widgets.barfield"
class="text-widget int-field" value="999" type="text" />
</div>
<input name="subobject-empty-marker" type="hidden" value="1" />
</div>
But on the return we get default values back:
>>> widget.value {'foofield': 2, 'barfield': 999}>>> value = converter.toFieldValue(widget.value) >>> value <z3c.form.testing.MySubObject object at ...>
HMMMM.... do we have to test error handling here? I’m tempted to leave it out as no widgets seem to do this.
#Error handling is next. Let’s use the value “bad” (an invalid integer literal) #as input for our internal (sub) widget. # # >>> widget.request = TestRequest(form={‘subobject.widgets.foofield’:u‘55’, # ... ‘subobject.widgets.barfield’:u’bad’, # ... ‘subobject-empty-marker’:u‘1’}) # # # >>> widget.update() # >>> print widget.render() # <html> # <body> # <div class=”object-widget required”> # <div class=”label”> # <label for=”subobject-widgets-foofield”> # <span>My foo field</span> # <span class=”required”>*</span> # </label> # </div> # <div class=”widget”> # <input class=”text-widget required int-field” # id=”subobject-widgets-foofield” # name=”subobject.widgets.foofield” # type=”text” value=”55”> # </div> # <div class=”label”> # <label for=”subobject-widgets-barfield”> # <span>My dear bar</span> # </label> # </div> # <div class=”error”>The entered value is not a valid integer literal.</div> # <div class=”widget”> # <input class=”text-widget int-field” # id=”subobject-widgets-barfield” # name=”subobject.widgets.barfield” # type=”text” value=”bad”> # </div> # <input name=”subobject-empty-marker” type=”hidden” value=”1”> # </div> # </body> # </html> # #Getting the widget value raises the widget errors: # # >>> widget.value # Traceback (most recent call last): # ... # MultipleErrors # #Our object still must retain the old values: # # >>> v # <z3c.form.testing.MySubObject object at ...> # >>> v.foofield # 42 # >>> v.barfield # 666 # >>> v.__marker__ # ‘ThisMustStayTheSame’
Do all that fun in add and edit forms too:
We have to provide an adapter first:
>>> zope.component.provideAdapter(z3c.form.browser.object.ObjectFieldWidget)
Forms and our objectwidget fire events on add and edit, setup a subscriber for those:
>>> eventlog = [] >>> import zope.lifecycleevent >>> @zope.component.adapter(zope.lifecycleevent.ObjectModifiedEvent) ... def logEvent(event): ... eventlog.append(event) >>> zope.component.provideHandler(logEvent) >>> @zope.component.adapter(zope.lifecycleevent.ObjectCreatedEvent) ... def logEvent2(event): ... eventlog.append(event) >>> zope.component.provideHandler(logEvent2)>>> def printEvents(): ... for event in eventlog: ... print str(event) ... if isinstance(event, zope.lifecycleevent.ObjectModifiedEvent): ... for attr in event.descriptions: ... print attr.interface ... print attr.attributes
We define an interface containing a subobject, and an addform for it:
>>> from z3c.form import form, field
>>> from z3c.form.testing import MyObject, IMyObject
Note, that creating an object will print some information about it:
>>> class MyAddForm(form.AddForm):
... fields = field.Fields(IMyObject)
... def create(self, data):
... print "MyAddForm.create", str(data)
... return MyObject(**data)
... def add(self, obj):
... self.context[obj.name] = obj
... def nextURL(self):
... pass
We create the form and try to update it:
>>> request = TestRequest() >>> myaddform = MyAddForm(root, request)>>> myaddform.update()
As usual, the form contains a widget manager with the expected widget
>>> myaddform.widgets.keys()
['subobject', 'name']
>>> myaddform.widgets.values()
[<ObjectWidget 'form.widgets.subobject'>, <TextWidget 'form.widgets.name'>]
The addform has our ObjectWidget which in turn contains a subform:
>>> myaddform.widgets['subobject'].subform
<z3c.form.object.ObjectSubForm object at ...>
Which in turn contains the sub-widgets:
>>> myaddform.widgets['subobject'].subform.widgets.keys()
['foofield', 'barfield']
If we want to render the addform, we must give it a template:
>>> import os
>>> from zope.app.pagetemplate import viewpagetemplatefile
>>> from z3c.form import tests
>>> def addTemplate(form):
... form.template = viewpagetemplatefile.BoundPageTemplate(
... viewpagetemplatefile.ViewPageTemplateFile(
... 'simple_edit.pt', os.path.dirname(tests.__file__)), form)
>>> addTemplate(myaddform)
Now rendering the addform renders the subform as well:
>>> print myaddform.render()
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form action=".">
<div class="row">
<label for="form-widgets-subobject">my object</label>
<div class="object-widget required">
<div class="label">
<label for="form-widgets-subobject-widgets-foofield">
<span>My foo field</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<input class="text-widget required int-field"
id="form-widgets-subobject-widgets-foofield"
name="form.widgets.subobject.widgets.foofield"
type="text" value="1,111">
</div>
<div class="label">
<label for="form-widgets-subobject-widgets-barfield">
<span>My dear bar</span>
</label>
</div>
<div class="widget">
<input class="text-widget int-field"
id="form-widgets-subobject-widgets-barfield"
name="form.widgets.subobject.widgets.barfield"
type="text" value="2,222">
</div>
<input name="form.widgets.subobject-empty-marker" type="hidden"
value="1">
</div>
</div>
<div class="row">
<label for="form-widgets-name">name</label>
<input class="text-widget required textline-field" id="form-widgets-name" name="form.widgets.name" type="text" value="">
</div>
<div class="action">
<input class="submit-widget button-field" id="form-buttons-add"
name="form.buttons.add" type="submit" value="Add">
</div>
</form>
</body>
</html>
We don’t have the object (yet) in the root:
>>> root['first']
Traceback (most recent call last):
...
KeyError: 'first'
Let’s try to add an object:
>>> request = TestRequest(form={ ... 'form.widgets.subobject.widgets.foofield':u'66', ... 'form.widgets.subobject.widgets.barfield':u'99', ... 'form.widgets.name':u'first', ... 'form.widgets.subobject-empty-marker':u'1', ... 'form.buttons.add':'Add'}) >>> myaddform.request = request>>> myaddform.update() MyAddForm.create {'subobject': <z3c.form.testing.MySubObject object at ...>, 'name': u'first'}
Wow, it got added:
>>> root['first'] <z3c.form.testing.MyObject object at ...>>>> root['first'].subobject <z3c.form.testing.MySubObject object at ...>
Field values need to be right:
>>> root['first'].subobject.foofield
66
>>> root['first'].subobject.barfield
99
Let’s see our event log:
>>> len(eventlog) 4>>> printEvents() <zope...ObjectCreatedEvent object at ...> <zope...ObjectModifiedEvent object at ...> <InterfaceClass z3c.form.testing.IMySubObject> ('foofield', 'barfield') <zope...ObjectCreatedEvent object at ...> <zope...contained.ContainerModifiedEvent object at ...>>>> eventlog=[]
Let’s try to edit that newly added object:
>>> class MyEditForm(form.EditForm): ... fields = field.Fields(IMyObject)>>> editform = MyEditForm(root['first'], TestRequest()) >>> addTemplate(editform) >>> editform.update()
Watch for the widget values in the HTML:
>>> print editform.render()
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form action=".">
<div class="row">
<label for="form-widgets-subobject">my object</label>
<div class="object-widget required">
<div class="label">
<label for="form-widgets-subobject-widgets-foofield">
<span>My foo field</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<input class="text-widget required int-field"
id="form-widgets-subobject-widgets-foofield"
name="form.widgets.subobject.widgets.foofield"
type="text" value="66">
</div>
<div class="label">
<label for="form-widgets-subobject-widgets-barfield">
<span>My dear bar</span>
</label>
</div>
<div class="widget">
<input class="text-widget int-field"
id="form-widgets-subobject-widgets-barfield"
name="form.widgets.subobject.widgets.barfield"
type="text" value="99">
</div>
<input name="form.widgets.subobject-empty-marker" type="hidden"
value="1">
</div>
</div>
<div class="row">
<label for="form-widgets-name">name</label>
<input class="text-widget required textline-field" id="form-widgets-name"
name="form.widgets.name" type="text" value="first">
</div>
<div class="action">
<input class="submit-widget button-field" id="form-buttons-apply"
name="form.buttons.apply" type="submit" value="Apply">
</div>
</form>
</body>
</html>
Let’s modify the values:
>>> request = TestRequest(form={
... 'form.widgets.subobject.widgets.foofield':u'43',
... 'form.widgets.subobject.widgets.barfield':u'55',
... 'form.widgets.name':u'first',
... 'form.widgets.subobject-empty-marker':u'1',
... 'form.buttons.apply':'Apply'})
They are still the same:
>>> root['first'].subobject.foofield 66 >>> root['first'].subobject.barfield 99>>> editform.request = request >>> editform.update()
Until we have updated the form:
>>> root['first'].subobject.foofield
43
>>> root['first'].subobject.barfield
55
Let’s see our event log:
>>> len(eventlog) 2>>> printEvents() <zope...ObjectModifiedEvent object at ...> <InterfaceClass z3c.form.testing.IMySubObject> ('foofield', 'barfield') <zope...ObjectModifiedEvent object at ...> <InterfaceClass z3c.form.testing.IMyObject> ('subobject',)>>> eventlog=[]
After the update the form says that the values got updated and renders the new values:
>>> print editform.render()
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<i>Data successfully updated.</i>
<form action=".">
<div class="row">
<label for="form-widgets-subobject">my object</label>
<div class="object-widget required">
<div class="label">
<label for="form-widgets-subobject-widgets-foofield">
<span>My foo field</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<input class="text-widget required int-field"
id="form-widgets-subobject-widgets-foofield"
name="form.widgets.subobject.widgets.foofield"
type="text" value="43">
</div>
<div class="label">
<label for="form-widgets-subobject-widgets-barfield">
<span>My dear bar</span>
</label>
</div>
<div class="widget">
<input class="text-widget int-field"
id="form-widgets-subobject-widgets-barfield"
name="form.widgets.subobject.widgets.barfield"
type="text" value="55">
</div>
<input name="form.widgets.subobject-empty-marker" type="hidden"
value="1">
</div>
</div>
<div class="row">
<label for="form-widgets-name">name</label>
<input class="text-widget required textline-field" id="form-widgets-name"
name="form.widgets.name" type="text" value="first">
</div>
<div class="action">
<input class="submit-widget button-field" id="form-buttons-apply"
name="form.buttons.apply" type="submit" value="Apply">
</div>
</form>
</body>
</html>
Let’s see if the widget keeps the old object on editing:
We add a special property to keep track of the object:
>>> root['first'].__marker__ = "ThisMustStayTheSame">>> root['first'].subobject.foofield 43 >>> root['first'].subobject.barfield 55
Let’s modify the values:
>>> request = TestRequest(form={ ... 'form.widgets.subobject.widgets.foofield':u'666', ... 'form.widgets.subobject.widgets.barfield':u'999', ... 'form.widgets.name':u'first', ... 'form.widgets.subobject-empty-marker':u'1', ... 'form.buttons.apply':'Apply'})>>> editform.request = request>>> editform.update()
Let’s check what are ther esults of the update:
>>> root['first'].subobject.foofield
666
>>> root['first'].subobject.barfield
999
>>> root['first'].__marker__
'ThisMustStayTheSame'
Let’s make a nasty error, by typing ‘bad’ instead of an integer:
>>> request = TestRequest(form={ ... 'form.widgets.subobject.widgets.foofield':u'99', ... 'form.widgets.subobject.widgets.barfield':u'bad', ... 'form.widgets.name':u'first', ... 'form.widgets.subobject-empty-marker':u'1', ... 'form.buttons.apply':'Apply'})>>> editform.request = request >>> eventlog=[] >>> editform.update()
Eventlog must be clean:
>>> len(eventlog)
0
Watch for the error message in the HTML: it has to appear at the field itself and at the top of the form:
>>> print editform.render()
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<i>There were some errors.</i>
<ul>
<li>
my object:
<div class="error">The entered value is not a valid integer literal.</div>
</li>
</ul>
<form action=".">
<div class="row">
<b>
<div class="error">The entered value is not a valid integer literal.</div>
</b>
<label for="form-widgets-subobject">my object</label>
<div class="object-widget required">
<div class="label">
<label for="form-widgets-subobject-widgets-foofield">
<span>My foo field</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<input class="text-widget required int-field"
id="form-widgets-subobject-widgets-foofield"
name="form.widgets.subobject.widgets.foofield"
type="text" value="99">
</div>
<div class="label">
<label for="form-widgets-subobject-widgets-barfield">
<span>My dear bar</span>
</label>
</div>
<div class="error">The entered value is not a valid integer literal.</div>
<div class="widget">
<input class="text-widget int-field"
id="form-widgets-subobject-widgets-barfield"
name="form.widgets.subobject.widgets.barfield"
type="text" value="bad">
</div>
<input name="form.widgets.subobject-empty-marker" type="hidden"
value="1">
</div>
</div>
<div class="row">
<label for="form-widgets-name">name</label>
<input class="text-widget required textline-field" id="form-widgets-name"
name="form.widgets.name" type="text" value="first">
</div>
<div class="action">
<input class="submit-widget button-field" id="form-buttons-apply"
name="form.buttons.apply" type="submit" value="Apply">
</div>
</form>
</body>
</html>
The object values must stay at the old ones:
>>> root['first'].subobject.foofield
666
>>> root['first'].subobject.barfield
999
Let’s make more errors: Now we enter ‘bad’ and ‘999999’, where ‘999999’ hits the upper limit of the field.
>>> request = TestRequest(form={ ... 'form.widgets.subobject.widgets.foofield':u'999999', ... 'form.widgets.subobject.widgets.barfield':u'bad', ... 'form.widgets.name':u'first', ... 'form.widgets.subobject-empty-marker':u'1', ... 'form.buttons.apply':'Apply'})>>> editform.request = request >>> editform.update()
Both errors must appear at the top of the form:
>>> print editform.render()
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<i>There were some errors.</i>
<ul>
<li>
my object:
<div class="error">Value is too big</div>
<div class="error">The entered value is not a valid integer literal.</div>
</li>
</ul>
<form action=".">
<div class="row">
<b>
<div class="error">Value is too big</div>
<div class="error">The entered value is not a valid integer literal.</div>
</b>
<label for="form-widgets-subobject">my object</label>
<div class="object-widget required">
<div class="label">
<label for="form-widgets-subobject-widgets-foofield">
<span>My foo field</span>
<span class="required">*</span>
</label>
</div>
<div class="error">Value is too big</div>
<div class="widget">
<input class="text-widget required int-field"
id="form-widgets-subobject-widgets-foofield"
name="form.widgets.subobject.widgets.foofield"
type="text" value="999999">
</div>
<div class="label">
<label for="form-widgets-subobject-widgets-barfield">
<span>My dear bar</span>
</label>
</div>
<div class="error">The entered value is not a valid integer literal.</div>
<div class="widget">
<input class="text-widget int-field"
id="form-widgets-subobject-widgets-barfield"
name="form.widgets.subobject.widgets.barfield"
type="text" value="bad">
</div>
<input name="form.widgets.subobject-empty-marker" type="hidden"
value="1">
</div>
</div>
<div class="row">
<label for="form-widgets-name">name</label>
<input class="text-widget required textline-field" id="form-widgets-name"
name="form.widgets.name" type="text" value="first">
</div>
<div class="action">
<input class="submit-widget button-field" id="form-buttons-apply"
name="form.buttons.apply" type="submit" value="Apply">
</div>
</form>
</body>
</html>
And of course, the object values do not get modified:
>>> root['first'].subobject.foofield
666
>>> root['first'].subobject.barfield
999
#Let’s make an other error, by typing a newline into name: # # >>> request = TestRequest(form={ # ... ‘form.widgets.subobject.widgets.foofield’:u‘1’, # ... ‘form.widgets.subobject.widgets.barfield’:u‘2’, # ... ‘form.widgets.name’:u’firstnline’, # ... ‘form.widgets.subobject-empty-marker’:u‘1’, # ... ‘form.buttons.apply’:’Apply’}) # # >>> editform.request = request # >>> eventlog=[] # >>> editform.update()
THIS IS BAD:
#>>> len(eventlog) #0 #>>> printEvents() #<zope.app.event.objectevent.ObjectModifiedEvent object at 0x97f688c> #<InterfaceClass z3c.form.testing.IMySubObject> #(‘foofield’, ‘barfield’)
Simple but often used use-case is the display form:
>>> editform = MyEditForm(root['first'], TestRequest())
>>> addTemplate(editform)
>>> editform.mode = interfaces.DISPLAY_MODE
>>> editform.update()
>>> print editform.render()
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form action=".">
<div class="row">
<label for="form-widgets-subobject">my object</label>
<div class="object-widget required">
<div class="label">
<label for="form-widgets-subobject-widgets-foofield">
<span>My foo field</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<span class="text-widget required int-field"
id="form-widgets-subobject-widgets-foofield">666</span>
</div>
<div class="label">
<label for="form-widgets-subobject-widgets-barfield">
<span>My dear bar</span>
</label>
</div>
<div class="widget">
<span class="text-widget int-field"
id="form-widgets-subobject-widgets-barfield">999</span>
</div>
</div>
</div>
<div class="row">
<label for="form-widgets-name">name</label>
<span class="text-widget required textline-field"
id="form-widgets-name">first</span>
</div>
<div class="action">
<input class="submit-widget button-field"
id="form-buttons-apply" name="form.buttons.apply" type="submit" value="Apply">
</div>
</form>
</body>
</html>
Let’s see what happens in HIDDEN_MODE: (not quite sane thing, but we want to see the objectwidget rendered in hidden mode)
>>> zope.component.provideAdapter( ... WidgetTemplateFactory(getPath('object_hidden.pt'), 'text/html'), ... (None, None, None, None, interfaces.IObjectWidget), ... IPageTemplate, name=interfaces.HIDDEN_MODE)>>> editform = MyEditForm(root['first'], TestRequest()) >>> addTemplate(editform) >>> editform.mode = interfaces.HIDDEN_MODE >>> editform.update()
Note, that the labels and the button is there because the form template for testing does/should not care about the form being hidden. What matters is that the objectwidget is rendered hidden.
>>> print editform.render()
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form action=".">
<div class="row">
<label for="form-widgets-subobject">my object</label>
<input id="form-widgets-subobject-widgets-foofield"
name="form.widgets.subobject.widgets.foofield"
value="666" class="hidden-widget" type="hidden" />
<input id="form-widgets-subobject-widgets-barfield"
name="form.widgets.subobject.widgets.barfield"
value="999" class="hidden-widget" type="hidden" />
</div>
<div class="row">
<label for="form-widgets-name">name</label>
<input id="form-widgets-name" name="form.widgets.name"
value="first" class="hidden-widget" type="hidden" />
</div>
<div class="action">
<input id="form-buttons-apply" name="form.buttons.apply"
class="submit-widget button-field" value="Apply" type="submit" />
</div>
</form>
</body>
</html>
Editforms might use dicts as context:
>>> newsub = MySubObject() >>> newsub.foofield = 78 >>> newsub.barfield = 87>>> class MyEditFormDict(form.EditForm): ... fields = field.Fields(IMyObject) ... def getContent(self): ... return {'subobject': newsub, 'name': u'blooki'}>>> editform = MyEditFormDict(None, TestRequest()) >>> addTemplate(editform) >>> editform.update()
Watch for the widget values in the HTML:
>>> print editform.render()
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form action=".">
<div class="row">
<label for="form-widgets-subobject">my object</label>
<div class="object-widget required">
<div class="label">
<label for="form-widgets-subobject-widgets-foofield">
<span>My foo field</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<input class="text-widget required int-field"
id="form-widgets-subobject-widgets-foofield"
name="form.widgets.subobject.widgets.foofield"
type="text" value="78">
</div>
<div class="label">
<label for="form-widgets-subobject-widgets-barfield">
<span>My dear bar</span>
</label>
</div>
<div class="widget">
<input class="text-widget int-field"
id="form-widgets-subobject-widgets-barfield"
name="form.widgets.subobject.widgets.barfield"
type="text" value="87">
</div>
<input name="form.widgets.subobject-empty-marker" type="hidden"
value="1">
</div>
</div>
<div class="row">
<label for="form-widgets-name">name</label>
<input class="text-widget required textline-field" id="form-widgets-name"
name="form.widgets.name" type="text" value="blooki">
</div>
<div class="action">
<input class="submit-widget button-field" id="form-buttons-apply"
name="form.buttons.apply" type="submit" value="Apply">
</div>
</form>
</body>
</html>
Let’s modify the values:
>>> request = TestRequest(form={
... 'form.widgets.subobject.widgets.foofield':u'43',
... 'form.widgets.subobject.widgets.barfield':u'55',
... 'form.widgets.name':u'first',
... 'form.widgets.subobject-empty-marker':u'1',
... 'form.buttons.apply':'Apply'})
They are still the same:
>>> newsub.foofield
78
>>> newsub.barfield
87
Until updating the form:
>>> editform.request = request >>> eventlog=[] >>> editform.update()>>> newsub.foofield 43 >>> newsub.barfield 55>>> len(eventlog) 2 >>> printEvents() <zope...ObjectModifiedEvent object at ...> <InterfaceClass z3c.form.testing.IMySubObject> ('foofield', 'barfield') <zope...ObjectModifiedEvent object at ...> <InterfaceClass z3c.form.testing.IMyObject> ('subobject', 'name')
We define an interface containing a subobject, and an addform for it:
>>> from z3c.form import form, field
>>> from z3c.form.testing import IMyComplexObject
Note, that creating an object will print some information about it:
>>> class MyAddForm(form.AddForm):
... fields = field.Fields(IMyComplexObject)
... def create(self, data):
... print "MyAddForm.create", str(data)
... return MyObject(**data)
... def add(self, obj):
... self.context[obj.name] = obj
... def nextURL(self):
... pass
We create the form and try to update it:
>>> request = TestRequest()>>> myaddform = MyAddForm(root, request)>>> myaddform.update()
As usual, the form contains a widget manager with the expected widget
>>> myaddform.widgets.keys()
['subobject', 'name']
>>> myaddform.widgets.values()
[<ObjectWidget 'form.widgets.subobject'>, <TextWidget 'form.widgets.name'>]
The addform has our ObjectWidget which in turn contains a subform:
>>> myaddform.widgets['subobject'].subform
<z3c.form.object.ObjectSubForm object at ...>
Which in turn contains the sub-widgets:
>>> myaddform.widgets['subobject'].subform.widgets.keys() ['subfield', 'moofield']>>> myaddform.widgets['subobject'].subform.widgets['subfield'].subform.widgets.keys() ['foofield', 'barfield']
If we want to render the addform, we must give it a template:
>>> addTemplate(myaddform)
Now rendering the addform renders the subform as well:
>>> print myaddform.render()
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form action=".">
<div class="row">
<label for="form-widgets-subobject">my object</label>
<div class="object-widget required">
<div class="label">
<label for="form-widgets-subobject-widgets-subfield">
<span>Second-subobject</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<div class="object-widget required">
<div class="label">
<label for="form-widgets-subobject-widgets-subfield-widgets-foofield">
<span>My foo field</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<input class="text-widget required int-field"
id="form-widgets-subobject-widgets-subfield-widgets-foofield"
name="form.widgets.subobject.widgets.subfield.widgets.foofield"
type="text" value="1,111">
</div>
<div class="label">
<label for="form-widgets-subobject-widgets-subfield-widgets-barfield">
<span>My dear bar</span>
</label>
</div>
<div class="widget">
<input class="text-widget int-field"
id="form-widgets-subobject-widgets-subfield-widgets-barfield"
name="form.widgets.subobject.widgets.subfield.widgets.barfield"
type="text" value="2,222">
</div>
<input name="form.widgets.subobject.widgets.subfield-empty-marker" type="hidden" value="1">
</div>
</div>
<div class="label">
<label for="form-widgets-subobject-widgets-moofield">
<span>Something</span>
<span class="required">*</span>
</label>
</div>
<div class="widget">
<input class="text-widget required textline-field"
id="form-widgets-subobject-widgets-moofield"
name="form.widgets.subobject.widgets.moofield" type="text" value="">
</div>
<input name="form.widgets.subobject-empty-marker" type="hidden" value="1">
</div>
</div>
<div class="row">
<label for="form-widgets-name">name</label>
<input class="text-widget required textline-field"
id="form-widgets-name" name="form.widgets.name" type="text" value="">
</div>
<div class="action">
<input class="submit-widget button-field" id="form-buttons-add" name="form.buttons.add" type="submit" value="Add">
</div>
</form>
</body>
</html>
Converting interfaces.NO_VALUE holds None:
>>> converter.toFieldValue(interfaces.NO_VALUE) is None
True
This is a complicated case. Happens when the context is a dict, and the dict misses the field. (Note, we’re making sub__object instead of subobject)
>>> context = dict(sub__object=None, foo=123, bar=456)
All the story the create a widget:
>>> field = zope.schema.Object( ... __name__='subobject', ... title=u'my object widget', ... schema=IMySubObject)>>> wv = {'foofield': 2, 'barfield': 999}>>> request = TestRequest() >>> widget = z3c.form.browser.object.ObjectWidget(request) >>> widget = FieldWidget(field, widget) >>> widget.context = context >>> widget.value = wv >>> widget.update() >>> converter = interfaces.IDataConverter(widget)
And still we get a MySubObject, no failure:
>>> value = converter.toFieldValue(wv)
>>> value
<z3c.form.testing.MySubObject object at ...>
>>> value.foofield
2
>>> value.barfield
999
Easy (after the previous). In case the previous value on the context is None (or missing). We need to create a new object to be able to set properties on.
>>> context['subobject'] = None
>>> value = converter.toFieldValue(wv)
>>> value
<z3c.form.testing.MySubObject object at ...>
>>> value.foofield
2
>>> value.barfield
999
In case there is something that cannot be adapted to the right interface, it just burps: (might be an idea to create in this case also a new blank object)
>>> context['subobject'] = u'brutal' >>> converter.toFieldValue(wv) Traceback (most recent call last): ... TypeError: ('Could not adapt', u'brutal', <InterfaceClass z3c.form.testing.IMySubObject>)>>> context['subobject'] = None
One more. Value to convert misses a field. Should never happen actually:
>>> wv = {'foofield': 2}
>>> value = converter.toFieldValue(wv)
Known property is set:
>>> value.foofield
2
Unknown sticks ti it’s default value:
>>> value.barfield
2222