Pull to refresh

Перевод Django Documentation: Models. Part 4 (Last)

Reading time10 min
Views7.2K
image

Доброго времени суток!

Это последняя часть серии моих переводов раздела о моделях из документации Django.

Перевод Django Documentation: Models. Part 1
Перевод Django Documentation: Models. Part 2
Перевод Django Documentation: Models. Part 3

_____Многотабличное наследование
_______Класс Meta и многотабличное наследование
_______Наследование и обратные отношения
_______Создание поля с parent_link
_____Прокси-модели
_______Запросы возвращают объекты модели, к которой адресованы
_______Ограничения для базовых классов
_______Менеджеры прокси-модели
_______Различия между прокси-моделями и unmanaged-моделями
_____Множественное наследование
_____Переопределение имен полей запрещено




Многотабличное наследование


Второй тип наследования моделей, поддерживаемый Django, используется когда каждая модель в иерархии является моделью сама по себе. То есть каждая модель относится к собственной таблице базы данных и может использоваться индивидуально. Отношения при наследовании представляют собой ссылки между производной моделью и каждым из его родителей (с помощью автоматически создаваемого поля OneToOneField). Например:
Copy Source | Copy HTML<br/>class Place(models.Model):<br/>    name = models.CharField(max_length=50)<br/>    address = models.CharField(max_length=80)<br/> <br/>class Restaurant(Place):<br/>    serves_hot_dogs = models.BooleanField()<br/>    serves_pizza = models.BooleanField() <br/>

Все поля класса Place будут так же доступны и в производном классе Restaurant, однако их данные будут содержаться в разных таблицах. Так что возможны два случая:
Copy Source | Copy HTML<br/>>>> Place.objects.filter(name="Bob's Cafe")<br/>>>> Restaurant.objects.filter(name="Bob's Cafe") <br/>

Если у вас имеется объект класса Place, являющийся так же объектом класса Restaurant, вы можете получить доступ к объекту Restaurant из объекта Place, используя название производной модели в нижнем регистре:
Copy Source | Copy HTML<br/>>>> p = Place.objects.filter(name="Bob's Cafe")<br/># If Bob's Cafe is a Restaurant object, this will give the child class:<br/>>>> p.restaurant<br/><Restaurant: ...> <br/>

Если p в данном примере не является объектом класса Restaurant (он был создан непосредственно как объект класса Place или является базовым для каких-то других классов), выражение p.restaurant возбудит исключение Restaurant.DoesNotExist.



Класс Meta и многотабличное наследование

В ситуации с многотабличным наследованием производным классам не имеет смысла наследовать данные класса Meta базового класса. Все Meta параметры уже применены к базовому классу и их повторное применение может привести к противоречивым последствиям (в отличие от ситуации с абстрактным базовым классом, который, фактически, не существует сам по себе).

Итак, производная модель не имеет доступа к родительскому классу Meta. Однако существует несколько случаев, когда производный класс наследует поведение базового: если производный класс не определит атрибуты django.db.models.Options.ordering или django.db.models.Options.get_latest_by, то он унаследует их от родительского класса.

Если базовый класс содержит данные атрибуты, но вы не хотите чтобы производные использовали их, вы можете принудительно отключить их:
Copy Source | Copy HTML<br/>class ChildModel(ParentModel):<br/>    ...<br/>    class Meta:<br/>        # Remove parent's ordering effect<br/>        ordering = [] <br/>




Наследование и обратные отношения

Так как при многотабличном наследовании неявно используется поле OneToOneField для создания ссылок между производным и базовым классом, мы можем получить доступ к производному классу через базовый, как показано в одном из примеров выше. Однако для этого используется имя, являющиеся значением по умолчанию для related_name полей django.db.models.fields.ForeignKey и django.db.models.fields.ManyToManyField. Если вы используете эти типы отношений в другой модели, вы должны определить атрибут related_name для каждого такого поля. В противном случает Django сообщит об ошибке при выполнении validate или syncdb.

Например, используем описанный выше класс Place еще раз для создания другого подкласса с полем ManyToManeField:
Copy Source | Copy HTML<br/>class Supplier(Place):<br/>    # Must specify related_name on all relations.<br/>    customers = models.ManyToManyField(Restaurant, related_name='provider') <br/>




Создание поля с parent_link

Как уже упоминалось, Django автоматически создает поле OneToOneField для связывания производного класса с неабстрактным базовым. Если вы хотите управлять именем атрибута, который ссылается на родительский класс, вы можете создать собственное поле OneToOneField и указать в нем parent_link=True, чтобы показать что ваше поле ссылается на базовый класс.



