Bonus 1: Django + Angular JS

It seems like everybody who learns Django wants to learn how to get it working with Angular JS. The problem with Django + Angular JS is the Django Templating System seems to break Angular’s Templating System.

Therefore, the trick to getting Django + Angular JS and any other Javascript library to work with Django is by using a special Django block that prevents Django from interpreting the rest of the template. If this doesn’t make a whole lot of sense now, read on because we are going to get Django up and running with Angular JS!!

Add Angular JS Library

First things first, download AngularJS library. I’m using Angular 1.5.x for this project. Then, add the ENTIRE unzipped directory into the the /static/ folder.

Now, you’ll add have to add the following to the bottom of our settings.py.

    STATICFILES_DIRS = (
        os.path.join(BASE_DIR, "static"),
    )

Next, in our app level urls.py file, we need to add the static folders to our URLs.

polls/urls.py

    from django.conf.urls import url
    from django.conf import settings
    from django.conf.urls.static import static

    from . import views, api_views


    urlpatterns = [
        # ... All of our urls ...
    ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

First add a Base Template

Next, create a new file. I called it base.html. Put this file in with the rest of our template files.

polls/templates/polls/base.html

    {% load staticfiles %}
    <html>
      <head>
        <title>Angular + Django</title>
        <script src="{% static 'angular/angular.min.js' %}"></script>
        <script src="{% static 'angular/angular-route.min.js' %}"></script>
        <script src="{% static 'app.js' %}"></script>
      </head>
      <body ng-app="pollsApp">
        <div ng-controller="PollsController">
          <div ng-view>
          </div>
        </div>
      </body>
    </html>

Also, we’ll have to add some stuff to our index.html file.

polls/templates/polls/index.html

    {% extends 'polls/base.html' %}

    <!-- the rest of the file -->

Since, we didn’t add a {% raw %}{% block content %}{% endblock %}{% endraw %} to our base.html file, the index.html file will just load our base.html template. There is another way to do this. You can modify the code to implement this other way for homework.

Add Verbatim Block

This is the secret sauce to using AngularJS with Django. It’s very simple. Simply modify our base.html file.

polls/templates/polls/base.html

    {% load staticfiles %}
    <html>
        <head>
            <title>Angular + Django</title>
            <script src="{% static 'angular/angular.min.js' %}"></script>
            <script src="{% static 'angular/angular-route.min.js' %}"></script>
            <script src="{% static 'app.js' %}"></script>
        </head>
        <body ng-app="pollsApp">

          {% verbatim %}
            <div ng-controller="PollsController">
              <div ng-view>
              </div>
            </div>
          {% endverbatim %}

        </body>
    </html>

The {% raw %}{% verbatim %}{% end verbatim %}{% endraw %} block will prevent the Django Templating system from interpretting anymore Template tags in our Angular templates. This is definitely the secret sauce to using Javascript libraries.

Convert Old Templates to Angular JS Templates

Now, we need to set up our Angular JS Templates. These will be slightly similar to our Django Templates. Put these templates in the static/templates directory.

/static/templates/index.html

    <ul>
      <li ng-repeat="question in polls">
        <a href="#/polls/{{question.id}}">{{ question.question_text }}</a>
      </li>
    </ul>

    <p ng-if="polls.length == 0">No polls are available.</p>

/static/templates/detail.html

    <h1>{{ question.question_text }}</h1>

    <div ng-repeat="choice in question.choices">
        <label>
          <input type="radio"
                 data-ng-model="selected_choice.choice"
                 ng-value="{{choice.id}}"  />
          {{ choice.choice_text }}
        </label><br />
    </div>
    <input type="button" ng-click="addVote()" value="Vote" />
    </form>

/static/templates/results.html

    <h1>{{ question.question_text }}</h1>

    <ul>
    <div ng-repeat="choice in question.choices">
        <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes == 0 || choice.votes > 1 ? "s" : "" }}</li>
    </div>
    </ul>

    <a ng-href="/polls/#/polls/{{question.id}}">Vote again?</a>

These templates are in our /static folder because Angular JS needs direct access to these files. We can’t reach them from polls/templates/polls directory because that folder can only be accessed serverside and AngularJS is is clientside.

Create the Angular Routes

Next, we need to create our AngularJS application. So, create a file called app.js in the static directory.

/static/app.js

    var pollsApp = angular.module('pollsApp', [
      'ngRoute'
    ])

    angular.
      module('pollsApp').
      config(['$routeProvider',
        function config($routeProvider) {

          $routeProvider.
            when('/', {
              templateUrl: '/static/templates/index.html',
              controller: 'PollsController'
            }).
            when('/polls/:question_id/results', {
              templateUrl: '/static/templates/results.html',
              controller: 'QuestionController'
            }).
            when('/polls/:question_id', {
              templateUrl: '/static/templates/detail.html',
              controller: 'QuestionController'
            }).
            otherwise('/');
        }
      ]);

