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));
AngularJS, Django и POST
Введение
Начал писать приложение на Angular. Стало сразу две проблемы.
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, соответствующий код приведён выше.