91

When processing a POST request in the Django views.py file, I sometimes need to redirect it to another url. This url I'm redirecting to is handled by another function in the same Django views.py file. Is there a way of doing this and maintaining the original POST data?

UPDATE: More explanation of why I want to do this. I have two web apps (let's call them AppA and AppB) which accept data entered into a text field by the user. When the the user clicks submit, the data is processed and detailed results are displayed. AppA and AppB expect different types of data. Sometimes a user mistakenly posts AppB type data to AppA. When this happens I want to redirect them to AppB and show the AppB results or at least have it populated with the data they entered into AppA.

Also:

  • The client wants two separate apps rather than combining them into just one.

  • I can't show the code as it belongs to a client.

UPDATE 2: I've decided that KISS is the best principle here. I have combined the two apps into one which makes things simpler and more robust; I should be able to convince the client it's the best way to go too. Thanks for all the great feedback. If I was going to maintain two apps as described then I think sessions would be the way to do this - thanks to Matthew J Morrison for suggesting that. Thanks to Dzida as his comments got me thinking about the design and simplification.

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
FunLovinCoder
  • 7,597
  • 11
  • 46
  • 57
  • do you really need to send a redirect to the client, or is this something that can be done by just calling a function and passing all of the post data to it? – Matthew J Morrison Jun 11 '10 at 15:44
  • I need to change the url at the client's browser, so this is the only way I can thing of doing that. – FunLovinCoder Jun 11 '10 at 15:45
  • and you cant just do all the processing with the post data first, and then redirect after the fact? – Matthew J Morrison Jun 11 '10 at 15:46
  • I have a similar situation, but the POST'ed data either is or is not matched to existing data. If it matches, I get the id for that data, then pass that id to the script via GET variable in the redirect. I also save the POST data in SESSION. Now the redirected page loads the data referred to by the `id` in GET, and also has access to other data submitted by POST. – Buttle Butkus Jan 02 '14 at 23:34

9 Answers9

68

If you faced such problem there's a slight chance that you might need to revise your designs.

This is a restriction of HTTP that POST data cannot go with redirects.

Can you describe what are you trying to accomplish and maybe then we can think about some neat solution.

If you do not want use sessions as Matthew suggested you can pass POST params in GET to the new page (consider some limitations such as security and max length of GET params in query string).

UPDATE to your update:) It sounds strange to me that you have 2 web apps and those apps use one views.py (am I right?). Anyway consider passing your data from POST in GET to the proper view (in case data is not sensitive of course).

dzida
  • 8,854
  • 2
  • 36
  • 57
  • 2
    I can see what what he is trying to do could be valid if he is trying to handle an expired login that is going to force a user to login after submitting a form... in that case, he is going to want to retain the data that was submitted and not force the user to re-enter everything after they complete the login screen. – Matthew J Morrison Jun 11 '10 at 16:43
  • Not sure if I got the point but in this case login operation can be performed by first view with little code modification and without making unnecessary redirects. It would be great to read exisitng code to make more accurate advices. – dzida Jun 11 '10 at 16:53
  • I'm saying if you submit a form, and you're not logged in, you're going to get redirected to a login form... in that scenario, you'll lose whatever it was that you submitted. I agree about being able to see some existing code. – Matthew J Morrison Jun 11 '10 at 17:02
  • 1
    its not up to you decide if the use case is valid or not, I am faced with the same situation for which a redirect with fresh POST is the perfect solution in terms of simplicity and modularity. – Rabih Kodeih Oct 18 '12 at 20:53
64

I think how I would probably handle this situation would be to save the post data in session, then remove it when I no longer need it. That way I can access the original post data after a redirect even though that post is gone.

Will that work for what you're trying to do?

Here is a code sample of what I'm suggesting: (keep in mind this is untested code)

def some_view(request):
    #do some stuff
    request.session['_old_post'] = request.POST
    return HttpResponseRedirect('next_view')

def next_view(request):
    old_post = request.session.get('_old_post')
    #do some stuff using old_post

One other thing to keep in mind... if you're doing this and also uploading files, i would not do it this way.

Paolo
  • 20,112
  • 21
  • 72
  • 113
Matthew J Morrison
  • 4,343
  • 3
  • 28
  • 45
  • 1
    I've never used sessions, but I'll have a look at that thanks. – FunLovinCoder Jun 11 '10 at 15:50
  • 1
    this isn't the best practice, but still helps. I guess for this issue we won't get anything prettier. – Guilherme David da Costa May 10 '13 at 19:14
  • @GuilhermeDaviddaCosta why do you say it isn't best practice? Can you give us a hint? – Buttle Butkus Jan 02 '14 at 23:32
  • because it doesn't seem a very good idea to store too much data in session. But as I said as well, I can't think of anything prettier. – Guilherme David da Costa Jan 16 '14 at 08:40
  • @guilherme How u think we can optimize this code? what if we really want to upload files as well? Can you tell me why cant we store POST data in Session? – A.J. Mar 10 '14 at 06:45
  • 4
    Well, that depends on where you are storing your session. Lately Ive been seeing people using an entire server with memcached for sessions and load balance(using round robin) every request. I dont want to give you advice I cant stand up for, but I would save the file as temp and get only a link for it on the session. Seems nobody is running out of ram these days. – Guilherme David da Costa Mar 10 '14 at 17:24
  • what if next_view is not under our control, and its under paypal ? – Nimish Bansal Jan 20 '19 at 15:00
