Александр Кошелев
Александр Кошелев Python-разработчик

Наследство с особенностями

И так, некоторые время назад состоялось моё погружение в новый для джанго мир наследования моделей. Как и в моих домашних проектах и так и в рабочих. Исследованию подверглось мульти-табличное наследование. Среди достаточного количества удобных и полезных свойств наследования преподнесло несколько неожиданностей и даже ошибок, которые были обнаружены в механизме самой джанги.

Начнем с особенностей:

  • Кастомные менеджеры наследуются. Что вполне логично, если учесть, что менеджер это всего-лишь обычное поле класса, которое при наследовании переходит к наследнику. Но, что не очень очевидно на первый взгляд, так то, что он остается “прикрепленным” к модели предка и его использование с моделью наследника будет давать не совсем ожидаемы результат. Вот пример:

      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. То ли ещё будет!:)

comments powered by Disqus