en fr

AJAX requests with Django and AngularJS

Posted on 2014-10-25 in Programmation

In this tutorial, I will explain how to submit a Django form with an AJAX request thanks to AngularJS. First, we need to prepare our app - I don't precise imports and I assume that you know in which file to write the code. First, the model:

class Card(models.Model):
    content = models.TextField()
    date = models.DateTimeField(auto_now_add=True, auto_now=True)
    category = models.ForeignKey('Category')

    def __str__(self):
        return self.content

The form:

class CardForm(forms.ModelForm):
    class Meta:
        model = Card

The view:

def add_card(request):
saved = False

if request.method == 'POST':
    form = CardForm(request.POST, request.FILES)
    if form.is_valid():
        card = Card()
        card.content = form.cleaned_data['content']
        card.category = form.cleaned_data['category']
        saved = True
        card.save()
else:
    form = CardForm()

return render(request, 'board/add_card.html', locals())

And the template:

<!DOCTYPE html>
{% load staticfiles %}
<html ng-app="ajax">
<head>
  <meta charset="UTF-8" />
   <script src={% static "angular.js" %} type="text/javascript" ></script>
   <script type="text/javascript" src={% static "angular-cookies.js" %}></script>
   <script src={% static "controllers.js" %} type="text/javascript" ></script>
 </head>
 <body>
<form action="{% url "board.views.add_card" %}" ng-controller="MyFormCtrl"
       method="post" name="form">{% csrf_token %}
       <p>
         <label for="id_content">Content:</label>
         <textarea cols="40" id="id_content" name="content" rows="10" ng-model="card.content"></textarea>
       </p>
         <p><label for="id_category">Category:</label> <select ng-model="card.category" id="id_category" name="category">
         <option value="" selected="selected">---------</option>
         <option value="1">Backlog</option>
         <option value="2">Done</option>
         <option value="3">In progress</option>
         </select>
         <button ng-click="submit($event)">Submit</button>
       </p>
 </form>
 </body>
 </html>

As any Angular app, it contains a ng-app and a ng-controllers tags to link your page with the JS code. I also added {% load staticfile %} to use django's static directive.

The form must be completely present in the template - you can't use {{ form.as_p }} - in order to correctly map its element with the model in Angular:

  • The Textaera with ng-model="card.content".
  • The options with ng-model="card.category".
  • The submit button with ng-click="submit($event)". $event is required to avoid a page reload when the form is submitted.

And now, the JS code:

var nameSpace = angular.module("trello", ['ngCookies']);

nameSpace.controller("MyFormCtrl", ['$scope', '$http', '$cookies',
function ($scope, $http, $cookies) {
    $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
    // To send the csrf code.
    $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;

    // This function is called when the form is submitted.
    $scope.submit = function ($event) {
        // Prevent page reload.
        $event.preventDefault();
        // Send the data.
        var in_data = jQuery.param({'content': $scope.card.content, 'category': $scope.card.category, 'csrfmiddlewaretoken': $cookies.csrftoken});
        $http.post('/add', in_data)
          .success(function(out_data) {
            // Reset the form in case of success.
            $scope.card = angular.copy({});
        });
    }
 }]);

In order to use the csrf code - which is stored in a cookie, you need to use the ngCookie plugin. You should be able to understand the rest of the code. In case of trouble, leave a comment.

This code requires jQuery. I have not found a way to correctly pass the argument with just Angular. The only method I have is:

var in_data = 'content=' + $scope.card.content + '&category=' + $scope.card.category + '&csrfmiddlewaretoken=' + $cookies.csrftoken;

Note: The AJAX request can also be written like this:

$http({
    url: '/add',
    method: 'POST',
    data: in_data
}).success(function(out_data) {
    $scope.card = angular.copy({});
});