Django REST framework and AngularJS


22 marca 2014


Tutorial prezentujący wykorzystanie Django REST framework oraz AngularJS. Repozytorium dostępne na GitHub: https://github.com/rasalom/drf-angularjs-tutorial

REST (Representational State Transfer) to wzorzec architektury oprogramowania zyskujący coraz większą popularność. Wykorzystując Django REST framework możemy szybciej tworzyć aplikacje W Django wykorzystujące ten wzorzec. Dokumentacja projektu jest dostępna pod adresem :http://www.django-rest-framework.org/ 

AngularJS jest frameworkiem języka JavaScript stworzonym przez Google. Narzędzie ma na celu ułatwiać budowanie aplikacji, które nie wymagają przeładowania strony. Dokumentacja projektu jest dostępna pod adresem http://www.angularjs.org/ 

Poniżej przedstawię przykład wykorzystania obu narzędzi. Cały projekt można pobrać z dostępnego repozytorium wykonując w git komendę:

git clone https://github.com/rasalom/drf-angularjs-tutorial.git

Aby uruchomić projekt wystarczy zainstalować wymagane pakiety w środowisku wirtualnym, skonfigurować poprawnie STATICFILES_DIRS a następnie uruchomić aplikację

pip install -r requirements.txt

Poniższy wpis prezentuje wszystkie kroki podczas tworzenia aplikacji.

1. Wirtualne środowisko, instalacja pakietów, utworzenie projektu

mkvirtualenv --distribute --no-site-packages drf
pip install Django
pip install djangorestframework
django-admin.py startproject core .
./manage.py startapp news

W settings.py dodajemy aplikacje "news" oraz "rest_framework". W katalogu aplikacji "core" tworzymy katalog "static" oraz dodajemy konfigurację do katalogu z plikami statycznymi

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, "static"),
    'ścieżka do katalogu static',
)

W katalogu static tworzymy dwa puste pliki "app.js" oraz "controller.js", które uzupełnimy później. W katalogu aplikacji "news" tworzymy katalog "templates", w ktorej będziemy trzymali szablony dla widoków. 
W "settings.py" dodajemy konfigurację dla Django REST framework

REST_FRAMEWORK = {
    # Use hyperlinked styles by default.
    # Only used if the `serializer_class` attribute is not set on a view.
    'DEFAULT_MODEL_SERIALIZER_CLASS':
        'rest_framework.serializers.HyperlinkedModelSerializer',

    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    ]
}

2. Tworzenie modeli, widoków, klasy dla serializacji

Tworzymy następujący model w aplikacji "news":

from django.db import models

class Category(models.Model):
    """
    Category model
    """
    name = models.CharField(max_length=255)
    slug = models.SlugField(unique=True)
    parent = models.ForeignKey('Category', blank=True, null=True)
    
    def __unicode__(self):
        return "%s" % self.name

W kolejnym kroku synchronizujemy bazę danych (w konfiguracji mamy domyślnie sqlite3).

./manage.py syncdb

Utwórzmy dwa rekordy z poziomu konsoli:

./manage.py shell 
>>> from news.models import Category
>>> a = Category()
>>> a.name="Kultura"
>>> a.slug="kultura"
>>> a.save()
>>> b= Category()
>>> b.name="Koncerty"
>>> b.slug="koncerty"
>>> b.parent=a
>>> b.save()
>>> Category.objects.all()
[<Category: Kultura>, <Category: Koncerty>]
>>> exit()

Wykorzystując Django REST framework musimy stworzyć klasę/funkcję, która będzie serializowała dane z modeli. W dokumentacji możemy znaleźć różne sposoby tworzenia (za pomocą funkcji, klas itp). Wykorzystałem opcję opartą na klasach, które serializują dane w oparciu o model. Tworzymy plik "serializers.py":

from rest_framework import serializers
from .models import Category

class CategorySerializer(serializers.ModelSerializer):
    tracks = serializers.RelatedField(many=True)

    class Meta:
        model = Category
        fields = ('id', 'name', 'slug', 'parent')

 

Tworzymy widoki, które będą odpowiedzialne za wyświetlanie danych.

from rest_framework import generics
from .models import Category
from .serializers import CategorySerializer
from django.views.generic import TemplateView


class CategoryList(generics.ListCreateAPIView):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer


class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    
class Homepage(TemplateView):
    template_name = "index.html"    
    
class CategoryListView(TemplateView):
    template_name = "category-list.html"    
    
class CategoryDetailView(TemplateView):
    template_name = "category-detail.html" 

Widoki "CategoryList" oraz "CategoryDetail" odposiadają za wyświetlenie danych wygenerowanych przez Django REST framework.
Widok "Homepage" to strona startowa projektu. Widoki "CategoryListView" "CategoryDetailView" będą służyły do pobrania szablonów dla danych pobieranych przez AngularJS. 
Musimy utworzyć plik "urls.py" w apikacji "news" oraz "zainkludować" go w "urls.py" projektu. W naszym przykładzie pliki bedą wyglądały następująco:

#core/urls.py
from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

from news.views import Homepage

urlpatterns = patterns('',
    url(
        regex = r'^$', 
        view = Homepage.as_view(),
        name = 'home'),
    url(r'^admin/', include(admin.site.urls)),
    url(r'^news', include('news.urls')),
)
#news/urls.py
from django.conf.urls import patterns, include, url
from rest_framework.urlpatterns import format_suffix_patterns

from .views import CategoryList, CategoryDetail,\
    CategoryListView, CategoryDetailView

