15 декабря 2013 г.

проект django project на shared hosting хостинге mod_wsgi

Историческая заметка по большей части. Черновик схемы настройки проекта на django 1.4 на хостинге с mod_wsgi.

13 ноября 2013 г.

django: contrib.comments работаем через ajax

Тут рассказ о том, как заставить стандартный django.contrib.comments работать через ajax. Цель была такова: минимальные изменения в клиентских view, чтобы работали стандартные templates перегружаемые, полноценная админка (обычная от приложения comments), да и вообще максимальное использование оригинального кода. Короче, в итоге после нескольких самопроизвольных переписываний (вместе с использованием подобного решения в проектах) получилась минимальная обёртка, которую по идее тоже можно вынести в отдельный app.

Да, скоро это уже не совсем актуально станет (django.contrib.comments устаревший и с версии 1.6 вынесен в отдельный проект), но решение отточилось на третьей версии и мне жалко его терять просто :) Пример тут для версии 1.5.

2 ноября 2013 г.

django: текущий пользователь в логах

Удобно в логах иметь текущего залогиненого юзера, чтобы видно было на ком падает, удобнее разбираться. Для логгера "django.request" передаётся экстра-параметр request. Его можно вытащить и залоггировать. Например, с помощью кастомного фильтра. Фильтр добавляет в вывод форматтера поле "user" с именем (строковое представление#id) юзера если оно есть. Фильтр вешается на нужные handlers. А в соответствующие formatters на этих handlers добавляется поле "user". В фильтре проверка на существование атрибутов request и request.user обязательна, если текущий formatter повешен на что-либо, кроме логгера "django.request", иначе будет падать в попытке найти поле с ид "user".

class RequestPushUserFilter(logging.Filter):
    def filter(self, record):
        if hasattr(record, 'request'):
            if hasattr(record.request, 'user'):
                record.user = u'%s#%s' % (record.request.user, record.request.user.pk)
            else:
                record.user = '?'
        else:
            record.user = '-'
        return True


LOGGING = {
    ...
    'filters': {
        ...
        'request_pushuser_filter': {
            '()': 'project.settings.RequestPushUserFilter',
        },
    },
    ...
    'formatters': {
        'standard': {
            'format': "[%(asctime)s] %(levelname)s [%(name)s:%(module)s:%(lineno)s] [%(user)s] %(message)s",
            'datefmt': '%d.%m.%Y %H:%M:%S',
        },
    },
    ...
    'handlers': {
        'logfile': {
            ...
            'filters': ['request_pushuser_filter'],
            ...
            'formatter': 'standard',
        },
    ...
В итоге в логах будет примерно так:
[31.10.2013 00:43:20] ERROR [django.request:base:212] [Driver John#1] Internal Server Error: блабла

См. также про логгирование в файл django.

31 октября 2013 г.

django: csrf при ajax-запросах

Несколько раз встречал вопросы на счёт csrf при самодельных ajax-запросах (например, через jquery как в примере ниже), и несколько вариантов странных даже видел. Я делаю проще - в основную страницу (например, в шаблон base.html или как он там у вас назван) выношу стандартный csrf_token в качестве javascript-переменной и во всех последующих скриптах удобно его использую.
Секюрность не страдает, конечно, т.к. сам токен не представляет интереса. Потом при запросах, например, через jquery.ajax можно просто передавать как параметр с именем "csrfmiddlewaretoken" (аналогично тому как он передаётся через скрытый input в обычных формах)

$.ajax({
  type: 'POST',
  url: '...',
  data : { ..., 'csrfmiddlewaretoken' : csrf_token, },
  ...
});

21 октября 2013 г.

linux: русский в консоли archlinux

Возникла проблема со шрифтами в виртуальной консоли. Квадратики вместо кириллицы. В графическом эмуляторе терминала, разумеется, всё нормально, а в /dev/ttyX беда. Выяснилось, что всё портит systemd, сначала загружая шрифты и настраивая их согласно vconsole.conf как и положено, а потом подгружая drm-модуль видеокарты, который создаёт новый фреймбуфер (например, у меня /dev/fb0), в котором уже никаких настроек не делается.

7 октября 2013 г.

django: логгирование в файл и перехват warnings

В django иногда удобно вести файловые логи в приложении. По умолчанию настроен только один handler — mail_admins (отправка на мыло админам), что не всегда хорошо, а иногда невозможно (в принципиальном интранете, например). Вторая задача возникла — хорошо бы отлавливать туда же и все варнинги из warnings.warn, которые в том числе и django иногда вываливает.
Первая задача тривиальна и описана в документации. Создаём новый handler RotatingFileHandler и добавляем в нужные логгеры (очевидно, что речь про settings.py):
LOGGING = {
...
    'handlers': {
...
        'logfile': {
            'level': 'WARNING',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': rel('log', 'logfile.log'),
            'maxBytes': 1000000,
            'backupCount': 666,
            'formatter': 'standard',
        },
...
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins', 'logfile'],
            'level': 'WARNING',
            'propagate': True,
        },
...
    },
}
вторая задача тоже просто решается. Делается
import logging
logging.captureWarnings(True)
После этого все варнинги начинаются писаться также в логгер с именем "py.warnings", откуда их и логируем:
...
    'loggers': {
...
        'py.warnings': {
            'handlers': ['console', 'logfile'],
            'level': 'WARNING',
            'propagate': True,
        },
...
    },

6 октября 2013 г.

django: удобные относительные пути в settings

В settings-файле джанги удобно придумать какой-то порядок, потому что рутинных записей всяких копится целая куча. Например, для работы с путями (которые почти все относительны) использую небольшой трюк.
import os

# путь корня этого проекта (там где manage лежит)
PROJECT_DEPLOY_PATH = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))

rel = lambda *path: os.path.join(PROJECT_DEPLOY_PATH, *path)
Без лямбды функция rel может выглядеть как-то так:
def rel(*path):
    return os.path.join(PROJECT_DEPLOY_PATH, *path)
И далее просто используется:
STATIC_ROOT = rel('static')
Или для путей из нескольких подпапок (/static/files/)
STATIC_ROOT = rel('static','files')

21 сентября 2013 г.

django: нормальные bootstrap3 инпуты в autocomplete_light

В bootstrap 3 поля автозаполнения от django-модуля autocomplete_light выглядят непотребно из-за требования иметь красивым инпутам формы обязательный класс "form-control". Никаких возможностей кастомизации через autocomplete_light_registry.py и т.п. нету, т.к. класс намертво захардкожен (widget.html):
{% block input %}
    {# a text input, that is the 'autocomplete input' #}
    
{% endblock %}
Пришлось сделать патчик в js и всем полям с class="autocomplete" добавить ещё и класс "form-control" (используется jquery):
if ($(".autocomplete").length) {
 $(".autocomplete").addClass( "form-control" );
}
Если используется другой вариант хардкода (типа насильное назначение вообще всем input, либо хардкодом в css), то неактуально. Я люблю чистые решения, но чище этого ничего не смог придумать.

16 сентября 2013 г.

swt jface button dropdown popup menu

Хочу я, чтобы при нажатии на кнопку вываливалось меню. Ну типа как Start-меню в винде. Всё очень просто - по нажатию кнопки создаём и разворачиваем в месте тыкания мышкой.
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Menu;
...
final Button mainMenuButton = new Button(composite, SWT.PUSH);
mainMenuButton.setText("¿?");
mainMenuButton.setLayoutData( ... );
mainMenuButton.addSelectionListener(new SelectionAdapter() {
    @Override
    public void widgetSelected(SelectionEvent event) {
        Menu dropMenu = createMainMenu( getShell() );
        Point point = mainMenuButton.toDisplay(new Point(event.x, event.y));
        dropMenu.setLocation( point.x, point.y+mainMenuButton.getBounds().height );
        dropMenu.setVisible(true);
    }
});
Далее на чистом SWT как-то так:
public static Menu createMainMenu( final Shell shell )
{
    Menu dropMenu = new Menu(shell, SWT.POP_UP);
    ...
    MenuItem item0 = new MenuItem(dropMenu, SWT.PUSH);
    item0.setText(...);
    item0.addListener(SWT.Selection, ...);
    ....
    return dropMenu;
}
На JFace как-то так:
public Menu createMainMenu( final Shell shell )
{
    MenuManager popManager = new MenuManager();
    popManager.add(new ActionOne(shell));
    ...
    popManager.add(new Separator());
    ...
    Menu dropMenu = popManager.createContextMenu(shell);
    return dropMenu;
}

29 августа 2013 г.

django upload_to windows 123 bad chars

Использовал свою реализацию upload_to-метода для формирования пути сохранения файлов в FileField-поле модели на основании заголовка сущности (чтобы файлики сохранялись в подпапки контрагентов). При разработке под linux всё было отлично, но в продакшене на одной windows-машине периодически валилась загрузка файлов
File "C:\python27\lib\site-packages\django\core\files\storage.py", line 168, in _save
    os.makedirs(directory)
  File "C:\python27\lib\os.py", line 150, in makedirs
    makedirs(head, mode)
  File "C:\python27\lib\os.py", line 157, in makedirs
    mkdir(name, mode)
WindowsError: [Error 123] Синтаксическая ошибка в имени файла,: u'C:\\inetpub\\wwwroot\\ZooDjangoProject\\media\\file\\\blabla "blabla"'
Выяснилось, что дело в кавычках в имени файлов, который есть запретный символ в винде. Пришлось вспомнить и остальные ограничения, после чего родилось прижившееся экспресс-решение.
def removebadchars(value):
    for c in r'\/:*?"<>|':
        value = value.replace(c, '')
    return value
...
filename = removebadchars(filename)

27 августа 2013 г.

Версия приложения через makefile прямиком из git

Так как код я держу в той или иной VCS (последнее время чаще всего в git) я подумал, что было бы круто версию приложений брать из репозитория, а конкретно — из тегов. Для небольших утилит особенно удобно. Некоторые похожие решения из интернета натолкнули меня на такое решение. Всё довольно просто, пример для обычного gnu-makefile и исходника на Си.

26 августа 2013 г.

django admin запрет редактирования модели

Запрещение в админке django редактирования какой-либо сущности. В моём случае это оповещения об оплатах платёжной системы. В ModelAdmin есть методы has_add_permission, has_change_permission, has_delete_permission с очевидным предназначением. Правда, если все они вернут в каком-то случае False, то модель вообще не отобразится в списке сущностей админки и по прямой ссылке тоже не будет работать. Так что все поля вместо has_change_permission надо сделать readonly.
class SuccessNotificationAdmin(admin.ModelAdmin):
    ...
    readonly_fields = ('order', 'sum', )

    def has_add_permission(self, request):
        return False

    #def has_change_permission(self, request, obj=None):
    #    return False

    def has_delete_permission(self, request, obj=None):
        return False

admin.site.register(SuccessNotification, SuccessNotificationAdmin)

25 августа 2013 г.

django favicon.ico robots.txt

Есть некоторая проблема с такого рода файлами в django. Они предполагаются находящимися в корне, а статика как правило раздаётся из «подкаталога» (например, /static/). Да, в случае favicon.ico его можно указать в meta-тегах страницы, но в некоторых случаях, например, при отдаче 500 ошибки или в любом другом случае без учёта корневого шаблона с meta-тегами, некоторые браузеры всё равно запрашивают /favicon.ico из корня, напрягая логи WARN-ошибками 404. Потому удобно в urls.py перенаправлять редиректом на реальные расположения файлов.
url(r'^favicon\.ico$', RedirectView.as_view(url=settings.STATIC_URL + 'images/favicon.ico')),
url(r'^robots\.txt$', RedirectView.as_view(url=settings.STATIC_URL + 'robots.txt')),
В случае robots.txt можно отдавать как шаблон, если удобнее (можно и mime задать, начиная с 1.5, емнип)
url(r'^robots\.txt$', TemplateView.as_view(template_name='robots.txt', content_type='text/plain')),
Для старых версий django это будут процедурные вьюшки redirect_to и direct_to_template соответственно, приводить код тут не буду за бессмысленностью.

12 июля 2013 г.

linux eclipse crash libsoup

Eclipse крашится вместе с JRE (причём любой, опен или сановской) на новом libsoup в момент попытки вывода всплывающего окошка почти в любом редакторе.
C  [libsoup-2.4.so.1+0x6d9b1]  soup_session_feature_detach+0x11
Пришлось порыться и перепробовать несколько вариантов, но нагуглил решение. Помогает добавление
-Dorg.eclipse.swt.browser.DefaultType=mozilla
в eclipse.ini или параметры запуска.

з.ы. баг на багтрекере: https://bugs.eclipse.org/bugs/show_bug.cgi?id=405786

11 июля 2013 г.

svn-проект в git-проект (с googlecode на github)

Просто памятка. Переносил несколько проектов с разных svn-репозиториев (googlecode, например). Делается просто, да и материал в сети есть на этот счёт. Выкачиваем репозиторий и превращаем его в git-svn репозиторий:
git svn clone https://projectsvn.googlecode.com/svn/trunk --authors-file=/home/user/authors --no-metadata
Файл authors нужен, чтобы сопоставить svn-юзеров с git-юзерами в новом репозитории, у меня он был такой:
user@gmail.com = darkbarker <user@gmail.com>
(no author) = darkbarker <user@gmail.com>
"(no author)" нужен для коммитов без автора (например, инициальный коммит svn-сервера).
Дальше можно отфильтровать дерево коммитов, исключив пустые коммиты, которые при конвертации образуются по разным причинам, т.к. не всем изменениям в svn-коммитах соответствуют изменения в git-коммитах (изменения в директориях, например).
git filter-branch --commit-filter 'git_commit_non_empty_tree "$@"' HEAD
Это отпочкует ветку от низа дерева и перенесёт туда master, оставив старые коммиты в старой ветке git-svn. Ну а дальше как обычно, заливаем в новый существующий git-репозиторий (надо создать его, конечно).
git remote add origin git@github.com:user/projectgit.git
git push origin master
Для bitbucket то же самое потребуется сделать:
git remote add origin https://user@bitbucket.org/user/projectgit.git
git push origin master

9 июля 2013 г.

midnight commander user menu hardlink

Очень бесит создание хардлинков в midnight commander -- запрашивает пустым окошком имя линка и кладёт его в текущую папку (актуальная версия 4.8.7). Так как я довольно часто использую хардлинки для общих файлов разных проектов, написал мелкий скриптик на user menu. Редактируется через "редактировать файл маню -> пользовательский".
+ t t
H       Hardlink to other panel
        set %t
        while [ -n "$1" ]; do
         ln "$1" "%D"
         shift
        done
Теперь выделяем файлы, нажимаем F2, "Hardlink to other panel" и линки с теми же именами оказываются на противоположной панели. Круто.

27 июня 2013 г.

django: что-то типа select_related для m2m

Заметка о проблемах со множественными выборками из БД при обходе списка сущностей в случае many-to-many связей.

Проблема

У нас есть такая схема:
# категория афиши
class CategoryPlaybill(models.Model):
    ...

# афиша
class Playbill(models.Model):
    categories = models.ManyToManyField(CategoryPlaybill, ...)
    ...

# проведение афиши
class ConductingPlaybill(models.Model):
    playbill = models.ForeignKey(Playbill, ...)
    ...
Итак, есть типы событий. Есть события по многие-ко-многим (событие может входить в несколько категорий). А есть их так называемые проведения — событие с конкретной датой, временем, местом. Мне надо получить проведения событий с разбивкой по категориям: категория1=>(список проведений), категория2 =>(список проведений).

Приходит в голову решение в лоб:
cond_playbill_list = ConductingPlaybill.objects.select_related().all()
cond_playbill_map = {}
for cond_playbill in cond_playbill_list:
    cats = cond_playbill.playbill.categories.all()
    for cat in cats:
        if cat not in cond_playbill_map:
            cond_playbill_map[cat] = []
        cond_playbill_map[cat].append(cond_playbill)
Но это решение ожидаемо даёт кучу запросов в БД на предмет SELECT FROM categoryplaybill WHERE playbill_categories.playbill_id = N для каждой итерации внешнего цикла. Дело в том, что select_related не тянет playbill_categories и ниже.

django: шаблонизатор не отображает defaultdict

Проблема

Шаблонизатор Django некорректно отображает словарь, созданный через defaultdict. На этот счёт есть тикет #16335.

21 июня 2013 г.

django: QuerySet расширяемый Manager

Приспособился использовать небольшой QuerySetManager для удобства добавления налету методов в QuerySet модели. Т.е. просто прописыванием в классе модели соответствующий QuerySet расширяется, довольно удобно, если надо писать сложные менеджеры для модели. Сам класс выглядит так:
from django.db import models

class QuerySetManager(models.Manager):
    def get_query_set(self):
        return self.model.QuerySet(self.model)

    def __getattr__(self, attr, *args):
        return getattr(self.get_query_set(), attr, *args)
Внутрь класса где-то рекомендовалось добавить полем "use_for_related_fields=True", но работает без него. Зато не работает без __getattr__, пишет: 'RelatedManager' object has no attribute 'useractive', вопреки ещё какой-то рекомендации из инета. В итоге имеем некий хак.

Используется как-то так:
class Clazz(models.Model):
    ...
    objects = QuerySetManager()
    ...
    class QuerySet(models.query.QuerySet):
        def useractive(self,date):
            return self.filter( Q(active=True) & (Q(date__isnull=True)|Q(date__gte=date)) )
И потом во вьюшках:
Clazz.objects.useractive(now).order_by('date')

30 мая 2013 г.

django: отправка email шаблонами из FlatPage

Приспичило сделать в django отправку email с удобной настройкой формата(-ов) письма. Понятно, что при этом должны работать какие-то переменные в шаблоне, куда удобно подставится что-то заранее предусмотренное. Ну и придумал для этого использовать FlatPage, которые идут «искаропки» и уже нормально редактируются в админке (я прикрутил туда ещё и TinyMCE). Плюс надо бы письма слать в формате html, для пущей красоты.

20 мая 2013 г.

django: минимальное количество formset inline

Захотелось мне в django, чтобы formset содержал не менее некоторого количества полностью заполненных форм. С нормальной валидацией где нужно, а не в середине вьюшки, например. На момент 1.4.x ничего особо удобного на этот счёт не было (возможно, это странно), хотя какие-то реквесты в багзилле я находил. На самом деле довольно просто ручками всё решается наследованием и переопределением InlineFormSet при конструировании FormSet через factory (а я так использую всегда).

Допустим, мы конструируем FormSet таким образом (обратите внимание на formset=...):
ConductingClazzRequestMorePersonFormSet = inlineformset_factory(ConductingClazzRequest, ConductingClazzRequestPerson, extra=1, formset=ConductingClazzRequestPersonFormsetBase, form=ConductingClazzRequestPersonForm)

17 апреля 2013 г.

Баг в camera slideshow при одном слайде

Использовал довольно приятную крутилку баннеров (слайдшоу) camera slideshow (http://www.pixedelic.com/plugins/camera/). Но один косяк очень сильно напрягал всё время — при единственном слайде работает некорректно: показ этого слайда перемежается показом пустого слайда.

14 апреля 2013 г.

django: не разрешать формам в formset быть пустыми

В django при использовании formset по умолчанию* действует такая штука: при сохранении всего formset незаполненные формы (полностью незаполненные) в нём игнорируются и валидируются/сохраняются только остальные. Если по какой-то причине это неудобно (у меня, например, нужно было заполнять минимум N форм в формсете), то можно отключить, установив empty_permitted = False.

* На самом деле ставится некоторым (extra) формам при конструировании formset, что в общем-то логично.

В документации на этот счёт сказано: «The formset is smart enough to ignore extra forms that were not changed».

Можно намертво прописать в конструкторе формы, например.
class BlablaForm(forms.ModelForm):
    class Meta:
        model = Blabla

    def __init__(self, *arg, **kwarg):
        super(BlablaForm, self).__init__(*arg, **kwarg)
        self.empty_permitted = False

7 апреля 2013 г.

Самодельный упрощённый accordion на jquery

Нужна была схлопывалка дивов, с заголовками, раскрывающая содержимое по очереди. Изначально был код на jquery-ui через accordion, но мучение со стилями достало. Т.к. стили наворачиваются хардкорно, а переопределять все в css получается в несколько раз длиннее, чем сам код.
$( "#accordion" ).accordion({
  active: false,
  collapsible: true,
  header: ".accordion_later",
  heightStyle: "content",
  beforeActivate: function( event, ui ){
   ui.newHeader.hide();
   ui.oldHeader.show();
  }
 });
Рабочий пример: http://jsfiddle.net/Fx4nA/
Потом переделал тупо на jquery, получилось в несколько раз короче.
$('#accordion .collapsible').hide();
$('#accordion .accordion_later').bind('click', function (e) {
    // show all "more..."
    $('#accordion .accordion_later').show();
    // close all collapsible
    $('#accordion .collapsible').slideUp();
    // hide current "more..."
    $(e.target).hide();
    // open current collapsible
    $(e.target).next('div').slideDown();       
});
Рабочий пример: http://jsfiddle.net/8qtXH/

5 апреля 2013 г.

jquery ui dialog без заголовка

Убрать заголовок можно переопределив класс ui-dialog-titlebar, но чтобы не испортить все диалоги сразу можно выборочно через dialogClass.
dialogdiv.dialog({
...
    dialogClass: 'dialog-wo-title',
...

Потом скрываем и всё.
.dialog-wo-title .ui-dialog-titlebar
{
    display:none;
}
Или убираем всё кроме кнопки закрытия (которая остаётся справа)
.dialog-wo-title .ui-dialog-titlebar
{
    width: 0px;
    float: right;
}

22 марта 2013 г.

mysql 5.6 grant ERROR 2013 (HY000) Lost connection to MySQL server during query

После перехода с 5.5 на 5.6 при попытках некоторых операций, например, GRANT вываливает:
ERROR 2013 (HY000): Lost connection to MySQL server during query
Всё потому что нужно не забывать про:
# mysql_upgrade