Как вы догадались, я продолжаю тему денормализации и моей реализации композитных полей.
С момента прошлого поста я успел значительно улучшить базовый CompositionField и решить несколько концептуальных проблем.
Итак что же новое появилось:
- Сделал низкоуровневый
CFполноценным классом, который теперь удобно сабклассить и добавлять новый функционал - Появилось место для интроспекции. Присоединение к хост модели стало более интеллектуальным из-за возможного отложенного присоединения.
- Чуть-чуть изменился интерфейс - параметр
commitтеперь может быть задан для конкретного триггера. Это сугубо практическое изменение, которое помогло решить одну проблему с бесконечной рекурсией. - По совету Вани Сагалаева добавил генерацию
freeze_FOOметода, который включает/выключает обработку сигналов. Полезно когда надо обработать какие-то данные скопом, а потом так же скопом пересчитать денормализованные поля.
Я грозился написать высокоуровневые обертки над CF чтобы облегчить использование и упростить конечный интерфейс. Сегодня я расскажу о первом сабклассе CF: ForeignAttributeField - поле которое отслеживает изменения некого внешнего поля в связанном объекте. Причем, поле может находится на любом уровне вложенности связи, что иногда очень удобно. Как я уже писал в прошлый раз - поля объектов, на которые непосредственно ссылается модель, так денормализовывать смыла мало. Проще использовать select_related. Но если заветный атрибут лежит глубже и для доступа к нему надо приджоинить, допустим, пять таблиц, то тут уже другой расклад и это может оказаться вполне хорошим юзкейсом. Кстати, это поле очень похоже на оригинальный DenormField, предложенный Эндрю Годвином. Я конечно не буду тут показывать пять уровней связей, а покажу лишь две, но формально их может быть сколь угодно много. Так же и композитных полей в модели может быть не ограниченное количество.
Пример с фильмом и режиссером из предыдущего поста перепишу по новому и добавлю ещё одно поле:
class Country(models.Model):
name = models.CharField(max_length=250)
class Person(models.Model):
name = models.CharField(max_length=250)
country = models.ForeignKey(Country)
class Movie(models.Model):
title = models.CharField(max_length=250)
director = models.ForeignKey(Person)
director_name = ForeignAttributeField("director.name")
director_country = ForeignAttributeField("director.country.name")
Теперь посмотрим как это работает:
>>> country = Country.objects.create(name="USA")
>>> person = Person.objects.create(name="Steven Spielberg", country=country)
>>> movie = Movie.objects.create(title="ET", director=person)
>>> movie.director_name
'Steven Spielberg'
>>> movie.director_country
'USA'
>>> person.name = "Steven Allan Spielberg"
>>> person.save()
>>> movie = Movie.objects.get(pk=movie.id)
>>> movie.director_name
u'Steven Allan Spielberg'
>>> country.name = "United States"
>>> country.save()
>>> movie = Movie.objects.get(pk=movie.id)
>>> movie.director_country
u'United States'
Как видите всё довольно прозрачно и естественно. За это я и боролся. Надо только отметить, что в данном примере я явно каждый раз после изменения связных моделей тяну из базы объект movie, чтобы в нем были уже обновленные данные.
Кстати о борьбе. Скажу я вам, это потрясающий фан - придумывать и реализовывать подобные вещи. Сколько же нового понимаешь о структуре фреймворка. Словно как какой-то путешественик, пробираешься сквозь джунгли, прорубаешь себе дорогу и находишь выходы в непростых ситуациях. Очень интересно, занимательно и позновательно.
Поразило также насколько универсальным и обобщенным оказался CF сам по себе. Поскольку его функциональную часть не пришлось менять практически совсем за исключением выноса параметра commit в объявление триггера. Это связано с тем, что далеко не после всех вызовов обработчиков сигналов можно звать save объекта. Например его нельзя вызывать после pre_save/post_save сигналов того же объекта что и был обновлен, а то получается - бесконечная рекурсия.
Ещё - вся функциональность этого высокоуровнего поля заключается в правильном составлении тригеров и обработчиков событий (на базе интроспекции моделей), которые передаются в "отложенный" конструктор CF. Ничего более. Супер!
Файл с исходниками доступен как и раньше тут. Если вы его посмотрите, то заметите заготовки для ещё двух типов полей: ChildsAggregationField, AttributesAggregationField - но они пока на уровне идеи. Как дойдут руки, то обязательно их реализую. Некие намеки на их использования можно посмотреть в тестах.
Ах да, все примеры использования которые я приводил и буду - либо уже являются частью тест набора для CF либо будут туда вписаны. Сами тесты доступны здесь. Они тоже пока ещё не устаканились, не покрывают максимум кода и возможно подвергнуться какой-то реорганизации. Но как источник примеров годятся уже сейчас.
А вам как? Нравится?
Комментарии 8
Красота =)
Оставлен 07 Октябрь 2008 в 10:39 ¶Алекс, я твой фанат)
Оставлен 07 Октябрь 2008 в 12:21 ¶Я пока не совсем понял. Какое практическое применение? Чтобы избежать кучу джоинов?
Оставлен 07 Октябрь 2008 в 12:31 ¶Точно. Эдакий кеш атрибутов. Но, конечно, эффективен он, когда уровней связи много.
Оставлен 09 Октябрь 2008 в 22:24 ¶Сделал для своих нужд шорткат для кол-ва связанных объектов ForeignCountField:
Что думаете насчет кода?
Оставлен 26 Апрель 2009 в 02:28 ¶Идея хорошая. Вообще, чем больше таких шорткатов тем лучше. Самые распространенные случаи вполне можно охватить.
Надо подумать насчет выделения композиций в отдельное приложение. Нет желания помочь?:-)
Оставлен 26 Апрель 2009 в 15:00 ¶bq. Нет желания помочь?:-)
Спасибо, что подрерактировал мое описание. Вынести его конечно было бы правильным - мне вот потребовалось использовать только composition.py и я сделал симлинк /utils/composition.py. Выделение его в отдельное приложение разве сложный процесс? Или ты предпологаешь некий рефакторинг?
Оставлен 26 Апрель 2009 в 15:49 ¶Суть в том что просто вытащить модуль наружу не интересно. Надо оформить стандарный питонячий пакет, с
setup.pyи прочими радостями. Разместить его в PyPI, чтобы была возможность его устанавливать черезeasy_install/pip.Плюс какой-то минимум документации надо предоставить.
Но главное надо побольше шорткатов написать. Возможно и отрефакторить в каких-то местах уже имеющийся код.
За время с момента когда я это разрабатывал по сути никаких настолько гибких альтернатив не появилось.
Поэтому мне кажется что такой пакет может оказать полезным. А его maitenance не такая простая задача.
Оставлен 26 Апрель 2009 в 17:10 ¶Оставьте комментарий