Беру небольшую передышку в своих изысканиях о композитных полях для большего прилива вдохновения. Подамся-ка я в другую область на время.
Представим себе ситуацию(а лучше вспомним один из прошлых проектов, который вы делали:)), что есть пара приложений от сторонних разработчиков. И это не какие-то совсем простые приложения, а что-то более комплексное. А отличительной чертой их комплексности будет наличие в каждом своей модели профиля. Ведь было такое? Увы, было. Ну и пусть будут, только вот ещё незадача - их поля являются пересекающимися множествами. Ну т.е. и в том и в другом есть, допустим, поле с ссылкой на сайт владельца. И что же делать? Конечно, для облегчения работы интерфейс редактирования информации профиля один для выбранного основного профиля. А объектов профиля закрепленных за пользователем два (а может быть и больше, если проект используем много разных приложений). Надо как-то синхронизировать данные...
Вообще ситуация того, что в каждом мало-мальски большом приложении свой профиль - это как бы уже давно проблема. И тут ничег уже не поможет. Универсальный профиль помещенный в contrib, конечно, может как-то решить её, но не всегда. Да и потом, с профилем разберемся, так что-то другое появится. Надо находить универсальные пути решения.
Кстати, обсуждения данной темы были как на неформальной встрече джангистов по поводу 1.0 релиза, так и на не менее неформальном минибаре для программистов. В результате всех обсуждений, стало понятно, что решений данной проблемы два - это либо синхронизация пересекающихся полей в моделях, либо какой-то базовый прототип/интерфейс для модели, на который можно рассчитывать и ссылаться в коде.
Сейчас я покажу свой вариант первого решения - синхронизации. В лучших своих традициях - начну с примера.
Итак, есть две модели профиля пользователя в разных приложениях. Вот такие:
class MyProfile(models.Model):
user_ptr = models.ForeignKey(User, unique=True)
nickname = models.CharField(max_length=100)
www = models.URLField()
birth = models.DateField()
class OtherProfile(models.Model):
user = models.ForeignKey(User, unique=True)
nickname = models.CharField(max_length=100)
website = models.URLField()
dob = models.DateField()
Поля у них отражают одну и туже суть, но некоторые по разному названы. Для краткости, я не показывал отличных полей, но они скорей всего будут и будут отражать специфику уже конкретного приложения.
Нам надо чтобы объекты этих моделей, прикрепленные к одному и тому же пользователю, синхронизировались при изменении любых из них. Т.е. нам надо слить изменения одной в другую. Я почему-то решил назвать это, уже преследующим меня, словом - merge. И получился у меня merging framework.
Применим его для трекинга изменений в наших гипотетических профилях. Для указания мета информации трекинга, надо объявить объекты прослойки. Например так:
import merging
class MyProfileLayer(merging.ModelLayer):
model = MyProfile
fields = ["nickname"]
aliases = {
"site": "www",
"day_of_birth": "birth"
}
key = 'user_ptr'
class OtherProfileLayer(merging.ModelLayer):
model = OtherProfile
fields = ["nickname"]
aliases = {
"site": "website",
"day_of_birth": "dob"
}
key = 'user'
create = True
Тут я указал модель(model) и поля в атрибуте fields, в частности нужно сливать никнеймы. Для полей с разными названиями я указываю алиасы через aliases. Причем, обратите внимания, что алиасы имеют одинаковое название для обеих прослоек. На основе именно этих имен будет происходить синхронизация настоящих полей. Для обеих прослоек я указываю ещё и атрибут - key. На базе него будет происходить выборка связных профилей.
Для второй прослойки я ещё указал create=True, чтобы, если главный профиль(MyProfile) существует, а OtherProfile нет, создавался бы автоматически объект последнего.
Ура, описательную часть закончили, теперь надо запустить механизм отслеживания. Вот так:
merging.track([MyProfileLayer, OtherProfileLayer])
Конечно, прослоек может быть больше двух. Этот код, скорей всего, нужно размещать уже на уровне проекта, тем самым, связывая приложения в нем.
Итак, теперь все изменения в указанных полях будут перетекать в связные объекты:
>>> profile = MyProfile.objects.create(user_ptr=user, nickname="test",
... www="http://webnewage.org/", birth="1987-06-25")
...
>>> other = OtherProfile.objects.get(user=user)
>>> profile.__dict__
{'birth': datetime.date(1987, 6, 25),
'id': 1,
'nickname': u'test',
'user_ptr_id': 1,
'www': u'http://webnewage.org/'}
>>> other.__dict__
{'dob': datetime.date(1987, 6, 25),
'id': 1,
'nickname': u'test',
'user_id': 1,
'website': u'http://webnewage.org/'}
>>> profile.www = "http://yandex.ru/"
>>> profile.save()
>>> other = OtherProfile.objects.get(pk=other.id)
>>> other.website
u'http://yandex.ru/'
>>> other.nickname = "foobar"
>>> other.save()
>>> profile = MyProfile.objects.get(pk=profile.id)
>>> profile.nickname
u'foobar'
Отныне интеграция нескольких больших приложений может стать немного проще. Базовым требованиям такое решение удовлетворяет, но я уже вижу, где могут возникнуть проблемы и где можно сделать несколько улучшений.
Как обычно основные use cases покрыты тестами, так что можете смело пользоваться.
На очереди следующий вызов - фреймворк прототипов. Вперед и с песней!;)
Комментарии 5
Когда же будет метафреймворк метафреймворков? :-)
Оставлен 13 Октябрь 2008 в 19:46 ¶Иван, Вы узнаете об этом первым;-)
Оставлен 13 Октябрь 2008 в 20:46 ¶Оффтоп: Увидел в ссылках "Адриана Холовати". Его зовут Эдриан Головатый. Головатый - украинская фамилия.
Оставлен 08 Ноябрь 2008 в 23:13 ¶Да. Спасибо. То что у него русскоязычные корни я знал, но как-то не задумался над фамилией тогда.
Оставлен 08 Ноябрь 2008 в 23:31 ¶У него украиноязычные корни.
Оставлен 08 Ноябрь 2008 в 23:34 ¶Оставьте комментарий