Spec-Zone.ru › Django 2.1

Модели

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

Основы:

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

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

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

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

Поля

Самая важная часть модели (и единственная обязательная часть) — список полей базы данных, которые она определяет. Поля задаются атрибутами класса. Будьте внимательны, не выбирайте имена полей, конфликтующие с API моделей 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

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

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

YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    ('GR', 'Graduate'),
)

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

Для получения значения для отображения для экземпляра модели с полем 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'
default
Значение по умолчанию для поля. Может быть значением или вызываемым объектом. Если вызываемым объектом, то он будет вызываться каждый раз при создании нового объекта.
help_text
Дополнительный текст справки для отображения с виджетом формы. Полезно для документации, даже если ваше поле не используется в форме.
primary_key

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

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

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

from django.db import models

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

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

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

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

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 ManyToManyField ), так как более естественно думать о пицце, имеющей добавки, чем о добавке, присутствующей на нескольких пиццах. Таким образом, форма Pizza позволит пользователям выбрать добавки.

См. также

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

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

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

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

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

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

from django.db import models

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

    def __str__(self):
        return self.name

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

    def __str__(self):
        return self.name

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

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

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

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

Теперь, когда вы настроили свою 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() для создания связей:

>>> # The following statements will not work
>>> beatles.members.add(john)
>>> beatles.members.create(name="George Harrison")
>>> beatles.members.set([john, paul, ringo, george])

Почему? Вы не можете просто создать связь между Person и Group — вам нужно указать все детали связи, требуемые моделью Membership. Простые вызовы add, create и присваивания не предоставляют способа указать эти дополнительные детали. В результате они отключены для связей «многие ко многим», использующих промежуточную модель. Единственный способ создания такого типа связи — создать экземпляры промежуточной модели.

Метод remove() отключён по аналогичным причинам. Например, если настраиваемая таблица через, определённая промежуточной моделью, не обеспечивает уникальность пары (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 will not work because it cannot tell which membership to remove
>>> beatles.members.remove(ringo)

Однако, метод 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!
    

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

from django.db import models

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

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

    def do_something(self):
        # ...
        pass

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

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

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

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

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

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

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

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

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

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

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

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

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

from django.db import models

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

class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

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

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

    class Meta:
        abstract = True

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

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

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

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

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

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

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

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

Множественное наследование моделей

Как и при множественном наследовании в Python, Django-модель может наследоваться от нескольких родительских моделей. Имейте в виду, что применяются обычные правила разрешения имен Python. Первый базовый класс, в котором появляется определенное имя (например, Meta), будет использоваться; например, это означает, что если несколько родителей содержат класс Meta, будет использоваться только первый, а все остальные будут проигнорированы.

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

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

class Article(models.Model):
    article_id = models.AutoField(primary_key=True)
    ...

class Book(models.Model):
    book_id = models.AutoField(primary_key=True)
    ...

class BookReview(Book, Article):
    pass

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

class Piece(models.Model):
    pass

class Article(Piece):
    article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
    ...

class Book(Piece):
    book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
    ...

class BookReview(Book, Article):
    pass

Скрытие имен полей запрещено

В обычном наследовании классов Python допускается переопределение любого атрибута родительского класса дочерним классом. В Django это обычно не допускается для полей модели. Если у базового класса неабстрактной модели есть поле под названием author, вы не можете создать другое поле модели или определить атрибут под названием author в любом классе, который наследуется от этого базового класса.

Это ограничение не применяется к полям модели, унаследованным от абстрактной модели. Такие поля можно переопределить другим полем или значением, или удалить, установив field_name = None.

Предупреждение

Менеджеры моделей наследуются от абстрактных базовых классов. Переопределение унаследованного поля, на которое ссылается унаследованный Manager, может привести к скрытым ошибкам. См. настраиваемые менеджеры и наследование моделей.

Примечание

Некоторые поля определяют дополнительные атрибуты в модели, например, ForeignKey определяет дополнительный атрибут с добавлением _id к имени поля, а также related_name и related_query_name в модели-получателе.

Эти дополнительные атрибуты нельзя переопределять, если не изменить или удалить поле, которое их определяет.

Переопределение полей в родительской модели приводит к трудностям при инициализации новых экземпляров (указание, какое поле инициализируется в Model.__init__) и сериализации. Это функции, с которыми обычное наследование классов Python не должно сталкиваться таким же образом, поэтому различие между наследованием моделей Django и наследованием классов Python не произвольно.

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

Django поднимет FieldError, если вы переопределите любое поле модели в какой-либо родительской модели.

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

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

Для этого создайте пакет models. Удалите models.py и создайте директорию myapp/models/, содержащую файл __init__.py и файлы для хранения ваших моделей. Вы должны импортировать модели в файл __init__.py.

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

myapp/models/__init__.py
from .organic import Person
from .synthetic import Robot

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

См. также

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

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

Spec-Zone.ru

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