Spec-Zone.ru › Django 6.0

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

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

В этом руководстве (и в справочнике) мы будем обращаться к следующим моделям, составляющим приложение для ведения блога:

from datetime import date

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(default=date.today)
    authors = models.ManyToManyField(Author)
    number_of_comments = models.IntegerField(default=0)
    number_of_pingbacks = models.IntegerField(default=0)
    rating = models.IntegerField(default=5)

    def __str__(self):
        return self.headline

Создание объектов

Для представления данных таблиц базы данных в виде объектов Python Django использует интуитивно понятную систему: класс модели представляет таблицу базы данных, а экземпляр этого класса — конкретную запись в таблице базы данных.

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

Предположим, что модели находятся в файле models.py внутри приложения Django blog. Вот пример:

>>> from blog.models import Blog
>>> b = Blog(name="Beatles Blog", tagline="All the latest Beatles news.")
>>> b.save()

В фоновом режиме выполняется инструкция SQL INSERT. Django обращается к базе данных только после явного вызова save().

Метод save() не возвращает значение.

См. также

Метод save() принимает ряд дополнительных параметров, которые здесь не описаны. Полное описание см. в документации по save().

Чтобы создать и сохранить объект за один шаг, используйте метод create().

Сохранение изменений объектов

Чтобы сохранить изменения объекта, который уже находится в базе данных, используйте save().

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

>>> b5.name = "New name"
>>> b5.save()

В фоновом режиме выполняется инструкция SQL UPDATE. 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 класса модели.

QuerySet представляет собой набор объектов из базы данных. Он может содержать ноль, один или несколько фильтров. Фильтры сужают результаты запроса на основе заданных параметров. В терминах SQL QuerySet соответствует оператору SELECT, а фильтр — ограничивающему условию, например WHERE или 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."

Примечание

Доступ к Manager возможен только через классы моделей, а не через экземпляры моделей. Это позволяет разделить операции «на уровне таблицы» и операции «на уровне записи».

Manager — основной источник наборов запросов для модели. Например, Blog.objects.all() возвращает 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)

Цепочки фильтров

Результат уточнения QuerySet сам является 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 со всеми записями, заголовок которых начинается с «What» и которые были опубликованы в период с 30 января 2005 года по сегодняшний день.

Отфильтрованные QuerySets уникальны

Каждый раз при уточнении 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())

Эти три набора запросов независимы. Первый — базовый QuerySet, содержащий все записи с заголовком, начинающимся с «What». Второй — подмножество первого с дополнительным условием, исключающим записи, у которых pub_date приходится на сегодняшний день или будущее. Третий — подмножество первого с дополнительным условием, выбирающим только записи, у которых pub_date приходится на сегодняшний день или будущее. Исходный QuerySet (q1) не меняется в процессе уточнения.

QuerySets выполняются отложенно

Объекты QuerySet выполняются отложенно: создание 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)). Как правило, результаты QuerySet не извлекаются из базы данных, пока вы их не «запросите». Когда это происходит, QuerySet вычисляется посредством обращения к базе данных. Подробнее о том, когда именно происходит вычисление, см. в разделе Когда вычисляются QuerySet.

Получение одного объекта с помощью get()

filter() всегда возвращает 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(). Но это далеко не всё; полный список различных методов QuerySet см. в справочнике по API 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]) не поддерживается.

Как правило, срез QuerySet возвращает новый QuerySet — запрос при этом не вычисляется. Исключение составляет использование параметра «шага» в синтаксисе срезов Python. Например, следующий код действительно выполнит запрос, чтобы вернуть список каждого второго объекта из первых 10:

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

Дальнейшая фильтрация или сортировка срезанного набора запросов запрещена из-за неоднозначности возможного поведения.

Чтобы получить один объект, а не список (например, SELECT foo FROM bar LIMIT 1), используйте индекс вместо среза. Например, этот код возвращает первую запись Entry в базе данных после сортировки записей по заголовку в алфавитном порядке:

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

Указанное в поисковом запросе поле должно быть именем поля модели. Однако есть одно исключение: для ForeignKey можно указать имя поля с суффиксом _id. В этом случае параметр значения должен содержать необработанное значение первичного ключа связанной модели. Например:

>>> 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 Django обработал бы её так, будто к ней не прикреплён и 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) фильтрация по нескольким атрибутам ставит вопрос: должны ли все атрибуты совпадать у одного и того же связанного объекта? Например, нам могут быть нужны блоги, у которых есть запись за 2008 год с «Lennon» в заголовке, или блоги, у которых есть любая запись за 2008 год, а также какая-нибудь более новая или старая запись с «Lennon» в заголовке.

Чтобы выбрать все блоги, содержащие хотя бы одну запись за 2008 год с «Lennon» в заголовке (то есть одну и ту же запись, удовлетворяющую обоим условиям), напишем:

Blog.objects.filter(entry__headline__contains="Lennon", entry__pub_date__year=2008)

Если же нужен более свободный запрос, выбирающий блоги, где есть просто какая-нибудь запись с «Lennon» в заголовке и какая-нибудь запись за 2008 год, напишем:

Blog.objects.filter(entry__headline__contains="Lennon").filter(
    entry__pub_date__year=2008
)

Предположим, существует только один блог, в котором есть записи с «Lennon» в заголовке и записи за 2008 год, но ни одна из записей за 2008 год не содержит «Lennon». Первый запрос не вернёт ни одного блога, а второй вернёт этот единственный блог. (Это объясняется тем, что записи, выбранные вторым фильтром, могут отличаться от записей, выбранных первым фильтром. Каждый оператор фильтрации применяется к элементам Blog, а не к элементам Entry.) Иными словами, если каждое условие должно совпадать у одного и того же связанного объекта, все условия нужно включить в один вызов filter().

Примечание

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

>>> from datetime import date
>>> beatles = Blog.objects.create(name="Beatles Blog")
>>> pop = Blog.objects.create(name="Pop Music Blog")
>>> Entry.objects.create(
...     blog=beatles,
...     headline="New Lennon Biography",
...     pub_date=date(2008, 6, 1),
... )
<Entry: New Lennon Biography>
>>> Entry.objects.create(
...     blog=beatles,
...     headline="New Lennon Biography in Paperback",
...     pub_date=date(2009, 6, 1),
... )
<Entry: New Lennon Biography in Paperback>
>>> Entry.objects.create(
...     blog=pop,
...     headline="Best Albums of 2008",
...     pub_date=date(2008, 12, 15),
... )
<Entry: Best Albums of 2008>
>>> Entry.objects.create(
...     blog=pop,
...     headline="Lennon Would Have Loved Hip Hop",
...     pub_date=date(2020, 4, 1),
... )
<Entry: Lennon Would Have Loved Hip Hop>
>>> Blog.objects.filter(
...     entry__headline__contains="Lennon",
...     entry__pub_date__year=2008,
... )
<QuerySet [<Blog: Beatles Blog>]>
>>> Blog.objects.filter(
...     entry__headline__contains="Lennon",
... ).filter(
...     entry__pub_date__year=2008,
... )
<QuerySet [<Blog: Beatles Blog>, <Blog: Beatles Blog>, <Blog: Pop Music Blog]>

Примечание

Описанное выше поведение filter() для запросов с переходом по связям со множеством значений не реализовано аналогичным образом для exclude(). Вместо этого условия в одном вызове exclude() не обязательно относятся к одному и тому же элементу.

Например, следующий запрос исключит блоги, содержащие и записи с «Lennon» в заголовке, и записи, опубликованные в 2008 году:

Blog.objects.exclude(
    entry__headline__contains="Lennon",
    entry__pub_date__year=2008,
)

Однако, в отличие от поведения filter(), этот запрос не будет ограничивать выборку блогами, записи которых удовлетворяют обоим условиям одновременно. Чтобы это сделать, то есть выбрать все блоги, не содержащие записи с «Lennon», опубликованные в 2008 году, нужно выполнить два запроса:

Blog.objects.exclude(
    entry__in=Entry.objects.filter(
        headline__contains="Lennon",
        pub_date__year=2008,
    ),
)

Фильтры могут ссылаться на поля модели

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

Django предоставляет F expressions, позволяющие выполнять такие сравнения. Экземпляры F() служат ссылками на поля модели внутри запроса. Эти ссылки можно использовать в фильтрах запросов для сравнения значений двух разных полей одного экземпляра модели.

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

Например, чтобы найти все объекты Entry, опубликованные в том же году, когда они были изменены в последний раз:

>>> from django.db.models import F
>>> Entry.objects.filter(pub_date__year=F("mod_date__year"))

Чтобы найти самый ранний год публикации записи, можно выполнить запрос:

>>> from django.db.models import Min
>>> Entry.objects.aggregate(first_published_year=Min("pub_date__year"))

Этот пример находит значение наибольшего рейтинга записи и общее количество комментариев ко всем записям для каждого года:

>>> from django.db.models import OuterRef, Subquery, Sum
>>> 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

Поиски по полям, соответствующие операторам SQL LIKE (iexact, contains, icontains, startswith, istartswith, endswith и iendswith), автоматически экранируют два специальных символа, используемых в операторах LIKE: знак процента и подчёркивание. (В операторе LIKE знак процента обозначает подстановочный знак для нескольких символов, а подчёркивание — для одного символа.)

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

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

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

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

То же самое относится к подчёркиваниям. Знаки процента и подчёркивания обрабатываются автоматически.

Кэширование и QuerySet

Каждый QuerySet содержит кэш для минимизации обращений к базе данных. Понимание его работы позволит писать максимально эффективный код.

У только что созданного QuerySet кэш пуст. При первой оценке QuerySet — и, следовательно, выполнении запроса к базе данных — Django сохраняет результаты запроса в кэше QuerySet и возвращает явно запрошенные результаты (например, следующий элемент, если выполняется итерация по QuerySet). При последующих оценках QuerySet используются кэшированные результаты.

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

>>> 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])  # Reuse the cache from the evaluation.

Когда QuerySet не кэшируются

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

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

>>> 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__() возвращает только срез всего набора запросов.

Асинхронные запросы

Если вы пишете асинхронные представления или код, вы не можете использовать ORM для запросов точно так, как описано выше: из асинхронного кода нельзя вызывать блокирующий синхронный код — это заблокирует цикл событий (или, что вероятнее, Django заметит это и вызовет SynchronousOnlyOperation, чтобы этого не произошло).

К счастью, многие запросы можно выполнять с помощью асинхронных API запросов Django. У каждого метода, который может блокировать выполнение, например get() или delete(), есть асинхронный вариант (aget() или adelete()), а при переборе результатов можно использовать асинхронную итерацию (async for).

Итерация по запросам

При обычном способе перебора запроса с помощью for за кулисами выполняется блокирующий запрос к базе данных, поскольку Django загружает результаты во время итерации. Чтобы исправить это, используйте async for:

async for entry in Authors.objects.filter(name__startswith="A"):
    ...

Обратите внимание, что также нельзя выполнять другие действия, которые могут перебирать набор запросов, например оборачивать его в list(), чтобы принудительно выполнить его оценку (при необходимости можно использовать async for в выражении-генераторе).

Поскольку методы QuerySet, такие как filter() и exclude(), фактически не выполняют запрос, а лишь подготавливают набор запросов к выполнению при итерации, их можно свободно использовать в асинхронном коде. В следующем разделе описано, какие методы можно использовать таким образом, а для каких есть асинхронные варианты.

Методы QuerySet и менеджеров

Некоторые методы менеджеров и наборов запросов, например get() и first(), принудительно выполняют набор запросов и блокируют выполнение. Другие, например filter() и exclude(), не принуждают к выполнению, поэтому их безопасно вызывать из асинхронного кода. Но как понять, чем они отличаются?

Можно поискать версию метода с префиксом a (например, у нас есть aget(), но нет afilter()), однако есть более логичный способ — посмотреть, к какому типу относится метод, в справочнике по QuerySet.

В нём методы QuerySet сгруппированы в два раздела:

  • Методы, возвращающие новые наборы запросов: это неблокирующие методы, у которых нет асинхронных вариантов. Их можно использовать в любой ситуации, однако перед использованием прочитайте примечания о defer() и only().
  • Методы, не возвращающие наборы запросов: это блокирующие методы, у которых есть асинхронные варианты. Асинхронное имя каждого метода указано в его документации, но обычно к имени добавляется префикс a.

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

user = await User.objects.filter(username=my_input).afirst()

filter() возвращает набор запросов, поэтому его можно продолжать объединять с другими вызовами в асинхронной среде, тогда как first() выполняет запрос и возвращает экземпляр модели. Поэтому мы заменяем его на afirst() и добавляем await в начало всего выражения, чтобы вызвать его безопасным для асинхронного кода способом.