Прокси-модели

добавлено в версии Django 1.1: пожалуйста, прочтите примечания к релизу.

При использовании многотабличного наследования новые таблицы базы данных создаются для каждого подкласса модели. Это то что нужно, если подклассу требуется место для хранения дополнительных данных, которые представлены в базовом классе. Однако иногда вам нужно лишь изменить поведение модели — например, добавить какой-то метод.

Для этого используется наследование с помощью прокси-модели. Создав прокси вашей модели, вы можете создавать, удалять и изменять экземпляры прокси-модели, и все результаты будут сохраняться, как если бы вы использовали основную (не прокси) модель. Разница между ними в том, что вы можете менять такие вещи как стандартную сортировку или менеджер по умолчанию прокси-модели без изменения оригинала.

Прокси-модели объявляются так же как и обычные модели. Вы сообщаете Django о том, что данный класс является прокси-моделью, присваивая значение True атрибуту proxy класса Meta.

Например, предположим, что вы хотите добавить метод стандартной модели User, которая будет использоваться в ваших шаблонах. Вы можете сделать это следующим образом:
Copy Source | Copy HTML<br/>from django.contrib.auth.models import User<br/> <br/>class MyUser(User):<br/>    class Meta:<br/>        proxy = True<br/> <br/>    def do_something(self):<br/>        ... <br/>

Класс MyUser оперирует той же таблицей базы данных, что и базовый класс User. В частности, любые экземпляры класса User будут доступны через класс MyUser, и наоборот:
Copy Source | Copy HTML<br/>>>> u = User.objects.create(username="foobar")<br/>>>> MyUser.objects.get(username="foobar")<br/><MyUser: foobar> <br/>

Вы также можете использовать прокси-модель для определения в вашей модели различных сортировок по умолчанию. Стандартная модель User не имеет собственной сортировки (это сделано умышленно: сортировка требует затрат памяти, и мы не хотим использовать ее каждый раз, когда выбираем пользователей). Возможно, вы хотите постоянно сортировать пользователей по имени при использовании прокси-модели. Это просто:
Copy Source | Copy HTML<br/>class OrderedUser(User):<br/>    class Meta:<br/>        ordering = ["username"]<br/>        proxy = True <br/>

Теперь запросы к классу User не будут отсортированы, а запросы к классу OrderedUser будут сортироваться по имени.



Запросы возвращают объекты модели, к которой адресованы

Не существует способа заставить Django вернуть, скажем, объект класса MyUser, когда мы посылаем запрос объектам класса User. Запрос для объектов класса User вернет этот же тип объекта (User прим.пер.). Основная идея использования прокси-объектов заключается в том, что код, относящийся к классу User будет использовать этот класс, а ваш собственный код получит возможность использовать расширения, которые вы подключили. Отметим также, что это вовсе не способ повсеместно заменить модель User (или любую другую) вашей собственной.



Ограничения для базовых классов

Прокси-модель должна наследовать только один неабстрактный класс. Вы не можете наследовать несколько неабстрактных моделей, так как прокси-модель не предусматривает какой-либо связи между столбцами в различных таблицах базы данных. Прокси-модель может наследовать сколько угодно абстрактных классов, если они не имеют полей.

Прокси-модели наследуют Meta параметры, которые они не получают от своих неабстрактных базовых классов (моделей, для которых они являются прокси).



Менеджеры прокси-модели

Если вы самостоятельно не создадите менеджеры для прокси-модели, они унаследуются от базовых классов вашей модели. Если же вы объявите менеджер для прокси-модели, он станет менеджером по умолчанию, также будут доступны остальные менеджеры, определенные в базовых классах. Продолжая наш приведенный выше пример, вы можете изменить стандартный менеджер, использующийся при запросе к модели User:
Copy Source | Copy HTML<br/>class NewManager(models.Manager):<br/>    ...<br/> <br/>class MyUser(User):<br/>    objects = NewManager()<br/> <br/>    class Meta:<br/>        proxy = True <br/>

Если вы хотите добавить новый менеджер прокси-модели так, чтобы он не заменил уже существующий менеджер по умолчанию, вы можете использовать прием, описанный в документации по собственным менеджерам: создать базовый класс, содержащий новые менеджеры, и наследовать его после основного базового класса:
Copy Source | Copy HTML<br/># Create an abstract class for the new manager.<br/>class ExtraManagers(models.Model):<br/>    secondary = NewManager()<br/> <br/>    class Meta:<br/>        abstract = True<br/> <br/>class MyUser(User, ExtraManagers):<br/>    class Meta:<br/>        proxy = True <br/>

