[Checkins] SVN: grok/branches/ksmith_mcweekly-groklets/src/grok/ initial prototype

Kevin Smith kevin at mcweekly.com
Mon Oct 1 16:45:16 EDT 2007


Log message for revision 80455:
  initial prototype

Changed:
  U   grok/branches/ksmith_mcweekly-groklets/src/grok/__init__.py
  U   grok/branches/ksmith_mcweekly-groklets/src/grok/components.py
  A   grok/branches/ksmith_mcweekly-groklets/src/grok/groklet.py
  A   grok/branches/ksmith_mcweekly-groklets/src/grok/groklets.txt
  U   grok/branches/ksmith_mcweekly-groklets/src/grok/meta.py

-=-
Modified: grok/branches/ksmith_mcweekly-groklets/src/grok/__init__.py
===================================================================
--- grok/branches/ksmith_mcweekly-groklets/src/grok/__init__.py	2007-10-01 20:38:37 UTC (rev 80454)
+++ grok/branches/ksmith_mcweekly-groklets/src/grok/__init__.py	2007-10-01 20:45:16 UTC (rev 80455)
@@ -36,7 +36,7 @@
 from grok.components import Application, Form, AddForm, EditForm, DisplayForm
 from grok.components import Indexes
 from grok.components import Permission, Role
-from grok.components import Skin, IGrokLayer
+from grok.components import Skin, IGrokLayer, Groklet
 from grok.directive import (context, name, title, template, templatedir,
                             provides, baseclass, global_utility, local_utility,
                             permissions, require, site, layer)

Modified: grok/branches/ksmith_mcweekly-groklets/src/grok/components.py
===================================================================
--- grok/branches/ksmith_mcweekly-groklets/src/grok/components.py	2007-10-01 20:38:37 UTC (rev 80454)
+++ grok/branches/ksmith_mcweekly-groklets/src/grok/components.py	2007-10-01 20:45:16 UTC (rev 80455)
@@ -480,3 +480,99 @@
 
 class Skin(object):
     pass
+
+class Groklet(BrowserPage):
+    """ Groklet - viewlets without viewletmanagers
+
+    Groklets also contain all the handy helper functions
+    that views do.
+    """
+    interface.implements(interfaces.IGroklet)
+
+    @property
+    def order(self):
+        """This is meant to be overridden with a value"""
+        return self.__class__.__name__
+
+    def __init__(self, context, request, view, manager):
+        self.__parent__ = view
+        self.context = view.context
+        self.request = request
+        self.manager = manager
+        self.static = component.queryAdapter(
+            self.request,
+            interface.Interface,
+            name=self.module_info.package_dotted_name
+            )
+
+    @property
+    def response(self):
+        return self.request.response
+
+    def __call__(self):
+        self.update()
+        return self.render()
+
+    def _render_template(self):
+        namespace = self.template.pt_getContext()
+        namespace['request'] = self.request
+        namespace['view'] = self
+        namespace['context'] = self.context
+        namespace['static'] = self.static
+        namespace['parent'] = self.__parent__
+        return self.template.pt_render(namespace)
+
+    def __getitem__(self, key):
+        # XXX give nice error message if template is None
+        return self.template.macros[key]
+
+
+    def url(self, obj=None, name=None):
+        # if the first argument is a string, that's the name. There should
+        # be no second argument
+        if isinstance(obj, basestring):
+            if name is not None:
+                raise TypeError(
+                    'url() takes either obj argument, obj, string arguments, '
+                    'or string argument')
+            name = obj
+            obj = None
+
+        if name is None and obj is None:
+            # create URL to view itself
+            obj = self
+        elif name is not None and obj is None:
+            # create URL to view on context
+            obj = self.context
+        return util.url(self.request, obj, name)
+
+    def application_url(self, name=None):
+        obj = self.context
+        while obj is not None:
+            if isinstance(obj, Application):
+                return self.url(obj, name)
+            obj = obj.__parent__
+        raise ValueError("No application found.")
+
+    def redirect(self, url):
+        return self.request.response.redirect(url)
+
+    def update(self):
+        pass
+
+    def render(self):
+        # no need to do this since the update method
+        # is called by the viewlet manager
+        #mapply(self.update, (), self.request)
+        if self.request.response.getStatus() in (302, 303):
+            # A redirect was triggered somewhere in update().  Don't
+            # continue rendering the template or doing anything else.
+            return
+
+        template = getattr(self, 'template', None)
+        if template is not None:
+            return self._render_template()
+
+
+
+

