You are on page 1of 145

Моделі та бази даних 


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

 Моделі
 Подання запитів
 Агрегація
 Пошук
 Менеджери
 Виконання необроблених SQL-запитів
 Транзакції з базами даних
 Багато баз даних
 Табличні простори
 Оптимізація доступу до бази даних
 Інструментарій бази даних
 Приклади використання API взаємозв'язку моделі

Моделі ¶
Модель - це єдине, остаточне джерело інформації про ваші дані. Він містить основні поля та поведінку
даних, які ви зберігаєте. Як правило, кожна модель відображається в єдиній таблиці бази даних.

Основи:

 Кожна модель є класом Python, який є підкласами django.db.models.Model.


 Кожен атрибут моделі представляє поле бази даних.
 З усім цим, Django надає вам автоматично згенерований API доступу до бази даних; див. Подання
запитів .

Швидкий приклад ¶
Цей приклад моделі визначає a Person, який має a first_nameта last_name:

from django.db import models

class Person(models.Model):

first_name = models.CharField(max_length=30)

last_name = models.CharField(max_length=30)

first_nameі last_nameє полями моделі. Кожне поле вказується як атрибут класу, і кожен атрибут


відображається у стовпці бази даних.
Наведена вище Personмодель створить таблицю бази даних, як це:

CREATE TABLE myapp_person (

"id" serial NOT NULL PRIMARY KEY,

"first_name" varchar(30) NOT NULL,

"last_name" varchar(30) NOT NULL

);

Деякі технічні примітки:

 Назва таблиці, myapp_personавтоматично походить від деяких метаданих моделі, але може бути
замінена. Докладніше див. У розділі Назви таблиць .
 idПоле додається автоматично, але це поведінка може бути перевизначені. Див. Автоматичні
поля первинного ключа .
 SQL в цьому прикладі відформатований з використанням синтаксису PostgreSQL, але варто
відзначити , Django використовує SQL спеціально для зберігання бази даних , зазначених у
вашому файлі налаштувань .CREATE TABLE

Використання моделей ¶
Після того, як ви визначили свої моделі, вам потрібно повідомити Django, що ви
збираєтеся використовувати ці моделі. Зробіть це, відредагувавши файл налаштувань та
змінивши INSTALLED_APPSпараметр, щоб додати назву модуля, що містить ваш models.py.

Наприклад, якщо моделі для вашої програми живуть у модулі myapp.models(структура пакета, яка
створюється для програми за допомогою сценарію), слід читати,
частково:manage.py startappINSTALLED_APPS

INSTALLED_APPS = [

#...

'myapp',

#...

Коли ви додаєте нові програми INSTALLED_APPS, не забудьте запустити їх , необов’язково зробивши для
них перенесення .manage.py migratemanage.py makemigrations

Поля ¶
Найважливішою частиною моделі - і єдиною необхідною частиною моделі - є перелік визначених нею
полів бази даних. Поля задаються атрибутами класу. Будьте обережні , щоб не вибирати імена полів , які
конфліктують з моделями API , як clean, saveабо delete.

Приклад:

from django.db import models


class Musician(models.Model):

first_name = models.CharField(max_length=50)

last_name = models.CharField(max_length=50)

instrument = models.CharField(max_length=100)

class Album(models.Model):

artist = models.ForeignKey(Musician, on_delete=models.CASCADE)

name = models.CharField(max_length=100)

release_date = models.DateField()

num_stars = models.IntegerField()

Типи полів ¶
Кожне поле у вашій моделі має бути екземпляром відповідного Fieldкласу. Django використовує типи
польових класів для визначення кількох речей:

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

Django поставляється з десятками вбудованих типів полів; Ви можете знайти повний список у посиланні на
поле моделі . Ви можете легко писати власні поля, якщо вбудовані в Django не роблять
трюку; див. Написання полів власної моделі .

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

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

null
Якщо True, Django буде зберігати порожні значення, як NULLу базі даних. За
замовчуванням False.

blank

Якщо Trueполе допускається пустим. За замовчуванням False.

Зверніть увагу, що це відрізняється від null. nullє суто пов’язаним з базою даних, тоді


як blankпов’язаним із валідацією. Якщо поле має blank=True, перевірка форми дозволить ввести
порожнє значення. Якщо поле має blank=False, це поле буде обов’язковим.

choices
Послідовність з 2-кортежів , щоб використовувати в якості варіантів для цієї області. Якщо це
вказано, віджетом форми за замовчуванням буде поле для вибору замість стандартного
текстового поля і обмежить можливість вибору.

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

YEAR_IN_SCHOOL_CHOICES = [

('FR', 'Freshman'),

('SO', 'Sophomore'),

('JR', 'Junior'),

('SR', 'Senior'),

('GR', 'Graduate'),

]
Примітка

Нова міграція створюється кожного разу, коли порядок choicesзмін змінюється.

Першим елементом кожного кортежу є значення, яке буде зберігатися в базі даних. Другий
елемент відображається у віджеті форми поля.

Враховуючи екземпляр моделі, доступ до відображуваного значення поля з choicesможна


отримати за допомогою 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'
Ви також можете використовувати класи перерахування для choicesстислого визначення:

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)

Подальші приклади доступні у посиланні на поле моделі .

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 надає кожній моделі первинний ключ із автоматичним збільшенням із типом,
зазначеним для кожного додатка в налаштуваннях AppConfig.default_auto_fieldабо
глобально DEFAULT_AUTO_FIELD. Наприклад:

id = models.BigAutoField(primary_key=True)

Якщо ви хочете вказати власний первинний ключ, вкажіть primary_key=Trueв одному з полів. Якщо


Django побачить, що ви явно встановили Field.primary_key, він не додасть автоматичний idстовпець.

Для кожної моделі потрібно мати рівно одне поле primary_key=True(або явно оголошене, або
автоматично додане).

Змінено в Django 3.2:


У старих версіях поля автоматичного створення первинного ключа завжди були AutoFields.

Багатослівні назви полів ¶


Кожен тип поля, крім ForeignKey, ManyToManyFieldі OneToOneField, приймає необов’язковий перший
позиційний аргумент - багатослівне ім’я. Якщо детальне ім'я не вказане, Django автоматично створить
його, використовуючи ім'я атрибута поля, перетворюючи підкреслення в пробіли.

У цьому прикладі багатослівна назва :"person's first name"

first_name = models.CharField("person's first name", max_length=30)

У цьому прикладі багатослівна назва :"first name"

first_name = models.CharField(max_length=30)

ForeignKey, ManyToManyFieldі OneToOneFieldпотрібно, щоб перший аргумент був класом моделі, тому


використовуйте verbose_nameаргумент ключового слова:

poll = models.ForeignKey(

Poll,

on_delete=models.CASCADE,

verbose_name="the related poll",

sites = models.ManyToManyField(Site, verbose_name="list of sites")

place = models.OneToOneField(

Place,

on_delete=models.CASCADE,

verbose_name="related place",

)
Конвенція не полягає у написанні великої літери першої літери verbose_name. Django автоматично буде
писати велику першу літеру там, де це потрібно.

Відносини ¶
Очевидно, що сила реляційних баз даних полягає у зв’язуванні таблиць між собою. Django пропонує
способи визначити три найпоширеніші типи взаємозв’язків з базами даних: багато-до-одного, багато-до-
багатьох і один до одного.

Відносини багато до одного ¶

Для визначення взаємозв'язку "багато-до-одного" використовуйте django.db.models.ForeignKey. Ви


використовуєте його так само, як і будь-який інший Fieldтип: включаючи його як атрибут класу вашої
моделі.

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

Наприклад, якщо Carмодель має Manufacturer- тобто, Manufacturerробить кілька автомобілів, але


кожна Carмає лише один Manufacturer- використовуйте такі визначення:

from django.db import models

class Manufacturer(models.Model):

# ...

pass

class Car(models.Model):

manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)

# ...

Ви також можете створити рекурсивні зв'язки (об'єкт, що має відношення "багато до одного" до себе),


і відносини до моделей, які ще не визначені ; детальніше див. посилання на поле моделі .

Пропонується, але не обов’язково, щоб ім’я ForeignKeyполя ( manufacturerу прикладі вище) було


назвою моделі, нижній регістр. Ви можете називати поле як завгодно. Наприклад:

class Car(models.Model):

company_that_makes_it = models.ForeignKey(

Manufacturer,

on_delete=models.CASCADE,

# ...
Дивитися також

ForeignKeyполя приймають ряд додаткових аргументів, які пояснюються у посиланні на поле моделі . Ці
варіанти допомагають визначити, як повинні працювати стосунки; всі є необов’язковими.
Докладніше про доступ до об’єктів, пов’язаних із зворотною стороною, див. У наведеному нижче прикладі
зворотних зв’язків .

Зразок коду див. У прикладі моделі взаємозв'язку "багато-до-одного" .

Відносини багато-до-багатьох ¶

Щоб визначити відносини багато-до-багатьох, використовуйте ManyToManyField. Ви використовуєте


його так само, як і будь-який інший Fieldтип: включаючи його як атрибут класу вашої моделі.

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

Наприклад, якщо у a Pizzaє кілька Toppingоб’єктів - тобто a Toppingможе бути на декількох піцах, і


кожна з них Pizzaмає кілька начинок - ось як ви це подаєте:

from django.db import models

class Topping(models.Model):

# ...

pass

class Pizza(models.Model):

# ...

toppings = models.ManyToManyField(Topping)

Як і у випадку ForeignKey, ви також можете створювати рекурсивні відносини (об'єкт із відношенням


"багато-до-багатьох" до себе) та відносини до моделей, які ще не визначені .

Пропонується, але не обов’язково, щоб ім’я ManyToManyField( toppingsу наведеному вище прикладі)


було множиною, що описує набір пов’язаних об’єктів моделі.

Не має значення, яка модель є ManyToManyField, але ви повинні помістити її лише в одну з моделей, а не
в обидві.

Як правило, ManyToManyFieldекземпляри повинні входити в об'єкт, який буде редагуватися у формі. У


наведеному вище прикладі, toppingsє в Pizza(а не Toppingмати pizzas ManyToManyField), тому що
більш природно думати про те, що піца має начинку, ніж заливка на декількох піцах. Як це встановлено
вище, Pizzaформа дозволить користувачам вибирати доливання.

Дивитися також

Дивіться багато-до-багатьох приклад моделі відносини для повного прикладу.

ManyToManyFieldполя також приймають ряд додаткових аргументів, які пояснюються у посиланні на


поле моделі . Ці варіанти допомагають визначити, як повинні працювати стосунки; всі є необов’язковими.

Додаткові поля щодо відносин багато-до-багатьох ¶

Коли ви маєте справу лише із стосунками багато-до-багатьох, такими як змішування та поєднання піци та
начинки, стандарт ManyToManyField- це все, що вам потрібно. Однак іноді може знадобитися пов’язати
дані із взаємозв’язком двох моделей.
Наприклад, розглянемо випадок програми, яка відстежує музичні групи, до яких належать музиканти. Між
людиною та групами, членами яких вона є, існує взаємозв’язок «багато-до-багатьох», тому ви можете
використати a 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як зазначено вище, інакше помилка перевірки буде видана.

Тепер, коли ви налаштували свою 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>]>

Ви також можете використовувати add(), create()або set()для створення зв'язків, якщо ви


вказали through_defaultsбудь-які обов'язкові поля:

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

Можливо, ви віддасте перевагу створювати екземпляри проміжної моделі безпосередньо.

Якщо спеціальна таблиця, визначена проміжною моделлю, не застосовує унікальність пари, дозволяючи


кілька значень, виклик видалить усі екземпляри проміжної моделі:(model1, model2)remove()
>>> 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>]>

clear() Метод може бути використаний , щоб видалити всі багато-до-багатьох для примірника:

>>> # Beatles have broken up

>>> beatles.members.clear()

>>> # Note that this deletes the intermediate model instances

>>> Membership.objects.all()

<QuerySet []>

Після встановлення взаємозв’язків «багато-до-багатьох» ви можете надсилати запити. Як і у звичайних


відносинах "багато-до-багатьох", ви можете робити запити, використовуючи атрибути моделі "багато-до-
багатьох":

# 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(тому що ресторан «є» місцем, справді, в ручку для
цього ви зазвичай використовуєте успадкування , яке передбачає неявне відношення один до одного).

Як і у випадку ForeignKey, можна визначити рекурсивний зв’язок та зробити посилання на ще не


визначені моделі .

Дивитися також

Див моделі відносини приклад Один-до-одному для повного прикладу.

OneToOneFieldполя також приймають необов'язковий parent_linkаргумент.

OneToOneFieldкласи, що використовуються для автоматичного перетворення первинного ключа на


моделі. Це вже не відповідає дійсності (хоча ви можете вручити primary_keyаргумент вручну, якщо
хочете). Таким чином, тепер можна мати кілька полів типу OneToOneFieldна одній моделі.

Моделі між файлами ¶


Цілком нормально пов’язувати модель з однією з іншої програми. Для цього імпортуйте відповідну
модель у верхній частині файлу, де визначена ваша модель. Потім зверніться до іншого класу моделі, де
це потрібно. Наприклад:

from django.db import models

from geography.models import ZipCode


class Restaurant(models.Model):

# ...

zip_code = models.ForeignKey(

ZipCode,

on_delete=models.SET_NULL,

blank=True,

null=True,

Обмеження назви полів ¶


Django встановлює деякі обмеження на назви полів моделі:

1. Ім'я поля не може бути зарезервованим словом Python, оскільки це призведе до синтаксичної
помилки Python. Наприклад:

2. class Example(models.Model):

3. pass = models.IntegerField() # 'pass' is a reserved word!

4. Ім'я поля не може містити більше одного підкреслення поспіль через те, як працює синтаксис
пошуку запитів Django. Наприклад:

5. class Example(models.Model):

6. foo__bar = models.IntegerField() # 'foo__bar' has two underscores!

7. Ім'я поля не може закінчуватися підкресленням з подібних причин.

Однак ці обмеження можна обійти, оскільки ім’я поля не обов’язково має відповідати імені стовпця бази
даних. Див. db_columnВаріант.

SQL зарезервованих слів, наприклад join, whereабо select, які допускаються в якості імен порожнистої


моделі, так як Django вислизає все імена таблиць бази даних і імена стовпців в кожному основному запиті
SQL. Він використовує синтаксис цитування вашого конкретного механізму баз даних.

Спеціальні типи полів ¶


Якщо одне з існуючих полів моделі не може бути використане відповідно до ваших цілей, або якщо ви
хочете скористатися деякими менш поширеними типами стовпців бази даних, ви можете створити власний
клас полів. Повне охоплення створення власних полів надається в Написанні полів власної моделі .

Metaваріанти ¶
Надайте метадані вашої моделі, використовуючи внутрішній , наприклад:class Meta

from django.db import models

class Ox(models.Model):
horn_length = models.IntegerField()

class Meta:

ordering = ["horn_length"]

verbose_name_plural = "oxen"

Метадані моделі - це “все, що не є полем”, наприклад параметри впорядкування ( ordering), ім’я таблиці
бази даних ( db_table) або зручні для читання одиничні та множинні імена
( verbose_nameта verbose_name_plural). Жодне з них не потрібно, і додавання до моделі є абсолютно
необов’язковим.class Meta

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

Атрибути моделі ¶
objects
Найважливішим атрибутом моделі є Manager. Це інтерфейс, за допомогою якого операції запиту
бази даних надаються моделям Django і використовуються для отримання екземплярів з бази
даних. Якщо не Managerвказано спеціальне ім'я, ім'я за замовчуванням - objects. До менеджерів
можна отримати доступ лише за допомогою класів моделей, а не екземплярів моделей.

Модельні методи ¶
Визначте власні методи на моделі, щоб додати користувацькі функції рівня "рядка" до ваших об'єктів. У
той час, як Managerметоди призначені робити «загальнотабличні» речі, модельні методи повинні діяти на
конкретному екземплярі моделі.

Це цінна техніка збереження ділової логіки в одному місці - моделі.

Наприклад, ця модель має кілька власних методів:

from django.db import models

class Person(models.Model):

first_name = models.CharField(max_length=50)

last_name = models.CharField(max_length=50)

birth_date = models.DateField()

def baby_boomer_status(self):

"Returns the person's baby-boomer status."

import datetime

if self.birth_date < datetime.date(1945, 8, 1):

return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):

return "Baby boomer"

else:

return "Post-boomer"

@property

def full_name(self):

"Returns the person's full name."

return '%s %s' % (self.first_name, self.last_name)

Останній метод у цьому прикладі - це властивість .

Посилання на примірник моделі має повний перелік методів, що автоматично надаються кожній


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

__str__()

"Чарівний метод" Python, який повертає рядкове представлення будь-якого об'єкта. Це те, що
Python і Django використовуватимуть, коли екземпляр моделі потрібно примусити і відображати у
вигляді простого рядка. Найголовніше, це трапляється, коли ви відображаєте об’єкт на
інтерактивній консолі або в адміністраторі.

Ви завжди хочете визначити цей метод; за замовчуванням це не дуже корисно.

get_absolute_url()

Це говорить Django, як розрахувати URL-адресу для об’єкта. Django використовує це в своєму


інтерфейсі адміністратора, і в будь-який час йому потрібно з'ясувати URL-адресу об'єкта.

Будь-який об’єкт, який має URL-адресу, яка його однозначно ідентифікує, повинен визначати цей
метод.

Перевизначення зумовлених методів моделі ¶


Існує ще один набір модельних методів, які інкапсулюють купу поведінки бази даних, яку ви хочете
налаштувати. Зокрема, вам часто хочеться змінити спосіб роботи save()та delete()роботу.

Ви можете замінити ці методи (та будь-який інший метод моделі), щоб змінити поведінку.

Класичний варіант використання для перевизначення вбудованих методів полягає в тому, що якщо ви
хочете, щоб щось відбувалося щоразу, коли ви зберігаєте об'єкт. Наприклад (див. save()Документацію
щодо прийнятих параметрів):

from django.db import models

class Blog(models.Model):

name = models.CharField(max_length=100)
tagline = models.TextField()

def save(self, *args, **kwargs):

do_something()

super().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.

Важливо пам’ятати, що потрібно викликати метод суперкласу - це той бізнес, - щоб об’єкт все одно
зберігався у базі даних. Якщо ви забудете викликати метод суперкласу, поведінка за замовчуванням не
відбудеться, і база даних не торкнеться.super().save(*args, **kwargs)

Також важливо, щоб ви передали аргументи, які можна передати методу моделі - це те, що робить
біт. Django час від часу буде розширювати можливості вбудованих методів моделі, додаючи нові
аргументи. Якщо ви використовуєте у своїх визначеннях методів, ви гарантуєте, що ваш код автоматично
підтримуватиме ці аргументи при їх додаванні.*args, **kwargs*args, **kwargs

Перевизначені методи моделі не використовуються для масових операцій

Зверніть увагу, що delete()метод для об'єкта не обов'язково викликається при видаленні об'єктів


груповим способом за допомогою QuerySet або як результат . Щоб забезпечити виконання налаштованої
логіки видалення, ви можете використовувати та /
або сигнали.cascading deletepre_deletepost_delete

На жаль, немає обхідного способу , коли creatingабо updatingоб’єкти в навальній сумі, оскільки жоден


з , і не викликається.save()pre_savepost_save

Виконання користувацького SQL ¶


Інша поширена закономірність - написання власних операторів SQL у методах моделі та методах на рівні
модуля. Докладніше про використання вихідного SQL див. У документації щодо використання
необробленого SQL .
Спадкування моделі ¶
Спадкування моделі в Django працює майже ідентично тому, як працює звичайне успадкування класу в
Python, але основних слів на початку сторінки все одно слід дотримуватися. Це означає, що базовий клас
повинен підклас django.db.models.Model.

Єдине рішення, яке вам потрібно прийняти, - чи хочете ви, щоб батьківські моделі були власними
моделями (зі своїми таблицями баз даних), чи батьки є лише власниками загальної інформації, яка буде
видно лише через дочірні моделі.

У Django можливі три стилі успадкування.

1. Часто ви просто хочете використовувати батьківський клас для зберігання інформації, яку ви не
хочете вибирати для кожної дочірньої моделі. Цей клас ніколи не використовуватиметься
ізольовано, тому базові класи Abstract - це те, що вам потрібно.
2. Якщо ви підкласуєте існуючу модель (можливо, щось інше з іншого додатка) і хочете, щоб кожна
модель мала власну таблицю бази даних, мультитабличне успадкування - це шлях.
3. Нарешті, якщо ви хочете лише змінити поведінку моделі на рівні Python, не змінюючи жодним
чином поля моделей, ви можете використовувати проксі-моделі .

Абстрактні базові класи ¶


Абстрактні базові класи корисні, коли ви хочете додати загальну інформацію до ряду інших моделей. Ви
пишете свій базовий клас і ставите abstract=Trueв Meta- клас. Тоді ця модель не
використовуватиметься для створення таблиці бази даних. Натомість, коли він використовується як
базовий клас для інших моделей, його поля будуть додані до полів дочірнього класу.

Приклад:

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, оскільки він є абстрактним базовим класом. Він не генерує
таблицю бази даних або не має менеджера, і не може бути створений екземпляром або збережений
безпосередньо.
Поля, успадковані від абстрактних базових класів, можна замінити іншим полем або значенням або
видалити за допомогою None.

Для багатьох застосувань цей тип успадкування моделі буде саме тим, що ви хочете. Він забезпечує спосіб
виділення загальної інформації на рівні Python, одночасно створюючи лише одну таблицю бази даних для
дочірньої моделі на рівні бази даних.

Metaспадщина ¶

Коли створюється абстрактний базовий клас, Django робить будь-який внутрішній клас Meta, який ви
оголосили в базовому класі, доступним як атрибут. Якщо дочірній клас не оголошує власний Meta- клас,
він успадкує батьківський Meta . Якщо дитина хоче розширити батьківський клас Meta , вона може його
підкласувати. Наприклад:

from django.db import models

class CommonInfo(models.Model):

# ...

class Meta:

abstract = True

ordering = ['name']

class Student(CommonInfo):

# ...

class Meta(CommonInfo.Meta):

db_table = 'student_info'

Django робить одне коригування до класу Meta абстрактного базового класу: перед


встановленням атрибута Meta він встановлює abstract=False. Це означає, що діти абстрактних базових
класів автоматично не стають самими абстрактними класами. Щоб створити абстрактний базовий клас,
який успадковується від іншого абстрактного базового класу, вам потрібно явно
встановити abstract=Trueдочірній елемент .

Деякі атрибути не мають сенсу включати в клас Meta абстрактного базового класу. Наприклад,


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

Через те, як працює успадкування Python, якщо дочірній клас успадковується від декількох абстрактних
базових класів, за замовчуванням успадковуються лише параметри Meta з першого перерахованого
класу. Щоб успадкувати параметри Meta з безлічі абстрактних базових класів, потрібно явно
оголосити успадкування Meta . Наприклад:

from django.db import models

class CommonInfo(models.Model):
name = models.CharField(max_length=100)

age = models.PositiveIntegerField()

class Meta:

abstract = True

ordering = ['name']

class Unmanaged(models.Model):

class Meta:

abstract = True

managed = False

class Student(CommonInfo, Unmanaged):

home_group = models.CharField(max_length=5)

class Meta(CommonInfo.Meta, Unmanaged.Meta):

pass

Будьте обережні з related_nameта related_query_name¶

Якщо ви використовуєте related_nameабо related_query_nameна ForeignKeyабо ManyToManyField,
ви завжди повинні вказати унікальне зворотне ім'я та ім'я запиту для поля. Зазвичай це може спричинити
проблему в абстрактних базових класах, оскільки поля цього класу включаються до кожного з дочірніх
класів із точно однаковими значеннями атрибутів (включаючи related_nameта related_query_name)
кожного разу.

Щоб обійти цю проблему, коли ви використовуєте related_nameабо related_query_nameв


абстрактному базовому класі (лише), частина значення повинна містити '%(app_label)s'і '%(class)s'.

 '%(class)s' замінюється на нижню літеру назви дочірнього класу, в якому використовується


поле.
 '%(app_label)s'замінюється на нижню літеру назви програми, в якій міститься дочірній
клас. Кожне ім'я встановленої програми має бути унікальним, а назви класів моделей у кожній
програмі також повинні бути унікальними, тому в підсумку назва буде різною.

Наприклад, для додатка common/models.py:

from django.db import models

