Модели

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

Основы:

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

  • The column type, which tells the database what kind of data to store (e.g. INTEGER, VARCHAR, TEXT).
  • Виджет используемый при создании поля формы (например: <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

A sequence of 2-tuples to use as choices for this field. If this is given, the default form widget will be a select box instead of the standard text field and will limit choices to the choices given.

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

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

Примечание

A new migration is created each time the order of choices changes.

The first element in each tuple is the value that will be stored in the database. The second element is displayed by the field’s form widget.

Given a model instance, the display value for a field with choices can be accessed using the get_FOO_display() method. For example:

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'

You can also use enumeration classes to define choices in a concise way:

from django.db import models

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

Further examples are available in the model field reference.

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

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

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

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

from django.db import models

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

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

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

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

id = models.AutoField(primary_key=True)

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

If you’d like to specify a custom primary key, specify primary_key=True on one of your fields. If Django sees you’ve explicitly set Field.primary_key, it won’t add the automatic id column.

Каждая модель должна иметь хотя бы одно поле с 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,
    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",
)

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, 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)

As with ForeignKey, you can also create recursive relationships (an object with a many-to-many relationship to itself) and relationships to models not yet defined.

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

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

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

См.также

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

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

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

When you’re only dealing with many-to-many relationships such as mixing and matching pizzas and toppings, a standard ManyToManyField is all you need. However, sometimes you may need to associate data with the relationship between two models.

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

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

from django.db import models

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

    def __str__(self):
        return self.name

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

    def __str__(self):
        return self.name

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

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

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

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

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

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

You can also use add(), create(), or set() to create relationships, as long as you specify through_defaults for any required fields:

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

You may prefer to create instances of the intermediate model directly.

If the custom through table defined by the intermediate model does not enforce uniqueness on the (model1, model2) pair, allowing multiple values, the remove() call will remove all intermediate model instances:

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

The clear() method can be used to remove all many-to-many relationships for an instance:

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

Once you have established the many-to-many relationships, you can issue queries. Just as with normal many-to-many relationships, you can query using the attributes of the many-to-many-related model:

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

Вы можете использовать поле промежуточной модели в запросах:

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

Вы можете получить данные непосредственно из модели Membership:

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

Другой способ - используя обратную связь объекта модели Person:

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

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

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

Чаще всего связь одни-к-одному используется для первичного ключа для модели, которая «расширяет» другую модель.

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

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

As with ForeignKey, a recursive relationship can be defined and references to as-yet undefined models can be made.

См.также

Примеры можно найти в этом разделе.

OneToOneField fields also accept an optional parent_link argument.

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

Связь моделей из разных модулей

It’s perfectly OK to relate a model to one from another app. To do this, import the related model at the top of the file where your model is defined. Then, refer to the other model class wherever needed. For example:

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 places some restrictions on model field names:

  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!
    
  3. A field name cannot end with an underscore, for similar reasons.

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

Зарезервированные SQL слова, такие как join, where или select, можно использовать как название поля, потому что Django экранирует название таблиц и полей для каждого SQL запроса. Используются «кавычки»(quoting syntax) базы данных, которую вы используете.

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

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

Meta options

Дополнительные настройки для модели можно определить через 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 можно найти в разделе о настройках модели.

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

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

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

