Spec-Zone.ru › Django 4.0

Модели

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

Основы:

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

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

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

from django.db import models

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

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

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

CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

Некоторые технические замечания:

  • Имя таблицы, myapp_person, автоматически выводится из метаданных модели, но может быть переопределено. Дополнительные сведения см. в Именах таблиц.
  • Поле id добавляется автоматически, но это поведение можно переопределить. Дополнительные сведения см. в Автоматические поля первичного ключа.
  • SQL-запрос CREATE TABLE в этом примере отформатирован с использованием синтаксиса PostgreSQL, но следует отметить, что Django использует SQL, настроенный на указанный в вашем файле настроек файле настроек бэкэнд базы данных.

Использование моделей

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

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

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

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

Примечание

При каждом изменении порядка choices создаётся новая миграция.

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

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

from django.db import models

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

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

from django.db import models

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

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

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

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

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

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

from django.db import models

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

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

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

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

id = models.BigAutoField(primary_key=True)

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

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

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

В более старых версиях автоматически созданные поля первичного ключа всегда были AutoField.

Поля с описательными именами

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

В этом примере описательное имя — "person's first name":

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

В этом примере описательное имя — "first name":

first_name = models.CharField(max_length=30)

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

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

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

Связи

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

Связи «многие к одному»

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

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

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

from django.db import models

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

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

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

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

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

См. также

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

Для получения подробной информации об обращении к связанным объектам, см. пример Обратного следования по связям.

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

Связи «многие ко многим»

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

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

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

from django.db import models

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

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

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

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

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

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

См. также

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

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

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

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

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

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

from django.db import models

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

    def __str__(self):
        return self.name

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

    def __str__(self):
        return self.name

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

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

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

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

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

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

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

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

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

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

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

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

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

После того, как вы установили связи «многие ко многим», вы можете выполнить запросы. Как и в обычных отношениях «многие ко многим», вы можете выполнять запросы, используя атрибуты модели «многие ко многим»:

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

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

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

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

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

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

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

Связи «один к одному»

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

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

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

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

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

См. также

См. пример модели «один к одному» для полного примера.

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

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

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

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

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

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

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

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

  1. Имя поля не может быть зарезервированным словом Python, так как это приведет к синтаксической ошибке Python. Например:

    class Example(models.Model):
        pass = models.IntegerField() # 'pass' is a reserved word!
    
  2. Имя поля не может содержать более одного подчеркивания подряд, из-за работы синтаксиса поиска запросов Django. Например:

    class Example(models.Model):
        foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
    
  3. Имя поля не может заканчиваться подчеркиванием по аналогичным причинам.

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

SQL-зарезервированные слова, такие как join, where или select, разрешены в качестве имён полей модели, потому что Django экранирует все имена таблиц и столбцов базы данных в каждом запросе к SQL. Он использует синтаксис цитирования вашей конкретной СУБД.

Пользовательские типы полей

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

Meta опции

Установите метаданные вашей модели, используя внутренний class Meta, как показано ниже:

from django.db import models

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

Метаданные модели — это «все, что не является полем», такие как параметры сортировки (ordering), имя таблицы базы данных (db_table) или удобочитаемые единственное и множественное числа имен (verbose_name и verbose_name_plural). Ни один из них не является обязательным, и добавление class Meta к модели полностью необязательно.

Полный список всех возможных Meta параметров можно найти в справочнике по параметрам модели.

Атрибуты модели

objects
Наиболее важным атрибутом модели является Manager. Это интерфейс, через который операции запросов к базе данных предоставляются моделям Django и используется для получения экземпляров из базы данных. Если не определён пользовательский Manager, то имя по умолчанию — objects. Менеджеры доступны только через классы моделей, а не через экземпляры моделей.

Методы модели

Определите пользовательские методы в модели, чтобы добавить пользовательскую функциональность на уровне «строки» к вашим объектам. В то время как методы Manager предназначены для действий «по всей таблице», методы модели должны действовать на конкретный экземпляр модели.

Это ценный метод для сохранения бизнес-логики в одном месте — в модели.

Например, эта модель имеет несколько пользовательских методов:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

    @property
    def full_name(self):
        "Returns the person's full name."
        return '%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, сохраняя при этом создание только одной таблицы базы данных на дочернюю модель на уровне базы данных.

END_OF_DOCUMENT_MARKER

Meta наследование

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

from django.db import models

class CommonInfo(models.Model):
    # ...
    class Meta:
        abstract = True
        ordering = ['name']

