Spec-Zone.ru › Django 4.2

Модели

Модель — единственный, определяющий источник информации о ваших данных. Она содержит основные поля и поведение хранимых данных. Как правило, каждая модель отображается в отдельной таблице базы данных.

Основы:

  • Каждая модель — это класс 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-код CREATE TABLE в этом примере отформатирован с использованием синтаксиса PostgreSQL, но стоит отметить, что Django использует SQL, настроенный под базу данных, указанную в вашем файле настроек.

Использование моделей

После определения моделей необходимо сообщить Django, что вы собираетесь использовать эти модели. Сделайте это, отредактировав файл настроек и изменив настройку INSTALLED_APPS, чтобы добавить имя модуля, содержащего ваши models.py.

Например, если модели вашего приложения находятся в модуле myapp.models (структура пакета, созданная для приложения скриптом manage.py startapp), то INSTALLED_APPS должно выглядеть примерно так:

INSTALLED_APPS = [
    # ...
    "myapp",
    # ...
]

При добавлении новых приложений в INSTALLED_APPS, обязательно запустите manage.py migrate, предварительно, при необходимости, создав для них миграции с помощью manage.py makemigrations.

Поля

Самая важная часть модели (и единственная обязательная часть модели) — это список полей базы данных, которые она определяет. Поля задаются атрибутами класса. Будьте внимательны, чтобы не выбирать имена полей, которые конфликтуют с API моделей, такими как clean, save, или delete.

Пример:

from django.db import models


