I have a simple messaging system built in my Django project. Currently, a user needs to refresh the page to see the new messages. How do I add Ajax to load the messages asynchronously?
View:
def user_messages(request):
time_now = datetime.datetime.now()
user = request.user
if request.method == "POST":
sender = request.user
receiver_name = request.POST.get('msg_receiver')
receiver = User.objects.get(username=receiver_name)
msg_content = request.POST.get('msg_content')
Messages.objects.create(sender=sender, receiver=receiver, msg_content=msg_content)
inbox = Messages.objects.filter(receiver=user).order_by('-timestamp')
outbox = Messages.objects.filter(sender=user).order_by('-timestamp')
context = {'inbox': inbox, 'outbox': outbox, 'time_now': time_now}
return render(request, 'accounts/messages.html', context)
Messages.html:
<h6>Inbox</h6>
{% for message in inbox %}
<ul>
<li title="{{ message.sender.username }}">{{ message.sender.first_name }}: {{ message.msg_content }}
<button onclick="myFunction()">Reply</button>
</li>
<small>-{{ message.timestamp }}</small>
<hr>
</ul>
{% endfor %}
You can create a Javascript file in the static folder to do this.
As you are sending a POST request, you need to include the CSRF token in order to do this process in a secure manner. Check on Django's documentation for more details. This first function get the CSRF token from the cookies:
getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
let cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
This is the Ajax code for sending/receiving data, just vanilla Javascript. The same code would load the previous messages when the user loads the page for the first time, after x seconds (to display messages if someone else sends a message) or after the user sends a message:
requestAjax(data) {
// Gets the CSRF token using the previous function
let csrftoken = getCookie('csrftoken');
// Initiate new AJAX request
const request = new XMLHttpRequest();
request.open('POST', '/add-messages/', true);
// Set request header with CSRF token code
request.setRequestHeader('X-CSRFToken', csrftoken);
request.setRequestHeader('contentType', 'application/json; charset=utf-8');
// Callback function for when the request completes
request.onload = () => {
// The server returns a request object
const serverResponse = JSON.parse(request.responseText);
// The request object includes a 'success' key with a boolean value to indicate if the request was successful or not
if (serverResponse.success) {
// If 'success' is true, display the received messages
}
else {
// If 'success' is false, there is a server side error, display error message
}
}
// Sends msg_receiver and msg_content to server
request.send(JSON.stringify(data));
}
Related
currently when i submit the function first takes me to the api page where i have to click on post again for the results to update in the database then click on back button and refresh the page to see the updated data, it will be nice if the reduce button could update and display the data on the table without redirecting to the api page. Thank You, Please Help. trying to render the data here
#api_view(['POST'])
def sRd(request, pk):
sw = get_object_or_404(Swimmers,id=pk) # gets just one record
current_sessions = sw.sessions + 10
sw.sessions = current_sessions # updates just the one in memory field for sw (for the one record)
sw.save() # you may want to do this to commit the new value
serializer = SubSerializer(instance=sw, data=request.data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
return JsonResponse(data=serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
Want to render the data here on this page:
return render(request, 'accounts/modals/swimming/_vw_table.html', {'sw': sw})
The solution was to use Ajax asynchronous to be able to process data on the backend and display the results on the frontend using jQuery or any of the frontend framework. You must also include a csrf token or exempt it using #csrf_token by adding the plugin
from django.views.decorators.csrf import csrf_protect
<script type="text/javascript">
// CSRF TOKEN //
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
//Post Button Working With View Function sRd
$(document).ready(function () {
$("#post-form").submit(function (event) {
event.preventDefault();
$.ajax({
headers: {
'Content-type':'application/json',
'X-CSRFToken': csrftoken,
},
url: "{% url 'srd' srd.pk %}",
type: "POST",
data: $('#post-form').serialize(),
success: function (data) {
console.log(data);
}
}); // end ajax
});
});
</script>
I am building an ecom website and in order to implement an add_to_cart function I've done the following.
Clicking the add to cart button calls the javascript add_to_cart function that I wrote:
<button type="button" onclick = "add_to_cart({{ product.pk }})">Add to Cart</button>
This is the function:
function add_to_cart(product_pk) {
let url = '/add-to-cart/' + product_pk.toString()
$.ajax({
type: 'GET',
url: url,
processData: false,
contentType: false
})
}
the urls for this look like this:
path('add-to-cart/<str:product_pk>', views.add_to_cart, name='add_to_cart')
and finally my view looks like this:
def add_to_cart(request, product_pk):
cart = request.session['cart']
cart.append(product_pk)
request.session['cart'] = cart
context = {'length_of_cart': len(cart)}
return HttpResponse(content = dumps(context), content_type='application/json')
tldr: Click button, button calls js, js makes get request to url, url triggers view, logic in view adds product to cart.
I feel like this is pretty "hacky". Are there any security issues involved with what I've done here?
I feel like this is pretty "hacky". Are there any security issues involved with what I've done here?
A GET request is not supposed to have side-effects. Indeed, as the HTTP specifications say [w3.org]:
In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered “safe”.
You should make use of POST, PUT, PATCH or DELETE to make requests with side effects.
Django will automatically try to validate a CSRF token if you make a POST request. This to prevent a vulnerability that could result in a malicious JavaScript file that uses the credentials of the logged in user to make requests. Django (aims to) prevent this by using a CSRF token. You add such token to the POST request as is explained in the AJAX section of the documentation:
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
function add_to_cart(product_pk) {
let url = '/add-to-cart/' + product_pk.toString()
$.ajax({
type: 'POST',
url: url,
processData: false,
contentType: false,
headers: {'X-CSRFToken': csrftoken}
})
}
finally we should product the view to only accept POST requests with the #require_POST decorator [Django-doc]:
from django.views.decorators.http import require_POST
#require_POST
def add_to_cart(request, product_pk):
# …
I don't use a django form, we only process it with API and respond to the result.
I want to handle it without using #csrf_exempt.
When using a form, I know that you are using a tag, but in this case, it is difficult to write a tag. I can't get rid of csrf so I need help.
When receiving a request as a post, "CSRF token missing or incorrect." Appears. How can I solve this problem?
If this is a stateless API (i.e. you don't use cookies) you can safely disable CSRF as follows:
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def post(request):
return 'page'
If you need the csrf token check the csrf doc.
You can add the given code to a global js file and then reference it anywhere. I'm including the code here, but it is the same in the docs.
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
And then to get the csrf token:
const csrftoken = getCookie('csrftoken');
Here is the an example of how I use it in my fetch:
fetch('some_url', {
method: 'POST',
headers:{
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': csrftoken,
},
body: JSON.stringify({
some_key: some_var,
...
})
})
.then(response => {
jsonResponse = response.json();
status_code = response.status;
if(status_code != 200) {
alert('error');
} else {
alert('success');
}
})
.catch(error => {
console.log(error)
})
But make sure the csrf token is available in your template by including the csrf template tag {% csrf_token %}
When using {% url 'query' %} inside an AJAX get call is returning a string but when I put a static url it works properly.
I'm using Django-Filters and Django-rest-framework in installed apps.
url.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^query/$', 'my_app.views.app_function', name='query')
]
app.js
$(document).ready(function(){
// LOAD COOKIE
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function callServer () {
$.ajax({
type: 'GET',
url: "{% url 'query' %}",
success: function (json) {
console.log(json)
},
error: function(x, t, m) {
if(t==="timeout") {
alert("got timeout");
} else {
alert(t);
}
},
headers: {
'X-CSRFToken': csrftoken
}
});
}
$("#query").click(function () {
$('#sub').submit(function (e) {
e.preventDefault();
});
return callServer();
});
});
views.py
class AppFilter(django_filters.FilterSet):
class Meta:
model = Post
fields = ['first', 'second']
#api_view(['GET'])
def app_function(request):
qs = Post.objects.all()
f = AppFilter(request.GET, queryset=qs)
serializer = PostSerializer(f, many=True)
return Response(serializer.data)
forms.py
class QueryForm(forms.Form):
first = forms.TypedChoiceField(
widget=forms.Select,
choices=choice_dict1
)
second = forms.TypedChoiceField(
widget=forms.Select,
choices=choice_dict2
)
Any help before I burn the place?
Django would not be able to resolve {% url 'query' %} in JS file, since that's client side stuff. reverse url is resolved at the time of html file rendering.
What you can do is, pass that url as init() function of that JS module from html file:
<!-- In your Template file -->
<script>
$(function(){
app.init("{% url 'query' %}");
});
</script>
And export the app module from js file. Set that url as a variable, and use it in ajax call.
The problem is that your javascript file (app.js) is probably not the template. You don't show it, but I assume you have an HTML file that is loading the app.js file. The HTML file is where the template variables and such will get expanded. The simplest way to resolve this is to embed the javascript code into your HTML file inside a <script> tag.
There is a library for Flask called Flask-JSGlue that solves this problem and lets you use template variables in your javascript files, but I cannot find a similar library for Django.
I have written a very small example: a junit button which sends a POST request with a pair of values:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>jQuery UI Button - Default functionality</title>
<script src="{{STATIC_URL}}js/jquery-1.9.1.js"></script>
<script src="{{STATIC_URL}}js/jquery-ui-1.10.3.custom.js"></script>
<link rel="stylesheet" href="{{STATIC_URL}}css/jquery-ui-1.10.3.custom.css">
<script>
$(function() {
$( "button" )
.button()
.click(function( event ) {
var postdata = {
'value1': 7,
'value2': 5
};
$.post('', postdata); // POST request to the same view I am now
window.alert("Hello world!"); // To know it is working
});
});
</script>
</head>
<body>
<button>Submit</button>
</body>
</html>
So, the view is rendered when a GET request is sent to localhost:8000/button/, and when the button is pushed a POST request is also sent to localhost:8000/button/.
urls.py
from django.conf.urls import patterns, url
urlpatterns = patterns('',
url(r'^button/$', 'helloworld.views.buttonExample'),
)
views.py
def buttonExample(request):
print 'RECEIVED REQUEST: ' + request.method
if request.method == 'POST':
print 'Hello'
else: #GET
return render(request, 'buttonExample.html')
When the GET request is done, the view is displayed correctly and I can also read at Django console the lines:
RECEIVED REQUEST: GET <---- This line is because of my print
[28/May/2013 05:20:30] "GET /button/ HTTP/1.1" 200 140898
[28/May/2013 05:20:30] "GET /static/js/jquery-1.9.1.js HTTP/1.1" 304 0
[28/May/2013 05:20:30] "GET /static/js/jquery-ui-1.10.3.custom.js HTTP/1.1" 304 0
[28/May/2013 05:20:30] "GET /static/css/jquery-ui-1.10.3.custom.css HTTP/1.1" 304 0
...
And when the button is pushed, I can see:
[28/May/2013 05:20:34] "POST /register/ HTTP/1.1" 403 142238
But "RECEIVED REQUEST: POST" is never printed. Neither is "Hello". It seems like the urls.py is not serving the view when a POST arrived, because in Firebug I can see that POST status is 403 FORBIDDEN.
This is probably a silly newbie mistake, but I don't know what am I missing. I have read the django book chapter about advanced URLConf and Views, and it looks like it should work just by checking request.method value.
This is by design. Your POST data must contain csrfmiddlewaretoken value. You can get it from your cookies and then send it with POST requests. Details here. For your specific case, you can do this -
<script>
$(function () {
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
$("button")
.button()
.click(function (event) {
var postdata = {
'value1': 7,
'value2': 5,
'csrfmiddlewaretoken': csrftoken
};
$.post('', postdata); // POST request to the same view I am now
window.alert("Hello world!"); // To know it is working
});
});
</script>
You are receiving a 403 because of CSRF protection - you have not provided a token to protect yourself from attacks. The documentation tells you all you need to know.