Spec-Zone.ru › Django 3.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" serial NOT NULL PRIMARY KEY,
    "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.choices, max_length=10)

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

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

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

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

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

from django.db import models

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

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

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

По умолчанию Django добавляет к каждой модели следующее поле:

id = models.AutoField(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), так как более естественно думать о пицце, имеющей соусы, чем о соусе, находящемся на нескольких пиццах. В соответствии с указанным выше способом форма 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 '%s %s' % (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 в определениях своих методов, вам гарантируется, что ваш код автоматически будет поддерживать эти аргументы при их добавлении.

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

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

Будьте осторожны с 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. Запрос на объекты 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 primary key, вызовет ошибку. Для правильного использования множественного наследования можно использовать явное 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, если вы переопределите какое-либо поле модели в любой родительской модели.

Организация моделей в пакете

Команда manage.py startapp создает структуру приложения, которая включает файл models.py. Если у вас много моделей, организация их в отдельных файлах может быть полезной.

END_OF_DOCUMENT_MARKER

Для этого создайте пакет 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/3.0/topics/db/models/

Spec-Zone.ru

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