[z3-checkins] r9705 - in z3/Five/trunk: . tests/products/FiveTest

faassen at codespeak.net faassen at codespeak.net
Tue Mar 8 16:13:06 MET 2005


Author: faassen
Date: Tue Mar  8 16:13:06 2005
New Revision: 9705

Modified:
   z3/Five/trunk/browser.py
   z3/Five/trunk/browserconfigure.py
   z3/Five/trunk/configure.zcml
   z3/Five/trunk/meta.zcml
   z3/Five/trunk/tests/products/FiveTest/configure.zcml
   z3/Five/trunk/traversable.py
Log:
Initial support for add views.

What it adds:

* browser:view directive

* container/+/addfoo.html style add views, based on schema. That is, addform
  and adding (+) support for ObjectManager.

Limitations:

* no tests yet (this will be corrected soon)

* if you want to do this for something else than ObjectManager subclasses,
  may be complicated.
 
* browsing to +/ directly doesn't work properly yet.

* completely untested support for container constraint machinery



Modified: z3/Five/trunk/browser.py
==============================================================================
--- z3/Five/trunk/browser.py	(original)
+++ z3/Five/trunk/browser.py	Tue Mar  8 16:13:06 2005
@@ -11,12 +11,18 @@
 $Id$
 """
 
+# python
+import sys
+from datetime import datetime
+
+# Zope 2
 import Acquisition
 from  Acquisition import aq_inner, aq_parent, aq_base
 from AccessControl import ClassSecurityInfo
 from Globals import InitializeClass
+
+# Zope 3
 from interfaces import ITraversable
-from datetime import datetime
 from zope.interface import implements
 from zope.interface.common.mapping import IItemMapping
 from zope.component import getView
@@ -27,8 +33,14 @@
 from zope.app.form.utility import setUpEditWidgets, applyWidgetsChanges
 from zope.app.form.browser.submit import Update
 from zope.app.form.interfaces import WidgetsError, MissingInputError
-from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+from zope.event import notify
+from zope.app.form.utility import setUpWidgets, getWidgetsData
+from zope.app.form.interfaces import IInputWidget, WidgetsError
+from zope.schema.interfaces import ValidationError
+from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent
 
+# Five
+from Products.Five.pagetemplatefile import FivePageTemplateFile
 
 class BrowserView(Acquisition.Explicit):
     security = ClassSecurityInfo()
@@ -96,6 +108,32 @@
                  'url': context.absolute_url()
                  },)
 
+class Macros:
+    implements(IItemMapping)
+
+    macro_pages = ()
+    aliases = {
+        'view': 'page',
+        'dialog': 'page',
+        'addingdialog': 'page'
+        }
+
+    def __getitem__(self, key):
+        key = self.aliases.get(key, key)
+        context = self.context
+        request = self.request
+        for name in self.macro_pages:
+            page = getView(context, name, request)
+            try:
+                v = page[key]
+            except KeyError:
+                pass
+            else:
+                return v
+        raise KeyError, key
+
+class StandardMacros(BrowserView, Macros):
+    pass
 
 class EditView(BrowserView):
     """Simple edit-view base class
@@ -111,7 +149,7 @@
     # Fall-back field names computes from schema
     fieldNames = property(lambda self: getFieldNamesInOrder(self.schema))
     # Fall-back template
-    generated_form = ViewPageTemplateFile('edit.pt')
+    generated_form = FivePageTemplateFile('edit.pt')
 
     def __init__(self, context, request):
         BrowserView.__init__(self, context, request)
@@ -180,31 +218,99 @@
         self.update_status = status
         return status
 
+class AddView(EditView):
+    """Simple edit-view base class.
 
-class Macros:
+    Subclasses should provide a schema attribute defining the schema
+    to be edited.
+    """
 
-    implements(IItemMapping)
+    def _setUpWidgets(self):
+        setUpWidgets(self, self.schema, IInputWidget, names=self.fieldNames)
 
-    macro_pages = ()
-    aliases = {
-        'view': 'page',
-        'dialog': 'page',
-        'addingdialog': 'page'
-        }
+    def update(self):
 
-    def __getitem__(self, key):
-        key = self.aliases.get(key, key)
-        context = self.context
-        request = self.request
-        for name in self.macro_pages:
-            page = getView(context, name, request)
+        if self.update_status is not None:
+            # We've been called before. Just return the previous result.
+            return self.update_status
+
+        if self.request.form.has_key(Update):
+
+            self.update_status = ''
             try:
-                v = page[key]
-            except KeyError:
-                pass
-            else:
-                return v
-        raise KeyError, key
+                data = getWidgetsData(self, self.schema, names=self.fieldNames)
+                self.createAndAdd(data)
+            except WidgetsError, errors:
+                self.errors = errors
+                self.update_status = "An error occured."
+                return self.update_status
+
+            self.request.response.redirect(self.nextURL())
+
+        return self.update_status
+
+    def create(self, *args, **kw):
+        """Do the actual instantiation."""
+        # hack to please typical Zope 2 factories, which expect id and title
+        args = ('tmp_id', 'Temporary title') + args
+        return self._factory(*args, **kw)
+
+    def createAndAdd(self, data):
+        """Add the desired object using the data in the data argument.
+
+        The data argument is a dictionary with the data entered in the form.
+        """
+
+        args = []
+        if self._arguments:
+            for name in self._arguments:
+                args.append(data[name])
+
+        kw = {}
+        if self._keyword_arguments:
+            for name in self._keyword_arguments:
+                if name in data:
+                    kw[str(name)] = data[name]
+
+        content = self.create(*args, **kw)
+        adapted = self.schema(content)
+
+        errors = []
+
+        if self._set_before_add:
+            for name in self._set_before_add:
+                if name in data:
+                    field = self.schema[name]
+                    try:
+                        field.set(adapted, data[name])
+                    except ValidationError:
+                        errors.append(sys.exc_info()[1])
+
+        if errors:
+            raise WidgetsError(*errors)
+
+        notify(ObjectCreatedEvent(content))
+
+        content = self.add(content)
+
+        adapted = self.schema(content)
+
+        if self._set_after_add:
+            for name in self._set_after_add:
+                if name in data:
+                    field = self.schema[name]
+                    try:
+                        field.set(adapted, data[name])
+                    except ValidationError:
+                        errors.append(sys.exc_info()[1])
+
+        if errors:
+            raise WidgetsError(*errors)
+
+        return content
 
-class StandardMacros(BrowserView, Macros): pass
+    def add(self, content):
+        return self.context.add(content)
 
+    def nextURL(self):
+        return self.context.nextURL()

Modified: z3/Five/trunk/browserconfigure.py
==============================================================================
--- z3/Five/trunk/browserconfigure.py	(original)
+++ z3/Five/trunk/browserconfigure.py	Tue Mar  8 16:13:06 2005
@@ -21,16 +21,18 @@
 from zope.component.servicenames import Presentation
 from zope.publisher.interfaces.browser import IBrowserRequest
 from zope.app.publisher.browser.viewmeta import pages as zope_app_pages
+from zope.app.publisher.browser.viewmeta import view as zope_app_view
 from zope.app.publisher.browser.globalbrowsermenuservice import\
      menuItemDirective
 from zope.app.component.metaconfigure import handler
 from zope.app.component.interface import provideInterface
 from zope.app.form.browser.metaconfigure import BaseFormDirective
+from zope.app.container.interfaces import IAdding
 
 from resource import FileResourceFactory, ImageResourceFactory
 from resource import PageTemplateResourceFactory
 from resource import DirectoryResourceFactory
-from browser import BrowserView, EditView
+from browser import BrowserView, EditView, AddView
 from metaclass import makeClass
 from security import getSecurityInfo, protectClass, protectName,\
      initializeClass
@@ -170,6 +172,110 @@
 
     _handle_for(_context, for_)
 
