QuerySets: Работа с запросами


После того как вы создали модели данных, Django автоматически предоставляет вам API-интерфейс для базы данных, который позволяет создавать, извлекать, обновлять и удалять объекты. Этот документ объясняет, как использовать этот API. Обратитесь к справочнику по модели данных для получения полной информации обо всех различных параметрах поиска модели.

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

from django.db import models

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

def __str__(self):
return self.name

class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()

def __str__(self):
return self.name

class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField()
number_of_pingbacks = models.IntegerField()
rating = models.IntegerField()

def __str__(self):
return self.headline
Создание объектов
Для представления данных таблицы базы данных в объектах Python Django использует интуитивно понятную систему: класс модели представляет таблицу базы данных, а экземпляр этого класса представляет конкретную запись в таблице базы данных.

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

Предполагая, что модели находятся в файле mysite/blog/models.py, вот пример:

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
Выполняется 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)
Джанго сообщит, если вы попытаетесь назначить или добавить объект неправильного типа.

Получение объектов
Чтобы получить объекты из вашей базы данных, создайте QuerySet через Manager в своем классе модели.

QuerySet представляет коллекцию объектов из вашей базы данных. Может иметь ноль, один или несколько фильтров. Фильтры сужают результаты запроса на основе заданных параметров. В терминах SQL QuerySet приравнивается к оператору SELECT, а фильтр является ограничивающим предложением, таким как WHERE или LIMIT .

Вы получаете QuerySet, используя класс менеджер :class:` ~ django.db.models.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() `` возвращает :class:`~django.db.models.query.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». Второе - это подмножество первого, с дополнительными критериями, исключающими записи, чье pub_date сегодня или в будущем. Третий - это подмножество первого, с дополнительными критериями, которые выбирают только те записи, чьи pub_date находятся сегодня или в будущем. Начальный 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 - он не вызывает запрос. Исключением является использование параметра «step» синтаксиса фрагмента 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. Они указываются в качестве аргументов ключевых слов для методов filter(), exclude() и get() класса QuerySet.

Базовые аргументы поиска по ключевым словам имеют вид field__lookuptype=value. (Это двойное подчеркивание). Например:

>>> Entry.objects.filter(pub_date__lte='2006-01-01')
переводит (примерно) в следующий SQL:

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
Как это возможно

Python имеет возможность определять функции, которые принимают произвольные аргументы имя-значение, имена и значения которых оцениваются во время выполнения. Для получения дополнительной информации см. Keyword Arguments в официальном руководстве по Python.

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

>>> Entry.objects.filter(blog_id=4)
Если вы передадите неверный аргумент ключевого слова, функция поиска вызовет TypeError.

API базы данных поддерживает около двух десятков типов поиска; полная документация может быть найдена в справочнике поиска по полям. Чтобы дать вам представление о том, что доступно, вот некоторые из наиболее часто используемых поисков:

exact
«Точное» совпадение. Например:

>>> Entry.objects.get(headline__exact="Cat bites dog")
Будет генерировать SQL по этим строкам:

SELECT ... WHERE headline = 'Cat bites dog';
Если вы не предоставляете тип поиска - то есть, если аргумент ключевого слова не содержит двойного подчеркивания, - тип поиска считается точным.

Например, следующие два оператора эквивалентны:

>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
Это сделано для удобства, потому что точный поиск является распространенным случаем.

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')
Этот охват может быть настолько глубоким, насколько вы захотите.

Он работает и в обратном направлении. Хотя может быть настроен как, по умолчанию вы ссылаетесь на «обратную» связь в поиске, используя имя модели в нижнем регистре.

В этом примере извлекаются все объекты Blog, у которых есть хотя бы один Entry, чей headline содержит 'Lennon':

>>> Blog.objects.filter(entry__headline__contains='Lennon')
Если вы фильтруете по нескольким отношениям и одна из промежуточных моделей не имеет значения, которое удовлетворяет условию фильтра, Django будет обрабатывать его как пустой (все значения NULL), но допустимый объект там. Все это означает, что никакой ошибки не возникнет. Например, в этом фильтре:

Blog.objects.filter(entry__authors__name='Lennon')
(если была связанная Author модель), если бы не было author, связанного с записью, она будет обрабатываться так, как если бы к ней также не было прикреплено name, вместо того, чтобы выдавать ошибку из-за пропавшего author. Обычно это именно то, что вы хотите, чтобы произошло. Единственный случай, когда это может сбить с толку, это если вы используете isnull. Таким образом:

Blog.objects.filter(entry__authors__name__isnull=True)
вернет объекты Blog, которые имеют пустое name в author, а также те, которые имеют пустое author в entry. Если вы не хотите получать эти последние объекты, вы можете написать:

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)
Охватывающие многозначные отношения
Когда вы фильтруете объект на основе ManyToManyField или обратного ForeignKey, вас могут заинтересовать два вида фильтров. Рассмотрим отношение Blog/Entry (отношение Blog к Entry является отношением один ко многим). Мы могли бы заинтересоваться поиском блогов, в которых есть запись с заголовком «Lennon» и которая была опубликована в 2008 году. Или мы могли бы также найти блоги, в которых есть запись с заголовком «Lennon» в заголовке как запись, которая была опубликована в 2008 году. Поскольку существует несколько записей, связанных с одним Blog, оба эти запроса возможны и имеют смысл в некоторых ситуациях.

Ситуация такого же типа возникает с ManyToManyField. Например, если Entry имеет ManyToManyField, называемый tags, мы можем захотеть найти записи, связанные с тегами «music» и «band» или нам может потребоваться запись, которая содержит тег с именем «music» и статусом «public».

Чтобы справиться с обеими этими ситуациями, Django имеет согласованный способ обработки filter(). Все внутри одного вызова filter() применяется одновременно, чтобы отфильтровать элементы, соответствующие всем этим требованиям. Последовательные вызовы filter() дополнительно ограничивают набор объектов, но для многозначных отношений они применяются к любому объекту, связанному с первичной моделью, не обязательно к тем объектам, которые были выбранный ранее вызовом :meta:`~django.db.models.query.QuerySet.filter`.

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

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». Первый запрос не вернет ни одного блога, но второй запрос вернет этот блог.

Во втором примере первый фильтр ограничивает набор запросов всеми теми блогами, которые связаны с записями с «Lennon» в заголовке. Второй фильтр ограничивает набор блогов далее теми, которые также связаны с записями, опубликованными в 2008 году. Записи, выбранные вторым фильтром, могут совпадать или не совпадать с записями в первом фильтре. Мы фильтруем элементы Blog с каждым оператором фильтра, а не элементы Entry.

Примечание

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

Выражения могут ссылаться на преобразования
New in Django 3.2.
Django поддерживает использование преобразований в выражениях.

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

>>> Entry.objects.filter(pub_date__year=F('mod_date__year'))
Чтобы найти самый ранний год публикации записи, мы можем выполнить запрос:

>>> Entry.objects.aggregate(first_published_year=Min('pub_date__year'))
В этом примере определяется значение записи с наивысшим рейтингом и общее количество комментариев ко всем записям за каждый год:

>>> Entry.objects.values('pub_date__year').annotate(
... top_rating=Subquery(
... Entry.objects.filter(
... pub_date__year=OuterRef('pub_date__year'),
... ).order_by('-rating').values('rating')[:1]
... ),
... total_comments=Sum('number_of_comments'),
... )
Использование сокращения pk
Для удобства Django предоставляет сокращение для поиска pk, который обозначает «первичный ключ».

В примере модели Blog первичным ключом является поле id, поэтому эти три оператора эквивалентны:

>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # pk implies id__exact
Использование pk не ограничивается __exact запросами - любое условие запроса может быть объединено с pk для выполнения запроса по первичному ключу модели:

# Get blogs entries with id 1, 4 and 7
>>> Blog.objects.filter(pk__in=[1,4,7])

# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)
Поиск pk также работает через объединения. Например, эти три утверждения эквивалентны:

>>> Entry.objects.filter(blog__id__exact=3) # Explicit form
>>> Entry.objects.filter(blog__id=3) # __exact is implied
>>> Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
Экранирующие знаки процента и подчеркивания в выражениях LIKE
Поиск в полях, эквивалентный операторам LIKE (iexact, contains, icontains, startswith, istartswith, endswith and iendswith) автоматически уберет два специальных символа, используемых в операторах LIKE - знак процента и подчеркивание. (В операторе LIKE знак процента означает подстановочный знак из нескольких символов, а подчеркивание означает подстановочный знак из одного символа.)

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

>>> Entry.objects.filter(headline__contains='%')
Джанго заботится об экранировании для вас; результирующий 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()])
Это означает, что один и тот же запрос к базе данных будет выполнен дважды, что фактически удваивает нагрузку на вашу базу данных. Кроме того, существует вероятность того, что два списка могут не включать в себя одни и те же записи базы данных, потому что «запись» может быть добавлена или удалена за доли секунды между двумя запросами.

Чтобы избежать этой проблемы, просто сохраните QuerySet и повторно используйте его:

>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
Когда QuerySet не кэшируется
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__() возвращает только часть всего набора запросов.

Запросы к JSONField
Реализация поисков отличается в JSONField, главным образом из-за существования ключевых преобразований. Для демонстрации мы будем использовать следующий пример модели:

from django.db import models

class Dog(models.Model):
name = models.CharField(max_length=200)
data = models.JSONField(null=True)

def __str__(self):
return self.name
Хранение и запрос для `` None``
Как и в случае других полей, сохранение None в качестве значения поля сохранит его как SQL NULL. Хотя это не рекомендуется, можно хранить скаляр JSON null вместо SQL NULL, используя Value('null').

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

Это относится только к None как значению верхнего уровня поля. Если None находится внутри list или dict, он всегда будет интерпретироваться как JSON null.

При запросе значение None всегда будет интерпретироваться как JSON null. Чтобы запросить SQL NULL, используйте isnull:

>>> Dog.objects.create(name='Max', data=None) # SQL NULL.
<Dog: Max>
>>> Dog.objects.create(name='Archie', data=Value('null')) # JSON null.
<Dog: Archie>
>>> Dog.objects.filter(data=None)
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data=Value('null'))
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data__isnull=True)
<QuerySet [<Dog: Max>]>
>>> Dog.objects.filter(data__isnull=False)
<QuerySet [<Dog: Archie>]>
Если вы не уверены, что хотите работать со значениями SQL NULL, рассмотрите возможность установки null=False и обеспечения подходящего значения по умолчанию для пустых значений, например default=dict.

Примечание

Хранение JSON скаляра null не нарушает null=False.

Преобразование ключа, индекса и пути
Для запроса на основе заданного ключа словаря используйте этот ключ в качестве имени поиска:

>>> Dog.objects.create(name='Rufus', data={
... 'breed': 'labrador',
... 'owner': {
... 'name': 'Bob',
... 'other_pets': [{
... 'name': 'Fishy',
... }],
... },
... })
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': None})
<Dog: Meg>
>>> Dog.objects.filter(data__breed='collie')
<QuerySet [<Dog: Meg>]>
Несколько ключей могут быть объединены в цепочку для формирования пути поиска:

>>> Dog.objects.filter(data__owner__name='Bob')
<QuerySet [<Dog: Rufus>]>
Если ключ является целым числом, он будет интерпретирован как преобразование индекса в массиве:

>>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy')
<QuerySet [<Dog: Rufus>]>
Если ключ, который вы хотите запросить, конфликтует с именем другого поиска, используйте вместо этого поиск contains.

Чтобы запросить отсутствующие ключи, используйте поиск isnull:

>>> Dog.objects.create(name='Shep', data={'breed': 'collie'})
<Dog: Shep>
>>> Dog.objects.filter(data__owner__isnull=True)
<QuerySet [<Dog: Shep>]>
Примечание

В приведенных выше примерах поиска неявно используется поиск exact. Преобразования ключей, индекса и пути также могут быть связаны с: icontains, endswith, iendswith, iexact, regex, iregex, startswith, istartswith, lt, lte, gt, и gte, также как с Сдерживание и ключевые поиски.

Примечание

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

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

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

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

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

Пользователи Oracle

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

Пользователи PostgreSQL

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

Сдерживание и ключевые поиски
contains
Поиск contains переопределяется в JSONField. Возвращаемые объекты - это те, в которых заданный dict пар ключ-значение содержится в верхнем уровне поля. Например:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.create(name='Fred', data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contains={'owner': 'Bob'})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={'breed': 'collie'})
<QuerySet [<Dog: Meg>]>
Oracle и SQLite

contains не поддерживается в Oracle и SQLite.

contained_by
Это обратное значение contains - возвращаемые объекты будут теми, где пары ключ-значение на объекте являются подмножеством пар в переданном значении. Например:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.create(name='Fred', data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contained_by={'breed': 'collie', 'owner': 'Bob'})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={'breed': 'collie'})
<QuerySet [<Dog: Fred>]>
Oracle и SQLite

contains_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() и т.д. - объединяются вместе «AND». Если вам нужно выполнить более сложные запросы (например, запросы с помощью операторов OR), вы можете использовать Q объекты.

Q объект (django.db.models.Q) - это объект, используемый для инкапсуляции набора ключевых аргументов. Эти ключевые аргументы указаны также как и в «Поиск по полям» выше.

Например, этот объект Q инкапсулирует один запрос LIKE:

from django.db.models import Q
Q(question__startswith='What')
Объекты Q можно комбинировать с помощью операторов & и |. Когда оператор используется с двумя объектами Q, он создает новый объект Q.

Например, этот оператор выдает один объект Q, который представляет «OR» двух запросов "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 для функции поиска, аргументы будут объединены «AND». Например:

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), соединяются «AND». Однако, если предоставляется объект 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))
)
… не будет верным.

См.также

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