Spec-Zone.ru › Django 2.2

Модели

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

Основы:

  • Каждая модель — это класс Python, который наследуется от django.db.models.Model.
  • Каждое свойство модели представляет поле базы данных.
  • При этом Django предоставляет автоматически сгенерированный API для доступа к базе данных; см. Выполнение запросов.

Быстрый пример

Эта модель примера определяет Person, которая имеет first_name и last_name:

from django.db import models

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

first_name и last_name являются полями модели. Каждое поле задается как атрибут класса, и каждый атрибут отображается на столбец базы данных.

Вышеупомянутая модель Person создаст таблицу базы данных примерно такого вида:

CREATE TABLE myapp_person (
    "id" 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

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

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

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'
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() для создания взаимосвязей, при условии, что вы укажете 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,
)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

from django.db import models

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

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

    def do_something(self):
        # ...
        pass

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

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

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

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

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

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

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

Spec-Zone.ru

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