[Checkins] SVN: grok/branches/ksmith_mcweekly-layers/ * merge trunk to branch 74120:74325

Kevin Smith kevin at mcweekly.com
Wed Apr 25 13:59:16 EDT 2007


Log message for revision 74759:
  * merge trunk to branch 74120:74325
  * implement grok.Layer
  * implement grok.Skin
  * implement layer, skin and static tests
  * all tests pass
  

Changed:
  U   grok/branches/ksmith_mcweekly-layers/CREDITS.txt
  U   grok/branches/ksmith_mcweekly-layers/doc/index.txt
  U   grok/branches/ksmith_mcweekly-layers/setup.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/__init__.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/components.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/directive.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/formlib.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_app_interface.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_class.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_module.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_multiple.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_multiple_conflict.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_name.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_no_app.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_nonexistent.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_site.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/actions.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/addform.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/addform_applydata.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/addform_catalog.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/editform_applydata.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/editform_applydata_classfields.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/editform_applydata_schema.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/security/json.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/static/simple.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/static/simple_fixture/ellie.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/traversal/containertraverser.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/traversal/items_before_views.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/url/url_function.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/ftests/view/layer.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/index.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/interfaces.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/meta.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/tests/json/
  U   grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission_json.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission_json2.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission_xmlrpc.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission_xmlrpc2.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission_xmlrpc3.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/multiple_require.py
  A   grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/multiple_require_json.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/multiple_require_xmlrpc.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/tests/test_grok.py
  U   grok/branches/ksmith_mcweekly-layers/src/grok/util.py

-=-
Modified: grok/branches/ksmith_mcweekly-layers/CREDITS.txt
===================================================================
--- grok/branches/ksmith_mcweekly-layers/CREDITS.txt	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/CREDITS.txt	2007-04-25 17:59:15 UTC (rev 74759)
@@ -1,22 +1,26 @@
 CREDITS
 =======
 
-* Martijn Faassen
+* Martijn Faassen (original developer)
 
-* Wolfgang Schnerring
+* Wolfgang Schnerring (original developer)
 
-* Christian Theune
+* Christian Theune (original developer)
 
-* Philipp von Weitershausen
+* Philipp von Weitershausen (original developer)
 
-* Jan-Wijbrand Kolman
+* Jan-Wijbrand Kolman (Grok Zwei sprint, meta grokker improvements)
 
-* Christian Zagrodnick
+* Darryl Cousins (website, admin interface)
 
-* Darryl Cousins
+* Kevin Teague (website)
 
-* ME GROK
+* Tim Terlegård (JSON support)
 
+* Christian Zagrodnick (early grok discussions)
+
+* ME GROK (team mascot)
+
 Thank you
 ---------
 

Modified: grok/branches/ksmith_mcweekly-layers/doc/index.txt
===================================================================
--- grok/branches/ksmith_mcweekly-layers/doc/index.txt	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/doc/index.txt	2007-04-25 17:59:15 UTC (rev 74759)
@@ -21,10 +21,9 @@
 benefits a lot from it, you do not need to know Zope at all in order
 to get productive with Grok.
 
-`Read More`_
+`Read More <./about.html>`_
 
 .. _Zope 3: http://wiki.zope.org/zope3
-.. _read more: ./about.html
 
 
 Who is Grok?
@@ -67,14 +66,17 @@
     class HelloWorld(grok.Application, grok.Model):
         pass
 
