Перевод: "Руководство: используем AngularJS вместе с Django"

May 20, 2014 14:34 · 1408 words · 7 minute read django angular перевод tutorial

Данный материал сильно устарел и оставлен лишь для истории

Оригинал: ‘Tutorial: Using AngularJS with Django’ by Glyn Jackson

Я надеялся написать простенькое руководство по использованию Angular вместе с Django. В том, что получилось, есть заслуга Red Bull. Прошу прощения за несколько скомканный конец!

Прочитав несколько статей, посвящённых Angular+Django, мне показалось, что все изобретают свои велосипеды. Пример, приведённый в этой статье, сыроват, но он должен показать как я использую эту связку.

Модели

Давайте зададим простую модель.

/jobs/models.py

class Job(models.Model):
    name = models.CharField(max_length=50)
    description = models.TextField(null=True, blank=True)

Пока ничего особенного - создали простую модель для описания работ.

REST API (TastyPie)

AngularJS работает с веб-сервисами, так что нам нужен какой-либо способ представления только что созданной модели.

У Django есть множество библиотек, позволяющих реализовать RESTful API, одна из них - TastyPie. Она невероятно мощная, но проста в настройке и использовании. Однако, это моё личное предпочтение, т.к. того же результата можно добиться и аналогичными библиотеками, в частности Django REST framework. Выбор остаётся за вами, можете даже попробовать свою реализацию API написать, но я буду использовать TastyPie.

Если вы не знакомы с TastyPie, то можете почитать документацию. Не буду углубляться в детали установки, будем считать, что вы её уже подключили и готовы двинуться дальше.

Для начала нужно создать ресурс для объектов Jobs. TastyPie использует концепцию “ресурсов” - посредников между конечным пользователем и объектами, в данном случае моделями Job.

Начнём с создания соответствующего ресурса:

class JobResource(ModelResource):
    class Meta:
        queryset = Jobs.objects.all()
        resource_name = 'job'
        allowed_methods = ['post', 'get', 'patch', 'delete']
        authentication = Authentication()
        authorization = Authorization()
        always_return_data = True

Если мне не изменяет память, TastyPie предлагает хранить их в файле api.py вашего приложения, но это совсем не обязательно.

То, что происходит в этом фрагменте кода, выходит за рамки данного руководства. Хочу лишь обратить ваше внимание на то, что JobResource наследуется от ModelResource. Скорее всего вы будете использовать TastyPie вместе с Django ORM; при таком наследовании многие методы API уже реализованы за вас.

Однако, можно работать не только с моделями. Для этого надо пронаследоваться от Resource и переопределить необходимые методы. Таким образом можно уйти от использования ORM, например, при работе с noSQL базами данных, как это описано в документации.

Раз уж мы создали модель Job, то давайте дадим пользователю способы взаимодействия с ней. Далее нам нужен способ подключения ресурса к фактическим URL, который будет использоваться в AngularJS. Адаптируем наш URLconf, добавив туда путь, по которому будет доступно API:

from tastypie.api import Api
from .apps.your_app.api import JobResource

v1_api = Api(api_name='v1')
v1_api.register(JobResource())

urlpatterns = patterns('',

     (r'^api/', include(v1_api.urls)),
)

Атрибут ‘resource_name’, определённый в JobResource, указывает на конечную точку в url. Теперь у нас есть API для работы с ресурсом, указывающим на Job. Запустите локальный сервер и перейдите по адресу http://127.0.0.1:8000/api/v1/job/?format=json.

Вот так вот легко и непринуждённом мы получили API для моделей Job.

Формы

Прежде чем погрузиться в AngularJS нам нужно создать форму Django для модели Job. Она позволит в будущем редактировать объект в одностраничном приложении. А, собственно, почему именно в Django?

Одна из заповедей этого фреймворка: “Don’t repeat yourself (DRY)”. Так что не стоит создавать HTML форму для Angular, а потом ещё и в Django - пусть последний делает всю работу. К тому же у вас наверняка есть ещё свои формы, которые нужно преобразовывать в формат, понятный Angular. Установите django-angular - это ещё один классный пакет, который облегчает всем жизнь.

