When {% csrf_token %} may be inconvenient? - django

Django 1.10
In the documentation we can read:
While the above method can be used for AJAX POST requests, it has some
inconveniences: you have to remember to pass the CSRF token in as POST
data with every POST request.
https://docs.djangoproject.com/es/1.10/ref/csrf/#ajax
The mentioned "above method" is about adding {% csrf_token %} to forms.
And it is said in the documentation that more convenient may be:
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
Well, I can't catch in which case {% csrf_token %} may be inconvenient.
I tried:
<form action="" method="post">{% csrf_token %}
function post_create(){
$.ajax({
method: "POST",
url: '{{ object.get_frame_date_create_url }}',
data: $("#object_form").serialize(),
success: add_post_data,
error: fail,
});
}
Seems to be working. The result of $("#object_form").serialize() looks like this:
"csrfmiddlewaretoken=NFgXO1gsHJbi0N1IUb5ZPQ2rno2RGBrRR8kxboewWDC63sm2hxlvXtUtyviCSoZ1&date=2015-01-01&precision=F&frame=1"
What must I remember here? How can I forget to pass csrfmiddlewaretoken? It is just an ordinary field of the form. Hidden from users, but not from the programmer.
Is it supposed that there are cases when it is necessary to manually touch every field in the form and send it separately. I can't imagine that.
Your comment would be highly appreciated: do I understand correctly that $("#object_form").serialize() is fine? And in which case it is more convent to use ajaxSetup?

There's absolutely no reason why you need an HTML form to send a POST request. For example, both the upvote/downvote buttons and the favorite button on this page are simple buttons that send an AJAX POST request without ever touching an HTML form.
Submitting a form asynchronously is just one of the many use-cases for AJAX. If that is the only way you use AJAX, the code from the docs may not give you much of an advantage. However, if you use it for one the many other use-cases, it will be much more convenient.

Related

Django CSRF failure, using React forms

I'm having a problem with CSRF with Django and React.
I have read through the already high number of questions around this, as well as the django docs naturally. I have tried every possible combination of different things that should address the issue but am still struggling with it.
Firstly I tried to create a register page, but when I POST to register/ I get CSRF cookie not set, 403.
I have gone so far as disabling the CSRF middleware [bad I know, just trying to get somewhere] and I am getting 405s, method not allowed [attempting to post]. I just thought maybe this is something someone has run into before or sounds familiar and could give some guidance?
I have tried:
- adding the decorator #csrf_exempt,
- adding the CSRF to the header of a request,
- attaching the whole cookie,
- attaching a hidden form field with the token.
I am using this seed project: https://github.com/Seedstars/django-react-redux-base if anyone wants to have a look, I've done a bit in React, but not a lot on the Django side, so it isn't far off what's there
You should not disable the csrf check in django.
Instead in your form/template simply do
{% csrf_token %} not {{ csrf_token }}
It will print a hidden form element with value assigned to your csrf token already.
If you are using ajax, you can simply set your ajax headers globally as:
$.ajaxSetup({
beforeSend: function (xhr, settings) {
// this time double brackets
xhr.setRequestHeader("X-CSRFToken", "{{csrf_token}}");
}
});
if you are using fetch then:
fetch('some/url/here', {
method: 'GET',
headers: {
'X-CSRFToken': window.CSRF_TOKEN // or pass it in your own way
}
}).then(function (response) {
return response.json()
})
These are pretty much all the ones i can think of.
Hope this helps.

403s resubmitting a form after login