26

You need to use a HTTP 1.1 Temporary Redirect (307).

Unfortunately, Django redirect() and HTTPResponseRedirect (permanent) return only a 301 or 302. You have to implement it yourself:

from django.http import HttpResponse, iri_to_uri
class HttpResponseTemporaryRedirect(HttpResponse):
    status_code = 307

    def __init__(self, redirect_to):
        HttpResponse.__init__(self)
        self['Location'] = iri_to_uri(redirect_to)

See also the django.http module.

Edit:

on recent Django versions, change iri_to_uri import to:

from django.utils.encoding import iri_to_uri
Paolo
  • 20,112
  • 21
  • 72
  • 113
Lloeki
  • 6,573
  • 2
  • 33
  • 32
  • Newer versions of Django have a permanent redirect HttpResponsePermanentRedirect but not sure if it solves the original problem https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpResponsePermanentRedirect – JiminyCricket Dec 12 '13 at 21:44
  • Does this preserve the POST data through the redirect? – dfrankow Sep 02 '21 at 19:28
13

use requests package.Its very easy to implement

pip install requests

then you can call any urls with any method and transfer data

in your views import requests

import requests

to post data, follow the format

r = requests.post('http://yourdomain/path/', data = {'key':'value'})

to get the absolute url in django view, use

request.build_absolute_uri(reverse('view_name'))

Thus the django view code looks like

r = requests.post(
            request.build_absolute_uri(reverse('view_name')), 
            data = {'key':'value'}
    )

where r is the response object with status_code and content attribute. r.status_code gives the status code(on success it will be 200) and r.content gives the body of response. There is a json method(r.json()) that will convert response to json format

requests

requests.post

Aneesh R S
  • 3,807
  • 4
  • 23
  • 35
  • 2
    I believe this does not change the URL in the user's browser. It sounds like OP wants to forward to a new URL – dfrankow Sep 02 '21 at 19:27
6

Just call your new view from your old view using the same request object. Of course it won't result in a redirect as such, but if all you care about is 'transferring' data from one view to the other, then it should work.
I tested the following snippet and it works.

from django.views.generic import View

class MyOldView(View):
    def post(self, request):
        return MyNewView().post(request)

class MyNewView(View):
    def post(self, request):
        my_data = request.body
        print "look Ma; my data made it over here:", my_data
Komu
  • 14,174
  • 2
  • 28
  • 22
  • 1
    This is a cool idea, but does not change the URL in the user's browser. It sounds like OP wants to forward to a new URL. – dfrankow Sep 02 '21 at 19:27
  • That worked, thank you. Hovever, self.request was lost in the new view, so that's what I did: `new_view = MyNewView(); new_view.request = request; new_view.post(request)` – Mario Orlandi Jun 05 '22 at 08:59
1

You can use render and context with with it:

Render(request,"your template path",        {'vad name' : var value}

You can recive vars in template :

{% If var name %}
 {{ var name }}
{% endif %}
Omid Reza Heidari
  • 658
  • 12
  • 27
1

I faced a similar issue recently.

Basically I had a form A, upon submitting it another form B would show up, which contains some results + a form. Upon submitting B, i wanted to display some alert to user and keep user on B only.

The way I solved this, is by displaying the results in a <output> field, in B.

<output name="xyz" value="xyz">{{xyz}}</output>

And I used the same view for A->B and B->B. Now I just had to distinguish if the request is coming from A or B and render accordingly.

def view1(request):
    if "xyz" in request.POST:
        # request from B
        # do some processing
        return render(request, 'page.html', {"xyz":request.POST["xyz"]})
    else:
        # request from A
        res = foo() # some random function
        return render(request, 'page.html', {"xyz":res})

But this only works if form B is small and not that dynamic.

rkp768
  • 56
  • 4
0

If you are using a redirect after processing the POST to AppB, you can actually get away with calling the AppB method from the AppA method.

An Example:

def is_appa_request(request):
    ## do some magic.
    return False or True
is_appb_request = is_appa_request

def AppA(request):
    if is_appb_request(request):
       return AppB(request)
    ## Process AppA.
    return HttpResponseRedirect('/appa/thank_you/')

def AppB(request):
    if is_appa_request(request):
       return AppA(request)
    ## Process AppB.
    return HttpResponseRedirect('/appb/thank_you/')

This should yield a transparent experience for the end-user, and the client who hired you will likely never know the difference.

If you're not redirecting after the POST, aren't you worried about duplicate data due to the user refreshing the page?

Jack M.
  • 30,350
  • 7
  • 55
  • 67
  • It would be great if a simple solution like this worked. However, I need to display detailed results after processing the data. This would require sessions (as proposed by Matthew J Morrison) wouldn't it? – FunLovinCoder Jun 12 '10 at 06:14
  • 1
    You can do it one of three ways. #1, store the data in the database and pass the `pk` of the new entry when you redirect. #2, store the data in the `cache` backend, and pass the key again. #3, store it in the session. Any of these are perfectly normal for a web-app to do, even if it is temporary. If the form data is non-trivial to parse, it would also make the system faster if the output was already cached. – Jack M. Jun 14 '10 at 16:27
0

You can redirect with session using request.session["key"] as shown below:

# "views.py"

from django.shortcuts import redirect

def my_view(request): 
            # Here
    request.session["message"] = "success"
    return redirect("https://example.com")
# "index.html"

{{ request.session.message }} {# success #}
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129