Debian, AngularJS, Django, Python

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

Django 1.8, Python 3, WSGI и Gunicorn

Введение

В данной статье рассказывается, как можно запустить приложение на Django и Python 3 под WSGI. Способ не претендует на звание самого лучшего, замечания по настройке и прочим нюансам приветствуются.

Структура каталогов

Следует создать в каталоге /var/www подкаталог для нашего проекта. Пусть сегодня он будет называться talos. В нём нужно будет создать каталоги для размещения статичных файлов, файлов, загружаемых пользователями, и логов.

От имени www-data
cd /var/www
mkdir talos
cd talos/
mkdir static media log

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

Создание файла запуска

Здесь же, в каталоге /var/www/talos, создадим файл run.bash, который будет запускать приложение, передавая ему нужные параметры.

/var/www/talos/run.bash
   #!/bin/bash

NAME="talos"                                # Название приложения
DJANGODIR=/var/www/.virtualenvs/talos/talos # Директорая проекта - путь к виртуальному окружению
                                            # плюс папка с проектом
SOCKFILE=/var/www/sockets/talos.sock        # Тут будет лежать сокет
USER=www-data                               # От чьего имени запускается
GROUP=www-data                              # Группа для запуска
NUM_WORKERS=3                               # Кол-во воркеров, обычно число ядер * 2 + 1
DJANGO_SETTINGS_MODULE=talos.settings       # Откуда брать настройки
DJANGO_WSGI_MODULE=talos.wsgi               # Имя wsgi-файла для запуска

echo "Starting $NAME as `whoami`"

# Активация окружения
cd $DJANGODIR
source ../bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

# Если папки для сокета нет, её надо создать
RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR

# Запуск через gunicorn с передачей параметров
exec ../bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $NUM_WORKERS \
  --user=$USER --group=$GROUP \
  --bind=unix:$SOCKFILE \
  --log-level=debug \
  --log-file=/var/www/talos/log/talos.log
  

Создание виртуального окружения

Последние версии Django написаны на Python 3, да и вообще использовать Python 2 в 2015 году - дурной тон. Ставим нужные пакеты, если их ещё нет:

Установка пакетов
apt-get install python3-dev virtualenvwrapper -y

Первый пакет нужен для сборки пакетов, поставляемых в исходных кодах (lxml, psycopg2, pillow), второй - для удобного управления виртуальными окружениями. Пакеты установлены, пользователь www-data в системе. Пришло время создать окружение и поставить нужные пакеты:

Создание окружения, установка пакетов
mkvirtualenv talos --python=/usr/bin/python3
workon talos

В результате в каталоге для виртуальных окружений (у каждого пользователя свой, по умолчанию называется .virtualenvs) будет создан подкаталог talos. В нём будут размещены необходимые для работы с окружением скрипты и несколько других каталогов. Разместим наш проект внутри каталога /var/www/.virtualenvs/talos.

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

Не забудьте создать статику и при необходимости подкорректировать файл настроек приложения.

Запуск через supervisor

Если supervisor ещё не установлен, пришло время это сделать:

Установка supervisor
apt-get install supervisor -y

Настройки хранятся в каталоге /etc/supervisor. Главный файл называется supervisor.conf, файлы для запуска приложений следует расположить в каталоге /etc/supervisor/conf.d/, указав расширение .conf. В нашем случае файл будет лежать по пути /etc/supervisor/conf.d/talos.conf

/etc/supervisor/conf.d/talos.conf
[program:talos]
command=/var/www/talos/run.bash
user=www-data
group=www-data

autostart=true
autorestart=true

redirect_stderr=true
stdout_logfile=/var/www/talos/log/supervisor.log

Когда файл будет создан, следует обновить данные Supervisor'а:

supervisorctl update

При необходимости перезапустить то или иное приложение следует вызывать не перезапуск системной службы supervisor, а давать команды supervisorctl:

Управление Supervisor'ом
supervisorctl update        # Перечитать файлы конфигации приложений
supervisorctl start talos   # Запустить приложение talos
supervosorctl stop talos    # Остановить приложение talos
supervisorctl restart talos # Перезапуск приложения talos
supervisorctl status        # Посмотреть статус всех приложений
supervisorctl ДЕЙСТВИЕ all  # Выполнить ДЕЙСТВИЕ со всеми приложениями, например, перезапуск

Если всё сделано правильно, приложение будет запущено, а в каталоге /var/www/sockets/ появится файл сокета talos.sock (как было настроено в файле run.bash).

Подключение к nginx

Всё, что нужно теперь сделать - указать nginx путь к сокету и откуда брать статику для проекта. В общем-то, всё довольно просто. Nginx лучше ставить из официального репозитория проекта, а не из Debian'овского, там пакет не обновляется годами.

apt-get install nginx -y

Все настройки в каталоге /etc/nginx, главный файл - nginx.conf, настройки для сайтов в .conf-файлах, лежащих в каталоге /etc/nginx/conf.d/.

Создадим файл настроек, общих для всех сайтов: /etc/nginx/proxy_params.conf:

/etc/nginx/proxy_params.conf
proxy_redirect                          off;
proxy_set_header Host                   $http_host;
proxy_set_header X-Real-IP              $remote_addr;
proxy_set_header X-Url-Scheme           $scheme;
proxy_set_header X-Forwarded-For        $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto      $scheme;
client_max_body_size                    20m;
client_body_buffer_size                 1m;
proxy_buffering                         off;
proxy_send_timeout                      180;
proxy_read_timeout                      180;
proxy_connect_timeout                   180;
proxy_buffer_size                       4k;
proxy_buffers                           32 32k;
proxy_busy_buffers_size                 64k;
proxy_temp_file_write_size              1m;
add_header X-Frame-Options "SAMEORIGIN";

Возможно, данные настройки придётся доработать исходя из реальной конфигурации сервера. Здесь я на этом останавливаться не буду. Переходим к созданию файла конфигурации для нашего проекта:

/etc/nginx/conf.d/talos.conf
upstream talos {
    server unix:/var/www/sockets/talos.sock fail_timeout=0;
    keepalive 30;
}

server {
    listen 80;
    server_name talos.lo;
    keepalive_timeout 3;
    access_log off;
    error_log /var/www/talos/log/nginx_error.log crit;

    location / {
        proxy_pass http://talos/;
        include /etc/nginx/proxy_params.conf;
    }

    location /static/ {
        alias /var/www/talos/static/;
        expires 3d;
    }

    location /media/ {
        alias /var/www/talos/media/;
           expires 3d;
    }

    location ~* \.(7z|jpg|jpeg|gif|png|ico|css|bmp|swf|js|html|txt|doc|docx|pdf|rar|xls|xlsx|zip)$ {
        root /var/www/talos/;
        expires 3d;
        add_header Cache-Control: public;
        access_log off;
        error_log /var/www/talos/log/nginx_static_error.log;
    }
}

Перезапустите nginx, тобы он подхватил новый конфигурационный файл. На этом всё.

Обо всех ошибках или дополнения прошу писать мне на почту dunmaksim@yandex.ru

Django: пути к шаблонам

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

Структура каталогов для шаблонов
/template
    admin/
        index.html
        articles/
            add.html
            detail.html
            list.html        
        news/
            add.html
            detail.html
            list.html
    desktop/
        index.html
        articles/
            add.html
            detail.html
            list.html        
        news/
            add.html
            detail.html
            list.html
    urls.py

Есть много вариантов того, как написать urls.py, но я написал так:

Использование генератора для создания urlpatterns
from os.path import join

from django.conf.urls import include
from django.conf.urls import url
from django.views.generic import TemplateView


def template_url(folder, template):
    return url(
        '^' + template + '.html$',
        TemplateView.as_view(template_name=(join(folder, template) + '.html'))
    )


def urls_list(prefix, urls_list):
    return [template_url(prefix, item) for item in urls_list]

admin = urls_list('admin', [
    r'index.html',
    r'articles/add',
    r'articles/list',
    r'articles/detail',
    r'news/add',
    r'news/list',
    r'news/detail',
])

