Модели

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

Основы:

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

Модель 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 добавлено автоматически, но его также можно переопределить. Подробнее Первичный ключ по умолчанию.

  • CREATE TABLE SQL в этом примере соответствует синтаксису PostgreSQL, но стоит учесть что Django использует синтаксис SQL соответственно настройкам базы данных в файле настроек.

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

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

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

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

После добавления приложения в INSTALLED_APPS, не забудьте выполнить manage.py syncdb.

Поля

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

  • Виджет используемый при создании поля формы (например: <input type="text">, <select>).

  • Минимальные правила проверки данных, используемые в интерфейсе администратора и для автоматического создания формы.

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

Настройка полей

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

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

null

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

blank

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

Это не то же что и null. null относится к базе данных, blank - к проверке данных. Если поле содержит blank=True, форма позволить передать пустое значение. При blank=False - поле обязательно.

choices

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

Список значений выглядит:

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
u'L'
>>> p.get_shirt_size_display()
u'Large'
default

Значение по умолчанию для этого поля. Это может быть значение или функция. Если это функция - она будет вызвана при каждом создании объекта.

help_text

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

primary_key

При ``True``поле будет первичным ключом.

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

unique

При True поле будет уникальным.

Это краткое описание самых используемых аргументов. Полный список можно найти на странице описания аргументов поля модели.

Первичный ключ по умолчанию

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

id = models.AutoField(primary_key=True)

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

Для его переопределения, просто укажите primary_key=True для одного из полей. При этом Django не добавит поле 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 первым аргументов принимает класс модели, по-этому используется keyword аргумент 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")

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

Связи

Основное преимущество реляционных баз данных - возможность добавлять связи для таблиц. 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 (вместо того, чтобы добавить поле pizzas типа ManyToManyField в модель Topping), потому что обычно думают о пицце с ингредиентами, а не об ингредиентах в различных пиццах. В примере выше, форма для Pizza позволит пользователям редактировать ингредиенты для пиццы.

См.также

Примеры использования связи многое-ко-многому можно найти в соответствующем разделе

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

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

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

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

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

from django.db import models

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

    # On Python 3: def __str__(self):
    def __unicode__(self):
        return self.name

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

    # On Python 3: def __str__(self):
    def __unicode__(self):
        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)

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

Есть несколько ограничений для промежуточной модели:

  • Промежуточная модель должна содержать один и только один внешний ключ на связанную модель (в нашем примере это Person). При нескольких ключах будет вызвано исключение.

  • Промежуточная модель должна содержать один и только один внешний ключ на модель-источник (в нашем примере это Group). При нескольких ключах будет вызвано исключение.

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

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

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

# 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
u'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
u'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, т.к. это приведет к синтаксической ошибке. Например:

    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 запроса. Используются “кавычки”(quoting syntax) базы данных, которую вы используете.

Собственные типы полей

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

Мета-настройки

Дополнительные настройки для модели можно определить через 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), или human-readable название в единственной и множественной форме(verbose_name и verbose_name_plural). Все они не обязательны и добавлять class Meta тоже не обязательно.

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

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

Для добавления функционала работы с экземпляром модели(“row-level” functionality), необходимо просто добавить метод в модель. В то время, как методы 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()
    address = models.CharField(max_length=100)
    city = models.CharField(max_length=50)
    state = models.CharField(max_length=2)      # yes, this is America-centric

    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 is_midwestern(self):
        "Returns True if this person is from the Midwest."
        return self.state in ('IL', 'WI', 'MI', 'IN', 'OH', 'IA', 'MO')

    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)

Последний метод в примере - свойство(property).

Раздел о моделях содержит полный список методов автоматически добавляемых в модель. Вы можете переопределить большинство из них – смотрите `overriding predefined model methods`_, – но есть методы, которые вы чаще всего определите для каждой модели:

__str__() (Python 3)

Python 3 аналог __unicode__().

__unicode__() (Python 2)

“Волшебный метод” Python, который возвращает unicode “представление” объекта. Это то, что 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 можно быть уверенным, что все аргументы будут приняты.

Overridden model methods are not called on bulk operations

Учтите что метод delete() не вызывается при удалении объектов через QuerySet. Для гарантированного выполнения действий после удаления объекта используйте сигналы pre_delete и/или post_delete.

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

Выполнение SQL запросов

