[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