Repository: Pylons/pyramid

Issue:
No way to add query parameters without a value
I occasionally need to put a hint in the query string for a URL, which is essentially a parameter without a value. This can be important to provide information to javascript or as a hint to GA. For example I may need to use `http://localhost/dashboard?new-user` as URL when I redirect a new user to the dashboard after completing registration.

Intuitively I expected this to work:

``` python
return HTTPFound(request.route_url('dashboard', _query={'new-user': None}))
```

but that returns `/dashboard?new-user=None` which is not very pretty.



Discussion:
Underneath the query is just forwarded to urllib.urlencode() which afaict
does not offer this option.

On Tue, Sep 10, 2013 at 4:46 AM, Wichert Akkerman
notifications@github.comwrote:

> I occasionally need to put a hint in the query string for a URL, which is
> essentially a parameter without a value. This can be important to provide
> information to javascript or as a hint to GA. For example I may need to use
> http://localhost/dashboard?new-user as URL when I redirect a new user to
> the dashboard after completing registration.
> 
> Intuitively I expected this to work:
> 
> return HTTPFound(request.route_url('dashboard', _query={'new-user': None}))
> 
> but that returns /dashboard?new-user=None which is not very pretty.
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/Pylons/pyramid/issues/1119
> .

Lukcily that is not true: it is handled by pyramid.encode.urlencode which has a unicode-compatible implementation. It should be trivial to add a `is None` check in there.

Ah whoops you're right of course.

On Tue, Sep 10, 2013 at 5:16 AM, Wichert Akkerman
notifications@github.comwrote:

> Lukcily that is not true: it is handled by pyramid.encode.urlencode which
> has a unicode-compatible implementation. It should be trivial to add a `is
> None` check in there.
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/Pylons/pyramid/issues/1119#issuecomment-24149483
> .

I tested how each of these would flow back into request.GET.

It looks like ?new-user would flow into request.GET as an empty string, while ?new_user=None flows into request.GET as the string "None".

Getting the "None" back as a string doesn't seem all that intuitive. IMO this further strengthens the case for changing this behaviour in the encoding function.

Thoughts?

Well any time you are using `None` outside of actual Python code, whether you think it's a good idea or not it's gonna be a string. Magically parsing a received string, checking if its equal to some magic constant, and converting it to some other value isn't something Pyramid is going to, or should, do for you automatically.

Anyway I agree that we can fix the generation of the URL to drop the value if it is `None`, causing an empty string on the parsing side. Do you want to submit a pull request, tests, and a doc update for this feature?


Resolving patch:
diff --git a/pyramid/encode.py b/pyramid/encode.py
--- a/pyramid/encode.py
+++ b/pyramid/encode.py
@@ -32,6 +32,10 @@ def urlencode(query, doseq=True):
 
     See the Python stdlib documentation for ``urllib.urlencode`` for
     more information.
+
+    .. versionchanged:: 1.5
+       In a key/value pair, if the value is ``None`` then it will be
+       dropped from the resulting output.
     """
     try:
         # presumed to be a dictionary
@@ -50,6 +54,8 @@ def urlencode(query, doseq=True):
                 x = _enc(x)
                 result += '%s%s=%s' % (prefix, k, x)
                 prefix = '&'
+        elif v is None:
+            result += '%s%s=' % (prefix, k)
         else:
             v = _enc(v)
             result += '%s%s=%s' % (prefix, k, v)


Test patch:
diff --git a/pyramid/tests/test_encode.py b/pyramid/tests/test_encode.py
--- a/pyramid/tests/test_encode.py
+++ b/pyramid/tests/test_encode.py
@@ -41,6 +41,18 @@ def test_dict(self):
         result = self._callFUT({'a':1})
         self.assertEqual(result, 'a=1')
 
+    def test_None_value(self):
+        result = self._callFUT([('a', None)])
+        self.assertEqual(result, 'a=')
+
+    def test_None_value_with_prefix(self):
+        result = self._callFUT([('a', '1'), ('b', None)])
+        self.assertEqual(result, 'a=1&b=')
+
+    def test_None_value_with_prefix_values(self):
+        result = self._callFUT([('a', '1'), ('b', None), ('c', None)])
+        self.assertEqual(result, 'a=1&b=&c=')
+
 class URLQuoteTests(unittest.TestCase):
     def _callFUT(self, val, safe=''):
         from pyramid.encode import url_quote