Создание запросов
После создания ваших моделей данных, 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): # __unicode__ on Python 2
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self): # __unicode__ on Python 2
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): # __unicode__ on Python 2
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. В этом случае ожидается, что параметр значения будет содержать исходное значение первичного ключа внешней модели. Например:
>>> Entry.objects.filter(blog_id=4)
Если вы передадите недопустимый ключевой аргумент, функция поиска вызовет TypeError.
API базы данных поддерживает около двух десятков типов поиска; полная справка находится в справочнике по поиску по полям. Для ознакомления вот некоторые из наиболее часто используемых типов поиска:
-
exact -
Полное совпадение. Например:
>>> Entry.objects.get(headline__exact="Cat bites dog")
сгенерирует SQL примерно такого вида:
SELECT ... WHERE headline = 'Cat bites dog';
Если вы не укажете тип поиска — то есть, если ваш ключевой аргумент не содержит двойного подчеркивания — тип поиска по умолчанию будет
exact.Например, следующие два утверждения эквивалентны:
>>> Blog.objects.get(id__exact=14) # Explicit form >>> Blog.objects.get(id=14) # __exact is implied
Это сделано для удобства, так как
exactпоиск — это общий случай. -
iexact -
Поиск без учета регистра. Таким образом, запрос:
>>> Blog.objects.get(name__iexact="beatles blog")
будет соответствовать заголовку
Blog,"Beatles Blog", или даже"beatles blog", или даже"BeAtlES blOG". -
contains -
Поиск по содержанию, учитывающий регистр. Например:
Entry.objects.get(headline__contains='Lennon')
приблизительно переводится в этот SQL:
SELECT ... WHERE headline LIKE '%Lennon%';
Обратите внимание, что это будет соответствовать заголовку
'Today Lennon honored', но не'today lennon honored'.Также есть вариант без учета регистра —
icontains. -
startswith,endswith - Поиск с начала и с конца строки соответственно. Также есть варианты без учета регистра, называемые
istartswithиiendswith.
Опять же, это лишь малая часть. Полную справку можно найти в справочнике по поиску по полям.
Поиск через связи
Django предлагает мощный и интуитивный способ «следовать» связям в поисках, автоматически заботясь о SQL JOIN за кулисами. Чтобы перейти по связи, просто используйте имя поля связанных полей через модели, разделенных двойными подчеркиваниями, пока не дойдете до нужного поля.
Этот пример извлекает все Entry объекты с Blog у которых поле name равно 'Beatles Blog':
>>> Entry.objects.filter(blog__name='Beatles Blog')
Это прослеживание может быть сколь угодно глубоким.
Также работает в обратном порядке. Чтобы сослаться на обратную связь, просто используйте имя модели в нижнем регистре.
В этом примере извлекаются все 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» и «bands», или мы можем захотеть запись, содержащую тег с именем «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() необязательно относятся к одному и тому же элементу.
Например, следующий запрос исключит блоги, содержащие обе записи с «Ленноном» в заголовке и записи, опубликованные в 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() действуют как ссылка на поле модели в запросе. Затем эти ссылки можно использовать в фильтрах запросов для сравнения значений двух разных полей одной и той же записи модели.
Например, чтобы найти список всех записей блога, у которых было больше комментариев, чем обращений к pingback, мы строим объект F() для ссылки на счет pingback и используем этот объект F() в запросе:
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django поддерживает использование арифметических операций сложения, вычитания, умножения, деления, модуля и возведения в степень с объектами F() как с константами, так и с другими объектами F(). Чтобы найти все записи блога с более чем в два раза большим количеством комментариев, чем обращений к pingback, мы изменяем запрос:
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
Чтобы найти все записи, где рейтинг записи меньше суммы количества обращений к pingback и комментариев, мы выполним запрос:
>>> 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 предоставляет сокращение поиска по первичному ключу 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 SQL-запросам (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 не кэшируются
Querysets не всегда кэшируют свои результаты. При оценке только части queryset кэш проверяется, но если он не заполнен, то элементы, возвращаемые последующим запросом, не кэшируются. В частности, это означает, что ограничение queryset с помощью среза массива или индекса не заполнит кэш.
Например, многократное получение определенного индекса в объекте queryset каждый раз будет обращаться к базе данных:
>>> queryset = Entry.objects.all() >>> print(queryset[5]) # Queries the database >>> print(queryset[5]) # Queries the database again
Однако, если весь queryset уже был оценен, кэш будет проверен вместо этого:
>>> queryset = Entry.objects.all() >>> [entry for entry in queryset] # Queries the database >>> print(queryset[5]) # Uses cache >>> print(queryset[5]) # Uses cache
Вот несколько примеров других действий, которые приведут к оценке всего queryset и, следовательно, заполнению кэша:
>>> [entry for entry in queryset] >>> bool(queryset) >>> entry in queryset >>> list(queryset)
Примечание
Просто вывод queryset не заполнит кэш. Это происходит потому, что вызов __repr__() возвращает только срез всего queryset.
Сложные поиски с объектами Q
Поиски с ключевыми словами — в filter() и т.д. — объединяются с помощью «И». Если вам нужны более сложные запросы (например, запросы с OR операторами), вы можете использовать Q objects.
Объект Q object (django.db.models.Q) используется для объединения набора ключевых аргументов. Эти ключевые аргументы указаны так же, как и в «Поисковых запросах к полю» выше.
Например, этот объект Q объединяет один запрос LIKE:
from django.db.models import Q Q(question__startswith='What')
Объекты Q могут объединяться с помощью операторов & и |. Когда оператор используется с двумя объектами Q, он создает новый объект Q.
Например, это утверждение создает один объект Q, представляющий «ИЛИ» двух запросов "question__startswith":
Q(question__startswith='Who') | Q(question__startswith='What')
Это эквивалентно следующему SQL WHERE оператору:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
Вы можете составлять выражения произвольной сложности, комбинируя объекты Q с операторами & и | и используя скобочные группировки. Кроме того, объекты Q можно отрицать с помощью оператора ~, что позволяет комбинировать запросы, объединяя как обычный запрос, так и отменённый (NOT):
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
Каждая функция поиска, которая принимает именованные аргументы (например, filter(), exclude(), get()) также может принимать один или несколько объектов Q в качестве позиционных (не именованных) аргументов. Если вы предоставляете несколько аргументов объекта Q, они будут объединены оператором «И». Например:
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
… примерно переводится на SQL:
SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
Функции поиска могут комбинировать использование объектов Q и именованных аргументов. Все аргументы, предоставленные функции поиска (будь то именованные аргументы или объекты Q ), объединяются оператором «И». Однако, если предоставляется объект Q, он должен предшествовать определению именованных аргументов. Например:
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who',
)
… будет допустимым запросом, эквивалентным предыдущему примеру; но:
# INVALID QUERY
Poll.objects.get(
question__startswith='Who',
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
… не будет допустимым.
См. также
Примеры запросов «ИЛИ» в примерах Django unit tests демонстрируют некоторые возможные варианты использования 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() в предложениях filter и exclude, вы не можете вводить соединения, когда используете объекты 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() с итерируемым объектом экземпляров объектов или списком значений первичных ключей. Например:
b = Blog.objects.get(id=1) b.entry_set.set([e1, e2])
В этом примере e1 и e2 могут быть полными экземплярами Entry или целочисленными значениями первичных ключей.
Если метод 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.
Отношения один-к-одному
Отношения один-к-одному очень похожи на отношения один-ко-многим. Если вы определите OneToOneField в вашей модели, экземпляры этой модели получат доступ к связанному объекту через простое свойство модели.
Например:
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
Различие заключается в обратных запросах. Связанная модель в отношении один-к-одному также имеет доступ к объекту менеджера Manager, но этот менеджер Manager представляет собой один объект, а не коллекцию объектов:
e = Entry.objects.get(id=2) e.entrydetail # returns the related EntryDetail object
Если объекту не назначено значение для этого отношения, Django поднимет исключение DoesNotExist.
Экземпляры могут быть назначены обратным отношением таким же образом, как и прямое отношение:
e.entrydetail = ed
Как возможны обратные отношения?
Другие объектно-реляционные мэпперы требуют определения отношений с обеих сторон. Разработчики Django считают это нарушением принципа DRY (Don’t Repeat Yourself), поэтому Django требует определения отношения только с одной стороны.
Но как это возможно, учитывая, что класс модели не знает, какие другие классы моделей связаны с ним, пока эти другие классы моделей не будут загружены?
Ответ кроется в app registry. Когда Django запускается, он импортирует каждую приложение, перечисленную в INSTALLED_APPS, а затем модуль models внутри каждого приложения. Всякий раз, когда создается новый класс модели, Django добавляет обратные отношения к любым связанным моделям. Если связанные модели еще не импортированы, Django отслеживает отношения и добавляет их, когда связанные модели будут импортированы.
По этой причине крайне важно, чтобы все используемые модели были определены в приложениях, перечисленных в INSTALLED_APPS. В противном случае обратные связи могут работать некорректно.
Запросы к связанным объектам
Например, если у вас есть объект Blog b с id=5, следующие три запроса будут идентичными:
Entry.objects.filter(blog=b) # Query using object instance Entry.objects.filter(blog=b.id) # Query using id from instance Entry.objects.filter(blog=5) # Query using id directly
Обращение к SQL напрямую
Если вам потребуется написать запрос SQL, который слишком сложен для обработки Django-мэппером базы данных, вы можете вернуться к написанию SQL вручную. Django предоставляет несколько вариантов для написания запросов SQL напрямую; см. Выполнение запросов SQL напрямую.
И, наконец, важно отметить, что уровень базы данных Django — это всего лишь интерфейс к вашей базе данных. Вы можете получить доступ к своей базе данных с помощью других инструментов, языков программирования или фреймворков баз данных; в базе данных нет ничего специфичного для Django.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.11/topics/db/queries/