Создание запросов
После создания ваших моделей данных, 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)
n_comments = models.IntegerField()
n_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()
Это выполняет 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 в вашем классе модели.
Объект 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 оценивается с помощью доступа к базе данных. Более подробная информация о том, когда происходит оценка, находится в Когда QuerySets оцениваются.
Получение одной записи с 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. В этом случае ожидается, что параметр value будет содержать исходное значение первичного ключа модели foreign.
>>> 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". -
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, Django будет считать, что не было и name объекта, вместо того чтобы возбуждать ошибку из-за отсутствующей author записи. Обычно это именно то, что нужно. Единственный случай, когда это может быть запутанным, — использование isnull.
Blog.objects.filter(entry__authors__name__isnull=True)
вернёт Blog объекты, у которых пустой name в author и также те, у которых пустой author в entry. Если вы не хотите получать такие объекты, вы можете написать:
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)
Прослеживание многозначных связей
При фильтрации объекта по ManyToManyField или обратной ForeignKey, есть два типа фильтров, которые могут вас заинтересовать. Рассмотрим связь Blog/Entry (Blog к Entry — это отношение один-ко-многим). Мы можем быть заинтересованы в поиске блогов, имеющих запись с заголовком, содержащим «Леннон», и опубликованную в 2008 году. Или мы можем найти блоги, имеющие запись со строкой «Леннон» в заголовке, а также запись, опубликованную в 2008 году. Поскольку с одним Blog связаны несколько записей, оба этих запроса возможны и осмысленны в некоторых ситуациях.
Такая же ситуация возникает с ManyToManyField. Например, если у Entry есть ManyToManyField с именем tags, мы можем искать записи, связанные с тегами «музыка» и «группы», или запись с тегом «музыка» и статусом «публично».
Для обработки обеих ситуаций Django имеет согласованный способ обработки вызовов filter(). Всё внутри одного вызова filter() применяется одновременно для фильтрации элементов, удовлетворяющих всем требованиям. Последовательные вызовы filter() дополнительно ограничивают набор объектов, но для многозначных связей они применяются ко всем объектам, связанным с основной моделью, а не обязательно к тем, которые были отобраны предыдущим вызовом filter().
Это может показаться немного запутанным, поэтому, надеюсь, пример прояснит ситуацию. Для выбора всех блогов, содержащих записи с заголовком, содержащим «Леннон», и опубликованные в 2008 году (одна и та же запись удовлетворяет обоим условиям), мы напишем:
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
Для выбора всех блогов, содержащих запись с заголовком, содержащим «Леннон», а также запись, опубликованную в 2008 году, мы напишем:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
Предположим, что только один блог содержал обе записи с «Леннон» и записи из 2008 года, но ни одна из записей из 2008 года не содержала «Леннон». Первый запрос не вернёт никаких блогов, но второй запрос вернёт этот один блог.
Во втором примере первый фильтр ограничивает набор результатов всеми блогами, связанными с записями, содержащими «Леннон» в заголовке. Второй фильтр дополнительно ограничивает набор результатов блогами, которые также связаны с записями, опубликованными в 2008 году. Записи, отобранные вторым фильтром, могут совпадать или не совпадать с записями первого фильтра. Мы фильтруем Blog элементы с каждой инструкцией фильтрации, а не Entry элементы.
Примечание
Поведение filter() для запросов, которые охватывают многозначные отношения, как описано выше, не реализовано аналогичным образом для exclude(). Вместо этого условия в одном вызове exclude() не обязательно ссылаются на один и тот же элемент.
Например, следующий запрос исключит блоги, содержащие как записи с «Леннон» в заголовке, так и записи, опубликованные в 2008 году:
Blog.objects.exclude(
entry__headline__contains='Lennon',
entry__pub_date__year=2008,
)
Однако, в отличие от поведения при использовании filter(), это не будет ограничивать блоги на основе записей, удовлетворяющих обоим условиям. Для этого, то есть для выбора всех блогов, не содержащих записей с «Леннон» в заголовке, опубликованных в 2008 году, вам нужно выполнить два запроса:
Blog.objects.exclude(
entry__in=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
Фильтры могут ссылаться на поля модели
В примерах выше мы создавали фильтры, сравнивающие значение поля модели с константой. Но что делать, если нужно сравнить значение поля модели с другим полем той же модели?
Django предоставляет F expressions для выполнения таких сравнений. Экземпляры F() выступают в качестве ссылки на поле модели в запросе. Эти ссылки могут быть использованы в фильтрах запросов для сравнения значений двух разных полей одной и той же модели.
Например, чтобы найти список всех записей блога, у которых количество комментариев больше, чем пингбеков, мы создаем объект F() для ссылки на количество пингбеков и используем этот объект F() в запросе:
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django поддерживает использование арифметических операций сложения, вычитания, умножения, деления, остатка от деления и возведения в степень с объектами F() как с константами, так и с другими объектами F(). Чтобы найти все записи блога, у которых количество комментариев более чем в два раза превышает количество пингбеков, мы изменяем запрос:
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
Чтобы найти все записи, где рейтинг записи меньше суммы количества пингбеков и комментариев, мы выполним запрос:
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_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(), .bitrightshift(), и .bitleftshift(). Например:
>>> F('somefield').bitand(16)
Была добавлена поддержка .bitrightshift() и .bitleftshift().
Сокращение поиска по первичному ключу
Для удобства Django предоставляет сокращение поиска по первичному ключу, которое обозначается как “primary key”.
В примере модели 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 используют кэшированные результаты.
Помните об этом поведении кэширования, так как это может повлиять на вашу работу, если вы не используете QuerySet правильно. Например, следующее создаст два QuerySet, оценит их и выбросит:
>>> print([e.headline for e in Entry.objects.all()]) >>> print([e.pub_date for e in Entry.objects.all()])
Это означает, что один и тот же запрос к базе данных будет выполнен дважды, что удвоит нагрузку на базу данных. Кроме того, возможно, что два списка не содержат одни и те же записи в базе данных, так как в доли секунды между двумя запросами могли быть добавлены или удалены Entry.
Чтобы избежать этой проблемы, просто сохраните QuerySet и используйте его повторно:
>>> queryset = Entry.objects.all() >>> print([p.headline for p in queryset]) # Evaluate the query set. >>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
Когда QuerySet не кэшируются
QuerySet не всегда кэшируют свои результаты. При оценке только части 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.
Сложные запросы с объектами 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-операторu 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))
)
… не был бы допустимым.
См. также
Примеры запросов 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, {'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.select_related().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.all().update(n_pingbacks=F('n_pingbacks') + 1)
Однако, в отличие от F() объектов в фильтрах и исключениях, вы не можете добавлять соединения, когда вы используете F() объекты в обновлении — вы можете ссылаться только на поля, локальные для обновляемой модели. Если вы попытаетесь добавить соединение с F() объектом, будет поднято исключение FieldError.
# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))
Связанные объекты
Например, объект 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()
Дополнительные методы для обработки связанных объектов
-
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 (Не повторяй себя), поэтому 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/2.0/topics/db/queries/