Debian, AngularJS, Django, Python

Делюсь опытом в описанных технологиях. Блог в первую очередь выполняет роль памяток для меня самого.

Работа с REST API в Angular JS

Введение

Так как я последнее время сильно увлечён фреймворком Angular, я стараюсь писать на нём настолько хорошо, насколько могу. Одна из последних интересных задач, с которыми мне пришлось столкнуться - работа с ресурсами. Для своего проекта я решил использовать так же Django REST Framework, т.к. видел о нём много положительных отзывов.

Ресурсом в данном контексте я понимаю экземпляр объекта, созданного Angular с помощью модуля $resource. Он позволяет легко обмениваться данными с сервером, полностью реализуя клиентскую часть архитектуры REST. В статье будут рассмотрены популярные решения и моё собственное.

Обзор REST

Рассматривать решение проблемы будем на примерах. Допустим, имеется новостной сайт. Каждая новость - это объект со своими свойствами. Кроме того, на сайте есть возможность оставлять комментарии. Итак, мы уже можем создать три взаимосвязанных класса объектов:

Пользователь
  • Логин
  • Дата регистрации
  • ФИО (для удобства объединим в одно поле, но на самом деле лучше сделать 3 отдельных)
  • Дата рождения
  • Блокировка
Новость
  • Автор - ссылка на пользователя
  • Дата публикации
  • Заголовок
  • Анонс
  • Полный текст
  • Опубликована (если нет, значит, находится в состоянии черновика)
  • Количество просмотров (автоматически увеличивается с каждым уникальным пользователем)
Комментарий
  • Автор - аналогично новости, ссылка на пользователя
  • Текст комментария
  • Новость - ссылка на описанную выше модель
  • Дата написания
  • Рейтинг - обновляется пользователями (автоматически вычисляется на сервере на основе анализа данных таблицы с плюсами и минусами)

Итак, у нас уже три сущности. Спроектируем API. Тут, в принципе, ничего сложного. Весь API уместится в несколько строк:

# Пользователи
/api/user/
/api/user/:id/
/api/user/:id/news/
/api/user/:id/comments/

# Новости
/api/news/
/api/news/:id/
/api/news/:id/comments/

# Комментарии
/api/comment/
/api/comment/:id/

Как видно, API получился очень простым. Сделаем допущение, что по ссылкам без указания версии используется последняя версия, а конкретную можно использовать, например, таким образом:

/api/v1/user/

Так же отметим, что здесь под :id имеется ввиду реальный id записи. Такое обозначение вдвойне удобно, учитывая, что подобным образом формируются ссылки при использовании модуля Angular Resource.

Так же важно понять, как именно работает данный API и почему так мало URL. Нижеприведённая таблица указывает, какого типа запросы отправляются на какой адрес и к чему это приводит:

URL Метод Результат
/api/user/ GET Возвращает список всех пользователей
POST Создаёт нового пользователя, возвращает его объект
/api/user/:id/ GET Возвращает информацию об указанном пользователе
POST Выполняет обновление информации об указанном пользователе
DELETE Удаляет указанного пользователя
/api/user/:id/news/ GET Возвращает все новости, автором которых является указанный пользователь

Сразу скажу, что при использовании Django REST Framework обновление записи методом POST запрещено, вместо неё следует использовать метод PUT. Для этого нужно будет особым образом сконфигурировать $resourceManager, о чём будет сказано ниже.

Установка $resource и его настройка

Для установки зависимостей в проектах я предпочитаю использовать bower. На указанном сайте рассмотрена установка данного менеджера пакетов, здесь я на ней останавливаться не буду. Помимо angular-resource понадобится так же пакет angular-cookie. Без правильной конфигурации печенек Django будет блокировать любые обращения к URL нашего API.

Итак, ставим пакеты:

bower install angular-resource angular-cookie --save

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

Теперь рассмотрим скрипт инициализации нашего приложения (как правило, это файл, имеющий название app.js)

