Модели
Модель — это единственный и определённый источник информации о ваших данных. Она содержит основные поля и поведение хранимых данных. Как правило, каждая модель соответствует одной базе данных.
Основные моменты:
- Каждая модель — это класс 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" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
Некоторые технические замечания:
- Имя таблицы,
myapp_person, автоматически выводится из метаданных модели, но может быть переопределено. Дополнительные сведения см. в Имена таблиц. - Поле
id, добавляется автоматически, но это поведение может быть переопределено. См. Автоматические поля первичного ключа. - SQL-код в этом примере отформатирован с использованием синтаксиса 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, max_length=10)Дополнительные примеры см. в справочнике по полям модели.
Изменено в Django 5.0:Добавлена поддержка отображений и вызываемых объектов.
-
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 увидит, что вы явно установили 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 f"{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, **kwargs):
do_something()
super().save(**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, **kwargs):
if self.name == "Yoko Ono's blog":
return # Yoko shall never have her own blog!
else:
super().save(**kwargs) # Call the "real" save() method.
Важно помнить о вызове метода суперкласса — это тот super().save(**kwargs) момент — для обеспечения того, что объект всё ещё сохраняется в базе данных. Если вы забудете вызвать метод суперкласса, поведение по умолчанию не произойдёт, и база данных не будет затронута.
Также важно передавать аргументы, которые могут быть переданы в метод модели — это то, что делает часть **kwargs. Время от времени Django будет расширять возможности встроенных методов моделей, добавляя новые ключевые аргументы. Если вы используете **kwargs в определениях своих методов, вы гарантируете, что ваш код автоматически будет поддерживать эти аргументы при их добавлении.
Если вы хотите обновить значение поля в методе save(), вам также может потребоваться добавить это поле в update_fields ключевой аргумент. Это гарантирует, что поле будет сохранено, когда update_fields указано. Например:
from django.db import models
from django.utils.text import slugify
class Blog(models.Model):
name = models.CharField(max_length=100)
slug = models.TextField()
def save(self, **kwargs):
self.slug = slugify(self.name)
if (
update_fields := kwargs.get("update_fields")
) is not None and "name" in update_fields:
update_fields = {"slug"}.union(update_fields)
super().save(**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 в класс Мета. Эта модель не будет использоваться для создания таблицы базы данных. Вместо этого, когда она используется в качестве базового класса для других моделей, ее поля добавляются к полям дочернего класса.
Пример:
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 делает любой внутренний класс Мета, который вы объявляли в базовом классе, доступным как атрибут. Если дочерний класс не объявляет собственный класс Мета, он наследует Мета родительского класса. Если дочерний класс хочет расширить класс Мета родительского класса, он может его унаследовать. Например:
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 вносит одну корректировку в класс Мета абстрактного базового класса: перед установкой атрибута Мета он устанавливает abstract=False. Это означает, что дочерние классы абстрактных базовых классов автоматически не становятся абстрактными классами. Чтобы сделать абстрактный базовый класс, который наследует от другого абстрактного базового класса, необходимо явно установить abstract=True в дочернем классе.
Некоторые атрибуты не будут иметь смысла для включения в класс Мета абстрактного базового класса. Например, включение db_table означало бы, что все дочерние классы (те, которые не указывают свой собственный класс Мета) будут использовать одну и ту же таблицу базы данных, что почти наверняка не то, что вам нужно.
Из-за того, как работает наследование в Python, если дочерний класс наследует от нескольких абстрактных базовых классов, только опции Мета от первого перечисленного класса будут унаследованы по умолчанию. Чтобы унаследовать опции Мета от нескольких абстрактных базовых классов, необходимо явно объявить наследование Мета. Например:
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 и наследование с несколькими таблицами
В ситуации с наследованием с несколькими таблицами, для дочернего класса не имеет смысла наследоваться от метакласса родителя. Все мета-опции уже применены к родительскому классу, и повторное их применение обычно приводит только к противоречивому поведению (это в отличие от случая с абстрактным базовым классом, где базовый класс не существует сам по себе).
Таким образом, дочерняя модель не имеет доступа к метаклассу родителя. Тем не менее, есть несколько ограниченных случаев, когда дочерний класс наследует поведение от родителя: если дочерний класс не указывает атрибут 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. В частности, любые новые экземпляры Person также будут доступны через 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 так же, как и обычные модели.
QuerySets по-прежнему возвращают запрошенную модель
Нет способа заставить 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, если вы переопределите какое-либо поле модели в какой-либо родительской модели.
Обратите внимание, что из-за способа разрешения полей во время определения класса, поля моделей, унаследованные от нескольких абстрактных родительских моделей, разрешаются в строгом порядке глубины-вперед. Это отличается от стандартного Python MRO, который разрешается в ширину в случаях наследования с алмазной формой. Это различие касается только сложных иерархий моделей, которых (как советуется выше) следует избегать.
Организация моделей в пакете
Команда manage.py startapp создает структуру приложения, которая включает файл models.py. Если у вас много моделей, организация их в отдельных файлах может быть полезной.
Для этого создайте пакет models. Удалите models.py и создайте директорию myapp/models/ с файлом __init__.py и файлами для хранения ваших моделей. Вы должны импортировать модели в файл __init__.py.
Например, если у вас были organic.py и synthetic.py в директории models:
myapp/models/__init__.pyfrom .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/5.1/topics/db/models/