+# view (named view with pages)
+
+class view(zope_app_view):
+
+    def __call__(self):
+        (_context, name, for_, permission, layer, class_,
+         allowed_interface, allowed_attributes) = self.args
+
+        required = {}
+
+        cdict = {}
+        pages = {}
+
+        for pname, attribute, template in self.pages:
+            try:
+                s = getGlobalService(Presentation)
+            except ComponentLookupError, err:
+                pass
+
+            if template:
+                cdict[pname] = ZopeTwoPageTemplateFile(template)
+                if attribute and attribute != name:
+                    cdict[attribute] = cdict[pname]
+            else:
+                if not hasattr(class_, attribute):
+                    raise ConfigurationError("Undefined attribute",
+                                             attribute)
+
+            attribute = attribute or pname
+            required[pname] = permission
+
+            pages[pname] = attribute
+
+        # This should go away, but noone seems to remember what to do. :-(
+        if hasattr(class_, 'publishTraverse'):
+
+            def publishTraverse(self, request, name,
+                                pages=pages, getattr=getattr):
+
+                if name in pages:
+                    return getattr(self, pages[name])
+                view = zapi.queryView(self, name, request)
+                if view is not None:
+                    return view
+
+                m = class_.publishTraverse.__get__(self)
+                return m(request, name)
+
+        else:
+            def publishTraverse(self, request, name,
+                                pages=pages, getattr=getattr):
+
+                if name in pages:
+                    return getattr(self, pages[name])
+                view = zapi.queryView(self, name, request)
+                if view is not None:
+                    return view
+
+                raise NotFoundError(self, name, request)
+
+        cdict['publishTraverse'] = publishTraverse
+
+        if not hasattr(class_, 'browserDefault'):
+            if self.default or self.pages:
+                default = self.default or self.pages[0][0]
+                cdict['browserDefault'] = (
+                    lambda self, request, default=default:
+                    (self, (default, ))
+                    )
+            elif providesCallable(class_):
+                cdict['browserDefault'] = (
+                    lambda self, request: (self, ())
+                    )
+
+        if class_ is not None:
+            bases = (class_, ViewMixinForTemplates)
+        else:
+            bases = (ViewMixinForTemplates)
+
+        try:
+            cname = str(name)
+        except:
+            cname = "GeneratedClass"
+            
+        newclass = makeClass(cname, bases, cdict)
+        
+        _handle_for(_context, for_)
+
+        if self.provides is not None:
+            _context.action(
+                discriminator = None,
+                callable = provideInterface,
+                args = ('', self.provides)
+                )
+
+        _context.action(
+            discriminator = ('view', for_, name, IBrowserRequest, layer,
+                             self.provides),
+            callable = handler,
+            args = (Presentation, 'provideAdapter',
+                    IBrowserRequest, newclass, name, [for_],  self.provides,
+                    layer, _context.info),
+            )
+
 def _handle_for(_context, for_):
     if for_ is not None:
         _context.action(
@@ -336,11 +442,6 @@
 
     # Not the prettiest solution, but it works...
     class_.__init__ = EditView.__init__
-#     XXX: replace with proper checks
-#     defineChecker(class_,
-#                   NamesChecker(("__call__", "__getitem__",
-#                                 "browserDefault", "publishTraverse"),
-#                                permission))
 
     s.provideView(for_, name, IBrowserRequest, class_, layer)
 
@@ -367,6 +468,120 @@
             kw={'menu': self.menu},
         )
 
+def AddViewFactory(name, schema, label, permission, layer,
+                   template, default_template, bases, for_,
+                   fields, content_factory, arguments,
+                   keyword_arguments, set_before_add, set_after_add,
+                   menu=u''):
+
+    s = getGlobalService(Presentation)
+    class_ = makeClassForTemplate(template, used_for=schema, bases=bases)
+
+    class_.schema = schema
+    class_.label = label
+    class_.fieldNames = fields
+    class_._factory = content_factory
+    class_._arguments = arguments
+    class_._keyword_arguments = keyword_arguments
+    class_._set_before_add = set_before_add
+    class_._set_after_add = set_after_add
+
+    class_.generated_form = ZopeTwoPageTemplateFile(default_template)
+
+    s.provideView(for_, name, IBrowserRequest, class_, layer)
+
+class AddFormDirective(BaseFormDirective):
+
+    view = AddView
+    default_template = 'add.pt'
+    for_ = IAdding
+
+    # default add form information
+    description = None
+    content_factory = None
+    arguments = None
+    keyword_arguments = None
+    set_before_add = None
+    set_after_add = None
+
+    def _handle_menu(self):
+        if self.menu or self.title:
+            if (not self.menu) or (not self.title):
+                raise ValueError("If either menu or title are specified, "
+                                 "they must both be specified")
+            # Add forms are really for IAdding components, so do not use
+            # for=self.schema.
+            menuItemDirective(
+                self._context, self.menu, self.for_, '@@' + self.name,
+                self.title, permission=self.permission,
+                description=self.description)
+
+    def _handle_arguments(self, leftover=None):
+        schema = self.schema
+        fields = self.fields
+        arguments = self.arguments
+        keyword_arguments = self.keyword_arguments
+        set_before_add = self.set_before_add
+        set_after_add = self.set_after_add
+
+        if leftover is None:
+            leftover = fields
+
+        if arguments:
+            missing = [n for n in arguments if n not in fields]
+            if missing:
+                raise ValueError("Some arguments are not included in the form",
+                                 missing)
+            optional = [n for n in arguments if not schema[n].required]
+            if optional:
+                raise ValueError("Some arguments are optional, use"
+                                 " keyword_arguments for them",
+                                 optional)
+            leftover = [n for n in leftover if n not in arguments]
+
+        if keyword_arguments:
+            missing = [n for n in keyword_arguments if n not in fields]
+            if missing:
+                raise ValueError(
+                    "Some keyword_arguments are not included in the form",
+                    missing)
+            leftover = [n for n in leftover if n not in keyword_arguments]
+
+        if set_before_add:
+            missing = [n for n in set_before_add if n not in fields]
+            if missing:
+                raise ValueError(
+                    "Some set_before_add are not included in the form",
+                    missing)
+            leftover = [n for n in leftover if n not in set_before_add]
+
+        if set_after_add:
+            missing = [n for n in set_after_add if n not in fields]
+            if missing:
+                raise ValueError(
+                    "Some set_after_add are not included in the form",
+                    missing)
+            leftover = [n for n in leftover if n not in set_after_add]
+
+            self.set_after_add += leftover
+
+        else:
+            self.set_after_add = leftover
+
+    def __call__(self):
+        self._processWidgets()
+        self._handle_menu()
+        self._handle_arguments()
+
+        self._context.action(
+            discriminator=self._discriminator(),
+            callable=AddViewFactory,
+            args=self._args()+(self.content_factory, self.arguments,
+                                 self.keyword_arguments,
+                                 self.set_before_add, self.set_after_add),
+            kw={'menu': self.menu},
+            )
+
 #
 # mixin classes / class factories
 #

