Наследство с особенностями - 2
Я всё продолжаю по рабочим и не только нуждам ковырять наследование моделей, поэтому как и обещал - то ли ещё есть!
-
Очередной сюрприз ждал меня, когда я обновляя свой блог-движок под текущий транк. Я обнаружил сломанным блок “Архив” из-за того, что метод
dates()
перестал возвращать уникальные значения на месяца, а выдавал мне всё подряд. Я сильно удивился. Но пофиксил всё при помощи, в последнее время моего любимого,set
. Да, костыль, но ковырять дальше пока желания нет. -
Далее уже по рабочим нуждам обновления тест наборы для приложения, проявились некоторые особенности работы
fixtures
. Для примера, допустим у нас есть такие связанные наследованием модели:class Base( models.Model ): field1 = models.IntegerField( default = 10 ) # + неявное поле id class Derived( Base ): field2 = models.IntegerField( default = 1 ) # + неявное поле base_ptr
-
Сериализатор дампит модель наследника целиком, т.е. со всеми полями в том числе и родителей.
-
При загрузке модели родителей не могут подцепить свой первичный ключ, а просто делают новый INSERT, получая тем самым новый id. Потом этот id идет вверх по иерархии и затирает имеющиеся уже(загруженные из фиксчюры) значения. Похоже, что из-за этого ломается и CRUD.
Так же это эффект проявляется когда вы просто хотите присоединить к уже имеющемуся базовому объекту какое-то дополнение в виде объекта отнаследованной модели. Сделать какими-то очевидными путями это не возможно. Первый вариант, который приходит на ум не срабатывает:
>> Base.objects.count()
1
>> Derived.objects.count()
0
>> base = Base.objects.get()
>> d = Derived.objects.create( base_ptr = base )
>> Derived.objects.count()
1
>> Base.objects.count()
2
Вот так вот, мы получаем лишний объект предок. Не хорошо. Суть проблемы в том, что метод модели save_base
, в который выродился метод save
после qsfr, не может найти первичный ключ для предка и решает его “любезно” создать. Чего нам не надо.
Думаем дальше. Ну хорошо дадим ему явно первичный ключ предка(ха! а что будет, если наследование множественное, а ключи у предков названы одинаково, но значения у них разные? Гоним эти мысли прочь, а то лишимся сна:)). Вариант:
>> Base.objects.count()
0
>> Derived.objects.count()
0
>> base = Base.objects.create( field1 = 777 )
>> d = Derived.objects.create( base_ptr = base, id = base.id )
>> Derived.objects.count()
1
>> Base.objects.count()
1
>> Base.objects.get().field1
10
Поняли? Да, не найдя у объекта d
атрибуты field1
, его джанга взяла по умолчанию, затерев тем самым уже имеющееся значение 777.
Собственно следующий трюк логическое продолжение предыдущего, но лишающий джангу радости подставлять дефолтные значения (если их нет, кстати, то выругается СУБД, если поле не null = True
):
base = Base.objects.create( field1 = 777 )
d = Derived()
d.__dict__.update( base.__dict__ )
d.save()
Вот этот вариант пока работает. Будем посмотреть.
-
При идеи полной прозрачности для клиентского кода, что объект модели наследника является объектом модели предка, то с удаление объекта наследника не всё очевидно. Если вызвать
delete
у объекта наследника, то удалиться только он, а предок останется. Пример:>> Derived.objects.count() 1 >> Base.objects.count() 1 >> d = Derived.objects.get() >> d.delete() >> Derived.objects.count() 0 >> Base.objects.count() 1 Во как! Если вам такое поведение не нужно, лечение достаточно оригинальное - переопределить метод `delete` у модели наследника в которой удалить своего предка. Иллюстрация: def delete( self ): self.base_ptr.delete()
Из-за чего так. При удалении предка происходит стандартная попытка ORM джанги сохранить ссылочную целостность данный, что заставляет его искать присоединенные к этому объекты и их тоже тащить за собой в мир иной. А поскольку наследник и предок соединены неявный OneToOneField
, удаляя предка мы автоматически удалим его наследника.
-
Так же можно отметить интересный факт найденный Владимиром Володиным.
-
Присоединяйтесь!
Вот такая очередная сводка с фронтов освоения текущего транка. Чувствую, что это не конец и немного начиню понимать тех кто говорит, что джанга пока не стабильна. Да временами бывает.