Django, JSON и формы
Очень интересные результаты выдает Google при поиске по словам django json forms. Большая часть ссылок ведет на Stack Overflow, но там всё одно и то же. Как правило, всё сводится к тому, чтобы: 1) сменить стек технологий; 2) разобрать request.POST как словарь (это не работает); 3) передать данные каким-то другим способом.
А вот рабочее решение, проверенное в Django 1.4.22 (из-за некоторых особенностей мне сейчас приходится пользоваться именно таким старьем).
# -*- coding: utf-8 -*-
from __future__ import absolute_imports
from django.utils import simplejson as json
from django import http
from app.items import forms
from app.items import models
from api.items import serializers
from rest import response
def api_post_view(request):
if request.method == 'POST' and request.is_ajax():
# Ну да, всего одна строка. А вы что думали?
raw_data = json.loads(request.POST)
new_item_form = forms.CreateItemForm(raw_data)
if new_item_form.is_valid():
new_item = new_item_form.save()
serializer = serializers.ItemReadSerializer()
return reponse.JsonResponse(serializer.serialize(new_item))
else:
return response.JsonResponse(new_tem_form.errors)
else:
return http.HttpResponseBadRequest()
Насчет JsonResponse тоже надо пару слов сказать. В старых версиях такого класса нет, но он отлично портируется из новых. Вот код:
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import simplejson as json
from django.http.response import HttpResponse
class JsonResponse(HttpResponse):
"""
An HTTP response class that consumes data to be serialized to JSON.
:param data: Data to be dumped into json. By default only ``dict`` objects
are allowed to be passed due to a security flaw before EcmaScript 5. See
the ``safe`` parameter for more information.
:param encoder: Should be a json encoder class. Defaults to
``django.core.serializers.json.DjangoJSONEncoder``.
:param safe: Controls if only ``dict`` objects may be serialized. Defaults
to ``True``.
:param json_dumps_params: A dictionary of kwargs passed to json.dumps().
"""
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
json_dumps_params=None, **kwargs):
if safe and not isinstance(data, dict):
raise TypeError(
'In order to allow non-dict objects to be serialized set the '
'safe parameter to False.'
)
if json_dumps_params is None:
json_dumps_params = {}
kwargs.setdefault('content_type', 'application/json')
data = json.dumps(data, cls=encoder, **json_dumps_params)
super().__init__(content=data, **kwargs)
dojoConfig
В Dojo Toolkit все базовые настройки приложения хранятся в глобальном объекте dojoConfig, и его инициализация должна происходить раньше, чем будет подключена сама библиотека Dojo, потому что при загрузке выполняется код, который проверяет существование этой переменной. В итоге пришёл к вот такому конфигу:
(function(G) {
"use strict"; // Возможно, избыточно, поскольку весь Dojo написан без этой директивы
G.dojoConfig = {
async: true, // Для всех браузеров должно быть так
parseOnLoad: false, // Разбор страницы на виджеты при загрузке? Лучше дождаться domReady
debug: true,
// Самая сложная часть - определение текущей локали браузера. Влияет на всякие
// внутренние механизмы Dojo, например, какой язык будет использован для виджетов.
// Хотите календарь на русском языке? Первый день недели - понедельник? Это сюда.
// Можно и явно указать "ru-ru", но мне нравится вот такой подход.
locale: navigator.language || navigator.userLanguage
};
})(this); // this === window
Dgrid: 100% высоты родителя, проблемы рендеринга и totalLength
Я довольно часто пользуюсь Dgrid. Это довольно мощная таблица, однако, у нее есть ряд недостатков, которые решаются обходными путями, например, через редактирование стилей. Вот так можно задать автоматическое растягивание таблицы по размеру родительского блока:
.dgrid {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: auto;
}
Решение проблемы взято со StackOverflow.
Но есть другая проблема. Допустим, мы решили разместить DGrid внутри компонента типа ContentPane. При отображении заголовок будет сжат в 0 и наедет на данные. Можно изменить размер окна браузера, чтобы отображалось нормально, а можно:
var cpWithDgrid = new ContentPane({
buildRendering: function() {
this.inherited(arguments);
this.grid = new DGrid({}); // Подставьте свой класс для Grid'а
this.set("content", this.grid);
},
resize: function() {
this.inherited(arguments);
this.grid.resize();
}
});
А разгадка такого поведения очень проста: высота элементов таблицы DGrid вычисляется до помещения в DOM, а потому они получают 0 в качестве значения. DGrid, разумеется, под суд. По-хорошему, при запуске вашего виджета он должен всем дочерним виджетам дать команду пересчитать размеры, но конкретно с DGrid это почему-то не работает. Костыль? Костыль. Но работает.
Надо ещё про работу с Rest сказать. Мои таблицы получают данные с сервера. В поле collection в качестве значения я указываю экземпляра класса-наследника dstore/Rest. Самое интересное, что при получении данных с сервера в логах часто можно увидеть сообщение об ошибке: Store reported null or undefined totalLength. Make sure your store (and service, if applicable) are reporting total correctly! Разумеется, это не правда. На самом деле DGrid ожидает увидеть в ответе сервера поле total, а не totalLength.
Поскольку на сервере у меня обычно Django + Django REST Framework, нужно соблюдать его требования. Одним из них является завершающий слеш в конце любого запроса. В итоге создал миксин, с которым смешиваю все создаваемые мной классы-наследники dstore/Rest:
define([
"dojo/_base/declare"
], function(declare) {
return declare(null, {
ascendingPrefix: "",
_getTarget: function(id) {
// По требованиям DRF в конце URL должен стоять /
var target = this.target;
// А можно решить с помощью строковых литералов {$id}, но не буду
if (target.slice(-1) == '/') {
return target + id + '/';
} else {
return target + '/' + id + '/';
} // fi
} // _getTarget
}); // declare
}); // define
CSRF-Token в Dojo Toolkit 1.x и Django
Постоянно забываю, как в Dojo Toolkit 1.x автоматически цеплять CSRF-Token к XHR-запросам. Ниже просто код, который должен запускаться при старте приложения (FrontEnd).
require([
"dojo/cookie",
"dojo/request/notify",
"dojo/domReady!"
], function(cookie, notify) {
notify("send", function(response, cancel) {
response.xhr.setRequestHeader("X-SCRFToken", cookie("csrftoken"));
});
});
И
csrf перенесена в модуль django.template.context_processors# -*- coding: utf-8 -*-
u"""Набор видов для построения базового интерфейса приложения."""
from django.shortcuts import render_to_response
from django.views.generic import View
from django.core.context_processors import csrf
class Index(View):
u"""Главное окно приложения."""
template_name = "index.html"
def get(self, request):
# Установка CSRF-токена
c = {}
c.update(csrf(request))
return render_to_response(self.template_name, c)
Но этого мало. Нужно ещё в теле главной страницы разместить скрытое поле, куда будет записан токен. Кстати, только так в старых версиях Django можно указать cookie для его хранения.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8"/>
<title>Установка CSRF-Token'а</title>
</head>
<body>
<!-- ТО САМОЕ СКРЫТОЕ ПОЛЕ -->
<input name="csrftoken" type="hidden" value="{% csrf_token %}" />
<!-- /ТО САМОЕ СКРЫТОЕ ПОЛЕ -->
</body>
</html>
Запуск Django-приложений через mod_python в Astra Linux SE
Запуск Python-Web-приложений в Astra Linux
В Astra Linux до версии 1.6 не было иного способа нормального запуска Web-приложений, написанных на Python, кроме mod_python. В настоящее время самый популярный способ - WSGI, однако, в сертифицированных версиях Astra Linux SE (1.4, 1.5) есть только указанный выше модуль для Apache. Безусловно, есть и третий способ - запуск приложения в отладочном режиме через вот эту команду:
python manage.py runserver
Надо ли объяснять, что делать так не нужно?
Итак, задачи, решение которых я предлагаю в этой статье:
- Настройка Apache для работы в рамках ALD (Astra Linux Domain)
- Настройка виртуального хоста для Django-приложения
- Авторизация в приложении Django через ALD/Kerberos
Настройка ALD и Apache
Установка необходимых модулей Apache.
Устанавливаем два модуля Apache (считаем, что Apache уже установлен на сервере).
apt-get install libapache2-auth-mod-kerb libapache2-mod-python
Включаем эти модули и перезапускаем WEB-сервер:
a2dismod auth_pam
a2enmod python
a2enmod auth_kerb
Настройка ALD.
Теперь нужно настроить сервер таким образом, чтобы клиенты, подключающиеся в рамках сессии ALD, прозрачно авторизовались на сервере. В инструкции всё описано, лишь немного постараюсь прояснить моменты, которые у меня вызвали вопросы.
Создадим принципала в ALD и добавим его в группу mac
ald-admin service-add HTTP/server.domain.lan
ald-admin sgroup-svc-add HTTP/server.domain.lan --sgroup=mac
Теперь создадим файл ключа Kerberos и дадим права на него пользователю www-data.
KEYTAB="/etc/apache2/keytab"
ald-client update-svc-keytab HTTP/server.domain.lan --ktfile=$KEYTAB
chown www:data $KEYTAB
chmod 644 $KEYTAB
Теперь можно перезапустить WEB-сервер:
service apache2 restart
Создание виртуального хоста Apache.
Подготовительные операции выполнены, на клиентах Firefox настроен на использование GSS API для авторизации (about:config, потом в параметр network.negotiate-auth.delegation-uris вписываем http://,https://). Самое время создать виртуальный хост для нашего Django-проекта, лежащего в каталоге /var/www/site/.
<VirtualHost *:80>
ServerName server.domain.lan
ServerAdmin useradmin@domain.lan
DocumentRoot /var/www/site
AddDefaultCharset utf-8
<Directory "/var/www/site/”>
Options -Indexes FollowSymLinks -MultiViews
AllowOverride None
AuthType Kerberos
KrbAuthRealms DOMAIN.LAN
KrbServiceName HTTP/server.domain.lan
Krb5Keytab /etc/apache2/keytab
KrbMethodNegotiate on
KrbMethodK5Passwd off
KrbSaveCredentials on
require valid-user
Order deny,allow
Allow from all
</Directory>
<Location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE site.settings
PythonOption diango.root /var/www/site
PythonPath "['/var/www/site/',] + sys.path"
PythonAutoReload On
</Location>
<Location "/media/”>
SetHandler None
</Location>
<Location "/static/">
SetHandler None
</Location>
<LocationMatch "\.(jpg|gif|png)$">
SetHandler None
</Location>
ErrorLog /var/www/site/log/error.log
LogLevel warn
SetEnfIf Request_URI "\.jpg$|\.gif$|\.css$|\.js" is_static
CustomLog /var/www/site/log/access.log combined env=!is_static # Убрать лишнее из логов доступа, например, статику
</VirtualHost>
Не забываем включить наш сайт в список разрешенных:
a2ensite astra-django-project
service apache2 reload
Побочные эффекты
Возможно, у меня руки кривые, или я чего-то не знаю, но есть некоторые факты, которым я не нахожу другого объяснения, например, откусывание заголовков HTTP сервером Apache. Допустим, у нас есть вот такой код:
# -*- coding: utf-8 -*-
u"""Виды для обработки данных абонентов."""
from django.shortcuts import get_object_or_404
from django.views.generic import View
from abonent.models import Abonent
from rest.responses import JsonResponse
from .serializers import AbonentSimpleSerializer
class AbonentRootView(View):
def get(self, request):
u"""
Именно здесь происходит сериализация модели в JSON.
Возвращаемый объект - JsonResponse.
"""
root_abonent = Abonent.objects.filter(parent=None)[0]
serializer = AbonentSimpleSerializer()
response = JsonResponse({
"items": [
serializer.serialize(root_abonent),
],
"total": 1
})
# Тут задаем заголовки, чтобы библиотека dgrid могла с ними работать
# Не надо смотреть на цифры, они сейчас не имеют значения (просто пример)
response["Content-Range"] = "items: 1-1/1"
return response
Не суть важно, как происходит сериализация (сериализатор возвращает словарь), важно то, что происходит в строке, где устанавливается заголовок ответа Content-Range. Если запустить отладочный сервер, то в заголовках ответа мы его увидим. Если выполнять этот же код с помощью Apache, т. е. так, как я выше написал, заголовок будет просто выброшен. Как это лечить в Astra Linux 1.4, я не знаю. Но, например, при использовании библиотеки DStore использовать свойство useRangeHeaders будет нельзя. Т. е. и DGrid тоже работать не будет, нужно помимо свойства items передавать ещё и total. Не такая уж большая проблема, но под определение подводного камня подходит хорошо.
Elpy, python-mode и use-package
Emacs - отличный редактор для тех, кто пишет на Python, и есть куча пакетов для поддержки этого языка. Я уже раньше писал, что теперь храню свою конфигурацию и управляю пакетами с помощью use-package, однако, долго не мог найти способ для красивого скрещивания замечательного пакета elpy cо стандартным python-mode.
Решение оказалось не очень сложным, хотя на его поиск пришлось потратить некоторое время. Ниже выкладываю кусок своего init.el.
(use-package python-mode
:mode ("\\.py\\'" . python-mode)
:init(add-hook 'python-mode-hook #'elpy-enable)
:config
(use-package elpy
:bind
("M-," . elpy-goto-definition)
:init
(elpy-enable)
(defalias 'workon 'pyvenv-workon)
:config
(add-to-list 'company-backends 'elpy-company-backend)
(elpy-enable))
(use-package py-autopep8
:hook
(python-mode . py-autopep8-enable-on-save))
(use-package py-isort
:init
(add-hook 'before-save-hook #'py-isort-before-save)))
Этот код не делает никакой магии. В общем-то, тут вообще нет ничего интересного.
Самому себе оставляю памятку: в настройках settings.el уже включена автоматическая загрузка недостающих пакетов с помощью use-package. Для всех Python'ьих файлов как major-mode указан python-mode. При его инициализации дополнительно подключаются пакеты elpy, py-autopep8 и py-isort, почти все настройки которых уже вынесены сами знаете куда. Кроме того, каждый пакет при загрузке ставит хуки на определенное действие. Например, автоматическое форматирование или упорядочение импортированных модулей.
Не уверен в том, какую строку с elpy-enable можно удалить, чтобы ничего не сломалось. Сегодня выяснять лень, потом разберусь.
Красивый способ хранения настроек Emacs
Интересный подход к хранению конфигурации Emacs
Интересный способ хранения конфигурации подсмотрел у автора use-package. Если вкратце, то все настройки для конкретных пакетов вынесены в файл settings.el, а вот список используемых пакетов и нюансы взаимодействия между ними описаны целиком и полностью в init.el.
Чтобы далеко не ходить, вот пример кода:
;;; init.el --- Summary
;;; Commentary:
;;; Main EMACS settings file, load settings from parts.
;;; Code:
;; Added by Package.el. This must come before configurations of
;; installed packages. Don't delete this line. If you don't want it,
;; just comment it out by adding a semicolon to the start of the line.
;; You may delete these explanatory comments.
(package-initialize)
(eval-when-compile(require 'cl))
(require 'package)
(setq package-archives nil)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t)
(add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives '("elpy" . "https://jorgenschaefer.github.io/packages/") t)
(setq package-enable-at-startup nil)
(package-initialize nil)
;;; Загрузка и установка пакета use-package, если его нет в системе
(unless(package-installed-p 'use-package)
(message "EMACS install use-package.el")
(package-refresh-contents)
(package-install 'use-package))
(require 'use-package)
;;; Шрифты
(set-face-attribute 'default nil :height 110)
(when (member "DejaVu Sans Mono" (font-family-list))
(set-face-attribute 'default nil :font "DejaVu Sans Mono"))
;;; Заставляю хоткеи работать и в русской раскладке
(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)
;; Resize windows
(global-set-key(kbd "S-C-") 'shrink-window-horizontally)
(global-set-key(kbd "S-C-") 'enlarge-window-horizontally)
(global-set-key(kbd "S-C-") 'shrink-window)
(global-set-key(kbd "S-C-") 'enlarge-window)
;; Isearch
(global-set-key (kbd "C-M-r") 'isearch-backward-other-window)
(global-set-key (kbd "C-M-s") 'isearch-forward-other-window)
(defun xah-new-empty-buffer()
"Open a new empty buffer.
URL `http: // ergoemacs.org / emacs / emacs_new_empty_buffer.html'
Version 2015 - 06 - 12"
(interactive)
(let((ξbuf(generate-new-buffer "untitled")))
(switch-to-buffer ξbuf)
(funcall(and initial-major-mode))
(setq buffer-offer-save t)))
;; Save/close/open
(global-set-key(kbd "C-w") 'kill-this-buffer)
(global-set-key(kbd "C-s") 'save-buffer)
(global-set-key(kbd "C-S-s") 'write-file)
(global-set-key(kbd "C-r") 'revert-buffer)
(global-set-key(kbd "C-a") 'mark-whole-buffer)
(global-set-key(kbd "M-'") 'comment-or-uncomment-region)
(global-set-key(kbd "C-o") 'dired)
(global-set-key(kbd "C-n") 'xah-new-empty-buffer)
(global-set-key(kbd "C-+") 'text-scale-increase)
(global-set-key(kbd "C--") 'text-scale-decrease)
;; Buffers and windows
(global-set-key(kbd "C-") 'next-buffer)
(global-set-key(kbd "C-") 'previous-buffer)
(global-set-key(kbd "C-") 'other-window)
(global-set-key(kbd "M-3") 'delete-other-windows)
(global-set-key(kbd "M-4") 'split-window-horizontally)
(global-set-key(kbd "M-5") 'split-window-vertically)
(global-set-key(kbd "M-6") 'balance-windows)
(global-set-key(kbd "C-f") 'isearch-forward)
(global-set-key(kbd "C-h") 'query-replace)
(global-set-key(kbd "C-S-h") 'query-replace-regexp)
(global-set-key(kbd "M-a") 'execute-extended-command)
(global-set-key(kbd "M-x") 'kill-whole-line)
(global-set-key(kbd "") 'keyboard-quit)
(when(get-buffer "*scratch*")
(kill-buffer "*scratch*"))
(fset 'yes-or-no-p 'y-or-n-p); ; ; Shortcuts for yea and no
;;; Format file before save
(defun format-current-buffer()
(indent-region (point-min) (point-max)))
(defun untabify-current-buffer()
(if (not indent-tabs-mode)
(untabify (point-min) (point-max)))
nil)
(add-to-list 'write-file-functions 'untabify-current-buffer)
(add-to-list 'write-file-functions 'delete-trailing-whitespace)
;;; САМЫЙ ВАЖНЫЙ МОМЕНТ - ВЫНЕСЕНИЕ НАСТРОЕК СРЕДЫ В settings.el и загрузка их оттуда
(setq custom-file "~/.emacs.d/settings.el")
(load-file "~/.emacs.d/settings.el")
;;; Теперь просто описание используемых пакетов
;;; Настройки в большинстве случаев подтягиваются из файла settings.el
(use-package airline-themes
:requires powerline
:init
(load-theme 'airline-molokai))
(use-package all-the-icons)
(use-package beacon
:diminish
:commands beacon-mode)
(use-package company
:diminish
:commands company-mode
:config
;; From https://github.com/company-mode/company-mode/issues/87
;; See also https://github.com/company-mode/company-mode/issues/123
(defadvice company-pseudo-tooltip-unless-just-one-frontend
(around only-show-tooltip-when-invoked activate)
(when (company-explicit-action-p)
ad-do-it))
(defun check-expansion ()
(save-excursion
(if (outline-on-heading-p t)
nil
(if (looking-at "\\_>") t
(backward-char 1)
(if (looking-at "\\.") t
(backward-char 1)
(if (looking-at "->") t nil))))))
(define-key company-mode-map [tab]
'(menu-item "maybe-company-expand" nil
:filter (lambda (&optional _)
(when (check-expansion)
#'company-complete-common))))
(eval-after-load "yasnippet"
'(progn
(defun company-mode/backend-with-yas (backend)
(if (and (listp backend) (member 'company-yasnippet backend))
backend
(append (if (consp backend) backend (list backend))
'(:with company-yasnippet))))
(setq company-backends
(mapcar #'company-mode/backend-with-yas company-backends))))
:init (global-company-mode t))
(use-package company-quickhelp
:bind
(:map company-active-map
("C-c h" . company-quickhelp-manual-begin)))
(use-package css-mode
:mode "\\.css\\'")
(use-package elpy
:mode "\\.py\\'"
:requires python-mode
:hook (add-hook 'before-save-hook #'elpy-format-code)
:init
(elpy-enable)
(defalias 'workon 'pyvenv-workon))
(use-package emmet-mode
:mode ("\\.html\\'" . emmet-mode)
:bind
("C-j" . emmet-expand-line))
(use-package flycheck
:commands flycheck-mode
:init(add-hook 'after-init-hook #'global-flycheck-mode))
(use-package highlight-numbers
:hook(prog-mode . highlight-numbers-mode))
(use-package ibuffer
:bind([f2] . ibuffer)
:init
(add-hook 'ibuffer-mode-hook #'(lambda ()(ibuffer-switch-to-saved-filter-groups "default"))))
(use-package js2-mode
:mode ("\\.js\\'" . js2-mode)
:requires flycheck
:bind(
:map js2-mode-map
("M-n" . flycheck-next-error)
("M-p" . flycheck-previous-error))
:config
(add-to-list 'flycheck-disabled-checkers #'javascript-jshint)
(flycheck-add-mode 'javascript-eslint 'js2-mode)
(flycheck-mode 1)
(js2-highlight-unused-variables-mode t))
(use-package json-mode
:mode (("\\.json\\'" . json-mode)
("\\.bowerrc\\'" . json-mode)
("\\.jshintrc\\'" . json-mode)))
(use-package json-reformat
:after json-mode)
(use-package magit
:bind([f5] . magit-status))
(use-package markdown-mode
:mode "\\.md\\'")
(use-package monokai-theme
:init(load-theme 'monokai))
(use-package neotree
:requires all-the-icons
:bind
([f8] . neotree-toggle))
(use-package powerline)
(use-package py-isort
:hook
(add-hook 'before-save-hook 'py-isort-before-save))
(use-package rainbow-delimiters
:hook
(prog-mode . rainbow-delimiters-mode))
(scroll-bar-mode -1)
(use-package tide
:requires js2-mode
:after js2-mode
:mode ("\\.js\\'" . tide-mode)
:hook((before-save-hook . tide-format-before-save)
(typescript-mode-hook . setup-tide-mode)))
(use-package web-beautify)
(use-package web-mode
:commands web-mode
:requires web-beautify
:mode("\\.phtml\\'" . web-mode)
("\\.html\\'" . web-mode)
:hook (add-hook 'before-save-hook 'web-beautify-html-buffer t t))
(use-package yasnippet
:after prog-mode
:defer 10
:diminish yas-minor-mode
:mode("/\\.emacs\\.d/snippets/" . snippet-mode)
:config
(yas-load-directory (expand-file-name "snippets" user-emacs-directory))
(yas-global-mode 1))
(use-package yasnippet-snippets
:after yasnippet)
;;; init.el ends here
Что интересного я взял у автора?
Во-первых, саму организацию хранения описания пакетов. Во-вторых, он отсортировал вызовы use-package по алфавиту. Быстрый поиск не нужен, если можно просто нажать пару раз [PgDn]. В-третьих, он и в файле настроек применил сортировку по алфавиту.
Для тех, кому интересен оригинальный конфиг, его можно найти здесь.