(function (A) {
    "use strict";
    var app = A.module('MyApp', ['ngResource', 'ngCookie']);

    app.config(['$resourceProvider', function($resourceProvider){
        $resourceProvider.defaults.stripTrailingSlashes = false;
    }]);

    app.run(['$http', '$cookies', function($http, $cookies) {
        $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
        $http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
    }]);

}(this.angular));

Собственно, что происходит в этих нескольких строках.

  • Указываем модуль angular-resource как зависимость для нашего приложения. Без этого ничего не заработает.
  • Конфигурируем провайдер ресурсов. Говоря человеческим языком, делаем фундаментальные настройки, которые повлияют на всё приложение целиком.
  • На этапе запуска приложения указываем, что любые HTTP-запросы должны содержать в себе поле X-CSRFToken. Значение берётся из Cookies, которые формируются на сервере в момент первого обращения к странице.

Работа с ресурсами

Здесь рассмотрим, как разработчики модуля $resource предлагают нам работать с ресурсами.

Получить нужную запись, изменить её данные и отправить изменения на сервер:

var News, //Ресурс
    newsInstance; //Экземпляр новости

function updateNewsItem(){
    newsInstance.published = false;
    newsInstance.$save();
}

News = $resource('/api/news/:id/');
newsInstance = News.get({id: 3}, updateNewsItem); //Новость с id = 3, просто для примера

А как создать новую запись? Вот так

newsInstance = new News({text: 'Примерный текст новости'});
newsInstance.$save();

Что насчёт обновления? Метод $update в свойствах объекта? Нет, не угадали.

newsInstance = News.get({id: 3});
newsInstance.author = 4; //Какой-то другой пользователь станет автором

News.update({id: 3}, newsInstance);

Немного странно, не правда ли? Скажу лишь, что скудность документации - одна из самых многочисленных жалоб на Angular.

Но это частности. Если вкратце, бОльшая часть решений, найденных мной в интернете, предполагает создание фабрики или сервиса, возвращающих созданный объект $resource, например, так мог бы выглядеть наш ресурс для работы с новостями:

(function (A){
    "use strict";
    var app = A.module('MyApp');

    app.factory('News', ['$resource', function ($resource) {
        return $resource('/api/news/:id/', {id: '@id'}, {
            update:{
                method: 'PUT' //Без этого не будет работать обновление объектов на стороне сервера,
                //если используется Django REST Framework
            }
        });
    }]);
}(this.angular));

Ладно, а что насчёт комментариев?


(function (A){
    "use strict";
    var app = A.module('MyApp');

    app.factory('Comment', ['$resource', function ($resource) {
        return $resource('/api/comment/:id/', {id: '@id'}, {
            update:{
                method: 'PUT' //Без этого не будет работать обновление объектов на стороне сервера,
                //если используется Django REST Framework
            }
        });
    }]);
}(this.angular));

Итак, налицо уже идёт дублирование кода! Значит, можно написать нечто такое:

(function (A){
    "use strict";
    var app = A.module('MyApp'),
        resources = {
            'User': '/api/user/:id/',
            'UserComments': '/api/user/:id/comments/',
            'UserNews': '/api/user/:id/news/',
            'News': '/api/news/:id/',
            'NewsCimments': '/api/news/:id/comments/',
            'Comment': '/api/comment/:id/'
        },
        idConfig = {id: '@id'},
        putSettings = {
            update: {
                method: 'PUT'
            }
        },
        i;

    for (i in items){
        if (resources.hasOwnProperty(i)){ //Стандартная проверка, что это свойство собственное,
                                          //а не унаследовано от прототипа
            app.factory(i, ['$resource', function ($resource){
                return $resource(resources[i], idConfig, putSettings);
            }]);
        }
    }
}(this.angular));

