Сливаемся

Беру небольшую передышку в своих изысканиях о композитных полях для большего прилива вдохновения. Подамся-ка я в другую область на время.

Представим себе ситуацию(а лучше вспомним один из прошлых проектов, который вы делали:)), что есть пара приложений от сторонних разработчиков. И это не какие-то совсем простые приложения, а что-то более комплексное. А отличительной чертой их комплексности будет наличие в каждом своей модели профиля. Ведь было такое? Увы, было. Ну и пусть будут, только вот ещё незадача - их поля являются пересекающимися множествами. Ну т.е. и в том и в другом есть, допустим, поле с ссылкой на сайт владельца. И что же делать? Конечно, для облегчения работы интерфейс редактирования информации профиля один для выбранного основного профиля. А объектов профиля закрепленных за пользователем два (а может быть и больше, если проект используем много разных приложений). Надо как-то синхронизировать данные...

Вообще ситуация того, что в каждом мало-мальски большом приложении свой профиль - это как бы уже давно проблема. И тут ничег уже не поможет. Универсальный профиль помещенный в 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

  1. Иван Сагалаев написал:

    Когда же будет метафреймворк метафреймворков? :-)

    Оставлен 13 Октябрь 2008 в 19:46
  2. Александр Кошелев написал:

    Когда же будет метафреймворк метафреймворков? :-)

    Иван, Вы узнаете об этом первым;-)

    Оставлен 13 Октябрь 2008 в 20:46
  3. Boo написал:

    Оффтоп: Увидел в ссылках "Адриана Холовати". Его зовут Эдриан Головатый. Головатый - украинская фамилия.

    Оставлен 08 Ноябрь 2008 в 23:13
  4. Александр Кошелев написал:

    Оффтоп: Увидел в ссылках "Адриана Холовати". Его зовут Эдриан Головатый. Головатый - украинская фамилия.

    Да. Спасибо. То что у него русскоязычные корни я знал, но как-то не задумался над фамилией тогда.

    Оставлен 08 Ноябрь 2008 в 23:31
  5. Boo написал:

    То что у него русскоязычные корни я знал, но как-то не задумался над фамилией тогда.

    У него украиноязычные корни.

    Оставлен 08 Ноябрь 2008 в 23:34