-        index = grok.PageTemplate("""
+    class Index(grok.View):
+        pass
+
+    index = grok.PageTemplate("""
         <html><body>
             <p>ME GROK HELLO WORLD!</p>
         </body></html>
         """")
 
 * `Herd of Mammoths`_ is a very simple application that only goes a bit beyond "hello world".
-* `Grokstar`_ is a simple wiki application written with Grok.
+* `Grokstar`_ is a simple blog application written with Grok.
 * `Grok Wiki`_ is a simple wiki application written with Grok.
 
 .. _Herd of Mammoths: http://www.z3lab.org/sections/blogs/philipp-weitershausen/2007_01_09_you-thought-zope-3-wasn

Modified: grok/branches/ksmith_mcweekly-layers/setup.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/setup.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/setup.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -15,5 +15,6 @@
     package_dir = {'': 'src'},
     include_package_data = True,
     zip_safe=False,    
-    install_requires=['setuptools'],
+    install_requires=['setuptools',
+                      'simplejson'],
 )

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/__init__.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/__init__.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/__init__.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -30,17 +30,20 @@
     IContainerModifiedEvent, ContainerModifiedEvent)
 
 from grok.components import ClassGrokker, InstanceGrokker, ModuleGrokker
-from grok.components import Model, Adapter, MultiAdapter, View, XMLRPC, Layer
+from grok.components import Model, Adapter, MultiAdapter, View, XMLRPC, JSON, Layer
 from grok.components import PageTemplate, PageTemplateFile, Container, Traverser
 from grok.components import Site, GlobalUtility, LocalUtility, Annotation
 from grok.components import Application, Form, AddForm, EditForm, DisplayForm
+from grok.components import Indexes, Skin
 from grok.directive import (context, name, template, templatedir, provides,
                             baseclass, global_utility, local_utility,
-                            define_permission, require, layer, register_skin)
+                            define_permission, require, site, layer)
+
 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, AutoFields, Fields
+from grok.util import url
 
 # Our __init__ provides the grok API directly so using 'import grok' is enough.
 from grok.interfaces import IGrokAPI

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/components.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/components.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/components.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -17,11 +17,16 @@
 import os
 import persistent
 import urllib
+import datetime
+import warnings
+import pytz
+import simplejson
 
 from zope import component
 from zope import interface
 from zope import schema
 from zope import event
+from zope.interface.common import idatetime
 from zope.lifecycleevent import ObjectModifiedEvent
 from zope.publisher.browser import BrowserPage
 from zope.publisher.interfaces import NotFound
@@ -45,7 +50,7 @@
 from zope.app.container.interfaces import IReadContainer
 from zope.app.component.site import SiteManagerContainer
 
-from grok import util, interfaces
+from grok import util, interfaces, formlib
 
 
 # These base grokkers exist in grok.components because they are meant
@@ -108,18 +113,14 @@
     interface.implements(IAttributeAnnotatable)
 
 
-class Layer(IBrowserRequest):
-    pass
-
-
 class Site(SiteManagerContainer):
     pass
 
 
 class Application(Site):
     """A top-level application object."""
+    interface.implements(interfaces.IApplication)
 
-
 class Adapter(object):
 
     def __init__(self, context):
@@ -199,13 +200,7 @@
         elif name is not None and obj is None:
             # create URL to view on context
             obj = self.context
-        url = component.getMultiAdapter((obj, self.request), IAbsoluteURL)()
-        if name is None:
-            # URL to obj itself
-            return url
-        # URL to view on obj
-        return url + '/' + urllib.quote(name.encode('utf-8'),
-                                        SAFE_URL_CHARACTERS)
+        return util.url(self.request, obj, name)
 
     def redirect(self, url):
         return self.request.response.redirect(url)
@@ -224,7 +219,14 @@
 class XMLRPC(object):
     pass
 
+class JSON(BrowserPage):
 
+    def __call__(self):
+        view_name = self.__view_name__
+        method = getattr(self, view_name)
+        method_result = mapply(method, (), self.request)
+        return simplejson.dumps(method_result)
+
 class GrokPageTemplate(object):
 
     def __repr__(self):
@@ -312,18 +314,19 @@
         if subob is not None:
             return subob
 
-        # XXX special logic here to deal with views and containers.
-        # would be preferrable if we could fall back on normal Zope
-        # traversal behavior
-        view = component.queryMultiAdapter((self.context, request), name=name)
-        if view:
-            return view
-
+        # XXX Special logic here to deal with containers.  It would be
+        # good if we wouldn't have to do this here. One solution is to
+        # rip this out and make you subclass ContainerTraverser if you
+        # wanted to override the traversal behaviour of containers.
         if IReadContainer.providedBy(self.context):
             item = self.context.get(name)
-            if item:
+            if item is not None:
                 return item
 
+        view = component.queryMultiAdapter((self.context, request), name=name)
+        if view is not None:
+            return view
+
         raise NotFound(self.context, name, request)
 
     def traverse(self, name):
@@ -411,12 +414,6 @@
         self.update_form()
         return self.render()
 
-    def applyChanges(self, obj, **data):
-        if form.applyChanges(obj, self.form_fields, data, self.adapters):
-            event.notify(ObjectModifiedEvent(obj))
-            return True
-        return False
-
 class Form(GrokForm, form.FormBase, View):
     # We're only reusing the form implementation from zope.formlib, we
     # explicitly don't want to inherit the interface semantics (mostly
@@ -425,6 +422,17 @@
 
     template = default_form_template
 
+    def applyData(self, obj, **data):
+        return formlib.apply_data_event(obj, self.form_fields, data,
+                                        self.adapters)
+
+    # BBB -- to be removed in June 2007
+    def applyChanges(self, obj, **data):
+        warnings.warn("The 'applyChanges' method on forms is deprecated "
+                      "and will disappear by June 2007. Please use "
+                      "'applyData' instead.", DeprecationWarning, 2)
+        return bool(self.applyData(obj, **data))
+
 class AddForm(Form):
     pass
 
@@ -436,6 +444,34 @@
 
     template = default_form_template
 
+    def applyData(self, obj, **data):
+        return formlib.apply_data_event(obj, self.form_fields, data,
+                                        self.adapters, update=True)
+
+    # BBB -- to be removed in June 2007
+    def applyChanges(self, obj, **data):
+        warnings.warn("The 'applyChanges' method on forms is deprecated "
+                      "and will disappear by June 2007. Please use "
+                      "'applyData' instead.", DeprecationWarning, 2)
+        return bool(self.applyData(obj, **data))
+
+    @formlib.action("Apply")
+    def handle_edit_action(self, **data):
+        if self.applyData(self.context, **data):
+            formatter = self.request.locale.dates.getFormatter(
+                'dateTime', 'medium')
+
+            try:
+                time_zone = idatetime.ITZInfo(self.request)
+            except TypeError:
+                time_zone = pytz.UTC
+
+            self.status = "Updated on %s" % formatter.format(
+                datetime.datetime.now(time_zone)
+                )
+        else:
+            self.status = 'No changes'
+
 class DisplayForm(GrokForm, form.DisplayFormBase, View):
     # We're only reusing the form implementation from zope.formlib, we
     # explicitly don't want to inherit the interface semantics (mostly
@@ -443,3 +479,34 @@
     interface.implementsOnly(interfaces.IGrokForm)
 
     template = default_display_template
+
+class IndexesClass(object):
+    def __init__(self, name, bases=(), attrs=None):
+        if attrs is None:
+            return
+        # make sure we take over a bunch of possible attributes
+        for name in ['__grok_context__', '__grok_name__',
+                     '__grok_site__']:
+            value = attrs.get(name)
+            if value is not None:
+                setattr(self, name, value)
+        # now read and store indexes
+        indexes = {}
+        for name, value in attrs.items():
+            if not interfaces.IIndexDefinition.providedBy(value):
+                continue
+            indexes[name] = value
+        self.__grok_indexes__ = indexes
+        # __grok_module__ is needed to make defined_locally() return True for
+        # inline templates
+        self.__grok_module__ = util.caller_module()
+        
+Indexes = IndexesClass('Indexes')
+
+
+class Layer(IBrowserRequest):
+    pass
+
+
+class Skin(object):
+    pass

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/directive.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/directive.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/directive.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -198,23 +198,7 @@
             raise GrokImportError("You can only pass interfaces to "
                                   "%s." % self.name)
 
-
-class RegisterSkinDirective(MultipleTimesDirective):
-    def check_arguments(self, layer, name=None):
-        if not IInterface.providedBy(layer):
-            raise GrokImportError("You can only pass an Interface as "
-                                  "first argument of %s." % self.name)
-
-    def value_factory(self, *args, **kw):
-        return RegisterSkinInfo(*args, **kw)
-
         
-class RegisterSkinInfo(object):
-    def __init__(self, layer, name=None):
-        self.layer = layer
-        self.name = name
-
-        
 class GlobalUtilityDirective(MultipleTimesDirective):
     def check_arguments(self, factory, provides=None, name=u''):
         if provides is not None and not IInterface.providedBy(provides):
@@ -283,8 +267,6 @@
 template = SingleTextDirective('grok.template', ClassDirectiveContext())
 context = InterfaceOrClassDirective('grok.context',
                                     ClassOrModuleDirectiveContext())
-layer = InterfaceDirective('grok.layer',
-                           ClassOrModuleDirectiveContext())
 templatedir = SingleTextDirective('grok.templatedir', ModuleDirectiveContext())
 provides = InterfaceDirective('grok.provides', ClassDirectiveContext())
 baseclass = MarkerDirective('grok.baseclass', ClassDirectiveContext())
@@ -296,6 +278,9 @@
                                           ModuleDirectiveContext())
 require = RequireDirective('grok.require', ClassDirectiveContext())
 
-register_skin = RegisterSkinDirective('grok.register_skin',
-                                 ModuleDirectiveContext())
+site = InterfaceOrClassDirective('grok.site',
+                                 ClassDirectiveContext())
 
+layer = InterfaceOrClassDirective('grok.layer',
+                           ClassOrModuleDirectiveContext())
+

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/formlib.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/formlib.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/formlib.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -1,5 +1,5 @@
 import types
-from zope import interface
+from zope import interface, event, lifecycleevent
 from zope.interface.interfaces import IInterface
 from zope.formlib import form
 from zope.schema.interfaces import IField
@@ -88,3 +88,60 @@
         if seen_iface.extends(iface):
             return True
     return False
+
+def apply_data(context, form_fields, data, adapters=None, update=False):
+    """Save form data (``data`` dict) on a ``context`` object.
+
+    This is a beefed up version of zope.formlib.form.applyChanges().
+    It allows you to specify whether values should be compared with
+    the attributes on already existing objects or not, using the
+    ``update`` parameter.
+
+    Unlike zope.formlib.form.applyChanges(), it will return a
+    dictionary of interfaces and their fields that were changed.  This
+    is necessary to appropriately send IObjectModifiedEvents.
+    """
+    if adapters is None:
+        adapters = {}
+
+    changes = {}
+
+    for form_field in form_fields:
+        field = form_field.field
+        # Adapt context, if necessary
+        interface = field.interface
+        adapter = adapters.get(interface)
+        if adapter is None:
+            if interface is None:
+                adapter = context
+            else:
+                adapter = interface(context)
+            adapters[interface] = adapter
+
+        name = form_field.__name__
+        newvalue = data.get(name, form_field) # using form_field as marker
+
+        if update:
+            if ((newvalue is not form_field) and
+                (field.get(adapter) != newvalue)):
+                field.set(adapter, newvalue)
+                changes.setdefault(interface, []).append(name)
+        else:
+            if newvalue is not form_field:
+                field.set(adapter, newvalue)
+                changes.setdefault(interface, []).append(name)
+
+    return changes
+
+def apply_data_event(context, form_fields, data, adapters=None, update=False):
+    """Like apply_data, but also sends an IObjectModifiedEvent.
+    """
+    changes = apply_data(context, form_fields, data, adapters, update)
+
+    if changes:
+        descriptions = []
+        for interface, names in changes.items():
+            descriptions.append(lifecycleevent.Attributes(interface, *names))
+        event.notify(lifecycleevent.ObjectModifiedEvent(context, *descriptions))
+
+    return changes

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes.py (from rev 74325, grok/trunk/src/grok/ftests/catalog/indexes.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_app_interface.py (from rev 74325, grok/trunk/src/grok/ftests/catalog/indexes_app_interface.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_class.py (from rev 74325, grok/trunk/src/grok/ftests/catalog/indexes_class.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_module.py (from rev 74325, grok/trunk/src/grok/ftests/catalog/indexes_module.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_multiple.py (from rev 74325, grok/trunk/src/grok/ftests/catalog/indexes_multiple.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_multiple_conflict.py (from rev 74325, grok/trunk/src/grok/ftests/catalog/indexes_multiple_conflict.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_name.py (from rev 74325, grok/trunk/src/grok/ftests/catalog/indexes_name.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_no_app.py (from rev 74325, grok/trunk/src/grok/ftests/catalog/indexes_no_app.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_nonexistent.py (from rev 74325, grok/trunk/src/grok/ftests/catalog/indexes_nonexistent.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/catalog/indexes_site.py (from rev 74325, grok/trunk/src/grok/ftests/catalog/indexes_site.py)

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/actions.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/actions.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/actions.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -2,7 +2,7 @@
 Using the @grok.action decorator, different actions can be defined on
 a grok.Form. When @grok.action is used, the default behaviour (the
 'Apply' action) is not available anymore, but it can triggered
-manually by calling self.applyChanges(object, data).
+manually by calling self.applyData(object, data).
 
   >>> import grok
   >>> from grok.ftests.form.actions import Mammoth
@@ -57,14 +57,14 @@
 class Edit(grok.EditForm):
     @grok.action("Apply")
     def handle_apply(self, **data):
-        if self.applyChanges(self.context, **data):
+        if self.applyData(self.context, **data):
             self.status = 'Modified!'
         else:
             self.status = 'No changes!'
 
     @grok.action("Hairy")
     def handle_hairy(self, **data):
-        self.applyChanges(self.context, **data)
+        self.applyData(self.context, **data)
         self.context.size += " and hairy"
 
 class Meet(grok.Form):

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/addform.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/addform.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/addform.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -1,5 +1,5 @@
 """
-We can use grok.Form to render an add form for objects:
+We can use grok.AddForm to render an add form for objects:
 
   >>> import grok
   >>> from grok.ftests.form.addform import Zoo, Mammoth
@@ -17,7 +17,10 @@
   >>> print browser.contents
   Hi, my name is Manfred the Mammoth, and I\'m "Really big"
 
-  >>> browser.open("http://localhost/zoo/@@addmammothapplychanges")
+Instead of calling an object constructor with the form data, we can
+also use the ``applyData`` method to store the data on the object.
+
+  >>> browser.open("http://localhost/zoo/@@addmammothapplydata")
   >>> browser.getControl(name="form.name").value = "Ellie the Mammoth"
   >>> browser.getControl(name="form.size").value = "Really small"
   >>> browser.getControl("Add entry").click()
@@ -57,11 +60,11 @@
         self.context['manfred'] = manfred = Mammoth(**data)
         self.redirect(self.url(manfred))
 
-class AddMammothApplyChanges(AddMammoth):
+class AddMammothApplyData(AddMammoth):
 
     @grok.action('Add entry')
     def add(self, **data):
-        # instantiate Mammoth and then use self.applyChanges()
+        # instantiate Mammoth and then use self.applyData()
         self.context['ellie'] = ellie = Mammoth()
-        self.applyChanges(ellie, **data)
+        self.applyData(ellie, **data)
         self.redirect(self.url(ellie))

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/addform_applydata.py (from rev 74325, grok/trunk/src/grok/ftests/form/addform_applydata.py)


Property changes on: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/addform_applydata.py
___________________________________________________________________
Name: svn:eol-style
   + native

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/addform_catalog.py (from rev 74325, grok/trunk/src/grok/ftests/form/addform_catalog.py)


Property changes on: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/addform_catalog.py
___________________________________________________________________
Name: svn:eol-style
   + native

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/editform_applydata.py (from rev 74325, grok/trunk/src/grok/ftests/form/editform_applydata.py)


Property changes on: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/editform_applydata.py
___________________________________________________________________
Name: svn:eol-style
   + native

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/editform_applydata_classfields.py (from rev 74325, grok/trunk/src/grok/ftests/form/editform_applydata_classfields.py)


Property changes on: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/editform_applydata_classfields.py
___________________________________________________________________
Name: svn:eol-style
   + native

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/editform_applydata_schema.py (from rev 74325, grok/trunk/src/grok/ftests/form/editform_applydata_schema.py)


Property changes on: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/form/editform_applydata_schema.py
___________________________________________________________________
Name: svn:eol-style
   + native

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/security/json.py (from rev 74325, grok/trunk/src/grok/ftests/security/json.py)

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/static/simple.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/ftests/static/simple.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/ftests/static/simple.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -48,13 +48,13 @@
   >>> print browser.contents
   stick figures
 
-  >>> browser.open('http://localhost/++skin++MammothSkin/ellie/@@tarpit')
+  >>> browser.open('http://localhost/++skin++mammothskin/ellie/@@tarpit')
   >>> print browser.contents
   inky darkness all around
 
 Static layer is not available to custom layers unless they subclass IDefaultBrowserLayer
 
-  >>> browser.open('http://localhost/++skin++MammothSkin/@@/grok.ftests.static.simple_fixture/subdir/otherfile.txt')
+  >>> browser.open('http://localhost/++skin++mammothskin/@@/grok.ftests.static.simple_fixture/subdir/otherfile.txt')
   Traceback (most recent call last):
   ...
   NotFound: ...

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/static/simple_fixture/ellie.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/ftests/static/simple_fixture/ellie.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/ftests/static/simple_fixture/ellie.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -13,18 +13,20 @@
 </body>
 </html>""")
 
-class MammothSkin(grok.Layer):
+class MammothLayer(grok.Layer):
     pass
 
-grok.register_skin(MammothSkin)
+class MammothSkin(grok.Skin):
+    grok.layer(MammothLayer)
 
+
 class CaveDrawings(grok.View):
 
     def render(self):
         return "stick figures"
 
 class TarPit(grok.View):
-    grok.layer(MammothSkin)
+    grok.layer(MammothLayer)
 
     def render(self):
         return "inky darkness all around"

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/traversal/containertraverser.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/ftests/traversal/containertraverser.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/ftests/traversal/containertraverser.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -51,6 +51,14 @@
   </body>
   </html>
 
+Also try traversing (an empty and therefore False in a Boolean sense) container
+as a subitem of a container:
+
+  >>> herd['subherd'] = Herd()
+  >>> browser.open("http://localhost/herd/subherd/special")
+  >>> print browser.contents
+  special view
+
 """
 import grok
 
@@ -63,7 +71,7 @@
         if name == 'special':
             return Special()
         return None
-    
+
 class Mammoth(grok.Model):
     def __init__(self, name):
         self.name = name

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/traversal/items_before_views.py (from rev 74325, grok/trunk/src/grok/ftests/traversal/items_before_views.py)


Property changes on: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/traversal/items_before_views.py
___________________________________________________________________
Name: svn:eol-style
   + native

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/url/url_function.py (from rev 74325, grok/trunk/src/grok/ftests/url/url_function.py)

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/ftests/view/layer.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/ftests/view/layer.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/ftests/view/layer.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -22,10 +22,11 @@
   >>> browser.open("http://localhost/++skin++Rotterdam/manfred/@@moredrawings")
   >>> print browser.contents
   Pretty
-  >>> browser.open("http://localhost/++skin++MySkin/manfred/@@evenmoredrawings")
-  >>> print browser.contents
-  Awesome
 
+  #>>> browser.open("http://localhost/++skin++MySkin/manfred/@@evenmoredrawings")
+  #>>> print browser.contents
+  #Awesome
+
 """
 import grok
 from zope.app.basicskin import IBasicSkin
@@ -35,10 +36,11 @@
 
 grok.layer(IBasicSkin)
 
-class MySkin(grok.Layer):
+class MySkinLayer(grok.Layer):
     pass
 
-grok.register_skin(MySkin)
+class MySkin(grok.Skin):
+    grok.layer(MySkinLayer)
 
 class Mammoth(grok.Model):
     pass

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/index.py (from rev 74325, grok/trunk/src/grok/index.py)

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/interfaces.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/interfaces.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/interfaces.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -196,6 +196,10 @@
     def grok(dotted_name):
         """Grok a module or package specified by ``dotted_name``."""
 
+    def url(request, obj, name=None):
+        """Generate the URL to an object with optional name attached.
+        """
+
     def notify(event):
         """Send ``event`` to event subscribers."""
 
@@ -366,9 +370,14 @@
         The errors are returned as an iterable.
         """
 
-    def applyChanges(obj, **data):
-        """Apply form data to an object.  Return True if the object
-        had to be modified, False otherwise.
+    def applyData(obj, **data):
+        """Save form data to an object.
+
+        This returns a dictionary with interfaces as keys and lists of
+        field names as values to indicate which fields in which
+        schemas had to be changed in order to save the data.  In case
+        the method works in update mode (e.g. on EditForms) and
+        doesn't have to update an object, the dictionary is empty.
         """
 
 
@@ -378,3 +387,16 @@
     Used to register applications as utilities to look them up and
     provide a list of grokked applications.
     """
+
+class IIndexDefinition(interface.Interface):
+    """Define an index for grok.Indexes.
+    """
+
+    def setup(catalog, name, context):
+        """Set up index called name in given catalog.
+
+        Use name for index name and attribute to index. Set up
+        index for interface or class context.
+        """
+    
+        

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/meta.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/meta.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/meta.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -7,7 +7,6 @@
                                                IBrowserPublisher,
                                                IBrowserSkinType)
 from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