Запросы на чистом SQL лучше выполнять в методе модели. Подробнее о SQL запросах можно прочитать в разделе о запросах на чистом SQL.

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

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

Существует три вида наследования моделей в Django.

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

  2. Если родительская модель независимая(возможно из другого приложения) и должна храниться в отдельной таблице, Multi-table наследование то, что вам нужно.

  3. Если же вы хотите переопределить поведение модели на уровне Python, не меня структуры базы данных, вы можете использовать Proxy-модели.

Абстрактные модели

Абстрактные модель удобны при определении общих, для нескольких моделей, полей. Вы создаете базовую модель и добавляете 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 для абстрактной модели: перед добавлением в модель изменяет атрибут abstract=False. Это означает что дочерняя модель, наследуя Meta, не становится сама абстрактной моделью. Конечно вы можете создать абстрактную модель, которая наследуется от другой абстрактной модели. Вам только нужно определить атрибут abstract=True.

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

Multi-table наследование

Это второй тип наследования в 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()
    serves_pizza = models.BooleanField()

Все поля 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 и multi-table наследование

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

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

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

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

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

Т.к. multi-table наследование использует OneToOneField для связи родительской и дочерней модели, возможно из родительской модели получить дочернюю, как это показано в примере выше. Используется название по умолчанию для атрибута related_name в ForeignKey и ManyToManyField. Если вы используете такие связи на дочернюю модель с аналогичным предком, вы должны определить related_name для каждого такого поля. Иначе Django вызовет исключение при выполнении команды validate и syncdb.

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

class Supplier(Place):
    # Must specify related_name on all relations.
    customers = models.ManyToManyField(Restaurant, related_name='provider')

Proxy-модели

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

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

Proxy-модели создаются так же, как и обычная модель. Указать что это proxy-модель можно установив атрибут 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>

Вы также можете использовать proxy модель для определения различной сортировки по умолчанию. Модель Person не имеет сортировки по умолчанию (намеренно; сортировка трудоемкая операция, и мы не хотим ее использовать при каждом доступе к данным о пользователях). Возможно вы хотите сортировать по полю last_name при использовании proxy-модели. Это просто:

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

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

QuerySets возвращает объекты запрашиваемого типа

Нет способа указать Django возвращать объекты модели MyPerson при запросе через модель Person. QuerySet для модели Person вернет объекты этого типа. Стоит помнить, что код, который использует модель Person, будет использовать ее независимо от добавления proxy-модели. Вы не можете полностью заменить модель Person (или любую другую) всюду, где она используется.

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

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

Proxy-модель наследует атрибуты класса Meta родительской не абстрактной модели, которые она не переопределяет.

Менеджер proxy-модели

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

Используя пример выше, вы можете переопределить менеджер по умолчанию, используемый для получения данных модели 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

Скорее всего вам это не понадобится, но если все таки понадобится, знайте, что это возможно.

Разница между proxy наследованием и неуправляемой(unmanaged) моделью

Proxy может выглядеть так же как и неуправляемая модель, используя атрибут managed класса Meta модели. Оба варианта не совсем то же самое, и это стоит учесть, при выборе что вы должны использовать.

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

Другим отличием, которое больше относится к proxy-модели - это как Django работает с этими моделями. Proxy-модели предназначены для использования так же, как и оригинальная модель. Они наследуют менеджеры оригинальной модели, включая менеджер по умолчанию. При multi-table наследовании дочерние модели не наследуют менеджеры от родительской т.к. они не всегда будут работать корректно, учитывая, что есть дополнительные поля. Документация о менеджерах содержит больше информации об этом.

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

Основные правила:

  1. Если вы хотите отобразить существующую модель или таблицу в базе данных без использования всех колонок таблицы, используйте Meta.managed=False. Также это очень полезно для использования таблиц базы данных, которые управляются не Django.

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

Множественное наследование

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

В большинстве случаев вам не нужно будет использовать множественное наследование. В основном множественное наследование используют для “mix-in” классов: добавление дополнительных полей и методов для каждой модели унаследованной от mix-in класса. Старайтесь содержать иерархию наследования настолько простой и понятной, насколько это возможно, чтобы не возникало проблем с определением, откуда взялась та или другая информация.

Переопределение полей запрещено

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

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

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

Django вызовет исключение FieldError, если вы переопределите поле родительской модели.