Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django redirect using reverse() to a URL that relies on query strings

I'm writing a django application with a URL like 'http://localhost/entity/id/?overlay=other_id'. Where id is the primary key of the particular entity and overlay is an optional query parameter for a second entity to be overlaid in the display. The user can only ever update an entity when viewing objects through an overlay. When POSTing to /update/id, I want to redirect back to /entity/id, but I don't want to lose my query parameter during the redirect, as the change in view would be jarring.

For example, I've got the following in my url.py:

...
(r'^update/(?P<id>.+)/(?P<overlay_id>.+)/$', 'update'),
(r'^entity/(?P<id>.+)/$', 'view'),
...

Because overlay_id is required when updating, it's part of the URL, not a query parameter. In the django view I want to redirect after a successful POST and use reverse() to avoid referencing URLs in my python code. The general idea is:

return HttpResponseRedirect(
  reverse('views.view',
    kwargs={
      'id': id,
    },
  )
)

But how do I pass my query parameter though reverse?

Thanks, Craig

like image 694
Craig Howard Avatar asked Dec 18 '10 07:12

Craig Howard


4 Answers

You can use a Django QueryDict object:

from django.http import QueryDict

# from scratch
qdict = QueryDict('',mutable=True)

# starting with our existing query params to pass along
qdict = request.GET.copy()

# put in new values via regular dict
qdict.update({'foo':'bar'})

# put it together
full_url = reversed_url + '?' + qdict.urlencode()

And of course you could write a convenience method for it similar to the previous answer.

like image 187
Jason Thaxter Avatar answered Nov 07 '22 21:11

Jason Thaxter


Can't you just check for an overlay_id and add it to your url?

redirect_url = reverse( ... )
extra_params = '?overlay=%s' % overlay_id if overlay_id else ''
full_redirect_url = '%s%s' % (redirect_url, extra_params)
return HttpResponseRedirect( full_redirect_url )
like image 40
istruble Avatar answered Nov 07 '22 23:11

istruble


Query string args should be properly escaped and not just concatenated!

Building an url with query string by string concatenation is as bad idea as building SQL queries by string concatenation. It is complicated, unelegant and especially dangerous with a user provided (untrusted) input. Unfortunately Django does not offer an easy possibility to pass query parameters to the reverse function.

Python standard urllib however provides the desired query string encoding functionality.

In my application I've created a helper function like this:

def url_with_querystring(path, **kwargs):
    return path + '?' + urllib.urlencode(kwargs)

Then I call it in the view as follows:

quick_add_order_url = url_with_querystring(reverse(order_add),
    responsible=employee.id, scheduled_for=datetime.date.today(),
    subject='hello world!')
# http://localhost/myapp/order/add/?responsible=5&
#     scheduled_for=2011-03-17&subject=hello+world%21

Please note the proper encoding of special characters like space and exclamation mark!

like image 4
geekQ Avatar answered Nov 07 '22 23:11

geekQ


You shouldn't generate the url string yourself. Given your urls.py you can use reverse like so:

from django.core.urlresolvers import reverse
print reverse('view_function_name', kwargs={"id":"id_value", "overlay_id": "overlay_id_value"})

# or to pass view function arguments as an array:
print reverse('view_function_name', args=("id_value","overlay_id_value",))

If you use named url patterns, which are great for disconnecting your view functions from url identifiers:

# urls.py
...
(r'^update/(?P<id>.+)/(?P<overlay_id>.+)/$', 'update', name="update_foo"),
...

Use reverse like so:

print reverse("update_foo", kwargs={"id":"id_value", "overlay_id": "overlay_id_value"})
like image 3
dbro Avatar answered Nov 07 '22 22:11

dbro