class Base(models.Model):
m2m = models.ManyToManyField(

OtherModel,

related_name="%(app_label)s_%(class)s_related",

related_query_name="%(app_label)s_%(class)ss",

class Meta:

abstract = True

class ChildA(Base):

pass

class ChildB(Base):

pass

Разом з іншим додатком rare/models.py:

from common.models import Base

class ChildB(Base):

pass

Зворотне ім'я common.ChildA.m2mполя буде, common_childa_relatedа ім'я зворотного запиту


буде common_childas. Зворотне ім'я common.ChildB.m2mполя буде, common_childb_relatedа ім'я
зворотного запиту буде common_childbs. Нарешті, зворотне ім'я rare.ChildB.m2mполя
буде, rare_childb_relatedа ім'я зворотного запиту буде rare_childbs. Це залежить від вас, як ви
використовуєте '%(class)s'та '%(app_label)s'частину для побудови пов’язаного імені або
пов’язаного імені запиту, але якщо ви забудете його використовувати, Django видасть помилки під час
перевірки системи (або запуску migrate).

Якщо ви не вказали related_name атрибут для поля в абстрактному базовому класі, типовим зворотним


ім'ям буде ім'я дочірнього класу, за яким слідує '_set', як це зазвичай було б, якби ви оголосили поле
безпосередньо в дочірньому класі . Наприклад, у наведеному вище коді, якщо related_name атрибут був
пропущений, зворотне ім'я m2mполя було б childa_setу ChildAрегістрі та childb_setдля ChildB поля.

Спадкування за декількома таблицями ¶


Другий тип успадкування моделі, що підтримується Django, - це коли кожна модель в ієрархії є моделлю
сама по собі. Кожна модель відповідає власній таблиці бази даних, і її можна запитувати та створювати
індивідуально. Відносини успадкування встановлюють зв'язки між дочірньою моделлю та кожним з її
батьків (за допомогою автоматично створеного OneToOneField). Наприклад:

from django.db import models


class Place(models.Model):

name = models.CharField(max_length=50)

address = models.CharField(max_length=80)

class Restaurant(Place):

serves_hot_dogs = models.BooleanField(default=False)

serves_pizza = models.BooleanField(default=False)

Усі поля Placeтакож будуть доступні у Restaurant, хоча дані будуть міститися в іншій таблиці бази
даних. Отже, ці два варіанти можливі:

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

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

Якщо у вас Placeє також a 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 виняток.

Автоматично створений OneToOneFieldна Restaurantщо пов'язує його Placeвиглядає наступним


чином :

place_ptr = models.OneToOneField(

Place, on_delete=models.CASCADE,

parent_link=True,

primary_key=True,

Ви можете змінити це поле, оголосивши свій


власний OneToOneFieldз parent_link=Trueна Restaurant.

Metaта мультитабличне успадкування ¶

У ситуації успадкування декількох таблиць немає сенсу для дочірнього класу успадковувати від
батьківського класу Meta . Всі параметри Meta вже застосовані до батьківського класу, і їх повторне
застосування, як правило, призведе лише до суперечливої поведінки (це на відміну від абстрактного
випадку базового класу, коли базовий клас не існує сам по собі).
Отже, дочірня модель не має доступу до батьківського класу Meta . Однак є кілька обмежених випадків,
коли дитина успадковує поведінку від батьків: якщо дитина не вказує orderingатрибут
або get_latest_byатрибут, вона успадкує їх від свого батька.

Якщо у батьків є замовлення, і ви не хочете, щоб у дитини було природне замовлення, ви можете явно
відключити його:

class ChildModel(ParentModel):

# ...

class Meta:

# Remove parent's ordering effect

ordering = []

Спадщина та зворотні відносини ¶

Оскільки успадкування багато таблиць використовує неявний OneToOneFieldзв’язок між дочірньою і


батьківською структурою, можливо перехід від батьківської до нижчої, як у наведеному вище
прикладі. Тим НЕ менше, це витратив ім'я, яке за
замовчуванням related_nameзначення ForeignKeyі ManyToManyFieldвідносин. Якщо ви покладаєте
такі типи відносин на підклас батьківської моделі, ви повинні вказати related_name атрибут у кожному
такому полі. Якщо ви забудете, Django викличе помилку перевірки.

Наприклад, знову використовуючи наведений вище Placeклас, давайте створимо ще один підклас


з ManyToManyField:

class Supplier(Place):

customers = models.ManyToManyField(Place)

Це призводить до помилки:

Reverse query name for 'Supplier.customers' clashes with reverse query

name for 'Supplier.place_ptr'.

HINT: Add or change a related_name argument to the definition for

'Supplier.customers' or 'Supplier.place_ptr'.

Додавання related_nameв customersполе нижче буде усунути


помилку: .models.ManyToManyField(Place, related_name='provider')

Вказівка поля батьківського посилання ¶

Як уже згадувалося, Django автоматично створить OneToOneFieldзв'язок вашого дочірнього класу з будь-


якими абстрактними батьківськими моделями. Якщо ви хочете контролювати ім'я атрибута, що зв'язується
з батьківським, ви можете створити власне OneToOneFieldта встановити, parent_link=True щоб
вказати, що ваше поле є посиланням на батьківський клас.

Проксі-моделі ¶
При використанні успадкування кількох таблиць створюється нова таблиця бази даних для кожного
підкласу моделі. Зазвичай це бажана поведінка, оскільки підклас потребує місця для зберігання будь-яких
додаткових полів даних, яких немає в базовому класі. Однак іноді потрібно лише змінити поведінку моделі
Python - можливо, змінити менеджера за замовчуванням або додати новий метод.

Для цього призначено успадкування моделі проксі: створення проксі для оригінальної моделі. Ви можете
створювати, видаляти та оновлювати екземпляри проксі-моделі, і всі дані будуть збережені так, ніби ви
використовуєте оригінальну (не проксі-модель) модель. Різниця полягає в тому, що ви можете змінити такі
речі, як замовлення моделі за замовчуванням або менеджер за замовчуванням у проксі, без необхідності
змінювати оригінал.

Проксі-моделі оголошені як звичайні моделі. Ви говорите Django, що це модель проксі,


встановлюючи proxyатрибут Metaкласу True.

Наприклад, припустимо, ви хочете додати метод до Personмоделі. Ви можете зробити це так:

from django.db import models

class Person(models.Model):

first_name = models.CharField(max_length=30)

last_name = models.CharField(max_length=30)

class MyPerson(Person):

class Meta:

proxy = True

def do_something(self):

# ...

pass

MyPersonКлас працює на одній і тій же таблиці бази даних в якості батьківського Personкласу. Зокрема,


будь-які нові екземпляри Personтакож будуть доступні через MyPerson, і навпаки:

>>> p = Person.objects.create(first_name="foobar")

>>> MyPerson.objects.get(first_name="foobar")

<MyPerson: foobar>

Ви також можете використовувати проксі-модель, щоб визначити інше впорядкування за замовчуванням


для моделі. Можливо, ви не завжди хочете замовляти Personмодель, але регулярно впорядковуйте
за last_nameатрибутом, коли використовуєте проксі:

class OrderedPerson(Person):

class Meta:

ordering = ["last_name"]

proxy = True
Тепер звичайні Personзапити будуть впорядковані, а OrderedPersonзапити упорядковані last_name.

Проксі-моделі успадковують Metaатрибути так само, як і звичайні моделі .

QuerySets все ще повертає модель, яку запитували ¶

Немає можливості, щоб Django повертав, скажімо, MyPersonоб’єкт, коли ви


запитуєте Personоб’єкти. Набір запитів для Personоб’єктів повертає такі типи об’єктів. Вся суть проксі-
об’єктів полягає в тому, що код, який покладається на оригінал Person, використовуватиме їх, а ваш
власний код може використовувати розширення, які ви включили (на що і так не покладається жоден
інший код). Це не спосіб замінити Person(або будь-яку іншу) модель скрізь чимось своїм творінням.

Обмеження базового класу ¶

Проксі-модель повинна успадковувати саме один не абстрактний клас моделі. Ви не можете успадкувати
від декількох абстрактних моделей, оскільки модель проксі не забезпечує зв’язку між рядками в різних
таблицях бази даних. Проксі-модель може успадковувати будь-яку кількість класів абстрактних моделей
за умови, що вони не визначають жодного поля моделі. Проксі-модель може також успадковувати будь-
яку кількість проксі-моделей, які мають спільний не абстрактний батьківський клас.

Менеджери проксі-моделей ¶

Якщо в проксі-моделі не вказано жодного менеджера моделі, він успадковує менеджерів від батьків-
моделей. Якщо ви визначите менеджера на проксі-моделі, це стане типовим, хоча будь-які менеджери,
визначені в батьківських класах, все одно будуть доступні.

Продовжуючи наш приклад зверху, ви можете змінити менеджера за замовчуванням, який


використовується при запиті Personмоделі, як це:

from django.db import models

class NewManager(models.Manager):

# ...

pass

class MyPerson(Person):

objects = NewManager()

class Meta:

proxy = True

Якщо ви хотіли додати нового менеджера до проксі-сервера, не замінюючи існуючий за замовчуванням,


ви можете скористатися прийомами, описаними в документації користувацького менеджера : створіть
базовий клас, що містить нових менеджерів, і успадкуйте його після основного базового класу:

# Create an abstract class for the new manager.

class ExtraManagers(models.Model):
secondary = NewManager()

class Meta:

abstract = True

class MyPerson(Person, ExtraManagers):

class Meta:

proxy = True

Ймовірно, вам не доведеться робити це дуже часто, але, коли ви це робите, це можливо.

Відмінності між успадкуванням проксі та некерованими моделями ¶

Успадкування моделі проксі може виглядати досить схожим на створення некерованої моделі,
використовуючи managedатрибут Metaкласу моделі .

За допомогою ретельного налаштування Meta.db_tableви можете створити некеровану модель, яка


затінює існуючу модель і додає до неї методи Python. Однак це було б дуже повторюваним та крихким,
оскільки вам потрібно синхронізувати обидві копії, якщо ви вносите зміни.

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

Загальними правилами є:

1. Якщо ви дзеркально відображаєте існуючу модель або таблицю бази даних і не хочете всі вихідні
стовпці таблиці бази даних, використовуйте Meta.managed=False. Цей параметр, як правило,
корисний для моделювання переглядів баз даних і таблиць, які не контролюються Django.
2. Якщо ви хочете змінити поведінку моделі лише для Python, але зберігаєте ті самі поля, що і в
оригіналі, використовуйте Meta.proxy=True. Це налаштовує ситуацію так, що модель проксі є
точною копією структури зберігання оригінальної моделі при збереженні даних.

Множинне успадкування ¶
Подібно до підкласування Python, модель Django може успадковувати від декількох батьківських
моделей. Майте на увазі, що застосовуються звичайні правила дозволу імен Python. Першим базовим
класом, у якому з’являється певне ім’я (наприклад, Meta ), буде той, який використовується; наприклад, це
означає, що якщо кілька батьків містять клас Meta , буде використаний лише перший, а всі інші будуть
проігноровані.

Як правило, вам не потрібно буде успадковувати від кількох батьків. Основним випадком використання,
коли це корисно, є клас “змішування”: додавання певного додаткового поля чи методу до кожного класу,
який успадковує змішування. Постарайтеся, щоб ієрархії успадкування були якомога простішими та
зрозумілішими, щоб вам не довелося боротися, щоб з’ясувати, звідки надходить певна інформація.

Зверніть увагу, що успадкування від декількох моделей, що мають спільне idполе первинного ключа,
спричинить помилку. Щоб правильно використовувати множинне успадкування, ви можете
використовувати явний AutoFieldу базових моделях:
class Article(models.Model):

article_id = models.AutoField(primary_key=True)

...

class Book(models.Model):

book_id = models.AutoField(primary_key=True)

...

class BookReview(Book, Article):

pass

Або використовуйте спільного предка, щоб утримувати AutoField. Це вимагає використання


явного OneToOneFieldвід кожної батьківської моделі для загального предка, щоб уникнути зіткнення між
полями, які автоматично генеруються та успадковуються дочірніми:

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

Назва поля "приховування" заборонено ¶


У звичайному успадкуванні класу Python допустимо, щоб дочірній клас перевизначив будь-який атрибут
батьківського класу. У Django це зазвичай не дозволяється для полів моделі. Якщо неосновна модель
базового класу має поле, яке називається author, ви не можете створити інше поле моделі або визначити
атрибут, викликаний authorу будь-якому класі, який успадковує цей базовий клас.
Це обмеження не застосовується до полів моделі, успадкованих від абстрактної моделі. Такі поля можна
замінити іншим полем або значенням або видалити за допомогою налаштування .field_name = None

Увага

Менеджери моделей успадковуються від абстрактних базових класів. Перевизначення успадкованого


поля, на яке посилається успадковане, Managerможе спричинити незначні помилки. Див. Користувацькі
менеджери та успадкування моделей .
Примітка

Деякі поля визначають додаткові атрибути на моделі, наприклад a ForeignKeyвизначає додатковий


атрибут із _idдодаванням до імені поля, а також related_nameта related_query_nameна іноземній
моделі.

Ці додаткові атрибути неможливо замінити, якщо поле, яке його визначає, не буде змінено або видалено
таким чином, що воно більше не визначає додатковий атрибут.

Перевизначення полів у батьківській моделі призводить до труднощів у таких областях, як ініціалізація


нових екземплярів (зазначення, яке поле ініціалізується Model.__init__) та серіалізація. Це особливості,
з якими нормальне успадкування класу Python не повинно мати справу однаково, тому різниця між
успадкуванням моделі Django та успадкуванням класу Python не є довільною.

Це обмеження стосується лише атрибутів, які є Fieldекземплярами. За бажанням звичайні атрибути


Python можна замінити. Це також стосується лише імені атрибута, як його бачить Python: якщо ви вручну
вказуєте ім'я стовпця бази даних, ви можете мати одне і те ж ім'я стовпця, яке відображається як у
дочірній, так і в моделі предка для успадкування кількох таблиць (це стовпці у двох різних таблицях баз
даних).

Django підніме a, FieldErrorякщо ви перевизначите будь-яке поле моделі в будь-якій моделі предка.

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


Команда створює структуру програми , яка включає в себе файл. Якщо у вас багато моделей, може бути
корисним їх упорядкування в окремі файли.manage.py startappmodels.py

Для цього створіть modelsпакет. Видаліть models.pyі створіть myapp/models/каталог


із __init__.pyфайлом та файлами для зберігання ваших моделей. Ви повинні імпортувати моделі
у __init__.pyфайл.

Наприклад, якщо у вас було organic.pyі synthetic.pyв models каталозі:

myapp / models / __ init__.py ¶

from .organic import Person

from .synthetic import Robot

Явний імпорт кожної моделі, а не використання, має переваги у тому, що не захаращує простір імен,
робить код більш читабельним та зберігає корисні інструменти аналізу коду.from .models import *

Дивитися також

Посилання на моделі
Охоплює всі API, пов'язані з моделлю, включаючи поля моделі, пов'язані об'єкти та QuerySet.

Подання запитів ¶
Після того, як ви створили свої моделі даних , Django автоматично надає вам API абстракції
бази даних, який дозволяє створювати, отримувати, оновлювати та видаляти об'єкти. Цей
документ пояснює, як користуватися цим API. Для отримання детальної інформації про всі
різні варіанти пошуку моделі зверніться до посилання на модель даних .

У цьому посібнику (і в посиланні) ми будемо посилатися на такі моделі, які містять програму
Weblog:

from django.db import models

class Blog(models.Model):

name = models.CharField(max_length=100)

tagline = models.TextField()

def __str__(self):

return self.name

class Author(models.Model):

name = models.CharField(max_length=200)

email = models.EmailField()

def __str__(self):

return self.name

class Entry(models.Model):

blog = models.ForeignKey(Blog, on_delete=models.CASCADE)

headline = models.CharField(max_length=255)

body_text = models.TextField()
pub_date = models.DateField()

mod_date = models.DateField()

authors = models.ManyToManyField(Author)

number_of_comments = models.IntegerField()

number_of_pingbacks = models.IntegerField()

rating = models.IntegerField()

def __str__(self):

return self.headline

Створення об'єктів ¶
Для представлення даних таблиці бази даних в об'єктах Python Django використовує
інтуїтивно зрозумілу систему: Клас моделі представляє таблицю бази даних, а екземпляр
цього класу представляє конкретний запис у таблиці бази даних.

Щоб створити об'єкт, створіть його за допомогою аргументів ключового слова для класу
моделі, а потім зателефонуйте, save()щоб зберегти його в базі даних.

Якщо припустити, що моделі живуть у файлі mysite/blog/models.py, ось приклад:

>>> from blog.models import Blog

>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles


news.')

>>> b.save()

Це виконує оператор INSERTSQL за кадром. Django не потрапляє в базу даних, поки ви явно


не зателефонуєте save().

save()Метод не має значення, що повертається.

Дивитися також

save()приймає ряд додаткових опцій, не описаних тут. Детальнішу інформацію див. У


документації save().

Щоб створити та зберегти об’єкт за один крок, використовуйте create()метод.


Збереження змін до об’єктів ¶
Щоб зберегти зміни в об’єкті, який уже є в базі даних, використовуйте save().

Враховуючи Blogпримірник b5, який вже було збережено в базі даних, цей приклад змінює
своє ім’я та оновлює запис у базі даних:

>>> b5.name = 'New name'

>>> b5.save()

Це виконує оператор UPDATESQL за кадром. Django не потрапляє в базу даних, поки ви явно


не зателефонуєте save().

Збереження ForeignKeyта ManyToManyFieldполя ¶

Оновлення ForeignKeyполя працює точно так само, як і збереження звичайного поля -


призначте об’єкту правильного типу для відповідного поля. Цей приклад
оновлює blogатрибут Entry екземпляра entry, припускаючи, що відповідні
екземпляри Entryта Blog вже збережені в базі даних (тому ми можемо отримати їх нижче):

>>> from blog.models import Blog, Entry

>>> entry = Entry.objects.get(pk=1)

>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")

>>> entry.blog = cheese_blog

>>> entry.save()

Оновлення ManyToManyFieldтвору працює трохи інакше - використовуйте add()метод у


полі, щоб додати запис до відношення. Цей приклад
додає Authorекземпляр joeдо entryоб’єкта:

>>> from blog.models import Author

>>> joe = Author.objects.create(name="Joe")

>>> entry.authors.add(joe)

Щоб додати кілька записів за ManyToManyFieldодин раз, включіть у виклик кілька


аргументів add(), наприклад:

>>> john = Author.objects.create(name="John")

>>> paul = Author.objects.create(name="Paul")


>>> george = Author.objects.create(name="George")

>>> ringo = Author.objects.create(name="Ringo")

>>> entry.authors.add(john, paul, george, ringo)

Django скаржиться, якщо ви спробуєте призначити або додати об'єкт неправильного типу.

Отримання об'єктів ¶
Щоб отримати об’єкти з бази даних, побудуйте за QuerySetдопомогою Managerкласу a на
вашому класі моделі.

A QuerySetявляє собою колекцію об’єктів з вашої бази даних. Він може мати нуль, один або
багато фільтрів . Фільтри звужують результати запиту на основі заданих параметрів. У
термінах SQL, a QuerySetприрівнюється до SELECTоператора, а фільтр - це обмежувальне
речення, таке як WHEREor LIMIT.

Ви отримуєте QuerySet, використовуючи свої моделі Manager. Кожна модель має принаймні


одну Manager, і вона викликається objectsза замовчуванням. Доступ до нього
безпосередньо через клас моделі, наприклад:

>>> Blog.objects

<django.db.models.manager.Manager object at ...>

>>> b = Blog(name='Foo', tagline='Bar')

>>> b.objects

Traceback:

...

AttributeError: "Manager isn't accessible via Blog instances."

Примітка

Managers доступні лише через класи класів, а не з екземплярів моделі, щоб забезпечити


розділення між операціями “на рівні таблиці” та операціями “на рівні запису”.

Це Managerголовне джерело QuerySetsмоделі. Наприклад, Blog.objects.all()повертає
a, QuerySetщо містить усі Blogоб'єкти в базі даних.

Отримання всіх об'єктів ¶


Найпростіший спосіб отримати об’єкти з таблиці - отримати їх усі. Для цього
використовуйте all()метод на Manager:

>>> all_entries = Entry.objects.all()

all()Метод повертає QuerySetвсі об'єкти в базі даних.

Отримання конкретних об’єктів за допомогою фільтрів ¶

QuerySetПовертається all()описує всі об'єкти в таблиці бази даних. Однак зазвичай вам


потрібно буде вибрати лише підмножину повного набору об’єктів.

Щоб створити таку підмножину, ви уточнюєте початкову QuerySet, додаючи умови


фільтра. Два найпоширеніші способи уточнення QuerySet:

filter(**kwargs)
Повертає новий QuerySetоб'єкт, що відповідає заданим параметрам пошуку.

exclude(**kwargs)
Повертає новий QuerySetоб’єкт, що не відповідає заданим параметрам пошуку.

Параметри пошуку ( **kwargsу наведених вище визначеннях функцій) мають бути у форматі,
описаному в Польових пошуках нижче.

Наприклад, щоб отримати QuerySetзаписи в блозі з 2006 року,


використовуйте filter()так:

Entry.objects.filter(pub_date__year=2006)

Клас менеджера за замовчуванням такий самий, як:

Entry.objects.all().filter(pub_date__year=2006)
Фільтри ланцюга ¶

Результатом удосконалення a QuerySetє сам a QuerySet, тож можна вдосконалити


ланцюжки уточнень. Наприклад:

>>> Entry.objects.filter(

... headline__startswith='What'

... ).exclude(

... pub_date__gte=datetime.date.today()

... ).filter(
... pub_date__gte=datetime.date(2005, 1, 30)

... )

Це бере початкові QuerySetдані всіх записів у базі даних, додає фільтр, потім виключення, а
потім ще один фільтр. Остаточний результат - це QuerySetвміщення всіх записів із
заголовком, що починається з „Що”, опублікованих між 30 січня 2005 року та поточним днем.

Відфільтровані QuerySetунікальні ¶

Кожного разу, коли ви вдосконалюєте a QuerySet, ви отримуєте абсолютно


новий, QuerySetякий жодним чином не пов’язаний з попереднім QuerySet. Кожне
вдосконалення створює окреме і чітке, QuerySetяке можна зберігати, використовувати та
використовувати повторно.

Приклад:

>>> q1 = Entry.objects.filter(headline__startswith="What")

>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())

>>> q3 = q1.filter(pub_date__gte=datetime.date.today())

Ці три QuerySetsокремі. Перший - це основа, QuerySetщо містить усі записи, які містять


заголовок, що починається з “Що”. Другий - це підмножина першого, з додатковими
критеріями, що виключає записи, чиї pub_dateсьогодні чи в майбутньому. Третій - це
підмножина першого, з додатковими критеріями, що відбирає лише ті записи,
чиї pub_dateсьогодні чи в майбутньому. На початковий QuerySet( q1) процес уточнення не
впливає.

QuerySets ледачі ¶

QuerySetsледачі - акт створення QuerySetне передбачає жодної діяльності з базою


даних. Ви можете складати фільтри разом протягом усього дня, і Django насправді не буде
виконувати запит, поки не QuerySetбуде оцінено . Погляньте на цей приклад:

>>> q = Entry.objects.filter(headline__startswith="What")

>>> q = q.filter(pub_date__lte=datetime.date.today())

>>> q = q.exclude(body_text__icontains="food")

>>> print(q)

Хоча це виглядає як три звернення до бази даних, насправді воно потрапляє до бази даних
лише один раз, в останньому рядку ( print(q)). Загалом, результати a QuerySetне
отримуються з бази даних, поки ви їх не "попросите". Коли ви
це зробите, QuerySetбуде оцінено шляхом доступу до бази даних. Детальніше про те, коли
саме відбувається оцінка, див. У розділі Коли оцінюються набори Query .

Отримання одного об'єкта за допомогою get()¶

filter()завжди дасть вам a QuerySet, навіть якщо лише один об'єкт відповідає запиту - в
цьому випадку він буде QuerySetмістити один елемент.

Якщо ви знаєте, що є лише один об’єкт, який відповідає вашому запиту, ви можете
використовувати get()метод, Managerякий повертає об’єкт безпосередньо:

>>> one_entry = Entry.objects.get(pk=1)

Ви можете використовувати будь-який вираз запиту з get(), так само, як і з filter()- знову
ж, дивіться Пошук полів нижче.

Зверніть увагу, що існує різниця між використанням get()та використанням filter()із


фрагментом [0]. Якщо результатів, які відповідають запиту,
немає, get()буде DoesNotExist виняток. Цей виняток є атрибутом класу моделі, за яким
виконується запит - тому у наведеному вище коді, якщо немає Entryоб’єкта з первинним
ключем 1, Django підніме Entry.DoesNotExist.

Так само Django скаржиться, якщо більше одного елемента відповідає get()запиту. У цьому
випадку він зросте MultipleObjectsReturned, що знову є атрибутом самого класу моделі.

Інші QuerySetметоди ¶

Велику частину часу ви будете використовувати all(), get(), filter()і , exclude()коли


потрібно шукати об'єкти з бази даних. Однак це далеко не все, що є; дивіться Посилання на
API QuerySet для повного переліку всіх різних QuerySetметодів.

Обмеження QuerySets ¶

Використовуйте підмножину синтаксису нарізування масивів Python, щоб обмежити


ваші QuerySetрезультати певною кількістю. Це еквівалент SQL LIMITта OFFSETречень.

Наприклад, це повертає перші 5 об’єктів ( ):LIMIT 5

>>> Entry.objects.all()[:5]

Це повертає з шостого по десятий об'єкти ( ):OFFSET 5 LIMIT 5

>>> Entry.objects.all()[5:10]

Негативне індексування (тобто Entry.objects.all()[-1]) не підтримується.


Як правило, нарізання a QuerySetповертає нове QuerySet- воно не оцінює запит. Виняток -
якщо ви використовуєте параметр “step” синтаксису зрізу Python. Наприклад, це фактично
виконає запит, щоб повернути список кожного другого об'єкта перших 10:

>>> Entry.objects.all()[:10:2]

Подальше фільтрування або упорядкування нарізаного набору запитів заборонено через


неоднозначний характер того, як це може працювати.

Щоб отримати один об'єкт, а не список (наприклад ), використовуйте індекс замість


фрагмента. Наприклад, це повертає перше в базі даних після впорядкування записів за
алфавітом за заголовком:SELECT foo FROM bar LIMIT 1Entry

>>> Entry.objects.order_by('headline')[0]

Це приблизно еквівалентно:

>>> Entry.objects.order_by('headline')[0:1].get()

Однак зауважте, що перший з них підніметься, IndexErrorа другий


підніметься, DoesNotExistякщо жоден об’єкт не відповідає заданим
критеріям. Детальніше get()див.

Польові пошуки ¶

Пошук полів - це спосіб, яким ви вказуєте м’ясо SQL- WHEREречення. Вони визначені в якості


ключових слів аргументів до QuerySet методам filter(), exclude()і get().

Основні аргументи ключових слів пошуку мають вигляд field__lookuptype=value. (Це


подвійне підкреслення). Наприклад:

>>> Entry.objects.filter(pub_date__lte='2006-01-01')

перекладає (приблизно) у такий SQL:

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

Як це можливо

Python має можливість визначати функції, які приймають довільні аргументи значення-імені,
імена та значення яких обчислюються під час виконання. Для отримання додаткової
інформації див. Аргументи ключових слів в офіційному посібнику з Python

Поле, вказане в підстановці, повинно бути назвою поля моделі. Однак є один виняток: у
випадку з a ForeignKeyви можете вказати ім'я поля з суфіксом _id. У цьому випадку, як
очікується, параметр value міститиме вихідне значення первинного ключа іноземної
моделі. Наприклад:

>>> Entry.objects.filter(blog_id=4)

Якщо ви передасте недійсний аргумент ключового слова, функція пошуку


підніметься TypeError.

API бази даних підтримує близько двох десятків типів пошуку; повне посилання можна
знайти в поштовому довіднику . Для того, щоб продемонструвати доступне, ось деякі
найпоширеніші пошуки, які ви, мабуть, будете використовувати:

exact

"Точний" збіг. Наприклад:

>>> Entry.objects.get(headline__exact="Cat bites dog")

Згенерував би SQL у таких рядках:

SELECT ... WHERE headline = 'Cat bites dog';

Якщо ви не вказали тип пошуку - тобто, якщо аргумент ключового слова не містить
подвійного підкреслення - передбачається, що тип пошуку exact.

Наприклад, наступні два твердження еквівалентні:

>>> Blog.objects.get(id__exact=14) # Explicit form

>>> Blog.objects.get(id=14) # __exact is implied

Це для зручності, оскільки exactпошук - це звичайний випадок.

iexact

Не відповідає регістру. Отже, запит:

>>> Blog.objects.get(name__iexact="beatles blog")

Сполучати Був би Blogназву , або
навіть ."Beatles Blog""beatles blog""BeAtlES blOG"

contains

Тест стримування з урахуванням регістру. Наприклад:

Entry.objects.get(headline__contains='Lennon')
Грубо перекладається на цей SQL:

SELECT ... WHERE headline LIKE '%Lennon%';

Зауважте, що це буде відповідати заголовку, але


ні .'Today Lennon honored''today lennon honored'

Там також НЕ чутливі до регістру версія, icontains.

startswith, endswith
Починається - з і закінчується - з пошуку відповідно. Існують також версії, що не
враховують регістр, з назвою istartswithта iendswith.

Знову ж таки, це лише дряпає поверхню. Повне посилання можна знайти в довідковому


полі по полю .

Пошук, який охоплює стосунки ¶

Django пропонує потужний та інтуїтивно зрозумілий спосіб "стежити" за взаємозв'язками під


час пошуку, автоматично доглядаючи за SQL- JOINфайлами, за кадром. Щоб охопити
відносини, використовуйте ім’я поля пов’язаних полів у моделях, розділених подвійними
підкресленнями, поки не дійдете до потрібного поля.

Цей приклад отримує всі Entryоб'єкти, Blogчиї name є :'Beatles Blog'

>>> Entry.objects.filter(blog__name='Beatles Blog')

Це охоплення може бути таким глибоким, як вам заманеться.

Це працює і назад. Хоча це , за замовчуванням ви посилаєтесь на "зворотне" відношення у


пошуку, використовуючи нижнє ім'я моделі.can be customized

Цей приклад отримує всі Blogоб'єкти, які мають принаймні один Entry ,


який headlineмістить 'Lennon':

>>> Blog.objects.filter(entry__headline__contains='Lennon')

Якщо ви фільтруєте кілька зв'язків, і одна з проміжних моделей не має значення, яке
відповідає умові фільтра, Django буде обробляти це так, ніби там є порожній (усі
значення NULL), але дійсний об'єкт. Все це означає, що помилок не буде. Наприклад, у цьому
фільтрі:

Blog.objects.filter(entry__authors__name='Lennon')
(якби існувала пов’язана Authorмодель), якби не було author пов’язаного із записом, це
було б розглянуто так, ніби також не було name прикріпленого, а не викликало помилку через
відсутність author. Зазвичай це саме те, що ви хочете, щоб сталося. Єдиний випадок, коли
це може заплутати, це якщо ви використовуєте isnull. Отже:

Blog.objects.filter(entry__authors__name__isnull=True)

поверне Blogоб'єкти, які мають порожнє значення nameна, authorа також ті, що мають


порожнє значення authorна entry. Якщо ви не хочете ці останні об'єкти, ви можете
написати:

Blog.objects.filter(entry__authors__isnull=False,
entry__authors__name__isnull=True)
Розширення багатозначних відносин ¶

При фільтрації об'єкта , заснований на ManyToManyFieldабо в зворотному


напрямку ForeignKey, існують два різних види фільтра ви можете бути зацікавлені в.
Розглянемо Blog/ Entryвідносини ( Blogщоб Entryце один-ко-многим). Нам може бути
цікаво знайти блоги, у яких є заголовок із заголовком "Леннон", який був опублікований у
2008 році. Або ми можемо захотіти знайти блоги, у яких в заголовку є запис із написом
"Леннон" , а також був опублікований у 2008 р. Оскільки з одним записом пов’язано кілька
записів Blog, обидва ці запити можливі і мають сенс у деяких ситуаціях.

Такий самий тип ситуації виникає з a ManyToManyField. Наприклад, якщо у Entrya


є ManyToManyFieldвиклик tags, ми можемо захотіти знайти записи, пов’язані з тегами, що
називаються “музика” та “діапазони”, або ми можемо захотіти запис, що містить тег із
назвою “музика” та статусом “загальнодоступний” .

Для вирішення обох цих ситуацій Django має послідовний спосіб


обробки filter()викликів. Все, що знаходиться в одному filter()виклику,
застосовується одночасно для фільтрації елементів, що відповідають усім цим
вимогам. Послідовні filter()виклики додатково обмежують набір об'єктів, але для
багатозначних відносин вони застосовуються до будь-якого об'єкта, пов'язаного з
первинною моделлю, не обов'язково до тих об'єктів, які були обрані
попереднім filter()викликом.

Це може здатися дещо заплутаним, тому, сподіваємось, приклад пояснить. Щоб вибрати всі
блоги, які містять записи із заголовком «Леннон» та опубліковані в 2008 році (той самий запис
відповідає обом умовам), ми б написали:

Blog.objects.filter(entry__headline__contains='Lennon',
entry__pub_date__year=2008)
Щоб вибрати всі блоги, що містять запис із заголовком "Леннон"  , а також запис,
опублікований у 2008 році, ми пишемо:

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry_
_pub_date__year=2008)

Припустимо, що існує лише один щоденник, який містив як записи, що містять “Леннон”,


так і статті 2008 року, але жоден із записів 2008 року не містить “Леннон” . Перший запит не
повертає жодних блогів, але другий запит повертає той самий щоденник.

У другому прикладі перший фільтр обмежує набір запитів усіма тими блогами, які пов’язані із
записами із заголовком “Lennon” . Другий фільтр обмежує безліч блогів додаткових до
тих , які також пов'язані з записами , які були опубліковані в 2008 році записи , вибрані
з допомогою другого фільтра , може або не може бути таким же , як записи в першому
фільтрі. Ми фільтруємо Blogелементи за кожною заявою про фільтр, а не
за Entryелементами.

Примітка

Поведінка filter()запитів, що охоплюють багатозначні відносини, як описано вище, не


реалізовано однаково для exclude(). Натомість умови в одному exclude() дзвінку не
обов’язково стосуватимуться одного і того ж товару.

Наприклад, наступний запит виключає блоги, що містять як записи із заголовком «Леннон»,


так і записи, опубліковані у 2008 році:

Blog.objects.exclude(

entry__headline__contains='Lennon',

entry__pub_date__year=2008,

Однак, на відміну від поведінки під час використання filter(), це не обмежує блоги на


основі записів, які відповідають обом умовам. Для цього, тобто для вибору всіх блогів, які не
містять записів, опублікованих разом із «Ленноном», які були опубліковані в 2008 році,
потрібно зробити два запити:

Blog.objects.exclude(

entry__in=Entry.objects.filter(

headline__contains='Lennon',

pub_date__year=2008,
),

)
Фільтри можуть посилатися на поля на моделі ¶

У наведених на сьогодні прикладах ми створили фільтри, які порівнюють значення поля


моделі з константою. Але що, якщо ви хочете порівняти значення поля моделі з іншим полем
на тій самій моделі?

Django передбачає можливість такого порівняння. Екземпляри виступають як посилання на


поле моделі в межах запиту. Потім ці посилання можна використовувати у фільтрах запитів
для порівняння значень двох різних полів в одному екземплярі моделі.F expressionsF()

Наприклад, щоб знайти список усіх записів у блозі, у яких було більше коментарів, ніж
пінгбеків, ми створюємо F()об’єкт для посилання на кількість пінгбеків і використовуємо
цей F()об’єкт у запиті:

>>> from django.db.models import F

>>>
Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))

Django підтримує використання додавання, віднімання, множення, ділення, модуля та


арифметики степенів з F()об'єктами як з константами, так і з іншими F()об'єктами. Щоб
знайти всі записи в блозі з більш ніж вдвічі більшою кількістю коментарів, ніж пінгбеки, ми
модифікуємо запит:

>>>
Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks')
* 2)

