[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