Spec-Zone.ru › Django 1.8

Модели

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

Основы:

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

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

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

from django.db import models

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

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

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

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

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

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

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

После определения моделей вам необходимо сообщить Django, что вы собираетесь использовать эти модели. Сделайте это, отредактировав файл настроек и изменив настройку INSTALLED_APPS, добавив в неё имя модуля, содержащего ваши models.py.

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

INSTALLED_APPS = (
    #...
    'myapp',
    #...
)

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

Поля

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

Пример:

from django.db import models

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

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

Типы полей

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

  • Тип столбца базы данных (например, INTEGER, VARCHAR).
  • Предпочтительный виджет 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'),
)

Первый элемент в каждой паре — это значение, которое будет храниться в базе данных, второй — это значение, которое будет отображаться виджетом формы по умолчанию или в поле ModelChoiceField. Для получения отображаемого значения поля вариантов для объекта модели можно использовать метод 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)
['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, verbose_name="the related poll")
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(Place, 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)
    # ...

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

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

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

См. также

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):              # __unicode__ on Python 2
        return self.name

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

    def __str__(self):              # __unicode__ on Python 2
        return self.name

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

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

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

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

В Django 1.6 и ранее промежуточные модели, содержащие более одного внешнего ключа к любому из шаблонов, участвующих в отношении "многие ко многим", были запрещены.

Теперь, когда вы настроили 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()
[<Person: Ringo Starr>]
>>> ringo.group_set.all()
[<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()
[<Person: Ringo Starr>, <Person: Paul McCartney>]

В отличие от обычных полей "многие ко многим", вы не можете использовать add, create, или присваивание (т.е., beatles.members = [...]) для создания отношений:

# THIS WILL NOT WORK
>>> beatles.members.add(john)
# NEITHER WILL THIS
>>> beatles.members.create(name="George Harrison")
# AND NEITHER WILL THIS
>>> beatles.members = [john, paul, ringo, george]

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

Метод remove() отключен по аналогичным причинам. Однако, метод clear() может использоваться для удаления всех отношений "многие ко многим" для экземпляра:

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

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

# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith='Paul')
[<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))
[<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)

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

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"

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

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

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

__str__() (Python 3)
Эквивалент Python 3 для __unicode__().
__unicode__() (Python 2)

Магический метод 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(Blog, self).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(Blog, self).save(*args, **kwargs) # Call the "real" save() method.

Важно помнить, что нужно вызвать метод суперкласса — это та часть с super(Blog, self).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. Эта модель тогда не будет использоваться для создания таблицы базы данных. Вместо этого, когда она используется в качестве базового класса для других моделей, ее поля добавляются к полям дочернего класса. Недопустимо иметь поля в абстрактном базовом классе с одинаковыми именами, что и в дочернем классе (Django выведет исключение).

Пример:

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

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

id="abstract-related-name">Будьте осторожны с related_name

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

Чтобы обойти эту проблему, когда вы используете related_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")

    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.ChildB.m2m будет common_childb_related, а обратное имя поля rare.ChildB.m2m будет rare_childb_related. Вам решать, как использовать части '%(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.

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.

Наборы запросов по-прежнему возвращают запрошенную модель

Нет способа заставить 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.managed=False. Вы могли бы, при тщательной настройке Meta.db_table, создать не управляемую модель, которая дублировала бы существующую модель, и добавить к ней Python-методы. Однако это было бы очень повторяющимся и хрупким решением, так как вам нужно будет синхронизировать обе копии, если вы внесёте какие-либо изменения.

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

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

Таким образом, общие правила:

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

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

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

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

До Django 1.7 наследование от нескольких моделей с полем первичного ключа id не вызывало ошибки, но могло привести к потере данных. Например, рассмотрите эти модели (которые больше не проходят проверку из-за конфликтующих полей id):

class Article(models.Model):
    headline = models.CharField(max_length=50)
    body = models.TextField()

class Book(models.Model):
    title = models.CharField(max_length=50)

class BookReview(Book, Article):
    pass

Этот фрагмент демонстрирует, как создание дочернего объекта перезаписывало значение ранее созданного родительского объекта:

>>> article = Article.objects.create(headline='Some piece of news.')
>>> review = BookReview.objects.create(
...     headline='Review of Little Red Riding Hood.',
...     title='Little Red Riding Hood')
>>>
>>> assert Article.objects.get(pk=article.pk).headline == article.headline
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AssertionError
>>> # the "Some piece of news." headline has been overwritten.
>>> Article.objects.get(pk=article.pk).headline
'Review of Little Red Riding Hood.'

Для правильного использования множественного наследования вы можете использовать явное поле 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:

class Piece(models.Model):
    pass

class Article(Piece):
    ...

class Book(Piece):
    ...

class BookReview(Book, Article):
    pass

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

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

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

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

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

См. также

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

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

Spec-Zone.ru

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