[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