Spec-Zone.ru › Django 5.0

Модели

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

Основы:

  • Каждая модель — это класс 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, что вы собираетесь использовать эти модели. Для этого измените файл настроек и добавьте имя модуля, содержащего ваши models.py, в настройку INSTALLED_APPS.

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

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

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

Поля

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

Пример:

from django.db import models


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


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

Типы полей

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

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

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

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

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

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

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

Если True, поле разрешено быть пустым. По умолчанию False.

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

choices

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

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

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

Примечание

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

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

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

from django.db import models


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

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

from django.db import models


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

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

Изменено в Django 5.0:

Была добавлена поддержка отображений и вызываемых объектов.

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

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

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

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

from django.db import models


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

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

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

По умолчанию Django добавляет к каждой модели автоинкрементируемый первичный ключ с типом, указанным в AppConfig.default_auto_field для каждой приложения или глобально в настройке DEFAULT_AUTO_FIELD. Например:

id = models.BigAutoField(primary_key=True)

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

Каждая модель требует ровно одного поля с primary_key=True (либо явно объявленного, либо автоматически добавленного).

Подробные имена полей

Каждый тип поля, кроме ForeignKey, ManyToManyField и OneToOneField, принимает необязательный первый позиционный аргумент — подробное имя. Если подробное имя не задано, Django автоматически создаст его, используя имя атрибута поля, преобразуя подчеркивания в пробелы.

В этом примере подробное имя — "person's first name":

first_name = models.CharField("person's first name", max_length=30)

В этом примере подробное имя — "first name":

first_name = models.CharField(max_length=30)

ForeignKey, ManyToManyField и OneToOneField требуют, чтобы первым аргументом был класс модели, поэтому используйте ключевой аргумент verbose_name:

poll = models.ForeignKey(
    Poll,
    on_delete=models.CASCADE,
    verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
    Place,
    on_delete=models.CASCADE,
    verbose_name="related place",
)

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

Взаимосвязи

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

Взаимосвязи многие-ко-одному

Для определения взаимосвязи многие-ко-одному используйте django.db.models.ForeignKey. Вы используете его так же, как и любой другой тип Field: включив его в качестве атрибута класса вашей модели.

ForeignKey требует позиционного аргумента: класс, с которым связана модель.

Например, если модель Car имеет взаимосвязь Manufacturer — то есть, Manufacturer создаёт множество автомобилей, но каждый Car имеет только один Manufacturer — используйте следующие определения:

from django.db import models


class Manufacturer(models.Model):
    # ...
    pass


class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    # ...

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

Рекомендуется, но не обязательно, чтобы имя поля ForeignKey (manufacturer в примере выше) было именем модели в нижнем регистре. Вы можете называть поле как угодно.

class Car(models.Model):
    company_that_makes_it = models.ForeignKey(
        Manufacturer,
        on_delete=models.CASCADE,
    )
    # ...

См. также

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

Подробности о доступе к обратно связанным объектам см. в примере обратной связи.

Примеры кода см. в примере модели связи многие-к-одному.

Связи многие-ко-многим

Для определения связи многие-ко-многим используйте ManyToManyField. Вы используете его как и любой другой тип Field: включив его в качестве атрибута класса вашей модели.

ManyToManyField требует позиционного аргумента: класс, с которым связана модель.

Например, если у Pizza есть несколько объектов Topping — то есть, Topping может быть на нескольких пиццах, а каждая Pizza имеет несколько добавок — вот как это можно представить:

from django.db import models


class Topping(models.Model):
    # ...
    pass


class Pizza(models.Model):
    # ...
    toppings = models.ManyToManyField(Topping)

Как и с ForeignKey, вы также можете создать рекурсивные связи (объект со связью многие-ко-многим с самим собой) и связи с моделями, которые еще не определены.

Рекомендуется, но не обязательно, чтобы имя ManyToManyField (toppings в примере выше) было множественным числом, описывающим набор связанных объектов модели.

Неважно, какая модель имеет ManyToManyField, но вы должны поместить его только в одну из моделей, а не в обе.

Как правило, экземпляры ManyToManyField должны находиться в объекте, который будет редактироваться в форме. В приведенном выше примере, toppings находится в Pizza (а не Topping имеет pizzas ManyToManyField), потому что более естественно думать о пицце, имеющей добавки, чем о добавке, находящейся на нескольких пиццах. Таким образом, форма Pizza позволит пользователям выбирать добавки.

См. также

См. пример модели связи многие-ко-многим для полного примера.

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

Дополнительные поля в связях многие-ко-многим

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

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

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

from django.db import models


class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name


class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through="Membership")

    def __str__(self):
        return self.name


class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

Когда вы настраиваете модель-посредник, вы явно указываете внешние ключи к моделям, которые участвуют в связи многие-ко-многим. Это явное объявление определяет, как связаны две модели.