Ну что ж, неплохо, если не считать предупреждения статического анализатора, что опасно создавать функции в цикле. Но по-прежнему остаётся несколько проблем:

  • Размножение сущностей. Почему для получения новости и комментариев к ней используются два разных ресурса?
  • Фабрика. Внутри Angular работает так, что при каждом вызове фабрики создаётся новый объект. Конечно, в JavaScript работает автоматический сборщик мусора, но сам подход не очень хорош.
  • В чём разница между $save и $update? В том, что во втором случае передаётся значение id? Почему бы тогда не вызывать нужный метод в зависимости от того, есть в объекте это свойство или нет?
  • Так и не решилась проблема с обновлением записей. Конечно, у созданного объекта есть все нужные методы, но выглядят они уродливо (это моё личное мнение), плюс приходится работать на уровне, достаточно близком к Pure JS, а хотелось бы чего-то более абстрагированного.
  • Что, если нам потребуются какие-то дополнительные методы для каждого из ресурсов? Например, автоматически выполнять локализацию дату публикации каждого комментария и каждой новости?
  • Безобразие с реализацией обещаний. В некоторых случаях методы ресурсов возвращают объект, а в некоторых - promise. Опять же, путаница.

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

В процессе написания модуля пришлось прибегнуть к ряду различных ресурсов, самым полезным из которых оказался MDN, конкретно - вот эта статья. Для имитации классического наследования я использовал описанный в стандарте ECMAScript 5 метод Object.create.

(function (A){
    "use strict";
    var app = A.module('MyApp'),
        idParams = {id: '@id'}, //Эта часть без изменений
        updParams = {
            update: {
                method: 'PUT'
            }
        };

    app.service('Managers', [
        '$q',
        '$resource',
        function($q, $resource){

            /*
             * Функция создаёт отложенный объект, т.е. тот, который
             * имеет свойство promise и может находиться только в
             * трех состояниях - разрешён, отклонён, в работе.
             * Как правило, такие объекты используются вместе с AJAX
             */
            function defer(){
                return $q.defer();
            }

            /*
             * Функция выполняет обращение к указанному ресурсу
             * указанным методом. При необходимости передаются
             * нужные параметры, например, экземпляр объекта.
             */
            function execQuery(method, data, resource){
                var d = defer();

                // Ниже выполняется обращение к нужному методу ресурса
                resource[method](
                    data,
                    function(response){ //Успешное выполнение запроса
                        d.resolve(response);
                    },
                    function (response){ //Запрос отклонён (ошибки)
                        d.reject(response);
                    }
                );

                return d.promise;
            }

            /*
             * Универсальная функция для создания ресурсов. Принимает
             * на вход два параметра - URL для обращения к API и
             * объект конфигурации. Если он не указан, используется
             * объект по-умолчанию, который будет брать id из
             * свойств самого объекта, передаваемого на вход ресурса.
             */
            function createResource(url, params){
                return $resource(url, params || idParams, updParams);
            }

            function BaseManager(url, params){
                this.sources = {
                    main: createResource(url, params);
                };
            }

            // А теперь к прототипу добавим нужные методы

            // Получить объект по id
            BaseManager.prototype.getById = function(id){
                return execQuery('get', {id: id}, this.sources.main);
            };

            // Получить все объекты
            BaseManager.prototype.getAll = function(){
                return execQuery('query', {}, this.sources.main);
            };

            //Удалить объект
            BaseManager.prototype.remove = function(id){
                return execQuery('remove', {id: id}, this.sources.main);
            };

            //Сохранить или обновить объект
            BaseManager.prototype.save = function(item){
                var method = item.hasOwnProperty('id') ? 'update' : 'save';

                return execQuery(method, item, this.sources.main);
            };



            // Создадим объект новостей, используя шаблон классического
            // наследования средствами ECMAScript 5

            function News(){
                //Вызов "предка" применительно к нашему "классу"
                BaseManager.apply(this, ['/api/news/:id/']);

                //Создадим дополнительный источник данных
                this.sources.comments = createResource('/api/news/:id/comments/');
            }

            // В статье на MDN описано, что здесь происходит
            News.prototype = Object.create(BaseManager.prototype);
            News.prototype.constructor = News;

            // А теперь - собственный метод
            News.prototype.getComments = function (id){
                return execQuery('query', {id: id}, this.sources.comments);
            };


            // Комментарии
            function Comment(){
                BaseManager.apply(this, ['/api/comment/:id/']);
            }

            Comment.prototype = Object.create(BaseManager);
            Comment.prototype.constructor = Comment;


            // Создаём объект, который будет хранить по одному экземпляру
            // Описанных выше классов

            return {
                News: new News(),
                Comment: new Comment()
            };
    }]);
}(this.angular));