class Student(CommonInfo):
    # ...
    class Meta(CommonInfo.Meta):
        db_table = 'student_info'

Django вносит одно изменение в класс Meta абстрактного базового класса: перед установкой атрибута Meta он устанавливает abstract=False. Это означает, что дочерние классы абстрактных базовых классов не автоматически становятся абстрактными классами. Чтобы сделать абстрактный базовый класс, который наследует от другого абстрактного базового класса, необходимо явно установить abstract=True у дочернего класса.

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

Из-за того, как работает наследование в Python, если дочерний класс наследует от нескольких абстрактных базовых классов, только параметры Meta первого перечисленного класса будут унаследованы по умолчанию. Чтобы унаследовать параметры Meta от нескольких абстрактных базовых классов, вы должны явно объявить наследование Meta. Например:

from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True
        ordering = ['name']

class Unmanaged(models.Model):
    class Meta:
        abstract = True
        managed = False

class Student(CommonInfo, Unmanaged):
    home_group = models.CharField(max_length=5)

    class Meta(CommonInfo.Meta, Unmanaged.Meta):
        pass

Будьте внимательны с related_name и related_query_name

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

Чтобы обойти эту проблему, когда вы используете related_name или related_query_name в абстрактном базовом классе, часть значения должна содержать '%(app_label)s' и '%(class)s'.

  • '%(class)s' заменяется на имя дочернего класса в нижнем регистре, в котором используется поле.
  • '%(app_label)s' заменяется на имя приложения в нижнем регистре, в котором содержится дочерний класс. Каждое имя установленного приложения должно быть уникальным, а имена классов моделей в каждом приложении также должны быть уникальными, поэтому результирующее имя будет отличаться.

Например, для приложения common/models.py:

from django.db import models

class Base(models.Model):
    m2m = models.ManyToManyField(
        OtherModel,
        related_name="%(app_label)s_%(class)s_related",
        related_query_name="%(app_label)s_%(class)ss",
    )

    class Meta:
        abstract = True

class ChildA(Base):
    pass

class ChildB(Base):
    pass

А также для другого приложения rare/models.py:

from common.models import Base

class ChildB(Base):
    pass

Обратное имя поля common.ChildA.m2m будет common_childa_related, а обратное имя запроса — common_childas. Обратное имя поля common.ChildB.m2m будет common_childb_related, а обратное имя запроса — common_childbs. Наконец, обратное имя поля rare.ChildB.m2m будет rare_childb_related, а обратное имя запроса — rare_childbs. Вам решать, как использовать части '%(class)s' и '%(app_label)s' для построения обратного имени или имени запроса, но если вы забудете это сделать, Django выведет ошибки при выполнении проверок системы (или при запуске migrate).

Если вы не укажете атрибут related_name для поля в абстрактном базовом классе, по умолчанию обратное имя будет именем дочернего класса с добавленной строкой '_set', так же, как если бы вы объявили поле непосредственно в дочернем классе. Например, в приведенном выше коде, если атрибут related_name был опущен, обратное имя поля m2m было бы childa_set в случае ChildA и childb_set для поля ChildB.

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

Второй тип наследования моделей, поддерживаемый Django, — это когда каждая модель в иерархии является моделью сама по себе. Каждая модель соответствует своей собственной таблице базы данных и может быть запрошена и создана индивидуально. Отношение наследования устанавливает связи между дочерней моделью и каждым её родителем (через автоматически созданный OneToOneField). Например:

from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

Все поля Place также будут доступны в Restaurant, хотя данные будут храниться в другой таблице базы данных. Следовательно, оба варианта возможны:

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

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

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

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

Автоматически созданный OneToOneField в Restaurant, который связывает его с Place, выглядит так:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

from django.db import models

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

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

    def do_something(self):
        # ...
        pass

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

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

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

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

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

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

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

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

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

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

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

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

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

from django.db import models

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

class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

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

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

    class Meta:
        abstract = True

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

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

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

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

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

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

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

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

Наследование от нескольких моделей

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

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

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

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

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

class BookReview(Book, Article):
    pass

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

class Piece(models.Model):
    pass

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

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

class BookReview(Book, Article):
    pass

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

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

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

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

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

Примечание

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

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

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

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

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

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

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

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

Для этого создайте пакет models. Удалите models.py и создайте каталог myapp/models/ с файлом __init__.py и файлами для хранения ваших моделей. Модели необходимо импортировать в файл __init__.py.

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

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

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

См. также

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

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

Spec-Zone.ru

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