Added: grok/branches/ksmith_mcweekly-groklets/src/grok/groklet.py
===================================================================
--- grok/branches/ksmith_mcweekly-groklets/src/grok/groklet.py	                        (rev 0)
+++ grok/branches/ksmith_mcweekly-groklets/src/grok/groklet.py	2007-10-01 20:45:16 UTC (rev 80455)
@@ -0,0 +1,74 @@
+import grok
+
+from zope import interface, component
+from zope.traversing.interfaces import IPathAdapter
+from zope.tales.interfaces import ITALESFunctionNamespace
+from zope.security.proxy import removeSecurityProxy
+
+from interfaces import IGroklet
+import operator
+
+
+
+class GrokletTalesAPIAdapter(object):
+    interface.implements(ITALESFunctionNamespace, IPathAdapter)
+
+
+    def __init__(self, context):
+        self.context= context
+
+    def setEngine(self, engine):
+        # make request and view available
+        self.request = removeSecurityProxy(engine.vars['request'])
+        self.view = removeSecurityProxy(engine.vars['view'])
+
+    def getGroklets(self, name):
+        groklets = list(component.getAdapters(
+            (self.context,
+             self.request,
+             self.view,
+             self.view), 
+            IGroklet))
+
+        for name, groklet in groklets:
+            print name, groklet.__view_name__
+                # keep only the groklets: with the right name
+                # e.g. /groklets:center  _groklet_name == 'center'
+                # if __view_name goes bye bye, create a __groklet_name
+        groklets = [groklet for gname, groklet in list(groklets) if getattr(groklet, '__view_name__', None) == name ]
+        return groklets 
+
+    def __getattr__(self, name):
+
+        # XXX: skip private methods, is there a better way to handle this?
+        if not name.startswith('__'):
+
+            # gather all /groklets:
+            
+            groklets = self.getGroklets(name)
+            
+            groklets = sorted( groklets,
+                               key=operator.attrgetter('order'))
+
+            results = []
+
+            # XXX: This could also be split into an update, then render pass
+            # XXX: Catch errors
+            for groklet in groklets:
+                try:
+                    result = groklet()
+                except:
+                    # XXX: Area for improvement
+                    # XXX: viewlets ignore errors and render the page
+                    # this is a stab at rendering the error as output
+                    # to the viewlet, not a good default, but here as
+                    # a reminder, since tracking down viewlet errors
+                    # are a big pain
+                    import sys
+                    exc_info = sys.exc_info()
+                    result = "<b>%s</b><p>%s</p><p>%s</p>" % (exc_info[0], exc_info[1], exc_info[2] )
+
+                results.append( result )
+                    
+            return '\n'.join(results)
+        

Added: grok/branches/ksmith_mcweekly-groklets/src/grok/groklets.txt
===================================================================
--- grok/branches/ksmith_mcweekly-groklets/src/grok/groklets.txt	                        (rev 0)
+++ grok/branches/ksmith_mcweekly-groklets/src/grok/groklets.txt	2007-10-01 20:45:16 UTC (rev 80455)
@@ -0,0 +1,87 @@
+========
+Groklets
+========
+
+Groklets are like viewlets but without viewletmanagers.
+
+Example Code
+------------
+
+The following registers a groklet for MyView named view::
+	      
+ class MyView(grok.View):
+     grok.context(App)
+     grok.name('index')
+
+ class MyGroklet(Groklet): # registers directly with the view
+     grok.context(MyView)
+     grok.name('center') # the 'provider' name
+     order = 10 # optional
+
+     def render(self):
+        return "<h2>groklet 1</h2>"
+
+ class MyGroklet2(Groklet):
+     grok.context(MyView) # associate with view or interface
+     grok.name('center')
+     order = 20
+
+     def render(self):
+        return "<h2>groklet 2</h2>"
+
+The groklets are available using the 'groklet' tal namespace.
+app_templates/myview.pt::
+
+ <span tal:replace="structure view/groklet:center" />
+
+Output...::
+
+ <h2>groklet 1</h2><h2>groklet2</h2>
+
+
+Comparison to Viewlet
+---------------------
+
+Typicla registration for a traditional viewlet...::
+
+ class MyView(grok.View):
+     grok.context(App)
+     grok.name('index')
+ 
+ class MyViewletManager(ViewletManager):
+     # unless customized, this is a dead chicken
+     grok.context(MyView)
+     grok.name('center')
+
+ class MyViewlet1(Viewlet):
+     grok.viewletmanager(MyViewletManager)
+     order =10
+
+     def render(self):
+        return "<h2>viewlet 1</h2>"
+
+ class MyViewlet2(Viewlet):
+     grok.viewletmanager(MyViewletManager)
+     order = 20
+
+     def render(self):
+        return "<h2>viewlet 2</h2>"
+
+app_templates/myview.pt::
+
+ <span tal:replace="structure view/provider:center" />
+
+output...::
+
+ <h2>viewlet 1</h2><h2>viewlet2</h2>
+
+In this example it may not seem obvious, but when you have a 'left',
+'center', 'right', 'header', 'footer' providers, the dead chickens
+pile up quick, since in the common case the viewletmanager does
+nothing more than associate the provider name.
+
+
+TODO
+----
+
+* Allow groklets to act as viewletmanagers for custom rendering solutions.
\ No newline at end of file