Поскольку service реализует в Angular паттерн Одиночка, объект с экземплярами наших классов будет создан всего лишь один раз. Затем при каждом следующем вызове будет возвращено ранее сформированное значение. Посмотрим на практический пример применения описанного выше сервиса:

(function (A){
    "use strict";
    var app = A.module('MyApp');

    app.controller('NewsListController', ['$scope', 'Managers', function ($scope, Managers){

        // Загрузка всех новостей
        function loadNews(){
            $scope.loading = true;
            $scope.news = [];
            Managers.News.getAll().then(
                function (rows){
                    $scope.news = rows;
                },
                function (response){
                    $scope.errors = response.data; // В DRF - именно так
                }
            );
        }

        loadNews();

        // Загрузка комментариев при нажатии на ссылку "Комментарии" для
        // выбранной новости
        $scope.loadNewsComments = function(newsInstance){
            newsInstance.commentsLoading = true;

            Managers.News.getComments(newsInstance.id).then(
                function(rows){
                    newsInstance.commentsLoading = false;
                    newsInstance.comments = rows;
                },
                function (response){
                    newsInstance.commentsLoading = false;
                    newsInstance.errors = response.data;
                }
            );
        }
    }]);
}(this.angular));

Как тот же код мог бы работать в личном кабинете администратора:

(function (A){
    "use strict";
    var app = A.module('MyApp');

    app.controller('NewsCreateController', ['$scope', 'Managers', function ($scope, Managers){
        $scope.save = function(){
            Managers.News.save($scope.news_data).then(
                function (response){
                    $scope.news_data = response; //Теперь у нашей новости
                    // появился id, и вообще все данные теперь - с сервера
                },
                function (response){
                    $scope.errors = response.data;
                }
            );
        };
    }]);

    app.controller('NewsEditController', [
        '$scope',
        '$routeParams', // Взят для примера, для работы требует angular-route
        'Managers',
        function ($scope, $routeParams, Managers){
            var newsId = $routeParams.id;

            $scope.loaging = true;
            Managers.News.getById(newsId).then(
                function (response){
                    $scope.loading = false;
                    $scope.news_item = response;
                },
                function (response){
                    $scope.loading = false;
                    $scope.errors = response.data;
                }
            );

            $scope.save = function(){
                $scope.saving = true;
                Managers.News.save($scope.news_item).then(
                    function (response){
                        $scope.saving = false;
                        $scope.news_item = response; //Строго говоря, это делать
                        //необязательно
                    },
                    function (response){
                        $scope.saving = false;
                        $scope.errors = response.data;
                    }
                );
            };

    }]);
}(this.angular));

Как вы заметили, отличия в этих контроллерах практически минимальны. Перед нами - унифицированный способ работы с данными. Одним из важнейших преимуществ описанного подхода является так же использование наследования, что приводит к уменьшению количества кода.

Преветствуется конструктивная критика рассмотренных решений

P. S. Далее пойдёт описание того, как я создавал REST API на сервере.

Анонс статьи по Django REST Framework и Angular

В сети крайне мало информации по работе с Django REST Framework, а на русском можно сказать что нет вообще. Учитывая сложность вопросов на Toster и устарелость статей на Хабре, решил написать довольно объёмную статью по работе с данным фреймворком. Сам не являюсь профессионалом в нём и WEB-разработке вообще, но поделиться накопленными знаниями будет не лишне.

