Django QueryDict empty with request.POST but populated in request.GET - python

Short version: On a django site, I can grab values from request.GET but not request.POST in response to a request from Twilio. I suspect it has something to do with csrf, but I'm not really sure how to debug the problem. Details below.
Long version:
I am helping a friend with a project where we are running a medical survey over SMS using the Twilio REST API. I had a domain and a very bare-bones django-built site on that domain, which I had built really just to better familiarize myself with django, so we're using that.
We're collecting SMS responses to our survey and as part of the Twilio API, it sends any response to our number to a url specified under the account, so we have the response targeting something like the following:
...mydomain.com/some_page/another_page/
The Twilio request then looks something like the following:
...mydomain.com/some_page/another_page/?AccountSid=###SOME_LONG_ACCOUNT_SIDE&From=%2BPHONE_NUMBER&Body=bla+BLA+bla+BLA&SmsSid=##MESSAGE_ID_KEY&SmsMessageSid=##MESSAGE_ID_KEY&FromCity=Santa+Cruz&FromState=California...
Working Code
I am testing that the incoming request has our AccountSid inside it (compared with the value in the database) and in my views.py for the app, I have something that looks like the following (and this works):
from our_app import TwilioAccount
our_account = TwilioAccount.objects.get(id=1)
def twilio_response(request):
assert request.GET.get('AccountSid', None) == our_account.account_sid
## log the incoming request to the database under survey responses...
Non-Working Code
If I log-in to our Twilio account and switch the request method to POST, and then I switch all of my data collecting to request.POST, the above assert statement fails. Further debugging reveals that my QueryDict is empty under POST, POST: {}, so there is no key value grabbed.
I thought this maybe was because POST under django requires a csrf_token, but I figured checking for the AccountSid was fairly good, so I imported csrf_exempt and wrapped the above function with that:
#csrf_exempt
def twilio_response(request):
assert request.POST.get('AccountSid', None) == our_account.account_sid
## log the incoming request to the database under survey responses...
AssertionError: ...
This does not work with the exact same request: the QueryDict is empty.
Questions:
1) Is there something else I need to do to make my #csrf_exempt work? Alternate question: is this a terrible and dumb way to do this? How do people usually satisfy this requirement when working with other company's APIs and not actual, logged-in users?
1a) Instead of making it csrf_exempt, I could just keep it as a GET request, knowing that it's still checking all incoming requests against our account_sid. Should I do that or is that a really naive way to do it?
2) I am eager to learn the best way to do this: should I build a django form and then route the request to my form and test validity and clean the data that way? If so, can anyone give me a loose outline on what the view/form would look like (complete with csrf_token) when there's not going to be a template for the form?

Matt from the Twilio Developer Evangelist team here.
1) Wrapping your twilio_response function with #csrf_exempt to remove the Django CSRF token check is the right way to go here. Twilio does not generate a Django CSRF token. Instead, there are other ways to validate POSTs are coming from Twilio, such as signature validation with the X-Twilio-Signature header. See the Twilio security docs for details.
1a) Using the GET request is convenient for testing and debugging, but POST should be used in production. GET requests do not have a body according to the HTTP spec, so the results are passed in the query string. If the parameters are too large, for example with a text message that has a maximum length of 1600 characters, the query string in the URL could exceed the maximum length of a URL and potentially cause issues when you handle the string.
2) Django forms are a good way to go for this use case, particularly a ModelForm that leverages your existing Model used to save the response. For example, your ModelForm could
look something like the following if you are saving your data to a TwilioMessage model:
from django.forms import ModelForm
from .models import TwilioMessage
class MessageForm(ModelForm):
pass
class Meta:
model = ReactionEvent
# include all fields you're saving from the form here
fields = ['body', 'to', 'from_', 'signature',]

In my option,Have you open DEBUG(= True) in settings.py?
And in the DEBUG mode, you can raise An Exception(raise Exception('')),And then you can see the Environment Varialbe like url, GET,POST.etc....in you page
OR return HttpResponse(request.POST.dict())
to see the post variable.
use crsf_exempt is a right way when post

Related

Why view functions require a request parameter In Django?