Щоб знайти всі записи, де рейтинг запису менший за суму підрахунку пінгбеків та підрахунку
коментарів, ми б видали запит:

>>> Entry.objects.filter(rating__lt=F('number_of_comments') +
F('number_of_pingbacks'))

Ви також можете використовувати позначення подвійного підкреслення, щоб охопити


відносини в F()об'єкті. F()Об'єкт з подвійним підкресленням буде ввести будь-які
об'єднання , необхідний для доступу пов'язаного об'єкта. Наприклад, щоб отримати всі
записи, де ім’я автора збігається з ім’ям блогу, ми можемо подати запит:

>>> Entry.objects.filter(authors__name=F('blog__name'))
Для полів дати та дати / часу можна додати або відняти timedeltaоб’єкт. Нижче подано всі
записи, які були змінені більше ніж через 3 дні після їх публікації:

>>> from datetime import timedelta

>>> Entry.objects.filter(mod_date__gt=F('pub_date') +
timedelta(days=3))

Ці F()об'єкти підтримують бітові операції


на .bitand(), .bitor(), .bitxor(), .bitrightshift(), і .bitleftshift(). Наприклад:

>>> F('somefield').bitand(16)

Oracle

Oracle не підтримує побітові операції XOR.

Змінено в Django 3.1:


Підтримка .bitxor()була додана.
Вирази можуть посилатися на перетворення ¶
Нове у Django 3.2.

Django підтримує використання перетворень у виразах.

Наприклад, щоб знайти всі Entryоб’єкти, опубліковані того ж року, коли вони востаннє були
змінені:

>>> Entry.objects.filter(pub_date__year=F('mod_date__year'))

Щоб знайти найдавніший рік публікації статті, ми можемо оформити запит:

>>>
Entry.objects.aggregate(first_published_year=Min('pub_date__year'))

У цьому прикладі знайдено значення найвищого рейтингу та загальну кількість коментарів


до всіх записів за кожен рік:

>>> Entry.objects.values('pub_date__year').annotate(

... top_rating=Subquery(

... Entry.objects.filter(

... pub_date__year=OuterRef('pub_date__year'),

... ).order_by('-rating').values('rating')[:1]
... ),

... total_comments=Sum('number_of_comments'),

... )

pkПошуку ярлик ¶

Для зручності Django пропонує pkярлик пошуку, який означає «первинний ключ».

У прикладі Blogмоделі первинним ключем є idполе, тому ці три твердження еквівалентні:

>>> Blog.objects.get(id__exact=14) # Explicit form

>>> Blog.objects.get(id=14) # __exact is implied

>>> Blog.objects.get(pk=14) # pk implies id__exact

Використання pkне обмежується __exactзапитами - будь-який термін запиту можна


поєднати з pkвиконанням запиту за первинним ключем моделі:

# Get blogs entries with id 1, 4 and 7

>>> Blog.objects.filter(pk__in=[1,4,7])

# Get all blog entries with id > 14

>>> Blog.objects.filter(pk__gt=14)

pkпошук також працює через об’єднання. Наприклад, ці три твердження еквівалентні:

>>> Entry.objects.filter(blog__id__exact=3) # Explicit form

>>> Entry.objects.filter(blog__id=3) # __exact is implied

>>> Entry.objects.filter(blog__pk=3) # __pk implies


__id__exact

Уникнення знаків відсотків та підкреслення у LIKEвисловлюваннях ¶

В поле пошуку , які прирівнюють до LIKEзвітності SQL


( iexact, contains, icontains, startswith, istartswith, endswith і iendswith) буде
автоматично уникнути двох спеціальних символів , які використовуються в LIKEзвітності -
знак відсотка і підкреслення. (У LIKE заяві знак відсотка позначає багатозначний
підстановочний знак, а підкреслення означає односимвольний підстановний знак.)
Це означає, що речі повинні працювати інтуїтивно, щоб абстракція не витікала. Наприклад,
щоб отримати всі записи, що містять знак відсотка, використовуйте знак відсотка як будь-
який інший символ:

>>> Entry.objects.filter(headline__contains='%')

Django подбає про цитування для вас; отриманий SQL буде виглядати приблизно так:

SELECT ... WHERE headline LIKE '%\%%';

Те саме стосується підкреслення. І знаки відсотків, і підкреслення обробляються для вас


прозоро.

Кешування та QuerySets ¶

Кожен QuerySetмістить кеш для мінімізації доступу до бази даних. Розуміння того, як це


працює, дозволить вам написати найбільш ефективний код.

У новоствореному QuerySetкеші порожньо. Перший раз, коли QuerySetоцінюється a - і,


отже, відбувається запит до бази даних - Django зберігає результати запиту в QuerySetкеш-
пам’яті та повертає результати, явно запитані (наприклад, наступний елемент,
якщо QuerySetітерація надсилається). Подальші оцінки QuerySetповторного використання
кешованих результатів.

Пам’ятайте про цю поведінку кешування, оскільки вона може вас вкусити, якщо ви
неправильно використовуєте свої QuerySets. Наприклад, наступне створить два QuerySets,
оцінить їх і викине:

>>> print([e.headline for e in Entry.objects.all()])

>>> print([e.pub_date for e in Entry.objects.all()])

Це означає, що один і той же запит до бази даних буде виконуватися двічі, фактично
подвоюючи завантаження бази даних. Крім того, існує ймовірність, що два списки можуть не
включати однакові записи бази даних, оскільки Entryвони могли бути додані або видалені
за частку секунди між двома запитами.

Щоб уникнути цієї проблеми, збережіть QuerySetта повторно використовуйте її:

>>> queryset = Entry.objects.all()

>>> print([p.headline for p in queryset]) # Evaluate the query set.

>>> print([p.pub_date for p in queryset]) # Re-use the cache from the


evaluation.
Коли QuerySets не кешуються ¶

Набори запитів не завжди кешують свої результати. При обчисленні лише частини набору


запитів перевіряється кеш, але якщо він не заповнений, елементи, повернені наступним
запитом, не кешуються. Зокрема, це означає, що обмеження набору запитів за допомогою
фрагмента масиву або індексу не заповнить кеш.

Наприклад, неодноразове отримання певного індексу в об’єкті запитів кожного разу


запитуватиме базу даних:

>>> queryset = Entry.objects.all()

>>> print(queryset[5]) # Queries the database

>>> print(queryset[5]) # Queries the database again

Однак, якщо весь набір запитів уже оцінений, натомість буде перевірено кеш:

>>> queryset = Entry.objects.all()

>>> [entry for entry in queryset] # Queries the database

>>> print(queryset[5]) # Uses cache

>>> print(queryset[5]) # Uses cache

Ось кілька прикладів інших дій, які призведуть до того, що весь набір запитів оцінюється, а
отже, заповнює кеш:

>>> [entry for entry in queryset]

>>> bool(queryset)

>>> entry in queryset

>>> list(queryset)

Примітка

Просто друк набору запитів не заповнить кеш. Це пов’язано з тим, що


виклик __repr__()лише повертає фрагмент усього набору запитів.

Запит JSONField¶
Впровадження пошукових запитів відрізняється JSONField, головним чином, завдяки
існуванню ключових перетворень. Для демонстрації ми використаємо такий приклад моделі:

from django.db import models


class Dog(models.Model):

name = models.CharField(max_length=200)

data = models.JSONField(null=True)

def __str__(self):

return self.name

Зберігання та запит на None¶

Як і у інших полях, зберігання Noneяк значення поля зберігатиме його як SQL NULL. Хоча це


не рекомендується, можна зберігати скаляр JSON nullзамість SQL NULLза
допомогою Value('null').

Незалежно від того, яке з значень зберігається, при отриманні з бази даних подання Python
скаляра JSON nullє однаковим із SQL NULL, тобто None. Тому їх важко розрізнити.

Це стосується лише Noneзначення верхнього рівня поля. Якщо None знаходиться


всередині listабо dict, це завжди буде інтерпретовано як JSON null.

Під час запиту Noneзначення завжди інтерпретується як JSON null. Для запиту


SQL NULLвикористовуйте isnull:

>>> Dog.objects.create(name='Max', data=None) # SQL NULL.

<Dog: Max>

>>> Dog.objects.create(name='Archie', data=Value('null')) # JSON


null.

<Dog: Archie>

>>> Dog.objects.filter(data=None)

<QuerySet [<Dog: Archie>]>

>>> Dog.objects.filter(data=Value('null'))

<QuerySet [<Dog: Archie>]>

>>> Dog.objects.filter(data__isnull=True)
<QuerySet [<Dog: Max>]>

>>> Dog.objects.filter(data__isnull=False)

<QuerySet [<Dog: Archie>]>

Якщо ви не впевнені , що ви хочете працювати з SQL NULLзначеннями, розглянути


можливість створення null=Falseі забезпечення відповідного по замовчуванням для
порожніх значень, наприклад default=dict.

Примітка

Зберігання скаляра JSON nullне порушує null=False.

Перетворення ключа, індексу та шляху ¶

Для запиту на основі даного ключа словника використовуйте цей ключ як ім’я підстановки:

>>> Dog.objects.create(name='Rufus', data={

... 'breed': 'labrador',

... 'owner': {

... 'name': 'Bob',

... 'other_pets': [{

... 'name': 'Fishy',

... }],

... },

... })

<Dog: Rufus>

>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner':


None})

<Dog: Meg>

>>> Dog.objects.filter(data__breed='collie')

<QuerySet [<Dog: Meg>]>

Кілька клавіш можуть бути пов’язані ланцюгами, щоб утворити пошук шляху:
>>> Dog.objects.filter(data__owner__name='Bob')

<QuerySet [<Dog: Rufus>]>

Якщо ключ є цілим числом, він буде інтерпретований як перетворення індексу в масиві:

>>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy')

<QuerySet [<Dog: Rufus>]>

Якщо ключ, який ви хочете запитати, зіткнеться з назвою іншого пошуку,


використовуйте containsзамість нього пошук.

Для запиту відсутніх ключів використовуйте isnullпошук:

>>> Dog.objects.create(name='Shep', data={'breed': 'collie'})

<Dog: Shep>

>>> Dog.objects.filter(data__owner__isnull=True)

<QuerySet [<Dog: Shep>]>

Примітка

Наведені вище приклади пошуку неявно використовують exactпошук. Основне, індекс, і


шлях перетворення також може бути пов'язане
з: icontains, endswith, iendswith, iexact, regex, iregex, startswith, istartswith, l
t, lte, gt, і gte, а також витоками і ключовими операціями пошуком .

Примітка

Через те, як працюють запити ключових шляхів, exclude()і filter()не гарантується


отримання вичерпних наборів. Якщо ви хочете включити об’єкти, які не мають шляху,
додайте isnullпошук.

Увага

Оскільки будь-який рядок може бути ключем в об'єкті JSON, будь-який пошук, крім
перелічених нижче, буде інтерпретований як пошук ключа. Помилок не виникає. Будьте
особливо обережні при введенні помилок і завжди перевіряйте, чи працюють ваші запити, як
ви задумали.

Користувачі MariaDB та Oracle

Використання order_by()перетворень ключа, індексу або шляху дозволить сортувати


об’єкти за допомогою рядкового представлення значень. Це тому, що MariaDB та Oracle
Database не надають функції, яка перетворює значення JSON у їх еквівалентні значення SQL.
Користувачі Oracle

У Oracle Database використання Noneв якості exclude()запиту значення у запиті поверне


об’єкти, які не мають nullзначення у даному шляху, включаючи об’єкти, що не мають
шляху. В інших інтерфейсах бази даних запит повертає об’єкти, які мають шлях, а значення -
ні null.

Користувачі PostgreSQL

У PostgreSQL, якщо використовується лише один ключ або індекс, використовується


оператор SQL ->. Якщо використовується кілька операторів, тоді
використовується #>оператор.

Обмеження та пошук ключів ¶


contains¶

Пошук containsзамінено на JSONField. Повернені об'єкти - це ті, де всі задані dictпари


ключ-значення містяться у верхньому рівні поля. Наприклад:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador',


'owner': 'Bob'})

<Dog: Rufus>

>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner':


'Bob'})

<Dog: Meg>

>>> Dog.objects.create(name='Fred', data={})

<Dog: Fred>

>>> Dog.objects.filter(data__contains={'owner': 'Bob'})

<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

>>> Dog.objects.filter(data__contains={'breed': 'collie'})

<QuerySet [<Dog: Meg>]>

Oracle та SQLite

contains не підтримується на Oracle та SQLite.


contained_by¶
Це зворотне значення containsпошуку - поверненими об’єктами будуть ті, де пари ключ-
значення на об’єкті є підмножиною об’єктів у переданому значенні. Наприклад:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador',


'owner': 'Bob'})

<Dog: Rufus>

>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner':


'Bob'})

<Dog: Meg>

>>> Dog.objects.create(name='Fred', data={})

<Dog: Fred>

>>> Dog.objects.filter(data__contained_by={'breed': 'collie',


'owner': 'Bob'})

<QuerySet [<Dog: Meg>, <Dog: Fred>]>

>>> Dog.objects.filter(data__contained_by={'breed': 'collie'})

<QuerySet [<Dog: Fred>]>

Oracle та SQLite

contained_by не підтримується на Oracle та SQLite.


has_key¶

Повертає об'єкти, де даний ключ знаходиться у верхньому рівні даних. Наприклад:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})

<Dog: Rufus>

>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner':


'Bob'})

<Dog: Meg>

>>> Dog.objects.filter(data__has_key='owner')

<QuerySet [<Dog: Meg>]>


has_keys¶
Повертає об'єкти, де всі задані ключі знаходяться у верхньому рівні даних. Наприклад:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})

<Dog: Rufus>

>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner':


'Bob'})

<Dog: Meg>

>>> Dog.objects.filter(data__has_keys=['breed', 'owner'])

<QuerySet [<Dog: Meg>]>


has_any_keys¶

Повертає об'єкти, де будь-який із заданих ключів знаходиться у верхньому рівні


даних. Наприклад:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})

<Dog: Rufus>

>>> Dog.objects.create(name='Meg', data={'owner': 'Bob'})

<Dog: Meg>

>>> Dog.objects.filter(data__has_any_keys=['owner', 'breed'])

<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

Складні пошуки з Qоб’єктами ¶


Запити аргументів ключових слів - в filter()і т. Д. - складаються разом із «І». Якщо вам
потрібно виконати більш складні запити (наприклад, запити з ORоператорами), ви можете
використовувати .Q objects

A ( ) - це об'єкт, який використовується для інкапсуляції набору аргументів ключового


слова. Ці аргументи ключового слова вказані, як у "Польових пошуках"
вище.Q objectdjango.db.models.Q

Наприклад, цей Qоб’єкт інкапсулює один LIKEзапит:

from django.db.models import Q

Q(question__startswith='What')
Qоб'єкти можна комбінувати за допомогою операторів &і |. Коли оператор використовується
для двох Qоб'єктів, він видає новий Qоб'єкт.

Наприклад, це твердження дає один Qоб’єкт, який представляє “АБО”


двох "question__startswith"запитів:

Q(question__startswith='Who') | Q(question__startswith='What')

Це еквівалентно наступному реченню SQL WHERE:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

Ви можете скласти заяви довільної складності шляхом об'єднання Qоб'єктів з &і |


операторами і використання вступної угрупованням. Також Q об'єкти можна заперечувати за
допомогою ~оператора, дозволяючи комбіновані пошуки, що поєднують як звичайний запит,
так і заперечений ( NOT) запит:

Q(question__startswith='Who') | ~Q(pub_date__year=2005)

Кожна функція пошуку , яка приймає ключові слова , аргументи


(наприклад filter(), exclude(), get()) також може бути передана один або
кілька Qоб'єктів , як позиційні (неназвані) аргументи. Якщо ви надаєте декілька Qаргументів
об'єкта функції пошуку, аргументи будуть "І" разом. Наприклад:

Poll.objects.get(

Q(question__startswith='Who'),

Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))

... приблизно перекладається на SQL:

SELECT * from polls WHERE question LIKE 'Who%'

AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

Функції пошуку можуть поєднувати використання Qоб’єктів та аргументів ключових слів. Усі


аргументи, надані функції пошуку (будь то аргументи ключових слів або Q об'єкти), мають
значення "І" разом. Однак, якщо надається Qоб'єкт, він повинен передувати визначенню
будь-яких аргументів ключового слова. Наприклад:

Poll.objects.get(

Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),


question__startswith='Who',

... буде дійсним запитом, еквівалентним попередньому прикладу; але:

# INVALID QUERY

Poll.objects.get(

question__startswith='Who',

Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))

... буде недійсним.

Дивитися також

В або Lookups приклади в модульних тестах Django показують деякі можливі застосування Q.

Порівняння об'єктів ¶
Для порівняння двох екземплярів моделі, використовуйте стандартний оператор порівняння
Python, подвійний знак рівності: ==. За лаштунками це порівнює значення первинного ключа
двох моделей.

Використовуючи Entryприклад вище, наступні два твердження еквівалентні:

>>> some_entry == other_entry

>>> some_entry.id == other_entry.id

Якщо первинний ключ моделі не викликається id, не біда. Порівняння завжди


використовуватимуть первинний ключ, як би він не називався. Наприклад, якщо
викликається поле первинного ключа моделі name, ці два твердження еквівалентні:

>>> some_obj == other_obj

>>> some_obj.name == other_obj.name

Видалення об’єктів ¶
Зручно називається метод видалення delete(). Цей метод негайно видаляє об'єкт і
повертає кількість видалених об'єктів та словник із кількістю видалень за типом
об'єкта. Приклад:
>>> e.delete()

(1, {'weblog.Entry': 1})

Ви також можете видаляти об'єкти масово. У кожного QuerySetє delete()метод, який


видаляє всіх його членів QuerySet.

Наприклад, це видаляє всі Entryоб’єкти з pub_date2005 роком:

>>> Entry.objects.filter(pub_date__year=2005).delete()

(5, {'webapp.Entry': 5})

Майте на увазі, що це, по можливості, буде виконуватися виключно в SQL, і


тому delete()методи окремих екземплярів об'єктів не обов'язково будуть викликані під час
процесу. Якщо ви вказали власний delete()метод для класу моделі і хочете переконатися,
що він викликається, вам потрібно буде «вручну» видалити екземпляри цієї моделі
(наприклад, перебираючи a QuerySetта викликаючи delete()кожен об’єкт окремо), а не
використовуючи основний delete()метод a QuerySet.

Коли Django видаляє об'єкт, за замовчуванням він емулює поведінку обмеження SQL -
іншими словами, будь-які об'єкти, у яких зовнішні ключі вказували на об'єкт, який потрібно
видалити, будуть видалятися разом із ним. Наприклад:ON DELETE CASCADE

b = Blog.objects.get(pk=1)

# This will delete the Blog and all of its Entry objects.

b.delete()

Цю поведінку каскаду можна налаштувати за допомогою on_deleteаргументу ForeignKey.

Зверніть увагу, що delete()це єдиний QuerySetметод, який не піддається Managerсамому


собі. Це механізм безпеки, який запобігає випадковому запиту Entry.objects.delete()та
видаленню всіх записів. Якщо ви дійсно хочете видалити всі об'єкти, то ви повинні явно
запросити повний набір запитів:

Entry.objects.all().delete()

Копіювання екземплярів моделі ¶


Хоча не існує вбудованого методу копіювання екземплярів моделі, можна легко створити
новий екземпляр із скопійованими значеннями всіх полів. У найпростішому випадку, ви
можете встановити pkв Noneі _state.addingдо True. На прикладі нашого блогу:
blog = Blog(name='My blog', tagline='Blogging is easy')

blog.save() # blog.pk == 1

blog.pk = None

blog._state.adding = True

blog.save() # blog.pk == 2

Справа ускладнюється, якщо ви використовуєте спадщину. Розглянемо підклас Blog:

class ThemeBlog(Blog):

theme = models.CharField(max_length=200)

django_blog = ThemeBlog(name='Django', tagline='Django is easy',


theme='python')

django_blog.save() # django_blog.pk == 3

З - за того , як працює успадкування, ви повинні


встановити , як pkі idдо Noneі _state.addingдо True:

django_blog.pk = None

django_blog.id = None

django_blog._state.adding = True

django_blog.save() # django_blog.pk == 4

Цей процес не копіює відносини, які не є частиною таблиці бази даних


моделі. Наприклад, Entryмає ManyToManyFieldдо Author. Після продублювання запису
потрібно встановити відношення "багато-до-багатьох" для нового запису:

entry = Entry.objects.all()[0] # some previous entry

old_authors = entry.authors.all()

entry.pk = None

entry._state.adding = True
entry.save()

entry.authors.set(old_authors)

Для a OneToOneField, ви повинні продублювати пов’язаний об’єкт і призначити його полю


нового об’єкта, щоб уникнути порушення унікального обмеження «один на
один». Наприклад, припускаючи, що entryвже дублюється, як зазначено вище:

detail = EntryDetail.objects.all()[0]

detail.pk = None

detail._state.adding = True

detail.entry = entry

detail.save()

Оновлення кількох об’єктів одночасно ¶


Іноді потрібно встановити для поля певне значення для всіх об’єктів у QuerySet. Ви можете
зробити це update()методом. Наприклад:

# Update all the headlines with pub_date in 2007.

Entry.objects.filter(pub_date__year=2007).update(headline='Everything
is the same')

За ForeignKey допомогою цього методу можна встановити лише поля та поля, що не мають


відношення . Щоб оновити поле, яке не стосується, введіть нове значення як константу. Щоб
оновити ForeignKeyполя, встановіть нове значення як новий екземпляр моделі, на який
потрібно вказати. Наприклад:

>>> b = Blog.objects.get(pk=1)

# Change every Entry so that it belongs to this Blog.

>>> Entry.objects.all().update(blog=b)

update()Метод застосовується миттєво і повертає кількість рядків , відповідних за запитом


(який не може бути дорівнює числу рядків оновлених , якщо деякі рядки вже є нове
значення). Єдиним обмеженням для QuerySetоновлення є те, що воно може отримати
доступ лише до однієї таблиці бази даних: основної таблиці моделі. Ви можете фільтрувати
на основі пов’язаних полів, але ви можете оновлювати лише стовпці в основній таблиці
моделі. Приклад:

>>> b = Blog.objects.get(pk=1)

# Update all the headlines belonging to this Blog.

>>> Entry.objects.filter(blog=b).update(headline='Everything is the


same')

Майте на увазі, що update()метод перетворюється безпосередньо на оператор SQL. Це


масова операція для безпосереднього оновлення. Він не запускає жодних save()методів на
ваших моделях, не випромінює сигнали pre_saveабо post_save(які є наслідком
виклику save()), або не виконує auto_nowпараметр поля. Якщо ви хочете зберегти кожен
елемент в a QuerySet і переконатися, що save()метод викликається в кожному екземплярі,
вам не потрібна ніяка спеціальна функція для обробки цього. Зациклюйтеся над ними і
зателефонуйте save():

for item in my_queryset:

item.save()

Виклики для оновлення також можна використовувати для оновлення одного поля на основі
значення іншого поля в моделі. Це особливо корисно для збільшення лічильників на основі їх
поточного значення. Наприклад, щоб збільшити кількість пінгбеків для кожного запису в
блозі:F expressions

>>>
Entry.objects.all().update(number_of_pingbacks=F('number_of_pingbacks
') + 1)

Однак, на відміну від F()об'єктів у реченнях фільтра та виключення, ви не можете вводити


об'єднання, коли використовуєте F()об'єкти в оновленні - ви можете посилатися лише на
поля, локальні для моделі, яка оновлюється. Якщо ви намагаєтеся ввести об'єднання
з F()об'єктом, FieldErrorбуде піднято:

# This will raise a FieldError

>>> Entry.objects.update(headline=F('blog__name'))

Пов’язані об’єкти ¶
Коли ви визначаєте зв'язок у моделі (тобто
a ForeignKey, OneToOneFieldабо ManyToManyField), екземпляри цієї моделі матимуть
зручний API для доступу до пов'язаних об'єктів.

Використання моделей у верхній частині цієї сторінки, наприклад, Entryоб'єкт e може


отримати пов'язаний з ним Blogоб'єкт, доступ до blogатрибуту: e.blog.

(За лаштунками ця функціональність реалізована дескрипторами Python . Це насправді для


вас не повинно мати значення, але ми вказуємо на це тут для цікавих.)

Django також створює засоби доступу API для "іншої" сторони відносин - посилання від
пов'язаної моделі до моделі, яка визначає відносини. Наприклад, Blogоб'єкт bмає доступ до
списку всіх пов'язаних з ними Entryоб'єктів через entry_setатрибут: b.entry_set.all().

Всі приклади в цьому розділі використовують зразок Blog, Authorі Entry модель , певний у


верхній частині цієї сторінки.

Один-ко-многим ¶
Вперед ¶

Якщо модель має a ForeignKey, екземпляри цієї моделі матимуть доступ до пов'язаного
(чужого) об'єкта через атрибут моделі.

Приклад:

>>> e = Entry.objects.get(id=2)

>>> e.blog # Returns the related Blog object.

Ви можете отримати та встановити за допомогою атрибута зовнішнього ключа. Як і слід було


очікувати, зміни зовнішнього ключа не зберігаються в базі даних, поки ви не
зателефонуєте save(). Приклад:

>>> e = Entry.objects.get(id=2)

>>> e.blog = some_blog

>>> e.save()

Якщо ForeignKeyполе null=Trueвстановило (тобто воно дозволяє NULLзначення), ви


можете призначити Noneвидалити відношення. Приклад:

>>> e = Entry.objects.get(id=2)

>>> e.blog = None


>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"

Прямий доступ до відносин один-до-багатьох кешується під час першого доступу до


відповідного об'єкта. Подальші звернення до зовнішнього ключа на тому самому екземплярі
об’єкта кешуються. Приклад:

>>> e = Entry.objects.get(id=2)

>>> print(e.blog) # Hits the database to retrieve the associated


Blog.

>>> print(e.blog) # Doesn't hit the database; uses cached version.

Зверніть увагу, що метод рекурсивно попередньо заповнює кеш усіх відносин "один до
багатьох". Приклад:select_related() QuerySet

>>> e = Entry.objects.select_related().get(id=2)

>>> print(e.blog) # Doesn't hit the database; uses cached version.

>>> print(e.blog) # Doesn't hit the database; uses cached version.


Наступні стосунки “відсталі” ¶

Якщо модель має a ForeignKey, екземпляри моделі із зовнішнім ключем матимуть доступ до
a, Managerякий повертає всі екземпляри першої моделі. За замовчуванням
це Managerім'я FOO_set, де FOOназва вихідної моделі, нижня. Це Managerповертає
дані QuerySets, які можна відфільтрувати та маніпулювати ними, як описано в розділі
«Отримання об’єктів» вище.

Приклад:

>>> b = Blog.objects.get(id=1)

>>> b.entry_set.all() # Returns all Entry objects related to Blog.

# b.entry_set is a Manager that returns QuerySets.

>>> b.entry_set.filter(headline__contains='Lennon')

>>> b.entry_set.count()

Ви можете замінити FOO_setназву, встановивши related_nameпараметр


у ForeignKeyвизначенні. Наприклад, якщо Entry модель була змінена , наведений вище
приклад коду буде виглядати
так:blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries
')

>>> b = Blog.objects.get(id=1)

>>> b.entries.all() # Returns all Entry objects related to Blog.

# b.entries is a Manager that returns QuerySets.

>>> b.entries.filter(headline__contains='Lennon')

>>> b.entries.count()
Використання спеціального диспетчера реверсів ¶

За замовчуванням RelatedManagerвикористовується для зворотних відносин - це


підклас менеджера за замовчуванням для цієї моделі. Якщо ви хочете вказати іншого
менеджера для даного запиту, ви можете використовувати такий синтаксис:

from django.db import models

class Entry(models.Model):

#...

objects = models.Manager() # Default Manager

entries = EntryManager() # Custom Manager

b = Blog.objects.get(id=1)

b.entry_set(manager='entries').all()

Якщо EntryManagerу своєму get_queryset() методі виконується фільтрація за


замовчуванням , ця фільтрація застосовується до all()виклику.

Вказавши спеціальний зворотний менеджер, також можна викликати його власні методи:

b.entry_set(manager='entries').is_published()
Додаткові методи обробки пов'язаних об'єктів ¶
На додаток до QuerySetметодів, визначених у "Отримання об'єктів" вище, є додаткові
методи, що використовуються для обробки набору пов'язаних об'єктів. Короткий зміст
кожного з них наведено нижче, а повні подробиці можна знайти у посиланні на пов’язані
об’єкти .ForeignKey Manager

add(obj1, obj2, ...)
Додає зазначені об'єкти моделі до пов'язаного набору об'єктів.

create(**kwargs)
Створює новий об'єкт, зберігає його та поміщає у відповідний набір об'єктів. Повертає
новостворений об'єкт.

remove(obj1, obj2, ...)
Видаляє вказані об’єкти моделі із пов’язаного набору об’єктів.

clear()
Видаляє всі об’єкти із пов’язаного набору об’єктів.

set(objs)
Замініть набір пов’язаних об’єктів.

Щоб призначити членів пов'язаного набору, використовуйте set()метод з ітерабельними


екземплярами об'єктів. Наприклад, якщо e1і e2є Entry екземплярами:

b = Blog.objects.get(id=1)

b.entry_set.set([e1, e2])

Якщо clear()метод доступний, будь-які вже існуючі об'єкти буде видалено з


попередніх, entry_setперш ніж усі об'єкти в ітерабельному (в даному випадку список)
будуть додані до набору. Якщо clear()метод НЕ доступний, всі об'єкти в ітерації будуть
додані без видалення будь - яких існуючих елементів.

Кожна операція "зворотного", описана в цьому розділі, негайно впливає на базу


даних. Кожне додавання, створення та видалення негайно та автоматично зберігаються у
базі даних.

Відносини багато-до-багатьох ¶

Обидва кінці відносин багато-до-багатьох отримують автоматичний доступ API до іншого


кінця. API працює подібно до „відсталих” відносин „один до багатьох” вище.
Одна відмінність полягає в іменуванні атрибутів: модель, яка
визначає ManyToManyFieldвикористання, використовує ім’я атрибута самого поля, тоді як
„зворотна” модель використовує нижню літеру назви моделі оригінальної моделі
плюс '_set'(як і зворотні відносини один-до-багатьох) .

Приклад полегшує це розуміння:

e = Entry.objects.get(id=3)

e.authors.all() # Returns all Author objects for this Entry.

e.authors.count()

e.authors.filter(name__contains='John')

a = Author.objects.get(id=5)

a.entry_set.all() # Returns all Entry objects for this Author.

Мовляв ForeignKey, ManyToManyFieldможу вказати related_name. У наведеному вище


прикладі, якби ManyToManyFieldin Entryвказав related_name='entries', тоді
кожен Authorекземпляр мав би entriesатрибут замість entry_set.

Ще одна відмінність від один-ко-многим, що на додаток до моделі


випадках add(), set()і remove()методи по багатьом-ко-многим набувати значень
первинного ключа. Наприклад, якщо e1і e2є Entryекземплярами, то ці set()виклики
працюють однаково:

a = Author.objects.get(id=5)

a.entry_set.set([e1, e2])

a.entry_set.set([e1.pk, e2.pk])

Індивідуальні стосунки ¶

Взаємозв'язки "один на один" дуже схожі на відносини "багато на один". Якщо ви визначите


а OneToOneFieldна своїй моделі, екземпляри цієї моделі матимуть доступ до пов'язаного
об'єкта через атрибут моделі.

Наприклад:

class EntryDetail(models.Model):

entry = models.OneToOneField(Entry, on_delete=models.CASCADE)


details = models.TextField()

ed = EntryDetail.objects.get(id=2)

ed.entry # Returns the related Entry object.

Різниця полягає в “зворотних” запитах. Пов’язана модель у відносинах один-на-один також


має доступ до Managerоб'єкта, але який Managerпредставляє один об'єкт, а не сукупність
об'єктів:

e = Entry.objects.get(id=2)

e.entrydetail # returns the related EntryDetail object

Якщо жодному об'єкту не було призначено цей зв'язок, Django


викличе DoesNotExistвиняток.

Екземпляри можуть бути присвоєні зворотному відношенню таким же чином, як ви


призначили пряме відношення:

e.entrydetail = ed

Як можливі зворотні стосунки? ¶

Інші об’єктно-реляційні картографа вимагають від вас визначити відносини з обох


сторін. Розробники Django вважають, що це є порушенням принципу СУХОГО (Не
повторюйся), тому Django вимагає, щоб ти визначив стосунки з одного боку.

Але як це можливо, враховуючи те, що клас моделі не знає, які інші класи моделей пов’язані з
ним, поки ці інші класи моделей не будуть завантажені?

Відповідь полягає в . Коли Django запускається, він імпортує кожну програму, перелічену в , а
потім модуль всередині кожної програми. Щоразу, коли створюється новий клас моделі,
Django додає зворотні зв'язки до будь-яких пов'язаних моделей. Якщо пов'язані моделі ще не
імпортовані, Django відстежує відносини та додає їх, коли пов'язані моделі врешті-решт
імпортуються.app registryINSTALLED_APPSmodels

З цієї причини особливо важливо, щоб усі використовувані вами моделі були визначені в
програмах, перелічених у INSTALLED_APPS. В іншому випадку зворотні відносини можуть не
працювати належним чином.

Запити щодо пов’язаних об’єктів ¶


Запити, що стосуються пов’язаних об’єктів, дотримуються тих самих правил, що й запити, що
включають поля нормального значення. Визначаючи значення для відповідного запиту, ви
можете використовувати або сам екземпляр об’єкта, або значення первинного ключа для
об’єкта.

Наприклад, якщо у вас є блог об'єкт bз id=5, наступні три запиту будуть ідентичні:

Entry.objects.filter(blog=b) # Query using object instance

Entry.objects.filter(blog=b.id) # Query using id from instance

Entry.objects.filter(blog=5) # Query using id directly

Повернення до вихідного SQL ¶


Якщо вам знадобиться написати запит SQL, який є занадто складним, щоб його можна було
обробляти картою баз даних Django, ви можете повернутися до написання SQL
вручну. Django має кілька варіантів для написання необроблених запитів
SQL; див. Виконання необроблених запитів SQL .

Нарешті, важливо зазначити, що рівень бази даних Django - це просто інтерфейс до вашої
бази даних. Ви можете отримати доступ до бази даних через інші інструменти, мови
програмування або фреймворки баз даних; у вашій базі даних немає нічого специфічного для
Django.

Агрегація ¶
Тематичний посібник з API абстракції бази даних Django описував спосіб використання Django-запитів, які
створюють, отримують, оновлюють та видаляють окремі об’єкти. Однак іноді вам потрібно буде отримати
значення, отримані шляхом підсумовування або агрегування колекції об'єктів. Цей посібник описує
способи генерації та повернення сукупних значень за допомогою запитів Django.

У цьому посібнику ми будемо посилатися на такі моделі. Ці моделі використовуються для відстеження
запасів для серії інтернет-магазинів:

from django.db import models

class Author(models.Model):

name = models.CharField(max_length=100)

age = models.IntegerField()

class Publisher(models.Model):

name = models.CharField(max_length=300)
class Book(models.Model):

name = models.CharField(max_length=300)

pages = models.IntegerField()

price = models.DecimalField(max_digits=10, decimal_places=2)

rating = models.FloatField()

authors = models.ManyToManyField(Author)

publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)

pubdate = models.DateField()

class Store(models.Model):

name = models.CharField(max_length=300)

books = models.ManyToManyField(Book)

Шпаргалка ¶
Поспіхом? Ось як робити загальні сукупні запити, припускаючи наведені вище моделі:

# Total number of books.

>>> Book.objects.count()

2452

# Total number of books with publisher=BaloneyPress

>>> Book.objects.filter(publisher__name='BaloneyPress').count()

73

# Average price across all books.

>>> from django.db.models import Avg

>>> Book.objects.all().aggregate(Avg('price'))

{'price__avg': 34.35}

# Max price across all books.

>>> from django.db.models import Max

>>> Book.objects.all().aggregate(Max('price'))
{'price__max': Decimal('81.20')}

# Difference between the highest priced book and the average price of all
books.

>>> from django.db.models import FloatField

>>> Book.objects.aggregate(

... price_diff=Max('price', output_field=FloatField()) - Avg('price'))

{'price_diff': 46.85}

# All the following queries involve traversing the Book<->Publisher

# foreign key relationship backwards.

# Each publisher, each with a count of books as a "num_books" attribute.

>>> from django.db.models import Count

>>> pubs = Publisher.objects.annotate(num_books=Count('book'))

>>> pubs

<QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>

>>> pubs[0].num_books

73

# Each publisher, with a separate count of books with a rating above and below
5

>>> from django.db.models import Q

>>> above_5 = Count('book', filter=Q(book__rating__gt=5))

>>> below_5 = Count('book', filter=Q(book__rating__lte=5))

>>> pubs =
Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5)

>>> pubs[0].above_5

23

>>> pubs[0].below_5

12
# The top 5 publishers, in order by number of books.

>>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-


num_books')[:5]

>>> pubs[0].num_books

1323

Генерування агрегатів за QuerySet¶


Django надає два способи генерації сукупностей. Перший спосіб - це створення зведених значень для
цілого QuerySet. Наприклад, скажімо, ви хочете розрахувати середню ціну всіх книг, доступних для
продажу. Синтаксис запитів Django надає засоби для опису набору всіх книг:

>>> Book.objects.all()

Нам потрібен спосіб обчислення підсумкових значень за об’єктами, що їм належать QuerySet. Це робиться


шляхом додавання aggregate() речення до QuerySet:

>>> from django.db.models import Avg

>>> Book.objects.all().aggregate(Avg('price'))

{'price__avg': 34.35}

У all()цьому прикладі надлишкове значення, тому це можна спростити до:

>>> Book.objects.aggregate(Avg('price'))

{'price__avg': 34.35}

Аргумент aggregate()речення описує сукупне значення, яке ми хочемо обчислити - у цьому випадку


середнє значення priceполя в Bookмоделі. Список доступних сукупних функцій можна знайти в посиланні
QuerySet .

aggregate()є термінальним реченням, QuerySetяке при виклику повертає словник пар ім'я-


значення. Назва є ідентифікатором сукупного значення; значення - це обчислений сукупний. Ім'я
автоматично генерується з назви поля та агрегатної функції. Якщо ви хочете вручну вказати ім'я для
сукупного значення, ви можете зробити це, вказавши це ім'я, коли ви вказуєте сукупне речення:

>>> Book.objects.aggregate(average_price=Avg('price'))

{'average_price': 34.35}

Якщо ви хочете згенерувати більше одного сукупного, додайте до aggregate()речення ще один


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

>>> from django.db.models import Avg, Max, Min

>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))

