Вчера состоялось долгожданное вливание ветки "magic-removal" в основной код Джанго. Вкратце это означает, что Джанго стал неизмеримо лучше, и если вы до сих пор откладывали знакомство с ним, то сейчас самое время!
Если же уже начали, то придется немного попотеть, переводя старый код на новый API, благо это последнее обратно несовместимое изменение такого масштаба перед версией 1.0.
Немножко истории
Magic-removal изначально был задуман для того, чтобы избавить ORM-слой от архитектурных неочевидностей, на которые натыкались буквально все новички. В частности, питоновский модуль с классами, которые описывают модели данных, не использовался в системе напрямую. Вместо этого классы из него читались, и на основе их полей и методов в памяти создавалась пачка новых динамических модулей, которые "магическим образом" появлялись в программе (отсюда название бранча). Помимо самой неочевидности это вылезало еще множеством разных плохих эффектов:
- в магических модулях не были видны подключенные библиотеки и описанные константы
- пользовательские атрибуты классов тоже не попадали в магические модули, потому что Джанго вытаскивал из классов только те атрибуты, которые знал
- в классах нельзя было использовать питоновские property
Так вот первоначальная идея была этот подход убрать и сделать так, чтобы пользователь работал непосредственно с теми классами, которые сам описал. Но процесс зашел гораздо дальше. Поскольку из-за этого изменения менялся и сам API доступа к объектам БД, все новые фичи, не связанные напрямую с magic-removal, рекомендовалось делать сразу в новом API. Что вскоре привело к тому, что практически вся разработка велась в этой неосновной ветке. И зашла она, надо сказать, очень далеко.
Переход
В итоге сейчас, когда новый API стал основным, возникает вопрос, что делать с написанным кодом.
В отличие от предыдущих изменений, magic-removal — это большое изменение. Поэтому если ваш код уже работает на живом сайте (интересно, кто-нибудь уже успел у нас? :-) ) или просто очень близок к выпуску, то лучше его не трогать. Но надо помнить, что Джанго в старом виде обновляться больше не будет.
Если же вы где-то в середине процесса, то призываю потратить время и все перевести. Потому что новый код просто лучше. Он гораздо логичней, он позволяет делать через джанговские функции даже очень сложные вещи, для которых раньше пришлось бы писать немаленький SQL, там есть транзакции и custom-менеджеры объектов (все хочу отдельно про них написать).
Само же изменение кода не столько сложное, сколько муторное. Оно полностью описано в инструкции Removing The Magic и требует аккуратного перевода кода. Чтобы можно было прикинуть масштаб работы, вот цифры. Мой "Некий Музыкальный Сервис" — это 17 моделей с примерно 50 нестандартными методами, около 70 view и около 60 шаблонов. Перевод занял один день, и за следующую неделю нашлось 2 или 3 бага, связанных непосредственно с переводом. То есть, процесс довольно надежный. Надо сказать, что я сильно сэкономил время на том, что можно было не сохранять текущие тестовые данные в базе, поэтому я просто всю ее грохнул и создал новую. То есть переименовывать таблички мне не пришлось.
В заключение приведу кусок кода, изменение которого произвело на меня самое неизгладимое впечатление. Речь идет о фичке под названием "скачивают также": на странице альбома рисуется список альбомов, которые скачивают пользователи, скачавшие этот альбом. Есть, соответственно, три таблички: "Альбом", "Пользователь" и "История скачивания" со ссылками на первые две.
Вот как список доставался раньше:
def downloaded_with(self):
cursor = db.cursor()
cursor.execute("""
SELECT DISTINCT album_id, download_count
FROM main_historys, main_albums
WHERE
main_historys.album_id=main_albums.id AND
user_id in (
SELECT DISTINCT user_id FROM main_historys where album_id=%s) AND
album_id<>%s
ORDER BY download_count DESC
LIMIT %s
""", [self.id, self.id, MAX_DOWNLOADED_WITH_COUNT])
from django.models.main import albums
album_ids=[int(r[0]) for r in cursor.fetchall()]
return album_ids and albums.get_list(id__in=album_ids)
Фактически все вручную. Новый API позволяет сделать так:
def downloaded_with(self):
return Album.objects.filter(history__user__history__album__pk=self.id).exclude(pk=self.id).distinct().order_by('-download_count')[:settings.MAX_DOWNLOADED_WITH_COUNT]
Выполняется за один запрос (с какими-то дикими INNER JOIN
ами, которых я никогда не мог понять :-) ).
P.S. Да... Ежели что будет не получаться, пишите в форум, подумаем.
Комментарии: 5
Хм, оно конечно хорошо, что так укоротилось, но на самом деле... Если в верхнем я понимаю, что происходит (ну благодаря тому, что SQL прочесть можно - а остальное уже не так важно), то второе... сижу вот разбираюсь. :]
Н-да... Нет, ну понять можно, но конструкция просто страшная. Пока в vim'е не реализуют автодополнение такой дряни, реально пользоваться таким будет очень сложно.
И, блин, как же достают эти питоновские два подчёркивания :4a7d3d609129a9296bf7ac0608c2097
Читать это надо так:
"Альбомы, у которых есть записи в историю, у которых есть юзеры, у которых есть записи в историю, у которых есть альбом такой-то".
То есть
__field
— это присоединение по совпадению ключей.А! Ну теперь понятнее стало. А __pk - это primary key? Вообще немного непривычно реализовано, проще было бы что-то типа history.Contains(user.Contains(..., чем вот такая интересная конструкция. ;)
Но всё достаточно весело - я уже начинаю подумывать, а не зря ли я схватился за Турбогеарс? ;)
P.S. Что-то в моём предыдущем комментарии так смайлик покоцало. :(
Да,
__pk
— это равенство primary key.Синтаксис действительно немного непривычный, и в django-developers Евгений Лазуткин предлагал его поменять. Но проблема в том, что все предложенные варианты все равно выглядят не особенно лучше. Поэтому решили не менять.
(Про Turbogears не буду, я предвзят :-) )