[Checkins] SVN: grok/trunk/ * Implement .url() method on views.

Martijn Faassen faassen at infrae.com
Tue Oct 31 09:07:20 EST 2006


Log message for revision 71005:
  * Implement .url() method on views.
  
  * Fix bug where IAbsoluteURL on grok Views wouldn't work due to lack
    of __name__. Since we already have __name__ in use, override
    IAbsoluteURL for grok Views so we use __view_name__. Make sure
    such exist on views registered by grok.
  
  * the work requires the introduction of a configure.zcml for grok; 
    please re-run your bin/buildout.
  

Changed:
  U   grok/trunk/buildout.cfg
  U   grok/trunk/src/grok/_grok.py
  U   grok/trunk/src/grok/components.py
  A   grok/trunk/src/grok/configure.zcml
  U   grok/trunk/src/grok/ftests/test_grok_functional.py
  A   grok/trunk/src/grok/ftests/url/
  A   grok/trunk/src/grok/ftests/url/__init__.py
  A   grok/trunk/src/grok/ftests/url/url.py
  U   grok/trunk/src/grok/interfaces.py

-=-
Modified: grok/trunk/buildout.cfg
===================================================================
--- grok/trunk/buildout.cfg	2006-10-31 14:04:36 UTC (rev 71004)
+++ grok/trunk/buildout.cfg	2006-10-31 14:07:19 UTC (rev 71005)
@@ -20,6 +20,7 @@
        grokwiki
 
 zcml = *
+       grok
        grok-meta
        grokwiki
 

Modified: grok/trunk/src/grok/_grok.py
===================================================================
--- grok/trunk/src/grok/_grok.py	2006-10-31 14:04:36 UTC (rev 71004)
+++ grok/trunk/src/grok/_grok.py	2006-10-31 14:07:19 UTC (rev 71005)
@@ -279,6 +279,8 @@
                                 factory)
 
         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=(view_context, IDefaultBrowserLayer),
                                  provides=interface.Interface,
@@ -305,6 +307,7 @@
 
         templates.markAssociated(name)
 
+        TemplateView.__view_name__ = name
         component.provideAdapter(TemplateView,
                                  adapts=(context, IDefaultBrowserLayer),
                                  provides=interface.Interface,

Modified: grok/trunk/src/grok/components.py
===================================================================
--- grok/trunk/src/grok/components.py	2006-10-31 14:04:36 UTC (rev 71004)
+++ grok/trunk/src/grok/components.py	2006-10-31 14:07:19 UTC (rev 71005)
@@ -16,6 +16,7 @@
 
 import persistent
 import types
+import urllib
 
 from zope import component
 from zope import interface
@@ -29,6 +30,9 @@
 from zope.formlib import form
 from zope.formlib.namedtemplate import INamedTemplate
 from zope.schema.interfaces import IField
+from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.traversing.browser.absoluteurl import AbsoluteURL
+from zope.traversing.browser.absoluteurl import _safe as SAFE_URL_CHARACTERS
 
 from zope.app.pagetemplate.engine import TrustedAppPT
 from zope.app.publisher.browser import getDefaultViewName
@@ -38,7 +42,7 @@
 from zope.app.container.btree import BTreeContainer
 from zope.app.container.contained import Contained
 
-from grok import util, security
+from grok import util, security, interfaces
 
 
 class Model(Contained, persistent.Persistent):  
@@ -67,7 +71,8 @@
 
 
 class View(BrowserPage):
-
+    interface.implements(interfaces.IGrokView)
+    
     def __init__(self, context, request):
         # Jim would say: WAAAAAAAAAAAAH!
         self.context = removeSecurityProxy(context)
@@ -94,10 +99,42 @@
         # 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
+        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)
+    
     def before(self):
         pass
 
 
+class GrokViewAbsoluteURL(AbsoluteURL):
+    def _getContextName(self, context):
+        return getattr(context, '__view_name__', None)
+    # XXX breadcrumbs method on AbsoluteURL breaks as it does not use
+    # _getContextName to get to the name of the view. What does breadcrumbs do?
+    
+
 class XMLRPC(object):
     pass
 

Added: grok/trunk/src/grok/configure.zcml
===================================================================
--- grok/trunk/src/grok/configure.zcml	2006-10-31 14:04:36 UTC (rev 71004)
+++ grok/trunk/src/grok/configure.zcml	2006-10-31 14:07:19 UTC (rev 71005)
@@ -0,0 +1,26 @@
+<configure    
+  xmlns="http://namespaces.zope.org/zope">
+
+  <!-- we register special IAbsoluteURL views on grok views so that
+       can have them inspect __view_name__ instead of __name__.  
+       __name__ is already used as the class name, and overriding it
+       may make error messages more confusing.  -->
+
+  <view
+      for=".interfaces.IGrokView"
+      name="absolute_url"
+      factory=".components.GrokViewAbsoluteURL"
+      type="zope.publisher.interfaces.http.IHTTPRequest"
+      permission="zope.Public"
+      allowed_interface="zope.traversing.browser.interfaces.IAbsoluteURL"
+      />
+
+  <view
+      for=".interfaces.IGrokView"
+      factory=".components.GrokViewAbsoluteURL"
+      type="zope.publisher.interfaces.http.IHTTPRequest"
+      permission="zope.Public"
+      provides="zope.traversing.browser.interfaces.IAbsoluteURL"
+      />
+
+</configure>

Modified: grok/trunk/src/grok/ftests/test_grok_functional.py
===================================================================
--- grok/trunk/src/grok/ftests/test_grok_functional.py	2006-10-31 14:04:36 UTC (rev 71004)
+++ grok/trunk/src/grok/ftests/test_grok_functional.py	2006-10-31 14:07:19 UTC (rev 71005)
@@ -56,7 +56,7 @@
 
 def test_suite():
     suite = unittest.TestSuite()
