Debian, Dojo, Django, Python

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

Настройка GRUB2 в Debian

Комментариев нет

Зачем настраивать GRUB 2

Решил я настроить свой GRUB 2, потому что не все его параметры меня устраивают. Ну, например, уменьшить таймаут, увеличить разрешение, в конце концов, поменять фоновую картинку. К чему в итоге пришёл, написано ниже.

Все выполняемые операции требуют привилегий пользователя root.

Настройка разрешения

Первое, что нужно сделать - зайти в консоль самого GRUB'а при запуске. Для этого нужно нажать клавишу c, и, если пароль на загрузчик не установлен, сразу же осуществляется переход к командной строке. Тут вводим одну команду из двух (результат на моём Debian 9 одинаковый):

Команды для определения графических режимов
videoinfo
vbeinfo

Обе команды дают один и тот же результат - список доступных GRUB'у режимов видеоадаптера. Однако, всё не так просто. Дело в том, что при запуске загрузчика загружается видеодрайвер, НО ЭТО НЕ ТОТ ВИДЕОДРАЙВЕР, который даёт полный доступ ко всем режимам видеоадаптера. Таким образом, у меня, например, максимально доступное разрешение не соответствует параметрам монитора, т. е. тут не всё так просто.

В общем, параметры монитора я выяснил, теперь надо было подкрутить /etc/default/grub. Настраивать нужно именно этот файл, поскольку при вызове скрипта update-grub настройки будут взяты оттуда. Ниже привожу только те настройки, которые менял.

/etc/default/grub
GRUB_DEFAULT=0
GRUB_TIMEOUT=3
GRUB_GFXMODE=1280x1024x32          # Разрешение загрузочного меню GRUB
GRUB_GFXPAYLOAD_LINUX=1920x1080x32 # Передается в параметрах ядра
GRUB_BACKGROUND=/etc/alternatives/desktop-theme/grub/Hexagons-16x9.png
GRUB_DISABLE_OS_PROBER="true"

Краткое описание параметров:

GRUB_DEFAULT Пункт меню по умолчанию
GRUB_TIMEOUT Время в секундах до загрузки пункта по умолчанию
GRUB_GFXMODE Графический режим загрузчика. Можно указать так же значение auto.
GRUB_GFXPAYLOAD_LINUX Разрешение графического режима, которое загрузчик передаст ядру. Если указать правильное значение, можно запускать ОС сразу с нужным разрешением экрана.
GRUB_BACKGROUND Путь к фону загрузчика. Допускается использовать файлы в форматах jpg, png и tga. При необходимости изображение будет отмасштабировано под GRUB_GFXMODE. Если картинки не будет в указанном месте, загрузчик будет запущен в текстовом режиме, без какой-либо графики.
GRUB_DISABLE_OS_PROBER Запретить запуск утилиты osprober, собирающей информацию о других установленных ОС. Поскольку у меня Linux единственная ОС на этом компьютере, могу себе позволить сэкономить немного времени.

После настройки всех параметров обязательно нужно вызвать команду update-grub. Для тех систем, где используется GRUB2, данная команда является символической ссылкой на update-grub2.

Комментариев нет :

Отправить комментарий

Django, JSON и формы

2 комментария

Очень интересные результаты выдает Google при поиске по словам django json forms. Большая часть ссылок ведет на Stack Overflow, но там всё одно и то же. Как правило, всё сводится к тому, чтобы: 1) сменить стек технологий; 2) разобрать request.POST как словарь (это не работает); 3) передать данные каким-то другим способом.

А вот рабочее решение, проверенное в Django 1.4.22 (из-за некоторых особенностей мне сейчас приходится пользоваться именно таким старьем).

views.py
# -*- 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 тоже надо пару слов сказать. В старых версиях такого класса нет, но он отлично портируется из новых. Вот код:

rest.response
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)

2 комментария :

Отправить комментарий

dojoConfig

Комментариев нет

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

dojoConfig.js
(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. Это довольно мощная таблица, однако, у нее есть ряд недостатков, которые решаются обходными путями, например, через редактирование стилей. Вот так можно задать автоматическое растягивание таблицы по размеру родительского блока:

Подключать после CSS плагина Dgrid:
.dgrid {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    height: auto;
}

Решение проблемы взято со StackOverflow.

Но есть другая проблема. Допустим, мы решили разместить DGrid внутри компонента типа ContentPane. При отображении заголовок будет сжат в 0 и наедет на данные. Можно изменить размер окна браузера, чтобы отображалось нормально, а можно:

Дополнительные действия в resize() родителя:
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:

Дополнительные действия в resize() родителя:
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-Token в Django. Для этого нужно не так уж и много:

Код ниже актуален для Django 1.4. В новых версиях, например, 1.8 функция 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 для его хранения.

index.html
<!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

Все выполняемые операции требуют привилегий пользователя root.
Если кому-то покажется, что далее идёт пересказ официальной инструкции для администратора, то вам не кажется - взято отсюда.

Установка необходимых модулей 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/.

Apache Virtual Host: /etc/apache2/sites-available/astra-django-project
<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. Допустим, у нас есть вот такой код:

Возврат JSON с заголовками.
# -*- 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.

init.el для elpy + python-mode
(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 можно удалить, чтобы ничего не сломалось. Сегодня выяснять лень, потом разберусь.

Комментариев нет :

Отправить комментарий