Модели¶
Модели отображают информацию о данных, с которыми вы работаете. Они содержат поля и поведение ваших данных. Обычно одна модель представляет одну таблицу в базе данных.
Основы:
Каждая модель это класс унаследованный от
django.db.models.Model.Атрибут модели представляет поле в базе данных.
Django предоставляет автоматически созданное API для доступа к данным; смотрите Выполнение запросов.
Примеры¶
Вот пример модели, которая определяет гипотетического человека(Person), с именем(first_name) и фамилией(last_name):
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
first_name и last_name поля модели. Каждое поле определено как атрибут класса, и каждый атрибут соответствует полю таблицы в базе данных.
Модель Person создаст в базе данных таблицу:
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
Технические замечания:
Название таблицы,
myapp_person, автоматически создано с метаданных модели и может быть переопределено. Подробнее Название таблицы.Поле
idдобавлено автоматически, но его также можно переопределить. Подробнее Первичный ключ по умолчанию.CREATE TABLESQL в этом примере соответствует синтаксису PostgreSQL, но стоит учесть что Django использует синтаксис SQL соответственно настройкам базы данных в файле настроек.
Использование моделей¶
После определения моделей необходимо указать Django что необходимо их использовать. Сделайте это отредактировав файл настроек и изменив INSTALLED_APPS, добавив пакет, который содержит ваш models.py.
Например, если модели вашего приложения находятся в myapp.models (структура пакетов создана с помощью команды создания приложения manage.py startapp), INSTALLED_APPS должен содержать:
INSTALLED_APPS = [
#...
'myapp',
#...
]
После добавления приложения в INSTALLED_APPS, не забудьте выполнить manage.py migrate. Возможно, нужно будет создать сначала миграции, выполнив manage.py makemigrations.
Поля¶
Самая важная часть модели – и единственная обязательная – это список полей таблицы базы данных которые она представляет. Поля определены атрибутами класса. Нельзя использовать имена конфликтующие с API моделей, такие как clean, save или delete.
Пример:
from django.db import models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
Типы полей¶
Каждое поле в вашей модели должно быть экземпляром соответствующего Field класса. Django использует классы полей для определения такой информации:
Тип столбца, который сообщает базе данных, какие данные следует хранить (например, INTEGER, VARCHAR, TEXT).
Виджет используемый при создании поля формы (например:
<input type="text">,<select>).Минимальные правила проверки данных, используемые в интерфейсе администратора и для автоматического создания формы.
В Django есть большое количество полей; полный список можно посмотреть на странице списка полей. Вы можете легко добавить собственное поле; смотрите Writing custom model fields.
Настройка полей¶
Для каждого поля есть набор предопределенных аргументов (описание на странице описания полей). Например, CharField (и унаследованные от него) имеют обязательный аргумент max_length, который определяет размер поля VARCHAR для хранения данных этого поля.
Также есть список стандартных аргументов для всех полей. Все они не обязательны. Все они описаны в разделе про аргументы полей модели, вот список самых используемых:
nullЕсли
True, Django сохранит пустое значение какNULLв базе данных. По умолчанию -False.blankЕсли
True, поле не обязательно и может быть пустым. По умолчанию -False.Это не то же что и
null.nullотносится к базе данных,blank- к проверке данных. Если поле содержитblank=True, форма позволит передать пустое значение. Приblank=False- поле обязательно.choicesA sequence of 2-tuples to use as choices for this field. If this is given, the default form widget will be a select box instead of the standard text field and will limit choices to the choices given.
Список значений выглядит:
YEAR_IN_SCHOOL_CHOICES = [ ('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ('GR', 'Graduate'), ]
Примечание
Новая миграция создается каждый раз, когда меняется порядок «выборов».
The first element in each tuple is the value that will be stored in the database. Второй элемент отображается виджетом формы поля.
Учитывая экземпляр модели, доступ к отображаемому значению поля с
выборамиможно получить с помощью методаget_FOO_display(). Например:from django.db import models class Person(models.Model): SHIRT_SIZES = ( ('S', 'Small'), ('M', 'Medium'), ('L', 'Large'), ) name = models.CharField(max_length=60) shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() >>> p.shirt_size 'L' >>> p.get_shirt_size_display() 'Large'
Вы также можете использовать классы перечисления для краткого определения
выборов:from django.db import models class Runner(models.Model): MedalType = models.TextChoices('MedalType', 'GOLD SILVER BRONZE') name = models.CharField(max_length=60) medal = models.CharField(blank=True, choices=MedalType.choices, max_length=10)
Дополнительные примеры доступны в ссылке на поле модели <field-choices>.
defaultЗначение по умолчанию для этого поля. Это может быть значение или функция. Если это функция - она будет вызвана при каждом создании объекта.
help_textПодсказка, отображаемая в поле формы. Это полезно для описания поля, даже если поле не используется в формах.
primary_keyПри
Trueполе будет первичным ключом.Если
primary_key=Trueне указан ни для одного поля, Django самостоятельно добавит поле типаIntegerFieldдля хранения первичного ключа, поэтому вам не обязательно указыватьprimary_key=Trueдля каждой модели. Подробнее Первичный ключ по умолчанию.Поле первичного ключа доступно только для чтения. Если вы поменяете значение первичного ключа для существующего объекта, а зачем сохраните его, будет создан новый объект рядом с существующим. Например:
from django.db import models class Fruit(models.Model): name = models.CharField(max_length=100, primary_key=True)
>>> fruit = Fruit.objects.create(name='Apple') >>> fruit.name = 'Pear' >>> fruit.save() >>> Fruit.objects.values_list('name', flat=True) <QuerySet ['Apple', 'Pear']>
uniqueПри
Trueполе будет уникальным.
Это краткое описание самых используемых аргументов. Полный список можно найти на странице описания аргументов поля модели.
Первичный ключ по умолчанию¶
По умолчанию Django предоставляет каждой модели автоматически увеличивающийся первичный ключ с типом, указанным для каждого приложения в AppConfig.default_auto_field или глобально в настройке DEFAULT_AUTO_FIELD. Например:
id = models.BigAutoField(primary_key=True)
Для его переопределения просто укажите primary_key=True для одного из полей. При этом Django не добавит поле id.
Каждая модель должна иметь хотя бы одно поле с primary_key=True (явно указанное или созданное автоматически).
In older versions, auto-created primary key fields were always
AutoFields.
Читабельное имя поля¶
Каждое поле, кроме ForeignKey, ManyToManyField и OneToOneField, первым аргументом принимает необязательное читабельное название. Если оно не указано, Django самостоятельно создаст его, используя название поля, заменяя подчеркивание на пробел.
В этом примере читабельное название - "person's first name":
first_name = models.CharField("person's first name", max_length=30)
Здесь - "first name":
first_name = models.CharField(max_length=30)
ForeignKey, ManyToManyField и OneToOneField первым аргументом принимает класс модели, поэтому используется keyword аргумент verbose_name:
poll = models.ForeignKey(
Poll,
on_delete=models.CASCADE,
verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
verbose_name="related place",
)
Django не делает первую букву прописной для verbose_name - только там, где это необходимо.
Связи¶
Основное преимущество реляционных баз данных - возможность добавлять связи для таблиц. Django предоставляет возможность использовать три самых используемых типа связей: многое-к-одному, многие-ко-многим и один-к-одному.
Cвязи один-ко-многим¶
Для определения связи многое-к-одному используется django.db.models.ForeignKey. Вы используете его так же, как и другие типы Field: добавляя как атрибут в модель.
Для ForeignKey необходимо указать обязательный позиционный аргумент: класс связанной модели.
Например, если модель Car содержит информацию о Manufacturer – это отношение многое-к-одному. Manufacturer производит много Car, которая связана только с одной Manufacturer – используйте следующее определение:
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...
Вы можете также создать рекурсивную связь (объект со связью многое-к-одному на себя) и связь с моделью, которая еще не определена; смотрите справку по полям модели для подробностей.
Предлагается, но не обязательно, чтобы имя поля ForeignKey (manufacturer в приведенном выше примере) было именем модели в нижнем регистре. Вы можете назвать поле как хотите. Например:
class Car(models.Model):
company_that_makes_it = models.ForeignKey(
Manufacturer,
on_delete=models.CASCADE,
)
# ...
См.также
ForeignKey принимает дополнительные аргументы, смотрите справку по полям модели. Эти аргументы определяют, как работает связь, все они не обязательны.
Пример работы с обратно-связанными объектами смотрите в в соответствующем разделе.
Примеры кода можно найти в соответствующем разделе.
Связи многие-ко-многим¶
Для определения связи многие-ко-многим, используйте ManyToManyField. Используется так же, как и остальные типы Field: добавлением как атрибут класса.
Для ManyToManyField необходимо указать обязательный позиционный аргумент: класс связанной модели.
Например, если пицца(Pizza) содержит много добавок(Topping) – то есть добавки(Topping) могут быть в различных сортах пиццы(Pizza) – вот как вы можете представить это:
from django.db import models
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
Так же, как и с ForeignKey, вы можете создать рекурсивную связь (объект со связью многие-ко-многим на себя) и связь с моделью, которая еще неопределенна; смотрите справку по полям модели.
Желательно, но не обязательно, чтобы название поля ManyToManyField (toppings в нашем примере) было множественным называнием связанных объектов.
Не имеет значения какая модель содержит поле ManyToManyField, но вы должны добавить его только для одной модели.
Обычно, ManyToManyField необходимо добавить в модель, которая будет редактироваться в форме. В примере выше, toppings добавлено в Pizza (вместо того, чтобы добавить поле pizzas типа ManyToManyField в модель Topping), потому что обычно думают о пицце с ингредиентами, а не об ингредиентах в различных пиццах. В примере выше, форма для Pizza позволит пользователям редактировать ингредиенты для пиццы.
См.также
Примеры использования связи многие-ко-многим можно найти в соответствующем разделе
Поле ManyToManyField принимает список дополнительных аргументов, подробнее в разделе справки о полях модели. Эти настройки помогают определить как работает связь, все они не обязательны.
Дополнительные поля для связи многие-ко-многим¶
В примере про пиццу нам нужно было всего лишь указать ингредиенты для пиццы, и поля ManyToManyField было достаточно для этого. Но иногда необходимо хранить дополнительную информацию о связи.
Например, разберем приложение о музыкальных группах и музыкантах. Для хранения связи между музыкантами и группами мы можем использовать поле ManyToManyField. Но нам также необходимо хранить дополнительную информацию, например, когда музыкант вступил в группу.
Для таких случаев Django позволяет определить модель для хранения связи многие-ко-многим и дополнительной информации. Теперь вы можете добавить дополнительные поля в эту модель. Эту промежуточную модель можно указать в поле ManyToManyField используя аргумент through, который указывает на промежуточную модель. Для нашего примера код будет приблизительно таким:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
В промежуточной модели необходимо добавить внешние ключи на модели, связанные отношением многие-ко-многим. Эти ключи указывают как связаны модели.
Есть несколько ограничений для промежуточной модели:
Промежуточная модель должна содержать только одну связь с исходной моделью (в нашем примере – это
Group), или вы должны явно указать Django какую связь использовать через параметрManyToManyField.through_fields. Если промежуточная модель содержит несколько связей с исходной модель иthrough_fieldsне указан, будет вызвана ошибка валидации. Аналогичные правила выполняются и для связанной модели (в нашем примере –Person).Исключение - промежуточная модель для рекурсивной связи. В таком случае у промежуточной модели будет два внешних ключа на одну модель, но в таком случае они будут восприниматься как две различных стороны связи многие-ко-многим. Если промежуточная модель содержит больше двух связей, необходимо указать
through_fields, иначе получите ошибку валидации.
Now that you have set up your ManyToManyField to use
your intermediary model (Membership, in this case), you’re ready to start
creating some many-to-many relationships. You do this by creating instances of
the intermediate model:
>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
... date_joined=date(1962, 8, 16),
... invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>
You can also use add(),
create(), or
set() to create
relationships, as long as you specify through_defaults for any required
fields:
>>> beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.create(name="George Harrison", through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})
Есть несколько ограничений для промежуточной модели:
If the custom through table defined by the intermediate model does not enforce
uniqueness on the (model1, model2) pair, allowing multiple values, the
remove() call will
remove all intermediate model instances:
>>> Membership.objects.create(person=ringo, group=beatles,
... date_joined=date(1968, 9, 4),
... invite_reason="You've been gone for a month and we miss you.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This deletes both of the intermediate model instances for Ringo Starr
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>
The clear()
method can be used to remove all many-to-many relationships for an instance:
>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
<QuerySet []>
Once you have established the many-to-many relationships, you can issue queries. Just as with normal many-to-many relationships, you can query using the attributes of the many-to-many-related model:
# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith='Paul')
<QuerySet [<Group: The Beatles>]>
As you are using an intermediate model, you can also query on its attributes:
# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
... group__name='The Beatles',
... membership__date_joined__gt=date(1961,1,1))
<QuerySet [<Person: Ringo Starr]>
If you need to access a membership’s information you may do so by directly
querying the Membership model:
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
Another way to access the same information is by querying the
many-to-many reverse relationship from a
Person object:
>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
Связи один-к-одному¶
Для определения связи один-к-одному используется OneToOneField. Вы используете его так же, как и другие типы Field: добавляя как атрибут в модель.
Чаще всего связь одни-к-одному используется для первичного ключа для модели, которая «расширяет» другую модель.
Для OneToOneField необходимо указать обязательный позиционный аргумент: класс связанной модели.
Например, вам необходима база данных «строений», обычным дело будет добавить адрес, номер телефона и др. в базу данных. После, если вы захотите дополнить базу данных строений ресторанами, вместо того, чтобы повторять поля в модели Restaurant, вы можете добавить в модель Restaurant поле OneToOneField связанное с Place (т.к. ресторан «это» строение; вы можете использовать наследование моделей, которое на самом деле работает через связь один-к-одному).
Так же как и для ForeignKey, вы можете использовать рекурсивную связь и связь на себя; смотрите раздел о полях модели.
См.также
Примеры можно найти в этом разделе.
OneToOneField также принимает один не обязательный аргумент parent_link описанный в спецификации поля.
Раньше OneToOneField автоматически использовались как первичный ключ. Теперь это не так (но вы можете сами указать это используя аргумент primary_key). Таким образом вы можете иметь несколько связей OneToOneField к одной модели.
Связь моделей из разных модулей¶
Нормальная практика использовать связи для моделей из разных приложений. Для этого, импортируйте связанную модель перед определением главной модели и используйте как аргумент для поля. Например:
from django.db import models
from geography.models import ZipCode
class Restaurant(models.Model):
# ...
zip_code = models.ForeignKey(
ZipCode,
on_delete=models.SET_NULL,
blank=True,
null=True,
)
Ограничения при выборе названия поля¶
В Django существует только два ограничения:
A field name cannot be a Python reserved word, because that would result in a Python syntax error. For example:
class Example(models.Model): pass = models.IntegerField() # 'pass' is a reserved word!
Название поля не может содержать несколько нижних подчеркиваний(_) подряд, т.к. такой подход Django использует для формирования запросов. Например:
class Example(models.Model): foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
По тем же причинам имя поля не может заканчиваться подчеркиванием.
Эти ограничения не сложно соблюдать т.к. название поля не обязано быть таким же, как и название колонки в базе данных. Смотрите аргумент db_column.
Зарезервированные SQL слова, такие как join, where или select, можно использовать как название поля, потому что Django экранирует название таблиц и полей для каждого SQL запроса. Используются «кавычки»(quoting syntax) базы данных, которую вы используете.
Собственные типы полей¶
Если ни одно существующее поле не удовлетворяет вашим потребностям, или вам необходимо использовать какие-либо особенности поля, присущие определенной базе данных - вы можете создать собственный тип поля. Подробнее вы можете прочитать в Writing custom model fields.
Мета-настройки¶
Дополнительные настройки для модели можно определить через class Meta, например:
from django.db import models
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
Сюда включено «все что не является полем», например, настройка сортировки по-умолчанию (ordering), название таблицы базы данных (db_table), или human-readable название в единственной и множественной форме(verbose_name и verbose_name_plural). Все они не обязательны и добавлять class Meta тоже не обязательно.
Полный список опций для Meta можно найти в разделе о настройках модели.
Атрибуты модели¶
objectsСамый важный атрибут модели –
Manager. Это интерфейс, через который Django выполняет запросы к базе данных и получает объекты. Если собственныйManagerне указан, название по умолчанию будетobjects. Менеджеры доступны только через класс модели, они не доступны в экземплярах модели.
Методы модели¶
Для добавления функционала работы с экземпляром модели(«row-level» functionality), необходимо просто добавить метод в модель. В то время, как методы Manager работают с таблицей, методы модели работают с конкретной записью в таблице.
Это хороший подход для хранения бизнес логики работы с данными в одном месте – модели.
Например, эта модель содержит два дополнительных метода:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
@property
def full_name(self):
"Returns the person's full name."
return '%s %s' % (self.first_name, self.last_name)
Последний метод в примере - свойство(property).
Раздел о моделях содержит полный список методов, автоматически добавляемых в модель. Вы можете переопределить большинство из них – смотрите Переопределение методов модели, – но есть методы, которые вы чаще всего определите для каждой модели:
__str__()«Волшебный метод» Python, который возвращает unicode «представление» объекта. Это то, что Python и Django используют для отображения объекта как строки, обычно в консоли, интерфейсе администратора или шаблоне.
Желательно определить этот метод, т.к. значение по умолчанию не слишком привлекательно.
get_absolute_url()Этот метод указывает Django, какой URL использовать для объекта. Django использует его в интерфейсе администратора и каждый раз, когда необходимо получить URL для объекта.
Каждый объект, который имеет уникальный URL должен иметь этот метод.
Переопределение методов модели¶
Существуют другие методы модели, которые инкапсулируют работу с базой данных. Чаще всего вы захотите переопределить метод save() и delete().
Вы можете переопределить эти методы и любые другие методы модели.
Распространенная задача, это выполнить какое-либо действие после сохранения объекта. Например (принимаемые параметры смотрите save()):
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs):
do_something()
super().save(*args, **kwargs) # Call the "real" save() method.
do_something_else()
Вы также можете отменить сохранение:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs):
if self.name == "Yoko Ono's blog":
return # Yoko shall never have her own blog!
else:
super().save(*args, **kwargs) # Call the "real" save() method.
It’s important to remember to call the superclass method – that’s
that super().save(*args, **kwargs) business – to ensure
that the object still gets saved into the database. If you forget to
call the superclass method, the default behavior won’t happen and the
database won’t get touched.
It’s also important that you pass through the arguments that can be
passed to the model method – that’s what the *args, **kwargs bit
does. Django will, from time to time, extend the capabilities of
built-in model methods, adding new arguments. If you use *args,
**kwargs in your method definitions, you are guaranteed that your
code will automatically support those arguments when they are added.
Переопределенные методы модели не вызываются при множественных операциях(bulk)
Учтите, метод delete() не обязательно вызывается при массовом удалении объектов через QuerySet, или как результат каскадного удаления. Для гарантированного выполнения действий после удаления объекта используйте сигналы pre_delete и/или post_delete.
К сожалению, вы не сможете изменить логику сохранения объектов при использовании creating или updating, так как save(), pre_save и post_save не будут выполнены.
Выполнение SQL запросов¶
Запросы на чистом SQL лучше выполнять в методе модели. Подробнее о SQL-запросах можно прочитать в разделе о запросах на чистом SQL.
Наследование моделей¶
Наследование моделей в Django работает почти так же, как и наследование классов в Python, но следует соблюдать правила, описанные выше. Это означает, что базовый класс должен наследоваться от django.db.models.Model.
Единственное, что вам нужно определить, это должна ли родительская модель быть независимой моделью (с собственной таблицей в базе данных), или же родительская модель просто контейнер для хранения информации, доступной только через дочерние модели.
Существует три вида наследования моделей в Django.
Чаще всего вы будете использовать родительскую модель для хранения общих полей, чтобы не добавлять их в каждую дочернюю модель. Если вы не собираетесь использовать его как независимую модель – Абстрактные модели то, что вам нужно.
Если родительская модель независимая(возможно, из другого приложения) и должна храниться в отдельной таблице, Multi-table наследование то, что вам нужно.
Если же вы хотите переопределить поведение модели на уровне Python, не меняя структуры базы данных, вы можете использовать Proxy-модели.
Абстрактные модели¶
Абстрактные модели удобны при определении общих, для нескольких моделей, полей. Вы создаете базовую модель и добавляете abstract=True в класс Meta. Для этой модели не будет создана таблица в базе данных. Используя эту модель как родительскую для другой модели - все ее поля будут добавлены в таблицу в базе данных для этой модели. Нельзя использовать поля с одинаковыми названиями в дочерней и родительской моделях (Django вызовет исключение).
Например:
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
class Student(CommonInfo):
home_group = models.CharField(max_length=5)
Модель Student содержит три поля: name, age и home_group. Модель CommonInfo не может использоваться как обычная модель Django, т.к. это абстрактный класс. Она не имеет собственной таблицы в базе данных и не имеет менеджера, нельзя создать экземпляр модели и сохранить его в базе данных.
Поля, унаследованные от абстрактных базовых классов, можно переопределить другим полем или значением или удалить с помощью параметра None.
В большинстве случаев вы будете использовать этот тип наследования моделей. Он позволяет на уровне Python разделить общие данные, используя в то же время одну таблицу в базе данных.
Наследование класса Meta¶
После создания абстрактной модели, Django добавляет класс Meta как атрибут класса. Если дочерний класс не определяет собственный класс Meta, он унаследует родительский класс Meta. Если дочерняя модель хочет расширить родительский Meta класс, она может унаследовать его. Например:
from django.db import models
class CommonInfo(models.Model):
# ...
class Meta:
abstract = True
ordering = ['name']
class Student(CommonInfo):
# ...
class Meta(CommonInfo.Meta):
db_table = 'student_info'
Django вносит одно изменение в класс Meta абстрактного базового класса: перед установкой атрибута Meta он устанавливает abstract=False. Это означает, что дочерние элементы абстрактных базовых классов сами по себе не становятся абстрактными классами автоматически. Чтобы создать абстрактный базовый класс, который наследуется от другого абстрактного базового класса, вам необходимо явно установить для дочернего элемента abstract=True.
Некоторые атрибуты не имеет смысла добавлять в класс Meta абстрактной модели. Например, добавление db_table означает, что каждая дочерняя модель(каждая, которая не определяет свой собственный класс Meta) будет использовать туже таблицу в базе данных, что точно не то, что вам нужно.
Из-за особенностей наследования Python, если дочерний класс наследуется от нескольких абстрактных базовых классов, по умолчанию будут унаследованы только параметры Meta из первого перечисленного класса. Чтобы наследовать параметры Meta от нескольких абстрактных базовых классов, вы должны явно объявить наследование Meta. Например:
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
ordering = ['name']
class Unmanaged(models.Model):
class Meta:
abstract = True
managed = False
class Student(CommonInfo, Unmanaged):
home_group = models.CharField(max_length=5)
class Meta(CommonInfo.Meta, Unmanaged.Meta):
pass
Multi-table наследование¶
Это второй тип наследования в Django - когда каждая модель в иерархии будет независимой. Каждая модель имеет собственную таблицу в базе данных и может быть использована независимо. Наследование использует связь между родительской и дочерней моделью (через автоматически созданное поле OneToOneField). Например:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
All of the fields of Place will also be available in Restaurant,
although the data will reside in a different database table. So these are both
possible:
>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")
If you have a Place that is also a Restaurant, you can get from the
Place object to the Restaurant object by using the lowercase version of
the model name:
>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>
Но, если p в примере выше не Restaurant (был создан непосредственно как объект Place или был родителем другой модели), использование p.restaurant вызовет ошибку Restaurant.DoesNotExist.
Автоматически созданный OneToOneField в Restaurant, который связывает его с Place, выглядит следующим образом:
place_ptr = models.OneToOneField(
Place, on_delete=models.CASCADE,
parent_link=True,
primary_key=True,
)
OneToOneField также принимает один не обязательный аргумент parent_link описанный в спецификации поля.
Meta и multi-table наследование¶
При multi-table наследовании, не имеет смысла дочерней модели наследовать Meta от родительской. Все атрибуты класса Meta уже используются в родительской модели и использование их снова может привести к противоречивому поведению (это отличие от абстрактной модели, которая сама по себе не существует как независимая модель).
Поэтому дочерняя модель не имеет доступа к родительскому классу Meta. Но есть исключения, когда дочерняя модель наследует поведение родительской: если дочерняя модель не определяет атрибут ordering или get_latest_by, они будут унаследованы.
Если родительская модель определяет сортировку, но вы не хотите ее наследовать в дочерней модели, вы можете указать это таким способом:
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
Наследование и обратные связи¶
Т.к. multi-table наследование использует OneToOneField для связи родительской и дочерней модели, возможно из родительской модели получить дочернюю, как это показано в примере выше. Используется название по умолчанию для атрибута related_name в ForeignKey и ManyToManyField. Если вы используете такие связи на дочернюю модель с аналогичным предком, вы должны определить related_name для каждого такого поля. Иначе Django вызовет исключение.
Например, используя Place определенную выше, создадим еще одну дочернюю модель с ManyToManyField:
class Supplier(Place):
customers = models.ManyToManyField(Place)
This results in the error:
Reverse query name for 'Supplier.customers' clashes with reverse query
name for 'Supplier.place_ptr'.
HINT: Add or change a related_name argument to the definition for
'Supplier.customers' or 'Supplier.place_ptr'.
Добавление related_name для поля customers – models.ManyToManyField(Place, related_name='provider') – решит эту проблему.
Определение связи к родительской модели¶
Как уже упоминалось, Django самостоятельно создает OneToOneField для связи дочерней модели с каждой родительской не абстрактной моделью. Если вы хотите определять имя обратной связи для родительской модели, вы можете создать собственный OneToOneField с parent_link=True чтобы указать, что это поле является связью с родительской моделью.
Proxy-модели¶
При использовании multi-table наследования, будет создана новая таблица в базе данных. Это обязательное требование, т.к. дочерней модели необходимо хранить дополнительные поля. Иногда вам необходимо изменить поведение модели на уровне Python – переопределить менеджер по умолчанию или добавить новые методы.
Вот для чего используют proxy-модель: создать proxy для оригинальной модели. Вы можете создать, изменить или обновить объект proxy модели и все изменения будут сохранены так же, как и при изменении оригинальной(non-proxied) модели. Разница в том, что вы можете изменить сортировку по-умолчанию или менеджер по умолчанию в proxy-модели, без изменения оригинальной модели.
Proxy-модели создаются так же, как и обычная модель. Указать что это proxy-модель можно установив атрибут proxy в классе Meta в True.
Например, вам нужно добавить метод в модель Person`. Вы можете сделать это так:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class MyPerson(Person):
class Meta:
proxy = True
def do_something(self):
# ...
pass
The MyPerson class operates on the same database table as its parent
Person class. In particular, any new instances of Person will also be
accessible through MyPerson, and vice-versa:
>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>
Вы также можете использовать proxy модель для определения различной сортировки по умолчанию. Модель Person не имеет сортировки по умолчанию (намеренно; сортировка трудоемкая операция, и мы не хотим ее использовать при каждом доступе к данным о пользователях). Возможно вы хотите сортировать по полю last_name при использовании proxy-модели. Это просто:
class OrderedPerson(Person):
class Meta:
ordering = ["last_name"]
proxy = True
Теперь запросы через Person будут не отсортированы а для модели OrderedPerson будут отсортированы по полю last_name.
Прокси-модели наследуют мета-атрибуты так же, как обычные модели.
QuerySets возвращает объекты запрашиваемого типа¶
Нет способа указать Django возвращать объекты модели MyPerson при запросе через модель Person. QuerySet для модели Person вернет объекты этого типа. Стоит помнить, что код, который использует модель Person, будет использовать ее независимо от добавления proxy-модели. Вы не можете полностью заменить модель Person (или любую другую) всюду, где она используется.
Ограничения базового класса¶
Proxy-модель должна наследоваться от одной не абстрактной модели. Вы не можете унаследоваться от нескольких не абстрактных моделей т.к. proxy-модель не может хранить информации о полях в нескольких таблицах базы данных. Proxy-модель может наследоваться от нескольких абстрактных моделей при условии, что они не определяют поля модели.
Менеджер proxy-модели¶
Если вы не определите ни один менеджер для proxy-модели, он будет унаследован от родительской модели. Если вы определите менеджер, он будет использован как менеджер по умолчанию, в то же время доступны менеджеры, определенные в родительской модели.
Используя пример выше, вы можете переопределить менеджер по умолчанию, используемый для получения данных модели Person, таким способом:
from django.db import models
class NewManager(models.Manager):
# ...
pass
class MyPerson(Person):
objects = NewManager()
class Meta:
proxy = True
Если вы хотите добавить новый менеджер, без замены существующего менеджера, вы можете использовать методы, описанные в разделе о собственных менеджерах: создайте базовый класс с новым менеджером и добавьте его в наследование после базового класса:
# Create an abstract class for the new manager.
class ExtraManagers(models.Model):
secondary = NewManager()
class Meta:
abstract = True
class MyPerson(Person, ExtraManagers):
class Meta:
proxy = True
Скорее всего вам это не понадобится, но если все таки понадобится, знайте, что это возможно.
Разница между proxy наследованием и неуправляемой(unmanaged) моделью¶
Proxy может выглядеть так же как и неуправляемая модель, используя атрибут managed класса Meta модели. Оба варианта не совсем то же самое, и это стоит учесть, при выборе что вы должны использовать.
Первое отличие в том, что вы можете (и должны, если вам не нужна пустая модель) добавить поля для модели с Meta.managed=False. Вы можете, указав необходимый атрибут Meta.db_table, создать неуправляемую модель, которая отображает существующую модель, и добавить ей новые методы. Тем не менее, это было не совсем удобно, т.к. нужно соблюдать синхронность моделей, делая какие-либо изменения в таблице.
С другой стороны, прокси-модели должны вести себя точно так же, как модель, которую они проксируют. Они всегда синхронизированы с родительской моделью, поскольку напрямую наследуют ее поля и менеджеры.
Основные правила:
Если вы хотите отобразить существующую модель или таблицу в базе данных без использования всех колонок таблицы, используйте
Meta.managed=False. Также это очень полезно для использования таблиц базы данных, которые управляются не Django.Если вы хотите изменить поведение модели на уровне Python, но использовать все существующие поля таблицы, используйте
Meta.proxy=True. Proxy-модель воспринимается как точная копия оригинальной при сохранении данных.
Множественное наследование¶
Так же, как и наследование в Python, можно использовать множественное наследование моделей Django. Имейте в виду, что используются правила именования Python. Например, есть несколько родительских объектов с классом Meta, в таком случае будет использован атрибут первой родительской модели, остальные будут проигнорированы.
В большинстве случаев вам не нужно будет использовать множественное наследование. В основном множественное наследование используют для «mix-in» классов: добавление дополнительных полей и методов для каждой модели унаследованной от mix-in класса. Старайтесь содержать иерархию наследования настолько простой и понятной, насколько это возможно, чтобы не возникало проблем с определением, откуда взялась та или другая информация.
Обратите внимание, наследование от нескольких моделей, которые содержат первичное поле id, вызовет ошибку. Чтобы избежать этой проблемы, можно явно указать AutoField поля в базовых моделях:
class Article(models.Model):
article_id = models.AutoField(primary_key=True)
...
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
...
class BookReview(Book, Article):
pass
Или используйте общего предка для хранения AutoField. Для этого необходимо использовать явный OneToOneField от каждой родительской модели к общему предку, чтобы избежать конфликта между полями, которые автоматически генерируются и наследуются дочерним элементом:
class Piece(models.Model):
pass
class Article(Piece):
article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
...
class Book(Piece):
book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
...
class BookReview(Book, Article):
pass
Переопределение полей запрещено¶
В Python можно переопределять атрибуты класса-родителя в дочернем классе. В Django это запрещено для атрибутов, которые являются экземплярами Field (по крайней мере, на данный момент). Если родительская модель имеет поле author, вы не можете создать поле с именем author в дочерних моделях.
Это ограничение не распространяется на поля модели, унаследованные от абстрактной модели. Такие поля можно переопределить другим полем или значением или удалить, установив field_name = None.
Предупреждение
Менеджеры моделей наследуются от абстрактных базовых классов. Переопределение унаследованного поля, на которое ссылается унаследованный Manager, может вызвать незначительные ошибки. См. настраиваемые менеджеры и наследование моделей.
Примечание
Некоторые поля определяют дополнительные атрибуты модели, например. a ForeignKey определяет дополнительный атрибут с _id, добавленным к имени поля, а также `` linked_name`` и `` linked_query_name`` во внешней модели.
Эти дополнительные атрибуты нельзя переопределить, если только определяющее их поле не будет изменено или удалено так, что оно больше не будет определять дополнительный атрибут.
Переопределение полей родительской модели приводит к проблемам при создании модели (нельзя точно узнать, какое поле таблицы указывается в Model.__init__) и при сериализации. При наследовании классов в Python работает все немного по другому.
Эти ограничения относятся только для атрибутов, которые являются экземплярами Field. Остальные атрибуты могут быть переопределены. Это также относится к атрибутам классов. Вы можете указать одинаковое название поля в таблице БД для родительской и дочерней моделей т.к. они находятся в разных таблицах.
Django вызовет исключение FieldError, если вы переопределите поле родительской модели.
Организация моделей в пакете¶
Команда manage.py startapp создает структуру приложения, включающую файл models.py. Если у вас много моделей, может оказаться полезным организовать их в отдельные файлы.
Для этого создайте пакет «models». Удалите models.py и создайте каталог myapp/models/ с файлом __init__.py и файлами для хранения ваших моделей. Вы должны импортировать модели в файл __init__.py.
Например, если у вас есть «organic.py» и «synthetic.py» в каталоге «models»:
myapp/models/__init__.py¶from .organic import Person
from .synthetic import Robot
Явный импорт каждой модели вместо использования from .models import * имеет преимущества: не загромождает пространство имен, делает код более читабельным и сохраняет полезные инструменты анализа кода.
См.также
- Справочник по моделям
Описывает API всего, что связано с моделями.