Spec-Zone.ru › Django 5.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 касается только базы данных, в то время как blank касается валидации. Если поле имеет blank=True, валидация форм позволит вводить пустые значения. Если поле имеет blank=False, поле будет обязательным.

choices

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

Список вариантов выглядит следующим образом:

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

Примечание

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

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

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

from django.db import models


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

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

from django.db import models


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

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

default

Значение по умолчанию для поля. Это может быть значение или вызываемый объект. Если вызываемый объект, он будет вызываться каждый раз при создании нового объекта.

db_default

Значение по умолчанию для поля, вычисляемое базой данных. Это может быть литеральное значение или функция базы данных.

Если и db_default, и Field.default заданы, default будет иметь приоритет при создании экземпляров в коде Python. db_default все равно будет установлено на уровне базы данных и будет использоваться при вставке строк вне ORM или при добавлении нового поля в миграцию.

help_text

Дополнительный текст «помощи», который будет отображаться вместе с виджетом формы. Это полезно для документации, даже если ваше поле не используется в форме.

primary_key

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

Если вы не укажете primary_key=True для каких-либо полей в вашей модели, Django автоматически добавит поле для хранения первичного ключа, поэтому вам не нужно устанавливать 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. Имя поля не может заканчиваться символом нижнего подчеркивания по аналогичным причинам.
  4. Имя поля не может быть check, так как это переопределит метод Model.check() фреймворка проверки.

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

SQL-зарезервированные слова, такие как join, where или select, разрешены в качестве имён полей модели, потому что Django экранирует все имена таблиц и столбцов базы данных в каждом подлежащем SQL-запросе. Он использует синтаксис цитирования, специфичный для конкретного движка базы данных.

Пользовательские типы полей

Если ни одно из существующих полей модели не подходит для ваших нужд, или если вы хотите воспользоваться менее распространёнными типами столбцов базы данных, вы можете создать собственный класс поля. Полное руководство по созданию собственных полей приведено в Руководстве по созданию пользовательских полей модели.

Meta параметры

Укажите метаданные вашей модели, используя внутренний class Meta, как показано ниже:

from django.db import models


class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

Метаданные модели — это «все, что не является полем», например, параметры сортировки (ordering), имя таблицы базы данных (db_table) или удобочитаемые единственное и множественное число (verbose_name и verbose_name_plural). Никакие из них не являются обязательными, и добавление class Meta к модели является полностью необязательным.

Полный список всех возможных Meta параметров можно найти в справочнике по параметрам модели.

Атрибуты модели

objects

Самым важным атрибутом модели является Manager. Это интерфейс, через который операции запросов к базе данных предоставляются моделям Django и используется для получения экземпляров из базы данных. Если пользовательский Manager не определен, имя по умолчанию — objects. Менеджеры доступны только через классы моделей, а не через экземпляры моделей.

Методы модели

Определите пользовательские методы в модели, чтобы добавить пользовательскую функциональность «уровня строки» к вашим объектам. В то время как методы Manager предназначены для выполнения действий «на уровне всей таблицы», методы моделей должны действовать на конкретном экземпляре модели.

Это ценный метод для хранения логики бизнес-процессов в одном месте — в модели.

Например, эта модель имеет несколько пользовательских методов:

from django.db import models


class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime

        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

    @property
    def full_name(self):
        "Returns the person's full name."
        return f"{self.first_name} {self.last_name}"

Последний метод в этом примере является свойством.

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

__str__()

Метод Python «магии», возвращающий строковое представление любого объекта. Именно его Python и Django будут использовать всякий раз, когда экземпляру модели нужно преобразовать и отобразить в виде обычной строки. В частности, это происходит, когда вы отображаете объект в интерактивной консоли или в админке.

Вы всегда должны определить этот метод; метод по умолчанию совершенно бесполезен.

get_absolute_url()