-from zope.security.checker import NamesChecker, defineChecker
 from zope.security.permission import Permission
 from zope.security.interfaces import IPermission
 from zope.annotation.interfaces import IAnnotations
@@ -17,6 +16,13 @@
 from zope.app.container.interfaces import INameChooser
 from zope.app.container.contained import contained
 
+from zope.app.intid import IntIds
+from zope.app.intid.interfaces import IIntIds
+from zope.app.catalog.catalog import Catalog
+from zope.app.catalog.interfaces import ICatalog
+
+from zope.exceptions.interfaces import DuplicationError
+
 import grok
 from grok import util, components, formlib
 from grok.error import GrokError
@@ -84,18 +90,8 @@
         # the outside -- need to discuss how to restrict such things.
         methods = util.methods_from_class(factory)
 
-        # Determine the default permission for the XMLRPC methods.
-        # There can only be 0 or 1 of those.
-        permissions = util.class_annotation(factory, 'grok.require', [])
-        if not permissions:
-            default_permission = None
-        elif len(permissions) == 1:
-            default_permission = permissions[0]
-        else:
-            raise GrokError('grok.require was called multiple times in '
-                            '%r. It may only be called once on class level.'
-                            % factory, factory)
-
+        default_permission = util.get_default_permission(factory)
+        
         for method in methods:
             # Make sure that the class inherits MethodPublisher, so that the
             # views have a location
