Опубликовано 28 февраля 2011, 00:19 под Kool and the Gang - Hollywood Swinging

Вчера вечером бухал водку. Голяком, потому что за колой было лень идти. Включил самый лучший в мире алкотрек на повтор, и так размазало что пиздец. К несчастью, весь следующий день — лишнее доказательство тому, что этот ваш алкоголь — ёбаный яд. С нормальными веществами как: схавал, поколбасило, отпустило, выпил водички, скушал яблочко, погрустил часок-другой, и как ни в чём не бывало живёшь дальше. Нет блять, обязательно должно жечь желудок, подташнивать, стучать сердце, грязная голова, папа нагрянул в гости, а у меня в раковине до потолка, везде еда и пустые пакеты. Впрочем, не об этом речь. Когда я совсем уже хороший завалился спать, посетила мысль, что Symfony, будучи писанным на питоне, окажется получше Django. Погибче, попродуманнее. По части админки по крайней мере точно. Беспонтовая админка там, всякую лапшу приходится писать типа:

class PageChoiceField(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        return obj.indented_menu_title()

class PageForm(forms.ModelForm):
    parent = PageChoiceField(label=u'Родитель', queryset=Page.objects.order_by('global_order'))
    
    class Meta:
        model = Page

class PageAdmin(admin.ModelAdmin):
    form = PageForm

Пьяное русское быдло, видимо, не подумало, что в Symfony придётся делать то же самое, только на ПОХАПИ. Но админка на самом деле далека от идеала.

Сегодня, например, потребовался упорядоченный Many-to-many relationship. Ну то есть если я указал, что к ТюремномуОбеду относятся блюда Первое, Второе и Компот, то в таком порядке они и должно храниться и выводиться. Нагуглилось:

  • Подтверждение того, что в самом Django такого бессмысленного и абсолютно никому не нужного функционала нет.
  • Отличный совет воспользоваться intermediary model и в ней добавить поле order. А то я, блядь, не додумался. Админка положит на это поле, я просто уверен, если вообще будет работать с такой моделью.
  • Убогий рюсский форум
  • На четвёртой (!) странице «django ordered many-to-many» заменитель ManyToManyField. На первый взгляд то, что нужно, только в админке он предлагает сделать наше поле raw text field и вводить id в нужном порядке через запятую, ничего, перекроем javascript'ом. Только вот модуль оказался нерабочим.

Хуле, давайте на питоне программировать!

Итак, ставим задачу. Предположим, что галочки браузер отослал в нужном порядке (на javascript попрограммируем потом, и вообще я знаю тут одно местечко...), и теперь нам нужно их в таком же порядке вставить в промежуточную таблицу. Первое препятствие на нашем пути поджидает прямо на входе: метод clean из ModelMultipleChoiceField. Смотрите что этот сучёнок делает:

def clean(self, value):
        ...
        qs = self.queryset.filter(pk__in=value)
        ...
        return qs

То есть ему на вход дают упорядоченный МОССИВ, а он этот порядок бездарно проёбывает, ведь ORDER BY FIND_IN_SET(...) наш бедненький ORM не поддерживает! Времени мало, так что строчку безжалостно заменяем на:

qs = [self.queryset.get(pk=val) for val in value]

Будет МНОГО SQL-ЗАПРОСОВ ТОРМАЗИТ СЕРВЕР ЛЁГ, но это же админка, в ней один человек сидит раз в день. Бтв как это заменяем? Исходники Django что ли правим? Нихуя! Субклассим, наследование, ПОЛИМОРФИЗМ-КОММУНИЗМ, получается SortableModelMultipleChoiceField.

Следующий этап на пути наших данных — сохранение. Здесь порядок тоже проёбывается и опять с благой целью — не вставлять уже вставленные данные об отношениях. Класс ManyRelatedManager, метод _add_items:

# Check that all the objects are of the right type
                new_ids = set()
                for obj in objs:
                    if isinstance(obj, self.model):
                        new_ids.add(obj._get_pk_val())
                    elif isinstance(obj, Model):
                        raise TypeError, "'%s' instance expected" % self.model._meta.object_name
                    else:
                        new_ids.add(obj)
                # Add the newly created or already existing objects to the join table.
                # First find out which items are already added, to avoid adding them twice
                cursor = connection.cursor()
                cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
                    (target_col_name, self.join_table, source_col_name,
                    target_col_name, ",".join(['%s'] * len(new_ids))),
                    [self._pk_val] + list(new_ids))
                existing_ids = set([row[0] for row in cursor.fetchall()])

                # Add the ones that aren't there already
                for obj_id in (new_ids - existing_ids):
                    cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
                        (self.join_table, source_col_name, target_col_name),
                        [self._pk_val, obj_id])

Думать как переписать это так, чтобы порядок сохранялся, влом, поэтому просто заменим на:

cursor = connection.cursor()
                cursor.execute("DELETE FROM %s WHERE %s = %s AND %s IN(%s)" % \
                    (self.join_table, source_col_name, self._pk_val, target_col_name, ",".join([str(obj._get_pk_val()) for obj in objs])))
                for obj in objs:
                    cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
                        (self.join_table, source_col_name, target_col_name),
                        [self._pk_val, obj._get_pk_val()])

На сей раз действительно заменим в файле, потому что изучать и субклассить всю иерархию от поля до менеджера никакого желания нет (водку бухал вчера, не забывайте). Поэтому, файл django/db/models/fields/related.py. Санёк, запоминай, на fpk.nstu.ru тоже править будем!

Хм, теперь данные сохраняются в нужном порядке (это в блоге всё быстро, а на деле прошло часа два). Остаётся задавать этот порядок в админке (как это делать стандартным джанговским filter_horizontal надо ещё додуматься, и то будет очень неудобно). На помощь приходит jQuery UI Multiselect. Отличная штука! Водгружаем её javascript'ом на место стандартного селекта, и сталкиваемся ещё с одной проблемой: при редактировании существующих отношений, продукты оказываются в списке упорядоченными по алфавиту, а нужные просто отмечены галочками, и переносятся в список выделенных тоже по алфавиту! Сука! Пробежался глазами по ui.multiselect.js, как задавать исходный порядок не нашёл. Очень странно, учитывая его умение сортировать. Ну да ладно. А откуда мы возьмём исходный порядок?

Нафигачим скрытых инпутов по порядку, оттуда и возьмём! Инпуты логично расположить сразу после селекта. Лезем к forms.SelectMultiple, видим:

def render_options(self, choices, selected_choices):

Как вы думаете, в selected_choices галочки идут по порядку? НЕТ БЛЯДЬ, ОНИ ТАМ НЕ ПО ПОРЯДКУ, НУ ЧТО ЗА ПИЗДЕЦ, ТАМ-ТО НА ХУЙ СОРТИРОВАТЬ? Это опять по иерархии лезть выше, искать падлу, которая сортирует, потом опять править исходники или создавать кучу классов... Нет уж, пошло оно всё в хуй. Дамы и господа, приготовьтесь, ТАКОГО вы ещё не видели:

class SortableTeacherSelectMultiple(forms.SelectMultiple):
    def render_options(self, choices, selected_choices):
        # I HATE DJANGO
        """Walk up the stack, return the nearest first argument named "request". Hope it is HttpRequest"""
        import inspect
        frame = None
        request = None
        try:
            for f in inspect.stack()[1:]:
                frame = f[0]
                code = frame.f_code
                if code.co_varnames and code.co_varnames[0] == "request" and type(frame.f_locals['request']).__name__ == "WSGIRequest":
                    request = frame.f_locals['request']
        finally:
            del frame

Вот так невинно начинается код нашего виджета. Поскольку это вам не похапэ и глобального массива $_GET нет, чтобы узнать, кого мы сейчас редактируем, бежим по стеку вызовов, находим переменную, названную request и надеемся, что это посланный нам запрос! Именно надеемся: там, откуда я взял этот код, последней проверки не было (а вроде прилично выглядел...), наверное, потому что это не кроссплатформенно Так или иначе, далее всё происходит как обычно (только все опции будут не выбраны), а потом прям как на похапэ:

from django.db import connection
        cursor = connection.cursor()
        cursor.execute('SELECT teacher_id FROM fpksite_course_teachers WHERE course_id = %s ORDER BY id ASC' % request.path.rstrip('/').split('/')[-1])
        html += u'\n' + u'\n'.join(['<input type="hidden" name="selected_teacher[]" value="%s" />' % row[0] for row in cursor.fetchall()])

Ну и потом немного правим конструктор нашего виджета (тут тоже заебался, по-петушански как-то сделано):

var that = this;
        var items = $(this.options.alreadySelected).each(function(_, selectedOption){
            that.availableList.children('.ui-element').each(function(_, option){
                if ($(option).data('optionLink').val() == $(selectedOption).val())
                {
                    $(option).find('a.action').trigger('click');
                    $(option).data('optionLink').remove().appendTo(that.element);
                }
            });
        });

Всё блядь, работает. От похапэ не уйдёшь. Пиздец.

Завтра покажу Элле и потом переделаю нормально!

Желудок пидор до сих пор болит

Комментарии (4)
s_mordvinov
28 февраля 2011, 00:51
S Q #1
Слышь, а годспид ю блэк эмперор весь такой? я почему-то думал, что это что-то бодрое-быстрое, по типу хадукен