These routes are the same routes as our Django Polls application, but since Javascript is controlling the URL history and retrieving the templates, interpretting the AngularJS template on the fly and pushing the HTML to the DOM seamlessly, it gives us a really cool Single Page Application in a very MVC Design sort-of-way.

Each of our routes contains a controller. The controller gets called when you go to each route. So, let’s create those now.

Create the AJAX Requests

The first controller is PollsController. Let’s take a look at what it looks like.

/static/app.js

    pollsApp.controller('PollsController',
      function PollsController($scope, $http) {
        $scope.polls = null;

        $http({
          method: 'GET',
          url: '/polls/api/questions',
        }).then(function (response) {
          $scope.polls = response.data;
        });
    });

This controller keeps a collection of polls. And, we make a AJAX request to /polls/api/questions and puts the returned data into our polls variable. That’s all this does. There is a really cool way to prevent the AJAX request from happening everytime you go to this route. You should figure this out as an exercise.

The next controller is our QuestionController.

/static/app.js

    pollsApp.controller('QuestionController',
      function QuestionController($scope, $http, $routeParams, $location) {
        $scope.question_id = $routeParams.question_id;
        $scope.question = null;
        $scope.selected_choice = {
          choice: 0
        };

        $http({
          method: 'GET',
          url: '/polls/api/questions/'+$scope.question_id
        }).then(function (response) {
          $scope.question = response.data;
        })

        $scope.addVote = function () {
          $http({
            method: 'POST',
            url: '/polls/'+$scope.question_id+'/vote/',
            data: "choice="+$scope.selected_choice.choice
          }).then(function (response) {
            $location.url('/polls/'+$scope.question_id+'/results');
          })
        }
      })

This is very complex controller because it’s doing double duty. It gets called when we go to the detail route and the results route. This is very inefficient as we are making too many AJAX requests. As homework, you should look into how to prevent all these requests from happening.

When, you click the “vote” button, it will call the function addVote() which will make a POST request to /polls/:question_id/vote/ and adds a vote to one of the choices.

The problem with POST requests is that same problem we’ve had in the past about our CSRF_TOKEN. In Angular it’s pretty easy to add the token to each POST request.

/static/app.js

    var pollsApp = angular.module('pollsApp', [
      'ngRoute'
    ]).config(function ($httpProvider) {
      $httpProvider.defaults.xsrfCookieName = 'csrftoken';
      $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
      $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
    });

Now, we can check out our ENTIRE Angular Application.

/static/app.js

    var pollsApp = angular.module('pollsApp', [
      'ngRoute'
    ]).config(function ($httpProvider) {
      $httpProvider.defaults.xsrfCookieName = 'csrftoken';
      $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
      $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
    });

    pollsApp.controller('PollsController',
      function PollsController($scope, $http) {
        $scope.polls = null;

        $http({
          method: 'GET',
          url: '/polls/api/questions',
        }).then(function (response) {
          $scope.polls = response.data;
        });
    });

    pollsApp.controller('QuestionController',
      function QuestionController($scope, $http, $routeParams, $location) {
        $scope.question_id = $routeParams.question_id;
        $scope.question = null;
        $scope.selected_choice = {
          choice: 0
        };

        $http({
          method: 'GET',
          url: '/polls/api/questions/'+$scope.question_id
        }).then(function (response) {
          $scope.question = response.data;
        })

        $scope.addVote = function () {
          $http({
            method: 'POST',
            url: '/polls/'+$scope.question_id+'/vote/',
            data: "choice="+$scope.selected_choice.choice
          }).then(function (response) {
            $location.url('/polls/'+$scope.question_id+'/results');
          })
        }
      })

    angular.
      module('pollsApp').
      config(['$routeProvider',
        function config($routeProvider) {

          $routeProvider.
            when('/', {
              templateUrl: '/static/templates/index.html',
              controller: 'PollsController'
            }).
            when('/polls/:question_id/results', {
              templateUrl: '/static/templates/results.html',
              controller: 'QuestionController'
            }).
            when('/polls/:question_id', {
              templateUrl: '/static/templates/detail.html',
              controller: 'QuestionController'
            }).
            otherwise('/');
        }
      ]);

Put Everything Together

Now, it’s your turn. I’ve just shown you how to create an Angular JS app inside of a Django Application using Django REST Framework.

Homework:

  1. Implement the steps above.
  2. Figure out how to prevent more than necessary AJAX requests.
  3. Download my sample code to check your work
  4. As always, email me with any questions.

Learn Django REST Framework FREE!

Get started learning Django REST Framework today.

Each email will contain a full length lesson that will have you mastering Django REST Framework in no time!

Join today!

Powered by ConvertKit