@@ -111,14 +107,10 @@
             # Protect method_view with either the permission that was
             # set on the method, the default permission from the class
             # level or zope.Public.
-            permission = getattr(method, '__grok_require__', default_permission)
-            if permission is None or permission == 'zope.Public':
-                checker = NamesChecker(['__call__'])
-            else:
-                checker = NamesChecker(['__call__'], permission)
-            defineChecker(method_view, checker)
-
-
+            permission = getattr(method, '__grok_require__',
+                                 default_permission)
+            util.make_checker(factory, method_view, permission)
+    
 class LayerGrokker(grok.ClassGrokker):
     component_class = grok.Layer
 
@@ -190,25 +182,9 @@
                                  name=view_name)
 
         # protect view, public by default
-        permissions = util.class_annotation(factory, 'grok.require', [])
-        if not permissions:
-            checker = NamesChecker(['__call__'])
-        elif len(permissions) > 1:
-            raise GrokError('grok.require was called multiple times in view '
-                            '%r. It may only be called once.' % factory,
-                            factory)
-        elif permissions[0] == 'zope.Public':
-            checker = NamesChecker(['__call__'])
-        else:
-            perm = permissions[0]
-            if component.queryUtility(IPermission, name=perm) is None:
-                raise GrokError('Undefined permission %r in view %r. Use '
-                                'grok.define_permission first.'
-                                % (perm, factory), factory)
-            checker = NamesChecker(['__call__'], permissions[0])
-
-        defineChecker(factory, checker)
-
+        default_permission = util.get_default_permission(factory)
+        util.make_checker(factory, factory, default_permission)
+    
         # safety belt: make sure that the programmer didn't use
         # @grok.require on any of the view's methods.
         methods = util.methods_from_class(factory)