Примерное оглавление

  • Зачем нужен DRF?
  • Установка, подключение к проекту.
  • Сериализаторы
  • Виды-функции и виды-классы
  • Типовые задачи
  • Подключение Angular Resource
  • Несколько советов по API
  • Исходный код моего модуля Angular для работы с API с объяснением написанного

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

Перекат на OpenSUSE с возвратом к Debian'у

Причины

Долгое время на рабочем ноуте у меня стояла Windows 7. Работала она там несколько лет и всем, в общем-то, устраивала, однако, со временем я пришёл к мысли, что так дальше продолжать нельзя и для повышения уровня следует полностью перейти на Linux, не ограничиваясь только поддержкой серверов, на которых стоит ПО, которое поддерживает контора, в которой я работаю (хотел написать "моя", но вспомнил, что учредитель не я).

Первым делом я попробовал поставить на ноутбук Ubuntu. "Раз уж она для серверов не очень, то может быть, будет работать на лэптопе?" - подумлал я, скачал установочный образ и приступил к установке.

Прямо тут и начались проблемы. Напоминаю, на ноутбуке стояла Windows 7, которая при создании разделов использует MBR. Однако, для загрузки системы в UEFI необходимо наличие раздела EFI на жёстком диске, а с MBR это сделать невозможно. Опуская подробности скажу, что даже GParted выдавал информацию о том, что диск не имеет никакой разметки.

Я перенёс все данные на съёмный накопитель, а особо ценные продублировал в облаке, и приступил к дальнейшим поискам решения. Поиски завершились получением следующего знания:

Для корректной работы Linux на машине с UEFI необходимо, чтобы таблица разделов жёсткого диска была организована через GPT, а не MBR.

Удалил все разделы, пересоздал таблицу и успешно установил систему. Дальше начались будни. Через несколько дней я почувствовал, что работа Ubuntu и её навязчивый сервис вызывает во мне отторжение. Взял ноутбук домой и успешно установил Debian 7 Wheezy. Добавил репозитории NodeJS, PostgreSQL и некоторых других проектов. Проблема пришла, откуда не ждали. Не было звука. После многочисленных опытов скачал исходные коды с сайта производителя, скомпилировал из них драйверы и добавил их в ядро. Звук в наушниках появился, в динамиках - нет. OOOOOOK.

Пришло время подключиться к AD. Помучив помощника админа, ноут и систему, я так и не смог довести конфиги до рабочего состояния, поэтому шара фирмы была для меня закрыта. Тут вдруг наступили праздники, и я решил, что можно будет не умирать от безделья, а попробовать пожить в OpenSUSE, благо один знакомый уже давно пользуется этим дистрибутивом и, теоретически, к нему можно будет обратиться за помощью.

Удобства

Что порадовало в этом дистрибутиве:

  • Звук "из коробки" - неудивительно, ядро свежее и содержит все необходимые драйверы
  • Большинство пакетов, в т.ч. всякая проприетарщина - в стандартных репозиториях
  • Почти официальный репозиторий драйверов nVidia. На рабочем ноуте я никогда не играл, но сам факт наличия таких репок порадовал.
  • "Скользящие" обновления. По-моему, это большой шаг не только для OpenSUSE, но и для Ubuntu. Кратко суть: при формировании дистрибутива последняя версия Gnome была, например, 3.11. Всё время поддержки дистрибутива версии 7.0 она не будет меняться, будут выходить лишь патчи и исправления уязвимостей. При скользящих же обновлениях в репозиторий помещается последняя стабильная версия программы, и обновить Gnome до новой версии довольно просто, нужно лишь выполнить обычное обновление системы. В итоге имеем систему в состоянии Stable, но со свежими версиями пакетов.

Что не нравится

