Как мы делали каркас приложения на AngularJS и Django

imageВесной нам в голову пришла идея сделать простой сервис для облачного бэкапа серверов. Поскольку в то время работа над проектом велась преимущественно по вечерам и по выходным, для ускорения процесса было решено использовать только те технологии, в которых у нас есть опыт. Для backend-части был выбран Django, а реализация клиентской части предполагалась в виде SPA на базе AngularJS. Задумка была в следующем: сделать продукт с минимальным функционалом, а затем постепенно добавлять новые возможности. Для этого необходимо было сделать достаточно гибкую и масштабируемую систему. Немного пораскинув мозгами, мы приступили.

РоутингиИ первый вопрос, который возник, был связан с роутингами в клиентской части. Нам была необходима надёжная и простая система, которая поддерживала бы вложенные друг в друга шаблоны и позволяла однозначно сопоставлять определённому URL необходимый шаблон. После недолгих поисков мы выбрали ui-router.Была утверждена следующая схема: По пути / пользователю показывается лэндинг, который никак не связан с приложением. При переходе на /app/ сервер отдаёт файл app.html, который содержит весь head, все скрипты в конце body и один единственный div со скромным атрибутом ui-view. Именно в этот div грузится всё приложение. В зависимости от того, залогинен пользователь или нет, ему показываются разные заполнения этого div«a.

Я не буду забегать вперёд, а рассмотрю случай для аутентифицированного пользователя. Итак, в этом случае, если в URL после /app/ нет никакого хэша, то внутрь

грузится следуюший слой: index.html. Этот файл содержит в себе статическую часть приложения, которая окружает всю рабочую область: хэдер, футер и боковое меню. В index.html так же есть div с атрибутом ui-view, в который будет подгружаться ещё один уровень приложения, а конкретно — различные экраны (в нашем случае это: главный экран, детальный экран сервера, экран биллинга, экран восстановления бэкапа и другие).

image

Рассмотрим, как же это всё описано с помощью ui-router:

app.config (['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {

$stateProvider .state ('index', { url: '/', templateUrl: '/static/views/index.html' }) .state ('index.main', { url: '^/main', templateUrl: '/static/views/pages/main.html' }) .state ('index.client', { url: '^/main/c/: id', templateUrl: '/static/views/pages/client.html' }) .state ('index.billing', { url: '^/billing', templateUrl: '/static/views/pages/billing.html' }) .state ('index.restore', { url: '^/restore', templateUrl: '/static/views/pages/restore.html' });

$urlRouterProvider.otherwise ('/main'); // Если хэш не совпадает ни с одним, то редирект на страницу /main }]) Публичные и приватные страницы Настало время задуматься над разграничением прав доступа пользователей к определённым страницам. Если пользователь не залогинен, то ему могут показываться только публичные страницы, а при попытке захода на приватную страницу его ждёт принудительный редирект на экран логина. Так же и в обратную сторону: если пользователь уже вошёл, то он не сможет увидеть страницы входа, регистрации и восстановления пароля.Итак, добавим данные о публичных страницах в конфигурацию роутера:

$stateProvider .state ('login', { url: '/login', templateUrl: '/static/views/login.html' }) .state ('signup', { url: '/signup', templateUrl: '/static/views/signup.html' }) .state ('recovery', { url: '/recovery', templateUrl: '/static/views/recovery.html' }); В модуле, отвечающем за авторизацию, создана фабрика, которая определяет залогинен ли пользователь:

AuthModule.factory ('Auth', ['$cookieStore', function ($cookieStore) { var currentUser = $cookieStore.get ('login') || 0, publicStates = ['login', 'signup', 'recovery'];

return { authorize: function (state) { return (this.isLoggedIn () && (publicStates.indexOf (state) < 0)) || (!this.isLoggedIn() && (publicStates.indexOf(state) >= 0)) }, isLoggedIn: function () { return! currentUser; } }

}]) Метод isLoggedIn возвращает true, если пользователь залогинен, либо false в противном случае. Метод authorize определяет для текущего состояния, имеет ли право пользователь в нём находиться.

Использование этих методов осуществляется в обработчике события $stateChangeStart, которое возникает в момент начала изменения состояния:

$rootScope.$on (»$stateChangeStart», function (event, toState, toParams, fromState, fromParams) { // Если пользователь не имеет права находиться в данном состоянии if (! Auth.authorize (toState.name)) { // Необходимо для предотвращения дальнейшего изменения состояния event.preventDefault (); // Для случая первичного определения пути (при заходе на /app/ без какого-либо хэша) if (fromState.url === '^') { if (Auth.isLoggedIn ()) { $state.go ('index.main'); } else { $state.go ('auth'); } } } }); Аутентификация Процедура аутентификации на стороне клиента реализована с помощью функции в фабрике Auth: login: function (user, success, error) { $http.post ('/login/', user) .success (function () { currentUser = 1; success (); }) .error (error); } Вызов этой функции производится в контроллере. В качестве аргументов передаются username, password и коллбэки:

Auth.login ({ username: $scope.login.username, password: $scope.login.password }, function () { $state.go ('index.main'); }, function () { $scope.login.error = true; }); На сервере с помощью стандартных django-сессий хранится информация о пользователе (его id). Для этого используются стандартные методы django.contrib.auth.

from django.contrib.auth import authenticate, login

def login_service (request): data = json.loads (request.body) user = authenticate (username=data['username'], password=data['password']) if user is not None: login (request, user) return HttpResponse (status=200) else: return HttpResponse ('Login error', status=401) Во время каждого http-запроса сервер проверяет, залогинен ли пользователь, и устанавливает в заголовок 'Set-Cookie' соответствующее значение. Это значение и проверяется в клиентской части с помощью $cookieStore.get ('login').

Связка между моделями сервера и клиента С целью ускорения разработки и повышения гибкости приложения, было решено использовать middleware между Django и AngularJS. Выбор пал на django-angular.Основные его преимущества:

предоставляет возможность выполнять основные CRUD операции; позволяет плотно связать django-формы и angular-контроллеры; даёт функционал для вызова методов в django прямо из angular-контроллера. Подробнее об установке и настройке можно прочитать в документации.

Итог В итоге у нас получилось расширяемое и гибкое приложение, в котором модули минимально зависят друг от друга. Код всех модулей хранится в отдельных файлах, а добавление новых оставляет другие нетронутыми. Кроме того, функционал django-angular значительно ускоряет разработку.Эта вторая статья из цикла про то, как мы делали сервис облачного резервного копирования серверов bitcalm.com.Первая статья: Разработка своей системы биллинга на Django

© Habrahabr.ru