[Checkins] SVN: grok/trunk/ A lot of work on formlib support.
Implementation by Martijn, after
Martijn Faassen
faassen at infrae.com
Fri Dec 1 13:03:59 EST 2006
Log message for revision 71364:
A lot of work on formlib support. Implementation by Martijn, after
extensive discussions with JW.
Previously, we were subclassing zope.formlib.form.*Form. This lead to
rather grotty name conflicts with our basic grok View - view gives
the names 'template', and 'render' special meaning, but formlib does
also.
Instead of untying this hairiness, we decided to use composition instead
of inheritance; instead of subclassing formlib's form, we now have
a 'form' attribute that contains the real formlib-level form.
New features:
* custom templates for forms. Get formlib specific information such
as widgets and actions from the 'form' attribute of the view.
* new barebones default templates for EditForm and DisplayForm
* can completely override a form's behavior by using form_fields on
EditForm or DisplayForm.
Changed:
U grok/trunk/TODO.txt
U grok/trunk/src/grok/__init__.py
U grok/trunk/src/grok/_grok.py
U grok/trunk/src/grok/components.py
A grok/trunk/src/grok/formlib.py
U grok/trunk/src/grok/ftests/form/actions.py
U grok/trunk/src/grok/ftests/form/form.py
A grok/trunk/src/grok/ftests/form/templateform.py
U grok/trunk/src/grok/interfaces.py
A grok/trunk/src/grok/templates/
A grok/trunk/src/grok/templates/default_display_form.pt
A grok/trunk/src/grok/templates/default_edit_form.pt
A grok/trunk/src/grok/tests/form/customform.py
U grok/trunk/src/grok/tests/form/form.py
A grok/trunk/src/grok/tests/form/norender.py
A grok/trunk/src/grok/tests/form/norender2.py
U grok/trunk/src/grok/tests/form/schemaform.py
-=-
Modified: grok/trunk/TODO.txt
===================================================================
--- grok/trunk/TODO.txt 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/TODO.txt 2006-12-01 18:03:58 UTC (rev 71364)
@@ -49,14 +49,8 @@
Schema/formlib support
----------------------
-- support rendering a form from an interface
-
- support nested class 'fields' directly on a view (do we really want this?)
-- provide default (better looking) template and styles for formlib in grok
-
-- custom templates for forms
-
- add form
- list form for grok.Container (w/ zc.table?)
Modified: grok/trunk/src/grok/__init__.py
===================================================================
--- grok/trunk/src/grok/__init__.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/__init__.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -16,7 +16,7 @@
from zope.interface import implements
from zope.component import adapts
-from zope.formlib.form import action, Fields
+from zope.formlib.form import Fields
from zope.event import notify
from zope.app.component.hooks import getSite
from zope.lifecycleevent import (
@@ -37,6 +37,7 @@
from grok._grok import do_grok as grok # Avoid name clash within _grok
from grok._grok import SubscribeDecorator as subscribe
from grok.error import GrokError, GrokImportError
+from grok.formlib import action
# Our __init__ provides the grok API directly so using 'import grok' is enough.
from grok.interfaces import IGrokAPI
Modified: grok/trunk/src/grok/_grok.py
===================================================================
--- grok/trunk/src/grok/_grok.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/_grok.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -26,14 +26,13 @@
from zope.publisher.interfaces.browser import (IDefaultBrowserLayer,
IBrowserRequest,
IBrowserPublisher)
-
from zope.app.publisher.xmlrpc import MethodPublisher
from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
from zope.app.component.site import LocalSiteManager
import grok
-from grok import util, scan, components, security
+from grok import util, scan, components, security, formlib
from grok.error import GrokError, GrokImportError
from grok.directive import (ClassDirectiveContext, ModuleDirectiveContext,
ClassOrModuleDirectiveContext,
@@ -110,7 +109,8 @@
find_filesystem_templates(module_info, templates)
- context = util.determine_module_context(module_info, components[grok.Model])
+ context = util.determine_module_context(module_info,
+ components[grok.Model])
register_models(components[grok.Model])
register_adapters(context, components[grok.Adapter])
@@ -123,7 +123,7 @@
register_subscribers(subscribers)
# Do various other initializations
- initialize_schema(components[grok.Model])
+ formlib.initialize_schema(components[grok.Model])
def scan_module(module_info):
models = []
@@ -212,12 +212,6 @@
if not getCheckerForInstancesOf(model):
defineChecker(model, NoProxy)
-def initialize_schema(models):
- # Set the default values as class attributes to make formlib work
- for model in models:
- for field in components.schema_fields(model):
- setattr(model, field.__name__, field.default)
-
def register_adapters(context, adapters):
for factory in adapters:
adapter_context = util.determine_class_context(factory, context)
@@ -258,9 +252,16 @@
def register_views(context, views, templates):
for factory in views:
view_context = util.determine_class_context(factory, context)
+
+ # some extra work to take care of if this view is a form
+ if util.check_subclass(factory, components.EditForm):
+ formlib.setup_editform(factory, view_context)
+ elif util.check_subclass(factory, components.DisplayForm):
+ formlib.setup_displayform(factory, view_context)
+
factory_name = factory.__name__.lower()
- # find inline templates
+ # find templates
template_name = util.class_annotation(factory, 'grok.template',
factory_name)
template = templates.get(template_name)
@@ -273,21 +274,40 @@
"a template called '%s'."
% (factory, template_name, factory_name),
factory)
+
+ # we never accept a 'render' method for forms
+ if util.check_subclass(factory, components.Form):
+ if getattr(factory, 'render', None):
+ raise GrokError(
+ "It is not allowed to specify a custom 'render' "
+ "method for form %r. Forms either use the default "
+ "template or a custom-supplied one." % factory,
+ factory)
if template:
if getattr(factory, 'render', None):
- raise GrokError("Multiple possible ways to render view %r. "
- "It has both a 'render' method as well as "
- "an associated template." % factory,
- factory)
+ # we do not accept render and template both for a view
+ raise GrokError(
+ "Multiple possible ways to render view %r. "
+ "It has both a 'render' method as well as "
+ "an associated template." % factory,
+ factory)
templates.markAssociated(template_name)
factory.template = template
else:
if not getattr(factory, 'render', None):
- raise GrokError("View %r has no associated template or "
- "'render' method." % factory,
- factory)
+ if util.check_subclass(factory, components.EditForm):
+ # we have a edit form without template
+ factory.template = formlib.defaultEditTemplate
+ elif util.check_subclass(factory, components.DisplayForm):
+ # we have a display form without template
+ factory.template = formlib.defaultDisplayTemplate
+ else:
+ # we do not accept a view without any way to render it
+ raise GrokError("View %r has no associated template or "
+ "'render' method." % factory,
+ factory)
view_name = util.class_annotation(factory, 'grok.name', factory_name)
# __view_name__ is needed to support IAbsoluteURL on views
@@ -296,7 +316,7 @@
adapts=(view_context, IDefaultBrowserLayer),
provides=interface.Interface,
name=view_name)
-
+
# TODO minimal security here (read: everything is public)
defineChecker(factory, NoProxy)
Modified: grok/trunk/src/grok/components.py
===================================================================
--- grok/trunk/src/grok/components.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/components.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -15,12 +15,13 @@
"""
import persistent
-import types
import urllib
from zope import component
from zope import interface
from zope import schema
+from zope import event
+from zope.lifecycleevent import ObjectModifiedEvent
from zope.security.proxy import removeSecurityProxy
from zope.publisher.browser import BrowserPage
from zope.publisher.interfaces import NotFound
@@ -29,7 +30,6 @@
from zope.pagetemplate import pagetemplate
from zope.formlib import form
from zope.formlib.namedtemplate import INamedTemplate
-from zope.schema.interfaces import IField
from zope.traversing.browser.interfaces import IAbsoluteURL
from zope.traversing.browser.absoluteurl import AbsoluteURL
from zope.traversing.browser.absoluteurl import _safe as SAFE_URL_CHARACTERS
@@ -249,43 +249,45 @@
return self.context.get(name)
class Form(View):
- def _init(self):
- fields = form.Fields(*schema_fields(self.context))
- fields += form.Fields(*interface.providedBy(self.context))
- fields = fields.omit('__name__')
- self.form_fields = fields
+ def __init__(self, context, request):
+ super(Form, self).__init__(context, request)
+ self.form = self.__real_form__(context, request)
+ # we need this pointer to the actual grok-level form in our
+ # custom Action
+ self.form.grok_form = self
- self.template = component.getAdapter(self, INamedTemplate,
- name='default')
def __call__(self):
- self.update()
- return self.render()
+ form = self.form
-class EditForm(Form, form.EditForm):
- def __init__(self, context, request):
- super(EditForm, self).__init__(context, request)
- self._init()
+ # update form
+ form.update()
- def default_handle_apply(self, action, data):
- form.EditForm.handle_edit_action.success_handler(self, action, data)
+ # this code is extracted and modified from form.render
+
+ # if the form has been updated, it may already have a result
+ if form.form_result is None:
+ # we reset, in case data has changed in a way that
+ # causes the widgets to have different data
+ if form.form_reset:
+ form.resetForm()
+ form.form_reset = False
+ # recalculate result
+ form.form_result = super(Form, self).__call__()
-class DisplayForm(Form, form.DisplayForm):
- def __init__(self, context, request):
- super(DisplayForm, self).__init__(context, request)
- self._init()
+ return form.form_result
+
+class EditForm(Form):
+ label = ''
+ status = ''
+
+ def applyChanges(self, action, data):
+ if form.applyChanges(self.context, self.form.form_fields, data,
+ self.form.adapters):
+ event.notify(ObjectModifiedEvent(self.context))
+ self.status = "Updated"
+ else:
+ self.status = "No changes"
-def schema_fields(obj):
- fields = []
- fields_class = getattr(obj, 'fields', None)
- if fields_class is None:
- return fields
- if type(fields_class) != types.ClassType:
- return fields
- for name in dir(fields_class):
- field = getattr(fields_class, name)
- if IField.providedBy(field):
- if not getattr(field, '__name__', None):
- field.__name__ = name
- fields.append(field)
- fields.sort(key=lambda field: field.order)
- return fields
+class DisplayForm(Form):
+ label = ''
+ status = ''
Added: grok/trunk/src/grok/formlib.py
===================================================================
--- grok/trunk/src/grok/formlib.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/formlib.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -0,0 +1,111 @@
+import os, types
+from zope import interface
+from zope.interface.interfaces import IInterface
+from zope.formlib import form
+from zope.schema.interfaces import IField
+from grok import components
+
+class action(form.action):
+ """We override the action decorator we pass in our custom Action.
+ """
+ def __call__(self, success):
+ action = Action(self.label, success=success, **self.options)
+ self.actions.append(action)
+ return action
+
+class Action(form.Action):
+ def success(self, data):
+ if self.success_handler is not None:
+ return self.success_handler(self.form.grok_form, self, data)
+
+def setup_editform(factory, context):
+ """Construct the real edit form, taking needed information from factory.
+ """
+ actions_ = getattr(factory, 'actions', None)
+ if actions_ is None:
+ # set up the default edit action
+ actions_ = form.Actions(form.EditForm.handle_edit_action)
+
+ class RealEditForm(form.EditForm):
+ form_fields = get_form_fields(factory, context)
+ actions = actions_
+ # we do not use the class annotation infrastructure as we use
+ # this information during *runtime* not groktime.
+ factory.__real_form__ = RealEditForm
+
+def setup_displayform(factory, context):
+ """Construct the real display form, taking needed information from factory.
+ """
+ # get actions; by default no actions at all
+ actions_ = getattr(factory, 'actions', form.Actions())
+
+ class RealDisplayForm(form.DisplayForm):
+ form_fields = get_form_fields(factory, context)
+ actions = actions_
+ # we do not use the class annotation infrastructure as we use
+ # this information during *runtime* not groktime.
+ factory.__real_form__ = RealDisplayForm
+
+def initialize_schema(models):
+ """Set the default values as class attributes to make formlib work
+ """
+ for model in models:
+ for field in get_context_schema_fields(model):
+ setattr(model, field.__name__, field.default)
+
+def get_context_schema_fields(context):
+ """Get the schema fields for a context object.
+ """
+ fields = []
+ fields_class = getattr(context, 'fields', None)
+ # bail out if there is no fields attribute at all
+ if fields_class is None:
+ return fields
+ # bail out if there's a fields attribute but it isn't an old-style class
+ if type(fields_class) != types.ClassType:
+ return fields
+ # get the fields from the class
+ for name in dir(fields_class):
+ field = getattr(fields_class, name)
+ if IField.providedBy(field):
+ if not getattr(field, '__name__', None):
+ field.__name__ = name
+ fields.append(field)
+ fields.sort(key=lambda field: field.order)
+ return fields
+
+def get_form_fields(factory, context):
+ """Get the form fields for a factory.
+
+ factory - the factory (view) we're determining the form fields for
+ context - the context that the factory creates a view for
+ """
+ # first check whether the factory already defines form fields,
+ # in which case we're done as those always override everything
+ fields = getattr(factory, 'form_fields', None)
+ if fields is not None:
+ return fields
+ # for an interface context, we generate them from that interface
+ if IInterface.providedBy(context):
+ return form.Fields(context)
+ # if we have a non-interface context,
+ # we're autogenerating them from any model-specific
+ # fields along with any schemas defined by the context
+ fields = form.Fields(*get_context_schema_fields(context))
+ fields += form.Fields(*interface.implementedBy(context))
+ # we pull in this field by default, but we don't want it in our form
+ fields = fields.omit('__name__')
+ return fields
+
+def load_template(name):
+ filename = os.path.join(os.path.dirname(__file__), 'templates', name)
+ f = open(filename, 'r')
+ result = f.read()
+ f.close()
+ return result
+
+defaultEditTemplate = components.PageTemplate(load_template(
+ 'default_edit_form.pt'))
+
+defaultDisplayTemplate = components.PageTemplate(load_template(
+ 'default_display_form.pt'))
Modified: grok/trunk/src/grok/ftests/form/actions.py
===================================================================
--- grok/trunk/src/grok/ftests/form/actions.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/ftests/form/actions.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -2,7 +2,7 @@
Using the @grok.action decorator, different actions can be defined on a
grok.EditForm. When @grok.action is used, the default behaviour (the 'Apply'
action) is not available anymore, but it can triggered manually by calling
-self.default_handle_apply(action, data).
+self.applyChanges(action, data).
>>> import grok
>>> from grok.ftests.form.actions import Mammoth
@@ -17,16 +17,17 @@
>>> browser.getControl(name="form.size").value = "Really big"
>>> browser.getControl("Apply").click()
>>> print browser.contents
- <!DOCTYPE ...
+ <html>...
...Manfred the Mammoth...
...Really big...
...
>>> browser.open("http://localhost/manfred/@@edit")
+ >>> browser.getControl(name="form.name").value = "Manfred the Second"
>>> browser.getControl("Hairy").click()
>>> print browser.contents
- <!DOCTYPE ...
- ...Manfred the Mammoth...
+ <html>...
+ ...Manfred the Second...
...Really big and hairy...
...
"""
@@ -41,10 +42,9 @@
class Edit(grok.EditForm):
@grok.action("Apply")
def handle_apply(self, action, data):
- self.default_handle_apply(action, data)
+ self.applyChanges(action, data)
@grok.action("Hairy")
def handle_hairy(self, action, data):
- self.default_handle_apply(action, data)
+ self.applyChanges(action, data)
self.context.size += " and hairy"
-
Modified: grok/trunk/src/grok/ftests/form/form.py
===================================================================
--- grok/trunk/src/grok/ftests/form/form.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/ftests/form/form.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -14,7 +14,7 @@
>>> browser.getControl(name="form.size").value = "Really big"
>>> browser.getControl("Apply").click()
>>> print browser.contents
- <!DOCTYPE ...
+ <html>...
...Manfred the Mammoth...
...Really big...
...
@@ -23,7 +23,7 @@
>>> browser.open("http://localhost/manfred/@@display")
>>> print browser.contents
- <!DOCTYPE ...
+ <html>...
...Manfred the Mammoth...
...Really big...
...
Added: grok/trunk/src/grok/ftests/form/templateform.py
===================================================================
--- grok/trunk/src/grok/ftests/form/templateform.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/ftests/form/templateform.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -0,0 +1,60 @@
+"""
+If a form does not have a template, a simple default template is
+associated with them. Otherwise, the supplied template is used.
+
+ >>> import grok
+ >>> from grok.ftests.form.templateform import Mammoth
+ >>> grok.grok('grok.ftests.form.templateform')
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
+ >>> from zope import component
+
+Default edit template:
+
+ >>> view = component.getMultiAdapter((Mammoth(), request), name='edit')
+ >>> print view()
+ <html>...
+
+Custom edit template:
+
+ >>> view = component.getMultiAdapter((Mammoth(), request), name='edit2')
+ >>> print view()
+ <p>Test edit</p>
+
+Default display template:
+
+ >>> view = component.getMultiAdapter((Mammoth(), request), name='display')
+ >>> print view()
+ <html>...
+
+Custom display template:
+
+ >>> view = component.getMultiAdapter((Mammoth(), request), name='display2')
+ >>> print view()
+ <p>Test display</p>
+
+"""
+import grok
+from zope import schema
+
+class Mammoth(grok.Model):
+ class fields:
+ name = schema.TextLine(title=u"Name")
+ size = schema.TextLine(title=u"Size", default=u"Quite normal")
+
+class Edit(grok.EditForm):
+ pass
+
+class Edit2(grok.EditForm):
+ pass
+
+edit2 = grok.PageTemplate('<p>Test edit</p>')
+
+class Display(grok.DisplayForm):
+ pass
+
+class Display2(grok.DisplayForm):
+ pass
+
+display2 = grok.PageTemplate('<p>Test display</p>')
Modified: grok/trunk/src/grok/interfaces.py
===================================================================
--- grok/trunk/src/grok/interfaces.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/interfaces.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -28,7 +28,8 @@
XMLRPC = interface.Attribute("Base class for XML-RPC methods.")
Traverser = interface.Attribute("Base class for custom traversers.")
EditForm = interface.Attribute("Base class for edit forms.")
-
+ DisplayForm = interface.Attribute("Base class form display forms.")
+
class IGrokErrors(interface.Interface):
def GrokError(message, component):
@@ -134,6 +135,10 @@
"""Return a list of formlib fields based on interfaces and/or schema
fields."""
+ def action(label, actions=None, **options):
+ """grok-specific action decorator.
+ """
+
class IGrokView(interface.Interface):
"""Grok views all provide this interface.
"""
Added: grok/trunk/src/grok/templates/default_display_form.pt
===================================================================
--- grok/trunk/src/grok/templates/default_display_form.pt 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/templates/default_display_form.pt 2006-12-01 18:03:58 UTC (rev 71364)
@@ -0,0 +1,36 @@
+<html>
+<head>
+</head>
+
+<body>
+ <table class="listing">
+ <thead>
+ <tr>
+ <th class="label-column"> </th>
+ <th> </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tal:block repeat="widget view/form/widgets">
+ <tr tal:define="odd repeat/widget/odd"
+ tal:attributes="class python: odd and 'odd' or 'even'">
+ <td class="fieldname">
+ <tal:block content="widget/label"/>
+ </td>
+ <td>
+ <input tal:replace="structure widget" />
+ </td>
+ </tr>
+ </tal:block>
+ </tbody>
+ <tfoot>
+ <tr class="controls">
+ <td colspan="2" class="align-right">
+ <input tal:repeat="action view/form/actions"
+ tal:replace="structure action/render" />
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+</body>
+</html>
Added: grok/trunk/src/grok/templates/default_edit_form.pt
===================================================================
--- grok/trunk/src/grok/templates/default_edit_form.pt 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/templates/default_edit_form.pt 2006-12-01 18:03:58 UTC (rev 71364)
@@ -0,0 +1,69 @@
+<html>
+<head>
+</head>
+
+<body>
+<form action="." tal:attributes="action request/URL" method="post"
+ class="edit-form" enctype="multipart/form-data">
+
+ <h1 i18n:translate=""
+ tal:condition="view/label"
+ tal:content="view/label">Label</h1>
+
+ <div class="form-status"
+ tal:define="status view/status"
+ tal:condition="status">
+
+ <div i18n:translate="" tal:content="view/status">
+ Form status summary
+ </div>
+
+ <ul class="errors" tal:condition="view/form/errors">
+ <li tal:repeat="error view/form/error_views">
+ <span tal:replace="structure error">Error Type</span>
+ </li>
+ </ul>
+ </div>
+
+ <table class="form-fields">
+ <tbody>
+ <tal:block repeat="widget view/form/widgets">
+ <tr>
+ <td class="label" tal:define="hint widget/hint">
+ <label tal:condition="python:hint"
+ tal:attributes="for widget/name">
+ <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">
+ <div class="widget" tal:content="structure widget">
+ <input type="text" />
+ </div>
+ <div class="error" tal:condition="widget/error">
+ <span tal:replace="structure widget/error">error</span>
+ </div>
+ </td>
+ </tr>
+ </tal:block>
+ </tbody>
+ </table>
+
+ <div id="actionsView">
+ <span class="actionButtons" tal:condition="view/form/availableActions">
+ <input tal:repeat="action view/form/actions"
+ tal:replace="structure action/render"
+ />
+ </span>
+ </div>
+</form>
+
+</body>
+</html>
Added: grok/trunk/src/grok/tests/form/customform.py
===================================================================
--- grok/trunk/src/grok/tests/form/customform.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/tests/form/customform.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -0,0 +1,41 @@
+"""
+A form view can completely override which fields are displayed by setting
+form_fields manually:
+
+ >>> grok.grok(__name__)
+
+We need to set up the default formlib template first, because even though we
+don't use the formlib NamedTemplates directly they need to be present to create
+a formlib form.
+
+ >>> from zope import component
+ >>> from zope.formlib import form
+ >>> component.provideAdapter(form.default_page_template, name='default')
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
+
+We only expect a single field to be present in the form, as we omitted 'size':
+
+ >>> view = component.getMultiAdapter((Mammoth(), request), name='edit')
+ >>> len(view.form_fields)
+ 1
+ >>> [w.__name__ for w in view.form.form_fields]
+ ['name']
+
+"""
+
+import grok
+from zope import interface, schema
+
+class IMammoth(interface.Interface):
+ name = schema.TextLine(title=u"Name")
+ size = schema.TextLine(title=u"Size", default=u"Quite normal")
+
+class Mammoth(grok.Model):
+ interface.implements(IMammoth)
+
+class Edit(grok.EditForm):
+ grok.context(Mammoth)
+
+ form_fields = grok.Fields(IMammoth).omit('size')
Modified: grok/trunk/src/grok/tests/form/form.py
===================================================================
--- grok/trunk/src/grok/tests/form/form.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/tests/form/form.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -37,18 +37,18 @@
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
>>> view = component.getMultiAdapter((manfred, request), name='edit')
- >>> len(view.form_fields)
+ >>> len(view.form.form_fields)
2
- >>> [w.__name__ for w in view.form_fields]
+ >>> [w.__name__ for w in view.form.form_fields]
['name', 'size']
It is important to keep the order of the fields:
>>> view = component.getMultiAdapter(
... (DifferentMammoth(), request), name='editdifferent')
- >>> len(view.form_fields)
+ >>> len(view.form.form_fields)
2
- >>> [w.__name__ for w in view.form_fields]
+ >>> [w.__name__ for w in view.form.form_fields]
['size', 'name']
"""
Added: grok/trunk/src/grok/tests/form/norender.py
===================================================================
--- grok/trunk/src/grok/tests/form/norender.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/tests/form/norender.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -0,0 +1,24 @@
+"""
+Forms cannot define a render method. Here we show the case where the
+EditForm has an explicit template associate with it.
+
+ >>> grok.grok(__name__)
+ Traceback (most recent call last):
+ ...
+ GrokError: It is not allowed to specify a custom 'render' method for
+ form <class 'grok.tests.form.norender.Edit'>. Forms either use the default
+ template or a custom-supplied one.
+
+"""
+
+import grok
+
+class Mammoth(grok.Model):
+ pass
+
+class Edit(grok.EditForm):
+ # not allowed to have a render method
+ def render(self):
+ return "this cannot be"
+
+edit = grok.PageTemplate('Foo!')
Added: grok/trunk/src/grok/tests/form/norender2.py
===================================================================
--- grok/trunk/src/grok/tests/form/norender2.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/tests/form/norender2.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -0,0 +1,22 @@
+"""
+Forms cannot define a render method. Here we show the case where the
+EditForm has no explicit template associated with it:
+
+ >>> grok.grok(__name__)
+ Traceback (most recent call last):
+ ...
+ GrokError: It is not allowed to specify a custom 'render' method for
+ form <class 'grok.tests.form.norender2.Edit'>. Forms either use the default
+ template or a custom-supplied one.
+
+"""
+
+import grok
+
+class Mammoth(grok.Model):
+ pass
+
+class Edit(grok.EditForm):
+ # not allowed to have a render method
+ def render(self):
+ return "this cannot be"
Modified: grok/trunk/src/grok/tests/form/schemaform.py
===================================================================
--- grok/trunk/src/grok/tests/form/schemaform.py 2006-12-01 17:49:24 UTC (rev 71363)
+++ grok/trunk/src/grok/tests/form/schemaform.py 2006-12-01 18:03:58 UTC (rev 71364)
@@ -17,28 +17,38 @@
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
>>> view = component.getMultiAdapter((manfred, request), name='edit')
- >>> len(view.form_fields)
+ >>> len(view.form.form_fields)
2
- >>> [w.__name__ for w in view.form_fields]
+ >>> [w.__name__ for w in view.form.form_fields]
['name', 'size']
When there are multiple schemas in play, we get all the fields:
>>> view = component.getMultiAdapter((Manfred(), request), name='edit2')
- >>> len(view.form_fields)
+ >>> len(view.form.form_fields)
3
- >>> [w.__name__ for w in view.form_fields]
+ >>> [w.__name__ for w in view.form.form_fields]
['can_talk', 'name', 'size']
Schema fields and model level fields are combined:
>>> view = component.getMultiAdapter(
... (AnotherMammoth(), request), name='edit3')
- >>> len(view.form_fields)
+ >>> len(view.form.form_fields)
3
- >>> [w.__name__ for w in view.form_fields]
+ >>> [w.__name__ for w in view.form.form_fields]
['can_talk', 'name', 'size']
+If the context is an interface instead of a model directly, the fields
+will be retrieved from that interface, and that interface only:
+
+ >>> view = component.getMultiAdapter(
+ ... (YetAnotherMammoth(), request), name='edit4')
+ >>> len(view.form.form_fields)
+ 2
+ >>> [w.__name__ for w in view.form.form_fields]
+ ['alpha', 'beta']
+
"""
import grok
from zope import interface, schema
@@ -61,10 +71,20 @@
class Edit2(grok.EditForm):
grok.context(Manfred)
-
+
class AnotherMammoth(Mammoth):
class fields:
can_talk = schema.Bool(title=u'Can talk', default=False)
class Edit3(grok.EditForm):
grok.context(AnotherMammoth)
+
+class IYetAnotherMammoth(interface.Interface):
+ alpha = schema.TextLine(title=u'alpha')
+ beta = schema.TextLine(title=u'beta')
+
+class YetAnotherMammoth(grok.Model):
+ interface.implements(IYetAnotherMammoth)
+
+class Edit4(grok.EditForm):
+ grok.context(IYetAnotherMammoth)
More information about the Checkins
mailing list