[Checkins] SVN: grok/trunk/ Added keyword parameter data to view function url()

Peter Bengtsson zope at peterbe.com
Thu May 1 10:38:35 EDT 2008


Log message for revision 86004:
  Added keyword parameter data to view function url()

Changed:
  U   grok/trunk/CHANGES.txt
  U   grok/trunk/doc/grok_overview.rst
  U   grok/trunk/src/grok/admin/docgrok.txt
  U   grok/trunk/src/grok/components.py
  U   grok/trunk/src/grok/ftests/url/url.py
  U   grok/trunk/src/grok/interfaces.py
  U   grok/trunk/src/grok/util.py

-=-
Modified: grok/trunk/CHANGES.txt
===================================================================
--- grok/trunk/CHANGES.txt	2008-05-01 14:29:58 UTC (rev 86003)
+++ grok/trunk/CHANGES.txt	2008-05-01 14:38:34 UTC (rev 86004)
@@ -7,6 +7,11 @@
 Feature changes
 ---------------
 
+* Added an optional parameter 'data' to the method 'url()' that accepts
+  a dictionary that is then converted to a query string. See 
+   
+  http://grok.zope.org/documentation/how-to/generate-urls-with-the-url-function-in-views/view
+  
 * Added an OrderedContainer component.
 
 * Introduced the new `sphinx`-based documentation engine. See

Modified: grok/trunk/doc/grok_overview.rst
===================================================================
--- grok/trunk/doc/grok_overview.rst	2008-05-01 14:29:58 UTC (rev 86003)
+++ grok/trunk/doc/grok_overview.rst	2008-05-01 14:38:34 UTC (rev 86004)
@@ -315,7 +315,8 @@
 
 Views have a special method called ``url()`` that can be used to
 create URLs to objects. The ``url`` method takes zero, one or two 
-arguments::
+arguments and an additional optional keyword argument 'data' that
+is converted into a CGI query string appended to the URL::
 
 * self.url() - URL to this view.
 
@@ -327,6 +328,13 @@
 * self.url(object, u"name") - URL to the provided object, with
   		   ``/name`` appended, to point to a view or subobject
   		   of the provided object.
+                   
+* self.url(object, u"name", data={'name':'Peter', 'age':28}) 
+            - URL to the provided object, with ``/name`` appended
+              with '?name=Peter&age=28' at the end.
+                   
+* self.url(data={'name':u'Andr\xe9', 'age:int':28}) - URL to the provided 
+                   object with '?name=Andre%C3%A9'&age%3Aint=28'.
 
 From the view, this is accessed through ``self.url()``. From the
 template, this method can be accessed using ``view.url()``.

Modified: grok/trunk/src/grok/admin/docgrok.txt
===================================================================
--- grok/trunk/src/grok/admin/docgrok.txt	2008-05-01 14:29:58 UTC (rev 86003)
+++ grok/trunk/src/grok/admin/docgrok.txt	2008-05-01 14:38:34 UTC (rev 86004)
@@ -229,10 +229,10 @@
     >>> entry = [x for x in methods if x['name'] == 'url'][0]
     >>> entry['name']
     'url'
-    >>> entry['doc']
-    ''
+    >>> entry['doc'].strip()
+    'Return string for the URL based on the obj and name. The data \n    argument is used to form a CGI query string.'
     >>> entry['signature']
-    '(obj=None, name=None)'
+    '(obj=None, name=None, data=None)'
 
 The other methods work as described in the ``DocGrokClass``
 documentation.

Modified: grok/trunk/src/grok/components.py
===================================================================
--- grok/trunk/src/grok/components.py	2008-05-01 14:29:58 UTC (rev 86003)
+++ grok/trunk/src/grok/components.py	2008-05-01 14:38:34 UTC (rev 86004)
@@ -191,9 +191,10 @@
         return value
 
 
-    def url(self, obj=None, name=None):
-        # if the first argument is a string, that's the name. There should
-        # be no second argument
+    def url(self, obj=None, name=None, data=None):
+        """Return string for the URL based on the obj and name. The data 
+        argument is used to form a CGI query string.
+        """
         if isinstance(obj, basestring):
             if name is not None:
                 raise TypeError(
@@ -208,8 +209,15 @@
         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)
+            
+        if data is None:
+            data = {}
+        else:
+            if not isinstance(data, dict):
+                raise TypeError('url() data argument must be a dict.')
 
+        return util.url(self.request, obj, name, data=data)
+
     def application_url(self, name=None):
         obj = self.context
         while obj is not None:

Modified: grok/trunk/src/grok/ftests/url/url.py
===================================================================
--- grok/trunk/src/grok/ftests/url/url.py	2008-05-01 14:29:58 UTC (rev 86003)
+++ grok/trunk/src/grok/ftests/url/url.py	2008-05-01 14:38:34 UTC (rev 86004)
@@ -65,6 +65,47 @@
   >>> another_view.url(herd, 'something')
   'http://127.0.0.1/herd/something'
 