@@ -220,6 +196,35 @@
                                 % (method.__name__, factory), factory)
 
 
+class JSONGrokker(grok.ClassGrokker):
+    component_class = grok.JSON
+
+    def register(self, context, name, factory, module_info, templates):
+        view_context = util.determine_class_context(factory, context)
+        methods = util.methods_from_class(factory)
+
+        default_permission = util.get_default_permission(factory)
+        
+        for method in methods:
+            # Create a new class with a __view_name__ attribute so the
+            # JSON class knows what method to call.
+            method_view = type(
+                factory.__name__, (factory,),
+                {'__view_name__': method.__name__}
+                )
+            component.provideAdapter(
+                method_view, (view_context, IDefaultBrowserLayer),
+                interface.Interface,
+                name=method.__name__)
+
+            # Protect method_view with either the permission that was
+            # set on the method, the default permission from the class
+            # level or zope.Public.
+
+            permission = getattr(method, '__grok_require__',
+                                 default_permission)
+            util.make_checker(factory, method_view, permission)
+
 class TraverserGrokker(grok.ClassGrokker):
     component_class = grok.Traverser
 
@@ -401,34 +406,51 @@
 
     for info in util.class_annotation(site.__class__,
                                       'grok.utilities_to_install', []):
-        utility = info.factory()
-        site_manager = site.getSiteManager()
+        setupUtility(site, info.factory(), info.provides, name=info.name,
+                     name_in_container=info.name_in_container,
+                     public=info.public, setup=info.setup)
 