I get a 403 under the following repro steps:
While logged out, try to submit a Django form that generates a validation error
Log in or signup for a valid account
Using the browser, go BACK to the page with the validation error
Resubmit the form
Results: 403 error. This is most likely expected behavior, however I'm looking for a more graceful way to handle this. Is there a good way to catch this and resubmit the form as the logged in user?
I have seen this question asked in the context of many frameworks, and the only elegant solution is JavaScript.
With JavaScript, you could store the input values in localStorage. Then on successful form submit event, clear those values. If the form is loaded with those values existing in localStorage (the form submission returned 403, and the user went back to the form page), then automatically populate the form with the values.
Its not really that complex to implement, just more work. I believe there are JS libraries based on this idea...
Give all your form elements a classname. In the example I will use store-data. This can be set in forms.Widget.attrs if you define your form in django, or just with the class attribute on input elements if you write your own html.
On submit, add an item named formData to localStorage. formData is a JS object mapping form field element ids with the classname from above to the element values.
If the form is submitted and processed as valid, on the redirect page remove formData from localStorage with localStorage.removeItem().
When loading the form page (this would be where the user went back to the form after a 403), if formData exists in localStorage then load the values into the form fields.
Here is an example form with this implemented:
<form name="myForm" action="{% url 'myapp:form_submit' %}" onsubmit="return storeData()">
<label>Name: </label>
<input type="text" class="store-data" id="inputName" />
<label>Description: </label>
<textarea class="store-data" id="textareaDescription"></textarea>
<input type="submit" />
</form>
<script>
function storeData() {
var elements = document.getElementsByClassName("store-data");
var formData = {};
// store element ids and values in formData obj
for (var i = 0; i < elements.length; i++) {
formData[elements[i].id] = elements[i].value;
}
// store formData to localStorage as string
localStorage.setItem('formData', JSON.stringify(formData));
}
// if the localStorage item has already been set, then the user tried to submit and failed
if (localStorage.getItem('formData')) {
formData = JSON.parse(localStorage.getItem('formData'))
// set all the form elements to the values that were stored when the user tried to submit
for (var key in formData) {
document.getElementById(key).value = formData[key];
}
}
</script>
And on the redirected success page, be sure to remove the formData item. Otherwise, any time the user goes back to the form the values will be loaded into the fields. (I suppose this may be a desired behavior, but I doubt it.)
<script>
localStorage.removeItem('formData');
</script>
Well, yes, it's expected behaviour. When you login, new csrf_token is generated. And when you navigate back to page with validation error, it still contains old csrf_token in <input type="hidden" name="csrfmiddlewaretoken" value="old_token" />. So you submit form with invalid csrf_token and get 403 error.
I can suggest two options for you (none of them I like)
Disable new csrf_token generation on login. Just place request.META['CSRF_COOKIE_USED'] = False after login(request, user) in your loggin view.
Disable csrf protection via decorator for your single view, or globally by removing csrf middleware from your settings.py.

Getting checkbox value in flask