Скорее всего, вам не придётся пользоваться этим часто, однако знайте, что это возможно.



Различия между прокси-моделями и unmanaged-моделями

Наследование с помощью прокси-модели с виду довольно похоже на создание unmanaged-модели с использованием атрибута managed в ее Meta классе. Эти две альтернативы не одно и тоже, но они обе заслуживают рассмотрения, чтобы вы могли определится, что именно следует использовать.

Первое отличие состоит в том, что вы можете (и, по сути, должны, если вы не хотите создать пустую модель) определить поля в модели с Meta.managed=False. Вы также могли бы, аккуратно настроив Meta.db_table создать unmanaged-модель, в точности повторяющую уже существующую, и добавить в нее методы. Однако это было бы очень скучным и ненадежным, так как вы должны синхронизировать копии при любых изменениях.

Другое отличие, более важное для прокси-моделей, заключается в том как обрабатываются менеджеры моделей. Прокси-модели ведут себя в точности так как и модели, для которых они являются прокси. Они наследуют менеджеры своих базовых классов, включая менеджер по умолчанию. В случае обычного многотабличного наследования производная модель не наследует менеджеры, потому что при использовании дополнительных полей собственные менеджеры (custom managers) не всегда уместны. Документация по менеджерам содержит больше информации по данному вопросу.

Когда были созданы данные принципы, были предприняты попытки объединить их в один вариант. Однако выяснилось, что взаимодействие с наследованием в общем случае, и в частности с использованием менеджеров, делало API очень сложным и потенциально трудным для восприятия и использования. Оказалось, что оба варианта необходимы в любом случае, поэтому возникло подобное разделение.

Таким образом, общие правила таковы:

  1. Если вы хотите создать точную копию существующей модели или таблицы базы данных, но не хотите, чтобы полученная модель содержала все колонки таблици базы данных, используйте Meta.managed=False. Этот метод обычно полезен для моделирования образов базы данных и таблиц вне Django.
  2. Если вы хотите изменить поведение модели на языковом уровне, но сохранить все те же поля, что и у оригинала, используйте Meta.proxy=True. В этом случае ваша модель становится точной копией оригинала, включая структуру хранения данных.




Множественное наследование


Так же как и при наследовании в Python, модель в Django имеет возможность наследовать множество базовых моделей. Имеется в виду, что в Django сохраняется соглашение о именах. Именно базовый класс (например, Meta), находящийся первым в определении, будет использоваться; например, это означает, что, если базовые классы содержат класс Meta, использоваться будет только первый из ни, все остальные будут игнорироваться.

Как правило, вам не нужно будет использовать множественное наследование. Основная область его применения — «mix-in» классы (all-in-one прим.пер.): добавление отдельных дополнительных полей каждому классу, наследующему mix-in. Старайтесь создавать ваше дерево наследования настолько просто и однозначно, насколько это возможно, чтобы вам не пришлось постоянно выяснять откуда появилась та или иная информация.



Переопределение имен полей запрещено

В обычном наследовании языка Python производный класс может переопределять атрибуты любого базового класса. В Django это не разрешено (по крайней мере, на данный момент) в случае, когда атрибуты являются экземплярами полей. Если базовый класс содержит поле author, вы не сможете создать новое поле с таким же именем в любой модели, наследующей данный класс.

Переопределение полей в базовой модели приводит к проблемам при инициализации новых экземпляров (указание какое поле было инициализировано в Model.__init__) и при сериализации. Эти особенности, с которыми обычное наследование в Python не сталкивается, поэтому различие в наследовании моделей Django и наследование классов Python не является причудой.

Это огрананичение распространяется только на атрибуты, являющиеся экземплярами полей. Обычные атрибуты могут быть переопределены по вашему желанию. Это относится только к именам атрибутов, таким, какими их видит Python: в случае многотабличного наследования, если вы вручную определили колонку базы данных, вы можете хранить колонки с одним и тем же именем как в производной, так и в базовой модели (колонки находятся в разных таблицах базы данных).

Django возбудит исключение FieldError, если вы переопределите какое-либо поле базовой модели.



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

Спасибо за внимание ;)

Перевод Django Documentation: Models. Part 1
Перевод Django Documentation: Models. Part 2
Перевод Django Documentation: Models. Part 3
Tags:
Hubs:
Total votes 26: ↑22 and ↓4+18
Comments11

Articles