Spec-Zone.ru › Django 1.9

Модели

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

Основы:

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

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

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

from django.db import models

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

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

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

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

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

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

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

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

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

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

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

Поля

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

Пример:

from django.db import models

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

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

Типы полей

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

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

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

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

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

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

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

Если True, поле можно оставить пустым. По умолчанию False.

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

choices

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

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

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

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

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

Связи

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

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

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

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

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

from django.db import models

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

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

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

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

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

См. также

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

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

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

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

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

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

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

from django.db import models

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

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

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

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

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

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

См. также

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

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

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

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

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

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

from django.db import models

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

    def __str__(self):              # __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, 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()
[<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,
        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"

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

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

__unicode__() (Python 2)
Эквивалент метода __str__() для Python 2.
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) будут использовать одну и ту же таблицу базы данных, что почти наверняка не то, что вам нужно.

Будьте осторожны с 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.

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

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

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

Итак, общие правила таковы:

  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:

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, если вы переопределите любое поле модели в любой родительской модели.

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

Команда 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/1.9/topics/db/models/

Spec-Zone.ru

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