desktop = urls_list('admin', [
    r'index.html',
    r'articles/add',
    r'articles/list',
    r'articles/detail',
    r'news/add',
    r'news/list',
    r'news/detail',
])

urlpatterns = admin + desktop

Данная простая конструкция заменяет огромные полотна такого вида:

Решение проблемы "в лоб"
from django.conf.urls import include
from django.conf.urls import url
from django.views.generic import TemplateView


admin = template_url('admin', [
    url('^index.html$', TemplateView.as_view(template_name='admin/index.html')),
    url('^articles/add.html$', TemplateView.as_view(template_name='admin/articles/add.html')),
    url('^articles/list.html$', TemplateView.as_view(template_name='admin/articles/list.html')),
    url('^articles/list.html$', TemplateView.as_view(template_name='admin/articles/list.html')),
    url('^news/add.html$', TemplateView.as_view(template_name='admin/news/add.html')),
    url('^news/list.html$', TemplateView.as_view(template_name='admin/news/list.html')),
    url('^news/list.html$', TemplateView.as_view(template_name='admin/news/list.html')),
])

desktop = template_url('desktop', [
    url('^index.html$', TemplateView.as_view(template_name='desktop/index.html')),
    url('^articles/add.html$', TemplateView.as_view(template_name='desktop/articles/add.html')),
    url('^articles/list.html$', TemplateView.as_view(template_name='desktop/articles/list.html')),
    url('^articles/list.html$', TemplateView.as_view(template_name='desktop/articles/list.html')),
    url('^news/add.html$', TemplateView.as_view(template_name='desktop/news/add.html')),
    url('^news/list.html$', TemplateView.as_view(template_name='desktop/news/list.html')),
    url('^news/list.html$', TemplateView.as_view(template_name='desktop/news/list.html')),
])

urlpatterns = admin + desktop

Последняя версия NodeJS через NPM

Наткнулся в сети на очень интересный способ обновления NodeJS до последней версии, не прибегая к услугам пакетного менеджера ОС. Ссылки на статью и оригинал:

В итоге у меня теперь ещё один фид в читаемых RSS.

Отмечу лишь, что установленную через пакетный менеджер версию нужно сначала вычистить из системы, так же рекомендуется удалить каталог /usr/local/lib/node_modules/. Вот команды (NodeJS должен быть установлен, желательно - собран из исходников, это не так уж и сложно):

От имени root
npm cache clean -f
npm install -g n
n stable

EMACS: Автоматическая установка пакетов

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

  • Он прост и понятен
  • Он работает

Чтобы не раздувать статью, сразу приведу начальную часть своего .emacs:

.emacs
;;; Источники для установки пакетов
(setq package-archives '(
                         ("gnu" . "http://elpa.gnu.org/packages/")
                         ("melpa" . "http://melpa.milkbox.net/packages/")
                         ("org" . "http://orgmode.org/elpa/")
                         ))

;;; Список пакетов, которые нужно установить (на самом деле я не уверен, что все
;;; перечисленнные пакеты мне нужны)
(setq package-list '(
       2048-game          
       ac-emmet
       ac-html
       ac-html-bootstrap
       ac-html-csswatcher
       airline-themes
       auto-complete
       dired+
       dired-rainbow
       dired-subtree
       dired-toggle
       direx
       dirtree
       emmet-mode
       ergoemacs-mode
       flycheck
       flycheck-pos-tip
       flycheck-pyflakes
       gh-md
       git-commit
       git-gutter
       git-lens
       highlight-indentation
       idomenu
       jedi
       jedi-core
       js2-mode
       js2-refactor
       json-mode
       json-reformat
       less-css-mode
       magit
       magit-popup
       markdown-mode
       monokai-theme
       multiple-cursors
       neotree
       paredit
       paredit-everywhere
       popup
       pos-tip
       powerline
       py-autopep8
       py-isort
       python-environment
       python-mode
       pyvenv
       rainbow-delimiters
       rainbow-identifiers
       rainbow-mode
       smartparens
       tern
       tide
       tree-mode
       typescript-mode
       undo-tree
       virtualenvwrapper
       web-beautify
       web-completion-data
       web-mode
       yasnippet))