-        # store utility
-        if not info.public:
-            container = site_manager
-        else:
-            container = site
+    # we are done. If this subscriber gets fired again, we therefore
+    # do not register utilities anymore
+    site.__grok_utilities_installed__ = True
 
-        name_in_container = info.name_in_container 
-        if name_in_container is None:
-            name_in_container = INameChooser(container).chooseName(
-                info.factory.__name__, utility)
-        container[name_in_container] = utility
 
-        # execute setup callback
-        if info.setup is not None:
-            info.setup(utility)
+def setupUtility(site, utility, provides, name=u'',
+                 name_in_container=None, public=False, setup=None):
+    """Set up a utility in a site.
 
-        # register utility
-        site_manager.registerUtility(utility, provided=info.provides,
-                                     name=info.name)
+    site - the site to set up the utility in
+    utility - the utility to set up
+    provides - the interface the utility should be registered with
+    name - the name the utility should be registered under, default
+      the empty string (no name)
+    name_in_container - if given it will be used to add the utility
+      object to its container. Otherwise a name will be made up
+    public - if False, the utility will be stored in the site manager. If
+      True, the utility will be storedin the site (it is assumed the
+      site is a container)
+    setup - if not None, it will be called with the utility as its first
+       argument. This function can then be used to further set up the
+       utility.
+    """
+    site_manager = site.getSiteManager()
 