urlpatterns = patterns('news.views',
    url(
        regex = r'^$', 
        view = CategoryList.as_view(),
        name = 'news-category-list'),
    url(
        regex = r'^/(?P[a-zA-Z0-9-]+)$', 
        view = CategoryDetail.as_view(),
        name = 'news-category-detail'),
                       
    url(r'^/templates/category-list.html', 
        view = CategoryListView.as_view()
        ),
    url(r'^/templates/category-detail.html', 
        view = CategoryDetailView.as_view()
        ),                   
                       
)

urlpatterns = format_suffix_patterns(urlpatterns)

Kod "urlpatterns = format_suffix_patterns(urlpatterns)" umożliwia późniejsze wyświetlenie danych przy użyciu rozszerzenia (.api lub .json).
Możemy uruchomić projekt i gdy to zrobimy pod adresem "http://127.0.0.1:8000/news.json" uzyskamy JSON z danymi o wszystkich dodanych kategoriach a pod adresem "http://127.0.0.1:8000/news.api" Djano REST framework wyświetli api dla danego modelu.
Podobnie jest z adresami "http://127.0.0.1:8000/news/kultura.api" oraz "http://127.0.0.1:8000/news/kultura.json", które wygenerują dane dla wybranego obiektu.

3. Tworzenie szablonów i wykorzystanie AngularJS

Tworzymy szablony w katalogu "templates" aplikacji "news": index.html, category-list.html, category-detail.html (odpowiednio: strona startowa, lista kategorii, szczegóły kategorii). W pliku "index.html" najważniejszymi elementami są:

... 
<html ng-app='restApp'>
...
{% load staticfiles %}
<script type="text/javascript" src="{% static 'app.js' %}"></script>
<script type="text/javascript" src="{% static 'controller.js' %}"></script>
...
<div ng-view></div>

"restApp" to nazwa aplikacji dla AnguarJS. Dołaczone pliki będą zawierały kod odpowiedzialny za wykorzystanie frameworka. W znaczniku "ng-view" będzie umieszczona zawartość pobrana przez AngularJS. W pliku "category-list.html" najaważniejszymi elementami są:

{% verbatim %} 
...
<ul class="categories">
    <li ng-repeat="category in categories | filter:query | orderBy:orderProp" 
class="thumbnail">
    <a href="#/news/{{category.slug}}">{{category.name}}</a>
    </li>
    </ul>
...
{% endverbatim %}

AngularJS w szablonach używa podobnej składni jak Django dlatego musimy wykorzystać blok "verbatim". W bloku tym znajduje się kod odpowiedzialny za pętlę po wszystkich kategoriach pobranych z news.json 
Szablon odpowiedzialny za wyświetlenie szczegółów kategorii jest podobny. Pełna zawartość szablonów dostępna w repozytorium na GitHub. 
W kolejnym kroku dodajemy funkcje frameworka AngularJS odpowiedzialne za pobranie danych z Django i umieszczenie ich w odpowiednich szablonach a następnie wygenerowanie w znaczniku "ng-view". 
Plik "app.js" definiuje kontrolery i odpowiedzialny jest za ustawienia routingu (w index.html oprócz angular.js dodaliśmy angular-route.js). W pliku "controller.js" definiujemy kontrolery odpowiedzialne za pobranie odpowiednich szablonów i danych. Aby zrozumieć więcej odsyłam do dokumentacji. Plik "app.js" zawiera następujące reguły:

'use strict';

/* App Module */

var restApp = angular.module('restApp', [
  'ngRoute',
  'restAppController'
]);

restApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/news', {
        templateUrl: 'news/templates/category-list.html',
        controller: 'CategoryListCtrl'
      }).
      when('/news/:categorySlug', {
        templateUrl: 'news/templates/category-detail.html',
        controller: 'CategoryDetailCtrl'
      }).
      otherwise({
        redirectTo: '/'
      });
  }]);

Jak można zauważyć definiuje on, że po wejściu na "/news" ma być uruchomiony kontroler CategoryListCtrl odpowiedzialny za pobranie listy kategorii i umieszczenie danych w szablonie "category-list" itd. Plik "controller.js" zawiera następujące reguły:

'use strict';

/* Controllers */

var restAppController = angular.module('restAppController', []);

restAppController.controller('CategoryListCtrl', ['$scope', '$http',
  function($scope, $http) {
    $http.get('/news.json').success(function(data) {
      $scope.categories = data;
    });
    
    $scope.orderProp = 'id';
    
  }]);

restAppController.controller('CategoryDetailCtrl', 
  ['$scope','$http','$routeParams',
  function($scope,$http, $routeParams) {
	
	$http.get('/news/'+ $routeParams.categorySlug +'.json').success(
        function(data) {
	$scope.category = data;
	});
  }]);

Poszczególny kontroler pobiera dane JSON i przekazuje je do dalszej obróbki.

Podsumowanie

Powyżej przedstawiłem przykład, który można rozbudować np. o uwierzytelnianie, edycję, zapis i usuwanie danych itd. Zachęcam do czytania dokumentacji oraz do pobrania kodu z repozytorium. 

Co zawiera blog?

Na blogu umieszczam wpisy dotyczące mojej pracy, zainteresowań. Głowna tematyka to programowanie oraz recenzje płyt oraz książek.

PostGIS + GeoDjango


Przykład wykorzystania przestrzennej bazy danych oraz frameworka GeoDjango.

Hello Nasty


Artwork By - Bill McMullen , Cey Adams
Już w ...

Twórzmy aplikacje wspólnie!


Grzegorz Tatara "Koma Software" - Tworzymy nowoczesne, bezpieczne oraz użyteczne ...

Raport sportowy #5


Zdjęcie: Semih Aydın, Unsplash

Inteligentne techno?


Fotografia: © Sony Music Entertainment (UK) Ltd.
Recenzja albumu "Leftism" ...

Virtualenv - odrębne środowiska pracy


Tworząc aplikacje w języku Python instalujemy różne pakiety. Co zrobić ...