Spec-Zone.ru › Django 5.2

Создание запросов

После создания ваших моделей данных, 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()

Это выполняет INSERT SQL-запрос за кулисами. Django не обращается к базе данных до тех пор, пока вы явно не вызовете save().

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

См. также

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

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

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

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

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

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

Это выполняет UPDATE SQL-запрос за кулисами. 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 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."

Примечание

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

Manager является основным источником QuerySets для модели. Например, 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 года и текущей датой.

Отдельные QuerySet

Каждый раз, когда вы уточняете 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, содержащий все записи, у которых заголовок начинается со слова «What». Второй — подмножество первого, с дополнительным критерием, исключающим записи, дата которых совпадает с текущей или будущей. Третий — подмножество первого, с дополнительным критерием, выбирающим только записи, дата которых совпадает с текущей или будущей. Исходный QuerySet (q1) не затрагивается процессом уточнения.

QuerySet — ленивые

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)). В общем случае результаты 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(), когда вам нужно искать объекты в базе данных. Однако это далеко не все; см. Справочник по API QuerySet для получения полного списка различных QuerySet методов.

Ограничение QuerySet

Используйте подмножество синтаксиса срезов массивов 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, то это будет обработано как отсутствие и 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"),
... )

Сокращение поиска по первичному ключу

Для удобства Django предоставляет сокращение поиска по первичному ключу.

В примере 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)

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

>>> 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 повторно используют кэшированные результаты.

Помните об этом поведении кэширования, так как оно может привести к ошибкам, если вы неправильно используете 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])  # Reuse the cache from the evaluation.

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

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

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

>>> queryset = Entry.objects.all()
>>> print(queryset[5])  # Queries the database
>>> print(queryset[5])  # Queries the database again

Однако, если весь queryset уже был оценен, кэш будет проверен:

>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset]  # Queries the database
>>> print(queryset[5])  # Uses cache
>>> print(queryset[5])  # Uses cache

Вот несколько примеров других действий, которые приведут к оценке всего queryset и, следовательно, к заполнению кэша:

>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)

Примечание

Простое выведение queryset не заполнит кэш. Это связано с тем, что вызов __repr__() возвращает только часть всего queryset.

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

Если вы пишете асинхронные представления или код, вы не можете использовать 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"):
    ...

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

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

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

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

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

Там вы найдете методы QuerySets, сгруппированные в две секции:

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

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

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

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

Примечание

Если вы забудете добавить await часть, вы можете увидеть ошибки, такие как «объект корутины не имеет атрибута x» или строки «<корутина …>» вместо ваших экземпляров модели. Если вы когда-нибудь увидите их, значит, где-то отсутствует 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()).

Какой бы из значений ни был сохранён, при извлечении из базы данных, представление 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(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>]>

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

Объект 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()

Обновление нескольких объектов одновременно

Иногда вам нужно установить значение поля для всех объектов в 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() в фильтрах и исключениях, при использовании объектов F() в обновлении нельзя вводить соединения — вы можете ссылаться только на поля, локальные для обновляемой модели. Если вы попытаетесь ввести соединение с объектом 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 возвращает 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()

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

При вызове 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/5.2/topics/db/queries/

Spec-Zone.ru

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