class Musician(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    instrument = models.CharField(max_length=100)


class Album(models.Model):
    artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    release_date = models.DateField()
    num_stars = models.IntegerField()

Типы полей

Каждое поле в вашей модели должно быть экземпляром соответствующего класса Field. Django использует типы классов полей для определения нескольких вещей:

  • Тип столбца, который сообщает базе данных, какой тип данных хранить (например, INTEGER, VARCHAR, TEXT).
  • Значение HTML-виджета по умолчанию для отображения поля формы (например, <input type="text">, <select>).
  • Минимальные требования к проверке, используемые в админке Django и в автоматически генерируемых формах.

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

Параметры полей

Каждое поле принимает определенный набор специфических для поля аргументов (документированных в справочнике по полям модели). Например, CharField (и его подклассы) требуют аргумента max_length, который определяет размер поля базы данных VARCHAR используемого для хранения данных.

Также существует набор общих аргументов, доступных для всех типов полей. Все они необязательны. Они подробно описаны в справочнике, но вот краткий обзор наиболее часто используемых:

null
Если True, Django будет хранить пустые значения как NULL в базе данных. По умолчанию False.
blank

Если True, поле допускается оставить пустым. По умолчанию False.

Обратите внимание, что это отличается от null. null — чисто базoво-данных, в то время как blank — проверка. Если поле имеет blank=True, проверка формы позволит ввести пустое значение. Если у поля есть blank=False, поле будет обязательным.

choices

Последовательность кортежей (2-х элементов) для использования в качестве вариантов для данного поля. Если это задано, виджет формы по умолчанию будет выпадающим списком (select box), а не стандартным текстовым полем, и доступ к вариантам будет ограничен.

Список вариантов выглядит так:

YEAR_IN_SCHOOL_CHOICES = [
    ("FR", "Freshman"),
    ("SO", "Sophomore"),
    ("JR", "Junior"),
    ("SR", "Senior"),
    ("GR", "Graduate"),
]

Примечание

Каждое изменение порядка choices приводит к созданию новой миграции.

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

Для получения отображаемого значения поля с choices для экземпляра модели можно использовать метод get_FOO_display(). Например:

from django.db import models


class Person(models.Model):
    SHIRT_SIZES = [
        ("S", "Small"),
        ("M", "Medium"),
        ("L", "Large"),
    ]
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'

Вы также можете использовать классы перечислений для определения choices кратким способом:

from django.db import models


class Runner(models.Model):
    MedalType = models.TextChoices("MedalType", "GOLD SILVER BRONZE")
    name = models.CharField(max_length=60)
    medal = models.CharField(blank=True, choices=MedalType.choices, max_length=10)

Дополнительные примеры доступны в справочнике по полям модели.

default
Значение по умолчанию для поля. Может быть значением или вызываемым объектом. Если вызываемым объектом, то он будет вызываться каждый раз при создании нового объекта.
help_text
Дополнительный текст «помощи» для отображения вместе с виджетом формы. Это полезно для документации, даже если ваше поле не используется в форме.
primary_key

Если True, это поле является первичным ключом для модели.

Если вы не задаете primary_key=True ни для одного поля в вашей модели, Django автоматически добавит IntegerField для хранения первичного ключа, поэтому вам не нужно устанавливать primary_key=True ни на одном из ваших полей, если только вы не хотите переопределить поведение по умолчанию первичного ключа. Дополнительная информация см. в Автоматические поля первичного ключа.

Поле первичного ключа является только для чтения. Если вы измените значение первичного ключа существующего объекта и затем сохраните его, будет создан новый объект наряду со старым. Например:

from django.db import models


class Fruit(models.Model):
    name = models.CharField(max_length=100, primary_key=True)
>>> fruit = Fruit.objects.create(name="Apple")
>>> fruit.name = "Pear"
>>> fruit.save()
>>> Fruit.objects.values_list("name", flat=True)
<QuerySet ['Apple', 'Pear']>
unique
Если True, это поле должно быть уникальным в таблице.

Опять же, это только краткие описания наиболее распространенных параметров полей. Полные сведения см. в справочнике по общим параметрам полей модели.

Автоматические поля первичного ключа

По умолчанию Django присваивает каждой модели автоматически инкрементируемый первичный ключ с типом, указанным в приложении в 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 накладывает некоторые ограничения на имена полей моделей:

  1. Имя поля не может быть зарезервированным словом Python, так как это приведёт к синтаксической ошибке Python. Например:

    class Example(models.Model):
        pass = models.IntegerField() # 'pass' is a reserved word!
    
  2. Имя поля не может содержать более одной последовательной подчёркивающей черты, из-за того, как работает синтаксис поиска по Django. Например:

    class Example(models.Model):
        foo__bar = models.IntegerField()  # 'foo__bar' has two underscores!
    
  3. Имя поля не может заканчиваться подчёркивающей чертой, по схожим причинам.

Однако эти ограничения можно обойти, потому что имя вашего поля не обязательно должно совпадать с именем столбца вашей базы данных. См. параметр 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, *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 или в результате cascading delete. Чтобы убедиться, что настраиваемая логика удаления выполняется, можно использовать сигналы pre_delete и/или post_delete.

К сожалению, нет обходного пути, когда creating или updating объекты по частям, так как ни save(), pre_save и post_save не вызываются.

Выполнение пользовательского SQL

Ещё одна распространённая практика — написание пользовательских SQL-запросов в методах модели и методах на уровне модуля. Подробнее об использовании чистого SQL см. в документации по использованию чистого SQL.

Наследование моделей

Наследование моделей в Django работает почти идентично тому, как работает обычное наследование классов в Python, но основные принципы в начале страницы всё ещё должны соблюдаться. Это означает, что базовый класс должен быть подклассом django.db.models.Model.

Единственное решение, которое вам нужно принять, — это то, хотите ли вы, чтобы родительские модели были моделями самостоятельно (с собственными таблицами базы данных) или если родители просто хранят общую информацию, которая будет видна только через дочерние модели.

Существует три стиля наследования, возможные в Django.

  1. Часто вы просто захотите использовать родительский класс для хранения информации, которую вам не нужно набирать для каждой дочерней модели. Этот класс никогда не будет использоваться изолированно, поэтому вам нужны абстрактные базовые классы.
  2. Если вы наследуете от существующей модели (возможно, из другого приложения) и хотите, чтобы каждая модель имела собственную таблицу базы данных, вам подойдёт наследование с несколькими таблицами.
  3. Наконец, если вы хотите только изменить поведение модели на уровне Python, не изменяя поля моделей, вы можете использовать модели-прокси.

Абстрактные базовые классы

Абстрактные базовые классы полезны, когда вы хотите поместить некоторую общую информацию в несколько других моделей. Вы пишете базовый класс и помещаете abstract=True в класс Meta. Эта модель больше не будет использоваться для создания таблицы базы данных. Вместо этого, когда она используется в качестве базового класса для других моделей, её поля будут добавлены к полям дочернего класса.

Пример:

from django.db import models


class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True


class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

Модель Student будет иметь три поля: name, age и home_group. Модель CommonInfo не может использоваться как обычная модель Django, так как она является абстрактным базовым классом. Она не генерирует таблицу базы данных, не имеет менеджера и не может быть создана или сохранена напрямую.

Поля, унаследованные от абстрактных базовых классов, могут быть переопределены другим полем или значением или удалены с помощью None.

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

Meta наследование

Когда создается абстрактный базовый класс, Django делает доступным любой вложенный класс Meta, который вы объявили в базовом классе, в качестве атрибута. Если дочерний класс не объявляет свой собственный класс Meta, он унаследует класс Meta родителя. Если дочерний класс хочет расширить класс Meta родителя, он может сделать его подклассом. Например:

from django.db import models


class CommonInfo(models.Model):
    # ...
    class Meta:
        abstract = True
        ordering = ["name"]


class Student(CommonInfo):
    # ...
    class Meta(CommonInfo.Meta):
        db_table = "student_info"

Django вносит одно изменение в класс Meta абстрактного базового класса: перед установкой атрибута Meta он устанавливает abstract=False. Это означает, что дочерние классы абстрактных базовых классов не становятся автоматически абстрактными классами. Чтобы сделать абстрактный базовый класс, наследующий от другого абстрактного базового класса, необходимо явно установить abstract=True у дочернего класса.

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

Из-за того, как работает наследование в Python, если дочерний класс наследуется от нескольких абстрактных базовых классов, только параметры Meta из первого перечисленного класса будут унаследованы по умолчанию. Чтобы унаследовать параметры Meta от нескольких абстрактных базовых классов, необходимо явно объявить наследование Meta. Например:

from django.db import models


class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True
        ordering = ["name"]


class Unmanaged(models.Model):
    class Meta:
        abstract = True
        managed = False


class Student(CommonInfo, Unmanaged):
    home_group = models.CharField(max_length=5)

    class Meta(CommonInfo.Meta, Unmanaged.Meta):
        pass

Будьте внимательны с related_name и related_query_name

Если вы используете related_name или related_query_name для ForeignKey или ManyToManyField, вы всегда должны указывать уникальное обратное имя и имя запроса для поля. Это обычно приведет к проблеме в абстрактных базовых классах, так как поля в этом классе включаются в каждый из дочерних классов с точно такими же значениями для атрибутов (включая related_name и related_query_name) каждый раз.

Чтобы обойти эту проблему, при использовании related_name или related_query_name в абстрактном базовом классе (только), часть значения должна содержать '%(app_label)s' и '%(class)s'.

  • '%(class)s' заменяется на имя дочернего класса в нижнем регистре, в котором используется поле.
  • '%(app_label)s' заменяется на имя приложения в нижнем регистре, в котором находится дочерний класс. Каждое имя установленного приложения должно быть уникальным, и имена классов моделей в каждом приложении также должны быть уникальными, поэтому полученное имя в итоге будет отличаться.

Например, для приложения common/models.py:

from django.db import models


class Base(models.Model):
    m2m = models.ManyToManyField(
        OtherModel,
        related_name="%(app_label)s_%(class)s_related",
        related_query_name="%(app_label)s_%(class)ss",
    )

    class Meta:
        abstract = True


class ChildA(Base):
    pass


class ChildB(Base):
    pass

Наряду с другим приложением rare/models.py:

from common.models import Base


class ChildB(Base):
    pass

Обратное имя поля common.ChildA.m2m будет common_childa_related, а обратное имя запроса — common_childas. Обратное имя поля common.ChildB.m2m будет common_childb_related, а обратное имя запроса — common_childbs. Наконец, обратное имя поля rare.ChildB.m2m будет rare_childb_related, а обратное имя запроса — rare_childbs. Вам решать, как использовать части '%(class)s' и '%(app_label)s' для построения обратного имени или имени обратного запроса, но если вы забудете их использовать, Django выдаст ошибки при проверке системы (или при запуске migrate).

Если вы не указываете атрибут related_name для поля в абстрактном базовом классе, значение обратного имени по умолчанию будет именем дочернего класса, за которым следует '_set', так же как это обычно делается при прямом объявлении поля в дочернем классе. Например, в приведенном выше коде, если атрибут related_name был опущен, обратное имя для поля m2m было бы childa_set в случае ChildA и childb_set для поля ChildB.

Наследование с разными таблицами

Второй тип наследования моделей, поддерживаемый Django, — когда каждая модель в иерархии является самостоятельной моделью. Каждая модель соответствует своей собственной таблице базы данных и может быть запрошена и создана индивидуально. Связь наследования вводит ссылки между дочерней моделью и каждым из её родителей (через автоматически созданное OneToOneField). Например:

from django.db import models


class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)