Это указывает Django, как рассчитать URL для объекта. Django использует это в своём админском интерфейсе и всякий раз, когда ему нужно определить URL для объекта.

Любой объект, у которого есть URL, однозначно его идентифицирующий, должен определить этот метод.

Переопределение предопределенных методов модели

Существует ещё один набор методов модели, которые обобщают ряд функций поведения базы данных, которые вы захотите настроить. В частности, вам часто захочется изменить способ работы save() и delete().

Вы можете свободно переопределять эти методы (и любые другие методы модели) для изменения поведения.

Классический пример переопределения встроенных методов — если вы хотите, чтобы что-то происходило каждый раз, когда вы сохраняете объект. Например (см. save() для документации параметров, которые он принимает):

from django.db import models


class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, **kwargs):
        do_something()
        super().save(**kwargs)  # Call the "real" save() method.
        do_something_else()

Вы также можете предотвратить сохранение:

from django.db import models


class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, **kwargs):
        if self.name == "Yoko Ono's blog":
            return  # Yoko shall never have her own blog!
        else:
            super().save(**kwargs)  # Call the "real" save() method.

Важно помнить о вызове метода суперкласса — это то, что super().save(**kwargs) бизнес — чтобы гарантировать, что объект всё ещё сохраняется в базе данных. Если вы забудете вызвать метод суперкласса, поведение по умолчанию не произойдёт, и база данных не будет затронута.

Также важно, чтобы вы передавали аргументы, которые могут быть переданы методу модели — именно это делает **kwargs часть. Django время от времени расширяет возможности встроенных методов модели, добавляя новые ключевые аргументы. Если вы используете **kwargs в своих определениях методов, вы гарантируете, что ваш код будет автоматически поддерживать эти аргументы при их добавлении.

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

from django.db import models
from django.utils.text import slugify


class Blog(models.Model):
    name = models.CharField(max_length=100)
    slug = models.TextField()

    def save(self, **kwargs):
        self.slug = slugify(self.name)
        if (
            update_fields := kwargs.get("update_fields")
        ) is not None and "name" in update_fields:
            kwargs["update_fields"] = {"slug"}.union(update_fields)
        super().save(**kwargs)

См. Указание полей для сохранения для получения дополнительной информации.

Переопределённые методы модели не вызываются при массовых операциях

Обратите внимание, что метод delete() объекта не обязательно вызывается при удалении объектов массово с помощью QuerySet или в результате cascading delete. Чтобы гарантировать выполнение настроенной логики удаления, вы можете использовать pre_delete и/или post_delete сигналы.

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

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

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

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

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

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

В Django доступны три стиля наследования.

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

>>> 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 так же, как и обычные модели.

QuerySet возвращают запрошенную модель

Нет способа заставить Django возвращать, скажем, объект MyPerson всякий раз, когда вы запрашиваете объекты Person. Запрос на объекты Person вернет объекты этих типов. Суть прокси-объектов заключается в том, что код, использующий исходную модель Person, будет использовать её, а ваш собственный код сможет использовать расширения, которые вы добавили (от которых другой код не зависит). Это не способ заменить модель Person (или любую другую) везде чем-то созданным вами.

Ограничения базового класса

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

Менеджеры моделей-прокси

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

Продолжая наш пример выше, вы можете изменить менеджер по умолчанию, используемый при запросе модели Person, следующим образом:

from django.db import models


class NewManager(models.Manager):
    # ...
    pass


class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

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

# Create an abstract class for the new manager.
class ExtraManagers(models.Model):
    secondary = NewManager()

    class Meta:
        abstract = True


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

Вероятно, вам это не понадобится очень часто, но, когда это потребуется, это возможно.

Отличия между наследственностью моделей-прокси и не управляемыми моделями

Наследование модели-прокси может быть довольно похожим на создание не управляемой модели, используя атрибут managed в классе модели Meta.

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

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

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

  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/5.2/topics/db/models/

Spec-Zone.ru

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