Цитата: “Django-Angular представляет собой набор утилит, которые облегчают интеграцию Django с AngularJS путём предоставления повторно используемых компонентов.”

Опять же, я не собираюсь здесь вдаваться в подробности установки и настройки. Достаточно сказать, что одна из возможностей этой библиотеки - использование форм Django для валидации в AngularJS. Объединив его с пакетом crispy forms, мы получим мощное решение всё-в-одном - “вот за что я люблю Django и его сообщество”.

from .app.your_app.models import Job
from .apps.djangular.forms import NgFormValidationMixin, NgModelFormMixin, AddPlaceholderFormMixin

class JobForm(NgModelFormMixin, forms.ModelForm):
    """
    Job Form with a little crispy forms added! 
    """
    def __init__(self, *args, **kwargs):
        super(JobForm, self).__init__(*args, **kwargs)
        setup_bootstrap_helpers(self)

    class Meta:
        model = Job
        fields = ('name', 'description',)

def setup_bootstrap_helpers(object):
    object.helper = FormHelper()
    object.helper.form_class = 'form-horizontal'
    object.helper.label_class = 'col-lg-3'
    object.helper.field_class = 'col-lg-8'

Погружение в AngularJS

Для простоты создадим 3 новых шаблона со следующей структурой:

templates
   jobs/index.html
   jobs/new.html
   base.html

Здесь предполагается, что вы уже установили и настроили приложение Job. Базовый шаблон будет выглядеть как-то так:

/jobs/base.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.2/css/bootstrap.min.css" rel="stylesheet">

    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.js"></script>
    <script src="/angular-ui-router.min.js"></script>
    <script type="text/javascript" src="http://cdn.jsdelivr.net/restangular/latest/restangular.js"></script>

</head>
<body>
   {% block content %}{% endblock content %}
   {% block extra_javascript %}{% endblock extra_javascript %}
</body>
</html>

Django-Angular предоставляет несколько чудесных тегов для шаблонизатора, которые вставляют необходимый javascript. Я рекомендую использовать сеть доставки контента (CDN) для загрузки, собственно, AngularJS. Это дает очевидное преимущество по местоположению и пропускной способности.

Теперь нужно создать шаблон базовой страницы, с которого будет начинаться наш проект. Этим будет файл index.html - главная страница нашего одностраничного приложения, которая в дальнейшем будет использоваться для CRUD.

/jobs/index.html

