Создание запросов
После создания ваших моделей данных, 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=50)
email = models.EmailField()
def __str__(self): # __unicode__ on Python 2
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
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 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(2005, 1, 30) ... )
Это берет начальный QuerySet всех записей в базе данных, добавляет фильтр, затем исключение, затем еще один фильтр. Конечный результат — QuerySet, содержащий все записи с заголовком, начинающимся с «Что», опубликованные между 30 января 2005 года и текущей датой.
Отфильтрованные QuerySets уникальны
Каждый раз, когда вы уточняете 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, содержащий все записи, которые содержат заголовок, начинающийся с «Что». Второй — подмножество первого с дополнительным критерием, исключающим записи, чья pub_date сегодня или в будущем. Третий — подмножество первого с дополнительным критерием, выбирающим только записи, чья pub_date сегодня или в будущем. Начальный QuerySet (q1) не изменяется в процессе уточнения.
QuerySets ленивы
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 методов.
Ограничение QuerySets
Используйте подмножество синтаксиса срезов массивов 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="Man bites dog")
Что сгенерирует SQL примерно так:
SELECT ... WHERE headline = 'Man 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 — это отношение один-ко-многим). Мы можем быть заинтересованы в поиске блогов, у которых есть запись, у которой в заголовке есть «Леннон» и публикация состоялась в 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=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(), например:
>>> F('somefield').bitand(16)
Краткая запись для поиска по первичному ключу
Для удобства 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)
Поиск по первичному ключу также работает через соединения. Например, эти три утверждения эквивалентны:
>>> 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 (и, следовательно, происходит запрос к базе данных) 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 = 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__() возвращает только часть всего набора результатов запроса.
Сложные запросы с объектами 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')
Это эквивалентно следующему WHERE оператору SQL:
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)))
… не будет корректным.
См. также
Примеры использования Q в тестах Django представлены в примерах OR-поиска.
Сравнение объектов
Для сравнения двух экземпляров модели используйте стандартный оператор сравнения 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()
Вы также можете удалять объекты в больших объёмах. У каждого QuerySet есть метод delete(), который удаляет всех членов этого QuerySet.
Например, это удаляет все Entry объекты с годом pub_date 2005:
Entry.objects.filter(pub_date__year=2005).delete()
Помните, что это, по возможности, будет выполнено чисто в 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 имеет поле «многие ко многим» к Author:
entry = Entry.objects.all()[0] # some previous entry old_authors = entry.authors.all() entry.pk = None entry.save() entry.authors = old_authors # saves new many2many relations
Обновление нескольких объектов сразу
Иногда вам нужно установить поле в определённое значение для всех объектов в 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, 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()
Дополнительные методы для работы со связанными объектами
В дополнение к методам набора запросов QuerySet, определённых в разделе «Получение объектов» выше, у менеджера ForeignKey Manager есть дополнительные методы, используемые для обработки набора связанных объектов. Краткое описание каждого из них приведено ниже, а полные детали можно найти в справочнике по связанным объектам.
-
add(obj1, obj2, ...) - Добавляет указанные объекты модели в набор связанных объектов.
-
create(**kwargs) - Создаёт новый объект, сохраняет его и добавляет в набор связанных объектов. Возвращает только что созданный объект.
-
remove(obj1, obj2, ...) - Удаляет указанные объекты модели из набора связанных объектов.
-
clear() - Удаляет все объекты из набора связанных объектов.
Чтобы назначить элементы связанного набора единым действием, просто назначьте ему значение из любого итерируемого объекта. Итерируемый объект может содержать экземпляры объектов или просто список значений первичных ключей. Например:
b = Blog.objects.get(id=1) b.entry_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)
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/1.8/topics/db/queries/