Для добавления функционала работы с экземпляром модели(«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()

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

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

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

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

__str__()

A Python «magic method» that returns a string representation of any object. This is what Python and Django will use whenever a model instance needs to be coerced and displayed as a plain string. Most notably, this happens when you display an object in an interactive console or in the admin.

Желательно определить этот метод, т.к. значение по умолчанию не слишком привлекательно.

get_absolute_url()

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

Каждый объект, который имеет уникальный URL должен иметь этот метод.

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

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

Вы можете переопределить эти методы и любые другие методы модели.

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

from django.db import models

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

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

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

from django.db import models

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

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

It’s important to remember to call the superclass method – that’s that super().save(*args, **kwargs) business – to ensure that the object still gets saved into the database. If you forget to call the superclass method, the default behavior won’t happen and the database won’t get touched.

Также не забывайте о аргументах, которые принимают встроенные методы – вам поможет *args и **kwargs. Django иногда использует дополнительные аргументы для методов. Используя *args, **kwargs, можно быть уверенным, что все аргументы будут приняты.

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

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

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

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

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

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

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

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

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

  1. Чаще всего вы будете использовать родительскую модель для хранения общих полей, чтобы не добавлять их в каждую дочернюю модель. Если вы не собираетесь использовать его как независимую модель – Абстрактные модели то, что вам нужно.
  2. Если родительская модель независимая(возможно, из другого приложения) и должна храниться в отдельной таблице, Multi-table наследование то, что вам нужно.
  3. Если же вы хотите переопределить поведение модели на уровне Python, не меняя структуры базы данных, вы можете использовать Proxy-модели.

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

Abstract base classes are useful when you want to put some common information into a number of other models. You write your base class and put abstract=True in the Meta class. This model will then not be used to create any database table. Instead, when it is used as a base class for other models, its fields will be added to those of the child class.

Например:

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

Fields inherited from abstract base classes can be overridden with another field or value, or be removed with None.

For many uses, this type of model inheritance will be exactly what you want. It provides a way to factor out common information at the Python level, while still only creating one database table per child model at the database level.

Наследование класса 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(default=False)
    serves_pizza = models.BooleanField(default=False)

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

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

If you have a Place that is also a Restaurant, you can get from the Place object to the Restaurant object by using the lowercase version of the model name:

>>> 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.

The automatically-created OneToOneField on Restaurant that links it to Place looks like this:

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

You can override that field by declaring your own OneToOneField with parent_link=True on Restaurant.

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 вызовет исключение.

Например, используя 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 для поля customersmodels.ManyToManyField(Place, 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>

You could also use a proxy model to define a different default ordering on a model. You might not always want to order the Person model, but regularly order by the last_name attribute when you use the proxy:

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

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

Proxy models inherit Meta attributes in the same way as regular models.

QuerySets still return the model that was requested

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

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

A proxy model must inherit from exactly one non-abstract model class. You can’t inherit from multiple non-abstract models as the proxy model doesn’t provide any connection between the rows in the different database tables. A proxy model can inherit from any number of abstract model classes, providing they do not define any model fields. A proxy model may also inherit from any number of proxy models that share a common non-abstract parent class.

Менеджер 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

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

Differences between proxy inheritance and unmanaged models

Proxy model inheritance might look fairly similar to creating an unmanaged model, using the managed attribute on a model’s Meta class.

With careful setting of Meta.db_table you could create an unmanaged model that shadows an existing model and adds Python methods to it. However, that would be very repetitive and fragile as you need to keep both copies synchronized if you make any changes.

On the other hand, proxy models are intended to behave exactly like the model they are proxying for. They are always in sync with the parent model since they directly inherit its fields and managers.

The general rules are:

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

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

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

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

Обратите внимание, наследование от нескольких моделей, которые содержат первичное поле 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

Or use a common ancestor to hold the AutoField. This requires using an explicit OneToOneField from each parent model to the common ancestor to avoid a clash between the fields that are automatically generated and inherited by the child:

class Piece(models.Model):
    pass

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

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

class BookReview(Book, Article):
    pass

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

In normal Python class inheritance, it is permissible for a child class to override any attribute from the parent class. In Django, this isn’t usually permitted for model fields. If a non-abstract model base class has a field called author, you can’t create another model field or define an attribute called author in any class that inherits from that base class.

This restriction doesn’t apply to model fields inherited from an abstract model. Such fields may be overridden with another field or value, or be removed by setting field_name = None.

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

Model managers are inherited from abstract base classes. Overriding an inherited field which is referenced by an inherited Manager may cause subtle bugs. See custom managers and model inheritance.

Примечание

Some fields define extra attributes on the model, e.g. a ForeignKey defines an extra attribute with _id appended to the field name, as well as related_name and related_query_name on the foreign model.

These extra attributes cannot be overridden unless the field that defines it is changed or removed so that it no longer defines the extra attribute.

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

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

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

Organizing models in a package

The manage.py startapp command creates an application structure that includes a models.py file. If you have many models, organizing them in separate files may be useful.

To do so, create a models package. Remove models.py and create a myapp/models/ directory with an __init__.py file and the files to store your models. You must import the models in the __init__.py file.

For example, if you had organic.py and synthetic.py in the models directory:

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

Explicitly importing each model rather than using from .models import * has the advantages of not cluttering the namespace, making code more readable, and keeping code analysis tools useful.

См.также

Справочник по моделям
Описывает API всего, что связано с моделями.