class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

Все поля Place также будут доступны в Restaurant, хотя данные будут храниться в разных таблицах базы данных. Поэтому оба варианта возможны:

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

Если у вас есть объект Place, который также является объектом Restaurant, вы можете получить от объекта Place к объекту Restaurant, используя нижний регистр имени модели:

>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>

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

Автоматически созданное OneToOneField в Restaurant, которое связывает его с Place, выглядит так:

place_ptr = models.OneToOneField(
    Place,
    on_delete=models.CASCADE,
    parent_link=True,
    primary_key=True,
)

Вы можете переопределить это поле, объявив собственное OneToOneField и установив parent_link=True в Restaurant.

Meta и наследование с разными таблицами

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

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

Если у родителя есть порядок сортировки, и вы не хотите, чтобы у дочернего класса был естественный порядок сортировки, вы можете явно отключить его:

class ChildModel(ParentModel):
    # ...
    class Meta:
        # Remove parent's ordering effect
        ordering = []

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

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

Например, используя приведенный выше класс Place снова, создадим другой подкласс с ManyToManyField:

class Supplier(Place):
    customers = models.ManyToManyField(Place)

Это приведет к ошибке:

Reverse query name for 'Supplier.customers' clashes with reverse query
name for 'Supplier.place_ptr'.

