Django - изменить размер изображения перед сохранением
Как изменить размер изображения перед сохранением? Никак. Но далее я опишу путь, на который указал пользователь StackOwerflow в своём ответе вот на этот вопрос.
Вкратце:
- Сохраняем объект
- Проверяем наличие данных в нужном поле типа
ImageField - Открываем сохранённое изображение и меняем его свойства, как нам нужно
Пример
from PIL import Image
from django import models
from os import path
# Максимальный размер изображения по большей стороне
_MAX_SIZE = 300
class CarManufacter(models.Model):
"""Производитель автомобилей, два поля
name - название, строка
logo - логотип, изображение
"""
name = models.CharField(
'Наименование',
max_length=100,
unique=True
)
logo = models.ImageField(
upload_to=path.join('car', 'manufacter', 'logo'),
null=True,
blank=True
)
def save(self, *args, **kwargs):
# Сначала - обычное сохранение
super(CarManufacter, self).save(*args, **kwargs)
# Проверяем, указан ли логотип
if self.logo:
filepath = self.logo.path
width = self.logo.width
height = self.logo.height
max_size = max(width, height)
# Может, и не надо ничего менять?
if max_size > _MAX_SIZE:
# Надо, Федя, надо
image = Image.open(filename)
# resize - безопасная функция, она создаёт новый объект, а не
# вносит изменения в исходный, поэтому так
image = image.resize(
(round(width / max_size * _MAX_SIZE), # Сохраняем пропорции
round(height / max_size * _MAX_SIZE)),
Image.ANTIALIAS
)
# И не забыть сохраниться
image.save(filename)
Зависимости в Angular
Внеднение зависимостей
Все знают, что Angular построен на постоянном и вездесущем внедрении зависимостей, т.н. dependency injection. Существуют как минимум три способа сделать это.
1. Implicit annotation
Зависимости просто перечисляются как аргументы нашего модуля.
Пример
(function (A){
"use strict";
A.module('app', []).controller('MyCtrl', function($scope, $sce){
//Тут должен быть код контроллера
});
}(this.angular));
Всё хорошо до момента минификации нашего кода. Это потому, что при таком объявлении функций Angular для определения нужных зависимостей превращает их код в строку, которую затем разбирает. А при минификации, как известно, все идентификаторы заменяются на более короткие, что делает наши скрипты неработоспособными.
2. $inject
Хороший способ внедрения зависимостей - создать статическое свойство функции под названием $inject. В нужный момент Angular возьмёт список зависимостей оттуда.
Пример
(function (A){
"use strict";
function MyCtrl($scope, $sce){
//Код контроллера
}
MyCtrl.$inject = [ '$scope', '$sce' ]; //Строки не сжимаются никогда
A.module('app', []).controller('MyCtrl', MyCtrl);
}(this.angular));
Уже лучше. Такой код успешно переживёт минификацию.
3. Inline array annotation
Весьма хороший метод, не хуже предыдущего, но многие статические анализаторы кода, например, в Eclipse, считают такое поведение ошибкой (Они не правы!):
Пример
(function (A){
"use strict";
function MyCtrl($scope, $sce){
//Код контроллера
}
A.module('app', []).controller('MyCtrl', [ '$scope', '$sce', MyCtrl ]);
}(this.angular));
Такой код тоже хорошо переживёт минификацию.
Дополнительно
Недавно узнал, что в Angular даже есть специальная директива для запрета внедрения зависимостей первым способом - ng-strict-di. Указывать её надо рядом с ng-app:
Пример
<!doctype html>
<html ng-app="app" ng-strict-di>
<head>
<!-- Код -->
</head>
<body>
<!-- И тут тоже код -->
</body>
</html>
После этого попытка внедрения зависимостей через Implicit Annotation будет вызывать ошибку.
DRF, часть 1 - Проектирование структуры API
Введение
Раз уж у меня не получается написать полноценную огромную статью про Django REST Framework, следует публиковать хотя бы небольшие заметки. Начать следует с прописных истин.
- 1. Дуб - дерево.
- 2. Олень - животное.
- 3. Смерь - неизбежна.
- 4.
api- отдельное приложение в нашем проекте - 5. Следует придерживаться общепринятых правил именования частей API
- 6. API должен быть версионным
- 7. API должен быть простым
С первыми тремя пунктами всё ясно, поэтому перейду сразу к четвёртому и нижеследующим.
Структура каталогов и версионность
Сначала я пытался запихнуть API в разные части основного проекта, создавал в каталоге с приложениями файлы api.py, serializers.py и permissions.py.
О том, насколько это плохая идея, я узнал почти сразу же, когда начал путаться с тем, что и где лежит. Стоило только переименовать один из каталогов, как сразу же всё начинало сыпаться. В итоге я пришёл к тому, что API - не просто отдельное приложение, содержащее только файлы __init__.py и urls.py (со ссылками на нужные файлы в приложениях Django), а полноценный модуль со множеством вложенных модулей. В общем, почувствуйте разницу:
/api/
__init__.py
urls.py
/news/
__init__.py
admin.py
api.py
models.py
serializers.py
tests.py
urls.py
views.py
/comments/
__init__.py
admin.py
api.py
models.py
serializers.py
tests.py
urls.py
views.py
/api/
/v1/
/news/
/comments/
__init__.pt
api.py
permissions.py
serializers.py
urls.py
__init__.py
api.py
permissions.py
serializers.py
urls.py
__init__.py
urls.py
/core/
/comment/
__init__.py
api.py
permissions.py
serializers.py
urls.py
/news/
/comments/
__init__.py
admin.py
models.py
tests.py
views.py
__init__.py
admin.py
models.py
tests.py
views.py
Надеюсь, структура понятна. Во-первых, всё, что связано с API, переехало в одноимённое приложение. Во-вторых, API теперь поддерживает версионность. Для этого нужно всего ничего - создать соответствующие каталоги и правильно описать файл urls.py в самом начале. Я сделал так:
from django.conf.urls import include
from django.conf.urls import url
urlpatterns = [
url(r'', include('api.v2.urls')),
url(r'^v1/', include('api.v1.urls')),
url(r'^v2/', include('api.v2.urls')),
]
Естественно, где-то в главном файле urls.py есть строка, в которой конфигурация URL для API присоединяется к общей конфигурации URL через всё тот же include().
Как видно из этого файла, если пользователь нашего API не указывает версию, он использует последнюю доступную. В то же время, при необходимости можно указать любую из имеющихся. В одной из статей я видел тезис, согласно которому компании, которые заботятся о своих клиентах и хотят сделать свой сервис успешным, крайне редко или вообще никогда не меняют API. Естественно, не меняеть его вообще никогда вряд ли получится, но можно сделать различия минимальными или предоставить возможность в течение длительного времени использовать старый API, т.к. разработчикам интересней писать что-то новое, а не переписывать старое только потому, что теперь какой-то метод в нашем API перестал работать.
Именование API
Сначала хочу сказать, как делать НЕ НАДО:
http://example.org/api/
Это что угодно, но не API. К сожалению, по ночам мне всё ещё снятся кошмары, в которых я вижу, как на одном из сайтов общение с сервисами сделано именно так.
GET /api/news/get/?id=1 - получить новость с id=1
GET /api/news/get/all/ - получить все новости
POST /api/news/add/ - добавить новость
POST /api/news/update/?id=1 - обновить новость с id=1
POST /api/news/delete/?id=1 - удалить новость с id=1
GET /api/news/?id=1 Получение записи с id=1
POST /api/news/?id=1?action=update Обновление записи с id=1
POST /api/news/?id=1?action=delete Удаление записи с id=1
Это API? Конечно же, нет! Суть REST-сервисов в том, что требуемое действие определяется HTTP-заголовком!
URL - /api/news/:id
GET /api/news/ - получить список всех новостей
POST /api/news/ - создать новость
GET /api/news/12/ - вернёт новость с id=12
PATCH /api/news/12/ - обновить новость с id=12
DELETE /api/news/12/ - удалить новость с id=12
Разница, как говорится, налицо.
В Django REST Framework обновление записи может быть произведено двумя способами - полностью или частично. В первом случае вызывается запрос с заголовком PUT, во втором - PATCH.
При полном обновлении сериализатор проверяет заполнение всех полей модели, у которых не указаны свойства null=True. Не будем забывать, что для модели пользователя обязательными для заполнения являются поля "Пароль" и "Имя пользователя". Но мы же всего лишь хотели указать новую дату рождения! Почему DRF проверяет все поля? Потому что надо было вызывать метод PATCH.
Теперь пора поговорить, как делать лучше (моё мнение по данному вопросу актуально только на момент написания статьи и в будущем может быть пересмотрено).
/api/articles/:id/ - статьи
/api/friends/:id/ - друзья
/api/news/:id/ - новости
/api/users/:id/ - пользователи
/api/videos/:id/ - видео
GET /api/acticles/:id/comments/ - получить комментарии к статье
GET /api/news/:id/comments/ - получить комментарии к новости
POST /api/articles/:id/comments/ - добавить комментарий к статье
POST /api/news/:id/comments/ - добавить комментарий к новости
DELETE /api/comments/articles/:id/ - удалить комментарий к статье
DELETE /api/comments/news/:id/ - удалить комментарий к новости
Сериализаторы и всё остальное.
Я считаю, что при написании новой версии API могут измениться поля, с которыми работают сериализаторы, поэтому для каждой версии их лучше создавать заново. Кроме того, сами методы для работы с данными могут стать другими. Отсюда следует простой вывод (до которого мне пришлось доходить своим умом пару месяцев):
Простота API - почти недостижимый идеал. Чтобы сделать его простым, придётся проделать очень сложную работу. Не удивляйтесь. Кнопка включения на корпусе компьютера - очень простой интерфейс, однако, по одному нажатию на неё выполняется огромное количество самых разнообразных и сложных действий, на глубокое понимание которых некоторые люди тратят большую часть своей жизни. Таким же должен быть и ваш API - всегда оставаться простым для внешнего наблюдателя, не смотря на то, какая бы сложная работа не происходила внутри системы.
Итоги
- Разработчики хотят работать с сущностями, а не с URL. Сделайте интуитивно понятными все URL вашего API.
- Разработчики ленивы и не хотят изучать 34 параметра, влияющие на результат вызова одного единственного метода (из 56, имеющихся в наличии). Старайтесь избегать реализации поведения API через параметры.
limit,offset,filterиsorting- необходимое зло, от них никуда не деться. - Действие, выполняемое методом API, должно зависеть от HTTP-заголовка.
GETдля чтения,POSTдля создания,PUT/PATCHдля обновления иDELETEдля удаления записей. - Пишите документацию к вашему API. Даже самая лучшая структура URL для API неспособна описать различные тонкости и нюансы его работы.
CSRFToken в Angular 1.4
В новом Angular 1.4 одно из приятных изменений - возможность задавать куку и заголовок для CSRF-токена. Раньше делали так:
angular.module('app', ['ngCookies']).run(['$http', '$cookies', function($http, $cookies){
var token = $cookies.csrftoken;
$http.defaults.headers.common['X-CSRFToken'] = token;
$http.defaults.headers.post['X-CSRFToken'] = token;
}]);
Сейчас на этапе конфигурирования приложения можно сделать такой финт:
angular.module('app', []).config(['$httpProvider', function($httpProvider){
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
}]);
Оригинальная документация - здесь. Проверял работу на Django 1.8.
EMACS - начало
Введение
Я не буду здесь рассказывать в деталях, чем плоха та или иная IDE или чем мне не угодили VIM и Sublime Text. Начнём лучше с того, чем EMACS хорош.
EMACS хорош в первую очередь тем, что это не просто текстовый редактор, а настоящий конструктор, из которого можно сделать как полноценную IDE для разработки практически на любом языке, так и издательскую систему или текстовый процессор. Основная фишка - возможность расширять возможности редактора путём написания сценариев на языке EMACS Lisp. К счастью, всё уже написано до нас, и лично мне ни разу не приходилось писать что-то своё, однако при этом я смог превратить EMACS в IDE для WEB- и Python-разработки. Да, эту статью я тоже пишу в нём.
Во-вторых, он быстр. Он не требует JRE для своей работы, написан на C и EMACS Lisp, запускается и работает очень быстро.
Это свободное ПО, что для обычного российского пользователя означает как раз "Free beer".
Он не перегружен меню, диалоговыми окнами, кучей разных панелей и т.д. Лаконичный интерфейс легко может быть расширен или сведён к пугающему минимализму - выбор за вами.
Огромная база готовых пакетов. Там есть практически всё, что нужно, от поддержки редких языков до веб-браузера и игр. Серьёзно!
Установка
Установка в Windows предельно проста - нужно зайти на этот сайт и скачать архив с последней версией. Распаковываем её в любое нужное место, находим в каталоге bin файл runemacs.exe и создаём для него ярлык на Рабочем столе.
Установка в Linux может быть чуть сложнее. Как правило, в Debian и Ubuntu LTS идёт устаревшая версия дистрибутива. Я рекомендую использовать не ниже 24.0, в противном случае часть пакетов (можно считать их плагинами) не будет работать. Если в официальном репозитории слишком старая версия, придётся собирать EMACS из исходных кодов, что заслуживает отдельной статьи. Здесь на этом останавливаться не буду.
Помимо самого EMACS необходимо иметь в системе установленную систему контроля версий Git. Пользователи Windows могут скачать здесь.
Комбинации клавиш
Оригинальные комбинации клавиш EMACS устарели. Нажимать [Ctrl] (в любом руководстве по EMACS эта клавиша обозначается как [C]) мизинцем неудобно. Когда-то [Ctrl] располагался на месте сегодняшнего [Alt], и пользоваться им было удобно, но времена мэйнфреймов давно прошли... Подробный разбор того, почему комбинации клавиш в EMACS такие, какие есть, и почему это плохо, производится здесь [EN]. Нет смысла изучать дефолтные настройки комбинаций клавиш, всё равно придётся потом переучитваться. Однако, в экстренных ситуациях может пригодиться как минимум две команды:
| Комбинация | Действие |
|---|---|
[M-X] |
Вход в минибуфер для ввода команд |
[C-X, C-C] |
Завершение работы редатора |
После установки и активации пакета ergoemacs-mode комбинации клавиш будут переопределены. Рекомендую сделать это как можно быстрее, т.к. отвыкнуть от Ctrl+X, Ctrl+C и Ctrl+V очень тяжело, да и нет смысла этого делать.
Терминология
Буфер - аналог вкладки с документом в других тектовых редакторах и IDE. Так же это область, куда выводятся сообщения плагинов или самого редактора.
Минибуфер - самый маленький буфер, расположенный в нижней части окна EMACS. В него вводятся различные команды. Для входа в него следует использовать комбинацию клавиш [M-X] без установленного и включенного пакета ergoemacs-mode, либо [M-A]. После ввода команда запускается на выполнение нажатием [Enter] (в документации EMACS эта клавиша называется [RET])
Пакет - аналог плагина в других редаторах и IDE. Представляет собой сценарий или набор сценариев на языке EMACS Lisp. Часть пакетов является встроенной в редактор и поставляется вместе с ним, другие нужно скачивать из репозитриев. Отметим, что сейчас практически не используется способ установки пакета ручным клонированием репозитория с GitHub, далее под репозиториями будут пониматься специализированные хранилища пакетов.
Основной режим - определяет поведение EMACS для буфера. От этого режима зависит поведение различных клавиатурных комбинаций и набор доступных дейстий. Например, при написании этой статьи я использую web-mode, что даёт подсветку синтаксиса, автоматическое форматирование и применение Emmet для создания разметки. Основной режим может быть лишь один (имеется пакет, позволяющий включить сразу несколько основных режимов для одного буфера, но я им ни разу не пользовался).
Дополнительный режим - в то время, как основной режим может быть лишь один, дополнительных режимов можно включить столько, сколько нужно. Например, сюда относятся средства проверки орфографии, возможность автокомплита, автоформатирование кода и т.д. Большая часть пакетов для EMACS работает именно в дополнительном режиме.
Первоначальная настройка
Настройка EMACS может производиться как минимум двумя способами.
Настройка через правку файла конфигурации .emacs
Основной файл настроек EMACS называется .emacs и располагается в домашнем каталоге пользователя. В случае с Windows это как правило каталог вроде этого:
C:\Users\xPhoenix\AppData\Roaming\.emacs
Однако, можно переопределить домашний каталог для хранения настроек (важно понимать, что файл .emacs станет вашей прелессссстью, которую вы будете оберегать и лелеять), создав для текущего пользователя системную переменную под именем HOME, и указав в ней путь к каталогу, который следует считать домашним для EMACS (возможно, работает и для других программ из мира Linux, не проверял). У меня эта переменная выглядит так:
D:\xphoenix\
Кроме этого файла EMACS при работе создаёт так же создаёт в домашнем каталоге папку .emacs.d. В ней будут храниться скачанные пакеты, сниппеты, файлы .desktop и т.д. Не надо его удалять!
В любом случае, в наш .emacs следует поместить как минимум вот эти строки:
(defalias 'yes-or-no-p 'y-or-n-p)
(setq package-archives '(
("elpy" . "http://jorgenschaefer.github.io/packages/")
("gnu" . "http://elpa.gnu.org/packages/")
;; ("melpa" . "http://melpa.milkbox.net/packages/")
("melpa-stable" . "http://melpa-stable.milkbox.net/packages/")
("org" . "http://orgmode.org/elpa/")
))
Первая строка указыает на то, что вместо ввода строк yes и no в ответы на запросы системы можно просто нажать [y] или [n] соответственно.
Чуть ниже идёт указание репозиториев для поиска пакетов. Репозиторий MELPA очень популярен и в Интернете, вы будете часто встречать отсылки именно к нему, но использовать его опасно, т.к. пакеты туда попадают прямо из ветки master с GitHub. Поэтому я предпочитаю MELPA-STABLE. Пусть пакеты старые и их гораздо меньше, зато ничего не сломается при очередном обновлении. Впрочем, в редких случаях я пользуюсь MELPA, например, если пакета вообще нет в STABLE или тот, что есть, слишком старый. Так же существует репозиторий MARMELADE, но я не вижу смысла его использовать, т.к. некоторые пакеты в нём даже более старые, чем в MELPA-STABLE.
Настройка через customize
В одной из версий EMACS в стандартную поставку стал входить пакет sustomize. Просто выполните одноименную команду, и попадете в буфер, где будет поле для поиска и множество гиперссылок для перехода к нужным настройкам. Отмечу лишь, что настройка некоторых параметров через customize конфликтует с ручной правкой .emacs, однако, в ряде случаев является более удобной. Некоторые параметры EMACS можно настроить только ручным редактированием .emacs, в customize вы просто не найдёте нужного раздела.
Установка пакетов
Указав в вашем .emacs репозитории так, как указано выше, перезапустите редактор (можно сделать и без перезапуска, но не буду усложнять) и выполните команду list-packages. Откроется буфер, содержащий список пакетов в доступных репозиториях. Выбор пакета для установки осуществляется нажатием клавиши [I], отмена выбора - [U], пометка на удаление - [D]. После того, как будет выбрано, что делать с пакетами, следует нажать клавишу X для запуска операций. EMACS установит и при необходимости выполнит компиляцию нужных пакетов.
Есть несколько пакетов, которые я настоятельно рекомендую к установке.
- auto-complete - предназначение ясно из названия. Через него работают многие другие пакеты, например,
jedi. - autopair - автоматически закрывает скобки
- company - ещё одно средство для автокомплита, через него работают некоторые пакеты, которые не работают с
auto-complete - emmet-mode - незаменимое средство для верстальщиков. Рекомендую ставить из MELPA, т.к. в STABLE очень старая версия, которая работает хуже и многого не умеет.
- ergoemacs-mode - пакет, устанавливающий комбинации клавиш, удобные для использования людьми, а не Ричардом Столлманом. Подробное описание клавиш здесь.
- flycheck - модуль проверки чего угодно на лету. На самом деле по-тихому вызывает имеющиеся в системе средства проверки и статического анализа кода. Имеет возможность конфигурирования того, какие средства и с какими параметрами следует использовать. Является более новым и прогрессивным в сравнении с загнивающим
flymake. - jedi - автокомплит для Python-разработчиков. Настройка данного пакета достойна отдельной статьи.
- js2-mode - расширенный по сравнению с
js-modeрежим правки JavaScript-кода. Ставитьjs3-modeне рекомендую, т.к. проект давно загнулся. - less-css-mode - не вижу смысла объяснять назначение данного пакета.
- monokai - тема из Sublime Text 2, пожалуй, лучшая, что мне приходилось видеть.
- neotree - порт плагина NerdTree из VIM, отображает слева (или справа, настраивается) дерево каталогов и файлов. Лучше ставить из MELPA.
- rainbow-mode, rainbow-delimiters - в паре подсвечивают скобки и другие элементы разными цветами, позволяет легко находить ошибки типа "Забыл закрыть скобку".
- web-beautify - доступен только из MELPA, позволяет автоматически форматировать JS, CSS и HTML-файлы. Для работы требует установленный в системе NodeJS и его пакет
web-beautify. - web-mode - добавляет в EMACS возможность редактировать файлы XML, HTML, XHTML и др. Обеспечивает подсветку синтаксиса и т.д.
Получение аргументов URL в сериализаторах Django REST Framework
Небольшая заметка о том, как в ClassBasedView Django REST Framework получить значение параметра из URL.
Допустим, наши URL сконфигурированы таким образом:
urls.pyfrom django.conf.urls import url
from .api import ArticleListCreateView
from .api import ArticleDetailView
from .api import ArticleCommentListCreateView
urlpatterns = [
url(r'^article/$', ArticleListCreateView.as_view()),
url(r'^article/((\d+))/$', ArticleDetailView.as_view()),
url(r'^article/((\d+))/comment/$', ArticleCommentListCreateView.as_view()),
]
Делать какую-либо работу для вида ArticleDetailView не приходится - достаточно создать класс, унаследованный от RetrieveUpdateDestroyAPIView.
Получить доступ к параметру pk в виде не проблема, достаточно переопределить метод get_queryset(), например, так:
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from article.models import Article
from .models import ArticleComment
class ArticleCommentListCreateView(RetrieveUpdateDestroyAPIView):
model = ArticleComment
# Возвращаем только комментарии к указанной статье
def get_queryset(self):
return ArticleComment.objects.filter(article=self.kwargs['pk])
def get_serializer_class(self):
if self.request.method == 'GET':
return ArticleCommentReadSerializer
return ArticleCommentWriteSerializer
А вот получить в сериализаторе значение параметра pk можно не вполне очевидным способом:
from django.shortcuts import get_object_or_404
from rest_framework import serializers
from .models import ArticleComment
from article.models import Article
# ArticleReadSerializer не описан из-за своей простоты
class ArticleCommentWriteSerializer(serializers.ModelSerializer):
article = serializers.PrimaryKeyRelatedField(
queryset=Article.objects.all(),
default=None
)
# Заменяем валидатор поля article таким образом, чтобы всегда
# подставлялось значение из URL
def validate_article(self, value):
# Кто бы мог подумать???
article_id = self.context['view'].kwargs['pk']
return get_object_or_404(Article, pk=article_id)
class Meta:
model = ArticleComment
Расширение контроллеров в Angular
Краткое содержание
В статье рассказывается о том, как в Angular можно реализовать наследование контроллеров.
Суть проблемы
Мне приходится писать довольно много кода на Angular, при этом заметил, что от контроллера к контроллеру меняются, порой, лишь незначительные части. Например, везде, где я использую ngInfiniteScroll, в $scope приходится помещать переменные allLoaded и loading, а так же обработчик loadMore(), выполняющий загрузку новых элементов. По незнанаю, приходилось многократно дублировать практически одинаковый код. Однако, один из пользователей StackOverflow нашёл очень хорошее решение данной проблемы.
Решение
Решение оказалось на удивление простым и заключается в использовании сервиса $controller.
(function (A){
"use strict";
// Базовый контроллер, который содержит код, общий для всех других контроллеров,
// которые будут наследоваться от него
A.module('App').controller('ListController', ['$scope', function($scope){
$scope.allLoaded = false;
$scope.items = [];
$scope.loading = false;
$scope.loadMore = function(){
if ($scope.loading){
return;
}
// Тут какая-то работа по загрузке данных
};
$scope.refresh = function(noPlease){
if (noPlease){
return;
}
$scope.items.length = 0; // Более правильное решение, нежели $scope.items = [];
// т.к. в этом случае сохраняется ссылка на оригинальный
// объект
};
}]);
}(this.angular));
ChildClass.js
(function(A){
"use strict";
A.module('App').controller('UserListController', [ '$controller', '$scope', function($controller, $scope){
// Расширяем контроллер
$controller('ListController', {
$scope: $scope
});
// Теперь можно собственные свойства описывать
}]);
}(this.angular));