{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min':


Decimal('12.99')}

Генерування агрегатів для кожного елемента в QuerySet¶


Другий спосіб генерації підсумкових значень - це створення незалежного підсумку для кожного об'єкта
в QuerySet. Наприклад, якщо ви отримуєте список книг, ви можете знати, скільки авторів внесли до
кожної книги. Кожна книга має стосунки «багато-до-багатьох» з автором; ми хочемо узагальнити ці
відносини для кожної книги в QuerySet.

Резюме для кожного об’єкта можна створити за допомогою annotate()пункту. Коли annotate()вказано


речення, кожен об'єкт у QuerySetбуде анотовано зазначеними значеннями.

Синтаксис цих анотацій ідентичний синтаксису, який використовується для


цього aggregate()речення. Кожен аргумент annotate()описує сукупність, яку слід
обчислити. Наприклад, для позначення книг кількістю авторів:

# Build an annotated queryset

>>> from django.db.models import Count

>>> q = Book.objects.annotate(Count('authors'))

# Interrogate the first object in the queryset

>>> q[0]

<Book: The Definitive Guide to Django>

>>> q[0].authors__count

# Interrogate the second object in the queryset

>>> q[1]

<Book: Practical Django Projects>

>>> q[1].authors__count

Як і в випадку aggregate(), ім'я анотації автоматично походить від імені функції сукупності та імені поля,
що агрегується. Ви можете замінити це ім’я за замовчуванням, вказавши псевдонім, коли ви вказуєте
анотацію:

>>> q = Book.objects.annotate(num_authors=Count('authors'))

>>> q[0].num_authors

>>> q[1].num_authors

На відміну від цього aggregate(), annotate()це не термінальне


застереження. Вихід annotate()пропозиції - a QuerySet; це QuerySetможе бути змінено з допомогою
будь-якої іншої QuerySetоперації, в тому числі filter(), order_by()чи навіть додаткові викликів
до annotate().

Поєднання кількох агрегатів ¶


Об'єднання декількох агрегатів з annotate()буде давати неправильні результати , тому що приєднується
використовуються замість підзапитів:

>>> book = Book.objects.first()

>>> book.authors.count()

>>> book.store_set.count()

>>> q = Book.objects.annotate(Count('authors'), Count('store'))

>>> q[0].authors__count

>>> q[0].store__count

Для більшості агрегатів неможливо уникнути цієї проблеми, однак Countагрегат має distinctпараметр,


який може допомогти:

>>> q = Book.objects.annotate(Count('authors', distinct=True), Count('store',


distinct=True))

>>> q[0].authors__count

>>> q[0].store__count

3
Якщо ви сумніваєтеся, перевірте запит SQL!

Для того, щоб зрозуміти, що відбувається з вашим запитом, розгляньте можливість


перевірки queryвластивості вашого QuerySet.

Приєднання та агрегати ¶
Поки що ми мали справу з сукупностями над полями, які належать до моделі, що перевіряється. Однак
іноді значення, яке ви хочете узагальнити, буде належати моделі, яка пов'язана з моделлю, яку ви
запитуєте.

Визначаючи поле, яке буде агреговано у функції агрегування, Django дозволить вам використовувати ту
саму нотатку подвійного підкреслення, яка використовується при посиланні на пов'язані поля у
фільтрах. Потім Django обробляє будь-які об’єднання таблиць, необхідні для отримання та агрегування
відповідного значення.

Наприклад, щоб знайти діапазон цін на книги, пропоновані в кожному магазині, ви можете скористатися
анотацією:

>>> from django.db.models import Max, Min

>>> Store.objects.annotate(min_price=Min('books__price'),
max_price=Max('books__price'))
Це говорить Django отримати Storeмодель, приєднатись (через взаємозв'язок "багато-до-багатьох")
із Bookмоделлю та об'єднати в полі ціни моделі книги, щоб отримати мінімальне та максимальне
значення.

Ті самі правила застосовуються до цього aggregate()пункту. Якщо ви хотіли знати найнижчу і найвищу


ціну будь-якої книги, яка продається в будь-якому з магазинів, ви можете скористатися сукупністю:

>>> Store.objects.aggregate(min_price=Min('books__price'),
max_price=Max('books__price'))

Приєднання ланцюгів може бути настільки глибоким, наскільки вам потрібно. Наприклад, щоб визначити
вік наймолодшого автора будь-якої книги, доступної для продажу, ви можете оформити запит:

>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))

Наступні відносини назад ¶


Подібно до пошуку, що охоплює взаємозв'язки , агрегації та анотації на полях моделей або моделей, які
пов'язані з тією, яку ви запитуєте, може включати обхід "зворотних" зв'язків. Тут також використовується
нижня літера відповідних моделей та подвійне підкреслення.

Наприклад, ми можемо попросити всіх видавців, анотованих відповідними лічильниками загальної


кількості книг (зверніть увагу, як ми використовуємо 'book'для вказівки Publisher-> Bookзворотний
стрибок зовнішнього ключа):

>>> from django.db.models import Avg, Count, Min, Sum

>>> Publisher.objects.annotate(Count('book'))

(Кожен Publisherв результаті QuerySetотримає додатковий атрибут, який називається book__count.)

Ми також можемо попросити найстарішу книгу будь-якої з тих, якими керує кожен видавець:

>>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))

(Отриманий словник матиме виклик ключа 'oldest_pubdate'. Якби не був вказаний такий псевдонім, це
було б досить довго 'book__pubdate__min'.)

Це стосується не лише зовнішніх ключів. Це також працює з відносинами багато-до-багатьох. Наприклад,


ми можемо попросити кожного автора, анотований загальною кількістю сторінок, враховуючи всі книги,
автором яких є (співавтори) (зверніть увагу, як ми використовуємо 'book'для вказівки Author-
> Bookзворотний стрибок багато-до-багатьох):

>>> Author.objects.annotate(total_pages=Sum('book__pages'))

(Кожен Authorз отриманих результатів QuerySetбуде мати додатковий атрибут, який


викликається total_pages. Якби такий псевдонім не був вказаний, це було б досить
довго book__pages__sum.)

Або запитайте середній рейтинг усіх книг, написаних автором (авторами), які ми маємо на обліку:

>>> Author.objects.aggregate(average_rating=Avg('book__rating'))

(Отриманий словник матиме виклик ключа 'average_rating'. Якби не був вказаний такий псевдонім, це
було б досить довго 'book__rating__avg'.)

Агрегації та інші QuerySetстатті ¶
filter()та exclude()¶
Агрегати також можуть брати участь у фільтрах. Будь-яке filter()(або exclude()), застосоване до
звичайних полів моделі, матиме ефект обмеження об’єктів, які розглядаються для агрегування.

При використанні з annotate()реченням фільтр має ефект обмеження об’єктів, для яких обчислюється
анотація. Наприклад, ви можете створити анотований список усіх книг, які мають заголовок, що
починається з “Django”, використовуючи запит:

>>> from django.db.models import Avg, Count

>>>
Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('auth
ors'))

При використанні з aggregate()реченням фільтр має ефект обмеження об'єктів, за якими обчислюється
сукупність. Наприклад, ви можете сформувати середню ціну всіх книг із заголовком, який починається з
“Django”, використовуючи запит:

>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))

Фільтрування анотацій ¶

Анотовані значення також можна відфільтрувати. Псевдонім анотації може використовуватися


в filter()і exclude()реченнях так само, як і будь-яке інше поле моделі.

Наприклад, щоб створити список книг, у яких є більше одного автора, ви можете подати запит:

>>>
Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)

Цей запит генерує анотований набір результатів, а потім генерує фільтр на основі цієї анотації.

Якщо вам потрібні дві анотації з двома окремими фільтрами, ви можете використовувати filterаргумент
з будь-яким агрегатом. Наприклад, щоб створити список авторів із кількістю високо оцінених книг:

>>> highly_rated = Count('book', filter=Q(book__rating__gte=7))

>>> Author.objects.annotate(num_books=Count('book'),
highly_rated_books=highly_rated)

Кожен Authorу наборі результатів матиме атрибути num_booksand highly_rated_books. Див.


Також Умовна агрегація .

Вибір між filterіQuerySet.filter()

Уникайте використання filterаргументу з однією анотацією чи агрегацією. Це ефективніше


використовувати QuerySet.filter()для виключення рядків. Аргумент агрегування filterкорисний
лише при використанні двох або більше агрегувань за одними і тими ж відносинами з різними умовними
умовами.

Порядок annotate()та filter()положення ¶

При розробці складного запиту, що включає як речення, так annotate()і filter()речення, зверніть


особливу увагу на порядок, у якому речення застосовуються до QuerySet.

Коли annotate()речення застосовується до запиту, анотація обчислюється за станом запиту аж до точки,


де запитується анотація. Практичне значення цього полягає в тому, що filter()і annotate()не є
комутаційними операціями.
Дано:

 Видавець А має дві книги з рейтингами 4 та 5.


 Видавець B має дві книги з рейтингами 1 та 4.
 Видавець C має одну книгу з рейтингом 1.

Ось приклад із Countсукупністю:

>>> a, b = Publisher.objects.annotate(num_books=Count('book',
distinct=True)).filter(book__rating__gt=3.0)

>>> a, a.num_books

(<Publisher: A>, 2)

>>> b, b.num_books

(<Publisher: B>, 2)

>>> a, b =
Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book')
)

>>> a, a.num_books

(<Publisher: A>, 2)

>>> b, b.num_books

(<Publisher: B>, 1)

Обидва запити повертають список видавців, у яких є принаймні одна книга з рейтингом, що перевищує
3,0, отже видавець С виключається.

У першому запиті анотація передує фільтру, тому фільтр не впливає на


анотацію. distinct=Trueнеобхідний, щоб уникнути помилки запиту .

Другий запит підраховує кількість книг, рейтинг яких перевищує 3,0 для кожного видавця. Фільтр передує
анотації, тому фільтр обмежує об'єкти, що враховуються під час обчислення анотації.

Ось ще один приклад із Avgсукупністю:

>>> a, b =
Publisher.objects.annotate(avg_rating=Avg('book__rating')).filter(book__rating_
_gt=3.0)

>>> a, a.avg_rating

(<Publisher: A>, 4.5) # (5+4)/2

>>> b, b.avg_rating

(<Publisher: B>, 2.5) # (1+4)/2


>>> a, b =
Publisher.objects.filter(book__rating__gt=3.0).annotate(avg_rating=Avg('book__r
ating'))

>>> a, a.avg_rating

(<Publisher: A>, 4.5) # (5+4)/2

>>> b, b.avg_rating

(<Publisher: B>, 4.0) # 4/1 (book with rating 1 excluded)

Перший запит вимагає середнього рейтингу всіх видавничих книг видавництва, у яких є принаймні одна
книга з рейтингом, що перевищує 3,0. Другий запит вимагає середнього значення рейтингу книги видавця
лише для тих, які перевищують 3,0.

Важко зрозуміти, як ORM перетворить складні набори запитів на запити SQL, тому, якщо ви сумніваєтеся,
огляньте SQL str(queryset.query)та напишіть безліч тестів.

order_by()¶
Анотації можуть бути використані як основа для замовлення. Коли ви визначаєте order_by()речення,
агрегати, які ви надаєте, можуть посилатися на будь-який псевдонім, визначений як
частина annotate()речення у запиті.

Наприклад, щоб замовити QuerySetкнигу за кількістю авторів, які зробили свій внесок у книгу, ви можете
скористатися таким запитом:

>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')

values()¶
Зазвичай анотації генеруються на основі кожного об’єкта - коментований QuerySetповертає один
результат для кожного об’єкта в оригіналі QuerySet. Однак, коли values()речення використовується
для обмеження стовпців, що повертаються в наборі результатів, метод оцінки анотацій дещо
відрізняється. Замість повернення анотованого результату для кожного результату в оригіналі QuerySet,
оригінальні результати групуються відповідно до унікальних комбінацій полів, зазначених
у values()пункті. Потім надається анотація для кожної унікальної групи; анотація обчислюється для всіх
членів групи.

Наприклад, розглянемо авторський запит, який намагається з’ясувати середній рейтинг книг, написаних
кожним автором:

>>> Author.objects.annotate(average_rating=Avg('book__rating'))

Це дасть по одному результату для кожного автора в базі даних із зазначенням середнього рейтингу
книги.

Однак результат буде дещо іншим, якщо використовувати values()пункт:

>>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))

У цьому прикладі автори будуть згруповані за іменами, тому ви отримаєте лише анотований результат для
кожного унікального імені автора. Це означає, що якщо у вас два автори з однаковим ім’ям, їх результати
будуть об’єднані в один результат у результатах запиту; середнє значення буде обчислюватися як середнє
за книги, написані обома авторами.

Порядок annotate()та values()положення ¶
Як і у filter()пункті, порядок, у якому annotate()та values()речення застосовуються до запиту, є
значним. Якщо values()речення передує annotate(), анотація буде обчислюватися за допомогою
групування, описаного в values()реченні.

Однак, якщо annotate()речення передує values()реченню, анотації будуть сформовані протягом


усього набору запитів. У цьому випадку values()речення обмежує лише поля, які генеруються на виході.

Наприклад, якщо ми змінимо порядок пропозиції values()та annotate() з нашого попереднього


прикладу:

>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name',
'average_rating')

Тепер це дасть по одному унікальному результату для кожного автора; однак average_ratingу вихідних


даних буде повернуто лише ім'я автора та анотацію.

Також слід зазначити, що average_ratingце було явно включено до списку значень, що


повертаються. Це потрібно через упорядкування пункту values()та annotate().

Якщо values()речення передує annotate()реченню, будь-які анотації будуть автоматично додані до


набору результатів. Однак, якщо values() речення застосовується після annotate()речення, вам
потрібно явно включити агрегований стовпець.

Взаємодія з order_by()¶

Поля, згадані в order_by()частині набору запитів, використовуються при виборі вихідних даних, навіть
якщо вони не вказані іншим чином у values()виклику. Ці додаткові поля використовуються для
групування результатів "подобається" разом, і в іншому випадку ідентичні рядки результатів можуть
здаватися окремими. Це проявляється, особливо, при підрахунку речей.

Для прикладу, припустимо, у вас є така модель:

from django.db import models

class Item(models.Model):

name = models.CharField(max_length=10)

data = models.IntegerField()

Якщо ви хочете підрахувати, скільки разів кожне окреме dataзначення відображається в упорядкованому


наборі запитів, ви можете спробувати це:

items = Item.objects.order_by('name')

# Warning: not quite correct!

items.values('data').annotate(Count('id'))

… Який об’єднує Itemоб’єкти за їх загальними dataзначеннями, а потім підраховує кількість idзначень у


кожній групі. Хіба що це не зовсім спрацює. Впорядкування за nameтакож буде відігравати певну роль у
групуванні, тому цей запит буде групуватися за окремими парами, а це не те, що ви хочете. Натомість вам
слід побудувати цей набір запитів:(data, name)

items.values('data').annotate(Count('id')).order_by()
… Очищення будь-якого замовлення у запиті. Ви також можете замовити, скажімо, data без шкідливих
наслідків, оскільки це вже відіграє роль у запиті.

Ця поведінка така ж, як зазначена в документації набору запитів для, distinct()і загальне правило


однакове: зазвичай ви не хочете, щоб зайві стовпці відігравали роль у результаті, тому очистіть порядок
або, принаймні, переконайтеся, що він обмежений лише до тих полів, які ви також вибрали під
час values()дзвінка.

Примітка

Ви можете розумно запитати, чому Django не видаляє для вас сторонні стовпці. Основна причина -
узгодженість з distinct()іншими місцями: Django ніколи не видаляє обмеження впорядкування, які ви
вказали (і ми не можемо змінити поведінку інших методів, оскільки це порушує нашу політику стабільності
API ).

Агрегуючі анотації ¶
Ви також можете створити сукупність результатів анотації. Коли ви визначаєте aggregate()речення,
агрегати, які ви надаєте, можуть посилатися на будь-який псевдонім, визначений як
частина annotate()речення у запиті.

Наприклад, якщо ви хочете обчислити середню кількість авторів на книгу, спочатку коментуйте набір книг
кількістю авторів, а потім агрегуйте їх, посилаючись на поле анотації:

>>> from django.db.models import Avg, Count

>>>
Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'
))

{'num_authors__avg': 1.66}

Пошук ¶
Типовим завданням веб-додатків є пошук деяких даних у базі даних за введенням користувачем. У
простому випадку це може бути фільтрація списку об’єктів за категорією. Складніший варіант
використання може вимагати пошуку із зважуванням, категоризацією, виділенням, кількома мовами
тощо. Цей документ пояснює деякі можливі випадки використання та інструменти, якими ви можете
скористатися.

Ми будемо посилатись на ті самі моделі, що використовуються під час створення запитів .

Використовуйте кейси ¶
Стандартні текстові запити ¶
Текстові поля мають вибір відповідних операцій. Наприклад, ви можете дозволити пошук автора таким
чином:

>>> Author.objects.filter(name__contains='Terry')

[<Author: Terry Gilliam>, <Author: Terry Jones>]

Це дуже крихке рішення, оскільки воно вимагає, щоб користувач знав точну підрядок імені
автора. Кращим підходом може бути збіг, який не враховує регістр ( icontains), але це лише незначно
краще.
Досконаліші функції порівняння бази даних ¶
Якщо ви використовуєте PostgreSQL, Django надає вибір інструментів для конкретних баз даних, що
дозволяють використовувати більш складні параметри запитів. Інші бази даних мають різний вибір
інструментів, можливо, за допомогою плагінів або визначених користувачем функцій. На даний момент
Django не включає жодної підтримки для них. Ми використаємо кілька прикладів з PostgreSQL, щоб
продемонструвати тип функціональних баз даних.

Пошук в інших базах даних

Усі засоби пошуку, що надаються, django.contrib.postgresповністю сконструйовані на


загальнодоступних API, таких як власні пошуки та функції баз даних . Залежно від вашої бази даних, ви
повинні мати можливість створювати запити, щоб дозволити подібні API. Якщо є конкретні речі, яких
неможливо досягти таким чином, відкрийте квиток.

У наведеному вище прикладі ми визначили, що пошук, що не враховує регістр, буде більш корисним. При
роботі з неанглійськими іменами подальшим вдосконаленням є використання :unaccented comparison

>>> Author.objects.filter(name__unaccent__icontains='Helen')

[<Author: Helen Mirren>, <Author: Helena Bonham Carter>, <Author: Hélène Joy>]

Це показує ще одну проблему, коли ми співпадаємо з різним написанням імені. У цьому випадку ми маємо
асиметрію - пошук Helen забирає Helenaабо Hélène, але не навпаки. Іншим варіантом було б
використання trigram_similarпорівняння, яке порівнює послідовності літер.

Наприклад:

>>> Author.objects.filter(name__unaccent__lower__trigram_similar='Hélène')

[<Author: Helen Mirren>, <Author: Hélène Joy>]

Зараз у нас інша проблема - довше ім'я "Хелена Бонем Картер" не з'являється, оскільки воно набагато
довше. Пошук у триграмі враховує всі комбінації з трьох букв та порівнює, скільки їх з’являється як у
рядках пошуку, так і у вихідних. Щодо довшого імені, існує більше комбінацій, які не відображаються у
вихідному рядку, тому воно більше не вважається близьким збігом.

Правильний вибір функцій порівняння тут залежить від вашого конкретного набору даних, наприклад,
використовуваної мови (мов) та типу тексту, який шукається. Усі приклади, які ми бачили, містять короткі
рядки, де користувач, швидше за все, може ввести щось близьке (за допомогою різних визначень) до
вихідних даних.

Пошук на основі документів ¶


Стандартні операції з базами даних перестають бути корисним підходом, коли ви починаєте розглядати
великі блоки тексту. Тоді як наведені вище приклади можна розглядати як операції над рядком символів,
повнотекстовий пошук розглядає фактичні слова. Залежно від використовуваної системи, скоріш за все,
буде використано деякі з наступних ідей:

 Ігнорування “стоп-слів”, таких як “a”, “the”, “та”.


 Вигадування слів, так що «поні» та «поні» вважаються подібними.
 Зважування слів на основі різних критеріїв, таких як частота їх появи в тексті, або важливість полів,
таких як заголовок або ключові слова, в яких вони відображаються.

Існує багато альтернатив для використання програмного забезпечення для пошуку, серед найбільш
відомих - Elastic та Solr . Це повні пошукові рішення на основі документів. Щоб використовувати їх із
даними з моделей Django, вам знадобиться шар, який перетворює ваші дані у текстовий документ,
включаючи зворотні посилання на ідентифікатори бази даних. Коли пошук за допомогою механізму
повертає певний документ, ви можете шукати його в базі даних. Існує безліч сторонніх бібліотек, які
призначені допомогти в цьому процесі.

Підтримка PostgreSQL ¶

PostgreSQL має вбудовану реалізацію повнотекстового пошуку. Хоча він не такий потужний, як деякі інші
пошукові системи, він має ту перевагу, що знаходиться всередині вашої бази даних, і тому його можна
легко поєднувати з іншими реляційними запитами, такими як категоризація.

django.contrib.postgresМодуль надає кілька помічників , щоб зробити ці запити. Наприклад, запит


може вибрати всі записи в блозі, в яких згадується “сир”:

>>> Entry.objects.filter(body_text__search='cheese')

[<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]

Ви також можете фільтрувати за комбінацією полів та за пов’язаними моделями:

>>> Entry.objects.annotate(

... search=SearchVector('blog__tagline', 'body_text'),

... ).filter(search='cheese')

<Entry: Cheese on Toast recipes>,

<Entry: Pizza Recipes>,

<Entry: Dairy farming in Argentina>,

Детальнішу інформацію див. У документі contrib.postgres пошуку в повному тексті .

Менеджери ¶
клас Manager¶

A Manager- це інтерфейс, за допомогою якого операції запиту до бази даних надаються моделям
Django. Щонайменше одна Managerіснує для кожної моделі в програмі Django.

Принцип Managerроботи класів задокументований у Виробництві запитів ; цей документ спеціально


