Spec-Zone.ru › Django 6.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, что вы собираетесь их использовать. Для этого отредактируйте файл настроек и измените параметр 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, при создании экземпляров в коде Python приоритет будет у default. 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)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["person", "group"], name="unique_person_group"
            )
        ]

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

Если не нужны несколько связей между одними и теми же экземплярами, добавьте UniqueConstraint, включив в него поля from и to. Автоматически создаваемые Django таблицы для связей «многие ко многим» содержат такое ограничение.

Для промежуточной модели существуют некоторые ограничения:

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

Вместо этого можно использовать отложенную ссылку на связанную модель в виде строки формата "app_label.ModelName". В этом случае импортировать связанную модель не требуется. Например:

from django.db import models


class Restaurant(models.Model):
    # ...
    zip_code = models.ForeignKey(
        "geography.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, перейти от объекта Place к объекту Restaurant можно с помощью имени модели в нижнем регистре:

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

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

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

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

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

Meta и наследование с несколькими таблицами

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Прокси-модели

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

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

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

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

from django.db import models


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


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

    def do_something(self):
        # ...
        pass

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

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

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

class OrderedPerson(Person):
    class Meta:
        ordering = ["last_name"]
        proxy = True

Обычные запросы Person теперь не будут отсортированы, а запросы OrderedPerson будут сортироваться по last_name.

Прокси-модели наследуют атрибуты Meta так же, как обычные модели.

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

Нельзя заставить Django возвращать, например, объект MyPerson при запросе объектов Person. QuerySet объектов 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.

Например, если в каталоге models у вас есть organic.py и synthetic.py:

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

Spec-Zone.ru

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