7

I have the following code

def ajax_login_request(request):
   try:
      request.POST[u'login']
      dictionary = request.POST
   except:
      dictionary = request.GET
   user = authenticate(username = dictionary[u'login'], password = dictionary[u'password'])
   if user and user.is_active:
      login(request, user)
      result = True
   else:
      result = False
   response = HttpResponse(json.dumps(result), mimetype = u'application/json')
   return response

which is being called via ajax. I'm a noob and this is from an example in a book. Unfortunately the version of Django I'm using throws a CSRF error on this. I've done the other CSRF bits, but I don't know how to change the HttpResponse bit to a render call. I do not want to use CSRF_exempt, because I have no idea when that is appropriate. Can someone please provide me the equivalent render call for the HttpResponse above.

Thanks

Superdooperhero
  • 7,584
  • 19
  • 83
  • 138

2 Answers2

7

Ok, I'm going to re-draft this answer so you understand where I'm coming from. The CSRF middleware works like this:

You make request   -------> request hits csrf --(invalid/no token)--> render 403 
                             middleware      
                                   |
                             (valid token)
                                   |
                                  \ /
                             Call view 
                                   |
                                  \ /
                            middleware sets 
                            csrf cookie
                                   |
                                  \ /
                            Response appears

In other words, if you are seeing a 403 csrf page, your view was never called. You can confirm this by sticking a spurious print statement in the view and watching the output from runserver when you make your request.

To solve this, you either need to disable csrf (not good) or use one of the ajax methods available to you. If the required token is passed in your view will actually be executed.

The reason your view is not called is to prevent the action from the forged site from actually ever taking place - for example, if you denied the template at response time the user would already be logged in. The same behaviour occurs with the function decorators.

As for the middleware setting the cookie, that does not alter or depend on the render function at all - this sets the HTTP header Cookie: ... in the response. All responses in Django are HttpResponse objects until it finally converts them to output; render functions are helpers, but that's not what's causing your problem here.

Edit I'll convert what you've got to a render call. You could do this:

return render_to_response(`ajax_templates/login_response.html`, 
                          {'loginresponse': json.dumps(result)})

Where ajax_templates/login_response.html is just:

{% loginresponse %}

That's it. HttpResponse has a main default argument which is the string to return (literally, the html of the web page); that's what you're doing initially. render_to_response and render are shortcuts to this which do this:

render_to_response called ----> open template asked for --> substitute arguments 
                                                                      |
                                                                     \ /
django instructs web server   <--- return this from view <-- create HttpResponse 
      to send to client                                          object
  • I know all that, but my point is I don't know how to write the render (I have found render_to_response does not work with csrf unless you do lots more ugly code) version of this call. So I'm afraid your answer does not help me at all. – Superdooperhero Nov 30 '11 at 14:28
  • @Superdooperhero the problem is the other end - the request is being denied at the top of the function if you like because the incoming request doesn't have a valid token. You either need to turn of csrf (which isn't so good), or add csrf to each Ajax request. I'll add a link to the answer, 2 secs. –  Nov 30 '11 at 14:45
  • I did add csrf, by adding the crsf middleware and by using {% csrf_token %} in my template. Now the only thing remaining is to use render instead of HTTPResponse. The CSRF works fine for my other views which use render. – Superdooperhero Nov 30 '11 at 14:50
  • @Superdooperhero I've gone for a different approach - does it make sense now? –  Nov 30 '11 at 15:00
  • I'm afraid I don't understand any of what you explained in your new post. Any chance you can just humour me and give me a render (or even render_to_response) version of my HTTPResponse call; which is what I asked for initially. – Superdooperhero Nov 30 '11 at 15:06
  • CSRF mechanism has not been used correctly. For POST forms, you need to ensure:
    • The view function uses RequestContext for the template, instead of Context.
    – Superdooperhero Nov 30 '11 at 15:18
  • @Superdooperhero added; if that doesn't clear things up a little, feel free to create a [chat room](http://chat.stackoverflow.com/) and I'll explain it to you in more detail. –  Nov 30 '11 at 15:18
  • As you can see above, the error message as traced by Firebug also states that I should be using RequestContext instead of Context, which is why I want to use render instead of HTTPResponse – Superdooperhero Nov 30 '11 at 15:20
  • Afraid I just got 20 experience points an hour ago an the faq doesn't immediately tell me how to create a chat room or even join one – Superdooperhero Nov 30 '11 at 15:23
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/5463/discussion-between-ninefingers-and-superdooperhero) –  Nov 30 '11 at 15:23
7

To make your original code work, you need to get a RequestContext object and pass it along with your response, something like this:

from django.http import HttpResponse
from django.template import RequestContext, Template

def ajax_login_request(request):
   # ...

   # This bit of code adds the CSRF bits to your request.
   c = RequestContext(request,{'result':json.dumps(result)})
   t = Template("{{result}}") # A dummy template
   response = HttpResponse(t.render(c), mimetype = u'application/json')
   return response

Do read up on the CSRF documentation as you might run into strange errors if you don't understand all the ways CSRF is "wired" in your app. The page also has a javascript snippet to make sure CSRF cookies are sent with your ajax requests if you are sending them without a form.

You can also use the render_to_response() shortcut, but you would need to have an actual template to load (in your case, you don't need a template, hence the "dummy" template in my example).

Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284
  • Thanks, your response is what I was looking for. Unfortunately I still have my CSRF woes after this. I'm assuming this is due to the Javascript snippet you mention, but sadly I don't understand a word of that snippet. I'll post a new question. – Superdooperhero Nov 30 '11 at 18:59
  • I have a problem with this solution because using the Template() function converts the quotes to HTML entities which outputs malformed JSON which gets treated as plain text by the browser. – mariachimike Nov 07 '12 at 08:52
  • One way for the solution above to work is to use the following instead `{% autoescape off %}{{result}}{% endautoescape %}` – mariachimike Nov 07 '12 at 09:25
  • You can also pass `autoescape=False` on the `Context` – Burhan Khalid Jan 26 '14 at 10:08