{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="container content" ng-app="JobApp">
    <div ui-view >Loading...</div>
</div>
{% endblock content %}
{% block extra_javascript %}
<script src="{{ STATIC_URL }}/javascript/app.js"></script>
{% endblock extra_javascript %}
 /javascript/app.js

var app = angular.module('JobApp', [
    'ui.router',
    'restangular'
])

app.config(function ($stateProvider, $urlRouterProvider, RestangularProvider) {
    // For any unmatched url, send to /route1
    $urlRouterProvider.otherwise("/");
    $stateProvider
        .state('index', {

            url: "/",
            templateUrl: "/static/html/partials/_job_list.html",
            controller: "JobList"
        })

       .state('new', {

            url: "/new",
            templateUrl: "/jobs/job-form",
            controller: "JobFormCtrl"
        })
})

app.controller("JobFormCtrl", ['$scope', 'Restangular', 'CbgenRestangular', '$q',
function ($scope, Restangular, CbgenRestangular, $q) {

}])// end controller

Представленные выше шаблон и js достаточно просты и унаследованы от базового шаблона. Правда, есть несколько новых атрибутов, с которыми придётся разобраться.

Первый - ng-app=‘JobApp’. Без него AngularJS ничего не будет делать. Эта директива сообщает ему какой элемент является корневым для приложения. Всё, что вы добавите внутрь этого тега, будет считаться частью шаблонов AngularJS.

Далее рассмотрим скрипт, который включён в index.html. Он называется app.js и объявляет модуль angular. Здесь под модулем подразумевается набор функций, которые будут запущены, когда приложение “загрузится”.

var app = angular.module('JobApp', [

Строка выше объявляет модуль под названием ‘JobApp’. В index.html мы уже дали ссылку на него через атрибут ng-app=‘JobApp’. Также тут говорится, что мы хотим управлять всем, что находится внутри тега с этим атрибутом.

Фактически, вы можете установить ng-app на любом элементе в DOM. Например, если нужно вынести что-то за область видимости Angular, то можно написать как-то так:

<h2>I am not inside an AngularJS app</h2>
<div ng-app="embeddedApp">
  <h3>Inside an AngularJS app</h3>
</div>

app.config в app.js задаёт роутинг URL’ов. AngularJS поддерживает этот механизм через сервис $route, который определён в ядре фреймворка, но он не самодостаточен и имеет некоторые ограничения.

Один из подключаемых модулей - AngularUI Router ‘ui.router’. Это другой плагин к AngularJS, который организован вокруг состояний, имеет опциональные маршруты, которые могут быть подключены через поведения (behaviour).

В рамках этого руководства мы реализуем только состояние ‘новый’, но вы можете включить множество своих, и, я надеюсь, вы уже догадываетесь как это сделать. Вы даже можете добавить поведение по умолчанию, когда ни одно состояние не обнаружено:

 $urlRouterProvider.otherwise("/");
    $stateProvider
        .state('index', {

            url: "/",
            templateUrl: "static/html/somepage.html",
            controller: "SomeController"
        })

По всем вопросам рекомендую обратиться к документации после того, как дочитаете данное руководство.

Последний элемент в index.html надо понимать как ‘ui-view’. Это тоже часть AngularUI Router. Директива ui-view сообщает $state где разместить ваш шаблон, например, templateUrl: “/job/new”.

Последний шаблон, который мы создадим - /jobs/new.html. Туда поместим базовую форму, которую описали ранее с помощью Django-Angular.

{% load crispy_forms_tags %}
{% crispy JobForm %}
<button type="button" class="btn btn-default"  ng-click="submitJob()">Create</button>

Теперь надо бы связать представление и URL с формой.

/jobs/views.py

from .forms import JobForm

class JobFormView(TemplateView):
    template_name = "jobs/new.html"

    def get_context_data(self, **kwargs):
        context = super(JobFormView, self).get_context_data(**kwargs)
        context.update(JobForm=JobForm())
        return context

/jobs/urls.py

from django.conf.urls import url
from django.conf.urls import patterns

from .views import JobFormView

urlpatterns = patterns('',

                        url(r'^job-form/$',
                           login_required(JobFormView.as_view()),
                           name='job_form'),

)

Открыв в браузере страницу http://127.0.0.1:8000/job/#new, вы увидите форму в нашем одностраничном приложении.

Заключительным шагом будет отправка данных формы при нажатии submitJob. Для этого нужно изменить контроллер, в примере ниже используется restangular.

Цитата: “Restangular - сервис AngularJS, который упрощает генерацию GET, DELETE и UPDATE запросов. Он идеально подойдёт для любого веб-приложения, которое работает с RESTful API”.

app.controller("JobFormCtrl", ['$scope', 'Restangular', 'CbgenRestangular', '$q',
function ($scope, Restangular, CbgenRestangular, $q) {

   $scope.submitJob = function () {
      var post_update_data = create_resource($scope, CbgenRestangular);
      $q.when(post_update_data.then(
                        function (object) {
                            // success!
                        },

                        function (object){
                            // error!
                            console.log(object.data)
                        }

                    ))
                }

}])// end controller

app.factory('CbgenRestangular', function (Restangular) {
        return Restangular.withConfig(function (RestangularConfigurer) {
            RestangularConfigurer.setBaseUrl('/api/v1');
        });
    })

populate_scope_values = function ($scope) {
    return {name: $scope.name, description: $scope.description };
},

create_resource = function ($scope, CbgenRestangular) {
var post_data = populate_scope_values($scope)
    return CbgenRestangular.all('job').post(post_data)
},

Куда двигаться дальше?

Тема слишком объёмна, чтобы разобрать её в одном посте. Следующим шагом рекомендую пройти уроки на egghead, на мой взгляд это лучшее продолжение обучения.