Примечание

Если забыть добавить часть await, можно увидеть ошибки вроде «у объекта coroutine object нет атрибута x» или строки «<coroutine …>» вместо экземпляров модели. Если такое произошло, значит, где-то отсутствует await, преобразующий эту сопрограмму в реальное значение.

Транзакции

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

Если вам нужна транзакция, рекомендуем поместить код ORM в отдельную синхронную функцию и вызвать её с помощью sync_to_async — подробнее см. раздел Асинхронная поддержка.

Запросы к 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(None, JSONField()).

Какое бы значение ни было сохранено, при получении из базы данных представление скалярного значения JSON null в Python совпадает с представлением 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(None, JSONField()))  # JSON null.
<Dog: Archie>
>>> Dog.objects.filter(data=None)
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data=Value(None, JSONField()))
<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>]>

Отрицательное целое число нельзя использовать непосредственно в ключевом аргументе фильтра, но его можно передать в запрос с помощью распаковки словаря:

>>> Dog.objects.filter(**{"data__owner__other_pets__-1__name": "Fishy"})
<QuerySet [<Dog: Rufus>]>

MySQL, MariaDB и Oracle

Отрицательные индексы массивов JSON не поддерживаются.

Изменено в Django 6.0:

Добавлена поддержка отрицательных индексов массивов JSON в SQLite.

Если ключ, по которому вы хотите выполнить запрос, совпадает с именем другого поиска, используйте вместо него поиск 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, lt, lte, gt и gte, а также с поисками по содержимому и ключам.

Выражения KT()

class KT(lookup)

Представляет текстовое значение преобразования ключа, индекса или пути для JSONField. Для объединения преобразований ключей словаря и индексов в lookup можно использовать нотацию с двойным подчёркиванием.

Например:

>>> from django.db.models.fields.json import KT
>>> Dog.objects.create(
...     name="Shep",
...     data={
...         "owner": {"name": "Bob"},
...         "breed": ["collie", "lhasa apso"],
...     },
... )
<Dog: Shep>
>>> Dog.objects.annotate(
...     first_breed=KT("data__breed__1"), owner_name=KT("data__owner__name")
... ).filter(first_breed__startswith="lhasa", owner_name="Bob")
<QuerySet [<Dog: Shep>]>

Примечание

Из-за особенностей работы запросов по путям ключей методы exclude() и filter() не гарантируют получение исчерпывающих наборов. Чтобы включить объекты, у которых отсутствует путь, добавьте поиск isnull.

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

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

Пользователям MariaDB и Oracle

При использовании order_by() для преобразований ключей, индексов или путей сортировка объектов выполняется по строковому представлению значений. Это связано с тем, что MariaDB и Oracle Database не предоставляют функцию преобразования значений JSON в соответствующие значения SQL.

Пользователям Oracle

В Oracle Database при использовании None в качестве значения поиска в запросе exclude() будут возвращены объекты, у которых по заданному пути значение не равно null, включая объекты, у которых отсутствует этот путь. В других серверных базах данных запрос вернёт объекты, у которых путь существует, а значение не равно null.

Пользователям PostgreSQL

В PostgreSQL, если используется только один ключ или индекс, применяется оператор SQL ->. Если используется несколько операторов, применяется оператор #>.

Пользователям SQLite

В SQLite строковые значения "true", "false" и "null" всегда интерпретируются соответственно как True, False и JSON null.

Поиски по содержимому и ключам

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.create(
...     name="Merry", data={"breed": "pekingese", "tricks": ["fetch", "dance"]}
... )
>>> Dog.objects.filter(data__contains={"owner": "Bob"})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={"breed": "collie"})
<QuerySet [<Dog: Meg>]>
>>> Dog.objects.filter(data__contains={"tricks": ["dance"]})
<QuerySet [<Dog: Merry>]>

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.create(
...     name="Merry", data={"breed": "pekingese", "tricks": ["fetch", "dance"]}
... )
>>> 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>]>
>>> Dog.objects.filter(
...     data__contained_by={"breed": "pekingese", "tricks": ["dance", "fetch", "hug"]}
... )
<QuerySet [<Dog: Merry>, <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.

Q object (django.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)),
)

… будет некорректным.

См. также

