CSRF-токены и CBV, требующие авторизации
Всё, что нужно знать о CSRF-токенах в Django
Ниже идёт код, который позволяет прописать в шаблон Cookies с CSRF-токеном:
from django.core.context_processors import csrf
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.views.generic import View
class Index(View):
def get(self, request):
context = {}
context.update(csrf(request))
return render_to_response(
'index.html',
RequestContext(request, context)
)
В сам шаблон нужно не забыть включить одну важную строку:
{% csrf_token %}
Без этой строки печенька покрошена на страницу НЕ БУДЕТ. Не знаю, почему, просто вот такой интересный факт.
Миксин для CBV (Class Based Views), которым нужна авторизация:
from os import path
from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf
from django.shortcuts import render_to_response
from django.template.context import RequestContext
from django.views.generic import View
class LoginRequiredMixin(object):
"""Собственно примесь """
@classmethod
def as_view(cls, **initkwargs):
view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
return login_required(view, login_url='/login/')
class ProfileView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
c = {}
c.update(csrf(request))
user = request.user
template_path = path.join(
'admin',
'index.html',
)
return render_to_response(
template_path,
RequestContext(request, c)
)
EMACS для Python
Введение
Решил наконец написать статью о том, как настроить EMACS для удобной работы с Python'ом. Ниже список рассматриваемых вопросов:
- Разбиение конфигурации на части
- Пакеты
- Ergoemacs
- Anaconda-mode
Разбиение конфигурации на части
Долгое время мой .emacs
представлял собой всего один файл. Естественно, править его было не очень удобно. С течением времени он рос и становился толще, наконец, в определённый момент поддерживать его стало совершенно невозможно. К счастью, я подсмотрел на StackOverflow, как можно разбить его на несколько маленьких частей.
Для подгрузки параметров из других файлов нужно в .emacs
написать следующее:
;;; package --- Summary
;;; Commentary:
;;; Main EMACS settings file, load settings from parts.
;;; Code:
(load "~/.emacs.d/fonts.el")
(load "~/.emacs.d/packages.el")
(load "~/.emacs.d/ergoemacs.el")
(load "~/.emacs.d/personal.el");
(load "~/.emacs.d/keyboard.el")
(load "~/.emacs.d/variables.el")
(load "~/.emacs.d/flycheck.el")
(load "~/.emacs.d/rust.el")
(load "~/.emacs.d/python.el")
(load "~/.emacs.d/web.el")
;;; .emacs ends here
Здесь load
- функция, выполняющая загрузку указанного файла. Сам файл находится в каталоге .emacs.d/ и называется fonts.el. Туда я положил настройки шрифтов. Затем идёт подгрузка файла, отвечающего за список пакетов. При необходимости недостающие пакеты будут установлены из MELPA, MELPA-STABLE или другого из указанных репозиториев, впрочем, об этом я уже писал ранее.
Я не буду давать рекомендаций по поводу того, на сколько частей нужно разбивать конфигурацию, как эти части называть и так далее, каждый решает сам, потому что, например, несколько дней назад я не видел необходимости выносить настройки шрифтов отдельно, а сейчас решил, что так будет лучше. Как видите, за настройки для разработки на Python'е отвечает файл .emacs.d/python.el, в него я поместил все нужные параметры EMACS'а.
Пакеты
Как я уже говорил, автоматическую установку пакетов я описывал в другой статье, ниже перечислю лишь те пакеты, которые понадобятся, а так же дам краткое описание того, для чего они нужны.
anaconda-mode
Данный пакет служит для поддержки функциональности IDE при работе с файлами Python'а, как то быстрый переход к декларациям, вывод документации и т.д.
company
Данный пакет рекомендуется как более современная замена пакету auto-complete. Ничего не имею против последнего, но лично мне использовать указанный приятнее.
company-anaconda
Автодополнение в режиме anaconda-mode, без этого подсказки работать не будут.
ergoemacs-mode
В общем-то, не скажу, что этот пакет жизненно необходим, но он даёт необходимые удобства, например, упрощает работу с командами, заменяя стандартные хоткеи EMACS'а на свои, более близкие к тем, которыми пользуются нормальные люди, например, комбинации Ctrl+C и Ctrl+V, а так же многие другие начинают работать так, как это принято в подавляющем большинстве современных программ, т.е. копируют и вставляют текст. Подробнее можно почитать на сайте проекта.
flycheck, flycheck-pos-tip
Стандарт де-факто для проверки синтаксиса. Какой именно синтаксис и чем будет проверяться - настраивается. По умолчанию для Python'а используются flake8, pylint и pycompile. Естественно, чтобы данные средства работали, они должны присутствовать в системе.
helm
Система подсказок. Допустим, нажали Вы Alt+X, начинаете вводить lis, а он сразу выдаёт отфильтрованные команды, в которых присутствует данная строка. Можно стрелками выбрать нужную строку и нажать Enter. Умеет подсказывать не только команды, но так же имена файлов, виртуальные окружения и многое другое, легко расширяется. Практически незаменимое средство.
neotree
Пакет просто выводит дерево файлов слева. Можно забиндить отображение / скрытие на горячую клавишу, работать станет в разы удобнее.
python-mode
Базовый пакет для поддержки EMACS'ом Python'а.
py-autopep8
Пакет позволяет применить autopep8 - средство для автоформатирования кода по стандартам. Можно настроить на автоформат кода при сохранении файла. Удобно, хотя в старых проектах, где код писан ногами, порождает тонны баттхёрта у тех, кто будет делать ревью вашего кода.
py-isort
Сортирует импортированные файлы. Можно передать разные настройки. Лично я предпочитаю импорт 1-1, т.е. одна строка - один модуль или класс. Рекомендую, т.к. в этом случае с помощью flycheck будет проще удалять неиспользованные модули - выделил строку, нажал Backspace, сохранил.
pyvenv, virtualenvwrapper
Позволяет EMACS'у видеть виртуальные окружения и нормально с ними работать.
pip-requirements
Небольшой пакет для удобного редактирования файла зависимостей. Если кто не в курсе - файл REQUIREMENTS
создаётся в корне Python-проекта и позволяет в одну команду установить все нужные для работы зависимости:
pip install -r REQUIREMENTS -U
В чём плюс данного пакета? Он выдаёт подсказки по именам, подгружая список с PyPi - главного всемирного рассадника питонячьих пакетов.
Установка через pip (от имени root)
pip install autopep8 flake8 isort pylint -U
Ergoemacs
Данный пакет довольно активно развивается и предоставляет кучу удобств. Вся его конфигурация у меня вынесена в отдельный файл, и чтобы далеко не ходить, публикую его ниже:
;;; Package --- Summary
;;; Comment:
;;; Settings for ergoemacs-mode
;;; Code:
(require 'ergoemacs-mode)
;;; Ergoemacs
(setq ergoemacs-theme nil)
(setq ergoemacs-keyboard-layout "us")
(setq ergoemacs-ini-mode t)
(setq ergoemacs-use-menus t)
(setq ergoemacs-smart-paste nil)
(setq ergoemacs-ctl-c-or-ctl-x-delay 0.3)
(ergoemacs-mode 1)
;;; ergoemacs.el ends here
Anaconda-mode
Тут речь не только об anaconda-mode, ниже приводится весь файл настроек для Python. Надеюсь, комментарии помогут в понимании данных настроек
;;; Package --- Summary
;;; Commentary:
;;; Settings for Python
;;; Code:
;;; Импорт необходимых модулей
(require 'py-autopep8)
(require 'py-isort)
(require 'pip-requirements-mode)
;;; Псевдоним для команды pyvenv-workon, пользователи virtualenv оценят удобство
(defalias 'workon 'pyvenv-workon)
;;; Автоматически загружать модуль python-mode, писать в статус баре "Python-mode"
(autoload 'python-mode "python-mode" "Python mode." t)
;;; Применять python-mode для файлов с расширением .py
;;; Использовать интерпретатор python для python-mode
(add-to-list 'auto-mode-alist '("\\.py\\'" . python-mode))
(add-to-list 'interpreter-mode-alist '("python" . python-mode))
(add-to-list 'auto-mode-alist '("\\REQUIREMENTS" . pip-requirements-mode))
(add-to-list 'auto-mode-alist '("\\REQUIREMENTS.txt" . pip-requirements-mode))
(add-to-list 'auto-mode-alist '("\\requirements" . pip-requirements-mode))
(add-to-list 'auto-mode-alist '("\\requirements.txt" . pip-requirements-mode))
;;; Подсвечивать строки, которые обычно используются при отладке
(defun annotate-pdb()
(interactive)
(highlight-lines-matching-regexp "import ipdb")
(highlight-lines-matching-regexp "import pdb")
(highlight-lines-matching-regexp "set_trace()")
(highlight-phrase "TODO")
(highlight-regexp "FIXME")
(highlight-regexp "BUG")
)
;;; В Python-mode автоматически включать anaconda-mode, pdb и
;;; автоматически применять autopep8 при сохранении файла
(add-hook 'python-mode-hook 'anaconda-mode)
(add-hook 'python-mode-hook 'annotate-pdb)
(add-hook 'python-mode-hook 'py-autopep8-enable-on-save)
;;; Перед сохранением так же отсортировать импортированные модули.
;;; Правило сортировки: одна строка - один модуль
(add-hook 'before-save-hook 'py-isort-before-save)
(setq py-isort-options '("-sl"))
;;; python.el ends here
Теперь следует перезапустить EMACS, чтобы он подхватил настройки, и начать работать. Ниже приводится вид моего EMACS'а, когда я пишу на Python.
Конфиг на GitHubОшибка "memmove does not exist on this platform"
Который раз вижу вот эту ошибку:
extensions/expat/lib/xmlparse.c:75:2: error: #error memmove does not exist on this platform, nor is a substitute available
Решение очень легко гуглится на StackOverflow, но для себя я решил сохранить, чтобы далеко не ходить. Проблема заключается в том, что в одном из заголовочных файлов Python 2.7 не определена константа HAVE_MEMMOVE
Решение
- Открыть файл /usr/include/python2.7/pyconfig.h
-
Добавить в его конец строку
#define HAVE_MEMMOVE 1
Послесловие: хватит уже пользоваться Python 2.7, Гвидо не для того старался с тройкой.
md-autocomplete со значением по умолчанию
Задача
С помощью Angular Material создать autocomplete со значением по-умолчанию. Статья навеяна вопросом со StackOverflow.
Самому как раз сегодня понадобилось решить эту же задачу. Результат можно посмотреть здесь.
Решение
HTML-разметка
<body ng-app="app" flex layout="column" layout-margin ng-controller="Main">
<md-content layout="column" class="md-whiteframe-z1" layout-margin>
<md-toolbar>
<div class="md-toolbar-tools">
<h3>Form</h3>
</div>
</md-toolbar>
<md-content class="md-whiteframe-z1">
<div class="md-padding">
<md-input-container>
<label for="firstname">First name</label>
<input type="text" name="firstname" ng-model="user.firstname" />
</md-input-container>
<md-input-container>
<label for="lastname">Last name</label>
<input type="text" name="lastname" ng-model="user.lastname" />
</md-input-container>
<md-autocomplete md-selected-item="user.group" md-items="item in loadGroups(filterText)" md-item-text="item.title" md-search-text="filterText">
<md-item-template>{{ item.title }}</md-item-template>
<md-not-found>No items.</md-not-found>
</md-autocomplete>
</div>
</md-content>
</md-content>
<md-content class="md-whiteframe-z1" layout-margin>
<md-toolbar>
<div class="md-toolbar-tools">
<h3>Model as JSON</h3>
</div>
</md-toolbar>
<md-content class="md-padding">
<p>
{{ user | json }}
</p>
</md-content>
</md-content>
</body>
JavaScript
(function(A) {
"use strict";
var app = A.module('app', ['ngMaterial']);
function main(
$q,
$scope,
$timeout
) {
$timeout(function() {
$scope.user = {
firstname: "Maxim",
lastname: "Dunaevsky",
group: {
id: 1,
title: "Administrator"
}
};
}, 500);
$scope.loadGroups = function(filterText) {
var d = $q.defer(),
allItems = [{
id: 1,
title: 'Administrator'
}, {
id: 2,
title: 'Manager'
}, {
id: 3,
title: 'Moderator'
}, {
id: 4,
title: 'VIP-User'
}, {
id: 5,
title: 'Standard user'
}];
$timeout(function() {
var items = [];
A.forEach(allItems, function(item) {
if (item.title.indexOf(filterText) > -1) {
items.push(item);
}
});
d.resolve(items);
}, 1000);
return d.promise;
};
}
main.$inject = [
'$q',
'$scope',
'$timeout'
];
app.controller('Main', main);
}(this.angular));
Теперь немного о том, что происходит.
Сначала происходит инициализация модели. В примере задержка ответа эмулируется с помощью $timeout, на практике следует использовать подгрузку данных с помощью $http или $resource. При этом поле, значение которого должно быть связано с полем автодополнения, уже инициализировано, при этом в нём находится целая модель. Вопрос, как получить основную модель, содержащую вложенное поле, излишен. Этим должен заниматься сервер. Например, в Django REST Framework для таких полей используются сериализаторы объектов.
На втором этапе идёт описание способов подгрузки элементов в список. Ну, тут ничего сложного, в общем-то. С помощью того же $timeout эмулируется задержка, во время которой идёт фильтрация элементов списка. Функция возвращает promise, который разрешается списком отфильтрованных записей, когда приходит время.
EMACS: автоустановка пакетов и хоткеи в русской раскладке
Введение
Наткнулся совершенно случайно на две отличных статьи про EMACS.
Если лень читать, то ниже приводятся готовые коды для включения в .emacs
Автоустановка пакетов
(require 'cl)
(require 'package)
;; Список пакетов для установки, на самом деле у меня их под 50 штук, но смысл
;; публиковать их здесь?
(defvar cfg-var:packages '(
anaconda-mode
company
company-anaconda
company-quickhelp
company-tern
company-web
emmet-mode
ergoemacs-mode
flycheck
powerline
py-autopep8
py-isort
web-mode
yafolding
yasnippet
))
(defun cfg:install-packages ()
(let ((pkgs (remove-if #'package-installed-p cfg-var:packages)))
(when pkgs
(message "%s" "Emacs refresh packages database...")
(package-refresh-contents)
(message "%s" " done.")
(dolist (p cfg-var:packages)
(package-install p)))))
(add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t)
(package-initialize)
(cfg:install-packages)
Рабочее решение, которое гораздо лучше тех, что предложены на StackOverflow.
Хоткеи в русской раскладке
;; Это надо добавить ближе к началу .emacs
(defun cfg:reverse-input-method (input-method)
"Build the reverse mapping of single letters from INPUT-METHOD."
(interactive
(list (read-input-method-name "Use input method (default current): ")))
(if (and input-method (symbolp input-method))
(setq input-method (symbol-name input-method)))
(let ((current current-input-method)
(modifiers '(nil (control) (meta) (control meta))))
(when input-method
(activate-input-method input-method))
(when (and current-input-method quail-keyboard-layout)
(dolist (map (cdr (quail-map)))
(let* ((to (car map))
(from (quail-get-translation
(cadr map) (char-to-string to) 1)))
(when (and (characterp from) (characterp to))
(dolist (mod modifiers)
(define-key local-function-key-map
(vector (append mod (list from)))
(vector (append mod (list to)))))))))
(when input-method
(activate-input-method current))))
;; А вот эта строка должна быть в самом конце
(cfg:reverse-input-method 'russian-computer)
Немного о Material Design Lite и Angular Material
Хочу сказать пару слов про библиотеку Material Design Lite (MDL).
TL;DR Если нужны только стили, сгодится. Если требуется качественный JS и интеграция с Angular - посмотрите в сторону Angular Material. Впрочем, в своих проектах я использую их вместе, это не так уж сложно.
Стили
Что касается стилей, тут всё довольно неплохо. Во-первых, даже при установке через bower скачиваются исходные файлы. Можно открыть нужный .scss и внести правки. Не уверен, что многие часто пользуются этой возможностью, но в любом случае она есть, и для этого даже не нужно клонировать репозиторий с GitHub.
В библиотеке используется БЭМ от Яндекса. Может, подход и хорош, но я пока не оценил всех его преимуществ и он кажется мне несколько многословным. Директивы или полифиллы мне как-то ближе, впрочем, даже к такой "разговорчивости" фреймворка привыкаешь довольно быстро.
Очень даже хорошо, что из коробки идёт поддержка стилей для таблиц, карточек, панели навигации и некоторых других объектов. По сравнению с Angular Material - шаг вперёд.
JS
А вот с JS не всё гладко. Во-первых, поведение меню. В Angular Material разработчики предусмотрели позиционирование таким образом, чтобы оно не уезжало за экран. В MDL приходится самому заботиться об этом и подбирать нужные стили. Впрочем, это не такая уж большая проблема. Есть кое-что ещё.
MDL выполняет обновление компонентов и установку обработчиков на них только в момент загрузки страницы. Да, он дожидается DOMReady, после чего выполняет заполнение свойств элементов. При использовании какого-либо роутинга в Angular сразу появляется проблема. Решается довольно легко, спасибо StackOverflow:
Application.run(['$rootScope', '$location', '$timeout', function (
$rootScope,
$location,
$timeout
) {
$rootScope.$on("$viewContentLoaded", function () {
$timeout(function () {
componentHandler.upgradeAllRegistered();
});
});
}]);
Нет некоторых нужных полей, например, поле для выбора даты, которое появилось в Angular Material 0.11.0. Впрочем, обе библиотеки прекрасно работают вместе.
ng-view
, без использования ng-include
или сторонних вещей типа ui-route.Django 1.8, Python 3, WSGI и Gunicorn
Введение
В данной статье рассказывается, как можно запустить приложение на Django и Python 3 под WSGI. Способ не претендует на звание самого лучшего, замечания по настройке и прочим нюансам приветствуются.
Структура каталогов
Следует создать в каталоге /var/www подкаталог для нашего проекта. Пусть сегодня он будет называться talos. В нём нужно будет создать каталоги для размещения статичных файлов, файлов, загружаемых пользователями, и логов.
cd /var/www
mkdir talos
cd talos/
mkdir static media log
Сам проект будет расположен в виртуальном окружении, которое будет создано чуть позже.
Создание файла запуска
Здесь же, в каталоге /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 ещё не установлен, пришло время это сделать:
apt-get install supervisor -y
Настройки хранятся в каталоге /etc/supervisor. Главный файл называется supervisor.conf, файлы для запуска приложений следует расположить в каталоге /etc/supervisor/conf.d/, указав расширение .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:
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:
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";
Возможно, данные настройки придётся доработать исходя из реальной конфигурации сервера. Здесь я на этом останавливаться не буду. Переходим к созданию файла конфигурации для нашего проекта:
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, тобы он подхватил новый конфигурационный файл. На этом всё.
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, но я написал так:
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 должен быть установлен, желательно - собран из исходников, это не так уж и сложно):
npm cache clean -f
npm install -g n
n stable
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>
md-icon
. Чтобы он превратился в кнопку, ему должен быть назначен среди прочих класс md-secondary
.Если кому интересно, то ниже код контроллера. Обратите внимание, я не отлавливаю объект события и не вызываю для него stopPropagation()
и preventDefault()
, это не требуется.
(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 из исходников
Скачиваем пакет с исходным кодом с официального FTP проекта GNU:
wget ftp://ftp.gnu.org/gnu/emacs/emacs-24.5.tar.xz
tar xf emacs-24.5.tar.xz
Для поиска более новой версии можно воспользоваться этой ссылкой:
Хорошо, распаковали, но EMACS'у нужна куча библиотек для успешной сборки. Можно поставить те, что нужны для сборки 24 версии, которая идёт в стандартной поставке Ubuntu 14.04:
apt-get install build-essentials -y && apt-get build-deb emacs24
После установки можно запустить .configure и make:
cd emacs-24.5/
./configure && make && make install
Процесс начнётся. Если проверка зависимостей пройдёт успешно, будет запущена компиляция проекта, а затем его установка. Однако, в главном меню не появится значка для запуска EMACS, как это происходит при установке через aptitude или apt-get install. Добавим его вручную. Всего лишь нужно создать файл формата .desktop в каталоге /usr/share/applications:
cd /usr/share/applications/
touch emacs.desktop
Теперь в этот файл нужно вписать следующие строки:
[Desktop Entry]
Version=24.5
Name=GNU Emacs
Type=Application
Comment=GNU Emacs text editor
Terminal=false
Icon=emacs
Categories=TextEditor;IDE
GenericName=GNU Text Editor
Exec=env UBUNTU_MENUPROXY=0 /usr/local/bin/emacs
После сохранения и перезапуска DE (можно выйти из системы и войти снова) ярлык появится в главном меню.
LESS для Google Material Icon Font
У Google для Web-разработки с использованием Angular Material есть даже специальный набор иконок, а так же репозиторий на GitHub с возможностью установки через Bower. Там всё хорошо, но вот CSS для иконок приходится по кускам собирать из официальной документации. Тут я и публикую такой LESS/CSS, собранный собственноручно по результатам чтения официальных доков.
Установка через Bower
bower install material-design-icons
Помимо шрифта в архиве куча иконок в разных форматах, так что будьте осторожны - bower скачает около 30 Мб, а потом будет его некоторое время распаковывать.
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. Так же есть отдельный ресурс с описанием и показом всех иконок.
Django Rest Framework - обновление поля типа ImageField
Убил сегодня полдня на решение этой проблемы. Чтобы не забыть, сразу же публикую всё здесь.
Исходные данные
Дано:
- Модель, имеющая поле типа
ImageField
- Django REST Framework
- ngFileUpload на фронте
Задача: сделать возможным загрузку изображений в указанное поле на основе Class-Based View в DRF.
Решение
Фронт-энд:
Вёрстка
<img ng-src="{$ item.logo200x200 $}" ng-model="logo" ngf-select ngf-change="uploadLogo(files)" accept="image/*" />
Да, всего одна строка. Вы можете поместить указанное изображение в любой подходящий контейнер, например, панель из Twitter Bootstrap.
Что делает этот код:
Параметр | Описание |
---|---|
ng-src="{$ item.logo200x200 $}" |
Связываем свойство модели и источник для нашего изображения. Делается через директиву Angular ng-src, как того советует официальная документация. На скобки в виде '{$' и '$}' не обращайте внимания. Т.к. на сервере используется стандартный шаблонизатор Django, приходится для Angular использовать другие скобки. |
ng-model="logo" |
Для выбора файлов будет использоваться отдельная модель - logo |
ngf-select |
Указываем, что данное изображение (можно использовать вообще-то что угодно) является полем ввода для плагина ngFileUpload |
ngf-change="uploadLogo(files)" |
При изменении значения поля выполняем указанную функцию. Загрузка без нажатия кнопки "Загрузить", в общем, достаточно лишь выбрать файл. |
accept="image/*" |
Разрешаем выбирать любые изображения. Фильтр для окна выбора файла. |
После того, как будет произведён клик по указанному изображению, откроется обычное окно открытия файла. Когда же файл будет выбран, запустится функция загрузки изображения:
LogoController.js
$scope.uploadLogo = function() {
if ($scope.logo.length < 1) {
return;
}
Upload.upload({
url: logoUrl, // /api/item/3/logo/
file: $scope.logo,
method: 'PATCH'
}).success(function(data) {
$scope.item.logo = data.logo;
});
};
Я описал лишь одну функцию контроллера. Надеюсь, догадаться, что нужно инжектировать $scope
и Upload
, не сложно.
file
- потом именно его будем обрабатывать на сервере.Бэк-энд
Нам понадобятся модель, отдельный сериализатор для логотипов и отдельное представление. Так же размеры всех логотипов следует нормализовать - не более 200px по большей стороне. Для этого можно написать отдельную функцию - resize_logo()
, принимающую как аргумент экземпляр нашей модели.
core.helpers.py
from PIL import Image
MAX_THUMBNAIL_SIZE = 200
def resize_logo(instance):
"""
Resize model logo to needed sizes.
"""
width = instance.logo.width
height = instance.logo.height
filename = instance.logo.path
max_size = max(width, height)
if max_size > MAX_THUMBNAIL_SIZE: # Да, надо изменять размер
image = Image.open(filename)
image = image.resize(
(round(width / max_size * MAX_THUMBNAIL_SIZE),
round(height / max_size * MAX_THUMBNAIL_SIZE)),
Image.ANTIALIAS
)
image.save(filename)
Пришло время описать саму модель, переопределив её метод save()
таким образом, чтобы при сохранении размеры изображения для логотипа нормализовались, как нам нужно:
core.items.models.py
from os import path
from django.db import models
from core.helpers import resize_logo
class ItemModel(models.Model):
name = models.CharField(
"Название",
max_length=255,
help_text='Максимум 255 знаков',
null=False,
blank=False
)
logo = models.ImageField(
"Логотип",
upload_to=path.join('item', 'logo'), # Отдельный каталог для аватаров
null=True,
blank=True,
)
def save(self, *args, **kwargs):
# Сначала модель нужно сохранить, иначе изменять/обновлять будет нечего
super(ItemModel, self).save(*args, **kwargs)
# Приводит размеры лого к одному виду - 200px по наибольшей стороне
if self.logo:
resize_logo(self)
class Meta:
app_label = 'core'
db_table = 'item'
verbose_name = 'элемент'
verbose_name_plural = 'элементы'
Теперь можно описать части, относящиеся к API - сериализатор, представление и часть конфигурации URL.
api.items.serializers.py
from rest_framework import serializers
from core.items.models import ItemModel
# Тут должны быть описаны остальные сериализаторы, сейчас же опускаю для краткости
class ItemLogoSerializer(serializers.ModelSerializer):
class Meta:
model = ItemModel
Как видно, сериализатор крайне прост. Опишем наше представление.
api.items.api.py
from rest_framework import permissions
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from core.items.models import ItemModel
from .serializers import ItemLogoSerializer
class ItemLogoAPIView(APIView):
permission_classes = [
permissions.IsAdminUser,
]
serializer_class = ItemLogoSerializer
# Обновление модели - методом PATCH, как я уже писал выше
def patch(self, *args, **kwargs):
# Находим нужную модель (по-хорошему надо обернуть в try ... except, но
# сейчас я этого делать не буду, чтобы не загромождать код)
instance = ItemModel.objects.get(pk=kwargs.get('pk'))
# Получаем из запроса наш файл (как указали выше, в JS)
instance.logo = self.request.FILES['file']
# Сохраняем запись (тут должна быть проверка значений встроенными в DRF
# методами, но сейчас я этого делать не буду)
instance.save()
# Возвращаем ответ - нашу сериализованную модель и статус 200
return Response(
ItemLogoSerializer(instance).data,
status=status.HTTP_200_OK
)
permission_classes
.Теперь - самое простое - конфигурация URL:
api.items.urls.py
from django.conf.urls import url
# Тут должен быть импорт остальных сериализаторов
from .api import ItemLogoAPIView
urlpatterns = [
# А здесь должны быть остальные URL (создание/получение/обнавление)
url(r'^(?P\d+)/logo/$', ServiceLogoAPIView.as_view()),
]
Ну что ж, всё выглядит не таким уж сложным. Пришло время закрыть вопросы на Toster'е и StackOverflow.
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));
Схема БД или таблицы в PostgreSQL
Всё время забываю, как в PostgreSQL сделать дамп схемы БД, без данных. Команда на удивление проста:
pg_dump -s -d %DATABASE_NAME% -U %USER_NAME% > %FILE_NAME%
Параметр | Назначение |
---|---|
-s |
Ключ указывает на то, что нужно сделать копию только схемы БД |
-d |
Ключ, задающий имя БД, с которой нужно работать. |
-U |
Ключ, указывающий пользователя, от имени которого будет делаться копия схемы. Пользователь должен иметь доступ хотя бы на чтение данной БД. |
%FILE_NAME% |
Файл, в который следует сохранить вывод. В противном случае вся схема будет выведена на экран. |
А если вместо -s
указать -t
и потом имя таблицы, то будет снята схема только с неё, например:
pg_dump -d project -U xphoenix -t auth_users > auth_users_shema.sql
Проблемы с кириллицей в имени пользователя Windows
При использовании Atom столкнулся с тем, что некоторые плагины отказывались работать, выдавая странные сообщения в консоль. Сначала я решал проблему тем, что правил переменную PATH на уровне системы и на уровне пользователя, однако, это особого эффекта не имело, т.к. стоило только поставить более новую версию io.js, как PATH тут же сбрасывалась к примерно такому виду:
C:\Python34\;C:\Python34\Scripts;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\Git\cmd;C:\Program Files (x86)\iojs\;C:\Users\%30o$321\AppData\Roaming\npm
Из этого видно, что портится путь к текущей системной папке пользователя. Изменение свойств папок "Видео", "Изображения", "Документы" и т.д. тут не может ничем помочь. Я использую учётную запись Microsoft со всеми её удобствами вроде OneDrive, WindowsPhone и т.д. и отказываться от неё в пользу локальной учётной записи не хотел (не за то деньги плачены). Поиск привёл меня к решению.
На первом этапе нам нужно создать дополнительную учётную запись типа "Администратор". Способ создания значения не имеет, я рекомендую через "Панель управления" (пользователи Windows 8.1 непрофессиональной редакции выбора не имеют). Теперь нужно загрузиться в безопасном режиме и войти в систему под новосозданной учётной записью. После этого нужно запустить редактор реестра - Regedit. Следует открыть раздел реестра HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
. В нём содержится несколько подразделов. Если щёлкнуть на одном из них, то появится список ключей, одним из которых будет ProfileImagePath
. Заменяем имеющееся значение на нужное, например, вот так:
C:\Users\Максим -> C:\Users\xPhoenix
Так же следует обязательно переименовать имеющуюся папку профиля. После этого следует перезагрузить компьютер и при необходимости удалить созданную ранее дополнительную учётную запись администратора.
Проблема с кириллицей в имени пользователя наблюдается и в некоторых других программах. Насколько помню, из-за неё среда MATLAB 7.5 вообще отказывалась сохранять файлы.
Собственная модель пользователя Django (>=1.8)
Введение
На данную тему в Интернете уже написано огромное множество статей, и моя станет лишь очередной попыткой описать то, что уже и так широко известно. Не претендуя на оригинальность, я попробую описать тот способ, которым пользуюсь сам. В статье пойдёт речь о расширении стандартной модели User
, имеющейся в Django.
В настоящее время так же широко используются ещё два способа:
- Создание связанной с пользователем 1 к 1 модели профиля.
- Полная замена стандартной модели пользователя на свою с последующим переписыванием бэкэндов авторизации.
Первый способ лично мне не нравится своей устарелостью. Ещё в Django 1.5 была проделана огромная работа по рефакторингу существующих модулей, связанных с пользователями, что дало возможность расширять имеющуюся модель. К тому же, способ с дополнительной моделью заставляет вас писать дополнительный код, например, на отлов события создания или удаления модели пользователя.
Второй способ плох опять же массой дополнительной работы. Нужно переопределить методы-обработчики событий авторизации и многих других действий.
Создание расширенной модели
Создавать новую модель пользователя мы будем на основе уже имеющейся в Django, т.е. будем использовать наследование. Первым делом следует создать новое приложение Django:
djangoadmin.py startapp extuser
Мне нравится использовать имя extuser
для решения поставленной задачи потому, что, оно полностью передаёт суть и назначение данного приложения - расширение стандартной модели пользователя.
Модифицируем файл models.py
нового приложения.
extuser/models.py
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.models import BaseUserManager
from django.db import models
class UserManager(BaseUserManager):
def create_user(self, email, password=None):
if not email:
raise ValueError('Email непременно должен быть указан')
user = self.model(
email=UserManager.normalize_email(email),
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password):
user = self.create_user(email, password)
user.is_admin = True
user.save(using=self._db)
return user
class ExtUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(
'Электронная почта',
max_length=255,
unique=True,
db_index=True
)
avatar = models.ImageField(
'Аватар',
blank=True,
null=True,
upload_to="user/avatar"
)
firstname = models.CharField(
'Фамилия',
max_length=40,
null=True,
blank=True
)
lastname = models.CharField(
'Имя',
max_length=40,
null=True,
blank=True
)
middlename = models.CharField(
'Отчество',
max_length=40,
null=True,
blank=True
)
date_of_birth = models.DateField(
'Дата рождения',
null=True,
blank=True
)
register_date = models.DateField(
'Дата регистрации',
auto_now_add=True
)
is_active = models.BooleanField(
'Активен',
default=True
)
is_admin = models.BooleanField(
'Суперпользователь',
default=False
)
# Этот метод обязательно должен быть определён
def get_full_name(self):
return self.email
# Требуется для админки
@property
def is_staff(self):
return self.is_admin
def get_short_name(self):
return self.email
def __str__(self):
return self.email
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()
class Meta:
verbose_name = 'Пользователь'
verbose_name_plural = 'Пользователи'
Рассмотрим этот код.
Менеджер моделей
Сначала идёт описание менеджера для данной модели. Описывать его нужно для того, чтобы правильно работали методы создания нового пользователя. Чуть ниже в коде будет указано, что для работы с объектами типа ExtUser
нужно использовать именно его. Можно определить несколько менеджеров при необходимости, каждый из которых будет отвечать за свою часть работы.
Модель ExtUser
В этой модели как ключевое указано поле email
. Я считаю, что это один из лучших способов для авторизации пользователей. Лучше может быть только двухфакторная авторизация через SMS. Создав ключевое поле, следуте обязательно указать, что оно используется в качестве имени пользователя:
USERNAME_FIELD = 'email'
Делали бы мы авторизацию через номер телефона - указали бы так:
phone = CharField(
'Номер телефона'
max_length=20,
unique=True,
db_index=True
)
#Чуть ниже в этом же классе:
USERNAME_FIELD = 'phone'
Надеюсь, общий принцип понятен. Дальше уже идёт отсебятина, которую можно не писать. Например, аватары, отчество и т.д. Список полей в каждом проекте будет разным. Не забудьте только описать методы проверки прав has_perm()
и has_module_perms()
соответственно.
Формы
Наш класс не будет работать, если не создать для него формы админки. Создадим в приложении файл forms.py
.
extuser/forms.py
from django import forms
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.contrib.auth import get_user_model
class UserCreationForm(forms.ModelForm):
password1 = forms.CharField(
label='Пароль',
widget=forms.PasswordInput
)
password2 = forms.CharField(
label='Подтверждение',
widget=forms.PasswordInput
)
def clean_password2(self):
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
if password1 and password2 and password1 != password2:
raise forms.ValidationError('Пароль и подтверждение не совпадают')
return password2
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user
class Meta:
model = get_user_model()
fields = ('email',)
class UserChangeForm(forms.ModelForm):
'''
Форма для обновления данных пользователей. Нужна только для того, чтобы не
видеть постоянных ошибок "Не заполнено поле password" при обновлении данных
пользователя.
'''
password = ReadOnlyPasswordHashField(
widget=forms.PasswordInput,
required=False
)
def save(self, commit=True):
user = super(UserChangeForm, self).save(commit=False)
password = self.cleaned_data["password"]
if password:
user.set_password(password)
if commit:
user.save()
return user
class Meta:
model = get_user_model()
fields = ['email', ]
class LoginForm(forms.Form):
"""Форма для входа в систему
"""
username = forms.CharField()
password = forms.CharField()
Здесь описаны две формы - для создания нового пользователя и для смены пароля. Т.к. я использую Django REST Framework, я сделал эти формы для того, чтобы корректно работало обновление модели пользователя, т.к. поле password
является обязательным. Если отправить запрос на указание, например, нового отчества, будет возвращена ошибка, т.к. поле пароля должно быть обязательно заполнено. Дополнительная форма решает эту проблему.
PATCH
вместо PUT
.Пожалуй, единственное, на что тут нужно обратить внимание - это описание связи наших форм с моделью пользователя. Если в будущем понадобится создать новую модель или переименовать её, переписывать код не придётся, т.к. используется функция get_user_model()
, возвращающая класс модели пользователя, используемый для авторизации. Если проще, то эта функция возвращает класс, указанный в параметре AUTH_USER_MODEL
в файле settings.py
нашего приложения.
Админка
Изменения придётся внести и в файл admin.py
:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import Group
from .forms import UserChangeForm
from .forms import UserCreationForm
from .models import ExtUser
class UserAdmin(UserAdmin):
form = UserChangeForm
add_form = UserCreationForm
list_display = [
'date_of_birth',
'email',
'firstname',
'is_admin',
'lastname',
'middlename',
]
list_filter = ('is_admin',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {
'fields': (
'avatar',
'date_of_birth',
'firstname',
'lastname',
'middlename',
)}),
('Permissions', {'fields': ('is_admin',)}),
('Important dates', {'fields': ('last_login',)}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': (
'date_of_birth',
'email',
'password1',
'password2'
)}
),
)
search_fields = ('email',)
ordering = ('email',)
filter_horizontal = ()
# Регистрация нашей модели
admin.site.register(ExtUser, UserAdmin)
admin.site.unregister(Group)
Настройка Django
Пришло время переустановить Windows внести изменения в файл settings.py
.
settings.py
# Тут должен быть импорт нужных модулей.
SECRET_KEY = # Какой-то ключ, автоматически созданный Django
DEBUG = True
TEMPLATE_DEBUG = True
ALLOWED_HOSTS = []
EXTERNAL_APPS = (
# У меня, например, здесь разные внешние библиотеки
)
INTERNAL_APPS = (
# Тут список наших приложений
'extuser',
# А тут его продолжение
)
DJANGO_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
)
INSTALLED_APPS = EXTERNAL_APPS + INTERNAL_APPS + DJANGO_APPS
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
)
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.csrf',
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.request',
)
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
)
LOGIN_URL = r"/login/"
AUTH_USER_MODEL = 'extuser.ExtUser'
Миграции
Заключительный этап настройки нашей модели - проведение миграций. Запустите скрипт:
python manage.py migrate
Для создания нового пользователя-администратора используйте команду createsuperuser
:
python manage.py createsuperuser
Комментариев нет :
Отправить комментарий