Модели
Модель — это единственный и определяющий источник информации о ваших данных. Она содержит основные поля и поведение хранимых данных. Как правило, каждая модель сопоставляется с одной таблицей базы данных.
Основы:
- Каждая модель — это класс Python, который наследуется от
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добавляется автоматически, но это поведение может быть переопределено. Подробности см. в Автоматические поля первичного ключа. - SQL-код
CREATE TABLEв этом примере отформатирован с использованием синтаксиса 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). - Стандартный HTML виджет для отображения поля формы (например,
<input type="text">,<select>). - Минимальные требования к валидации, используемые в админке Django и в автоматически сгенерированных формах.
Django поставляется с десятками встроенных типов полей; полный список можно найти в справочнике по полям моделей. Вы можете легко написать свои собственные поля, если встроенные поля Django не подходят; см. Написание пользовательских полей моделей.
Параметры полей
Каждое поле принимает определённый набор аргументов, специфичных для поля (документированных в справочнике по полям моделей). Например, CharField (и его подклассы) требуют аргумент max_length, который определяет размер поля базы данных VARCHAR, используемого для хранения данных.
Также существует набор общих аргументов, доступных для всех типов полей. Все они необязательны. Полное описание см. в справочнике, но здесь кратко рассмотрены наиболее часто используемые:
-
null - Если
True, Django будет хранить пустые значения какNULLв базе данных. По умолчаниюFalse. -
blank -
Если
True, поле может быть пустым. По умолчаниюFalse.Обратите внимание, что это отличается от
null.null— чисто баз данных, в то время какblank— валидация. Если поле имеетblank=True, валидация формы позволит вводить пустое значение. Если поле имеетblank=False, поле будет обязательным. -
choices -
Последовательность пар кортежей для использования в качестве выбора для данного поля. Если это задано, виджет формы по умолчанию будет выпадающим списком вместо стандартного текстового поля и будет ограничивать выбор предоставленными вариантами.
Список вариантов выглядит так:
YEAR_IN_SCHOOL_CHOICES = [ ('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ('GR', 'Graduate'), ]Примечание
Каждое изменение порядка
choicesсоздаёт новую миграцию.Первый элемент каждой пары кортежей — это значение, которое будет храниться в базе данных. Второй элемент отображается виджетом формы поля.
Для экземпляра модели значение отображения для поля с
choicesможно получить с помощью метода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'
Также вы можете использовать классы перечислений для определения
choicesлаконичным способом: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)Дополнительные примеры см. в справочнике по полям моделей.
-
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 добавляет каждое поле модели следующим образом:
id = models.AutoField(primary_key=True)
Это первичный ключ с автоматическим инкрементом.
Если вы хотите указать пользовательский первичный ключ, укажите primary_key=True для одного из ваших полей. Если Django увидит, что вы явно задали Field.primary_key, он не добавит автоматическое id поле.
Каждая модель требует ровно одного поля с primary_key=True (либо явно объявленного, либо автоматически добавленного).
Поля с описательными именами
Каждый тип поля, за исключением 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 требуют, чтобы первым аргументом была модель класса, поэтому используйте ключевой аргумент 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",
)
Принято не писать заглавную букву в начале описательного имени verbose_name. Django автоматически сделает это там, где нужно.
Связи
Очевидно, мощь реляционных баз данных заключается в связывании таблиц друг с другом. Django предоставляет способы определения трёх наиболее распространённых типов баз данных: многие-ко-многим, многие-ко-одному и один-ко-одному.
Связи многие-ко-одному
Для определения связи многие-ко-одному используйте 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 (а не Topping имеет pizzas ManyToManyField ), потому что естественнее думать о пицце с начинками, чем о начинке на нескольких пиццах. При таком создании форма 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так же, как и выше, иначе будет выведена ошибка валидации.
Теперь, когда вы настроили ManyToManyField для использования вашей промежуточной модели (Membership, в данном случае), вы готовы начать создание отношений «многие ко многим». Вы делаете это, создавая экземпляры промежуточной модели:
>>> 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>]>
Вы также можете использовать add(), create() или set() для создания отношений, если вы укажете through_defaults для необходимых полей:
>>> 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)})
Возможно, вам будет удобнее создавать экземпляры промежуточной модели напрямую.
Если настраиваемая таблица через промежуточную модель не обеспечивает уникальность пары (model1, model2), позволяя несколько значений, вызов remove() удалит все экземпляры промежуточной модели:
>>> 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>]>
Метод clear() можно использовать для удаления всех отношений «многие ко многим» для экземпляра:
>>> # Beatles have broken up >>> beatles.members.clear() >>> # Note that this deletes the intermediate model instances >>> Membership.objects.all() <QuerySet []>
После установления отношений «многие ко многим» вы можете выполнить запросы. Так же, как и при обычных отношениях «многие ко многим», вы можете выполнять запросы, используя атрибуты модели, связанной с отношением «многие ко многим»:
# Find all the groups with a member whose name starts with 'Paul' >>> Group.objects.filter(members__name__startswith='Paul') <QuerySet [<Group: The Beatles>]>
Так как вы используете промежуточную модель, вы также можете выполнять запросы по её атрибутам:
# 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]>
Если вам нужно получить информацию о членстве, вы можете сделать это, напрямую запросив модель Membership:
>>> 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.'
Другой способ получить ту же информацию — запросить обратное отношение «многие ко многим» из объекта Person:
>>> 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 накладывает некоторые ограничения на имена полей моделей:
-
Имя поля не может быть зарезервированным словом Python, так как это приведет к синтаксической ошибке Python. Например:
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. Он использует синтаксис цитирования вашей конкретной базы данных.
Настраиваемые типы полей
Если одно из существующих полей модели не подходит для ваших целей, или если вы хотите использовать некоторые менее распространенные типы столбцов базы данных, вы можете создать свой собственный класс поля. Полное описание создания собственных полей приведено в Написание пользовательских полей модели.
Meta опции
Укажите метаданные модели, используя внутреннюю 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), или удобочитаемые имена единственного и множественного числа (verbose_name и verbose_name_plural). Никакие не обязательны, и добавление class
Meta к модели является полностью необязательным.
Полный список всех возможных Meta опций можно найти в справочнике по опциям модели.
Атрибуты модели
-
objects - Самый важный атрибут модели —
Manager. Это интерфейс, через который операции запросов к базе данных предоставляются моделям Django и используется для получения экземпляров из базы данных. Если не определен пользовательскийManager, по умолчанию используетсяobjects. К менеджерам можно получить доступ только через классы моделей, а не через экземпляры моделей.
Методы моделей
Определяйте пользовательские методы в модели, чтобы добавить пользовательскую функциональность «на уровне строк» к вашим объектам. В то время как методы 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)
Последний метод в этом примере — свойство.
В ссылке на экземпляр модели представлен полный список методов, автоматически предоставляемых каждой модели. Вы можете переопределить большинство из них — см. переопределение предопределенных методов модели ниже — но есть несколько, которые вы практически всегда захотите определить:
-
__str__() -
Python-«волшебный» метод, возвращающий строковое представление любого объекта. Именно 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.
Важно помнить о вызове метода суперкласса — это та часть super().save(*args, **kwargs) — для обеспечения сохранения объекта в базе данных. Если вы забудете вызвать метод суперкласса, поведение по умолчанию не произойдёт, и база данных не будет затронута.
Также важно передавать аргументы, которые могут быть переданы в метод модели — это то, что делает часть *args, **kwargs. Время от времени Django расширяет возможности встроенных методов моделей, добавляя новые аргументы. Если вы используете *args,
**kwargs в определениях методов, вы гарантируете, что ваш код будет автоматически поддерживать эти аргументы при их добавлении.
Переопределённые методы модели не вызываются при массовых операциях
Обратите внимание, что метод delete() для объекта не обязательно вызывается при удалении объектов в объёме с помощью QuerySet или в результате cascading
delete. Чтобы убедиться, что логика пользовательского удаления выполняется, вы можете использовать сигналы pre_delete и/или post_delete.
К сожалению, нет обходного пути, когда creating или updating объектов в объёме, поскольку ни один из save(), pre_save и post_save не вызываются.
Выполнение пользовательского SQL
Ещё одна распространённая схема — написание пользовательских SQL-заявлений в методах модели и методах на уровне модуля. Более подробную информацию об использовании чистого SQL см. в документации по использованию чистого SQL.
Наследование моделей
Наследование моделей в Django работает практически так же, как и обычное наследование классов в Python, но основы в начале страницы всё равно должны соблюдаться. Это означает, что базовый класс должен быть подклассом django.db.models.Model.
Единственное решение, которое вам нужно принять, — это то, хотите ли вы, чтобы родительские модели были моделями сами по себе (со своими таблицами базы данных), или же родители просто хранят общую информацию, которая будет видна только через дочерние модели.
Существует три стиля наследования, возможные в Django.
- Часто вы просто захотите использовать родительский класс для хранения информации, которую вам не нужно вводить для каждой дочерней модели. Этот класс никогда не будет использоваться изолированно, поэтому вам подойдут абстрактные базовые классы.
- Если вы наследуете существующую модель (возможно, что-то из другого приложения) и хотите, чтобы каждая модель имела свою таблицу базы данных, наследование с несколькими таблицами — это путь.
- Наконец, если вы хотите только изменить поведение модели на уровне Python, не изменяя поля моделей каким-либо образом, вы можете использовать модели-прокси.
Абстрактные базовые классы
Абстрактные базовые классы полезны, когда вы хотите поместить некоторую общую информацию в несколько других моделей. Вы пишете базовый класс и помещаете abstract=True в класс Meta. Эта модель больше не будет использоваться для создания таблиц базы данных. Вместо этого, при использовании в качестве базового класса для других моделей, её поля будут добавлены к полюм дочернего класса.
Пример:
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
Будьте осторожны с related_name и related_query_name
Чтобы обойти эту проблему, когда вы используете related_name или related_query_name в абстрактном базовом классе (только), часть значения должна содержать '%(app_label)s' и '%(class)s'.
-
'%(class)s'заменяется на имя дочернего класса в нижнем регистре, в котором используется поле. -
'%(app_label)s'заменяется на имя приложения в нижнем регистре, в котором содержится дочерний класс. Каждое установленное имя приложения должно быть уникальным, а имена классов моделей в каждом приложении также должны быть уникальными, поэтому результирующее имя будет отличаться.
Например, для приложения common/models.py:
from django.db import models
class Base(models.Model):
m2m = models.ManyToManyField(
OtherModel,
related_name="%(app_label)s_%(class)s_related",
related_query_name="%(app_label)s_%(class)ss",
)
class Meta:
abstract = True
class ChildA(Base):
pass
class ChildB(Base):
pass
Вместе с другим приложением rare/models.py:
from common.models import Base
class ChildB(Base):
pass
Обратное имя поля common.ChildA.m2m будет common_childa_related, а обратное имя запроса будет common_childas. Обратное имя поля common.ChildB.m2m будет common_childb_related, а обратное имя запроса будет common_childbs. Наконец, обратное имя поля rare.ChildB.m2m будет rare_childb_related, а обратное имя запроса будет rare_childbs. Вам решать, как использовать часть '%(class)s' и '%(app_label)s' для построения обратного имени или имени запроса, но если вы забудете использовать ее, Django выведет ошибки при проверке системы (или при запуске migrate).
Если вы не укажете атрибут related_name для поля в абстрактном базовом классе, по умолчанию обратное имя будет именем дочернего класса, за которым следует '_set', точно так же, как это обычно происходит, если бы вы объявили поле напрямую в дочернем классе. Например, в приведенном выше коде, если атрибут related_name был опущен, обратное имя поля m2m будет childa_set в случае ChildA и childb_set для поля ChildB.
Наследование с несколькими таблицами
Второй тип наследования моделей, поддерживаемый 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)
Все поля Place также будут доступны в Restaurant, хотя данные будут храниться в другой таблице базы данных. Поэтому оба варианта возможны:
>>> Place.objects.filter(name="Bob's Cafe") >>> Restaurant.objects.filter(name="Bob's Cafe")
Если у вас есть Place, который также является Restaurant, вы можете перейти от объекта Place к объекту Restaurant, используя строчную версию имени модели:
>>> 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=True в Restaurant.
Meta и наследование с несколькими таблицами
В ситуации с наследованием с несколькими таблицами для дочернего класса не имеет смысла наследовать класс Meta родительского класса. Все опции Meta уже применены к родительскому классу, и их повторное применение обычно приведет только к противоречивому поведению (это в отличие от случая с абстрактным базовым классом, где базовый класс не существует сам по себе).
Таким образом, дочерняя модель не имеет доступа к классу Meta родительского класса. Однако есть несколько ограниченных случаев, когда дочерний класс наследует поведение от родительского класса: если дочерний класс не указывает атрибут ordering или атрибут get_latest_by, он унаследует их от родителя.
Если родитель имеет порядок сортировки, и вы не хотите, чтобы дочерний класс имел естественный порядок сортировки, вы можете явно отключить его:
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
Наследование и обратные связи
Поскольку наследование с несколькими таблицами использует неявное OneToOneField для связывания дочернего и родительского класса, можно перейти от родительского к дочернему, как в приведенном выше примере. Однако это использует имя, которое является значением по умолчанию related_name для ForeignKey и ManyToManyField связей. Если вы устанавливаете эти типы связей в подклассе родительской модели, вы обязательно должны указать атрибут related_name в каждом таком поле. Если вы забудете, Django выведет ошибку проверки.
Например, используя приведенный выше класс Place снова, давайте создадим другой подкласс с ManyToManyField:
class Supplier(Place):
customers = models.ManyToManyField(Place)
Это приведет к ошибке:
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, чтобы указать, что это поле — связь с родительским классом.
Модели-прокси
При использовании наследования с разными таблицами для каждого подкласса модели создаётся новая таблица базы данных. Это обычно желаемое поведение, поскольку подклассу нужно место для хранения дополнительных полей данных, которые отсутствуют в базовом классе. Иногда, однако, требуется лишь изменить поведение модели на Python — например, изменить менеджер по умолчанию или добавить новый метод.
Для этого предназначено наследование моделей-прокси: создание прокси для исходной модели. Вы можете создавать, удалять и обновлять экземпляры модели-прокси, и все данные будут сохранены так, как если бы вы использовали исходную (непроксированную) модель. Разница в том, что вы можете изменить такие вещи, как порядок сортировки по умолчанию или менеджер по умолчанию в прокси, не изменяя исходную модель.
Модели-прокси объявляются как обычные модели. Вы сообщаете Django, что это модель-прокси, установив атрибут 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
Класс MyPerson работает с той же таблицей базы данных, что и его родительский класс Person. В частности, любые новые экземпляры CharField также будут доступны через MyPerson, и наоборот:
>>> p = Person.objects.create(first_name="foobar") >>> MyPerson.objects.get(first_name="foobar") <MyPerson: foobar>
Вы также можете использовать модель-прокси для определения другого порядка сортировки по умолчанию для модели. Возможно, вам не всегда нужно сортировать модель Person, но регулярно сортировать по атрибуту last_name при использовании прокси:
class OrderedPerson(Person):
class Meta:
ordering = ["last_name"]
proxy = True
Теперь обычные запросы Person будут несортированными, а запросы OrderedPerson будут отсортированы по last_name.
Модели-прокси наследуют атрибуты Meta так же, как и обычные модели.
QuerySet возвращают запрошенную модель
Нет способа заставить Django возвращать, например, объект MyPerson всякий раз, когда вы запрашиваете объекты Person. Запрос на объекты Person вернёт объекты именно этого типа. Главная цель прокси-объектов в том, что код, использующий исходную Person модель, будет использовать её, а ваш код может использовать добавления, которые вы включили (от которых не зависит никакой другой код). Это не способ заменить модель Person (или любую другую) везде чем-то созданным вами.
Ограничения базового класса
Модель-прокси должна наследоваться точно от одного неабстрактного класса модели. Нельзя наследоваться от нескольких неабстрактных моделей, так как модель-прокси не обеспечивает никакой связи между строками в различных таблицах базы данных. Модель-прокси может наследоваться от любого количества абстрактных классов моделей, при условии, что они не определяют никаких полей модели. Модель-прокси также может наследоваться от любого количества моделей-прокси, которые разделяют один общий неабстрактный родительский класс.
Менеджеры моделей-прокси
Если вы не указываете менеджеры моделей для модели-прокси, она наследует менеджеры от своих родительских моделей. Если вы определите менеджер в модели-прокси, он станет менеджером по умолчанию, но любые менеджеры, определённые в родительских классах, всё ещё будут доступны.
Продолжая наш пример выше, вы можете изменить менеджер по умолчанию, используемый при запросе модели 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
Вероятнее всего, вам это не нужно делать очень часто, но, когда нужно, это возможно.
Различия между наследованием прокси и неуправляемыми моделями
Наследование моделей-прокси может казаться довольно похожим на создание неуправляемой модели, используя атрибут managed в классе модели Meta.
С помощью аккуратной настройки Meta.db_table вы могли бы создать неуправляемую модель, которая скрывает существующую модель и добавляет к ней Python-методы. Однако это будет очень громоздко и ненадежно, так как вам нужно будет поддерживать синхронизацию обеих копий при внесении любых изменений.
С другой стороны, модели-прокси предназначены для точного поведения модели, которую они проксируют. Они всегда синхронизированы с родительской моделью, так как непосредственно наследуют её поля и менеджеры.
Общие правила:
- Если вы хотите дублировать существующую модель или таблицу базы данных и не хотите всех столбцов оригинальной таблицы базы данных, используйте
Meta.managed=False. Этот параметр обычно полезен для моделирования представлений баз данных и таблиц, не находящихся под управлением Django. - Если вы хотите изменить только Python-поведение модели, но сохранить все поля, как в оригинале, используйте
Meta.proxy=True. Это настраивает модель-прокси так, чтобы она была точной копией структуры хранения исходной модели при сохранении данных.
Наследование от нескольких моделей
Так же, как и при наследовании в Python, модель Django может наследоваться от нескольких родительских моделей. Имейте в виду, что применяются обычные правила разрешения имён Python. Первый базовый класс, в котором появляется определённое имя (например, Meta), будет использоваться; например, это означает, что если несколько родителей содержат класс Meta, будет использован только первый, а все остальные будут проигнорированы.
Как правило, вам не придётся наследоваться от нескольких родителей. Основной случай использования, где это полезно, — это классы «миксов»: добавление конкретного дополнительного поля или метода ко всем классам, которые наследуют микс. Старайтесь, чтобы иерархии наследования были максимально простыми и понятными, чтобы не приходилось мучиться с тем, откуда взялась та или иная информация.
Обратите внимание, что наследование от нескольких моделей, имеющих общее поле первичного ключа 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 это обычно не допускается для полей модели. Если базовый класс модели (не абстрактный) имеет поле, названное author, вы не можете создать другое поле модели или определить атрибут, названный author, в любом классе, унаследованном от этого базового класса.
Это ограничение не применяется к полям модели, унаследованным от абстрактной модели. Такие поля можно переопределить другим полем или значением, или удалить, установив field_name = None.
Предупреждение
Менеджеры моделей наследуются от абстрактных базовых классов. Переопределение унаследованного поля, на которое ссылается унаследованный Manager, может привести к скрытым ошибкам. См. настраиваемые менеджеры и наследование моделей.
Примечание
Некоторые поля определяют дополнительные атрибуты модели, например, ForeignKey определяет дополнительный атрибут с добавленным _id к имени поля, а также related_name и related_query_name для модели-получателя.
Эти дополнительные атрибуты нельзя переопределять, если не изменить или удалить поле, которое их определяет, чтобы оно больше не определяло эти дополнительные атрибуты.
Переопределение полей в родительской модели приводит к проблемам при инициализации новых экземпляров (указании поля, которое инициализируется в Model.__init__ ) и сериализации. Это особенности, с которыми обычное наследование классов Python не сталкивается в такой же степени, поэтому разница между наследованием моделей Django и наследованием классов Python не произвольна.
Это ограничение применяется только к атрибутам, которые являются экземплярами Field. Обычные атрибуты Python можно переопределить, если необходимо. Оно также относится только к имени атрибута, как его видит Python: если вы вручную указываете имя столбца базы данных, вы можете иметь одинаковое имя столбца, появляющееся как в дочерней, так и в родительской модели для наследования с несколькими таблицами (это столбцы в двух разных таблицах базы данных).
Django выведет исключение FieldError, если вы переопределите любое поле модели в любой родительской модели.
Организация моделей в пакете
Команда manage.py startapp создаёт структуру приложения, которая включает файл models.py. Если у вас много моделей, организация их в отдельных файлах может быть полезной.
Для этого создайте пакет models. Удалите models.py и создайте директорию myapp/models/ с файлом __init__.py и файлами для хранения ваших моделей. Вы должны импортировать модели в файл __init__.py.
Например, если у вас были organic.py и synthetic.py в директории models.
from .organic import Person from .synthetic import Robot
Явное импортирование каждой модели, а не использование from .models import *, имеет преимущества: не засоряется пространство имён, код становится более читаемым и инструменты анализа кода остаются полезными.
См. также
- Справочник по моделям
- Охватывает все API, связанные с моделями, включая поля моделей, связанные объекты и
QuerySet.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.1/topics/db/models/