Spec-Zone.ru › Django 2.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 моделей моделей, например, clean, save, или delete.

Пример:

from django.db import models

class Musician(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    instrument = models.CharField(max_length=100)

class Album(models.Model):
    artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    release_date = models.DateField()
    num_stars = models.IntegerField()

Типы полей

Каждое поле в вашей модели должно быть экземпляром соответствующего класса Field. Django использует типы классов полей для определения нескольких вещей:

  • Тип столбца, который сообщает базе данных о типе данных для хранения (например, INTEGER, VARCHAR, TEXT).
  • Стандартный HTML-виджет для отображения поля формы (например, <input type="text">, <select>).
  • Минимальные требования к проверке, используемые в админ-панели Django и в автоматически генерируемых формах.

Django поставляется с десятками встроенных типов полей; полный список можно найти в справочнике по полям модели. Вы можете легко написать собственные поля, если встроенные типы Django не подходят; см. Написание пользовательских полей модели.

Параметры полей

Каждое поле принимает определенный набор аргументов, специфичных для поля (документированы в справочнике по полям модели). Например, CharField (и его подклассы) требуют аргумента max_length, который задает размер поля базы данных VARCHAR для хранения данных.

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

null
Если True, Django будет хранить пустые значения как NULL в базе данных. По умолчанию False.
blank

Если True, поле допускает пустое значение. По умолчанию False.

Обратите внимание, что это отличается от null. null связано только с базой данных, а blank — с проверкой. Если у поля есть blank=True, проверка формы позволит вводить пустые значения. Если у поля есть blank=False, поле будет обязательным.

choices

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

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

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

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

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

from django.db import models

class Person(models.Model):
    SHIRT_SIZES = (
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
    )
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
default
Значение по умолчанию для поля. Может быть значением или вызываемым объектом. Если вызываемым, он будет вызываться каждый раз при создании нового объекта.
help_text
Дополнительный текст «подсказки» для отображения с виджетом формы. Он полезен для документирования, даже если поле не используется в форме.
primary_key

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

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

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

from django.db import models

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

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

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

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

id = models.AutoField(primary_key=True)

Это автоинкрементный первичный ключ.

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

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

Имена полей с описаниями

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

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

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

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

first_name = models.CharField(max_length=30)

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

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

Принято не делать заглавную букву в имени с описанием verbose_name. Django автоматически сделает заглавную букву в нужном месте.

Связи

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

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

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

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

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

from django.db import models

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

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

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

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

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

См. также

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

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

Для примеров кода, см. пример Пример модели многих-к-одному.

Связи многие-ко-многим

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

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

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

from django.db import models

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

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

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

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

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

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

См. также

См. пример Пример модели многих-ко-многим для полного примера.

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

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

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

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

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

from django.db import models

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

    def __str__(self):
        return self.name

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

    def __str__(self):
        return self.name

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

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

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

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

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

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

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

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

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

Метод remove() отключён по аналогичным причинам. Например, если настроенная таблица через промежуточный шаблон не обеспечивает уникальность пары (model1, model2), вызов remove() не предоставит достаточно информации для удаления конкретного экземпляра промежуточного шаблона:

>>> Membership.objects.create(person=ringo, group=beatles,
...     date_joined=date(1968, 9, 4),
...     invite_reason="You've been gone for a month and we miss you.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This will not work because it cannot tell which membership to remove
>>> beatles.members.remove(ringo)

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

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

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

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

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

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

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

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

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

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

Связи один-к-одному

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

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

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

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

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

См. также

См. пример шаблона связи один-к-одному для полного примера.

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

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

Шаблоны в разных файлах

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

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

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

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

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

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

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

    class Example(models.Model):
        foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
    

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

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

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

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

Meta опции

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

from django.db import models

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

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

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

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

Атрибуты шаблонов

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

Методы шаблонов

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

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

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

from django.db import models

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

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

    @property
    def full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)

Последний метод в этом примере — свойство.

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

__str__()

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

Вы всегда захотите определить этот метод; по умолчанию он не очень полезен.

get_absolute_url()

Это сообщает Django, как вычислить URL для объекта. Django использует это в своем админ-интерфейсе и всякий раз, когда ему нужно определить URL для объекта.

Любой объект, у которого есть URL, однозначно его идентифицирующий, должен определять этот метод.

Переопределение предопределенных методов модели

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

Вы можете свободно переопределять эти методы (и любые другие методы модели) для изменения поведения.

Классический случай использования переопределения встроенных методов — если вы хотите, чтобы что-то происходило всякий раз, когда вы сохраняете объект. Например (см. save() для документации параметров, которые он принимает):

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        do_something()
        super().save(*args, **kwargs)  # Call the "real" save() method.
        do_something_else()

Вы также можете предотвратить сохранение:

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        if self.name == "Yoko Ono's blog":
            return # Yoko shall never have her own blog!
        else:
            super().save(*args, **kwargs)  # Call the "real" save() method.

Важно помнить о вызове метода суперкласса — это то, что super().save(*args, **kwargs) означает — для обеспечения того, чтобы объект все равно сохранялся в базе данных. Если вы забудете вызвать метод суперкласса, стандартное поведение не произойдет, и база данных не будет затронута.

Также важно передавать аргументы, которые могут быть переданы методу модели — именно это делает *args, **kwargs . Время от времени Django расширяет возможности встроенных методов модели, добавляя новые аргументы. Если вы используете *args, **kwargs в своих определениях методов, вы гарантируете, что ваш код автоматически будет поддерживать эти аргументы при их добавлении.

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

Обратите внимание, что метод delete() для объекта не обязательно вызывается при удалении объектов массово с помощью QuerySet или в результате cascading delete. Чтобы обеспечить выполнение настроенной логики удаления, вы можете использовать сигналы pre_delete и/или post_delete.

К сожалению, нет решения, когда creating или updating объекты массово, так как ни один из save(), pre_save и post_save не вызываются.

Выполнение пользовательского SQL

Еще один распространенный шаблон — написание пользовательских SQL-заявлений в методах модели и методах уровня модуля. Подробнее об использовании необработанного SQL см. в документации по использованию необработанного SQL.

Наследование моделей

Наследование моделей в Django работает почти идентично тому, как работает обычное наследование классов в Python, но основные принципы в начале страницы все равно следует соблюдать. Это означает, что базовый класс должен быть подклассом django.db.models.Model.

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

Существует три стиля наследования, возможные в Django.

  1. Часто вам нужно будет просто использовать родительский класс для хранения информации, которую вам не нужно вводить для каждой дочерней модели. Этот класс никогда не будет использоваться изолированно, поэтому вам нужны абстрактные базовые классы.
  2. Если вы наследуете существующую модель (возможно, что-то из другого приложения) и хотите, чтобы каждая модель имела собственную таблицу базы данных, наследование с несколькими таблицами — это правильный путь.
  3. Наконец, если вы хотите изменить только поведение модели на уровне Python, не изменяя поля моделей, вы можете использовать модели-прокси.

Абстрактные базовые классы

Абстрактные базовые классы полезны, когда вы хотите поместить некоторую общую информацию в ряд других моделей. Вы пишете базовый класс и помещаете abstract=True в класс Meta. Эта модель не будет использоваться для создания таблицы базы данных. Вместо этого, когда она используется в качестве базового класса для других моделей, ее поля добавляются к полям дочернего класса.

Пример:

from django.db import models

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

    class Meta:
        abstract = True

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

Модель Student будет иметь три поля: name, age и home_group. Модель CommonInfo не может быть использована как обычная модель Django, так как это абстрактный базовый класс. Она не генерирует таблицу базы данных или менеджер и не может быть создана или сохранена напрямую.

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

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

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

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

from django.db import models

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

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

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

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

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

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

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

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

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

from django.db import models

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

    class Meta:
        abstract = True

class ChildA(Base):
    pass

class ChildB(Base):
    pass

Вместе с другим приложением rare/models.py:

from common.models import Base

class ChildB(Base):
    pass

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

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

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

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

from django.db import models

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Spec-Zone.ru

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