-    for name in ['view', 'static', 'xmlrpc', 'traversal', 'form']:
+    for name in ['view', 'static', 'xmlrpc', 'traversal', 'form', 'url']:
         suite.addTest(suiteFromPackage(name))
     return suite
 

Added: grok/trunk/src/grok/ftests/url/__init__.py
===================================================================
--- grok/trunk/src/grok/ftests/url/__init__.py	2006-10-31 14:04:36 UTC (rev 71004)
+++ grok/trunk/src/grok/ftests/url/__init__.py	2006-10-31 14:07:19 UTC (rev 71005)
@@ -0,0 +1 @@
+#

Added: grok/trunk/src/grok/ftests/url/url.py
===================================================================
--- grok/trunk/src/grok/ftests/url/url.py	2006-10-31 14:04:36 UTC (rev 71004)
+++ grok/trunk/src/grok/ftests/url/url.py	2006-10-31 14:07:19 UTC (rev 71005)
@@ -0,0 +1,124 @@
+# -*- coding: UTF-8 -*-
+"""
+Views have a method that can be used to construct URLs:
+
+  >>> import grok
+  >>> grok.grok('grok.ftests.url.url')
+
+  >>> from grok.ftests.url.url import Herd, Mammoth
+  >>> herd = Herd()
+  >>> getRootFolder()['herd'] = herd
+  >>> manfred = Mammoth()
+  >>> herd['manfred'] = manfred
+
+The views in this test implement self.url():
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.handleErrors = False
+  >>> browser.open("http://localhost/herd/manfred/index")
+  >>> print browser.contents
+  http://localhost/herd/manfred/index
+  >>> browser.open("http://localhost/herd/manfred/another")
+  >>> print browser.contents
+  http://localhost/herd/manfred/another
+  >>> browser.open("http://localhost/herd/manfred/yet_another")
+  >>> print browser.contents
+  http://localhost/herd/manfred/yet_another
+  
+We get the views manually so we can do a greater variety of url() calls:
+
+  >>> from zope import component
+  >>> from zope.publisher.browser import TestRequest
+  >>> request = TestRequest()
+  >>> index_view = component.getMultiAdapter((manfred, request), name='index')
+  >>> index_view.url()
+  'http://127.0.0.1/herd/manfred/index'
+  >>> another_view = component.getMultiAdapter((manfred, request),
+  ... name='another')
+  >>> another_view.url()
+  'http://127.0.0.1/herd/manfred/another'
+  >>> yet_another_view = component.getMultiAdapter((manfred, request),
+  ... name='yet_another')
+  >>> yet_another_view.url()
+  'http://127.0.0.1/herd/manfred/yet_another'
+
+Now let's get a URL for a specific object:
+
+  >>> index_view.url(manfred)
+  'http://127.0.0.1/herd/manfred'
+
+This works with any other view too (as they share the same request):
+
+  >>> another_view.url(manfred)
+  'http://127.0.0.1/herd/manfred'
+
+This shows that the default argument is the view itself:
+
+  >>> another_view.url(another_view)
+  'http://127.0.0.1/herd/manfred/another'
+
+We can get the URL for any object in content-space:
+
+  >>> another_view.url(herd)
+  'http://127.0.0.1/herd'
+
+We can also pass a name along with this, to generate a URL to a
+particular view on the object:
+
+  >>> another_view.url(herd, 'something')
+  'http://127.0.0.1/herd/something'
+
+It works properly in the face of non-ascii characters in URLs:
+
+  >>> url = another_view.url(herd, unicode('árgh', 'UTF-8'))
+  >>> url
+  'http://127.0.0.1/herd/%C3%A1rgh'
+  >>> import urllib
+  >>> expected = unicode('http://127.0.0.1/herd/árgh', 'UTF-8')
+  >>> urllib.unquote(url).decode('utf-8') == expected
+  True
+
+It's also possible to just pass in a name. In this case, a URL to that
+view on the context object will be constructed:
+
+  >>> another_view.url('yet_another_view')
+  'http://127.0.0.1/herd/manfred/yet_another_view'
+
+Some combinations of arguments just don't make sense:
+
+  >>> another_view.url('foo', 'bar')
+  Traceback (most recent call last):
+    ...
+  TypeError: url() takes either obj argument, obj, string arguments, or
+  string argument
+  >>> another_view.url('foo', herd)
+  Traceback (most recent call last):
+    ...
+  TypeError: url() takes either obj argument, obj, string arguments, or
+  string argument
+  >>> another_view.url(herd, 'bar', 'baz')
+  Traceback (most recent call last):
+    ...
+  TypeError: url() takes at most 3 arguments (4 given)
+  
+"""
+import grok
+
+class Herd(grok.Container, grok.Model):
+    pass
+
+class Mammoth(grok.Model):
+    pass
+
+grok.context(Mammoth)
+
+class Index(grok.View):
+    def render(self):
+        return self.url()
+    
+class Another(grok.View):
+    def render(self):
+        return self.url()
+
+yet_another = grok.PageTemplate('<p tal:replace="view/url" />')

Modified: grok/trunk/src/grok/interfaces.py
===================================================================
--- grok/trunk/src/grok/interfaces.py	2006-10-31 14:04:36 UTC (rev 71004)
+++ grok/trunk/src/grok/interfaces.py	2006-10-31 14:07:19 UTC (rev 71005)
@@ -128,3 +128,7 @@
 
     def schema_fields(class_):
         """Return a list of schema fields defined for a model or view."""
+
+class IGrokView(interface.Interface):
+    """Grok views all provide this interface.
+    """



More information about the Checkins mailing list