торкається варіантів моделі, які налаштовують Manager поведінку.

Імена менеджерів ¶
За замовчуванням Django додає a Managerз іменем objectsдо кожного класу моделі Django. Однак, якщо
ви хочете використовувати objectsяк ім'я поля або якщо ви хочете використовувати ім'я, відмінне
від objectsдля Manager, ви можете перейменувати його на основі кожної моделі. Щоб
перейменувати Managerдля даного класу, визначте атрибут класу типу models.Manager()для цієї
моделі. Наприклад:

from django.db import models


class Person(models.Model):

#...

people = models.Manager()

Використовуючи цю прикладну модель, Person.objectsбуде створено AttributeErrorвиняток,


але Person.people.all()надано список усіх Personоб’єктів.

Спеціальні менеджери ¶
Ви можете використовувати користувальницький Managerв конкретній моделі, розширивши
базовий Managerклас і створивши свій власний Managerзразок у вашій моделі.

Є дві причини , по яким , можливо , захочете налаштувати Manager: додати додаткові Managerметоди, і /


або змінювати початковий QuerySetна Manager віддачу.

Додавання додаткових методів менеджера ¶


Додавання додаткових Managerметодів - найкращий спосіб додати функціональність на рівні таблиці до
ваших моделей. (Для функціональності "на рівні рядка" - тобто функцій, які діють на один екземпляр
об'єкта моделі - використовуйте методи моделі , а не власні Managerметоди.)

Наприклад, цей користувальницький Managerдодає метод with_counts():

from django.db import models

from django.db.models.functions import Coalesce

class PollManager(models.Manager):

def with_counts(self):

return self.annotate(

num_responses=Coalesce(models.Count("response"), 0)

class OpinionPoll(models.Model):

question = models.CharField(max_length=200)

objects = PollManager()

class Response(models.Model):

poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)

# ...
У цьому прикладі ви будете використовувати , OpinionPoll.objects.with_counts()щоб
отримати QuerySetз OpinionPollоб'єктів з додатковим num_responses атрибутом додається.

Спеціальний Managerметод може повернути все, що завгодно. Він не повинен повертати a QuerySet.

Ще одне, на що слід звернути увагу, це те, що Managerметоди можуть отримати доступ, self.modelщоб


отримати клас моделі, до якого вони приєднані.

Зміна початкового initial менеджера QuerySet¶


ManagerБаза «s QuerySetповертає всі об'єкти в системі. Наприклад, використовуючи цю модель:

from django.db import models

class Book(models.Model):

title = models.CharField(max_length=100)

author = models.CharField(max_length=50)

… Виписка Book.objects.all()поверне всі книги в базі даних.

Ви можете замінити Managerбазу a QuerySet,
замінивши Manager.get_queryset()метод. get_queryset()повинен повернути a QuerySetіз
властивостями, які вам потрібні.

Наприклад, наступна модель має два Manager s - один, який повертає всі об'єкти, і той, який повертає
лише книги Роальда Даля:

# First, define the Manager subclass.

class DahlBookManager(models.Manager):

def get_queryset(self):

return super().get_queryset().filter(author='Roald Dahl')

# Then hook it into the Book model explicitly.

class Book(models.Model):

title = models.CharField(max_length=100)

author = models.CharField(max_length=50)

objects = models.Manager() # The default manager.

dahl_objects = DahlBookManager() # The Dahl-specific manager.

За допомогою цієї зразкової моделі Book.objects.all()поверне всі книги в базі даних,


але Book.dahl_objects.all()поверне лише ті, що написані Роальдом Далем.
Тому що get_queryset()повертає QuerySetоб'єкт, ви можете використовувати filter(), exclude()і
всі інші QuerySetметоди на нього. Отже, ці твердження є законними:

Book.dahl_objects.all()

Book.dahl_objects.filter(title='Matilda')

Book.dahl_objects.count()

Цей приклад також вказав на ще одну цікаву техніку: використання декількох менеджерів на одній
моделі. Ви можете прикріпити Manager()до моделі скільки завгодно екземплярів. Це неповторний спосіб
визначити загальні «фільтри» для ваших моделей.

Наприклад:

class AuthorManager(models.Manager):

def get_queryset(self):

return super().get_queryset().filter(role='A')

class EditorManager(models.Manager):

def get_queryset(self):

return super().get_queryset().filter(role='E')

class Person(models.Model):

first_name = models.CharField(max_length=50)

last_name = models.CharField(max_length=50)

role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E',


_('Editor'))])

people = models.Manager()

authors = AuthorManager()

editors = EditorManager()

Цей приклад дозволяє


запитувати Person.authors.all(), Person.editors.all()і Person.people.all(), отримуючи
передбачувані результати.

Менеджери за замовчуванням ¶
Model._default_manager¶

Якщо ви використовуєте спеціальні Managerоб'єкти, зверніть увагу, що перші Manager зустрічі Django (у


тому порядку, в якому вони визначені в моделі) мають особливий статус. Django інтерпретує
перше, Managerвизначене в класі, як "за замовчуванням" Manager, і кілька частин Django
(включаючи dumpdata) використовуватимуть це Managerвиключно для цієї моделі. Як
результат, get_queryset()радимо бути обережним у виборі менеджера за замовчуванням, щоб
уникнути ситуації, коли заміщення призводить до неможливості отримати об’єкти, з якими ви хочете
працювати.

Ви можете вказати власний менеджер за замовчуванням за допомогою Meta.default_manager_name.

Якщо ви пишете код, який повинен обробляти невідому модель, наприклад, у сторонній програмі, що
реалізує загальний вигляд, використовуйте цього менеджера (або _base_manager), а не припускайте, що
модель має objects менеджера.

Менеджери баз ¶
Model._base_manager¶

Використання менеджерів для доступу до пов’язаних об’єктів ¶

За замовчуванням Django використовує екземпляр Model._base_managerкласу менеджера під час


доступу до пов'язаних об'єктів (тобто choice.question), а не _default_managerдо пов'язаного
об'єкта. Це пов’язано з тим, що Django повинен мати можливість отримати відповідний об’єкт, навіть якщо
він інакше був би відфільтрований (і, отже, недоступний) менеджером за замовчуванням.

Якщо звичайний клас базового менеджера ( django.db.models.Manager) не відповідає вашим


обставинам, ви можете сказати Django, який клас використовувати,
встановивши Meta.base_manager_name.

Базові менеджери не використовуються під час запитів щодо пов’язаних моделей або при доступі до
відносин один-до-багатьох або багато-до-багатьох . Наприклад, якщо Questionмодель із навчального
посібника мала deletedполе та диспетчер бази, який фільтрує екземпляри за допомогою deleted=True,
набір запитів like Choice.objects.filter(question__name__startswith='What')містив би
варіанти, пов’язані з видаленими питаннями.

Не відфільтровуйте жодних результатів у цьому підкласі менеджера ¶

Цей менеджер використовується для доступу до об’єктів, пов’язаних з якоюсь іншою моделлю. У цих
ситуаціях Django повинен мати можливість бачити всі об'єкти для моделі, яку він отримує, щоб можна було
отримати все, про що йдеться.

Тому не слід перевизначати, get_queryset()щоб відфільтрувати будь-які рядки. Якщо ви зробите це,


Django поверне неповні результати.

Виклик нестандартних QuerySetметодів з менеджера ¶


Хоча більшість методів зі стандарту QuerySetдоступні безпосередньо з Manager, це стосується лише
додаткових методів, визначених у користувацькому, QuerySetякщо ви також реалізуєте їх на Manager:

class PersonQuerySet(models.QuerySet):

def authors(self):

return self.filter(role='A')

def editors(self):

return self.filter(role='E')

class PersonManager(models.Manager):
def get_queryset(self):

return PersonQuerySet(self.model, using=self._db)

def authors(self):

return self.get_queryset().authors()

def editors(self):

return self.get_queryset().editors()

class Person(models.Model):

first_name = models.CharField(max_length=50)

last_name = models.CharField(max_length=50)

role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E',


_('Editor'))])

people = PersonManager()

Цей приклад дозволяє телефонувати як безпосередньо, так authors()і editors()безпосередньо від


менеджера Person.people.

Створення менеджера з QuerySetметодами ¶


Замість вищезазначеного підходу, який вимагає дублювання методів як на, так QuerySetі
на Manager, QuerySet.as_manager()можна використовувати для створення екземпляра Managerз
копією користувацьких QuerySetметодів:

class Person(models.Model):

...

people = PersonQuerySet.as_manager()

ManagerПримірник , створений QuerySet.as_manager()буде практично ідентичний PersonManagerз


попереднього прикладу.

Не кожен QuerySetметод має сенс на Managerрівні; наприклад, ми навмисно


не QuerySet.delete()допускаємо копіювання методу до Managerкласу.

Методи копіюються відповідно до таких правил:

 Відкриті методи копіюються за замовчуванням.


 Приватні методи (починаючи з підкреслення) не копіюються за замовчуванням.
 Методи із встановленим queryset_onlyатрибутом Falseзавжди копіюються.
 Методи із встановленим queryset_onlyатрибутом Trueніколи не копіюються.
Наприклад:

class CustomQuerySet(models.QuerySet):

# Available on both Manager and QuerySet.

def public_method(self):

return

# Available only on QuerySet.

def _private_method(self):

return

# Available only on QuerySet.

def opted_out_public_method(self):

return

opted_out_public_method.queryset_only = True

# Available on both Manager and QuerySet.

def _opted_in_private_method(self):

return

_opted_in_private_method.queryset_only = False

from_queryset()¶
classmethodfrom_queryset ( queryset_class ) ¶

Для розширеного використання вам може знадобитися як спеціальний, так Managerі


спеціальний QuerySet. Це можна зробити за допомогою виклику, Manager.from_queryset()який
повертає підклас бази Managerз копією власних QuerySetметодів:

class CustomManager(models.Manager):

def manager_only_method(self):

return

class CustomQuerySet(models.QuerySet):

def manager_and_queryset_method(self):

return
class MyModel(models.Model):

objects = CustomManager.from_queryset(CustomQuerySet)()

Ви також можете зберегти згенерований клас у змінну:

MyManager = CustomManager.from_queryset(CustomQuerySet)

class MyModel(models.Model):

objects = MyManager()

Спеціальні менеджери та успадкування моделей ¶


Ось як Django обробляє користувацькі менеджери та успадкування моделі :

1. Менеджери базових класів завжди успадковуються дочірнім класом, використовуючи звичайний


порядок роздільної здатності імен Python (імена дочірнього класу замінюють всі інші; потім
надходять імена першого батьківського класу тощо).
2. Якщо на моделі та / або її батьків не оголошено менеджерів, Django автоматично
створює objectsменеджера.
3. Менеджером за замовчуванням у класі є або той, кого вибрали Meta.default_manager_name,
або перший менеджер, оголошений у моделі, або менеджер за замовчуванням для першої
батьківської моделі.

Ці правила забезпечують необхідну гнучкість, якщо ви хочете встановити колекцію користувацьких


менеджерів на групу моделей через абстрактний базовий клас, але все ж налаштувати менеджера за
замовчуванням. Наприклад, припустимо, у вас є цей базовий клас:

class AbstractBase(models.Model):

# ...

objects = CustomManager()

class Meta:

abstract = True

Якщо ви використовуєте це безпосередньо в підкласі, objectsменеджером за замовчуванням буде, якщо


ви не заявите менеджерів у базовому класі:

class ChildA(AbstractBase):

# ...

# This class has CustomManager as the default manager.

pass

Якщо ви хочете успадкувати від AbstractBase, але надаєте іншого менеджера за замовчуванням, ви


можете надати менеджера за замовчуванням для дочірнього класу:

class ChildB(AbstractBase):
# ...

# An explicit default manager.

default_manager = OtherManager()

Тут default_managerє значення за замовчуванням. objectsМенеджер по - , як і раніше доступний, так як


він успадкував, але не використовується в якості значення за замовчуванням.

Нарешті, для цього прикладу, припустимо, ви хочете додати додаткові менеджери до дочірнього класу,
але все ще використовуйте типовий від AbstractBase. Ви не можете додати нового менеджера
безпосередньо в дочірній клас, оскільки це замінить за замовчуванням, і вам також доведеться явно
включити всіх менеджерів з абстрактного базового класу. Рішення полягає в тому, щоб помістити зайвих
менеджерів в інший базовий клас і ввести його в ієрархію успадкування після стандартних параметрів:

class ExtraManager(models.Model):

extra_manager = OtherManager()

class Meta:

abstract = True

class ChildC(AbstractBase, ExtraManager):

# ...

# Default manager is CustomManager, but OtherManager is

# also available via the "extra_manager" attribute.

pass

Зауважте, що, хоча ви можете визначити власний менеджер для абстрактної моделі, ви не


можете викликати жодні методи, використовуючи абстрактну модель. Це:

ClassA.objects.do_something()

є законним, але:

AbstractBase.objects.do_something()

викликає виняток. Це пов’язано з тим, що менеджери призначені для інкапсуляції логіки управління
колекціями об’єктів. Оскільки ви не можете мати колекцію абстрактних об’єктів, немає сенсу керувати
ними. Якщо у вас є функціонал, який застосовується до абстрактної моделі, ви повинні помістити цю
функцію в абстрактну модель staticmethodабо classmethodна неї.

Проблеми впровадження ¶
Які б функції ви не додали до свого замовлення Manager, має бути можливість зробити неглибоку
копію Managerекземпляра; тобто повинен працювати наступний код:

>>> import copy

>>> manager = MyManager()


>>> my_copy = copy.copy(manager)

Django робить дрібні копії об’єктів менеджера під час певних запитів; якщо ваш менеджер не вдається
скопіювати, ці запити не зможуть.

Це не буде проблемою для більшості користувацьких менеджерів. Якщо ви просто додаєте до своїх


простих методів Manager, навряд чи ви ненавмисно зробите екземпляри своїх Managerкопій. Однак, якщо
ви перевизначаєте __getattr__або якийсь інший приватний метод вашого Managerоб'єкта, який
контролює стан об'єкта, вам слід переконатися, що ви не вплинете на
можливість Managerкопіювання вашого .

Виконання необроблених SQL-запитів ¶


Django пропонує два способи виконання необроблених запитів SQL: ви можете
використовувати Manager.raw()для виконання необроблених запитів та повернення екземплярів
моделі , або ви можете повністю уникнути рівня моделі та виконати власний SQL безпосередньо .

Дослідіть ORM, перш ніж використовувати необроблений SQL!

Django ORM надає багато інструментів для вираження запитів без написання необробленого
SQL. Наприклад:

 QuerySet API - великий.


 Ви можете annotateта агрегувати, використовуючи багато вбудованих функцій бази даних . Крім
них, ви можете створювати власні вирази запитів .

Перш ніж використовувати необроблений SQL, вивчіть ORM . Запитайте на одному з каналів


підтримки, щоб перевірити, чи підтримує ORM ваш варіант використання.
Увага

Ви повинні бути дуже обережними, коли пишете необроблений SQL. Кожного разу, коли ви
використовуєте його, вам слід належним чином уникати будь-яких параметрів, якими користувач може
керувати, використовуючи їх paramsдля захисту від атак введення SQL. Будь ласка, прочитайте більше
про захист від ін'єкції SQL .

Виконання необроблених запитів ¶


Метод raw()менеджера можна використовувати для виконання необроблених запитів SQL, які
повертають екземпляри моделі:

Manager.raw( raw_query , params = () , translations = None ) ¶

Цей метод приймає необроблений SQL-запит, виконує його та


повертає django.db.models.query.RawQuerySetекземпляр. Цей RawQuerySetекземпляр може бути
повторений як звичайний QuerySetдля надання екземплярів об’єктів.

Це найкраще проілюструвати на прикладі. Припустимо, у вас є така модель:

class Person(models.Model):

first_name = models.CharField(...)

last_name = models.CharField(...)

birth_date = models.DateField(...)
Потім ви можете виконати власний SQL наступним чином:

>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):

... print(p)

John Smith

Jane Jones

Цей приклад не дуже захоплюючий - він точно такий же, як біг Person.objects.all(). Однак raw()є


купа інших варіантів, які роблять його дуже потужним.

Назви таблиць моделей

Звідки походить назва Personтаблиці в цьому прикладі?

За замовчуванням Django визначає ім'я таблиці бази даних, приєднуючи "ярлик програми" моделі - те ім'я,
яке ви використовували, - до імені класу моделі, підкреслюючи між ними. У прикладі ми припустили,
що модель живе в програмі з іменем , тому її таблиця буде
такою .manage.py startappPersonmyappmyapp_person

Для отримання додаткової інформації перегляньте документацію до db_tableопції, яка також дозволяє
вручну встановити назву таблиці бази даних.
Увага

Не виконується перевірка оператора SQL, який передається .raw(). Django очікує, що оператор поверне


набір рядків з бази даних, але не робить нічого, щоб забезпечити це. Якщо запит не повертає рядки,
виникає (можливо загадкова) помилка.
Увага

Якщо ви виконуєте запити на MySQL, зверніть увагу, що примус тихого типу MySQL може спричинити
несподівані результати при змішуванні типів. Якщо ви виконуєте запит у стовпці рядкового типу, але із
цілим числом, MySQL примусить типи всіх значень у таблиці до цілого числа перед виконанням
порівняння. Наприклад, якщо ваша таблиця містить значення 'abc', 'def'і ви запитуєте , обидва рядки
збігатимуться. Щоб цього не сталося, виконайте правильне видання тексту перед використанням
значення у запиті.WHERE mycolumn=0
Змінено в Django 3.2:
Значення paramsаргументу за замовчуванням було змінено з Noneпорожнього кортежу.

Зіставлення полів запитів із полями моделей ¶


raw() автоматично відображає поля в запиті на поля на моделі.

Порядок полів у вашому запиті не має значення. Іншими словами, обидва наведені запити працюють
однаково:

>>> Person.objects.raw('SELECT id, first_name, last_name, birth_date FROM


myapp_person')

...

>>> Person.objects.raw('SELECT last_name, birth_date, first_name, id FROM


myapp_person')

...
Відповідність здійснюється за іменами. Це означає, що ви можете використовувати пропозиції SQL ASдля
зіставлення полів запиту з полями моделювання. Отже, якщо у вас була інша таблиця, в якій
були Personдані, ви могли б легко зіставити її в Personекземпляри:

>>> Person.objects.raw('''SELECT first AS first_name,

... last AS last_name,

... bd AS birth_date,

... pk AS id,

... FROM some_other_table''')

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

Крім того, ви можете зіставити поля в запиті з полями моделювання за


допомогою translationsаргументу raw(). Це словник, що відображає назви полів у запиті до назв полів
на моделі. Наприклад, наведений вище запит також може бути написаний:

>>> name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date',


'pk': 'id'}

>>> Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)

Пошук по індексу ¶
raw() підтримує індексацію, тому, якщо вам потрібен лише перший результат, ви можете написати:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person')[0]

Однак індексація та нарізання не виконуються на рівні бази даних. Якщо Personу вашій базі даних є
велика кількість об’єктів, ефективніше обмежити запит на рівні SQL:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0]

Відкладені поля моделі ¶


Поля також можуть бути пропущені:

>>> people = Person.objects.raw('SELECT id, first_name FROM myapp_person')

В Personоб'єкти , які повертаються цим запитом буде відкладено екземпляр моделі (див defer()). Це
означає, що поля, пропущені в запиті, завантажуватимуться за запитом. Наприклад:

>>> for p in Person.objects.raw('SELECT id, first_name FROM myapp_person'):

... print(p.first_name, # This will be retrieved by the original query

... p.last_name) # This will be retrieved on demand

...

John Smith

Jane Jones
З зовнішнього вигляду це схоже на те, що запит отримав і ім’я, і прізвище. Однак цей приклад фактично
видав 3 запити. Запит raw () отримував лише перші імена - обидва прізвища отримувались на вимогу під
час їх надрукування.

Є лише одне поле, яке ви не можете пропустити - поле первинного ключа. Django використовує первинний
ключ для ідентифікації екземплярів моделі, тому його завжди потрібно включати в необроблений
запит. FieldDoesNotExistЗгенерує виняток , якщо ви забули включити первинний ключ.

Додавання анотацій ¶
Ви також можете виконувати запити, що містять поля, не визначені в моделі. Наприклад, ми могли б
використовувати функцію PostgreSQL age (), щоб отримати список людей з їх віком, розрахованим за
допомогою бази даних:

>>> people = Person.objects.raw('SELECT *, age(birth_date) AS age FROM


myapp_person')

>>> for p in people:

... print("%s is %s." % (p.first_name, p.age))

John is 37.

Jane is 42.

...

Часто можна уникнути використання необробленого SQL для обчислення анотацій, замість цього
використовуючи вираз Func () .

Передача параметрів у raw()¶


Якщо вам потрібно виконати параметризовані запити, ви можете використовувати params аргумент
для raw():

>>> lname = 'Doe'

>>> Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s',


[lname])

params- це список або словник параметрів. Ви будете використовувати %s заповнювачі в рядку запиту для
списку, або %(key)s заповнювачі для словника (де keyзамінено ключем словника), незалежно від
механізму бази даних. Такі заповнювачі будуть замінені параметрами з paramsаргументу.

Примітка

Параметри словника не підтримуються бекендом SQLite; за допомогою цієї серверної бази потрібно
передавати параметри у вигляді списку.
Увага

Не використовуйте форматування рядків для необроблених запитів або заповнювачів цитат у ваших
рядках SQL!

Заманливо написати вищезазначений запит як:

>>> query = 'SELECT * FROM myapp_person WHERE last_name = %s' % lname

>>> Person.objects.raw(query)
Ви також можете подумати, що вам слід написати свій запит так (із цитатами навколо %s):

>>> query = "SELECT * FROM myapp_person WHERE last_name = '%s'"

Не допускайте жодної з цих помилок.

Як обговорювалося в захисті від SQL-інжекції , використання params аргументу та залишення


заповнювачів без котирувань захищає вас від атак SQL-ін’єкцій , поширеного експлоїту, коли зловмисники
вводять довільний SQL у базу даних. Якщо ви використовуєте інтерполяцію рядків або цитуєте
заповнювач, ви ризикуєте ввести SQL.

Виконання користувацького SQL безпосередньо ¶


Іноді навіть Manager.raw()не цілком достатньо: вам може знадобитися для виконання запитів , що не
відображають чисто для моделей, або безпосередньо виконати UPDATE, INSERTабо DELETEзапити.

У цих випадках ви завжди можете отримати безпосередній доступ до бази даних, повністю проклавши
навколо шару моделі.

Об'єкт django.db.connectionпредставляє підключення до бази даних за замовчуванням. Щоб


використовувати підключення до бази даних, зателефонуйте, connection.cursor()щоб отримати
об’єкт курсору. Потім зателефонуйте для виконання SQL та або для повернення отриманих
рядків.cursor.execute(sql, [params])cursor.fetchone()cursor.fetchall()

Наприклад:

from django.db import connection

def my_custom_sql(self):

with connection.cursor() as cursor:

cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])

cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])

row = cursor.fetchone()

return row

Для захисту від ін’єкції SQL не слід включати лапки навколо %s заповнювачів у рядок SQL.

Зверніть увагу, що якщо ви хочете включити буквальні знаки відсотків у запит, вам доведеться подвоїти їх
у разі передачі параметрів:

cursor.execute("SELECT foo FROM bar WHERE baz = '30%'")

cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])

Якщо ви використовуєте більше однієї бази даних , ви можете скористатися django.db.connectionsдля


отримання з'єднання (і курсору) для певної бази даних. django.db.connections- це об’єкт, схожий на
словник, який дозволяє отримати певне з’єднання, використовуючи його псевдонім:

from django.db import connections


with connections['my_db_alias'].cursor() as cursor:

# Your code here...

За замовчуванням API Python DB повертає результати без їх імен полів, що означає, що в результаті ви
отримуєте listзначення a , а не a dict. За невеликої продуктивності та вартості пам'яті ви можете
повернути результати як a dict, використовуючи щось подібне:

def dictfetchall(cursor):

"Return all rows from a cursor as a dict"

columns = [col[0] for col in cursor.description]

