Работа с запросами¶
После того как вы создали модели данных, Django автоматически предоставляет вам API-интерфейс для базы данных, который позволяет создавать, извлекать, обновлять и удалять объекты. Этот документ объясняет, как использовать этот API. Обратитесь к справочнику по модели данных для получения полной информации обо всех различных параметрах поиска модели.
В этом руководстве (и в справочнике) мы будем ссылаться на следующие модели, которые составляют приложение Weblog:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField()
number_of_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __str__(self):
return self.headline
Создание объектов¶
Для представления данных таблицы базы данных в объектах Python Django использует интуитивно понятную систему: класс модели представляет таблицу базы данных, а экземпляр этого класса представляет конкретную запись в таблице базы данных.
Чтобы создать объект, создайте его экземпляр с помощью аргументов ключевого слова для класса модели, а затем вызовите save()
, чтобы сохранить его в базе данных.
Предполагая, что модели находятся в файле mysite/blog/models.py
:
>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
За кулисами выполняется SQL-оператор INSERT
. Django не обращается в базу данных, пока вы не вызовете явно save()
.
Метод save()
не имеет возвращаемого значения.
Сохранение изменений в объектах¶
Чтобы сохранить изменения в объекте, который уже находится в базе данных, используйте 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
, используя класс менеджер 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». Второе - это подмножество первого, с дополнительными критериями, исключающими записи, чье 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()
дополнительно ограничивают набор объектов, но для многозначных отношений они применяются к любому объекту, связанному с первичной моделью, не обязательно к тем объектам, которые были выбранный ранее вызовом 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.
Добавлена поддержка .bitxor()
.
Использование сокращения 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
Удаление объектов¶
The delete method, conveniently, is named
delete()
. This method immediately deletes the
object and returns the number of objects deleted and a dictionary with
the number of deletions per object type. Example:
>>> e.delete()
(1, {'weblog.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
. Используя наш пример блога:
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
blog.pk = None
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
:
django_blog.pk = None
django_blog.id = None
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.save()
entry.authors.set(old_authors)
Для OneToOneField
вы должны продублировать связанный объект и назначить его полю нового объекта, чтобы избежать нарушения однозначного уникального ограничения. Например, предполагая, что entry
уже продублировано, как указано выше:
detail = EntryDetail.objects.all()[0]
detail.pk = None
detail.entry = entry
detail.save()
Обновление нескольких объектов одновременно¶
Иногда вы хотите установить для поля определенное значение для всех объектов в QuerySet
. Вы можете сделать это с помощью метода update()
. Например:
# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
Используя этот метод, вы можете устанавливать только нереляционные поля и поля ForeignKey
. Чтобы обновить нереляционное поле, укажите новое значение как константу. Чтобы обновить ForeignKey
поля, установите новое значение в качестве нового экземпляра модели, на который вы хотите указать. Например:
>>> b = Blog.objects.get(pk=1)
# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)
Метод update()
применяется мгновенно и возвращает количество строк, соответствующих запросу (которое может быть не равно количеству обновленных строк, если некоторые строки уже имеют новое значение). Единственное ограничение на обновляемый QuerySet
состоит в том, что он может получить доступ только к одной таблице базы данных: главной таблице модели. Вы можете фильтровать на основе связанных полей, но вы можете обновить только столбцы в основной таблице модели. Пример:
>>> b = Blog.objects.get(pk=1)
# Update all the headlines belonging to this Blog.
>>> Entry.objects.filter(blog=b).update(headline='Everything is the same')
Помните, что метод update()
преобразуется непосредственно в оператор SQL. Это массовая операция для прямых обновлений. Он не запускает какие-либо методы save()
на ваших моделях или сигналы pre_save
или post_save
(которые являются следствием вызова save()
), и не обновляет поля auto_now
. Если вы хотите сохранить каждый элемент в QuerySet
и убедиться, что метод save()
вызывается в каждом экземпляре Вам не нужны никакие специальные функции для этого. Просто зациклите их и вызовите save()
:
for item in my_queryset:
item.save()
Вызовы обновлений также могут использовать выражения F
для обновления одного поля на основе значения другого поля в модели. Это особенно полезно для увеличения счетчиков на основе их текущего значения. Например, чтобы увеличить количество пингбэков для каждой записи в блоге:
>>> Entry.objects.all().update(number_of_pingbacks=F('number_of_pingbacks') + 1)
Однако, в отличие от объектов F()
в filter и exclude, вы не можете вводить объединения при использовании объектов F()
в обновлении - вы можете ссылаться только на поля, локальные для обновляемой модели. Если вы попытаетесь ввести объединение с объектом F()
, будет вызвано FieldError
:
# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))
Откат к сырому SQL¶
Если вам понадобится написать SQL-запрос, который слишком сложен для обработки базы данных Django, вы можете вернуться к написанию SQL вручную. У Django есть несколько вариантов написания необработанных SQL-запросов; смотрите Выполнение необработанных SQL-запросов.
Наконец, важно отметить, что уровень базы данных Django - это просто интерфейс к вашей базе данных. Вы можете получить доступ к вашей базе данных с помощью других инструментов, языков программирования или фреймворков баз данных; нет ничего специфичного для Django в вашей базе данных.