13

I have a Django View that uses a query parameter to do some content filtering. Something like this:

/page/?filter=one+and+two
/page/?filter=one,or,two

I have noticed that Django converts the + to a space (request.GET.get('filter') returns one and two), and I´m OK with that. I just need to adjust the split() function I use in the View accordingly.

But...

When I try to test this View, and I call:

from django.test import Client
client = Client()
client.get('/page/', {'filter': 'one+and+two'})

request.GET.get('filter') returns one+and+two: with plus signs and no spaces. Why is this?

I would like to think that Client().get() mimics the browser behaviour, so what I would like to understand is why calling client.get('/page/', {'filter': 'one+and+two'}) is not like browsing to /page/?filter=one+and+two. For testing purposes it should be the same in my opinion, and in both cases the view should receive a consistent value for filter: be it with + or with spaces.

What I don´t get is why there are two different behaviours.

eillarra
  • 5,027
  • 1
  • 26
  • 32

2 Answers2

22

The plusses in a query string are the normal and correct encoding for spaces. This is a historical artifact; the form value encoding for URLs differs ever so slightly from encoding other elements in the URL.

Django is responsible for decoding the query string back to key-value pairs; that decoding includes decoding the URL percent encoding, where a + is decoded to a space.

When using the test client, you pass in unencoded data, so you'd use:

client.get('/page/', {'filter': 'one and two'})

This is then encoded to a query string for you, and subsequently decoded again when you try and access the parameters.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Ok, so I have to use `client.get('/page/', {'filter': 'one two three'})` in my tests, as I was doing. My concern was that this doesn't seem consistent (if `Client` is mimicking a browser). It should be `client.get('/page/', {'filter': 'one+two+three'})` in my opinion. But I guess this is how `Client` is designed. Thanks anyway! – eillarra Apr 20 '15 at 10:57
  • 1
    @eillarra: it is consistent with how the data is presented in a Django view. How the data is passed around (encoded in a query string), doesn't really matter there. – Martijn Pieters Apr 20 '15 at 10:59
  • Django aims to give you 'nice' Python representations of the data you work with and hides the various peculiar HTML and HTTP representations where possible. Saves a lot of mental effort, actually, because getting encoding and decoding right is painful. – Lutz Prechelt May 11 '21 at 15:23
3

This is because the test client (actually, RequestFactory) runs django.utils.http.urlencode on your data, resulting in filter=one%2Band%2Btwo. Similarly, if you were to use {'filter': 'one and two'}, it would be converted to filter=one%20and%20two, and would come into your view with spaces.

If you really absolutely must have the pluses in your query string, I believe it may be possible to manually override the query string with something like: client.get('/page/', QUERY_STRING='filter=one+and+two'), but that just seems unnecessary and ugly in my opinion.

Joey Wilhelm
  • 5,729
  • 1
  • 28
  • 42
  • The only thing I want to do is to write my tests like a 'real' request. I could write `client.get('/page/', {'filter': 'one two three'})` in the test, and it will pass because the view receives `one two three` in this case. But it doesn't seem logical. – eillarra Apr 17 '15 at 16:28
  • @eillarra, Plus sign is special case in query string, it means space character. That's why Django performs the conversion. Refer: http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 and http://stackoverflow.com/questions/1005676/urls-and-plus-signs So I think it's OK to just use 'one two three' in test cases. – ZZY Apr 20 '15 at 07:18
  • `django.utils.http.urlencode()` produces `filter=one+and+two` because that's the correct encoding for spaces in a query parameter. – Martijn Pieters Apr 20 '15 at 09:24
  • In other words, there is no need to do any manually override anything here; the `QUERY_STRING` value will *already* be encoded correctly. – Martijn Pieters Apr 20 '15 at 09:38