I'm trying to poll the state of checkboxes (this is done in JS every three seconds). This solution returns "None" (see code below); Both printouts (with 'args' and with 'form') return "None". I'm expecting True/False, depending on the checkbox's boolean state.
index.html:
{% extends "layout.html" %}
{% block content %}
<div id="results" class="container">{{data_load|safe}}</div>
<input id='testName' type='checkbox' value='Yes' name='testName'>
{% endblock %}
and the relevant flask app snippet:
#app.route('/', methods = ['GET', 'POST'])
def index():
return render_template('index.html', data_load=timertry())
#app.route('/_timertry', methods = ['GET', 'POST'])
def timertry():
print request.args.get('testName')
print request.form.get('testName')
return "some html going into 'results' div.."
The JavaScript polling function (adapted from here):
$(document).ready(function() {
$.ajaxSetup({cache : false});
setInterval(function() {
$('#results').load('/_timertry?' + document.location );
}, 3000); // milliseconds!
});
This should be simple enough, but none of the SO solutions I looked into (e.g., using jquery, adapting the flask/ajax example, etc.) worked.
EDIT: following mark's suggestion (including the javascript) and adding
print request.values in index.html returns (seen on the console in Aptana):
CombinedMultiDict([ImmutableMultiDict([]), ImmutableMultiDict([])])
Clearly, the request seems empty. The post request is logged (when checkbox is pressed) as:
127.0.0.1 - - [03/Oct/2013 00:11:44] "POST /index HTTP/1.1" 200 -
Any ideas here?
Your javascript does the following every three seconds...
$('#results').load('/_timertry?' + document.location);
In other words, you are using the jQuery load() function. The documentation for that function states that this will, in essence, call an HTTP get request to the URL you provide as the parameter (something like /_timertry?http://www.example.org. In other words, a GET request will be called to /timertry?http://www.example.org, which will be handled by Flask's timertry method.
When you have an HTTP form, and you click the "Submit" button, the browser does some magic to push all of the values to the server in the request. When you just do a simple AJAX request, none of that happens for you. Instead, you need to explicitly state what you want to be passed as data to the server (although there are plugins to help you with "post the values of an HTML form using AJAX").
So, because at no point did you do anything in your Javascript to retrieve the value of checkbox to include it into the AJAX request, the AJAX request has no values specifying that the checkbox was checked. You would need to have jQuery check if the box is checked...
var isChecked = $('#testName').is(':checked');
# Now do something with isChecked...
From what I can tell, however, you are sort of misusing HTTP: the load function will make a GET request, and you probably want something to happen as a request of the request. You probably want to make it do a POST request instead (see here and here). Also, you mentioned that you're looking for something to post when a value is changed. Putting this together, you can do something like this...
// Ensure that the function is called when the DOM is ready...
$(function() {
// Register an event that should be fired when the checkbox value is changed
$('#testName').change(function() {
var data = { isChecked : $(this).is(':checked') };
$.post('/', data);
});
})
In this case, we have an event that is called when a checkbox is checked, and that event causes us to make a POST request to the server.
I'm going to answer this question which was found in the comments of the question
"which becomes a question of how to submit a form without a 'submit' button.."
So it is very possible to submit a value when a user clicks on the button
{% block content %}
<form id="target" action="YourViewName">
<div id="results" class="container">{{ data_load|safe }}</div>
<input id='testName' type='checkbox' value='Yes' name='testName'>
</form>
{% endblock %}
$( "#results" ).click(function() {
$( "#target" ).submit();
});
If you want to stay on the same page, however, you're going to need to use an ajax call to pass the data back rather then use a standard submit, however This tutorial covers that topic fairly well. but a basic change to send the data back would look like
$( "#results" ).click(function() {
var request = $.ajax({
type: "POST",
url: "/YourViewName",
data: {'input_value':$('#testName').val()},
dataType: "html"
}).done(function(msg) {
// I don''t know what you want to do with a return value...
// or if you even want a return value
}
});
and the flask would look like
#app.route("/YourViewName")
def example():
list_name = request.args.get("input_value")
#if you don't want to change the web page just give a blank return
return ""

Unable to jQuery $.post data to a view in django due to CSRF

Before posting this i've tried every solution method posted online, including solutions on Stackoverflow and Django. (I think the reason for error perhaps is due to the fact that i'm on a newer verison of jQuery and django and most solutions are dated, using jQuery 1.9 and django 1.5.1)
Here are some URL's to solutions that don't work:
Django CSRF check failing with an Ajax POST request
How to use $.post with django?
https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
Any help would be appreciated. Another possibility for error is the fact that i'm not actually sure where to place these snippets exactly. So far i've pasted everything inside the jquery on document load, i've also tried pasting the code in the very start of the .js file. (My javascript code is fragmented in chunks, some are seperate .js files and some are inline with the html being rendered with django context, so any solutions with "{{ csrftoken }}" are bad.
Thanks!!
The CSRF token only gets set if it's present in the template or if the view is decorated with ensure_csrf_cookie(). Putting {% csrf_token %} in index.html will make it apply for all your pages.
From the docs:
The CSRF token is also present in the DOM, but only if explicitly included using csrf_token in a template.
...
If your view is not rendering a template containing the csrf_token template tag, Django might not set the CSRF token cookie. This is common in cases where forms are dynamically added to the page. To address this case, Django provides a view decorator which forces setting of the cookie: ensure_csrf_cookie().
Can you try this:
$.ajax({
type: "POST",
url: '{% url "some_url_which_accepts_post" %}',
data: {'csrfmiddlewaretoken': '{{csrf_token}}', 'comment_id':1},
success: function(data, textStatus){
//something
},
});

jquery-autocomplete does not work with my django app

I have a problem with the jquery-autocomplete pluging and my django script. I want an easy to use autocomplete plugin. And for what I see this (http://code.google.com/p/jquery-autocomplete/) one seems very usefull and easy. For the django part I use this (http://code.google.com/p/django-ajax-selects/) I modified it a little, because the out put looked a little bit weired to me. It had 2 '\n' for each new line, and there was no Content-Length Header in the response. First I thought this could be the problem, because all the online examples I found had them. But that was not the problem.
I have a very small test.html with the following body:
<body>
<form action="" method="post">
<p><label for="id_tag_list">Tag list:</label>
<input id="id_tag_list" name="tag_list" maxlength="200" type="text" /> </p>
<input type="submit" value="Submit" />
</form>
</body>
And this is the JQuery call to add autocomplete to the input.
function formatItem_tag_list(row) {
return row[2]
}
function formatResult_tag_list(row) {
return row[1]
}
$(document).ready(function(){
$("input[id='id_tag_list']").autocomplete({
url:'http://gladis.org/ajax/tag',
formatItem: formatItem_tag_list,
formatResult: formatResult_tag_list,
dataType:'text'
});
});
When I'm typing something inside the Textfield Firefox (firebug) and Chromium-browser indicates that ther is an ajax call but with no response. If I just copy the line into my browser, I can see the the response. (this issue is solved, it was a safety feature from ajax not to get data from another domain)
For example when I am typing Bi in the textfield, the url "http://gladis.org/ajax/tag?q=Bi&max... is generated. When you enter this in your browser you get this response:
4|Bier|Bier
43|Kolumbien|Kolumbien
33|Namibia|Namibia
Now my ajax call get the correct response, but there is still no list showing up with all the possible entries. I tried also to format the output, but this doesn't work either. I set brakepoints to the function and realized that they won't be called at all.
Here is a link to my minimum HTML file http://gladis.org/media/input.html
Has anybody an idea what i did wrong. I also uploaded all the files as a small zip at http://gladis.org/media/example.zip.
Thank you for your help!
[Edit]
here is the urls conf:
(r'^ajax/(?P<channel>[a-z]+)$', 'ajax_select.views.ajax_lookup'),
and the ajax lookup channel configuration
AJAX_LOOKUP_CHANNELS = {
# the simplest case, pass a DICT with the model and field to search against :
'tag' : dict(model='htags.Tag', search_field='text'),
}
and the view:
def ajax_lookup(request,channel):
""" this view supplies results for both foreign keys and many to many fields """
# it should come in as GET unless global $.ajaxSetup({type:"POST"}) has been set
# in which case we'll support POST
if request.method == "GET":
# we could also insist on an ajax request
if 'q' not in request.GET:
return HttpResponse('')
query = request.GET['q']
else:
if 'q' not in request.POST:
return HttpResponse('') # suspicious
query = request.POST['q']
lookup_channel = get_lookup(channel)
if query:
instances = lookup_channel.get_query(query,request)
else:
instances = []
results = []
for item in instances:
results.append(u"%s|%s|%s" % (item.pk,lookup_channel.format_item(item),lookup_channel.format_result(item)))
ret_string = "\n".join(results)
resp = HttpResponse(ret_string,mimetype="text/html")
resp['Content-Length'] = len(ret_string)
return resp
You probably need a trailing slash at the end of the URL.
Also, your jQuery selector is wrong. You don't need quotes within the square brackets. However, that selector is better written like this anyway:
$("input#id_tag_list")
or just
$("#id_tag_list")
Separate answer because I've just thought of another possibility: is your static page being served from the same domain as the Ajax call (gladis.org)? If not, the same-domain policy will prevent Ajax from being loaded.
As an aside, assuming your document.ready is in your Django template, it would be a good idea to utilize the {% url %} tag rather than hardcoding your URL.
$(document).ready(function(){
$("input[id='id_tag_list']").autocomplete({
url:'{% url my_tag_lookup %}',
dataType:'text'
});
});
This way the JS snippet will be rendered with the computed URL and your code will remain portable.
I found a solution, but well I still don't know why the first approach didn't worked out. I just switched to a different library. I choose http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/. This one is actually promoted by jQuery and it works ;)