Существует несколько ограничений на модель-посредник:

  • Ваша модель-посредник должна содержать один - и только один - внешний ключ к исходной модели (это Group в нашем примере), или вы должны явно указать внешние ключи, которые Django должен использовать для связи, используя ManyToManyField.through_fields. Если у вас есть более одного внешнего ключа и through_fields не указан, будет выведено сообщение об ошибке валидации. Аналогичное ограничение относится к внешнему ключу к целевой модели (это Person в нашем примере).
  • Для модели, которая имеет связь многие-ко-многим с собой через модель-посредник, разрешены два внешних ключа к одной и той же модели, но они будут обрабатываться как две (разные) стороны связи многие-ко-многим. Однако если их больше двух, вы также должны указать through_fields как указано выше, иначе будет выведена ошибка валидации.

Теперь, когда вы настроили ManyToManyField для использования вашей модели-посредника (Membership, в данном случае), вы готовы начать создание связей многие-ко-многим. Для этого создайте экземпляры модели-посредника:

>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(
...     person=ringo,
...     group=beatles,
...     date_joined=date(1962, 8, 16),
...     invite_reason="Needed a new drummer.",
... )
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(
...     person=paul,
...     group=beatles,
...     date_joined=date(1960, 8, 1),
...     invite_reason="Wanted to form a band.",
... )
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>

Вы также можете использовать add(), create() или set() для создания связей, если вы укажете through_defaults для необходимых полей:

>>> beatles.members.add(john, through_defaults={"date_joined": date(1960, 8, 1)})
>>> beatles.members.create(
...     name="George Harrison", through_defaults={"date_joined": date(1960, 8, 1)}
... )
>>> beatles.members.set(
...     [john, paul, ringo, george], through_defaults={"date_joined": date(1960, 8, 1)}
... )

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

Если настраиваемая таблица посредника, определенная моделью-посредником, не обеспечивает уникальность пары (model1, model2), разрешая несколько значений, вызов remove() удалит все экземпляры модели-посредника:

>>> Membership.objects.create(
...     person=ringo,
...     group=beatles,
...     date_joined=date(1968, 9, 4),
...     invite_reason="You've been gone for a month and we miss you.",
... )
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This deletes both of the intermediate model instances for Ringo Starr
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>

Метод clear() может использоваться для удаления всех связей многие-ко-многим для экземпляра:

>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
<QuerySet []>

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

# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith="Paul")
<QuerySet [<Group: The Beatles>]>

Поскольку вы используете модель-посредник, вы также можете запросить по ее атрибутам:

# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
...     group__name="The Beatles", membership__date_joined__gt=date(1961, 1, 1)
... )
<QuerySet [<Person: Ringo Starr]>

Если вам нужно получить информацию о членстве, вы можете сделать это, напрямую запросив модель Membership.

>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'

Другой способ получить ту же информацию — запросить обратную связь многие-ко-многим от объекта Person:

>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'

Связи один-к-одному

Для определения связи один-к-одному используйте OneToOneField. Вы используете его как и любой другой тип Field: включив его в качестве атрибута класса вашей модели.

Это наиболее полезно для первичного ключа объекта, когда этот объект «расширяет» другой объект каким-либо образом.

OneToOneField требует позиционного аргумента: класс, с которым связана модель.

Например, если вы создаете базу данных «мест», вы будете добавлять стандартные данные, такие как адрес, номер телефона и т. д., в базу данных. Затем, если вы хотите создать базу данных ресторанов на основе мест, вместо того чтобы повторяться и дублировать эти поля в модели Restaurant, вы можете сделать, чтобы Restaurant имела OneToOneField к Place (потому что ресторан «является» местом; на самом деле, для этого обычно используется наследование, которое подразумевает однозначное отношение).

Как и ForeignKey, можно определить рекурсивное отношение и ссылки на еще не определенные модели.

См. также

См. пример модели однозначного отношения Пример модели однозначного отношения для полного примера.

OneToOneField поля также принимают необязательный аргумент parent_link.

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

Модели в разных файлах

Совершенно допустимо связать модель с моделью из другого приложения. Для этого импортируйте связанную модель в верхней части файла, где определена ваша модель. Затем используйте класс другой модели в необходимых местах. Например:

from django.db import models
from geography.models import ZipCode


class Restaurant(models.Model):
    # ...
    zip_code = models.ForeignKey(
        ZipCode,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

Ограничения имен полей

Django накладывает некоторые ограничения на имена полей моделей:

  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 и наследование с несколькими таблицами

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

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

Запросы всё ещё возвращают запрошенную модель

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

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

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

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

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

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

from django.db import models


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


class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

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

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

    class Meta:
        abstract = True


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

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

Различия между наследованием по модели proxy и необработанными моделями

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

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

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

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

  1. Если вы копируете существующую модель или таблицу базы данных и не хотите все исходные столбцы таблицы базы данных, используйте Meta.managed=False. Этот вариант обычно полезен для моделирования представлений и таблиц баз данных, не контролируемых Django.
  2. Если вы хотите изменить поведение модели только на языке Python, но сохранить все поля, как в оригинале, используйте Meta.proxy=True. Это настраивает вещи так, что модель proxy является точной копией структуры хранения исходной модели при сохранении данных.

Многократное наследование

Так же, как и в случае с наследованием в 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.0/topics/db/models/

Spec-Zone.ru

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