-    # we are done. If this subscriber gets fired again, we therefore
-    # do not register utilities anymore
-    site.__grok_utilities_installed__ = True
+    if not public:
+        container = site_manager
+    else:
+        container = site
 
+    if name_in_container is None:
+        name_in_container = INameChooser(container).chooseName(
+            utility.__class__.__name__, utility)
+    container[name_in_container] = utility
 
+    if setup is not None:
+        setup(utility)
+        
+    site_manager.registerUtility(utility, provided=provides,
+                                 name=name)
+    
 class DefinePermissionGrokker(grok.ModuleGrokker):
 
     priority = 1500
@@ -494,3 +516,82 @@
                                       provides=grok.interfaces.IApplication,
                                       name='%s.%s' % (module_info.dotted_name,
                                                       name))
+class IndexesGrokker(grok.InstanceGrokker):
+    component_class = components.IndexesClass
+
+    def register(self, context, name, factory, module_info, templates):
+        site = util.class_annotation(factory, 'grok.site', None)
+        if site is None:
+            raise GrokError("No site specified for grok.Indexes "
+                            "subclass in module %r. "
+                            "Use grok.site() to specify." % module_info.getModule(),
+                            factory)
+        indexes = util.class_annotation(factory, 'grok.indexes', None)
+        if indexes is None:
+            return
+        context = util.determine_class_context(factory, context)
+        catalog_name = util.class_annotation(factory, 'grok.name', u'')
+        zope.component.provideHandler(
+            IndexesSetupSubscriber(catalog_name, indexes,
+                                   context, module_info),
+            adapts=(site,
+                    grok.IObjectAddedEvent))
+        
+class IndexesSetupSubscriber(object):
+    def __init__(self, catalog_name, indexes, context, module_info):
+        self.catalog_name = catalog_name
+        self.indexes = indexes
+        self.context = context
+        self.module_info = module_info
+        
+    def __call__(self, site, event):
+        # make sure we have an intids
+        self._createIntIds(site)
+        # get the catalog
+        catalog = self._createCatalog(site)
+        # now install indexes
+        for name, index in self.indexes.items():
+            try:
+                index.setup(catalog, name, self.context, self.module_info)
+            except DuplicationError:
+                raise GrokError(
+                    "grok.Indexes in module %r causes "
+                    "creation of catalog index %r in catalog %r, "
+                    "but an index with that name is already present." %
+                    (self.module_info.getModule(), name, self.catalog_name),
+                    None)
+
+    def _createCatalog(self, site):
+        """Create the catalog if needed and return it.
+
+        If the catalog already exists, return that.
+        """
+        catalog = zope.component.queryUtility(
+            ICatalog, name=self.catalog_name, context=site, default=None)
+        if catalog is not None:
+            return catalog
+        catalog = Catalog()
+        setupUtility(site, catalog, ICatalog, name=self.catalog_name)
+        return catalog
+    
+    def _createIntIds(self, site):
+        """Create intids if needed, and return it.
+        """
+        intids = zope.component.queryUtility(
+            IIntIds, context=site, default=None)
+        if intids is not None:
+            return intids
+        intids = IntIds()
+        setupUtility(site, intids, IIntIds)
+        return intids
+
+
+class SkinGrokker(grok.ClassGrokker):
+    component_class = grok.Skin
+
+    def register(self, context, name, factory, module_info, templates):
+        layer = util.class_annotation(factory, 'grok.layer',
+                                    None) or module_info.getAnnotation('grok.layer',
+                                    None) or grok.IDefaultBrowserLayer
+        name = grok.util.class_annotation(factory, 'grok.name', factory.__name__.lower())
+        zope.component.interface.provideInterface(name, layer, IBrowserSkinType)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/tests/json (from rev 74325, grok/trunk/src/grok/tests/json)

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -4,7 +4,7 @@
 
   >>> grok.grok(__name__)
   Traceback (most recent call last):
