[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">&nbsp;</th>
+        <th>&nbsp;</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