Тем не менее, некоторые вещи мне не очень понравились (ну ещё бы)

  • Во-первых, нет жёстких зависимостей между пакетами. После удаления какой-либо программы куча пакетов, которые ставились вместе с ней, остаётся в системе. Хотя некоторые пользователи данного дистрибутива позиционируют это как огромное преимущество. Я пока не вкусил всех прелестей, но мне уже не нравится.
  • Во-вторых, проблемы с кодеками. Да, тут с этим сложнее, чем в Debian. Там было достаточно прописать в свойствах репозитория non-free, и можно было ставить кодеки mp3 и h264. Все советуют использовать репозиторий Packman. Подключил, обновил полсистемы, никакого результата. Проблему с mp3 решил, h264 играет только системный плеер, VLC мимо кассы.
  • В-третьих, ещё неизвестно, будет ли работать здесь AD. До выхода на работу ещё 4 дня, и проверить не смогу.

Возможно, мне этот дистрибутив придётся по душе и я на нём пока остановлюсь. Посмотрим, что нам принесёт Debian 8, который сейчас в стадии заморозки.

UPD: к домену так и не смог подключиться, также при появлении уведомлений Skype приостанавливается воспроизведение музыки. Работа с виртуальными окружениями Python значительно хуже, чем в Debian. Подумываю о переустановке винды, т.к. работать с шарой моей конторы периодически приходится, а сейчас к ней доступа нет.

Ошибка при запуске Eclipse Luna в Debian

Проблема

Я на работе окончательно переехал на Debian. После двух дней ненависти к Ubuntu понял, что лучше иметь старые проверенные пакеты, чем новые и постоянно глючащие. Как обычно, после установки системы поставил JRE:

apt-get update && apt-get install openjdk-7-jre -y

Скачал и распаковал последнюю стабильную версию Eclipse с официального сайта. Попробовал запустить:

./eclipse

Получил кучу ошибок, среди которых была такая: # C [libgdk-x11-2.0.so.0+0x5173f] gdk_display_open+0x3f.

Решение

Решение оказалось на редкость простым. Нужно импортировать в окружение переменную SWT_GTK3=0, тогда всё будет работать. Прописал в конце файла /etc/profile:

export SWT_GTK3=0

Чтобы не перезагружать систему, сделал то же самое для текущего пользователя, т.е. выполнил указанную выше команду. Всё работает!

Django и $resource

Суть проблемы

Решил использовать для реализации RESTful API своего приложения такой модуль Angular, как angular-resource. Проблем с установной и подключением, как обычно, не было. Создал свой сервис:

(function (A, U) { //U === undefined
    "use strict";
        
    A.module('DesktopApplication').factory('Category', function ($resource) {
        var instance = {
            selectedRow: U, //Используется в моём проекте для обмена данными между контроллерами,
                            //не является обязательным элементом
            resource: $resource('/category/rows/:id', {id: '@id'})
        };
        return instance;        
    });
}(this.angular));

Казалось бы, всё должно работать. И ведь правда, вот такое работает (GET-запрос на получение данных):

//Где-то в коде, использующем сервис Category
Category.resource.query(function (items){
    $scope.rows = items;
});

А вот такое уже нет:

//Чуть ниже
Category.save({'id': 4, 'name': 'Username'}, function (result) {
    $scope.success = result.success;
});

Проблема оказалась в том, что даже явно указав слэш в конце строки ресурса, я получил POST-запросы, идущие к адресам без слэша, и Django такие запросы тут же банит. Начался поиск и чтение StackOverflow.

Решение

Решение оказалось довольно простым. Нужно указать в настройках приложения, что удалять концевой слэш для URL в AJAX-запросах не надо (а по-умолчанию включено). Кроме того, для корректной работы запросов методом PUT и DELETE нужно дополнительно конфигурировать $http. Иначе декоратор @csrf_protect из комплекта Django будет резать такие запросы. С учётом вышесказанного, скрипт настройки приложения стал выглядеть так (для сокращения размера выброшены прочие модули, которые я на самом деле использую в своём проекте):