HINT: Add or change a related_name argument to the definition for
'Supplier.customers' or 'Supplier.place_ptr'.

Добавление related_name к полю customers следующим образом решит проблему: models.ManyToManyField(Place, related_name='provider').

Указание поля ссылки на родителя

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

Модели-прокси

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

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

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

Например, предположим, что вы хотите добавить метод к модели Person. Вы можете сделать это так:

from django.db import models


class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)


class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # ...
        pass

Класс MyPerson работает с той же таблицей базы данных, что и его родительский класс Person. В частности, любые новые экземпляры 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. Однако это будет очень повторяющимся и хрупким решением, так как вам нужно будет синхронизировать обе копии при внесении любых изменений.

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

Общие правила:

  1. Если вы хотите отразить существующую модель или таблицу базы данных и не хотите все исходные столбцы таблицы базы данных, используйте Meta.managed=False. Этот параметр обычно полезен для моделирования представлений баз данных и таблиц, которые не контролируются Django.
  2. Если вы хотите изменить поведение модели только на 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__.py
from .organic import Person
from .synthetic import Robot

Явное импортирование каждой модели вместо использования from .models import * имеет преимущества, заключающиеся в том, что пространство имён не засоряется, код становится более читабельным, и инструменты анализа кода остаются полезными.

См. также

Справочник по моделям
Охватывает все API, связанные с моделями, включая поля моделей, связанные объекты и QuerySet.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.2/topics/db/models/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API