(package-initialize)

;;; Обновим список пакетов
(unless package-archive-contents
  (package-refresh-contents))

;;; Пробежимся по списку. Если чего-то нет - устанавливаем
(dolist (package package-list)
  (unless (package-installed-p package)
    (package-install package)))

После того, как в начало вашего .emacs будут добавлены указанные строки и редактор перезапущен, он установит все недостающие пакеты из списка автоматически.

Angular Material и md-list - проблема с дополнительным действием

В Angular Material есть такой хороший компонент - md-list, и работающий с ним в паре md-list-item. Из них можно делать красивые списки, обладающие весьма важными свойствами. Во-первых, каждая строка реагирует на нажатие. Можно реализовать возможность перехода по ссылке. Во-вторых, к каждой строке можно добавить кнопку действия.

Это я всё к чему? А к тому, что сегодня почти час убил на то, чтобы разобраться, почему скопированный почти один в один пример с официальной доки работает у них и не работает у меня.

Разметка
<md-list>
    <md-list-item ng-repeat="item in items" ng-click="openDetail(item)">
        <img class="md-avatar" alt="" src=""/>
 <p>{{ item.name }}</p>
        <md-icon class="material-icons md-secondary md-warn" ng-click="remove(item)">remove</md-icon>
    </md-list-item>
</md-list>
Вся проблема заключалась в классах CSS для тега md-icon. Чтобы он превратился в кнопку, ему должен быть назначен среди прочих класс md-secondary.

Если кому интересно, то ниже код контроллера. Обратите внимание, я не отлавливаю объект события и не вызываю для него stopPropagation() и preventDefault(), это не требуется.

ctrl.js
(function (A) {
    "use strict";

    var inject = [
        '$location',
        '$mdDialog',
        '$scope'
    ];

    function Ctrl(
        $location,
        $mdDialog,
        $scope
    ){
        // Ничто не мешает загружать записи с сервера
        $scope.items = [
            {id: 1, name: 'Запись №1'},
            {id: 2, name: 'Запись №2'},
            {id: 3, name: 'Запись №3'},
            {id: 4, name: 'Запись №4'}
        ];

        function openDetail(item){
            // Переход на другой вид
            $location.path('/items/' + item.id);
        }

        function remove(item){
     // Запрос на удаление записи
            $mdDialog.show(
                $mdDialog
                    .confirm()
                    .title("Подтверждение")
                    .content('Удалить "' + item.name + '"?')
                    .ok("Да")
                    .cancel("Нет")
                ).then(function () {
                    $scope.items = $scope.items.splice($scope.items.indexOf(item), 1);
            });
        }

        $scope.openDetail = openDetail;
        $scope.remove = remove;  
    }

    Ctrl.$inject = inject;

    A.module('app').controller('Ctrl', Ctrl);
}(this.angular));

Сборка EMACS из исходников

Ради того, чтобы хоть как-то ускорить свой долгострой, я решил пока переключить диски - вместо SSD с Windows 8.1 у меня теперь старый Maxtor LS-120 с Linux Mint.

Репозитории он берёт от Ubuntu LTS, которая на данный момент 14.04. Trusty. Всё почти хорошо, но EMACS там старый, а значит, magit из MELPA не работает, т.к. ему требуется версия 24.4 и выше, а так же git версии 2 и выше. Впрочем, проблема легко решается.

Скачиваем пакет с исходным кодом с официального FTP проекта GNU:

Скачивание исходных кодов
wget ftp://ftp.gnu.org/gnu/emacs/emacs-24.5.tar.xz
tar xf emacs-24.5.tar.xz

Хорошо, распаковали, но EMACS не соберётся, если в системе нет вот этих пакетов:

  • libgtk-3-dev - для интеграции с X-ами, так же разработчики пишут, что лучше использовать заголовки от второй версии GTK, т.к. с этой могут быть проблемы
  • libxpm-dev
  • libtiff5-dev
  • libgif-dev
  • libtinfo-dev
  • libncurses5-dev
  • libacl1-dev

Ставится всё одной командой:

Установка необходимых для сборки пакетов
apt-get install libgtk-3-dev libxpm-dev libtiff5-dev libgif-dev libtinfo-dev libncurses5-dev libacl1-dev -y

После установки можно запустить .configure и make:

Конфигурирование и сборка
cd emacs-24.5/
./configure && make && make install

Процесс начнётся. Если проверка зависимостей пройдёт успешно, будет запущена компиляция проекта, а затем его установка. Однако, в главном меню не появится значка для запуска EMACS, как это происходит при установке через aptitude или apt-get install. Добавим его вручную. Всего лишь нужно создать файл формата .desktop в каталоге /usr/share/applications:

Создание ярлыка для EMACS
cd /usr/share/applications/
touch emacs.desktop

Теперь в этот файл нужно вписать следующие строки:

/usr/share/applications/emacs.desktop
[Desktop Entry]
Version=24.5
Name=GNU Emacs
Type=Application
Comment=GNU Emacs text editor
Terminal=false
Exec=emacs
Icon=emacs
Categories=TextEditor;
GenericName=GNU Text Editor

После сохранения и перезапуска DE (можно выйти из системы и войти снова) ярлык появится в главном меню.

P. S. Что касается свежести Git:

Установка новой версии Git:
apt-add-repository git-core/ppa
apt-get update
apt-get install git -y

LESS для Google Material Icon Font

У Google для Web-разработки с использованием Angular Material есть даже специальный набор иконок, а так же репозиторий на GitHub с возможностью установки через Bower. Там всё хорошо, но вот CSS для иконок приходится по кускам собирать из официальной документации. Тут я и публикую такой LESS/CSS, собранный собственноручно по результатам чтения официальных доков.

Установка через Bower

bower install material-design-icons

Помимо шрифта в архиве куча иконок в разных форматах, так что будьте осторожны - bower скачает около 30 Мб, а потом будет его некоторое время распаковывать.

У меня все сторонние библиотеки хранятся в каталоге static/libs/, вам же следует изменить пути к шрифтам (переменная @BASE_PATH) на подходящие.

material-icons.less

@BASE_PATH: '/static/libs/material-design-icons/iconfont/MaterialIcons-Regular.';
@font-face {
    font-family: 'Material Icons';
    font-style: normal;
    font-weight: 400;
    src: url("@{BASE_PATH}eot");
    /* For IE6-8 */
    src: local('Material Icons'),
         local('MaterialIcons-Regular'),
         url("@{BASE_PATH}woff2") format('woff2'),
         url("@{BASE_PATH}woff") format('woff'),
         url("@{BASE_PATH}ttf") format('truetype');
}

.material-icons {
    font-family: 'Material Icons';
    font-weight: normal;
    font-style: normal;
    font-size: 24px;
    /* Preferred icon size */
    display: inline-block;
    width: 1em;
    height: 1em;
    line-height: 1;
    text-transform: none;
    letter-spacing: normal;
    word-wrap: normal;
    -webkit-font-smoothing: antialiased; /* Support for all WebKit browsers. */
    text-rendering: optimizeLegibility;  /* Support for Safari and Chrome. */
    -moz-osx-font-smoothing: grayscale;  /* Support for Firefox. */
    font-feature-settings: 'liga';       /* Support for IE. */
}

.material-icons.md-18 { font-size: 18px; }
.material-icons.md-24 { font-size: 24px; }
.material-icons.md-36 { font-size: 36px; }
.material-icons.md-48 { font-size: 48px; }

// Rules for using icons as black on a light background.
.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); }
.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); }

// Rules for using icons as white on a dark background.
.material-icons.md-light { color: rgba(255, 255, 255, 1); }
.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); }

Работает данный шрифт через лигатуры. В отличие от FontAwesome, который оперирует классами для тегов <span> и <i>, здесь нужно использовать и класс, и лигатуру:

Пример использования

<md-icon>
    <i class="material-icons md-24">menu</i>
</md-icon>

Полный список лигатур находится в каталоге material-design-icons/iconfont/codepoints. Так же есть отдельный ресурс с описанием и показом всех иконок.