Создание запросов
После создания ваших моделей данных, Django автоматически предоставляет API абстракции базы данных, позволяющий создавать, получать, обновлять и удалять объекты. Этот документ объясняет, как использовать этот API. Обратитесь к справочнику по моделям данных для получения полной информации обо всех различных параметрах поиска моделей.
В этом руководстве (и в справочнике) мы будем ссылаться на следующие модели, которые составляют приложение Weblog:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField()
number_of_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __str__(self):
return self.headline
Создание объектов
Для представления данных таблиц базы данных в объектах Python Django использует интуитивную систему: класс модели представляет таблицу базы данных, а экземпляр этого класса представляет собой конкретную запись в таблице базы данных.
Чтобы создать объект, инициализируйте его с помощью ключевых аргументов для класса модели, а затем вызовите save() для сохранения его в базе данных.
Предполагая, что модели находятся в файле mysite/blog/models.py, вот пример:
>>> from blog.models import Blog >>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') >>> b.save()
Это выполняет 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». Второй — подмножество первого с дополнительным условием, которое исключает записи, дата публикации которых — сегодня или в будущем. Третий — подмножество первого с дополнительным условием, которое выбирает только записи, дата публикации которых — сегодня или в будущем. Начальный 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 выполняется путём обращения к базе данных. Более подробную информацию о том, когда происходит выполнение, см. в разделе Когда выполняются запросы к наборам результатов.
Получение одного объекта с помощью 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() при необходимости поиска объектов в базе данных. Но это далеко не всё; см. Список методов наборов результатов, чтобы получить полный список всех различных 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 будет содержать исходное значение первичного ключа внешней модели. Например:
>>> 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() необязательно будут относиться к одному и тому же элементу.
Например, следующий запрос исключит блоги, которые содержат обе записи с «Lennon» в заголовке и записи, опубликованные в 2008 году:
Blog.objects.exclude(
entry__headline__contains='Lennon',
entry__pub_date__year=2008,
)
Однако, в отличие от поведения при использовании filter(), это не будет ограничивать блоги записями, которые удовлетворяют обоим условиям. Для этого, то есть для выбора всех блогов, не содержащих записей, опубликованных с «Lennon» и опубликованных в 2008 году, необходимо выполнить два запроса:
Blog.objects.exclude(
entry__in=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
Фильтры могут ссылаться на поля модели
В примерах, приведенных до сих пор, мы создали фильтры, которые сравнивают значение поля модели с константой. Но что, если вам нужно сравнить значение поля модели с другим полем в той же модели?
Django предоставляет F expressions для таких сравнений. Экземпляры F() действуют как ссылка на поле модели в запросе. Эти ссылки затем могут использоваться в фильтрах запросов для сравнения значений двух различных полей в одном экземпляре модели.
Например, чтобы найти список всех записей блога, которые имеют больше комментариев, чем пинбеков, мы создаем объект F() для ссылки на количество пинбеков и используем этот объект F() в запросе:
>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))
Django поддерживает использование арифметических операций сложения, вычитания, умножения, деления, остатка от деления и возведения в степень с объектами F() как с константами, так и с другими объектами F(). Чтобы найти все записи блога с более чем в два раза большим количеством комментариев, чем пинбеков, мы изменяем запрос:
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)
Чтобы найти все записи, где рейтинг записи меньше суммы количества пинбеков и комментариев, мы выполнили бы запрос:
>>> Entry.objects.filter(rating__lt=F('number_of_comments') + F('number_of_pingbacks'))
Вы также можете использовать обозначение с двойным подчеркиванием для прохода по отношениям в объекте F(). Объект F() с двойным подчеркиванием добавит необходимые соединения для доступа к связанному объекту. Например, чтобы получить все записи, где имя автора совпадает с именем блога, мы могли бы выполнить запрос:
>>> Entry.objects.filter(authors__name=F('blog__name'))
Для полей даты и даты/времени вы можете добавить или вычесть объект timedelta. Следующее вернет все записи, которые были изменены более чем на 3 дня после публикации:
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
Объекты F() поддерживают побитовые операции с помощью .bitand(), .bitor(), .bitrightshift(), и .bitleftshift(). Например:
>>> F('somefield').bitand(16)
Сокращение поиска по первичному ключу
Для удобства Django предоставляет сокращение поиска по первичному ключу, которое обозначает «первичный ключ».
В примере 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 = 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')
Это эквивалентно следующему 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))
)
… будет недопустимым.
См. также
Примеры использования Q в тестах Django находятся по адресу https://github.com/django/django/blob/stable/2.2.x/tests/or_lookups/tests.py.
Сравнение объектов
Для сравнения двух экземпляров модели используйте стандартный оператор сравнения 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(number_of_pingbacks=F('number_of_pingbacks') + 1)
Однако, в отличие от F() объектов в предложениях filter и exclude, вы не можете вводить соединения, когда используете F() объекты в обновлении — вы можете только ссылаться на поля, локальные для модели, которая обновляется. Если вы попытаетесь ввести соединение с F() объектом, будет поднята ошибка FieldError:
# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))
Связанные объекты
Например, используя модели вверху этой страницы, объект 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 (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/2.2/topics/db/queries/