И так, некоторые время назад состоялось моё погружение в новый для джанго мир наследования моделей. Как и в моих домашних проектах и так и в рабочих. Исследованию подверглось мульти-табличное наследование. Среди достаточного количества удобных и полезных свойств наследования преподнесло несколько неожиданностей и даже ошибок, которые были обнаружены в механизме самой джанги.
Начнем с особенностей:
Кастомные менеджеры наследуются. Что вполне логично, если учесть, что менеджер это всего-лишь обычное поле класса, которое при наследовании переходит к наследнику. Но, что не очень очевидно на первый взгляд, так то, что он остается "прикрепленным" к модели предка и его использование с моделью наследника будет давать не совсем ожидаемы результат. Вот пример:
class Base( models.Model ): field1 = models.IntegerField() objects = models.Manager() my_custom_manager = MyManager() class Derived( Base ): field2 = models.IntegerField() #... Derived.my_custom_manager.filter( field2 = 777 )# Вот это не сработает # и ругнется на отсутсвие данного поля у Base модели, что есть правдаОтсюда вывод - переопределяйте свои менеджеры в моделях наследниках. Вот соответствующее обсуждение в джанго-девелоперс
Теперь немного копнем внутрь. Как вам известно список
fieldsу мета объекта модели содержит в себе поля данной модели. Так вот, у класса наследникаDerivedModel._meta.fieldsон содержит все поля, в том числе и поля всех родителей. Вот с этой конкретными моментом связана найденная ошибка - видимо Малкольм сам позабыл, что он сделал, но об этом попозже чуть-чуть. Если вам нужны поля только отнаследованной модели, то есть новый списокlocal_fields, где присутствуют только поля данной конкретной модели. Так же введен новый списокparents, где перечислены все модели предки данной.В стандартном CRUD'е, на странице редактирования модели наследника можно изменить и поля родителя, но при этом при сохранения джанге хочется предка создать заново, а не изменить имеющегося. Побыстрее бы уже newforms-admin влился...
И последняя особенность, которую словил я уже не из-за непосредственно наследования, но из-за qsrf, работая надо обновлением блога:
- Эксепшен о максимальной глубины рекурсии при простом запросе к базе. Минимальный пример сделать пока не получилось, но я попробую эту проблему поизучать. Пока обошелся workaround'ом в коде.
Теперь самое интересно и неожиданное. Не успел я похвалить валидность ORM, как практически сразу наткнулся на грубейшую ошибку. Пример кода, который рубит генерацию правильного SQL у нашего родного джанговского ORM:
class Base( models.Model ):
field1 = models.IntegerField()
class Derived( Base ):
field2 = models.IntegerField()
class Holder( models.Model ):
ref = models.ForeignKey( Derived )# обратите внимание - на наследника ссылка!
#...
query_set = Holder.objects.all().select_related( "ref" )
list( query_set )# Тадам!
Вот именно в последней строчке нам база данных скажет, что у таблицы myapp_derived нет поля field1. И эта чистая правда. Это поле есть у myapp_base, но её ORM забыл приджоинить и поменять алиас таблицы.
Виновник в коде джанго был обнаружен быстро, но исправить ошибку сразу не получилось. Т.е. добить генерации валидного SQL запросы было легко, но чтобы потом выбранные поля подхватились ORM не получилось. Но благодаря мозговому штурму Ивана, был создан-таки патч, который решает проблему и не валит тесты. Так что если вы планирует такой паттерн использования наследования, то рекомендую не забывать про этот патч. Но может быть ещё история не закончилось и далее всплывут какие-то особенности, пока Малкольм ненадолго(видимо) отошел от дел, точно сказать нельзя.
Вот таким интересным стало моё погружение в qsrf. То ли ещё будет!:)

Поправьте "Малкольм сам позабыл, что он сделала" = ... сделал, "Как вам известно список fields у мета объекта модели содержит в себя поля данной модели" = .. содержит в себе..
Спасибо.
Спасибо, интересная статья :)
p.s. Поправьте только "пока Малкольм ненадолго(видимо) отошел отдел" :) "от дел", я так понимаю..
Hard to read (Russian is hardly considered lingua franca of IT), but anyway nice rant.
Почему так происходит? У меня похожее поведение в связке newforms ModelForm + FormPreview + model inharitance. где-то теряется instance формы... связки newforms ModelForm + model inharitance и newforms ModelForm + model inharitance работает как требуется. код один и тотже для всех моделей, как-бы свои generic views.
Александр, заранее прошу прощения за вопрос не в тему статьи.
Где вы взяли код такого вменяемого паджинатора на этом блоге? :)
Самому интересно. Надо ковырять.
Хм... Да как-то он сам такой получился. Но он не очень вменяемый на самом деле. Надо прикрутить "..." при большом количестве страниц. А то уже скоро уже не будут помещаться:)
Александр, а вот, что нашел я:
Потом создаем троечку TestInh, добавляем первому в друзья двоих других. И пытаемся получить их в списке: t1.friends.all()
Получаем вот что:
Таблица myapp_test (из которой и берется инфа о "друзьях") джойнится по айдишнику юзера, друзей которого мы и пытаемся достать.