return [

dict(zip(columns, row))

for row in cursor.fetchall()

Інший варіант - використовувати collections.namedtuple()зі стандартної бібліотеки


Python. A namedtuple- це кортежоподібний об'єкт, який має поля, доступні за допомогою пошуку
атрибутів; це також індексується та переглядається. Результати незмінні та доступні за назвами полів або
індексами, що може бути корисним:

from collections import namedtuple

def namedtuplefetchall(cursor):

"Return all rows from a cursor as a namedtuple"

desc = cursor.description

nt_result = namedtuple('Result', [col[0] for col in desc])

return [nt_result(*row) for row in cursor.fetchall()]

Ось приклад різниці між цими трьома:

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");

>>> cursor.fetchall()

((54360982, None), (54360880, None))

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");

>>> dictfetchall(cursor)

[{'parent_id': None, 'id': 54360982}, {'parent_id': None, 'id': 54360880}]

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");


>>> results = namedtuplefetchall(cursor)

>>> results

[Result(id=54360982, parent_id=None), Result(id=54360880, parent_id=None)]

>>> results[0].id

54360982

>>> results[0][0]

54360982

Підключення та курсори ¶
connectionі в cursorосновному реалізують стандартний DB-API Python, описаний вPEP 249 - крім
випадків, коли мова йде про обробку транзакцій .

Якщо ви не знайомі з Python DB-API, зверніть увагу, що оператор SQL cursor.execute()використовує


заповнювачі "%s", а не додає параметри безпосередньо в SQL. Якщо ви використовуєте цей прийом,
основна бібліотека бази даних автоматично буде уникати ваших параметрів за необхідності.

Також відзначимо , що Джанго очікує "%s"заповнювач, НЕ"?" заповнювач, який використовується


кріпленнями SQLite Python. Це заради послідовності та розсудливості.

Використання курсору як менеджера контексту:

with connection.cursor() as c:

c.execute(...)

еквівалентно:

c = connection.cursor()

try:

c.execute(...)

finally:

c.close()

Виклик збережених процедур ¶


CursorWrapper.callproc( procname , params = None , kparams = None ) ¶

Викликає процедуру, що зберігається в базі даних із зазначеним іменем. Може бути


надана послідовність ( params) або словник ( kparams) вхідних параметрів. Більшість баз даних не
підтримують kparams. Із вбудованих серверних систем Django це підтримує лише Oracle.

Наприклад, враховуючи цю збережену процедуру в базі даних Oracle:

CREATE PROCEDURE "TEST_PROCEDURE"(v_i INTEGER, v_text NVARCHAR2(10)) AS

p_i INTEGER;

p_text NVARCHAR2(10);
BEGIN

p_i := v_i;

p_text := v_text;

...

END;

Це буде називати це:

with connection.cursor() as cursor:

cursor.callproc('test_procedure', [1, 'test'])

Транзакції з базою даних ¶


Django надає кілька способів контролювати управління транзакціями баз даних.

Керування транзакціями баз даних ¶


Поведінка транзакцій за замовчуванням Django ¶
Поведінка Django за замовчуванням - це запуск у режимі автозвернення. Кожен запит негайно
прив'язується до бази даних, якщо транзакція не активна. Детальніше див. Нижче .

Django автоматично використовує транзакції або точки збереження, щоб гарантувати цілісність операцій
ORM, які вимагають декількох запитів, особливо запитів видалення () та оновлення () .

TestCaseКлас Django також обгортає кожен тест у транзакції з міркувань продуктивності.

Прив’язка транзакцій до запитів HTTP ¶


Поширеним способом обробки транзакцій в Інтернеті є обгортання кожного запиту
транзакцією. Встановіть ATOMIC_REQUESTSзначення Trueу конфігурації кожної бази даних, для якої
потрібно ввімкнути цю поведінку.

Це працює так. Перш ніж викликати функцію перегляду, Django запускає транзакцію. Якщо відповідь
надається без проблем, Django здійснює транзакцію. Якщо подання створює виняток, Django відкочує
транзакцію.

Ви можете виконувати субтранзакції, використовуючи точки збереження в коді перегляду, як правило, за


допомогою atomic()менеджера контексту. Однак у кінці подання буде внесено або всі, або жодні зміни.

Увага

Хоча простота цієї моделі транзакцій є привабливою, вона також робить її неефективною, коли
збільшується трафік. Відкриття транзакції для кожного представлення має певні накладні витрати. Вплив
на продуктивність залежить від шаблонів запитів вашої програми та від того, наскільки ваша база даних
обробляє блокування.
Транзакції за запитом та потокові відповіді

Коли подання повертає a StreamingHttpResponse, читання вмісту відповіді часто виконує код для
генерації вмісту. Оскільки подання вже повернулось, такий код працює поза транзакцією.
Взагалі кажучи, не бажано писати в базу даних під час формування потокової відповіді, оскільки немає
розумного способу обробки помилок після початку відправки відповіді.

На практиці ця функція обертає кожну функцію перегляду в atomic() декораторі, описаному нижче.

Зверніть увагу, що в транзакціях включається лише виконання вашого подання. Проміжне програмне


забезпечення працює поза транзакцією, як і візуалізація відповідей шаблону.

Коли ATOMIC_REQUESTSцей параметр увімкнено, все ще можна запобігти запуску подань у транзакції.

non_atomic_requests( використовуючи = Немає ) ¶

Цей декоратор відмінить ефект ATOMIC_REQUESTSдля даного виду:

from django.db import transaction

@transaction.non_atomic_requests

def my_view(request):

do_stuff()

@transaction.non_atomic_requests(using='other')

def my_other_view(request):

do_stuff_on_the_other_database()

Це працює, лише якщо застосовується до самого подання.

Управління транзакціями явно ¶


Django забезпечує єдиний API для управління транзакціями баз даних.

atomic( використовуючи = None , savepoint = True , durable = False ) ¶

Атомність - визначальна властивість транзакцій баз даних. atomic дозволяє нам створити блок


коду, в межах якого гарантується атомність бази даних. Якщо блок коду успішно завершено, зміни
передаються в базу даних. Якщо є виняток, зміни відкочуються.

atomicблоки можуть бути вкладеними. У цьому випадку, коли внутрішній блок успішно
завершується, його ефекти все ще можуть бути скасовані назад, якщо пізніше в зовнішньому блоці
буде викликано виняток.

Іноді корисно переконатись, що atomicблок завжди є самим зовнішнім atomicблоком,


гарантуючи, що будь-які зміни бази даних здійснюються, коли блок виходить без помилок. Це
відоме як довговічність, і його можна досягти за допомогою
налаштування durable=True. Якщо atomicблок вкладений в інший, він піднімає a RuntimeError.

atomicможна використовувати як декоратор :

from django.db import transaction


@transaction.atomic

def viewfunc(request):

# This code executes inside a transaction.

do_stuff()

і як менеджер контексту :

from django.db import transaction

def viewfunc(request):

# This code executes in autocommit mode (Django's default).

do_stuff()

with transaction.atomic():

# This code executes inside a transaction.

do_more_stuff()

Загортання atomicв блок try / Osim дозволяє природним чином обробляти помилки цілісності:

from django.db import IntegrityError, transaction

@transaction.atomic

def viewfunc(request):

create_parent()

try:

with transaction.atomic():

generate_relationships()

except IntegrityError:

handle_exception()

add_children()

У цьому прикладі, навіть якщо generate_relationships()спричиняє помилку бази даних,


порушуючи обмеження цілісності, ви можете виконувати запити в add_children(), а зміни
від create_parent()все ще існують і прив'язані до тієї самої транзакції. Зверніть увагу, що будь-
які спроби generate_relationships(), які були здійснені , вже будуть безпечно відкочуватися
при handle_exception()виклику, тому обробник винятків також може працювати з базою даних,
якщо це необхідно.

Уникайте ловити винятки всередині atomic!

Виходячи з atomicблоку, Django перевіряє, чи вийшов він із режиму нормально, або за винятком,
щоб визначити, чи слід фіксувати чи відкочуватись назад. Якщо ви ловите та обробляєте винятки
всередині atomicблоку, ви можете приховати від Django той факт, що сталася проблема. Це може
спричинити несподівану поведінку.

Це здебільшого турбує DatabaseErrorта його підкласи, такі як IntegrityError. Після такої


помилки транзакція порушується, і Django виконає відкат в кінці atomicблоку. Якщо ви спробуєте
запустити запити до бази даних до того, як відбудеться відкат, Django підніме
файл TransactionManagementError. Ви також можете зіткнутися з такою поведінкою, коли
обробник сигналу, пов'язаний з ORM, викликає виняток.

Правильний спосіб виявлення помилок бази даних - це навколо atomicблоку, як показано


вище. Якщо потрібно, додайте atomicдля цього додатковий блок. Цей шаблон має ще одну
перевагу: він чітко визначає, які операції будуть відкатані у разі виникнення винятку.

Якщо ви ловите винятки, викликані необробленими запитами SQL, поведінка Django не визначена
і залежить від бази даних.
Можливо, вам доведеться вручну повернути стан моделі під час відкочування транзакції.

Значення полів моделі не повертаються, коли відбувається відкат транзакції. Це може призвести
до невідповідного стану моделі, якщо ви вручну не відновите початкові значення полів.

Наприклад, якщо MyModelз activeполем, цей фрагмент гарантує , що перевірка в кінці


використовує правильне значення , якщо оновлення для зазнає невдачі в
угоді:if obj.activeactiveTrue

from django.db import DatabaseError, transaction

obj = MyModel(active=False)

obj.active = True

try:

with transaction.atomic():

obj.save()

except DatabaseError:

obj.active = False

if obj.active:

...

Щоб гарантувати атомність, atomicвідключає деякі API. Спроба зафіксувати, відкотити або


змінити стан автокомісії підключення до бази даних всередині atomicблоку спричинить виняток.

atomicприймає usingаргумент, який повинен бути ім'ям бази даних. Якщо цього аргументу не


вказано, Django використовує "default" базу даних.
Під капотом код управління транзакціями Django:

 відкриває транзакцію при введенні самого зовнішнього atomicблоку;


 створює точку збереження при введенні внутрішнього atomicблоку;
 звільняється або відкочується до точки збереження при виході з внутрішнього блоку;
 фіксує або відкочує транзакцію при виході з самого зовнішнього блоку.

Ви можете відключити створення точок збереження для внутрішніх блоків, встановивши


для savepointаргументу значення False. Якщо трапляється виняток, Django виконає відкат при
виході з першого батьківського блоку з точкою збереження, якщо така є, а з самого зовнішнього
блоку - в іншому випадку. Атомність все ще гарантується зовнішньою транзакцією. Цей параметр
слід використовувати, лише якщо помітні накладні витрати на точки збереження. Він має недолік у
виправленні обробки помилок, описаних вище.

Ви можете використовувати, atomicколи автозв'язок вимкнено. Він використовуватиме лише


точки збереження, навіть для самого зовнішнього блоку.

Міркування щодо продуктивності

Відкриті транзакції мають вартість продуктивності для вашого сервера баз даних. Щоб мінімізувати ці
накладні витрати, тримайте свої операції якомога коротшими. Це особливо важливо, якщо ви
використовуєте atomic()тривалі процеси, поза циклам запитів / відповідей Django.
Увага

django.test.TestCaseвимикає перевірку довговічності, щоб дозволити тестувати довговічні атомні


блоки в транзакції з міркувань продуктивності. Використовуйте django.test.TransactionTestCaseдля
перевірки міцності.
Змінено в Django 3.2:
durableАргумент був доданий.

Автокомісія ¶
Чому Django використовує автокомісію ¶
У стандартах SQL кожен запит SQL запускає транзакцію, якщо вона вже не активна. Потім такі операції
повинні бути чітко скоєні або скасовані.

Це не завжди зручно для розробників додатків. Для усунення цієї проблеми більшість баз даних
забезпечують режим автокомісії. Коли автокомітет увімкнено і жодна транзакція не активна, кожен запит
SQL загортається у свою транзакцію. Іншими словами, не тільки кожен такий запит запускає транзакцію,
але транзакція також отримує автоматичне фіксацію або відкат, залежно від того, чи вдався запит.

PEP 249 , специфікація API бази даних Python v2.0, вимагає спочатку вимкнути автокомітування. Django
замінює це значення за замовчуванням і вмикає автокомітування.

Щоб цього уникнути, ви можете деактивувати управління транзакціями , але це не рекомендується.

Деактивація управління транзакціями ¶


Ви можете повністю вимкнути управління транзакціями Django для даної бази даних,
встановивши AUTOCOMMITзначення Falseв її конфігурації. Якщо ви зробите це, Django не вмикає
автокомітування та не виконує жодних комітів. Ви отримаєте регулярну поведінку базової бібліотеки бази
даних.
Для цього потрібно чітко фіксувати кожну транзакцію, навіть транзакцію, розпочату Django або
сторонніми бібліотеками. Таким чином, це найкраще використовувати в ситуаціях, коли ви хочете
запустити власне проміжне програмне забезпечення для контролю транзакцій або зробити щось справді
дивне.

Виконання дій після коміту ¶


Іноді потрібно виконати дію, пов’язану з поточною транзакцією бази даних, але лише в тому випадку, якщо
транзакція успішно фіксується. Приклади можуть включати завдання Celery , сповіщення електронною
поштою або скасування кешу.

Django надає on_commit()функцію реєстрації функцій зворотного виклику, які слід виконувати після
успішного здійснення транзакції:

on_commit( func , використовуючи = None ) ¶

Передайте будь-яку функцію (яка не бере аргументів) on_commit():

from django.db import transaction

def do_something():

pass # send a mail, invalidate a cache, fire off a Celery task, etc.

transaction.on_commit(do_something)

Ви також можете обернути свою функцію лямбда-звуком:

transaction.on_commit(lambda: some_celery_task.delay('arg1'))

Функція, яку ви on_commit()передаєте, буде викликана відразу після того, як гіпотетичний запис бази
даних, зроблений там, де викликається, буде успішно здійснений.

Якщо ви телефонуєте, on_commit()поки немає активної транзакції, зворотний дзвінок буде виконаний


негайно.

Якщо замість цього гіпотетичне записування бази даних відкочується (як правило, коли
в atomic()блоці виникає необроблене виняток ), ваша функція буде відкинута і ніколи не буде викликана.

Точки збереження ¶
Точки збереження (тобто вкладені atomic()блоки) обробляються правильно. Тобто,
викликається, on_commit()зареєстрований після точки збереження (у вкладеному atomic()блоці), буде
викликаний після фіксації зовнішньої транзакції, але не в тому випадку, якщо під час транзакції відбувся
відкат до цієї точки збереження або будь-якої попередньої точки збереження:

with transaction.atomic(): # Outer atomic, start a new transaction

transaction.on_commit(foo)

with transaction.atomic(): # Inner atomic block, create a savepoint


transaction.on_commit(bar)

# foo() and then bar() will be called when leaving the outermost block

З іншого боку, коли точка збереження відкочується (через виняток, що викликається), внутрішній виклик
не буде викликаний:

with transaction.atomic(): # Outer atomic, start a new transaction

transaction.on_commit(foo)

try:

with transaction.atomic(): # Inner atomic block, create a savepoint

transaction.on_commit(bar)

raise SomeError() # Raising an exception - abort the savepoint

except SomeError:

pass

# foo() will be called, but not bar()

Порядок виконання ¶
Функції on-commit для даної транзакції виконуються в тому порядку, в якому вони були зареєстровані.

Обробка винятків ¶
Якщо одна функція фіксації в межах даної транзакції викликає невловлюваний виняток, жодна з пізніше
зареєстрованих функцій у цій самій транзакції не буде запущена. Це така сама поведінка, як якщо б ви
виконували функції самостійно без них on_commit().

Термін виконання ¶
Ваші зворотні виклики виконуються після успішного фіксації, тому помилка зворотного виклику не
призведе до відкотування транзакції. Вони виконуються умовно після успіху операції, але вони не
є частиною операції. Що стосується випадків використання (сповіщення поштою, завдання Celery тощо),
це повинно бути добре. Якщо це не так (якщо ваша подальша дія настільки критична, що її збій повинен
означати провал самої транзакції), тоді ви не хочете використовувати on_commit()гачок. Натомість вам
може знадобитися двофазне комітування, таке як підтримка протоколу psycopg Двофазне
комітування танеобов’язкові двофазні розширення комітів у специфікації Python DB-API .

Зворотні виклики не запускаються, доки не буде відновлено autocommit у підключенні після коміту
(оскільки в іншому випадку будь-які запити, зроблені в зворотному виклику, відкриють неявну транзакцію,
не даючи змозі з'єднанню повернутися в режим автозавершення).

У режимі автокомісії та поза atomic()блоком функція запускається негайно, а не при фіксації.


Функції On-commit працюють лише з режимом автоздійснення та API atomic()(або ATOMIC_REQUESTS)
транзакцій. Виклик, on_commit()коли автокомісія вимкнено, і ви не знаходитесь в атомному блоці,
призведе до помилки.

Використання в тестах ¶
TestCaseКлас Django обертає кожен тест у транзакцію та відкочує цю транзакцію після кожного тесту,
щоб забезпечити ізоляцію тесту. Це означає, що жодна транзакція фактично ніколи не здійснюється, тому
ваші on_commit()зворотні дзвінки ніколи не будуть запущені.

Ви можете подолати це обмеження за допомогою TestCase.captureOnCommitCallbacks(). Це фіксує


ваші on_commit()зворотні виклики у списку, дозволяючи вам робити твердження щодо них або імітувати
транзакцію, що здійснюється, викликаючи їх.

Інший спосіб подолати обмеження - використовувати TransactionTestCaseзамість TestCase. Це


означатиме, що ваші транзакції здійснені, а зворотні дзвінки будуть
запущені. Однак TransactionTestCaseзмиває базу даних між тестами, що значно повільніше,
ніж TestCaseросійська ізоляція.

Чому немає відкату? ¶


Гак відкоту важче реалізувати надійно, ніж хук коміту, оскільки різні речі можуть спричинити неявний
відкат.

Наприклад, якщо ваше з’єднання з базою даних перервано через те, що ваш процес був убитий, не маючи
можливості елегантно вимкнутись, ваш відкат ніколи не буде запущений.

Але є рішення: замість того, щоб щось робити під час атомарного блоку (транзакції), а потім скасовувати
це, якщо транзакція не вдається, використовуйте, on_commit()щоб затримати виконання цього, перш ніж
транзакція буде успішною. Набагато простіше скасувати те, чого ти ніколи не робив!

API низького рівня ¶


Увага

Завжди віддайте перевагу, atomic()якщо це можливо взагалі. Він враховує особливості кожної бази


даних та запобігає недійсним операціям.

API низького рівня корисні лише в тому випадку, якщо ви впроваджуєте власне управління транзакціями.

Автокомісія ¶
Django надає API в django.db.transactionмодулі для управління станом автокомісії кожного з'єднання
з базою даних.

get_autocommit( використовуючи = Немає ) ¶
set_autocommit( автокомітування , використання = Немає ) ¶

Ці функції приймають usingаргумент, який повинен бути ім'ям бази даних. Якщо це не вказано, Django
використовує "default"базу даних.

Autocommit спочатку ввімкнено. Якщо ви вимкнете його, це ваш обов'язок відновити його.

Після вимкнення автокомісії ви отримуєте поведінку адаптера бази даних за замовчуванням, і Django вам
не допоможе. Хоча така поведінка вказана в PEP 249 , реалізації адаптерів не завжди узгоджуються між
собою. Уважно перегляньте документацію адаптера, який ви використовуєте.
Ви повинні переконатися, що жодна транзакція не активна, як правило, видаючи a commit()або
a rollback(), перш ніж знову вмикати автокомісію.

Django відмовиться вимкнути автокомісію, коли atomic()активний блок, оскільки це порушить атомність.

Операції ¶
Транзакція - це атомний набір запитів до бази даних. Навіть якщо ваша програма виходить з ладу, база
даних гарантує, що будуть застосовані або всі зміни, або жодна з них.

Django не надає API для запуску транзакції. Очікуваний спосіб розпочати транзакцію - відключити
автозвернення за допомогою set_autocommit().

Після того, як ви здійснили транзакцію, ви можете вибрати або застосувати зміни, які ви виконували до
цього моменту commit(), або скасувати їх за допомогою rollback(). Ці функції визначені
в django.db.transaction.

commit( використовуючи = Немає ) ¶
rollback( використовуючи = Немає ) ¶

Ці функції приймають usingаргумент, який повинен бути ім'ям бази даних. Якщо це не вказано, Django
використовує "default"базу даних.

Django відмовиться виконувати або відмовляти, коли atomic()активний блок, оскільки це порушить


атомність.

Точки збереження ¶
Точка збереження - це маркер усередині транзакції, що дозволяє відмовити частину транзакції, а не повну
транзакцію. Точки збереження доступні з бекендами SQLite, PostgreSQL, Oracle та MySQL (при
використанні механізму зберігання InnoDB). Інші бекенди надають функції точки збереження, але це
порожні операції - вони насправді нічого не роблять.

Точки збереження не особливо корисні, якщо ви використовуєте autocommit, поведінку Django за


замовчуванням. Однак, як тільки ви відкриваєте транзакцію atomic(), ви створюєте низку операцій з
базою даних, які очікують на фіксацію або відкат. Якщо ви робите відкат, вся транзакція
відкочується. Точки збереження надають можливість виконувати дрібнозернистий відкат, а не повний
відкат, який виконував би transaction.rollback().

Коли atomic()декоратор вкладений, він створює точку збереження, щоб дозволити часткове фіксацію
або відкат. Вам настійно рекомендується використовувати функції, atomic() а не функції, описані нижче,
але вони все ще є частиною загальнодоступного API, і не планується їх припиняти.

Кожна з цих функцій приймає usingаргумент, який повинен бути ім'ям бази даних, до якої застосовується
поведінка. Якщо usingаргумент не вказаний, використовується "default"база даних.

Точки збереження контролюються трьома функціями в django.db.transaction:

savepoint( використовуючи = Немає ) ¶

Створює нову точку збереження. Це позначає момент транзакції, який, як відомо, перебуває у
"хорошому" стані. Повертає ідентифікатор точки збереження ( sid).

savepoint_commit( sid , використовуючи = None ) ¶

Випускає точку збереження sid. Зміни, внесені з моменту створення точки збереження, стають


частиною транзакції.
savepoint_rollback( sid , використовуючи = None ) ¶

Відкочує транзакцію до точки збереження sid.

Ці функції нічого не роблять, якщо точки збереження не підтримуються або якщо база даних перебуває в
режимі автокомісії.

Крім того, є функція корисності:

clean_savepoints( використовуючи = Немає ) ¶

Скидає лічильник, який використовується для створення унікальних ідентифікаторів точки


збереження.

Наступний приклад демонструє використання точок збереження:

from django.db import transaction

# open a transaction

@transaction.atomic

def viewfunc(request):

a.save()

# transaction now contains a.save()

sid = transaction.savepoint()

b.save()

# transaction now contains a.save() and b.save()

if want_to_keep_b:

transaction.savepoint_commit(sid)

# open transaction still contains a.save() and b.save()

else:

transaction.savepoint_rollback(sid)

# open transaction now contains only a.save()

Точки збереження можуть бути використані для відновлення після помилки бази даних шляхом часткового
відкоту. Якщо ви робите це всередині atomic()блоку, весь блок все одно буде відкинутий назад, тому що
він не знає, що ви обробили ситуацію на нижчому рівні! Щоб запобігти цьому, ви можете контролювати
поведінку відкоту за допомогою таких функцій.
get_rollback( використовуючи = Немає ) ¶
set_rollback( відкат , використовуючи = Немає ) ¶

Встановлення прапора відкоту для Trueпримусового відкоту при виході з внутрішнього атомного


блоку. Це може бути корисно для активації відкоту без винятку.

Встановивши для Falseзапобігання такому відкоту. Перш ніж це зробити, переконайтеся, що ви


повернули транзакцію до відомої хорошої точки збереження в поточному атомному блоці! В іншому
випадку ви порушуєте атомність і може статися пошкодження даних.

Примітки щодо баз даних ¶


Точки збереження в SQLite ¶
Хоча SQLite підтримує точки збереження, недолік дизайну sqlite3 модуля робить їх навряд чи
придатними для використання.

Коли ввімкнено автозвернення, точки збереження не мають сенсу. Коли його


вимкнено, sqlite3фіксується неявно перед операторами точки збереження. (Насправді, воно робить
перед будь-яким заявою, крім SELECT, INSERT, UPDATE, DELETEі REPLACE.) Ця помилка має два наслідки:

 API низького рівня для точок збереження можна використовувати лише всередині транзакції,
тобто. всередині atomic()блоку.
 Це неможливо використовувати, atomic()коли автокомітет вимкнено.

Транзакції в MySQL ¶
Якщо ви використовуєте MySQL, ваші таблиці можуть підтримувати транзакції або не підтримувати їх; це
залежить від вашої версії MySQL та типів таблиць, які ви використовуєте. (Під «типами таблиць» ми маємо
на увазі щось на зразок «InnoDB» або «MyISAM».) Особливості транзакцій MySQL виходять за рамки цієї
статті, але на сайті MySQL є інформація про транзакції MySQL .

Якщо ваша установка MySQL не підтримує транзакції, то Django завжди буде функціонувати в режимі
автокомісії: оператори будуть виконуватися та фіксуватися, як тільки вони будуть викликані. Якщо ваша
установка MySQL робить підтримку транзакцій, Django буде обробляти транзакції , як описано в даному
документі.

Обробка винятків у транзакціях PostgreSQL ¶


Примітка

Цей розділ актуальний, лише якщо ви впроваджуєте власне управління транзакціями. Ця проблема не
може виникнути в режимі за замовчуванням Django і atomic()обробляє її автоматично.

Усередині транзакції, коли виклик курсору PostgreSQL викликає виняток (як правило IntegrityError),
усі наступні SQL в тій самій транзакції не справляються з помилкою "поточна транзакція скасована, запити
ігноруються до кінця блоку транзакції". Незважаючи на те, що базове використання save(), навряд чи,
може спричинити виняток у PostgreSQL, існують більш досконалі шаблони використання, які можуть,
наприклад, зберігати об'єкти з унікальними полями, зберігати за допомогою прапора force_insert /
force_update або викликати власний SQL.

Існує кілька способів відновити цю помилку.

Відкат транзакції ¶

Перший варіант - відмовити всю транзакцію. Наприклад:


a.save() # Succeeds, but may be undone by transaction rollback

try:

b.save() # Could throw exception

except IntegrityError:

transaction.rollback()

c.save() # Succeeds, but a.save() may have been undone

Виклик transaction.rollback()відкочує всю транзакцію. Будь-які незв'язані операції з базою даних


будуть втрачені. У цьому прикладі зміни, внесені користувачем a.save(), будуть втрачені, хоча сама
операція не викликала жодної помилки.

Відкат Savepoint ¶

Ви можете використовувати точки збереження, щоб контролювати ступінь відкоту. Перш ніж виконувати


операцію з базою даних, яка може вийти з ладу, ви можете встановити або оновити точку
збереження; таким чином, якщо операція не вдається, ви можете відмовити одну операцію, що порушує, а
не всю транзакцію. Наприклад:

a.save() # Succeeds, and never undone by savepoint rollback

sid = transaction.savepoint()

try:

b.save() # Could throw exception

transaction.savepoint_commit(sid)

except IntegrityError:

transaction.savepoint_rollback(sid)

c.save() # Succeeds, and a.save() is never undone

У цьому прикладі a.save()не буде скасовано у випадку, коли b.save()виникає виняток.

Кілька баз даних ¶


Цей посібник описує підтримку Django для взаємодії з багатьма базами даних. Більшість решти
документації Django передбачає, що ви взаємодієте з однією базою даних. Якщо ви хочете взаємодіяти з
кількома базами даних, вам потрібно буде зробити кілька додаткових кроків.

Дивитися також

Див Підтримка кілька баз даних для отримання інформації про тестування з декількома базами даних.

Визначення ваших баз даних ¶


Перший крок до використання більше однієї бази даних з Django - це повідомити Django про сервери баз
даних, якими ви будете користуватися. Це робиться за допомогою DATABASESналаштування. Цей
параметр відображає псевдоніми бази даних, які є способом посилання на певну базу даних у всьому
Django, у словник налаштувань для цього конкретного підключення. Параметри внутрішніх словників
повністю описані в DATABASES документації.
Бази даних можуть мати будь-який псевдонім, який ви вибрали. Однак псевдонім defaultмає особливе
значення. Django використовує базу даних з псевдонімом, defaultколи жодна інша база даних не
вибрана.

Далі наведено приклад settings.pyфрагмента, що визначає дві бази даних - базу даних PostgreSQL за
замовчуванням та базу даних MySQL users:

DATABASES = {

'default': {

'NAME': 'app_data',

'ENGINE': 'django.db.backends.postgresql',

'USER': 'postgres_user',

'PASSWORD': 's3krit'

},

'users': {

'NAME': 'user_data',

'ENGINE': 'django.db.backends.mysql',

'USER': 'mysql_user',

'PASSWORD': 'priv4te'

Якщо поняття defaultбази даних не має сенсу в контексті вашого проекту, потрібно бути обережним, щоб
завжди вказувати базу даних, яку ви хочете використовувати. Django вимагає визначити defaultзапис
бази даних, але словник параметрів можна залишити порожнім, якщо він не використовуватиметься. Для
цього потрібно налаштувати DATABASE_ROUTERSвсі моделі ваших додатків, включаючи моделі в будь-
якому додатку contrib та сторонніх програмах, якими ви користуєтесь, щоб жоден запит не
перенаправлявся до бази даних за замовчуванням. Далі наведено приклад settings.pyфрагмента, що
визначає дві бази даних, що не за замовчуванням, із defaultвведенням навмисно порожнім:

DATABASES = {

'default': {},

'users': {

'NAME': 'user_data',

'ENGINE': 'django.db.backends.mysql',

'USER': 'mysql_user',

'PASSWORD': 'superS3cret'

},

'customers': {
'NAME': 'customer_data',

'ENGINE': 'django.db.backends.mysql',

'USER': 'mysql_cust',

'PASSWORD': 'veryPriv@ate'

Якщо ви спробуєте отримати доступ до бази даних, яку ви не визначили в DATABASESналаштуваннях,


Django викличе django.utils.connection.ConnectionDoesNotExistвиняток.

Синхронізація баз даних ¶


Команда migrateуправління працює одночасно з однією базою даних. За замовчуванням він працює
з defaultбазою даних, але, надавши --databaseопцію, ви можете сказати їй синхронізувати іншу базу
даних. Отже, щоб синхронізувати всі моделі з усіма базами даних у першому прикладі вище, вам потрібно
буде викликати:

$ ./manage.py migrate

$ ./manage.py migrate --database=users

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

Якщо, як у другому прикладі вище, Ви залишили defaultбазу даних порожньою, Ви повинні вказувати


ім'я бази даних кожного разу, коли Ви запускаєте migrate. Пропуск імені бази даних спричинить
помилку. Для другого прикладу:

$ ./manage.py migrate --database=users

$ ./manage.py migrate --database=customers

Використання інших команд управління ¶


Більшість інших django-adminкоманд, які взаємодіють з базою даних, працюють так само, як і migrate-
вони працюють лише з однією базою даних одночасно, використовуючи --databaseдля управління
використовуваною базою даних.

Винятком із цього правила є makemigrationsкоманда. Він перевіряє історію міграції в базах даних, щоб
виявити проблеми з існуючими файлами міграції (що може бути спричинено їх редагуванням) перед
створенням нових міграцій. За замовчуванням він перевіряє лише defaultбазу даних, але він
використовує allow_migrate()метод маршрутизаторів, якщо такі встановлені.

Автоматична маршрутизація бази даних ¶


Найпростіший спосіб використання декількох баз даних - це встановлення схеми маршрутизації баз
даних. Схема маршрутизації за замовчуванням гарантує, що об'єкти залишаться «липкими» до своєї
вихідної бази даних (тобто об’єкт, отриманий з fooбази даних, буде збережений у тій самій базі
даних). Схема маршрутизації за замовчуванням гарантує, що якщо база даних не вказана, всі запити
повертаються до defaultбази даних.
Вам не потрібно нічого робити, щоб активувати схему маршрутизації за замовчуванням - вона надається
"нестандартно" для кожного проекту Django. Однак, якщо ви хочете реалізувати більш цікаві способи
розподілу баз даних, ви можете визначити та встановити власні маршрутизатори баз даних.

Маршрутизатори баз даних ¶


Маршрутизатор баз даних - це клас, який забезпечує до чотирьох методів:

db_for_read( модель , ** підказки ) ¶

Запропонуйте базу даних, яку слід використовувати для операцій зчитування об’єктів типу model.

Якщо операція з базою даних може надати будь-яку додаткову інформацію, яка може допомогти у
виборі бази даних, вона буде вказана у hintsсловнику. Детальна інформація щодо дійсних
підказок наведена нижче .

Повертає, Noneякщо пропозиції немає.

db_for_write( модель , ** підказки ) ¶

Запропонуйте базу даних, яку слід використовувати для запису об’єктів типу Model.

Якщо операція з базою даних може надати будь-яку додаткову інформацію, яка може допомогти у
виборі бази даних, вона буде вказана у hintsсловнику. Детальна інформація щодо дійсних
підказок наведена нижче .

Повертає, Noneякщо пропозиції немає.

allow_relation( obj1 , obj2 , ** підказки ) ¶

Повернення, Trueякщо відношення між obj1та obj2має бути дозволено, Falseякщо відношення


слід запобігти або Noneякщо маршрутизатор не має думки. Це суто операція перевірки, яка
використовується зовнішнім ключем і багато-багато операцій, щоб визначити, чи слід дозволяти
відношення між двома об'єктами.

Якщо жоден маршрутизатор не має думки (тобто всі маршрутизатори повертаються None),


дозволяються лише відносини в одній базі даних.

allow_migrate( db , app_label , model_name = None , ** підказки ) ¶

Визначте, чи дозволено виконувати операцію міграції в базі даних з


псевдонімом db. Повернення, Trueякщо операція повинна виконуватися, Falseякщо вона не
повинна запускатися або Noneякщо маршрутизатор не має думки.

app_labelПозиційна аргумент мітки додатки, міграція.

model_nameбільшістю операцій міграції встановлюється


значення model._meta.model_name(версія нижнього регістру моделі __name__) моделі, що
переноситься. Його значення Noneдля операцій RunPythonі, RunSQLякщо вони не надають його
за допомогою підказок.

hints використовуються певними операціями для передачі додаткової інформації


маршрутизатору.

Коли model_nameвстановлено, hintsзазвичай містить клас класу під ключем 'model'. Зверніть


увагу, що це може бути історична модель і, отже, не мати будь-яких користувацьких атрибутів,
методів або менеджерів. На це слід покладатися лише _meta.
Цей метод також може бути використаний для визначення наявності моделі в даній базі даних.

makemigrationsзавжди створює міграції для змін моделі, але


якщо allow_migrate()повернеться False, будь-які операції міграції для model_nameбуде
мовчки пропускатися під час запуску migrateна db. Зміна поведінки allow_migrate()моделей,
які вже мають міграції, може призвести до непрацюючих зовнішніх ключів, зайвих таблиць або
відсутніх таблиць. Коли makemigrationsперевіряється історія міграції, вона пропускає бази
даних, де жодна програма не може мігрувати.

Маршрутизатор не повинен надавати всі ці методи - він може пропустити один або кілька з них. Якщо один
із методів опущений, Django пропустить цей маршрутизатор під час виконання відповідної перевірки.

Підказки ¶

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

На даний момент, єдиною підказкою, яка буде надана instance, є екземпляр об’єкта, який пов’язаний із
операцією читання або запису, що виконується. Це може бути екземпляр, який зберігається, або
екземпляр, який додається у відношенні багато-до-багатьох. У деяких випадках жодних підказок не буде
надано взагалі. Маршрутизатор перевіряє наявність підказки екземпляра та визначає, чи слід
використовувати цю підказку для зміни поведінки маршрутизації.

Використання маршрутизаторів ¶
Маршрутизатори баз даних встановлюються за допомогою цього DATABASE_ROUTERS параметра. Цей
параметр визначає список назв класів, кожен із яких визначає маршрутизатор, який повинен
використовувати головний маршрутизатор ( django.db.router).

Головний маршрутизатор використовується операціями баз даних Django для розподілу використання
бази даних. Всякий раз, коли запит повинен знати, яку базу даних використовувати, він викликає головний
маршрутизатор, надаючи модель та підказку (якщо така є). Потім Django пробує кожен маршрутизатор по
черзі, поки не буде знайдено пропозицію бази даних. Якщо жодної пропозиції не вдається знайти, він
намагається виконати поточний instance._state.dbекземпляр підказки. Якщо екземпляр підказки не
був наданий або instance._state.dbє None, головний маршрутизатор виділить defaultбазу даних.

Приклад ¶
Тільки приклади!

Цей приклад призначений для демонстрації того, як інфраструктуру маршрутизатора можна


використовувати для зміни використання бази даних. Він навмисно ігнорує деякі складні проблеми, щоб
продемонструвати, як використовуються маршрутизатори.

Цей приклад не буде працювати, якщо будь-яка з моделей myappмістить зв'язки з моделями


поза otherбазою даних. Взаємозв’язки між базами даних створюють посилальні проблеми цілісності, які
Django наразі не може впоратись.

Описана конфігурація первинної / репліки (яку в деяких базах даних називають ведучою / веденою) також
неправомірна - вона не надає жодного рішення для обробки затримки реплікації (тобто невідповідності
запитів, внесені через час, необхідний для розповсюдження запису на копії). Він також не розглядає
взаємодію транзакцій зі стратегією використання бази даних.

Отже - що це означає на практиці? Давайте розглянемо інший зразок конфігурації. У ньому буде декілька
баз даних: одна для authпрограми та всі інші програми, що використовують первинну / реплічну установку
з двома прочитаними репліками. Ось налаштування, що визначають ці бази даних:

DATABASES = {
'default': {},

'auth_db': {

'NAME': 'auth_db_name',

'ENGINE': 'django.db.backends.mysql',

'USER': 'mysql_user',

'PASSWORD': 'swordfish',

},

'primary': {

'NAME': 'primary_name',

'ENGINE': 'django.db.backends.mysql',

'USER': 'mysql_user',

'PASSWORD': 'spam',

},

'replica1': {

'NAME': 'replica1_name',

'ENGINE': 'django.db.backends.mysql',

'USER': 'mysql_user',

'PASSWORD': 'eggs',

},

'replica2': {

'NAME': 'replica2_name',

'ENGINE': 'django.db.backends.mysql',

'USER': 'mysql_user',

'PASSWORD': 'bacon',

},

Тепер нам потрібно буде впоратися з маршрутизацією. Спочатку нам потрібен маршрутизатор, який знає,
що надсилатиме запити на додатки authта contenttypesдодатки auth_db ( authмоделі
пов’язані ContentType, тому вони повинні зберігатися в одній базі даних):

class AuthRouter:

"""

A router to control all database operations on models in the


auth and contenttypes applications.

"""

route_app_labels = {'auth', 'contenttypes'}

def db_for_read(self, model, **hints):

"""

Attempts to read auth and contenttypes models go to auth_db.

"""

if model._meta.app_label in self.route_app_labels:

return 'auth_db'

return None

def db_for_write(self, model, **hints):

"""

Attempts to write auth and contenttypes models go to auth_db.

"""

if model._meta.app_label in self.route_app_labels:

return 'auth_db'

return None

def allow_relation(self, obj1, obj2, **hints):

"""

Allow relations if a model in the auth or contenttypes apps is

involved.

"""

if (

obj1._meta.app_label in self.route_app_labels or

obj2._meta.app_label in self.route_app_labels

):

return True

return None
def allow_migrate(self, db, app_label, model_name=None, **hints):

"""

Make sure the auth and contenttypes apps only appear in the

'auth_db' database.

"""

if app_label in self.route_app_labels:

return db == 'auth_db'

return None

І нам також потрібен маршрутизатор, який надсилає всі інші програми до основної конфігурації / репліки і
випадково вибирає репліку для читання:

import random

class PrimaryReplicaRouter:

def db_for_read(self, model, **hints):

"""

Reads go to a randomly-chosen replica.

"""

return random.choice(['replica1', 'replica2'])

def db_for_write(self, model, **hints):

"""

Writes always go to primary.

"""

return 'primary'

def allow_relation(self, obj1, obj2, **hints):

"""

Relations between objects are allowed if both objects are

in the primary/replica pool.

"""
db_set = {'primary', 'replica1', 'replica2'}

if obj1._state.db in db_set and obj2._state.db in db_set:

return True

return None

def allow_migrate(self, db, app_label, model_name=None, **hints):

"""

All non-auth models end up in this pool.

"""

return True

Нарешті, у файл налаштувань ми додаємо наступне (замінюючи path.to.фактичним шляхом Python до


модуля (модулів), де визначено маршрутизатори):

DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.PrimaryReplicaRouter']

Порядок обробки маршрутизаторів є значним. Маршрутизатори будуть запитуватися в тому порядку, в


якому вони вказані в DATABASE_ROUTERSналаштуваннях. У цьому прикладі AuthRouterобробляється
до PrimaryReplicaRouter, і, як результат, рішення щодо моделей в authобробляються до прийняття
будь-якого іншого рішення. Якщо в DATABASE_ROUTERS налаштуваннях перераховані два маршрутизатори
в іншому порядку, PrimaryReplicaRouter.allow_migrate()спочатку буде оброблено. Загальний
характер реалізації PrimaryReplicaRouter означав би, що всі моделі будуть доступні у всіх базах даних.

Після встановлення цього налаштування та перенесення всіх баз даних відповідно до Синхронізації баз
даних , можна запустити деякий код Django:

>>> # This retrieval will be performed on the 'auth_db' database

>>> fred = User.objects.get(username='fred')

>>> fred.first_name = 'Frederick'

>>> # This save will also be directed to 'auth_db'

>>> fred.save()

>>> # These retrieval will be randomly allocated to a replica database

>>> dna = Person.objects.get(name='Douglas Adams')

>>> # A new object has no database allocation when created

>>> mh = Book(title='Mostly Harmless')


>>> # This assignment will consult the router, and set mh onto

>>> # the same database as the author object

>>> mh.author = dna

>>> # This save will force the 'mh' instance onto the primary database...

>>> mh.save()

>>> # ... but if we re-retrieve the object, it will come back on a replica

>>> mh = Book.objects.get(title='Mostly Harmless')

У цьому прикладі визначено маршрутизатор для обробки взаємодії з моделями з authпрограми, а інші
маршрутизатори для обробки взаємодії з усіма іншими програмами. Якщо ви залишили defaultбазу
даних порожньою і не хочете визначати загальнодоступний маршрутизатор баз даних для обробки всіх
додатків, не вказаних інакше, ваші маршрутизатори повинні обробляти імена всіх програм, що
перебувають, INSTALLED_APPSперед міграцією. Інформацію про програми contrib, які мають бути
об’єднаними в одній базі даних, див. У розділі Поведінка додатків contrib.

Вибір бази даних вручну ¶


Django також пропонує API, який дозволяє підтримувати повний контроль над використанням бази даних
у коді. Розподіл бази даних, вказаний вручну, матиме пріоритет над базою даних, призначеною
маршрутизатором.

Вибір бази даних для ¶ вручнуQuerySet


Ви можете вибрати базу даних для QuerySetбудь-якої
точки QuerySet"ланцюжка". Виклик using()на , QuerySetщоб отримати інший , QuerySetякий
використовує зазначену базу даних.

using()приймає один аргумент: псевдонім бази даних, для якої потрібно виконати запит. Наприклад:

>>> # This will run on the 'default' database.

>>> Author.objects.all()

>>> # So will this.

>>> Author.objects.using('default').all()

>>> # This will run on the 'other' database.

>>> Author.objects.using('other').all()

Вибір бази даних для save()¶


Використовуйте usingключове слово, щоб Model.save()вказати, до якої бази даних слід зберігати дані.
Наприклад, щоб зберегти об’єкт у legacy_usersбазі даних, слід використовувати це:

>>> my_object.save(using='legacy_users')

Якщо ви не вкажете using, save()метод збережеться у базі даних за замовчуванням, виділеній


маршрутизаторами.

Переміщення об’єкта з однієї бази даних в іншу ¶

Якщо ви зберегли екземпляр в одній базі даних, можливо, спокусливо використовувати


його save(using=...)як спосіб перенести екземпляр до нової бази даних. Однак, якщо ви не вживете
відповідних заходів, це може мати несподівані наслідки.

Розглянемо наступний приклад:

>>> p = Person(name='Fred')

>>> p.save(using='first') # (statement 1)

>>> p.save(using='second') # (statement 2)

У заяві 1 новий Personоб'єкт зберігається у first базі даних. На даний момент pне має первинного


ключа, тому Django видає оператор SQL INSERT. Це створює первинний ключ, і Django призначає цей
первинний ключ p.

Коли збереження відбувається в операторі 2, воно pвже має значення первинного ключа, і Django спробує
використати цей первинний ключ у новій базі даних. Якщо значення первинного ключа не
використовується в second базі даних, то у вас не виникне проблем - об’єкт буде скопійовано в нову базу
даних.

Однак, якщо первинний ключ pвже використовується в secondбазі даних, існуючий об'єкт у secondбазі
даних буде замінений при pзбереженні.

Уникнути цього можна двома способами. По-перше, ви можете очистити первинний ключ


екземпляра. Якщо об'єкт не має первинного ключа, Django буде розглядати його як новий об'єкт,
уникаючи втрати даних у second базі даних:

>>> p = Person(name='Fred')

>>> p.save(using='first')

>>> p.pk = None # Clear the primary key.

>>> p.save(using='second') # Write a completely new object.

Другий варіант - використовувати force_insertопцію, save() щоб переконатися, що Django робить


SQL INSERT:

>>> p = Person(name='Fred')

>>> p.save(using='first')

>>> p.save(using='second', force_insert=True)

Це гарантуватиме, що названа особа Fredматиме однаковий первинний ключ в обох базах даних. Якщо


цей первинний ключ уже використовується під час спроби збереження у secondбазі даних, буде винесено
помилку.
Вибір бази даних для видалення з ¶
За замовчуванням виклик для видалення існуючого об'єкта буде виконуватися в тій самій базі даних, яка
була використана для отримання об'єкта в першу чергу:

>>> u = User.objects.using('legacy_users').get(username='fred')

>>> u.delete() # will delete from the `legacy_users` database

Щоб вказати базу даних, з якої модель буде видалена, передайте в метод usingаргумент ключового
слова Model.delete(). Цей аргумент працює так само, як usingаргумент ключового слова для save().

Наприклад, якщо ви переносите користувача з legacy_users бази даних у new_usersбазу даних, ви


можете використовувати такі команди:

>>> user_obj.save(using='new_users')

>>> user_obj.delete(using='legacy_users')

Використання менеджерів з декількома базами даних ¶


Використовуйте db_manager()метод на менеджерах, щоб надати менеджерам доступ до бази даних, яка
не за замовчуванням.

Наприклад, скажімо, у вас є спеціальний метод менеджера, який стосується бази даних
- User.objects.create_user(). Оскільки create_user() метод менеджера, а не QuerySetметод, ви
не можете зробити User.objects.using('new_users').create_user(). ( create_user()Метод
доступний лише для User.objectsменеджера, а не для QuerySetоб’єктів, отриманих від менеджера.)
Рішення полягає у використанні db_manager(), наприклад:

User.objects.db_manager('new_users').create_user(...)

db_manager() повертає копію менеджера, прив’язану до вказаної вами бази даних.

Використання get_queryset()з кількома базами даних ¶

Якщо ви перевизначаєте get_queryset()свій менеджер, не забудьте або викликати метод на


батьківському (використовуючи super()), або виконати відповідну обробку _dbатрибута на менеджері
(рядок, що містить ім'я бази даних для використання).

Наприклад, якщо ви хочете повернути власний QuerySetклас із get_querysetметоду, ви можете зробити


це:

class MyManager(models.Manager):

def get_queryset(self):

qs = CustomQuerySet(self.model)

if self._db is not None:

qs = qs.using(self._db)

return qs

Розкриття декількох баз даних в інтерфейсі адміністратора


Django ¶
Адміністратор Django не має явної підтримки для декількох баз даних. Якщо ви хочете надати інтерфейс
адміністратора для моделі в базі даних, відмінній від тієї, що вказана вашим ланцюгом маршрутизатора,
вам потрібно буде написати власні ModelAdminкласи, які спрямовуватимуть адміністратора
використовувати певну базу даних для вмісту.

ModelAdmin Об'єкти мають п'ять методів, які вимагають налаштування для підтримки декількох баз
даних:

class MultiDBModelAdmin(admin.ModelAdmin):

# A handy constant for the name of the alternate database.

using = 'other'

def save_model(self, request, obj, form, change):

# Tell Django to save objects to the 'other' database.

obj.save(using=self.using)

def delete_model(self, request, obj):

# Tell Django to delete objects from the 'other' database

obj.delete(using=self.using)

def get_queryset(self, request):

# Tell Django to look for objects on the 'other' database.

return super().get_queryset(request).using(self.using)

def formfield_for_foreignkey(self, db_field, request, **kwargs):

# Tell Django to populate ForeignKey widgets using a query

# on the 'other' database.

return super().formfield_for_foreignkey(db_field, request,


using=self.using, **kwargs)

def formfield_for_manytomany(self, db_field, request, **kwargs):

# Tell Django to populate ManyToMany widgets using a query

# on the 'other' database.

return super().formfield_for_manytomany(db_field, request,


using=self.using, **kwargs)
Запропонована тут реалізація реалізує стратегію декількох баз даних, де всі об'єкти даного типу
зберігаються в певній базі даних (наприклад, усі Userоб'єкти знаходяться в otherбазі даних). Якщо
використання кількох баз даних є більш складним, вам ModelAdminпотрібно буде відобразити цю
стратегію.

InlineModelAdminз об’єктами можна обробляти подібним чином. Вони вимагають трьох індивідуальних


методів:

class MultiDBTabularInline(admin.TabularInline):

using = 'other'

def get_queryset(self, request):

# Tell Django to look for inline objects on the 'other' database.

return super().get_queryset(request).using(self.using)

def formfield_for_foreignkey(self, db_field, request, **kwargs):

# Tell Django to populate ForeignKey widgets using a query

# on the 'other' database.

return super().formfield_for_foreignkey(db_field, request,


using=self.using, **kwargs)

def formfield_for_manytomany(self, db_field, request, **kwargs):

# Tell Django to populate ManyToMany widgets using a query

# on the 'other' database.

return super().formfield_for_manytomany(db_field, request,


using=self.using, **kwargs)

Після того, як ви напишете визначення адміністратора моделі, їх можна зареєструвати в будь-


якому Adminекземплярі:

from django.contrib import admin

# Specialize the multi-db admin objects for use with specific models.

class BookInline(MultiDBTabularInline):

model = Book

class PublisherAdmin(MultiDBModelAdmin):

inlines = [BookInline]
admin.site.register(Author, MultiDBModelAdmin)

admin.site.register(Publisher, PublisherAdmin)

othersite = admin.AdminSite('othersite')

othersite.register(Publisher, MultiDBModelAdmin)

Цей приклад встановлює два сайти адміністратора. На першому місці, як Authorі Publisherоб'єкти


піддаються; Publisher об’єкти мають табличний рядок, що показує книги, видані цим видавцем. На
другому сайті представлені лише видавці, без вкладених текстів.

Використання необроблених курсорів з кількома базами


даних ¶
Якщо ви використовуєте більше однієї бази даних, ви можете скористатися django.db.connectionsдля
отримання з'єднання (і курсору) для конкретної бази даних. django.db.connections- це об’єкт, схожий
на словник, який дозволяє отримати певне з’єднання, використовуючи його псевдонім:

from django.db import connections

with connections['my_db_alias'].cursor() as cursor:

...

Обмеження декількох баз даних ¶


Міжбазові відносини ¶
На даний момент Django не надає жодної підтримки зовнішніх ключів або відносин багато-до-багатьох, що
охоплюють кілька баз даних. Якщо ви використовували маршрутизатор для розділення моделей на різні
бази даних, будь-який зовнішній ключ та відносини багато-до-багатьох, визначені цими моделями,
повинні бути внутрішніми для однієї бази даних.

Це через референційну цілісність. Для того, щоб підтримувати взаємозв'язок між двома об'єктами, Django
повинен знати, що первинний ключ пов'язаного об'єкта є дійсним. Якщо первинний ключ зберігається в
окремій базі даних, неможливо легко оцінити дійсність первинного ключа.

Якщо ви використовуєте Postgres, Oracle або MySQL з InnoDB, це застосовується на рівні цілісності бази
даних - ключові обмеження на рівні бази даних перешкоджають створенню відносин, які неможливо
перевірити.

Однак, якщо ви використовуєте SQLite або MySQL з таблицями MyISAM, немає примусової посилальної
цілісності; як результат, ви можете «підробляти» зовнішні ключі бази даних. Однак ця конфігурація
офіційно не підтримується Django.

Поведінка додаткових програм ¶


Деякі додаткові програми включають моделі, а деякі додатки залежать від інших. Оскільки взаємозв'язки
між базами даних неможливі, це створює деякі обмеження щодо розподілу цих моделей між базами
даних:
 кожен з contenttypes.ContentType, sessions.Sessionі sites.Siteможуть бути збережені в
будь-якій базі даних, з урахуванням відповідного маршрутизатора.
 authмоделі - User, Groupта Permission- пов’язані між собою та пов’язані ContentType, тому
вони повинні зберігатися в тій самій базі даних, що і ContentType.
 adminзалежить від auth, тому його моделі повинні бути в тій самій базі даних, що і auth.
 flatpagesі redirectsзалежать від sites, тому їх моделі повинні бути в тій самій базі даних, що
і sites.

Крім того, деякі об'єкти автоматично створюються відразу після migrateстворення таблиці для
зберігання їх у базі даних:

 за замовчуванням Site,
 a ContentTypeдля кожної моделі (включаючи ті, що не зберігаються в цій базі даних),
 PermissionS для кожної моделі ( в тому числі тих , які не зберігається в цій базі даних).

Для типових установок з кількома базами даних неційно мати ці об’єкти у кількох базах даних. Загальні
установки включають первинні / репліки та підключення до зовнішніх баз даних. Тому рекомендується
написати маршрутизатор баз даних, який дозволяє синхронізувати ці три моделі лише до однієї бази
даних. Використовуйте той самий підхід для додатків contrib та сторонніх розробників, які не потребують
своїх таблиць у декількох базах даних.

Увага

Якщо ви синхронізуєте типи вмісту з кількома базами даних, пам’ятайте, що їх первинні ключі можуть не
збігатися між базами даних. Це може призвести до пошкодження даних або їх втрати.

абличні простори ¶
Загальною парадигмою оптимізації продуктивності в системах баз даних є використання табличних
просторів для організації розміщення диска.

Увага

Django не створює табличні простори для вас. Для отримання детальної інформації про створення та
керування табличними просторами зверніться до документації вашого двигуна баз даних.

Оголошення табличних просторів для таблиць ¶


Простір таблиць можна вказати для таблиці, сформованої моделлю, надавши db_tablespaceопцію
всередині моделі . Цей параметр також впливає на таблиці, автоматично створені для s у
моделі.class MetaManyToManyField

За допомогою цього DEFAULT_TABLESPACEпараметра можна вказати значення за замовчуванням


для db_tablespace. Це корисно для встановлення табличного простору для вбудованих програм Django
та інших програм, кодом яких ви не можете керувати.

Оголошення табличних просторів для індексів ¶


Ви можете передати db_tablespaceваріант Indexконструктору, щоб вказати ім'я табличної області, яка
буде використана для індексу. Для індексів окремих полів ви можете
передати db_tablespaceваріант Fieldконструктору, щоб вказати альтернативну табличну область для
індексу стовпця поля. Якщо стовпець не має індексу, параметр ігнорується.
За допомогою цього DEFAULT_INDEX_TABLESPACEпараметра можна вказати значення за замовчуванням
для db_tablespace.

Якщо db_tablespaceне вказано і ви не встановили DEFAULT_INDEX_TABLESPACE, індекс створюється в


тій самій табличній області, що і таблиці.

Приклад ¶
class TablespaceExample(models.Model):

name = models.CharField(max_length=30, db_index=True,


db_tablespace="indexes")

data = models.CharField(max_length=255, db_index=True)

shortcut = models.CharField(max_length=7)

edges = models.ManyToManyField(to="self", db_tablespace="indexes")

class Meta:

db_tablespace = "tables"

indexes = [models.Index(fields=['shortcut'],
db_tablespace='other_indexes')]

У цьому прикладі таблиці, згенеровані TablespaceExampleмоделлю (тобто таблиця моделі та таблиця


багато-до-багатьох), будуть зберігатися в tables табличному просторі. Індекс поля імені та індекси
таблиці багато-до-багатьох зберігатимуться у indexesтабличній області. dataПоле також буде
генерувати індекс, але не табличная для цього не вказано, так що вона буде зберігатися в моделі
табличного tablesза замовчуванням. Індекс shortcutполя буде зберігатися в other_indexesтабличній
області.

Підтримка баз даних ¶


Простіри таблиць підтримки PostgreSQL та Oracle. SQLite, MariaDB та MySQL цього не роблять.

Коли ви використовуєте бекенд, який не має підтримки для табличних просторів, Django ігнорує всі
параметри, пов'язані з табличною простором.

Оптимізація доступу до бази даних ¶


Рівень бази даних Django надає різні способи допомогти розробникам отримати максимум від своїх баз
даних. Цей документ збирає посилання на відповідну документацію та додає різні підказки, організовані
під кількома заголовками, що окреслюють кроки, необхідні для оптимізації використання бази даних.

Спершу профіль ¶
Як загальна практика програмування, це само собою зрозуміло. Дізнайтеся, які запити ви робите і що вони
вам коштують . Використовуйте, QuerySet.explain()щоб зрозуміти, як конкретні QuerySets
виконуються у вашій базі даних. Ви також можете використовувати зовнішній проект, такий як django-
debug-toolbar , або інструмент, який безпосередньо контролює вашу базу даних.
Пам’ятайте, що, можливо, ви оптимізуєте швидкість, пам’ять або те й інше, залежно від ваших потреб. Іноді
оптимізація для одного буде згубною для іншого, але іноді вони допомагають одне одному. Крім того,
робота, яка виконується процесом бази даних, може не мати такої ж вартості (для вас), як однаковий обсяг
роботи, виконаної у вашому процесі Python. Ви повинні вирішити, якими є ваші пріоритети, де повинен
знаходитись баланс, і профілювати все це за необхідності, оскільки це буде залежати від вашої програми
та сервера.

З усім, що далі, пам’ятайте про профілі після кожної зміни, щоб переконатися, що ці зміни є корисними та
досить великими, оскільки зменшується читабельність вашого коду. Усі наведені нижче пропозиції містять
застереження, що за ваших обставин загальний принцип може не застосовуватися, а може бути і
зворотним.

Використовуйте стандартні методи оптимізації БД ¶


... в тому числі:

 Покажчики . Це пріоритет номер один, після того , як ви визначили з профілювання, які індекси


слід додавати. Використовуйте Meta.indexesабо, Field.db_indexщоб додати їх із
Django. Розглянемо додавання індексів для полів , які ви часто використовуєте
запит filter(), exclude(), order_by()і т.д. в якості індексів може допомогти прискорити
пошук. Зверніть увагу, що визначення найкращих індексів - це складна тема, що залежить від бази
даних, і яка буде залежати від вашого конкретного додатка. Накладні витрати на підтримку
індексу можуть переважати будь-які збільшення швидкості запиту.

 Відповідне використання типів полів.

Будемо вважати, що ви зробили перелічені вище справи. Решта цього документа зосереджена на тому, як
використовувати Django таким чином, щоб ви не робили зайвої роботи. Цей документ також не стосується
інших методів оптимізації, які застосовуються до всіх дорогих операцій, таких як кешування загального
призначення .

Зрозумійте QuerySets ¶
Розуміння QuerySets життєво важливо для отримання хорошої продуктивності за допомогою простого
коду. Зокрема:

Зрозумійте QuerySetоцінку ¶
Щоб уникнути проблем із продуктивністю, важливо розуміти:

 що QuerySets ледачі .
 коли вони оцінюються .
 як дані зберігаються в пам'яті .

Розуміння кешованих атрибутів ¶


Окрім кешування цілого QuerySet, існує кешування результату атрибутів на об'єктах ORM. Загалом,
атрибути, які не можна викликати, будуть кешовані. Наприклад, припустимо приклади моделей Weblog :

>>> entry = Entry.objects.get(id=1)

>>> entry.blog # Blog object is retrieved at this point

>>> entry.blog # cached version, no DB access


Але загалом атрибути, що викликаються, щоразу викликають пошук у БД:

>>> entry = Entry.objects.get(id=1)

>>> entry.authors.all() # query performed

>>> entry.authors.all() # query performed again

Будьте обережні при читанні коду шаблону - система шаблонів не дозволяє використовувати дужки, але
автоматично викликатиме виклики, приховуючи вищезазначену відмінність.

Будьте обережні з власними власними властивостями - це вам вирішувати, коли потрібно кешувати,
наприклад, використовуючи cached_propertyдекоратор.

Використовуйте withтег шаблону ¶
Щоб скористатися поведінкою кешування QuerySet, вам може знадобитися використовувати withтег
шаблону.

Використовуйте iterator()¶
Коли у вас багато об’єктів, поведінка кешування QuerySetможе призвести до використання великої
кількості пам’яті. У цьому випадку iterator()може допомогти.

Використовуйте explain()¶
QuerySet.explain()надає детальну інформацію про те, як база даних виконує запит, включаючи
використовувані індекси та об’єднання. Ці деталі можуть допомогти вам знайти запити, які можна було б
переписати ефективніше, або визначити індекси, які можна додати для підвищення продуктивності.

Працюйте з базою даних у базі даних, а не в Python ¶


Наприклад:

 На найпростішому рівні використовуйте фільтр і виключити для фільтрації в базі даних.


 Використовується для фільтрації на основі інших полів тієї ж моделі.F expressions
 Використовуйте анотацію для агрегування в базі даних .

Якщо цього недостатньо для створення SQL, вам потрібно:

Використовуйте RawSQL¶
Менш портативним, але більш потужним методом є RawSQLвираз, який дозволяє явно додавати до запиту
деякий SQL. Якщо це все ще недостатньо потужне:

Використовувати необроблений SQL ¶


Напишіть свій власний SQL для отримання даних або заповнення
моделей . Використовуйте, django.db.connection.queriesщоб дізнатись, що Django пише для вас, і
почніть звідти.

Отримати окремі об’єкти за допомогою унікального


індексованого стовпця ¶
Є дві причини використовувати стовпець із uniqueабо db_indexпід час використання get()для
отримання окремих об’єктів. По-перше, запит буде швидшим через базовий індекс бази даних. Крім того,
запит може виконуватися набагато повільніше, якщо декілька об'єктів відповідають пошуку; наявність
унікального обмеження колонки гарантує, що цього ніколи не станеться.

Отже, використовуючи приклади моделей Weblog :

>>> entry = Entry.objects.get(id=10)

буде швидше, ніж:

>>> entry = Entry.objects.get(headline="News Item Title")

оскільки idіндексується базою даних і гарантовано буде унікальним.

Виконання наступних дій є досить повільним:

>>> entry = Entry.objects.get(headline__startswith="News")

Перш за все, headlineне індексується, що зробить базову базу даних повільнішою.

По-друге, пошук не гарантує повернення лише одного об’єкта. Якщо запит відповідає кільком об’єктам, він
отримає та перенесе їх усі з бази даних. Цей штраф може бути суттєвим, якщо повернути сотні чи тисячі
записів. Штраф буде посилений, якщо база даних живе на окремому сервері, де накладні витрати на
мережу та затримка також відіграють важливу роль.

Отримайте все одразу, якщо знаєте, що вам це знадобиться ¶


Потрапляння в базу даних кілька разів для різних частин одного "набору" даних, для яких вам
знадобляться всі частини, загалом менш ефективно, ніж отримання всього цього в одному запиті. Це
особливо важливо, якщо у вас є запит, який виконується у циклі, і, отже, може в кінцевому підсумку робити
багато запитів до бази даних, коли потрібен був лише один. Так:

Використання QuerySet.select_related()та prefetch_related()¶
Зрозумійте select_related()і prefetch_related()ретельно, і використовуйте їх:

 у менеджерів та менеджерів за замовчуванням, де це доречно Будьте в курсі, коли ваш менеджер


використовується і не використовується; іноді це складно, тому не робіть припущень.
 у вигляді коду перегляду або інших шарів, можливо,
використовуючи prefetch_related_objects()де потрібно.

Не отримуйте речі, які вам не потрібні ¶


Використання QuerySet.values()та values_list()¶
Коли вам потрібні лише значення dictабо list, а вам не потрібні об'єкти моделі ORM, використовуйте
відповідне використання values(). Вони можуть бути корисними для заміни об'єктів моделі в коді
шаблону - доки наведені вами дикти мають ті самі атрибути, що й ті, що використовуються в шаблоні, ви
добре.

Використання QuerySet.defer()та only()¶
Використовуйте, defer()і only()якщо є стовпці бази даних, ви знаєте, що вам не потрібно (або не
потрібно в більшості випадків), щоб уникнути їх завантаження. Зауважте, що якщо ви все-
таки їх використовуєте, ORM доведеться йти та отримувати їх в окремому запиті, що робить це
песимізацією, якщо ви використовуєте його неналежним чином.

Не будьте занадто агресивними у відкладанні полів без профілювання, оскільки база даних повинна
зчитувати більшість нетекстових, не-VARCHAR даних з диска за один рядок у результатах, навіть якщо в
кінцевому підсумку використовується лише кілька стовпців. Методи defer()and only()є найбільш
корисними, коли ви можете уникнути завантаження великої кількості текстових даних або для полів, для
переробки яких у Python може знадобитися багато обробки. Як завжди, спочатку сформулюйте, а потім
оптимізуйте.

Використовуйте QuerySet.count()¶
... якщо ви хочете лише підрахувати, а не робити len(queryset).

Використовуйте QuerySet.exists()¶
... якщо ви хочете лише з’ясувати, чи існує хоча б один результат, а не .if queryset

Але:

Не зловживайте count()та exists()¶
Якщо вам потрібні інші дані із QuerySet, негайно оцініть їх.

Наприклад, якщо припустити, що модель електронної пошти має subjectатрибут та відношення багато


до багатьох до Користувача, оптимальним є наступний код:

if display_emails:

emails = user.emails.all()

if emails:

print('You have', len(emails), 'emails:')

for email in emails:

print(email.subject)

else:

print('You do not have any emails.')

Це оптимально, оскільки:

1. Оскільки QuerySets ледачі, це не робить запитів до бази даних, якщо display_emailsє False.


2. Зберігання user.emails.all()у emailsзмінній дозволяє повторно використовувати кеш її
результатів.
3. Рядок викликає виклик, що призводить до запуску запиту в базі даних. Якщо результатів не буде,
він повернеться , інакше .if emailsQuerySet.__bool__()user.emails.all()FalseTrue
4. Використання len(emails)викликів QuerySet.__len__(), повторне використання кешу
результатів.
5. forЦикл перебирає вже заповненої кеш.
Загалом цей код робить або один, або нуль запитів до бази даних. Єдиною цілеспрямованою оптимізацією
є використання emailsзмінної. Використання QuerySet.exists()для ifабо QuerySet.count()для
підрахунку призведе до додаткових запитів.

Використання QuerySet.update()та delete()¶
Замість того, щоб отримувати навантаження об’єктів, встановлювати деякі значення та зберігати їх
окремо, використовуйте масовий оператор SQL UPDATE через QuerySet.update () . Аналогічно
робіть масові видалення, де це можливо.

Однак зауважте, що ці методи масового оновлення не можуть викликати save()або delete()методи


окремих екземплярів, а це означає, що будь-яка спеціальна поведінка, яку ви додали для цих методів, не
буде виконана, включаючи будь-що, що керується звичайними сигналами об'єктів бази даних .

Використовуйте значення зовнішнього ключа безпосередньо ¶


Якщо вам потрібне лише значення зовнішнього ключа, використовуйте значення зовнішнього ключа, яке
вже є на об’єкті, який у вас є, замість того, щоб отримати весь пов’язаний об’єкт і взяти його первинний
ключ. тобто робити:

entry.blog_id

замість:

entry.blog.id

Не замовляйте результати, якщо вам все одно ¶


Замовлення не є безкоштовним; кожне поле для замовлення - це операція, яку повинна виконати база
даних. Якщо модель має замовлення за замовчуванням ( Meta.ordering), і воно вам не потрібне, видаліть
його за QuerySetдопомогою виклику order_by()без параметрів.

Додавання індексу до бази даних може допомогти покращити ефективність замовлення.

Використовуйте масові методи ¶


Використовуйте масові методи, щоб зменшити кількість операторів SQL.

Створювати навалом ¶
Створюючи об'єкти, де це можливо, використовуйте bulk_create()метод для зменшення кількості
запитів SQL. Наприклад:

Entry.objects.bulk_create([

Entry(headline='This is a test'),

Entry(headline='This is only a test'),

])

... переважно:

Entry.objects.create(headline='This is a test')

Entry.objects.create(headline='This is only a test')


Зверніть увагу, що їх існує декілька , тому переконайтеся, що це підходить для вашого випадку
використання.caveats to this method

Оновлення масово ¶
Під час оновлення об’єктів, де це можливо, використовуйте bulk_update()метод, щоб зменшити
кількість запитів SQL. Дано список об’єктів або набір запитів:

entries = Entry.objects.bulk_create([

Entry(headline='This is a test'),

Entry(headline='This is only a test'),

])

Наступний приклад:

entries[0].headline = 'This is not a test'

entries[1].headline = 'This is no longer a test'

Entry.objects.bulk_update(entries, ['headline'])

... переважно:

entries[0].headline = 'This is not a test'

entries[0].save()

entries[1].headline = 'This is no longer a test'

entries[1].save()

Зверніть увагу, що їх існує декілька , тому переконайтеся, що це підходить для вашого випадку
використання.caveats to this method

Вставити навалом ¶
Під час вставки об’єктів у ManyToManyFields, використовуйте add()з кількома об’єктами, щоб зменшити
кількість запитів SQL. Наприклад:

my_band.members.add(me, my_friend)

... переважно:

my_band.members.add(me)

my_band.members.add(my_friend)

... де Bandsі Artistsмають стосунки багато-до-багатьох.

Під час вставки різних пар об’єктів у ManyToManyFieldабо коли визначена спеціальна throughтаблиця,
використовуйте bulk_create()метод, щоб зменшити кількість запитів SQL. Наприклад:

PizzaToppingRelationship = Pizza.toppings.through

PizzaToppingRelationship.objects.bulk_create([
PizzaToppingRelationship(pizza=my_pizza, topping=pepperoni),

PizzaToppingRelationship(pizza=your_pizza, topping=pepperoni),

PizzaToppingRelationship(pizza=your_pizza, topping=mushroom),

], ignore_conflicts=True)

... переважно:

my_pizza.toppings.add(pepperoni)

your_pizza.toppings.add(pepperoni, mushroom)

... де Pizzaі Toppingмають стосунки багато-до-багатьох. Зверніть увагу, що їх існує декілька , тому


переконайтеся, що це підходить для вашого випадку використання.caveats to this method

Видалити навалом ¶
Видаляючи об’єкти з ManyToManyFields, використовуйте remove()з кількома об’єктами, щоб зменшити
кількість запитів SQL. Наприклад:

my_band.members.remove(me, my_friend)

... переважно:

my_band.members.remove(me)

my_band.members.remove(my_friend)

... де Bandsі Artistsмають стосунки багато-до-багатьох.

При видаленні різних пар об'єктів з ManyToManyFields, використання delete()на Qвираз з


декількома through екземплярами моделі , щоб зменшити кількість запитів SQL. Наприклад:

from django.db.models import Q

PizzaToppingRelationship = Pizza.toppings.through

PizzaToppingRelationship.objects.filter(

Q(pizza=my_pizza, topping=pepperoni) |

Q(pizza=your_pizza, topping=pepperoni) |

Q(pizza=your_pizza, topping=mushroom)

).delete()

... переважно:

my_pizza.toppings.remove(pepperoni)

your_pizza.toppings.remove(pepperoni, mushroom)

... де Pizzaі Toppingмають стосунки багато-до-багатьох.

Інструментарій бази даних ¶


Щоб допомогти вам зрозуміти та контролювати запити, видані вашим кодом, Django пропонує гачок для
встановлення функцій обгортки навколо виконання запитів до бази даних. Наприклад, обгортки можуть
підраховувати запити, вимірювати тривалість запитів, запитувати журнали або навіть запобігати
виконанню запитів (наприклад, щоб переконатися, що жоден запит не видається під час надання шаблону
із заздалегідь отриманими даними).

Обгортки створені за зразком проміжного програмного забезпечення - це виклики, які беруть інший


виклик як один із своїх аргументів. Вони викликають цього викликається, щоб викликати (можливо,
загорнуту) запит до бази даних, і вони можуть робити те, що хочуть, навколо цього виклику. Однак вони
створюються та встановлюються за кодом користувача, тому їм не потрібна окрема фабрика, як це робить
проміжне програмне забезпечення.

Встановлення обгортки виконується в диспетчері контекстів - отже, обгортки є тимчасовими та


специфічними для певного потоку у вашому коді.

Як згадувалося вище, прикладом обгортки є блокувальник виконання запитів. Це може виглядати так:

def blocker(*args):

raise Exception('No database access allowed here.')

І він буде використовуватися у поданні для блокування запитів із шаблону так:

from django.db import connection

from django.shortcuts import render

def my_view(request):

context = {...} # Code to generate context with all data.

template_name = ...

with connection.execute_wrapper(blocker):

return render(request, template_name, context)

Параметри, що надсилаються до обгортки:

 execute - викликається, який слід викликати з рештою параметрів для виконання запиту.
 sql- а str, запит SQL, який буде надіслано до бази даних.
 params- список / кортеж значень параметрів для команди SQL, або список / кортеж списків /
кортежів, якщо загортається виклик executemany().
 many- boolвказівка, чи є кінцевий викликаний виклик execute()чи executemany()(і
чи paramsпередбачається, що це буде послідовність значень, або послідовність послідовностей
значень).
 context- словник з додатковими даними про контекст виклику. Це включає з'єднання та курсор.

Використовуючи параметри, дещо складніша версія блокувальника може включати ім'я з'єднання в
повідомлення про помилку:

def blocker(execute, sql, params, many, context):

alias = context['connection'].alias
raise Exception("Access to database '{}' blocked here".format(alias))

Для більш повного прикладу реєстратор запитів може виглядати так:

import time

class QueryLogger:

def __init__(self):

self.queries = []

def __call__(self, execute, sql, params, many, context):

current_query = {'sql': sql, 'params': params, 'many': many}

start = time.monotonic()

try:

result = execute(sql, params, many, context)

except Exception as e:

current_query['status'] = 'error'

current_query['exception'] = e

raise

else:

current_query['status'] = 'ok'

return result

finally:

duration = time.monotonic() - start

current_query['duration'] = duration

self.queries.append(current_query)

Для цього потрібно створити об’єкт реєстратора та встановити його як обгортку:

from django.db import connection

ql = QueryLogger()

with connection.execute_wrapper(ql):

do_queries()
# Now we can print the log.

print(ql.queries)

connection.execute_wrapper()¶
execute_wrapper( обгортка ) ¶

Повертає менеджер контексту, який при введенні встановлює обгортку навколо виконання запитів до
бази даних, а після виходу видаляє обгортку. Обгортка встановлюється на об'єкт локального з'єднання
потоку.

wrapperвикликається п'ятьма аргументами. Вона викликається для кожного виконання запиту в рамках


менеджера контексту, з аргументами execute, sql, params, many, і , contextяк описано вище. Очікується
виклик і повернення значення повернення цього дзвінка.execute(sql, params, many, context)

Приклади використання API взаємозв'язку


моделей ¶
 Відносини багато-до-багатьох
 Відносини багато до одного
 Індивідуальні стосунки

Відносини багато-до-багатьох ¶
Щоб визначити відносини багато-до-багатьох, використовуйте ManyToManyField.

У цьому прикладі a Articleможе бути опубліковано в декількох Publication об'єктах, а


a Publicationмає кілька Articleоб'єктів:

from django.db import models

class Publication(models.Model):

title = models.CharField(max_length=30)

class Meta:

ordering = ['title']

def __str__(self):

return self.title

class Article(models.Model):
headline = models.CharField(max_length=100)

publications = models.ManyToManyField(Publication)

class Meta:

ordering = ['headline']

def __str__(self):

return self.headline

Далі наведено приклади операцій, які можна виконати за допомогою засобів Python API.

Створіть кілька Publications:

>>> p1 = Publication(title='The Python Journal')

>>> p1.save()

>>> p2 = Publication(title='Science News')

>>> p2.save()

>>> p3 = Publication(title='Science Weekly')

>>> p3.save()

Створіть Article:

>>> a1 = Article(headline='Django lets you build Web apps easily')

Ви не можете пов’язати його з, Publicationпоки його не збережено:

>>> a1.publications.add(p1)

Traceback (most recent call last):

...

ValueError: "<Article: Django lets you build Web apps easily>" needs to have a
value for field "id" before this many-to-many relationship can be used.

Збережи це!

>>> a1.save()

Пов’язати Articleз Publication:

>>> a1.publications.add(p1)

Створіть інший Articleі встановіть його для відображення в Publications:

>>> a2 = Article(headline='NASA uses Python')

>>> a2.save()
>>> a2.publications.add(p1, p2)

>>> a2.publications.add(p3)

Додавання другого разу - це нормально, це не дублює відношення:

>>> a2.publications.add(p3)

Додавання об’єкта неправильного типу викликає TypeError:

>>> a2.publications.add(a1)

Traceback (most recent call last):

...

TypeError: 'Publication' instance expected

Створити і додати Publicationдо Articleв одну стадію з використанням create():

>>> new_publication = a2.publications.create(title='Highlights for Children')

Articleоб'єкти мають доступ до пов'язаних з ними Publicationоб'єктів:

>>> a1.publications.all()

<QuerySet [<Publication: The Python Journal>]>

>>> a2.publications.all()

<QuerySet [<Publication: Highlights for Children>, <Publication: Science News>,


<Publication: Science Weekly>, <Publication: The Python Journal>]>

Publicationоб'єкти мають доступ до пов'язаних з ними Articleоб'єктів:

>>> p2.article_set.all()

<QuerySet [<Article: NASA uses Python>]>

>>> p1.article_set.all()

<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA
uses Python>]>

>>> Publication.objects.get(id=4).article_set.all()

<QuerySet [<Article: NASA uses Python>]>

Відносини багато-до-багатьох можна запитувати, використовуючи пошук між відносинами :

>>> Article.objects.filter(publications__id=1)

<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA
uses Python>]>

>>> Article.objects.filter(publications__pk=1)

<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA
uses Python>]>
>>> Article.objects.filter(publications=1)

<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA
uses Python>]>

>>> Article.objects.filter(publications=p1)

<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA
uses Python>]>

>>> Article.objects.filter(publications__title__startswith="Science")

<QuerySet [<Article: NASA uses Python>, <Article: NASA uses Python>]>

>>>
Article.objects.filter(publications__title__startswith="Science").distinct()

<QuerySet [<Article: NASA uses Python>]>

У count()функції відносини , distinct()а також:

>>> Article.objects.filter(publications__title__startswith="Science").count()

>>>
Article.objects.filter(publications__title__startswith="Science").distinct().co
unt()

>>> Article.objects.filter(publications__in=[1,2]).distinct()

<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA
uses Python>]>

>>> Article.objects.filter(publications__in=[p1,p2]).distinct()

<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA
uses Python>]>

Підтримуються зворотні запити m2m (тобто, починаючи з таблиці, яка не має a ManyToManyField):

>>> Publication.objects.filter(id=1)

<QuerySet [<Publication: The Python Journal>]>

>>> Publication.objects.filter(pk=1)

<QuerySet [<Publication: The Python Journal>]>


>>> Publication.objects.filter(article__headline__startswith="NASA")

<QuerySet [<Publication: Highlights for Children>, <Publication: Science News>,


<Publication: Science Weekly>, <Publication: The Python Journal>]>

>>> Publication.objects.filter(article__id=1)

<QuerySet [<Publication: The Python Journal>]>

>>> Publication.objects.filter(article__pk=1)

<QuerySet [<Publication: The Python Journal>]>

>>> Publication.objects.filter(article=1)

<QuerySet [<Publication: The Python Journal>]>

>>> Publication.objects.filter(article=a1)

<QuerySet [<Publication: The Python Journal>]>

>>> Publication.objects.filter(article__in=[1,2]).distinct()

<QuerySet [<Publication: Highlights for Children>, <Publication: Science News>,


<Publication: Science Weekly>, <Publication: The Python Journal>]>

>>> Publication.objects.filter(article__in=[a1,a2]).distinct()

<QuerySet [<Publication: Highlights for Children>, <Publication: Science News>,


<Publication: Science Weekly>, <Publication: The Python Journal>]>

Виключення пов'язаного елемента працює, як і слід було очікувати (хоча задіяний SQL трохи складний):

>>> Article.objects.exclude(publications=p2)

<QuerySet [<Article: Django lets you build Web apps easily>]>

Якщо ми видалимо a Publication, Articlesвін не зможе отримати до нього доступ:

>>> p1.delete()

>>> Publication.objects.all()

<QuerySet [<Publication: Highlights for Children>, <Publication: Science News>,


<Publication: Science Weekly>]>

>>> a1 = Article.objects.get(pk=1)

>>> a1.publications.all()

<QuerySet []>

Якщо ми видалимо файл Article, Publicationsвін не зможе отримати до нього доступ:

>>> a2.delete()

>>> Article.objects.all()
<QuerySet [<Article: Django lets you build Web apps easily>]>

>>> p2.article_set.all()

<QuerySet []>

Додавання через `` інший '' кінець м2:

>>> a4 = Article(headline='NASA finds intelligent life on Earth')

>>> a4.save()

>>> p2.article_set.add(a4)

>>> p2.article_set.all()

<QuerySet [<Article: NASA finds intelligent life on Earth>]>

>>> a4.publications.all()

<QuerySet [<Publication: Science News>]>

Додавання через інший кінець за допомогою ключових слів:

>>> new_article = p2.article_set.create(headline='Oxygen-free diet works


wonders')

>>> p2.article_set.all()

<QuerySet [<Article: NASA finds intelligent life on Earth>, <Article: Oxygen-


free diet works wonders>]>

>>> a5 = p2.article_set.all()[1]

>>> a5.publications.all()

<QuerySet [<Publication: Science News>]>

Видалення Publicationз Article:

>>> a4.publications.remove(p2)

>>> p2.article_set.all()

<QuerySet [<Article: Oxygen-free diet works wonders>]>

>>> a4.publications.all()

<QuerySet []>

І з іншого кінця:

>>> p2.article_set.remove(a5)

>>> p2.article_set.all()

<QuerySet []>

>>> a5.publications.all()

<QuerySet []>
Набори відносин можна встановити:

>>> a4.publications.all()

<QuerySet [<Publication: Science News>]>

>>> a4.publications.set([p3])

>>> a4.publications.all()

<QuerySet [<Publication: Science Weekly>]>

Набори відносин можна очистити:

>>> p2.article_set.clear()

>>> p2.article_set.all()

<QuerySet []>

І ви можете зрозуміти з іншого кінця:

>>> p2.article_set.add(a4, a5)

>>> p2.article_set.all()

<QuerySet [<Article: NASA finds intelligent life on Earth>, <Article: Oxygen-


free diet works wonders>]>

>>> a4.publications.all()

<QuerySet [<Publication: Science News>, <Publication: Science Weekly>]>

>>> a4.publications.clear()

>>> a4.publications.all()

<QuerySet []>

>>> p2.article_set.all()

<QuerySet [<Article: Oxygen-free diet works wonders>]>

Відтворіть Articleі Publicationми видалили:

>>> p1 = Publication(title='The Python Journal')

>>> p1.save()

>>> a2 = Article(headline='NASA uses Python')

>>> a2.save()

>>> a2.publications.add(p1, p2, p3)

Масове видалення деяких Publications- посилання на видалені публікації повинні йти:

>>> Publication.objects.filter(title__startswith='Science').delete()

>>> Publication.objects.all()
<QuerySet [<Publication: Highlights for Children>, <Publication: The Python
Journal>]>

>>> Article.objects.all()

<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA
finds intelligent life on Earth>, <Article: NASA uses Python>, <Article:
Oxygen-free diet works wonders>]>

>>> a2.publications.all()

<QuerySet [<Publication: The Python Journal>]>

Масове видалення деяких статей - посилання на видалені об’єкти повинні йти:

>>> q = Article.objects.filter(headline__startswith='Django')

>>> print(q)

<QuerySet [<Article: Django lets you build Web apps easily>]>

>>> q.delete()

Після delete(), QuerySetкеш-пам’ять потрібно очистити, а об’єкти, на які посилаються, слід зникнути:

>>> print(q)

<QuerySet []>

>>> p1.article_set.all()

<QuerySet [<Article: NASA uses Python>]>

Відносини багато до одного ¶


Щоб визначити відносини багато-до-одного, використовуйте ForeignKey:

from django.db import models

class Reporter(models.Model):

first_name = models.CharField(max_length=30)

last_name = models.CharField(max_length=30)

email = models.EmailField()

def __str__(self):

return "%s %s" % (self.first_name, self.last_name)

class Article(models.Model):

headline = models.CharField(max_length=100)
pub_date = models.DateField()

reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)

def __str__(self):

return self.headline

class Meta:

ordering = ['headline']

Далі наведено приклади операцій, які можна виконати за допомогою засобів Python API.

Створіть кілька репортерів:

>>> r = Reporter(first_name='John', last_name='Smith',


email='john@example.com')

>>> r.save()

>>> r2 = Reporter(first_name='Paul', last_name='Jones',


email='paul@example.com')

>>> r2.save()

Створити статтю:

>>> from datetime import date

>>> a = Article(id=None, headline="This is a test", pub_date=date(2005, 7, 27),


reporter=r)

>>> a.save()

>>> a.reporter.id

>>> a.reporter

<Reporter: John Smith>

Зверніть увагу, що ви повинні зберегти об’єкт, перш ніж його можна буде присвоїти відношенню
зовнішнього ключа. Наприклад, створення Articleз
незбереженими Reporter підвищеннями ValueError:

>>> r3 = Reporter(first_name='John', last_name='Smith',


email='john@example.com')
>>> Article.objects.create(headline="This is a test", pub_date=date(2005, 7,
27), reporter=r3)

Traceback (most recent call last):

...

ValueError: save() prohibited to prevent data loss due to unsaved related


object 'reporter'.

Об'єкти статей мають доступ до пов'язаних з ними об'єктів Reporter:

>>> r = a.reporter

Створіть статтю за допомогою об’єкта Reporter:

>>> new_article = r.article_set.create(headline="John's second story",


pub_date=date(2005, 7, 29))

>>> new_article

<Article: John's second story>

>>> new_article.reporter

<Reporter: John Smith>

>>> new_article.reporter.id

Створіть нову статтю:

>>> new_article2 = Article.objects.create(headline="Paul's story",


pub_date=date(2006, 1, 17), reporter=r)

>>> new_article2.reporter

<Reporter: John Smith>

>>> new_article2.reporter.id

>>> r.article_set.all()

<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article:


This is a test>]>