Хм, сначала хотел сказать, что неслухабельно, а потом втянулся
На 10.30 так и представляю тебя, пьющего в одного
Слышь, а годспид ю блэк эмперор весь такой? =-O я почему-то думал, что это что-то бодрое-быстрое, по типу хадукен :( Хм, сначала хотел сказать, что неслухабельно, а потом втянулся =-O На 10.30 так и представляю тебя, пьющего в одного :D
anonymous
28 февраля 2011, 08:03
S Q #2
где-то в твиттере наш thelogin обещал "хорошую жизнь и курицу в духовке"
где-то в твиттере наш thelogin обещал "хорошую жизнь и курицу в духовке" :-!
themylogin
28 февраля 2011, 10:44
S Q #3
Да, он весь такой. Потому что Godspeed - это "бог в помощь".
Именно 10:30! Жалко, что один из них пришлось провести в ванной (тревога оказалась ложной). Интересно как ты дотуда дослушал.

anonymous, ты кто бля воще?
Жизнь кстати хорошая, а курица в духовке coming very soon, у меня просто картошка кончилась, а овощехранилище только в четверг
Да, он весь такой. Потому что Godspeed - это "бог в помощь". Именно 10:30! Жалко, что один из них пришлось провести в ванной (тревога оказалась ложной). Интересно как ты дотуда дослушал. <b>anonymous</b>, ты кто бля воще? Жизнь кстати хорошая, а курица в духовке coming very soon, у меня просто картошка кончилась, а овощехранилище только в четверг :(
s_mordvinov
28 февраля 2011, 11:00
S Q #4
Я эту песню в итоге раз 6 переслушал. Она с твоего поста, до моего поста играла постоянно.

А у меня есть курица из духовки
Я эту песню в итоге раз 6 переслушал. Она с твоего поста, до моего поста играла постоянно. 8-) А у меня есть курица из духовки :-P
Добавить комментарий
Войдите через  Доступные тэги: <b>, <i>, <s>, <u>, <a href="">, <img src="" /> (загрузить), <pre>, <quote>
Теги
1nsk Alternative E-Business Embedded GNU/GPL-софт HTML/CSS ICQ KDE last.fm Linux Linuxnsk Live Lyceum game Microsoft Motivator MySQL New Age P2P Party Photoshop PHP Python Qt4/C++ SEO Sibnet thelogin.ru Timelapse Web 2.0 Webdev Webstream Windows Wireless А я говорил! Авто Админ Алексеев Аниме Атмосфера Безобразие Бесплатный Wi-Fi Блоги Большой бизнес Боты Бред Бухать Бытовуха Велосипед Весна Вечер Взлом Винил Винтаж Вконтакте Вода Воспоминания Гетто Гламур Гопота Горский Графити Грузовик Дача Двор Девушки Девченки!!! Деньги Дизайн Дом Домофоны Драка Драки Еда Железо Животные Заведения общественного отдыха Задроты Затулинка Зима Игрушки Игры Идея Интересно Интернет Исследование История Казань Карикатура Квартира Класс Компьютеры Коромшук Коты КПК Кран Красиво Криминал Крыша Курение Лето Литература Лицей Лицей НГТУ Лохи Магнитофоны Маркса Мат Математика Машинный перевод Мегафон Менты Метро Мечты Мифы Мне стыдно Мобайл Можга МТС Музыка Мультикасса Мультики Мы помним Надписи Наука НГТУ Недвижимость Непоняятно Новосибирск Носители Ноутбуки Ночь Обидно Обман Образование Общественный транспорт Омск Опечатки Орфография Осень Парк Периферия Пиратство Плохо Погода Поезд Помойка Праздники Презрение Природа Провайдеры Программирование Протест Радиотехника Растения Реклама Религия и вера Ретро Рисунок Руины Рэп Салют Сервер Серверы Сервис Сериалы Скринкаст Скриншоты Смешно СМИ Снег Собаки Совет Софт Спам Спасём Россию Спецтранспорт Стоп-ляп Стройка Студяга Танцы ТВ Типа страх Трамваи Транс Транспорт Уважаю Ужас Умный дом Утро Филдрепорты Философия Форум ФПМИ Хикки Хостинг Цитатник Школа Школа 208 ЭГ Эксперимент Юго-Западный
Twitter 28-02-2011
Lorem Ipsum
Нашёл все 8 лаб второго варианта по упресам. Студент: Тракимус Ю.В. Новосибирск, 2001 год.
Lorem Ipsum
Проснулся на кровати - значит плохо побухал!
Lorem Ipsum
Расскажи друзьям!
Lorem Ipsum
В шерегеше. Тут классно:):)
Lorem Ipsum
Бабушка онлайн серьёзно напрягает.
Lorem Ipsum
Попытался облокотиться на спинку дивана, который выкинул в июле
Lorem Ipsum
mptt.admin.FeinCMSModelAdmin падает, если в модели есть поле short_title. Угадайте есть ли оно в моей модели.