Вчера вечером бухал водку. Голяком, потому что за колой было лень идти. Включил самый лучший в мире алкотрек на повтор, и так размазало что пиздец. К несчастью, весь следующий день — лишнее доказательство тому, что этот ваш алкоголь — ёбаный яд. С нормальными веществами как: схавал, поколбасило, отпустило, выпил водички, скушал яблочко, погрустил часок-другой, и как ни в чём не бывало живёшь дальше. Нет блять, обязательно должно жечь желудок, подташнивать, стучать сердце, грязная голова, папа нагрянул в гости, а у меня в раковине до потолка, везде еда и пустые пакеты. Впрочем, не об этом речь. Когда я совсем уже хороший завалился спать, посетила мысль, что 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); } }); });
Всё блядь, работает. От похапэ не уйдёшь. Пиздец.
Завтра покажу Элле и потом переделаю нормально!
Желудок пидор до сих пор болит 
я почему-то думал, что это что-то бодрое-быстрое, по типу хадукен 
Хм, сначала хотел сказать, что неслухабельно, а потом втянулся

На 10.30 так и представляю тебя, пьющего в одного


Именно 10:30! Жалко, что один из них пришлось провести в ванной (тревога оказалась ложной). Интересно как ты дотуда дослушал.
anonymous, ты кто бля воще?
Жизнь кстати хорошая, а курица в духовке coming very soon, у меня просто картошка кончилась, а овощехранилище только в четверг


А у меня есть курица из духовки

Доступные тэги: <b>, <i>, <s>, <u>, <a href="">, <img src="" /> (загрузить), <pre>, <quote>
- themylogin › Самое дорогое в жизни Вчера, 02:19
- ramwoolf › Am I not always be wanting this? (x8) 16 мая, 23:08
- anonymous › Написание «не» с различными частями речи 16 мая, 16:06
- anonymous › A Tragedy in the Air 15 мая, 18:24
- themylogin › Итоги 2011 15 мая, 12:10
- themylogin › На гелике езжу 13 мая, 16:24
- anonymous › Waking up at ten 13 мая, 13:16
- themylogin › Жук 12 мая, 11:55
- themylogin › Давайте шутки из твиттера продолжим развивать здесь 7 мая, 11:03
- anonymous › Шевченко лох 6 мая, 14:02