Додайте ту саму статтю до іншого набору статей - перевірте, чи вона рухається:

>>> r2.article_set.add(new_article2)

>>> new_article2.reporter.id

>>> new_article2.reporter

<Reporter: Paul Jones>


Додавання об’єкта неправильного типу викликає TypeError:

>>> r.article_set.add(r2)

Traceback (most recent call last):

...

TypeError: 'Article' instance expected, got <Reporter: Paul Jones>

>>> r.article_set.all()

<QuerySet [<Article: John's second story>, <Article: This is a test>]>

>>> r2.article_set.all()

<QuerySet [<Article: Paul's story>]>

>>> r.article_set.count()

>>> r2.article_set.count()

Зверніть увагу, що в останньому прикладі стаття перейшла від Івана до Павла.

Пов’язані менеджери також підтримують пошук на місцях. API автоматично відстежує взаємозв'язки,


наскільки вам потрібно. Використовуйте подвійне підкреслення, щоб розділити стосунки. Це працює
стільки рівнів, скільки завгодно. Немає обмежень. Наприклад:

>>> r.article_set.filter(headline__startswith='This')

<QuerySet [<Article: This is a test>]>

# Find all Articles for any Reporter whose first name is "John".

>>> Article.objects.filter(reporter__first_name='John')

<QuerySet [<Article: John's second story>, <Article: This is a test>]>

Точний збіг мається на увазі тут:

>>> Article.objects.filter(reporter__first_name='John')

<QuerySet [<Article: John's second story>, <Article: This is a test>]>

Двічі звертайтеся до відповідного поля. Це означає умову AND у реченні WHERE:

>>> Article.objects.filter(reporter__first_name='John',
reporter__last_name='Smith')
<QuerySet [<Article: John's second story>, <Article: This is a test>]>

Для відповідного пошуку ви можете вказати значення первинного ключа або передати пов'язаний об'єкт
явно:

>>> Article.objects.filter(reporter__pk=1)

<QuerySet [<Article: John's second story>, <Article: This is a test>]>

>>> Article.objects.filter(reporter=1)

<QuerySet [<Article: John's second story>, <Article: This is a test>]>

>>> Article.objects.filter(reporter=r)

<QuerySet [<Article: John's second story>, <Article: This is a test>]>

>>> Article.objects.filter(reporter__in=[1,2]).distinct()

<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article:


This is a test>]>

>>> Article.objects.filter(reporter__in=[r,r2]).distinct()

<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article:


This is a test>]>

Ви також можете використовувати набір запитів замість буквального списку примірників:

>>>
Article.objects.filter(reporter__in=Reporter.objects.filter(first_name='John'))
.distinct()

<QuerySet [<Article: John's second story>, <Article: This is a test>]>

Запит у зворотному напрямку:

>>> Reporter.objects.filter(article__pk=1)

<QuerySet [<Reporter: John Smith>]>

>>> Reporter.objects.filter(article=1)

<QuerySet [<Reporter: John Smith>]>

>>> Reporter.objects.filter(article=a)

<QuerySet [<Reporter: John Smith>]>

>>> Reporter.objects.filter(article__headline__startswith='This')

<QuerySet [<Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John


Smith>]>

>>> Reporter.objects.filter(article__headline__startswith='This').distinct()

<QuerySet [<Reporter: John Smith>]>


Підрахунок у зворотному напрямку працює у поєднанні з distinct ():

>>> Reporter.objects.filter(article__headline__startswith='This').count()

>>>
Reporter.objects.filter(article__headline__startswith='This').distinct().count(
)

Запити можна кружляти по колу:

>>> Reporter.objects.filter(article__reporter__first_name__startswith='John')

<QuerySet [<Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John


Smith>, <Reporter: John Smith>]>

>>>
Reporter.objects.filter(article__reporter__first_name__startswith='John').disti
nct()

<QuerySet [<Reporter: John Smith>]>

>>> Reporter.objects.filter(article__reporter=r).distinct()

<QuerySet [<Reporter: John Smith>]>

Якщо ви видалите репортера, їх статті будуть видалені (припускаючи, що ForeignKey було визначено
з django.db.models.ForeignKey.on_deleteвстановленим значенням CASCADE, яке є типовим):

>>> Article.objects.all()

<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article:


This is a test>]>

>>> Reporter.objects.order_by('first_name')

<QuerySet [<Reporter: John Smith>, <Reporter: Paul Jones>]>

>>> r2.delete()

>>> Article.objects.all()

<QuerySet [<Article: John's second story>, <Article: This is a test>]>

>>> Reporter.objects.order_by('first_name')

<QuerySet [<Reporter: John Smith>]>

Ви можете видалити за допомогою JOIN у запиті:

>>> Reporter.objects.filter(article__headline__startswith='This').delete()

>>> Reporter.objects.all()

<QuerySet []>

>>> Article.objects.all()
<QuerySet []>

Індивідуальні стосунки ¶
Для визначення взаємозв'язку "один на один" використовуйте OneToOneField.

У цьому прикладі Placeнеобов’язково може бути Restaurant:

from django.db import models

class Place(models.Model):

name = models.CharField(max_length=50)

address = models.CharField(max_length=80)

def __str__(self):

return "%s the place" % self.name

class Restaurant(models.Model):

place = models.OneToOneField(

Place,

on_delete=models.CASCADE,

primary_key=True,

serves_hot_dogs = models.BooleanField(default=False)

serves_pizza = models.BooleanField(default=False)

def __str__(self):

return "%s the restaurant" % self.place.name

class Waiter(models.Model):

restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)

name = models.CharField(max_length=50)

def __str__(self):
return "%s the waiter at %s" % (self.name, self.restaurant)

Далі наведено приклади операцій, які можна виконати за допомогою засобів Python API.

Створіть пару місць:

>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')

>>> p1.save()

>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')

>>> p2.save()

Створи ресторан. Передайте “батьківський” об’єкт як первинний ключ цього об’єкта:

>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)

>>> r.save()

Ресторан може отримати доступ до свого місця:

>>> r.place

<Place: Demon Dogs the place>

Місце може отримати доступ до свого ресторану, якщо такий є:

>>> p1.restaurant

<Restaurant: Demon Dogs the restaurant>

p2 не має відповідного ресторану:

>>> from django.core.exceptions import ObjectDoesNotExist

>>> try:

>>> p2.restaurant

>>> except ObjectDoesNotExist:

>>> print("There is no restaurant here.")

There is no restaurant here.

Ви також можете використовувати, hasattrщоб уникнути необхідності вилучення винятків:

>>> hasattr(p2, 'restaurant')

False

Встановіть місце, використовуючи позначення призначення. Оскільки місце є основним ключем


ресторану, збереження створить новий ресторан:

>>> r.place = p2

>>> r.save()

>>> p2.restaurant
<Restaurant: Ace Hardware the restaurant>

>>> r.place

<Place: Ace Hardware the place>

Поверніть місце знову, використовуючи призначення в зворотному напрямку:

>>> p1.restaurant = r

>>> p1.restaurant

<Restaurant: Demon Dogs the restaurant>

Зауважте, що ви повинні зберегти об’єкт, перш ніж його можна буде присвоїти відношенню «один на
один». Наприклад, створення Restaurantз незбереженими Place підвищеннями ValueError:

>>> p3 = Place(name='Demon Dogs', address='944 W. Fullerton')

>>> Restaurant.objects.create(place=p3, serves_hot_dogs=True,


serves_pizza=False)

Traceback (most recent call last):

...

ValueError: save() prohibited to prevent data loss due to unsaved related


object 'place'.

Restaurant.objects.all () повертає ресторани, а не місця. Зверніть увагу, що є два ресторани - Ace Hardware,


ресторан був створений під час виклику r.place = p2:

>>> Restaurant.objects.all()

<QuerySet [<Restaurant: Demon Dogs the restaurant>, <Restaurant: Ace Hardware


the restaurant>]>

Place.objects.all () повертає всі місця, незалежно від того, чи є в них ресторани:

>>> Place.objects.order_by('name')

<QuerySet [<Place: Ace Hardware the place>, <Place: Demon Dogs the place>]>

Ви можете запитувати моделі, використовуючи пошук між різними зв’язками :

>>> Restaurant.objects.get(place=p1)

<Restaurant: Demon Dogs the restaurant>

>>> Restaurant.objects.get(place__pk=1)

<Restaurant: Demon Dogs the restaurant>

>>> Restaurant.objects.filter(place__name__startswith="Demon")

<QuerySet [<Restaurant: Demon Dogs the restaurant>]>

>>> Restaurant.objects.exclude(place__address__contains="Ashland")

<QuerySet [<Restaurant: Demon Dogs the restaurant>]>


Це також працює в зворотному порядку:

>>> Place.objects.get(pk=1)

<Place: Demon Dogs the place>

>>> Place.objects.get(restaurant__place=p1)

<Place: Demon Dogs the place>

>>> Place.objects.get(restaurant=r)

<Place: Demon Dogs the place>

>>> Place.objects.get(restaurant__place__name__startswith="Demon")

<Place: Demon Dogs the place>

Додайте офіціанта в ресторан:

>>> w = r.waiter_set.create(name='Joe')

>>> w

<Waiter: Joe the waiter at Demon Dogs the restaurant>

Запитайте офіціантів:

>>> Waiter.objects.filter(restaurant__place=p1)

<QuerySet [<Waiter: Joe the waiter at Demon Dogs the restaurant>]>

>>> Waiter.objects.filter(restaurant__place__name__startswith="Demon")

<QuerySet [<Waiter: Joe the waiter at Demon Dogs the restaurant>]>

You might also like