+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'
+
+The url() method supports a data argument which is converted to a CGI type query 
+string. If any of the values are of type unicode it's converted to a string 
+assuming the encoding is UTF-8.
+
+There is some object/name/data resolution code available that provides the magic 
+to make mixing of positional arguments and keyword arguments work.
+
+This is the key word argument signature::
+
+  >>> index_view.url(herd, '@@sample_view', data=dict(age=28))
+  'http://127.0.0.1/herd/@@sample_view?age=28'
+  >>> index_view.url(herd, data=dict(age=28))
+  'http://127.0.0.1/herd?age=28'
+  >>> index_view.url('@@sample_view', data=dict(age=28))
+  'http://127.0.0.1/herd/manfred/@@sample_view?age=28'
+  >>> index_view.url(data=dict(age=28))
+  'http://127.0.0.1/herd/manfred/index?age=28'
+
+There is no problem putting one of the 'reserved' arguments inside the data 
+argument or explicitely supplying 'None':
+
+  >>> index_view.url(herd, None, data=dict(name="Peter"))
+  'http://127.0.0.1/herd?name=Peter'
+
+Since order in dictionairies is arbitrary we'll test the presence of multiple 
+keywords by using find()
+
+  >>> url = index_view.url('sample_view', data=dict(a=1, b=2, c=3))
+  >>> url.find('a=1') > -1
+  True
+  >>> url.find('b=2') > -1
+  True
+  >>> url.find('c=3') > -1
+  True
+
 It works properly in the face of non-ascii characters in URLs:
 
   >>> url = another_view.url(herd, unicode('árgh', 'UTF-8'))
@@ -75,29 +116,60 @@
   >>> 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
+  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')
+  TypeError: url() takes either obj argument, obj, string arguments, or string \
+  argument
+  >>> another_view.url(herd, 'bar', data='baz')
   Traceback (most recent call last):
     ...
-  TypeError: url() takes at most 3 arguments (4 given)
+  TypeError: url() data argument must be a dict.
   
+Since we're relying on urllib to do the CGI parameter encoding it's quite 
+smart but fails on unicode objects but url() is programmed to automatically
+convert unicode to UTF-8 on the fly.
+
+  >>> index_view.url(data={'name':u'Andr\xe9'})
+  'http://127.0.0.1/herd/manfred/index?name=Andr%C3%A9'
+   
+As we're relying on urllib to do the url encoding, it also converts values that
+are lists to repeated key value pairs such that key=[1,2] becomes key=1&key=2
+
+  >>> index_view.url(data={'key':[1,2]})
+  'http://127.0.0.1/herd/manfred/index?key=1&key=2'
+
+We also make sure the values in the list that are unicode instances are encoded 
+properly:
+
+  >>> result = index_view.url(data={'key':[u'\xe9',2]})
+  >>> print result
+  http://127.0.0.1/herd/manfred/index?key=%C3%A9&key=2
+
+  >>> import cgi
+  >>> print unicode(cgi.parse_qs(result.split('?')[1])['key'][0], 'utf-8')
+  é
+
+Zope magic!! Here we test casting parameters in the CGI query string:
+
+  >>> result = index_view.url('multiplier', data={'age:int':1})
+  >>> result
+  'http://127.0.0.1/herd/manfred/multiplier?age%3Aint=1'
+
+  >>> browser.open(result)
+  >>> browser.contents
+  '2'
+  >>> browser.open('http://127.0.0.1/herd/manfred/multiplier?age=1')
+  >>> browser.contents
+  '11'
+
 """
 import grok
 
@@ -120,4 +192,11 @@
 class YetAnother(grok.View):
     pass
 
+class Multiplier(grok.View):
+    def update(self, age=0):
+        self.age = age
+    
+    def render(self):
+        return unicode(self.age * 2)
+
 yetanother = grok.PageTemplate('<p tal:replace="view/url" />')

Modified: grok/trunk/src/grok/interfaces.py
===================================================================
--- grok/trunk/src/grok/interfaces.py	2008-05-01 14:29:58 UTC (rev 86003)
+++ grok/trunk/src/grok/interfaces.py	2008-05-01 14:38:34 UTC (rev 86004)
@@ -244,8 +244,10 @@
         for some grokkers which rely on them.
         """
 
-    def url(request, obj, name=None):
+    def url(request, obj, name=None, data=None):
         """Generate the URL to an object with optional name attached.
+        An optional argument 'data' can be a dictionary that is converted
+        into a query string appended to the URL.
         """
 
     def notify(event):
@@ -294,7 +296,7 @@
     def redirect(url):
        """Redirect to given URL"""
 
-    def url(obj=None, name=None):
+    def url(obj=None, name=None, data=None):
         """Construct URL.
 
         If no arguments given, construct URL to view itself.
@@ -306,6 +308,9 @@
 
         If both object and name arguments are supplied, construct
         URL to obj/name.
+        
+        Optionally pass a 'data' keyword argument which gets added to the URL 
+        as a cgi query string.
         """
 
     def update(**kw):

Modified: grok/trunk/src/grok/util.py
===================================================================
--- grok/trunk/src/grok/util.py	2008-05-01 14:29:58 UTC (rev 86003)
+++ grok/trunk/src/grok/util.py	2008-05-01 14:38:34 UTC (rev 86004)
@@ -80,16 +80,19 @@
     result = permissions[0]
     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.
-    """
+def url(request, obj, name=None, data={}):
     url = component.getMultiAdapter((obj, request), IAbsoluteURL)()
-    if name is None:
-        return url
-    return url + '/' + urllib.quote(name.encode('utf-8'),
-                                    SAFE_URL_CHARACTERS)
+    if name is not None:
+        url += '/' + urllib.quote(name.encode('utf-8'), SAFE_URL_CHARACTERS)
+    if data:
+        for k,v in data.items():
+            if isinstance(v, unicode):
+                data[k] = v.encode('utf-8')
+            if isinstance(v, (list, set, tuple)):
+                data[k] = [isinstance(item, unicode) and item.encode('utf-8') 
+                or item for item in v]
+        url += '?' + urllib.urlencode(data, doseq=True)
+    return url
 
 def safely_locate_maybe(obj, parent, name):
     """Set an object's __parent__ (and __name__) if the object's



More information about the Checkins mailing list