Modified: grok/branches/ksmith_mcweekly-groklets/src/grok/meta.py
===================================================================
--- grok/branches/ksmith_mcweekly-groklets/src/grok/meta.py	2007-10-01 20:38:37 UTC (rev 80454)
+++ grok/branches/ksmith_mcweekly-groklets/src/grok/meta.py	2007-10-01 20:45:16 UTC (rev 80455)
@@ -20,7 +20,8 @@
 from zope.publisher.interfaces.browser import (IDefaultBrowserLayer,
                                                IBrowserRequest,
                                                IBrowserPublisher,
-                                               IBrowserSkinType)
+                                               IBrowserSkinType,
+                                               IBrowserView)
 from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
 from zope.security.permission import Permission
 from zope.app.securitypolicy.role import Role
@@ -45,7 +46,7 @@
 from martian import util
 
 import grok
-from grok import components, formlib
+from grok import components, formlib, interfaces
 from grok.util import check_adapts, get_default_permission, make_checker
 
 
@@ -665,3 +666,68 @@
         return directive
     else:
         return default
+
+
+class GrokletGrokker(martian.ClassGrokker):
+    component_class = grok.Groklet
+
+    def grok(self, name, factory, context, module_info, templates):
+        print "Grokking %s " % name
+
+        view_context = util.determine_class_context(factory, context)
+
+        factory.module_info = module_info
+        factory_name = factory.__name__.lower()
+
+        # find templates
+        template_name = util.class_annotation(factory, 'grok.template',
+                                              factory_name)
+        template = templates.get(template_name)
+
+        if factory_name != template_name:
+            # grok.template is being used
+            if templates.get(factory_name):
+                raise GrokError("Multiple possible templates for view %r. It "
+                                "uses grok.template('%s'), but there is also "
+                                "a template called '%s'."
+                                % (factory, template_name, factory_name),
+                                factory)
+
+        if template:
+            templates.markAssociated(template_name)
+            factory.template = template
+        else:
+            if not getattr(factory, 'render', None):
+                # 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)
+
+        # grab layer from class or module
+        view_layer = determine_class_directive('grok.layer', factory, module_info, default=IDefaultBrowserLayer)
+
+        view_name = util.class_annotation(factory, 'grok.name',
+                                          factory_name)
+        # __view_name__ is needed to support IAbsoluteURL on views
+        factory.__view_name__ = view_name
+        component.provideAdapter(factory,
+                                 adapts=(None,
+                                         view_layer,
+                                         IBrowserView,
+                                         view_context),
+                                 provides=interfaces.IGroklet,
+                                 name=view_name)
+
+        # protect view, public by default
+        default_permission = get_default_permission(factory)
+        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)
+        for method in methods:
+            if getattr(method, '__grok_require__', None) is not None:
+                raise GrokError('The @grok.require decorator is used for '
+                                'method %r in view %r. It may only be used '
+                                'for XML-RPC methods.'
+                                % (method.__name__, factory), factory)
+        return True



More information about the Checkins mailing list