-  GrokError: Undefined permission 'doesnt.exist' in view <class 'grok.tests.security.missing_permission.MissingPermission'>. Use grok.define_permission first.
+  GrokError: Undefined permission 'doesnt.exist' in <class 'grok.tests.security.missing_permission.MissingPermission'>. Use grok.define_permission first.
 
 """
 
@@ -17,3 +17,4 @@
 
     def render(self):
         pass
+

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission_json.py (from rev 74325, grok/trunk/src/grok/tests/security/missing_permission_json.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission_json2.py (from rev 74325, grok/trunk/src/grok/tests/security/missing_permission_json2.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission_xmlrpc.py (from rev 74325, grok/trunk/src/grok/tests/security/missing_permission_xmlrpc.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission_xmlrpc2.py (from rev 74325, grok/trunk/src/grok/tests/security/missing_permission_xmlrpc2.py)

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/missing_permission_xmlrpc3.py (from rev 74325, grok/trunk/src/grok/tests/security/missing_permission_xmlrpc3.py)

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/multiple_require.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/multiple_require.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/multiple_require.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -3,7 +3,8 @@
 
   >>> grok.grok(__name__)
   Traceback (most recent call last):
-  GrokError: grok.require was called multiple times in view <class 'grok.tests.security.multiple_require.MultipleView'>. It may only be called once.
+    ...
+  GrokError: grok.require was called multiple times in <class 'grok.tests.security.multiple_require.MultipleView'>. It may only be set once for a class.
 
 """
 import grok

Copied: grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/multiple_require_json.py (from rev 74325, grok/trunk/src/grok/tests/security/multiple_require_json.py)

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/multiple_require_xmlrpc.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/multiple_require_xmlrpc.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/tests/security/multiple_require_xmlrpc.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -3,8 +3,8 @@
 
   >>> grok.grok(__name__)
   Traceback (most recent call last):
-  GrokError: grok.require was called multiple times in <class 'grok.tests.security.multiple_require_xmlrpc.MultipleXMLRPC'>. It may only be called once on class level.
-
+     ...
+  GrokError: grok.require was called multiple times in <class 'grok.tests.security.multiple_require_xmlrpc.MultipleXMLRPC'>. It may only be set once for a class.
 """
 import grok
 import zope.interface

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/tests/test_grok.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/tests/test_grok.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/tests/test_grok.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -33,7 +33,7 @@
 def test_suite():
     suite = unittest.TestSuite()
     for name in ['adapter', 'error', 'view', 'scan', 'event', 'security',
-                 'zcml', 'static', 'utility', 'xmlrpc', 'container',
+                 'zcml', 'static', 'utility', 'xmlrpc', 'json', 'container',
                  'traversal', 'form', 'site', 'grokker', 'directive', 'util',
                  'baseclass', 'annotation', 'application']:
         suite.addTest(suiteFromPackage(name))

Modified: grok/branches/ksmith_mcweekly-layers/src/grok/util.py
===================================================================
--- grok/branches/ksmith_mcweekly-layers/src/grok/util.py	2007-04-25 17:48:02 UTC (rev 74758)
+++ grok/branches/ksmith_mcweekly-layers/src/grok/util.py	2007-04-25 17:59:15 UTC (rev 74759)
@@ -18,10 +18,16 @@
 import types
 import sys
 import inspect
+import urllib
 
 from zope import component
 from zope import interface
+from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.traversing.browser.absoluteurl import _safe as SAFE_URL_CHARACTERS
 
+from zope.security.checker import NamesChecker, defineChecker
+from zope.security.interfaces import IPermission
+
 from grok.error import GrokError, GrokImportError
 
 def not_unicode_or_ascii(value):
@@ -144,3 +150,56 @@
                   if name != '__provides__' ]
     methods = [c for c in candidates if inspect.ismethod(c)]
     return methods
+
+def make_checker(factory, view_factory, permission):
+    """Make a checker for a view_factory associated with factory.
+
+    These could be one and the same for normal views, or different
+    in case we make method-based views such as for JSON and XMLRPC.
+    """
+    if permission is not None:
+        check_permission(factory, permission)
+    if permission is None or permission == 'zope.Public':
+        checker = NamesChecker(['__call__'])
+    else:
+        checker = NamesChecker(['__call__'], permission)
+    defineChecker(view_factory, checker)
+
+def check_permission(factory, permission):
+    """Check whether a permission is defined.
+
+    If not, raise error for factory.
+    """
+    if component.queryUtility(IPermission,
+                              name=permission) is None:
+       raise GrokError('Undefined permission %r in %r. Use '
+                       'grok.define_permission first.'
+                       % (permission, factory), factory)
+
+def get_default_permission(factory):
+    """Determine the default permission for a view.
+    
+    There can be only 0 or 1 default permission.
+    """
+    permissions = class_annotation(factory, 'grok.require', [])
+    if not permissions:
+        return None
+    if len(permissions) > 1:
+        raise GrokError('grok.require was called multiple times in '
+                        '%r. It may only be set once for a class.'
+                        % factory, factory)
+
+    result = permissions[0]
+    check_permission(factory, result)
+    return result
+
+def url(request, obj, name=None):
+    """Given a request and an object, give the URL.
+
+    Optionally pass a third argument name which gets added to the URL.
+    """    
+    url = component.getMultiAdapter((obj, request), IAbsoluteURL)()
+    if name is None:
+        return url
+    return url + '/' + urllib.quote(name.encode('utf-8'),
+                                    SAFE_URL_CHARACTERS)



More information about the Checkins mailing list