It took me a while to get Ajax calls working with Angular and Django REST. This is what finally got it done.

NOTE: The implementation below is not really REST. It is more intended to illustrate some workarounds and custom methods. If the client can adhere to REST, a lot of the code below won’t be needed. See the Django REST tutorial for a simpler implementation.

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);
        });
    };
});
NOTE: There is a jQuery dependency to use $.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...

Leave a reply