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...

Django has some very useful error pages built in that can help with a lot of debugging. Unfortunately sometimes you need to dig into the code a little more at run time to track down errors, etc.

Here are some additional methods for debugging Django…

Using pdb

pdb is part of the Python standard library. It allows you to set breakpoints, inspect objects, etc.

In Django, start your local server by invoking pdb

python -m pdb manage.py runserver

In your code, add this line right before the line you want to set a breakpoint on

import pdb; pdb.set_trace()

At the breakpoint, you will see the command line terminal stop and a (Pdb) prompt will show up. From here you can enter an object’s name to inspect it. You can also run simple functions to see what they output.

For example:

(Pdb) request.is_ajax()
True

Just type c to continue on from a breakpoint.