Модели отображают информацию о данных, с которыми вы работаете. Они содержат поля и поведение ваших данных. Обычно одна модель представляет одну таблицу в базе данных.
Основы:
Каждая модель это класс унаследованный от 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 использует классы полей для определения такой информации:
Типа колонки в базе данных (например: INTEGER, VARCHAR).
Виджет используемый при создании поля формы (например: <input type="text">, <select>).
Минимальные правила проверки данных, используемые в интерфейсе администратора и для автоматического создания формы.
В Django есть большое количество полей; полный список можно посмотреть на странице списка полей. Вы можете легко добавить собственное поле; смотрите Создание собственных полей для модели.
Для каждого поля есть набор предопределенных аргументов (описание на странице описания полей). Например, CharField (и унаследованные от него) имеют обязательный аргумент max_length, который определяет размер поля VARCHAR для хранения данных этого поля.
Также есть список стандартных аргументов для всех полей. Все они не обязательны. Все они описаны в разделе про аргументы полей модели, вот список самых используемых:
Если True, Django сохранит пустое значение как NULL в базе данных. По умолчанию - False.
Если True, поле не обязательно и может быть пустым. По умолчанию - False.
Это не то же что и null. null относится к базе данных, blank - к проверке данных. Если поле содержит blank=True, форма позволит передать пустое значение. При blank=False - поле обязательно.
Итератор (например, список или кортеж) 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
'L'
>>> p.get_shirt_size_display()
'Large'
Значение по умолчанию для этого поля. Это может быть значение или функция. Если это функция - она будет вызвана при каждом создании объекта.
Подсказка, отображаемая в поле формы. Это полезно для описания поля, даже если поле не используется в формах.
При 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']
При 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,
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)
Так же, как и с 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)
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 существует только два ограничения:
Название поля не может быть слово зарезервированное Python, т.к. это приведет к синтаксической ошибке. Например:
class Example(models.Model):
pass = models.IntegerField() # 'pass' is a reserved word!
Название поля не может содержать несколько нижних подчеркиваний(_) подряд, т.к. такой подход 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 можно найти в разделе о настройках модели.
Самый важный атрибут модели – 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"
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).
Раздел о моделях содержит полный список методов, автоматически добавляемых в модель. Вы можете переопределить большинство из них – смотрите Переопределение методов модели, – но есть методы, которые вы чаще всего определите для каждой модели:
“Волшебный метод” Python, который возвращает unicode “представление” объекта. Это то, что Python и Django используют для отображения объекта как строки, обычно в консоли, интерфейсе администратора или шаблоне.
Желательно определить этот метод, т.к. значение по умолчанию не слишком привлекательно.
Python 2 аналог __str__().
Этот метод указывает 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, можно быть уверенным, что все аргументы будут приняты.
Переопределенные методы модели не вызываются при множественных операциях(bulk)
Учтите, метод delete() не обязательно вызывается при массовом удалении объектов через QuerySet, или как результат каскадного удаления. Для гарантированного выполнения действий после удаления объекта используйте сигналы pre_delete и/или post_delete.
К сожалению, вы не сможете изменить логику сохранения объектов при использовании creating или updating, так как save(), pre_save и post_save не будут выполнены.
Запросы на чистом SQL лучше выполнять в методе модели. Подробнее о SQL-запросах можно прочитать в разделе о запросах на чистом SQL.
Наследование моделей в Django работает почти так же, как и наследование классов в Python, но следует соблюдать правила, описанные выше. Это означает, что базовый класс должен наследоваться от django.db.models.Model.
Единственное, что вам нужно определить, это должна ли родительская модель быть независимой моделью (с собственной таблицей в базе данных), или же родительская модель просто контейнер для хранения информации, доступной только через дочерние модели.
Существует три вида наследования моделей в Django.
Чаще всего вы будете использовать родительскую модель для хранения общих полей, чтобы не добавлять их в каждую дочернюю модель. Если вы не собираетесь использовать его как независимую модель – Абстрактные модели то, что вам нужно.
Если родительская модель независимая(возможно, из другого приложения) и должна храниться в отдельной таблице, Multi-table наследование то, что вам нужно.
Если же вы хотите переопределить поведение модели на уровне 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 разделить общие данные, используя в то же время одну таблицу в базе данных.
После создания абстрактной модели, 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) будет использовать туже таблицу в базе данных, что точно не то, что вам нужно.
Это второй тип наследования в 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.
При 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 для поля customers – models.ManyToManyField(Place, related_name='provider') – решит эту проблему.
Как уже упоминалось, Django самостоятельно создает OneToOneField для связи дочерней модели с каждой родительской не абстрактной моделью. Если вы хотите определять имя обратной связи для родительской модели, вы можете создать собственный OneToOneField с parent_link=True чтобы указать, что это поле является связью с родительской моделью.
При использовании 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.
Нет способа указать Django возвращать объекты модели MyPerson при запросе через модель Person. QuerySet для модели Person вернет объекты этого типа. Стоит помнить, что код, который использует модель Person, будет использовать ее независимо от добавления proxy-модели. Вы не можете полностью заменить модель Person (или любую другую) всюду, где она используется.
Proxy-модель должна наследоваться от одной не абстрактной модели. Вы не можете унаследоваться от нескольких не абстрактных моделей т.к. proxy-модель не может хранить информации о полях в нескольких таблицах базы данных. 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 может выглядеть так же как и неуправляемая модель, используя атрибут managed класса Meta модели. Оба варианта не совсем то же самое, и это стоит учесть, при выборе что вы должны использовать.
Первое отличие в том, что вы можете (и должны, если вам не нужна пустая модель) добавить поля для модели с Meta.managed=False. Вы можете, указав необходимый атрибут Meta.db_table, создать неуправляемую модель, которая отображает существующую модель, и добавить ей новые методы. Тем не менее, это было не совсем удобно, т.к. нужно соблюдать синхронность моделей, делая какие-либо изменения в таблице.
Другим отличием, которое больше относится к proxy-модели - это как Django работает с этими моделями. Proxy-модели предназначены для использования так же, как и оригинальная модель. Они наследуют менеджеры оригинальной модели, включая менеджер по умолчанию. При multi-table наследовании дочерние модели не наследуют менеджеры от родительской т.к. они не всегда будут работать корректно, учитывая, что есть дополнительные поля. Документация о менеджерах содержит больше информации об этом.
Когда эти две функции были реализованы, была попытка объединить их в одно решение. Оказалось, что работа с наследованием в общем и менеджеры в частности делает API очень сложным и потенциально тяжелым для понимания. Так возникло решение использовать два механизма.
Основные правила:
Если вы хотите отобразить существующую модель или таблицу в базе данных без использования всех колонок таблицы, используйте Meta.managed=False. Также это очень полезно для использования таблиц базы данных, которые управляются не Django.
Если вы хотите изменить поведение модели на уровне 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
Или использовать общего предка, который содержит поле 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 работает все немного по другому.
Эти ограничения относятся только для атрибутов, которые являются экземплярами Field. Остальные атрибуты могут быть переопределены. Это также относится к атрибутам классов. Вы можете указать одинаковое название поля в таблице БД для родительской и дочерней моделей т.к. они находятся в разных таблицах.
Django вызовет исключение FieldError, если вы переопределите поле родительской модели.
См.также
Описывает API всего, что связано с моделями.
Mar 31, 2016