It took me a while to get Ajax calls working with Angular and Django REST. This is what finally got it done.
First, both frameworks use templates and they both use the same default syntax {{ }}
. There are a couple ways to make them both play together. The easiest way is to surround Angular code with Django’s {% verbatim %}
tags.
{% verbatim %}
<tr ng-repeat="dbconn in dbconns">
<td>{{ dbconn.name }}</td>
<td>{{ dbconn.schemaname }}</td>
<td>{{ dbconn.hostname }}</td>
<td>{{ dbconn.port }}</td>
<td>{{ dbconn.username }}</td>
<td><button ng-click="remove()">DELETE</button></td>
</tr>
</table>
{% endverbatim %}
When defining your Angular app, there are some necessary configurations to use. I’ll let the comments do the explaining.
var app = angular.module('myappname', ['ngCookies']).
config([
'$httpProvider',
function($httpProvider) {
// Change content type for POST so Django gets correct request object
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
// 2 reasons: Allows request.is_ajax() method to work in Django
// Also, so 500 errors are returned in responses (for debugging)
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}]).
run([
'$http',
'$cookies',
function($http, $cookies) {
// Handles the CSRF token for POST
$http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
}]);
The bottom line in the code above has a dependency on the angular-cookies.js
file (in Angular’s standard bundle). So don’t forget to load that script before you load your Angular app.
<script src="{% static "myappname/js/angular-cookies.js" %}"></script>
JSON Data Exchange Example
Here is an example Ajax call method on the Angular Controller.
app.controller('MyAppController', function ($scope, $http){
/* Post example */
$scope.add = function(formdata) {
// Add requesttype to data object
formdata['requesttype'] = 'ADD';
$http(
{method: 'POST',
url: '',
data: $.param(formdata)
}).
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
$scope.dbconns = data['current_conns'];
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
alert("Status: " + status + ", Data: " + data);
});
};
});
$.param()
views.py
The code below shows an example of using a custom ['requesttype']
property to handle different types of Ajax calls in the same function.
from django.shortcuts import render
from django.http import HttpResponse
from managedb.models import DatabaseConnection
from managedb.serializers import DatabaseConnectionSerializer
from rest_framework.renderers import JSONRenderer
def example_page(request):
# Draw initial page
if request.method == 'GET':
return render(request, 'homepage.html', {})
# Handle posts
elif request.method == 'POST':
# Make sure request is Ajax
if request.is_ajax():
# Handle DELETE requests
if request.POST['requesttype'] == 'DELETE':
# Retrieve and then delete object by id
current_conn = DatabaseConnection.objects.get(id=request.POST['id'])
current_conn.delete()
# Get updated list of connections
context = {}
# Use custom ModelSerializer to get data from QuerySet
context['current_conns'] = DatabaseConnectionSerializer(DatabaseConnection.objects.all()).data
# Return response as JSON
return HttpResponse(JSONRenderer().render(context), content_type='application/json')
# Handle ADD requests
elif request.POST['requesttype'] == 'ADD':
# TODO - Do stuff here
pass
else:
# TODO - Handle wrong request types here
pass
else:
# TODO - Handle wrong request types here
pass
return HttpResponse('error')
serializers.py
And then just to show the Class definition of the serializer used above.
from rest_framework import serializers
from managedb.models import DatabaseConnection
class DatabaseConnectionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = DatabaseConnection
# Optional to include certain fields
# Here we are not returning the password field so it should never be exposed
fields = ('id', 'name', 'hostname', 'port', 'username', 'schemaname')
Troubleshooting – Force CSRF Cookie
A final note on CSRF Cookies when going live. When I first pushed the project live I encountered an issue where the cookie was not being sent and ended up throwing 403 Forbidden errors on my Ajax calls. I didn’t investigate the issue too much, but the solution I found was to add the @ensure_csrf_cookie
decorator to the main view function serving the core Django template.
from django.views.decorators.csrf import ensure_csrf_cookie
@ensure_csrf_cookie
def runcustomquery(request):
# view function body...