Example from Django documentation:
def index(request):
return HttpResponse('<h1>hellworld!</h1>')
def detail(request, question_id):
return HttpResponse("Question: %s" % question_id)
Since the request argument is never used, why should I include it in each function signature?
The purpose of a web server is to reply to HTTP requests (In simple terms). Django being a web framework generates responses depending on the request that it receives.
The business logic of handling the requests is done by Django Views, it is their purpose. This is why Django supplies the request to the view function, so the code can access the request data and choose what actions it should take and which response it should send back (even though that specific view doesn't make use of it).
The decision of requiring the view to receive the request as an argument is an implementation decision made by Django developers, making it part of the interface the view has with the rest of the "system". Other frameworks might choose making it available globally and not suplying it to the views/controllers directly, but the merits of one or another approach is other discussion.
Many of the examples in Django's documentation (and any documentation, really) are necessarily contrived. They're simple because the complexity of a real example would take away from the point that's being made.
In real use cases, you'll frequently want access to the request object. You may want to know which User is attached to the request (request.user) or whether the request is a GET or a POST (request.method).
You can also inspect the headers (request.headers), decode the JSON object sent with a POST request (json.loads(request.body)), etc.
Those are just examples. In real world a programmer would want to find request GET arguments, decode JSON POST body, inspect headers, get user or session data, etc.

How to use just one url pattern in django to handle GET, POST and kwargs in HttpResponseRedirect?

I know that after dealing with a post request we should not render the page but should use HttpResponseRedirect. I wanted to send some data back with this redirect. I went through multiple SO but I have few questions.
Using message framework.
Good until you are just sending text messages. If I want to send objects or dictionaries back to template what should I use?
I came to know we can use *args or **kwargs.
when I tried to use kwargs in HttpResponseRedirect, it complain about url matching. So does that mean I need to create another URL which accepts kwargs.
Lets see one example here:-
I am saving some data for which I have this url.
url(r'^add-event/$', views.add_event, name='add_event')
on GET request of this page, template is loaded which shows form to save the data.
On POST request of this page, form data is saved.
Now if everything is fine in POST request, I can send success message back with query string or with message framework. But if there is some error while saving data, I want to send whole data back to the template to render it into the form as it is so that user need not to fill the form again.
Here comes my question. If I use args or kwargs like below :
return HttpResponseRedirect(reverse("tenant:add_event kwargs=data))
where data is dictionary containing lots of data (not simple text message), How do I form the matching url?
Is it possible to make just one URL for GET and POST request and to handle the above scenario?
With regards to your question about the messages framework take a look at this question here describing how to pass a dictionary using the messages framework.
You can also use the django sessions framework in order to save the POST data in a session.In that way the redirected page will have access to the data submitted by the Http POST.

Submitting 2 forms at once

I must submit two forms at once and I don't want javascript. So I think I can post the form to myself, do my work and then post it on to the third-party payment provider. This is to make the order appear in our datastore. The manual says how to submit a form over HTTP POST but I don't know how to direct the user's browser to this page. Is there a way? Instead of posting the form directly to the third party I thought doing something like this:
def post(self):
import urllib
#do my work
params = urllib.urlencode({'spam': 1, 'eggs': 2, 'bacon': 0})
f = urllib.urlopen("http://www.thirdparty.com/cgi-bin/query", params)
#direct the user to the reponse page but how?
It will work to submit the form but the user must be take to the page. Is there a way to achieve this?
Thanks for any help
There is something you can do like this:
Get the post form, do stuff on your server, and then return a redirect page that contains a form with same params but action to the remote target.
Then, you can use little js to submit the form automatically, and give something like "Not redirecting? click here" to the user.
So it's not working as what you expected, and it used js too, but I did not find any better way to handle this job. Hope this can help in any way.
You could use 307 to redirect, which preserves all the POST parameters (you'll need to make sure the POST params your servlet uses are the same as the ones your remote server uses).
self.response.set_status(307)
self.response.headers['Location'] = "http://www.thirdparty.com/cgi-bin/query"
However, not all browsers implement 307 correctly. See here: Response.Redirect with POST instead of Get?
That response is old, so maybe more browsers handle 307 correctly now.

Better way of passing form parameters into hidden form in Pyramid

In a previous question, I was trying to figure out the right strategy for to passing data between forms in Pyramid. Based on the answer I received, I decided the approach of using a hidden form.
I started implementing this and think there must be a better way of passing along the data. Specifically, passing parameters through the url results in a tuple that is messy to parse.
I want it to be general enough to not to know what parameters the form has and also it needs to handle file fields as well.
How I'm currently attempting to pass the form data to the confirmation page:
#view_config(renderer="templates/derived/load/error.mak", route_name='process_model_route')
def process_model(self):
#processing logic and validaton, failiure in validation sends user to error.mak
return HTTPFound(route_url('confirm_model_route', self.request, fparams=self.request.POST))
Route: config.add_route('confirm_model_route', 'rnd2/model/confirm/*fparams')
#view_config(renderer="templates/derived/confirm/model.mak", route_name='confirm_model_route')
def confirm_model(self):
form_dict = self.request.matchdict['fparams']
#need to decode and pass to template
return dict({'load_route':load_route, 'form_dict':form_dict})
The confirm/model.mak template would contain the hidden form.
The idea with this method is:
Client visits page.
Server renders the form.
Client fills in form and POSTs to URL.
Server renders a new page that contains a hidden form with all of the data it just received in the POST.
Client POSTs to a URL, confirming the submission.
Server persists the data from the hidden form and redirects.
Now depending on usability, it's up to you to decide how many different URLs you actually want here and how many views in Pyramid. You have to think about what happens with invalid data?
Notice in the outline above, once the user POSTs the form to a URL, that URL must return the confirmation page containing a hidden form. If you try to redirect the user to a confirmation page instead, you must persist the data somehow, either in a session or through the hack you showed in your example (shoving all of the data into the GET). The second solution is very bad because it abuses the true purpose of GET in HTTP.
There is also the convention that every POST should result in a redirect to avoid a client submitting the form multiple times. With this in mind you might consider the simple solution of rejecting POSTs that do not have a "confirmed" flag and simply setting the "confirmed" flag in javascript after prompting the user. This allows you to keep your form handling logic simple.
If you don't want to rely on javascript and you don't want to persist the form data in a session, then you run into the issue of not redirecting after the first POST but other than that it should be simple from the outline above.

Code samples for Django + SWFUpload?

Does anyone have any simple code samples for Django + SWFUpload? I have it working perfectly in my PHP application but Django is giving me headaches.
Unfortunately I can't give you any very detailed code samples, but I have quite a bit of experience with working with SWFUpload + Django (for a photo sharing site I work on). Anyway, here are a few pointers that will hopefully help you on your quest for DjSWF happiness :)
You'll want to use the cookies plugin (if of course you are using some sort of session-based authentication [like django.contrib.auth, and care who uploaded what).
The cookies plugin sends the data from cookies as POST, so you'll have to find some way of getting this back into request.COOKIES (process_request middleware that looks for a settings.SESSION_COOKIE_NAME in request.POST on specific URLs and dumps it into request.COOKIES works nicely for this :)
Also, remember that you must return something in the response body for SWFUpload to recognize it as a successful upload attempt. I believe this has changed in the latest beta of SWFUpload, but anyway it's advisable just to stick something in there like 'ok'. For failures, make use of something like HttpResponseBadRequest or the like.
Lastly, in case you're having trouble finding them, the uploaded file is in request.FILES :)
If you have anything perplexing I haven't covered, feel free to post something more detailed and I'll be happy to help.
Django version of the samples for SWFUpload:
http://github.com/naltimari/django-swfupload-samples/tree/master
So long uploadify. Great idea but it is just buggy, especially on Windows.
The following is my Django-specific implementation for fixing this issue (i.e. my uploads were failing in Firefox with a 302 Redirect).
In my initial view that generates the page with the uploader on it, I looked at the cookies and found sessionid
ipdb> self.request.COOKIES
{'csrftoken': '43535f552b7c94563ada784f4d469acf', 'sessionid': 'rii380947wteuevuus0i5nbvpc6qq7i1'}
When I looked at what was being posted in the SWFUploadMiddleware (when using Firefox), I found that the sessionid was not being set.
In my intial view that generates the page that contains the upload handler, I added the sessionid to the context.
context['sessionid'] = self.request.session.session_key
In my swfuploader settings, I added sessionid to the post-params option as follows:
post_params: {... "sessionid": "{{ sessionid }}" ...},
Now, when I looked in the SWFUploadMiddleware, I could see the sessionid being posted, and my uploads started working if Firefox.
ipdb> request.POST
<QueryDict: {... u'session_id': [u'rii380947wteuevuus0i5nbvpc6qq7i1'],...}>
For completeness, my SWFUploadMiddleware looks like this...
from django.conf import settings
from django.core.urlresolvers import reverse
class SWFUploadMiddleware(object):
def process_request(self, request):
if (request.method == 'POST') and (request.path == reverse('upload_handler')) and request.POST.has_key(settings.SESSION_COOKIE_NAME):
request.COOKIES[settings.SESSION_COOKIE_NAME] = request.POST[settings.SESSION_COOKIE_NAME]
# http://stackoverflow.com/questions/6634666/403-forbidden-error-on-swfupload-and-django
# Fix for problem uploading images (403 error) in Firefox 20 and others
if request.POST.has_key('csrftoken'):
request.COOKIES['csrftoken'] = request.POST['csrftoken']

Categories