Модели¶
Модель является единственным источником информации о ваших данных. Она содержит основные поля и поведение данных, которые вы храните. Как правило, каждая модель отображается в одну таблицу базы данных.
Основы:
- Каждая модель представляет собой класс 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
являются fields модели. Каждое поле указывается как атрибут класса, и каждый атрибут отображается в столбец базы данных.
Приведенная выше модель 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
CREATE TABLE
в этом примере отформатирован с использованием синтаксиса PostgreSQL, но стоит отметить, что Django использует SQL с учетом серверной части базы данных, указанной в вашем файле settings.
Использование моделей¶
После того, как вы определили свои модели, вам нужно сообщить Django, что вы собираетесь использовать эти модели. Сделайте это, отредактировав файл настроек и изменив параметр INSTALLED_APPS
, чтобы добавить имя модуля, который содержит ваш models.py
.
Например, если модели для вашего приложения находятся в модуле myapp.models
(структура пакета, которая создается для приложения сценарием manage.py startapp
), INSTALLED_APPS
следует читать частично:
INSTALLED_APPS = [
#...
'myapp',
#...
]
Когда вы добавляете новые приложения в INSTALLED_APPS
, обязательно запустите manage.py migrate
, при необходимости сначала сделав миграцию для них с помощью manage.py makemigrations
.
Поля¶
Самая важная часть модели - и единственная необходимая часть модели - это список полей базы данных, которые она определяет. Поля определяются атрибутами класса. Будьте внимательны и не выбирайте имена полей, которые конфликтуют с models 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-код по умолчанию widget, используемый при визуализации поля формы (например,
<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
A sequence of 2-tuples to use as choices for this field. If this is given, the default form widget will be a select box instead of the standard text field and will limit choices to the choices given.
Список выбора выглядит следующим образом:
YEAR_IN_SCHOOL_CHOICES = [ ('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ('GR', 'Graduate'), ]
Примечание
Новая миграция создается каждый раз, когда меняется порядок
выборов
.Первый элемент в каждом кортеже - это значение, которое будет храниться в базе данных. Второй элемент отображается виджетом формы поля.
Для данного экземпляра модели доступ к отображаемому значению поля с
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 предоставляет каждой модели автоматически увеличивающийся первичный ключ с типом, указанным для каждого приложения в 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 предлагает способы определения трех наиболее распространенных типов отношений с базой данных: многие-к-одному (many-to-one), многие-ко-многим (many-to-many) и один-к-одному (one-to-one).
Отношения много-к-одному¶
Чтобы определить отношение многие-к-одному, используйте 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
поля принимают ряд дополнительных аргументов, которые описаны в :ref:`ссылке на поле модели <foreign-key-arguments> `. Эти параметры помогают определить, как должны работать отношения; все необязательно.
Для получения подробной информации об обратном доступе к связанным объектам, см. :ref:`Пример обратной связи <backwards-related-objects>.
Пример кода смотрите в примере отношения моделей «многие к одному».
Отношения многие ко многим¶
Чтобы определить отношение «многие ко многим», используйте 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
также принимают ряд дополнительных аргументов, которые объясняются в :ref:`по ссылке <manytomany-arguments> `. Эти параметры помогают определить, как должны работать отношения; все необязательно.
Дополнительные поля в отношениях «многие ко многи컶
Когда вы имеете дело только с отношениями «многие ко многим», такими как смешивание и сопоставление пиццы и начинки, вам нужен стандартный ManyToManyField
. Однако иногда вам может потребоваться связать данные с отношениями между двумя моделями.
Например, рассмотрим случай, когда приложение отслеживает музыкальные группы, к которым принадлежат музыканты. Существует отношение «многие ко многим» между человеком и группами, членами которых он является, поэтому вы можете использовать ManyToManyField
для представления этих отношений. Тем не менее, есть много деталей о членстве, которые вы, возможно, захотите получить, например, о дате, когда человек присоединился к группе.
В этих ситуациях Django позволяет вам указать модель, которая будет использоваться для управления отношением «многие ко многим». Затем вы можете поместить дополнительные поля в промежуточную модель. Промежуточная модель связана с ManyToManyField
с помощью аргумента через
, указывающего на модель, которая будет выступать в качестве посредника. Для нашего примера с музыкантами код будет выглядеть примерно так:
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)
Последний метод в этом примере - это property.
В справочнике экземпляра модели имеется полный список методов, автоматически предоставляемых каждой модели. Вы можете переопределить большинство из них - см. «Переопределение предопределенных методов модели», ниже - но есть пара, которую вы почти всегда захотите определить:
__str__()
«Волшебный метод» Python, который возвращает строковое представление любого объекта. Это то, что Python и Django будут использовать всякий раз, когда необходимо привести экземпляр модели и отобразить ее в виде простой строки. В частности, это происходит, когда вы отображаете объект в интерактивной консоли или в админке.
Вы всегда будете переопределять этот метод; значение по умолчанию не очень полезно.
get_absolute_url()
Эта функция возвращает 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
в определениях вашего метода, вы гарантированно, что ваш код будет автоматически поддерживать эти аргументы при их добавлении.
Если вы хотите обновить значение поля в методе 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, force_insert=False, force_update=False, using=None, update_fields=None
):
self.slug = slugify(self.name)
if update_fields is not None and "name" in update_fields:
update_fields = {"slug"}.union(update_fields)
super().save(
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields,
)
Более подробную информацию см. в разделе Указание полей для сохранения.
Переопределенные методы модели не вызываются в массовых операциях
Обратите внимание, что метод delete()
для объекта не обязательно вызывается, например массовое удаление объектов с использованием QuerySet или в результате каскадное удаление
. Для обеспечения выполнения настраиваемой логики удаления вы можете использовать сигналы pre_delete
и / или post_delete
.
К сожалению, нет обходного пути, когда создание объектов
или обновление
, поскольку ни один из следующих методов 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
Мультитабличное наследование¶
Второй тип наследования моделей, поддерживаемый 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
и установить :attr:`parent_link=True <django.db.models. OneToOneField.parent_link> `, чтобы указать, что ваше поле является ссылкой на родительский класс.
Модели прокси¶
При использовании наследование нескольких таблиц создается новая таблица базы данных для каждого подкласса модели. Обычно это желаемое поведение, поскольку подклассу требуется место для хранения любых дополнительных полей данных, которые отсутствуют в базовом классе. Однако иногда вам нужно только изменить поведение модели - возможно, изменить менеджер по умолчанию или добавить новый метод.
Для этого и используется наследование прокси-модели: создание прокси для исходной модели. Вы можете создавать, удалять и обновлять экземпляры прокси-модели, и все данные будут сохранены, как если бы вы использовали исходную (не-прокси) модель. Разница в том, что вы можете изменить такие вещи, как порядок модели по умолчанию или менеджер по умолчанию в прокси, без необходимости изменять оригинал.
Прокси-модели объявлены как обычные модели. Вы сообщаете 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
так же, как и обычные модели.
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
Если вы хотите добавить в Proxy новый менеджер без замены существующего по умолчанию, вы можете использовать методы, описанные в документации свой менеджер: создайте базовый класс, содержащий новые менеджеры и наследуйте его после первичного базового класса:
# 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
Название поля «hiding» не допускается¶
В обычном наследовании классов 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
:
from .organic import Person
from .synthetic import Robot
Явный импорт каждой модели вместо использования from .models import *
имеет преимущества, заключающиеся в том, чтобы не загромождать пространство имен, делать код более читабельным и сохранять инструменты анализа кода полезными.
См.также
- Справочник по моедлям
- Охватывает все API, связанные с моделью, включая поля модели, связанные объекты и
QuerySet
.