Примеры поисков с оператором ИЛИ в модульных тестах 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, {'blog.Entry': 1})

Можно также удалять объекты группами. У каждого QuerySet есть метод delete(), который удаляет все объекты из этого QuerySet.

Например, этот код удаляет все объекты Entry с годом pub_date, равным 2005:

>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

Помните, что эта операция по возможности выполняется непосредственно средствами SQL, поэтому методы delete() отдельных экземпляров объектов не обязательно будут вызываться в процессе. Если вы определили в классе модели собственный метод delete() и хотите гарантировать его вызов, необходимо удалять экземпляры этой модели «вручную» (например, перебирая QuerySet и вызывая delete() для каждого объекта отдельно), а не использовать групповой метод delete() объекта 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)

Для OneToOneField необходимо дублировать связанный объект и присвоить его полю нового объекта, чтобы не нарушить ограничение уникальности связи «один к одному». Например, если entry уже дублирован, как показано выше:

detail = EntryDetail.objects.all()[0]
detail.pk = None
detail._state.adding = True
detail.entry = entry
detail.save()

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

>>> blog = Blog.objects.defer("name")[0]
>>> blog.pk = None
>>> blog._state.adding = True
>>> blog.save()
Traceback (most recent call last):
    ...
AttributeError: Cannot retrieve deferred field 'name' from an unsaved model.
>>> blog.name = "Another Blog"
>>> blog.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.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. Если требуется сохранить каждый элемент в QuerySet и убедиться, что для каждого экземпляра вызывается метод save(), для этого не нужна специальная функция. Переберите объекты и вызовите save():

for item in my_queryset:
    item.save()

Вызовы update также могут использовать F expressions, чтобы обновить одно поле на основе значения другого поля модели. Это особенно полезно для увеличения счётчиков на основе их текущих значений. Например, чтобы увеличить число pingback для каждой записи блога:

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

Однако, в отличие от использования объектов F() в условиях filter и exclude, при использовании объектов F() в update нельзя добавлять соединения — можно ссылаться только на поля самой обновляемой модели. При попытке добавить соединение с помощью объекта F() будет вызвано исключение FieldError:

# This will raise a FieldError
>>> Entry.objects.update(headline=F("blog__name"))

Связанные объекты

Когда вы задаёте в модели связь (например, 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, приведённые в начале этой страницы.

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

Прямое направление

Если у модели есть 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.

Обратный доступ к связям

Если у модели есть ForeignKey, экземпляры модели с внешним ключом получат доступ к Manager, возвращающему все экземпляры первой модели. По умолчанию этот Manager называется FOO_set, где FOO — имя исходной модели в нижнем регистре. Этот Manager возвращает экземпляры QuerySet, которые можно фильтровать и обрабатывать, как описано выше в разделе «Получение объектов».

Пример:

>>> 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 ``QuerySet`` instances.
>>> 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()

Взаимодействие с предварительной загрузкой

При вызове prefetch_related() для обратной связи используется менеджер по умолчанию. Чтобы предварительно загрузить связанные объекты с помощью пользовательского обратного менеджера, используйте Prefetch(). Например:

from django.db.models import Prefetch

prefetch_manager = Prefetch("entry_set", queryset=Entry.entries.all())
Blog.objects.prefetch_related(prefetch_manager)

Дополнительные методы для работы со связанными объектами

Помимо методов 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. В приведённом выше примере, если бы поле ManyToManyField в 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 считают, что это нарушает принцип DRY (Don’t Repeat Yourself — «не повторяйся»), поэтому в Django достаточно определить связь только с одной стороны.

Но как это возможно, если класс модели не знает, с какими другими классами моделей он связан, пока эти классы не загружены?

Ответ кроется в app registry. При запуске Django импортирует каждое приложение, указанное в INSTALLED_APPS, а затем модуль models внутри каждого приложения. При создании нового класса модели Django добавляет обратные связи во все связанные модели. Если связанные модели ещё не импортированы, Django отслеживает эти связи и добавляет их, когда связанные модели будут импортированы.

Поэтому особенно важно, чтобы все используемые вами модели были определены в приложениях, перечисленных в INSTALLED_APPS. В противном случае обратные связи могут работать неправильно.

Запросы к связанным объектам

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

Например, если у вас есть объект Blog 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.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/topics/db/queries/

Spec-Zone.ru

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