(function(A) {
    "use strict";
    A.module('DesktopApplication', [ 'ngCookies', 'ngResource')
     .config(function ($interpolateProvider, $resourceProvider) {
         $interpolateProvider.startSymbol('{$');
         $interpolateProvider.endSymbol('$}');
         $resourceProvider.defaults.stripTrailingSlashes = false;
      })
     .run(function ($http, $cookies) {
          $http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
          $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
          $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
     });
}(this.angular, this.jQuery));

Вывод дат в Django

Проблема с датами

Часто приходится выводить даты в JSON или в шаблон. Начал искать решения, позволяющие обойти вечные ошибки при преобразовании, в итоге сделал для себя небольшую памятку:

item.pub_date = child.date_of_birth.strftime('%d.%m.%Y')

В таком виде данные без проблем выводятся в шаблонах и при отправке через HttpResponse.

Как, например, вывожу данные о новости:

for item in news:
    rows.append({
        id: item.id,
        "pub_date": '%s в %s' % (item.pub_date.strftime('%d.%m.%Y'), item.pub_date.strftime('%H:%M'))       
    })
return render_to_response('detail.html', RequestContext(request, {'rows': rows}))

AngularJS, Django и POST

Введение

Начал писать приложение на Angular. Стало сразу две проблемы.

  • Как передавать CSRF-Token Django при каждом запросе?
  • Почему данные, отправляемые через POST, не видны в объекте request Django?
  • CSRF-Token

    Для CSRF-Token нашлось очень простое решение. Достаточно подключить модуль angular-cookies и при старте приложения задать параметры сервиса $http. С учётом того, что скобки для Angular у меня заменены с {{ и }} на {$ и $}, заготовка главного модуля приложения у меня выглядит так:

    (function(A){
        "use strict";
        A.module('DesktopApplication', ['ngCookies']).config(function($interpolateProvider){
            $interpolateProvider.startSymbol('{$');
            $interpolateProvider.endSymbol('$}');
    
        }).run(['$http', '$cookies', function ($http, $cookies){
            $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
            $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
            }]);
    }(this.angular));

    Естественно, на сервере при отдаче шаблонов нужно, чтобы соответствующие Cookies были установлены. Делается очень просто:

    return render_to_response('template.html', RequestContext(request))

    Angular и POST

    Отдельной болью стала отправка данных на сервер через POST. Было потрачено несколько часов на чтение Тостера, официальной документации по Angular и Django, пока наконец я не наткнулся на решение одного из пользователей StackOverflow. Суть решения такова: Angular при отправке данных не превращает их в ожидаемый на бекэнде формат, а передаёт их как строку.

    Допустим, у нас имеется такой объект:

    userdata = {
        login: 'userlogin',
        password: '123456'
    };

    Попробуем отправить эти данные:

    $http.post('/login/', userdata).success(function(data){
        //Что-то делаем с полученными в ответ данными
    });

    В консоли Firebug будет выведена строка запроса:

    "{"login": "userlogin", "password": "123456"}"

    Попробуем получить эти данные:

    def login_view(request):
        if request.method == 'POST' and request.is_ajax():
            username = request.POST.get('login', '')
            password = request.POST.get('password', '')

    Мы ожидаем, что в третьей и четвёртой строках будет примерно следующее:

    username = 'username'
    password = '123456'

    На самом деле этого не происходит, т.к. данные должны быть переданы в таком виде:

    login=userlogin&password=123456

    Проблема в том, что в текущей версии Angular (1.3.4 на момент написания статьи) нет функции для приведения объекта в такой вид. Поэтому автор посоветовал использовать старый добрый jQuery, точнее его функцию param:

    $http.post('/login/', $.param(userdata)).success(function(data){
        //Обработка ответа сервера
    });

    Вот такой код будет работать. Так же пользователи предлагали самописные функции, выполняющие ту же работу, но я советую использовать проверенные решения. Кроме этого обязательно присутствие в заголовке пост параметра Content-Type, равного application/x-www-form-urlencoded, соответствующий код приведён выше.