Using Django's request after updating POST or GET attributes

If you update request.POST or request.GET in a view (or else where), any subsequent calls to request.REQUEST will still return values from the old, outdated GET and POST dictionaries.

I came across an issue that recently left me scratching my head. Although it's quite specific, this post might help somebody in the future should they notice strange things happening when editing their request object.

TLDR

If you update (i.e. copy and replace) request.POST or request.GET in a view or elsewhere, any subsequent calls to request.REQUEST will still return values from the old, outdated GET and POST dictionaries. This can cause difficult-to-debug problems

I was in the following situation; a view foo_view received a request, copied and updated the request.POST passing control over to a bar_view. This bar_view then read the request information using request.REQUEST and used the updated value to execute some code and return a response.

def bar_view(request):
  country = request.REQUEST.get("country", None)
  ...
  return render_to_response(...)
 
def foo_view(request):
  request.POST = request.POST.copy()
  request.POST.update({ 'country': 'Ireland' })
  return bar_view(request)

In the above code, country should be Ireland but instead it was repeatedly returning None. I imagined that after updating the POST dictionary REQUEST would now return values from that updated dictionary. This is not the case. The docs say of request.REQUEST:

For convenience, a dictionary-like object that searches POST first, then GET. Inspired by PHP's $_REQUEST. For example, if GET = {"name": "john"} and POST = {"age": '34'}, REQUEST["name"] would be "john", and REQUEST["age"] would be "34".

While request.POST and request.GET are dictionary-like QueryDict instance, request.REQUEST is actually a MergeDict instance. This MergeDict looks like a dictionary from the outside, but actually acts as a wrapper around a number of other dictionaries, and simply loops through its children looking for matches when queried.

This means that if you update request.POST or request.GET, any calls to request.REQUEST after the update will still return values from the old GET and POST dictionaries.

What to take away:

  • Avoid rendering and returning other views. I find that this only leads to trouble. I can become very difficult to visualise what is happening. Issue redirects instead where possible.
  • Don't use request.REQUEST. It is a nice convenience for checking all passed parameters but if you are doing anything more complex be sure you understand what it does.
  • Be extra careful if you are editing the request object within a request/response cycle.