Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulating a POST request in Django

Let's suppose I have the following url: /valid/django/app/path/?foo=bar&spam=eggs

I can simulate a request to this URL in Django thusly:

from django.shortcuts import render
from django.core.urlresolvers import resolve

def simulate(request, url=None, template_name="not_important.html"):
    if url:
        dv = resolve(url.split('?')[0])
        return dv.func(request, *dv.args, **dv.kwargs)
    else:
        return render(request, template_name)

However, I'd like to include the parameters to the included view, so that the request.REQUEST and request.GET objects would also include foo and spam

I don't see how I can do this cleanly; as far as I understand the request.GET and request.REQUEST dictionaries are immutable so I can't merely do something like:

import urlparse

def simulate(request, url=None, template_name="not_important.html"):
    if url:
        dv = resolve(url.split('?')[0])
        qs = "".join(url.split('?')[1:])
        if qs:
            request.REQUEST.update(urlparse.parse_qs(qs))
            request.GET.update(urlparse.parse_qs(qs))
        return dv.func(request, *dv.args, **dv.kwargs)
    else:
        return render(request, template_name)

Or I'll get the error message

This QueryDict instance is immutable

for the request.GET object and

'MergeDict' object has no attribute 'update'

for the request.REQUEST object

In case anyone is wondering why I want to do this: I want to allow users to fill out a form and then, when they submit, if they aren't logged in it sends them to a login form that includes the original URL in a hidden field. After logging in, rather than redirecting back to that link (which would be a GET request) I want it to call the original view, with the request variables it originally had, so that it can use the same POST request.

And so of course in the process I'm also just interested in whether it would be possible to simulate a POST/GET request to a Django view when given a valid URL for the site.

like image 522
Jordan Reiter Avatar asked Dec 09 '22 02:12

Jordan Reiter


1 Answers

request.GET/POST are QueryDict instances. According to the documentation on QueryDict, there are indeed "immutable" unless you clone them:

QueryDict instances are immutable, unless you create a copy() of them. That means you can't change attributes of request.POST and request.GET directly.

You can copy, update and re-assign QueryDicts as such:

ipdb> request.GET
<QueryDict: {u'x': [u'1']}>
ipdb> request.POST
<QueryDict: {}>
ipdb> request.REQUEST
MergeDict(<QueryDict: {}>, <QueryDict: {u'x': [u'1']}>)
ipdb> new_post = request.POST.copy()
ipdb> new_post.update(request.GET)
ipdb> request.POST = new_post
ipdb> request.POST
<QueryDict: {u'x': [u'1']}>
ipdb> request.GET
<QueryDict: {u'x': [u'1']}>
ipdb> request.REQUEST
MergeDict(<QueryDict: {}>, <QueryDict: {u'x': [u'1']}>)

The trick to update the MergeDict is to override its dicts attribute as such:

ipdb> request.REQUEST
MergeDict(<QueryDict: {}>, <QueryDict: {u'x': [u'1']}>)
ipdb> request.REQUEST.dicts = (request.POST, request.GET)
ipdb> request.REQUEST
MergeDict(<QueryDict: {u'x': [u'1']}>, <QueryDict: {u'x': [u'1']}>)

Note that MergeDict is defined in module django.utils.datastructures, and instanciated in django.core.handlers.wsgi (and django.core.handlers.modpython) as such: self._request = datastructures.MergeDict(self.POST, self.GET).

DISCLAMER: MergeDict is not documented, will break one day, and probably even kill some kittens. Use at your own discretion and with your own kittens. That said I like your use case, it's a pretty good idea.

like image 119
jpic Avatar answered Dec 28 '22 06:12

jpic