Modified: z3/Five/trunk/configure.zcml
==============================================================================
--- z3/Five/trunk/configure.zcml	(original)
+++ z3/Five/trunk/configure.zcml	Tue Mar  8 16:13:06 2005
@@ -61,6 +61,28 @@
       provides="zope.app.traversing.browser.interfaces.IAbsoluteURL"
       />
 
+  <browser:view
+      for=".interfaces.IObjectManager"
+      name="+"
+      class=".adding.ContentAdding"
+      permission="zope2.ViewManagementScreens"
+      >
+
+    <browser:page name="index.html"  template="adding.pt" />
+    <browser:page name="action.html" attribute="action" />
+
+  </browser:view>
+
+  <adapter
+      for=".interfaces.IObjectManager"
+      factory=".adding.ObjectManagerNameChooser"
+      provides="zope.app.container.interfaces.INameChooser"
+      />
+
+  <!-- this is really lying, but it's to please checkContainer -->
+  <five:implements class="OFS.ObjectManager.ObjectManager"
+                   interface="zope.app.container.interfaces.IContainer" />
+
   <!-- make Zope 2's REQUEST implement the right thing -->
   <five:implements class="ZPublisher.HTTPRequest.HTTPRequest"
                    interface="zope.publisher.interfaces.browser.IBrowserRequest"

Modified: z3/Five/trunk/meta.zcml
==============================================================================
--- z3/Five/trunk/meta.zcml	(original)
+++ z3/Five/trunk/meta.zcml	Tue Mar  8 16:13:06 2005
@@ -165,6 +165,23 @@
 
     </meta:complexDirective>
 
+    <meta:complexDirective
+        name="view"
+        schema="zope.app.publisher.browser.metadirectives.IViewDirective"
+        handler=".browserconfigure.view"
+        >
+
+      <meta:subdirective
+          name="page"
+          schema="zope.app.publisher.browser.metadirectives.IViewPageSubdirective"
+          />
+
+      <meta:subdirective
+          name="defaultPage"
+          schema="zope.app.publisher.browser.metadirectives.IViewDefaultPageSubdirective"
+          />
+
+    </meta:complexDirective>
 
     <meta:complexDirective
         name="editform"
@@ -179,6 +196,19 @@
 
     </meta:complexDirective>
 
+    <meta:complexDirective
+        name="addform"
+        schema="zope.app.form.browser.metadirectives.IAddFormDirective"
+        handler=".browserconfigure.AddFormDirective"
+        >
+
+      <meta:subdirective
+          name="widget"
+          schema="zope.app.form.browser.metadirectives.IWidgetSubdirective"
+          />
+
+    </meta:complexDirective>
+
   </meta:directives>
 
 

Modified: z3/Five/trunk/tests/products/FiveTest/configure.zcml
==============================================================================
--- z3/Five/trunk/tests/products/FiveTest/configure.zcml	(original)
+++ z3/Five/trunk/tests/products/FiveTest/configure.zcml	Tue Mar  8 16:13:06 2005
@@ -294,6 +294,15 @@
       permission="zope2.Public"
       />
 
+  <five:traversable class="OFS.ObjectManager.ObjectManager" />
+  
+  <browser:addform
+     schema=".interfaces.IFieldSimpleContent"
+     content_factory=".simplecontent.FieldSimpleContent"
+     name="addsimplecontent.html"
+     permission="zope2.Public"
+     />
+
   <!-- stuff that we'll override in overrides.zcml -->
 
   <browser:page

Modified: z3/Five/trunk/traversable.py
==============================================================================
--- z3/Five/trunk/traversable.py	(original)
+++ z3/Five/trunk/traversable.py	Tue Mar  8 16:13:06 2005
@@ -78,8 +78,8 @@
         # con Zope 3 into using Zope 2's checkPermission
         newInteraction()
         try:
-            kw = dict(path=[name], request=REQUEST)
-            return ITraverser(self).traverse(**kw).__of__(self)
+            return ITraverser(self).traverse(
+                path=[name], request=REQUEST).__of__(self)
         except (ComponentLookupError, NotFoundError,
                 AttributeError, KeyError, NotFound):
             pass


More information about the z3-checkins mailing list