Создание запросов
После создания ваших моделей данных, Django автоматически предоставляет вам API абстракции базы данных, позволяющий создавать, извлекать, обновлять и удалять объекты. Этот документ объясняет, как использовать этот API. Обратитесь к справочнику по моделям данных для получения полной информации обо всех различных опциях поиска моделей.
В этом руководстве (и в справочнике) мы будем ссылаться на следующие модели, которые составляют приложение блога:
from datetime import date
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(default=date.today)
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField(default=0)
number_of_pingbacks = models.IntegerField(default=0)
rating = models.IntegerField(default=5)
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, содержащий все записи с заголовком, начинающимся с «Что», опубликованные между 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, содержащий все записи, у которых заголовок начинается с «Что». Второй — подмножество первого с дополнительным критерием, исключающим записи, у которых pub_date сегодня или в будущем. Третий — подмножество первого с дополнительным критерием, выбирающим только записи, у которых pub_date сегодня или в будущем. Начальный QuerySet (q1) не затрагивается процессом уточнения.
QuerySet ленивы
QuerySets ленивы — создание QuerySet не подразумевает никаких действий с базой данных. Вы можете составлять фильтры весь день, и Django не выполнит запрос до тех пор, пока QuerySet не будет оценён. Посмотрите на этот пример:
>>> q = Entry.objects.filter(headline__startswith="What") >>> q = q.filter(pub_date__lte=datetime.date.today()) >>> q = q.exclude(body_text__icontains="food") >>> print(q)
Хотя это выглядит как три обращения к базе данных, на самом деле база данных обращается только один раз, в последней строке (print(q)). В общем случае результаты QuerySet не извлекаются из базы данных, пока вы не «спросите» их. Когда вы это делаете, QuerySet оценивается путём доступа к базе данных. Более подробную информацию о том, когда происходит оценка, см. в разделе Когда происходят оценки QuerySet.
Получение отдельного объекта с get()
filter() всегда вернёт QuerySet, даже если только один объект соответствует запросу — в этом случае это будет QuerySet, содержащий один элемент.
Если известно, что только один объект соответствует вашему запросу, можно использовать метод get() на Manager, который возвращает объект напрямую:
>>> one_entry = Entry.objects.get(pk=1)
Можно использовать любые выражения запроса с get(), как и с filter() — опять же, см. Поиск по полям ниже.
Обратите внимание на разницу между использованием get() и использованием filter() с фрагментом [0]. Если результаты, соответствующие запросу, отсутствуют, get() вызовет исключение DoesNotExist. Это исключение является атрибутом класса модели, для которого выполняется запрос — так что в приведенном выше коде, если объект Entry с первичным ключом 1 не существует, Django вызовет Entry.DoesNotExist.
Аналогично, Django будет жаловаться, если несколько элементов соответствуют запросу get(). В этом случае он вызовет MultipleObjectsReturned, который опять же является атрибутом самого класса модели.
Другие методы QuerySet
В большинстве случаев вы будете использовать all(), get(), filter() и exclude(), когда вам нужно искать объекты в базе данных. Однако это далеко не всё; см. Справочник по API QuerySet для получения полного списка всех различных QuerySet методов.
Ограничение QuerySet
Используйте подмножество синтаксиса срезов массивов Python, чтобы ограничить свой QuerySet определённым количеством результатов. Это эквивалентно условиям LIMIT и OFFSET в SQL.
Например, это возвращает первые 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]
Дальнейшее фильтрация или сортировка среза queryset запрещены из-за неясной природы того, как это может работать.
Чтобы получить один объект, а не список (например, 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")
Этот охват может быть сколь угодно глубоким.
Это также работает в обратном направлении. Хотя он can be customized, по умолчанию вы обращаетесь к «обратному» отношению в поиске, используя имя модели в нижнем регистре.
Этот пример извлекает все 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), фильтрация по нескольким атрибутам поднимает вопрос о том, нужно ли требовать совпадения каждого атрибута в одном связанном объекте. Мы можем искать блоги, в которых есть запись 2008 года со словом «Леннон» в заголовке, или мы можем искать блоги, в которых просто есть какая-либо запись 2008 года, а также какая-то более поздняя или более ранняя запись со словом «Леннон» в заголовке.
Чтобы выбрать все блоги, содержащие, по крайней мере, одну запись 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 года не содержит «Леннон». Первый запрос не вернёт никаких блогов, а второй вернёт этот единственный блог. (Это потому, что записи, выбранные вторым фильтром, могут или не могут совпадать с записями в первом фильтре. Мы фильтруем Blog элементы с каждой инструкцией фильтра, а не Entry элементы). Короче говоря, если каждое условие должно соответствовать одному и тому же связанному объекту, то каждое должно содержаться в одном вызове filter().
Примечание
Поскольку второй (более либеральный) запрос цепочкой соединяет несколько фильтров, он выполняет несколько объединений с основной моделью, потенциально возвращая дубликаты.
>>> from datetime import date >>> beatles = Blog.objects.create(name="Beatles Blog") >>> pop = Blog.objects.create(name="Pop Music Blog") >>> Entry.objects.create( ... blog=beatles, ... headline="New Lennon Biography", ... pub_date=date(2008, 6, 1), ... ) <Entry: New Lennon Biography> >>> Entry.objects.create( ... blog=beatles, ... headline="New Lennon Biography in Paperback", ... pub_date=date(2009, 6, 1), ... ) <Entry: New Lennon Biography in Paperback> >>> Entry.objects.create( ... blog=pop, ... headline="Best Albums of 2008", ... pub_date=date(2008, 12, 15), ... ) <Entry: Best Albums of 2008> >>> Entry.objects.create( ... blog=pop, ... headline="Lennon Would Have Loved Hip Hop", ... pub_date=date(2020, 4, 1), ... ) <Entry: Lennon Would Have Loved Hip Hop> >>> Blog.objects.filter( ... entry__headline__contains="Lennon", ... entry__pub_date__year=2008, ... ) <QuerySet [<Blog: Beatles Blog>]> >>> Blog.objects.filter( ... entry__headline__contains="Lennon", ... ).filter( ... entry__pub_date__year=2008, ... ) <QuerySet [<Blog: Beatles Blog>, <Blog: Beatles Blog>, <Blog: Pop Music Blog]>
Примечание
Поведение filter() для запросов, охватывающих многозначные отношения, как описано выше, не реализовано эквивалентно для exclude(). Вместо этого условия в одном вызове exclude() не обязательно будут относиться к одному и тому же элементу.
Например, следующий запрос исключит блоги, которые содержат и записи со словом «Леннон» в заголовке и записи, опубликованные в 2008 году:
Blog.objects.exclude(
entry__headline__contains="Lennon",
entry__pub_date__year=2008,
)
Однако, в отличие от поведения при использовании filter(), это не ограничит блоги на основе записей, удовлетворяющих обоим условиям. Для того, чтобы сделать это, т.е. выбрать все блоги, не содержащие записи с «Леннон» в заголовке, опубликованные в 2008 году, необходимо выполнить два запроса:
Blog.objects.exclude(
entry__in=Entry.objects.filter(
headline__contains="Lennon",
pub_date__year=2008,
),
)
Фильтры могут ссылаться на поля модели
В примерах, приведенных до сих пор, мы создавали фильтры, сравнивающие значение поля модели с константой. Но что, если вам нужно сравнить значение поля модели с другим полем той же модели?
Django предоставляет F expressions для таких сравнений. Экземпляры F() действуют как ссылки на поле модели внутри запроса. Эти ссылки затем могут использоваться в фильтрах запросов для сравнения значений двух разных полей одного и того же экземпляра модели.
Например, чтобы получить список всех записей блога, у которых количество комментариев больше, чем пинбеков, мы создаём объект F() для ссылки на количество пинбеков и используем этот объект F() в запросе:
>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks"))
Django поддерживает использование сложения, вычитания, умножения, деления, модуля и возведения в степень с объектами F() , как с константами, так и с другими объектами F() . Чтобы найти все записи блога с более чем в два раза большим количеством комментариев, чем пинбеков, мы изменяем запрос:
>>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks") * 2)
Чтобы найти все записи, где рейтинг записи меньше суммы количества пинбеков и комментариев, мы выполним запрос:
>>> Entry.objects.filter(rating__lt=F("number_of_comments") + F("number_of_pingbacks"))
Также можно использовать двойное подчеркивание для указания взаимосвязей в объекте F(). Объект F() с двойным подчеркиванием добавит необходимые соединения для доступа к связанному объекту. Например, для получения всех записей, где имя автора совпадает с именем блога, можно выполнить запрос:
>>> Entry.objects.filter(authors__name=F("blog__name"))
Для полей даты и даты/времени можно добавить или вычесть объект timedelta. Следующее вернёт все записи, изменённые более чем на 3 дня после публикации:
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F("pub_date") + timedelta(days=3))
Объекты F() поддерживают побитовые операции с помощью .bitand(), .bitor(), .bitxor(), .bitrightshift(), и .bitleftshift(). Например:
>>> F("somefield").bitand(16)
Oracle
Oracle не поддерживает побитовую операцию XOR.
Выражения могут ссылаться на преобразования
Django поддерживает использование преобразований в выражениях.
Например, чтобы найти все Entry объекты, опубликованные в том же году, что и последняя их модификация:
>>> from django.db.models import F
>>> Entry.objects.filter(pub_date__year=F("mod_date__year"))
Чтобы найти самый ранний год публикации записи, можно выполнить запрос:
>>> from django.db.models import Min
>>> Entry.objects.aggregate(first_published_year=Min("pub_date__year"))
В этом примере находится значение записи с наивысшим рейтингом и общее количество комментариев ко всем записям для каждого года:
>>> from django.db.models import OuterRef, Subquery, Sum
>>> Entry.objects.values("pub_date__year").annotate(
... top_rating=Subquery(
... Entry.objects.filter(
... pub_date__year=OuterRef("pub_date__year"),
... )
... .order_by("-rating")
... .values("rating")[:1]
... ),
... total_comments=Sum("number_of_comments"),
... )
Краткая запись поиска по первичному ключу
Для удобства 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]) # Reuse the cache from the evaluation.
Когда QuerySet не кэшируются
Множества запросов не всегда кэшируют свои результаты. При оценке только части набора запросов кэш проверяется, но если он не заполнен, то элементы, возвращаемые последующим запросом, не кэшируются. В частности, это означает, что ограничение набора запросов с помощью среза массива или индекса не заполнит кэш.
Например, повторный доступ к определённому индексу в объекте набора запросов будет каждый раз обращаться к базе данных:
>>> queryset = Entry.objects.all() >>> print(queryset[5]) # Queries the database >>> print(queryset[5]) # Queries the database again
Однако, если весь набор запросов уже был оценен, будет проверено содержимое кэша:
>>> queryset = Entry.objects.all() >>> [entry for entry in queryset] # Queries the database >>> print(queryset[5]) # Uses cache >>> print(queryset[5]) # Uses cache
Вот несколько примеров других действий, которые приведут к оценке всего набора запросов и, следовательно, к заполнению кэша:
>>> [entry for entry in queryset] >>> bool(queryset) >>> entry in queryset >>> list(queryset)
Примечание
Простой вывод набора запросов не заполнит кэш. Это связано с тем, что вызов __repr__() возвращает только срез всего набора запросов.
Асинхронные запросы
Если вы пишете асинхронные представления или код, вы не можете использовать ORM для запросов так, как описано выше, так как вы не можете вызывать блокирующие синхронные функции из асинхронного кода — это заблокирует цикл событий (или, скорее всего, Django заметит это и вызовет SynchronousOnlyOperation чтобы остановить это).
К счастью, вы можете выполнять многие запросы с помощью асинхронных API Django. Каждый метод, который может заблокировать — такой как get() или delete() — имеет асинхронную версию (aget() или adelete()), и при итерировании по результатам вы можете использовать асинхронное итерирование (async for) вместо него.
Итерация по запросам
Стандартный способ итерирования по запросу — с for — приведет к блокирующему запросу к базе данных в фоновом режиме, поскольку Django загружает результаты во время итерации. Чтобы исправить это, можно использовать async for.
async for entry in Authors.objects.filter(name__startswith="A"):
...
Обратите внимание, что вы также не можете выполнять другие действия, которые могут итерировать по набору запросов, например, обёртку list() вокруг него, чтобы принудительно выполнить его оценку (вы можете использовать async for в списке понимания, если вам это нужно).
Поскольку методы QuerySet такие как filter() и exclude() на самом деле не запускают запрос — они настраивают набор запросов для запуска при итерировании по нему — вы можете использовать их свободно в асинхронном коде. Для руководства по методам, которые могут использоваться таким образом, и методам, имеющим асинхронные версии, прочитайте следующий раздел.
QuerySet и методы менеджеров
Некоторые методы менеджеров и наборов запросов — такие как get() и first() — принудительно выполняют запрос набора запросов и являются блокирующими. Некоторые, такие как filter() и exclude(), не принуждают к выполнению и поэтому безопасны для использования в асинхронном коде. Но как узнать разницу?
Хотя вы можете попробовать найти метод с префиксом a (например, у нас есть aget(), но нет afilter()), есть более логичный способ — посмотреть, какого типа метод в справочнике по наборам запросов.
Там вы найдете методы наборов запросов, сгруппированные в два раздела:
-
Методы, возвращающие новые наборы запросов: Это неблокирующие методы, и у них нет асинхронных версий. Вы можете использовать их в любой ситуации, хотя перед их применением прочтите примечания к
defer()иonly(). -
Методы, не возвращающие наборы запросов: Это блокирующие методы, и у них есть асинхронные версии — асинхронное имя каждого указано в его документации, хотя наш стандартный шаблон заключается в добавлении префикса
a.
Используя это различие, вы можете понять, когда вам нужны асинхронные версии, а когда нет. Например, вот допустимый асинхронный запрос:
user = await User.objects.filter(username=my_input).afirst()
filter() возвращает набор запросов, поэтому его можно продолжать цеплять в асинхронной среде, тогда как first() оценивает и возвращает экземпляр модели — поэтому мы меняем на afirst(), и используем await в начале всего выражения, чтобы вызвать его асинхронно-дружелюбным способом.
Примечание
Если вы забудете указать часть await, вы можете увидеть ошибки, такие как «объект сопрограммы не имеет атрибута x» или «<сопрограмма…>» вместо экземпляров модели. Если вы увидите их, значит, вы пропустили await для преобразования сопрограммы в действительное значение.
Транзакции
Транзакции в настоящее время не поддерживаются при асинхронных запросах и обновлениях. При попытке их использовать возникнет SynchronousOnlyOperation.
Если вы хотите использовать транзакцию, рекомендуется написать код ORM внутри отдельной синхронной функции, а затем вызвать её с помощью sync_to_async - см. Поддержка асинхронности для получения дополнительной информации.
Запрос JSONField
Реализация поиска отличается в JSONField, в основном из-за наличия преобразований ключей. Для демонстрации мы используем следующую модель:
from django.db import models
class Dog(models.Model):
name = models.CharField(max_length=200)
data = models.JSONField(null=True)
def __str__(self):
return self.name
Хранение и поиск None
Как и в случае с другими полями, хранение None в качестве значения поля сохранит его в виде SQL NULL. Хотя это не рекомендуется, можно сохранить скалярное значение JSON null вместо SQL NULL, используя Value(None, JSONField()).
Независимо от того, какое значение хранится, при извлечении из базы данных, представление Python скалярного значения JSON null будет таким же, как SQL NULL, т.е. None. Поэтому их может быть сложно отличить.
Это относится только к None в качестве значения верхнего уровня поля. Если None находится внутри list или dict, оно всегда будет интерпретироваться как JSON null.
При запросе значение None всегда будет интерпретироваться как JSON null. Чтобы найти SQL NULL, используйте isnull:
>>> Dog.objects.create(name="Max", data=None) # SQL NULL. <Dog: Max> >>> Dog.objects.create(name="Archie", data=Value(None, JSONField())) # JSON null. <Dog: Archie> >>> Dog.objects.filter(data=None) <QuerySet [<Dog: Archie>]> >>> Dog.objects.filter(data=Value(None, JSONField())) <QuerySet [<Dog: Archie>]> >>> Dog.objects.filter(data__isnull=True) <QuerySet [<Dog: Max>]> >>> Dog.objects.filter(data__isnull=False) <QuerySet [<Dog: Archie>]>
Если вы не уверены, что хотите работать со значениями SQL NULL, рассмотрите возможность установки null=False и предоставления подходящего значения по умолчанию для пустых значений, например default=dict.
Примечание
Хранение скалярного значения JSON null не нарушает null=False.
Добавлена поддержка выражения JSON null с помощью Value(None, JSONField()).
Устарело начиная с версии 4.2: Передача Value("null") для выражения JSON null устарела.
Преобразования ключей, индексов и путей
Для поиска по заданному ключу словаря используйте этот ключ как имя поиска:
>>> Dog.objects.create(
... name="Rufus",
... data={
... "breed": "labrador",
... "owner": {
... "name": "Bob",
... "other_pets": [
... {
... "name": "Fishy",
... }
... ],
... },
... },
... )
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": None})
<Dog: Meg>
>>> Dog.objects.filter(data__breed="collie")
<QuerySet [<Dog: Meg>]>
Несколько ключей могут быть объединены вместе для создания поиска по пути:
>>> Dog.objects.filter(data__owner__name="Bob") <QuerySet [<Dog: Rufus>]>
Если ключ является целым числом, он будет интерпретирован как преобразование индекса в массиве:
>>> Dog.objects.filter(data__owner__other_pets__0__name="Fishy") <QuerySet [<Dog: Rufus>]>
Если ключ, по которому вы хотите выполнить поиск, совпадает с именем другого поиска, используйте вместо этого поиск contains.
Чтобы выполнить поиск по отсутствующим ключам, используйте поиск isnull:
>>> Dog.objects.create(name="Shep", data={"breed": "collie"})
<Dog: Shep>
>>> Dog.objects.filter(data__owner__isnull=True)
<QuerySet [<Dog: Shep>]>
Примечание
Примеры поиска выше неявно используют поиск exact. Преобразования ключей, индексов и путей также могут быть объединены с: icontains, endswith, iendswith, iexact, regex, iregex, startswith, istartswith, lt, lte, gt и gte, а также с Включения и поиски по ключам.
KT() выражения
-
class KT(lookup) -
Представляет текстовое значение преобразования ключа, индекса или пути
JSONField. Вы можете использовать обозначение с двумя подчеркиваниями вlookupдля объединения преобразований ключей и индексов словаря.Например:
>>> from django.db.models.fields.json import KT >>> Dog.objects.create( ... name="Shep", ... data={ ... "owner": {"name": "Bob"}, ... "breed": ["collie", "lhasa apso"], ... }, ... ) <Dog: Shep> >>> Dogs.objects.annotate( ... first_breed=KT("data__breed__1"), owner_name=KT("data__owner__name") ... ).filter(first_breed__startswith="lhasa", owner_name="Bob") <QuerySet [<Dog: Shep>]>
Примечание
Из-за того, как работают поиски по пути ключа, exclude() и filter() не гарантируют получения исчерпывающих наборов. Если вы хотите включить объекты, у которых нет пути, добавьте поиск isnull.
Предупреждение
Так как любой строковый тип может быть ключом в объекте JSON, любой поиск, кроме перечисленных ниже, будет интерпретирован как поиск по ключу. Ошибки не возникают. Будьте внимательны к ошибкам ввода и всегда проверяйте, что ваши запросы работают так, как вы ожидаете.
Пользователи MariaDB и Oracle
Использование order_by() с преобразованиями ключей, индексов или путей отсортирует объекты с использованием строкового представления значений. Это происходит потому, что MariaDB и Oracle Database не предоставляют функцию преобразования JSON-значений в эквивалентные SQL-значения.
Пользователи Oracle
В запросах exclude() на Oracle Database использование None в качестве значения поиска вернёт объекты, у которых значение по заданному пути не null, включая объекты, у которых нет пути. На других бэкендах баз данных запрос вернёт объекты, у которых есть путь, и значение не равно null.
Пользователи PostgreSQL
В PostgreSQL, если используется только один ключ или индекс, используется оператор SQL -> . Если используется несколько операторов, используется оператор #>.
Пользователи SQLite
В SQLite строковые значения "true", "false", и "null" всегда будут интерпретироваться как True, False, и JSON null соответственно.
Включения и поиски по ключам
contains
Поиск contains переопределяется для JSONField. Возвращаются объекты, где все заданные dict пар ключ-значение содержатся в верхнем уровне поля. Например:
>>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contains={"owner": "Bob"})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={"breed": "collie"})
<QuerySet [<Dog: Meg>]>
Oracle и SQLite
contains не поддерживается в Oracle и SQLite.
contained_by
Это обратное значение поиска contains - возвращаемые объекты будут теми, где пары ключ-значение в объекте являются подмножеством пар в переданном значении. Например:
>>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contained_by={"breed": "collie", "owner": "Bob"})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={"breed": "collie"})
<QuerySet [<Dog: Fred>]>
Oracle и SQLite
contained_by не поддерживается в Oracle и SQLite.
has_key
Возвращает объекты, где заданный ключ находится на верхнем уровне данных. Например:
>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_key="owner")
<QuerySet [<Dog: Meg>]>
has_keys
Возвращает объекты, где все заданные ключи находятся на верхнем уровне данных. Например:
>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_keys=["breed", "owner"])
<QuerySet [<Dog: Meg>]>
has_any_keys
Возвращает объекты, где любой из заданных ключей находится на верхнем уровне данных. Например:
>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_any_keys=["owner", "breed"])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
Сложные поиски с объектами Q
Запросы с аргументами ключевых слов – в filter() и т. д. – объединяются с помощью оператора «И». Если вам нужно выполнить более сложные запросы (например, запросы с операторами 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 представлены в примерах OR lookups в тестах Django.
Сравнение объектов
Для сравнения двух экземпляров модели используйте стандартный оператор сравнения 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, {'blog.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 и _state.adding в True. Используя наш пример блога:
blog = Blog(name="My blog", tagline="Blogging is easy") blog.save() # blog.pk == 1 blog.pk = None blog._state.adding = True 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, и _state.adding в True:
django_blog.pk = None django_blog.id = None django_blog._state.adding = True 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._state.adding = True entry.save() entry.authors.set(old_authors)
Для OneToOneField, вам необходимо дублировать связанный объект и назначить его полю нового объекта, чтобы избежать нарушения уникального ограничения «один-ко-одному». Например, предположим, что entry уже дублирован, как указано выше:
detail = EntryDetail.objects.all()[0] detail.pk = None detail._state.adding = True 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.update(blog=b)
Метод update() применяется мгновенно и возвращает количество строк, соответствующих запросу (что может не совпадать с количеством обновленных строк, если некоторые строки уже имеют новое значение). Единственное ограничение на обновляемый QuerySet состоит в том, что он может обращаться только к одной таблице базы данных: основной таблице модели. Вы можете фильтровать по связанным полям, но обновлять вы можете только столбцы в основной таблице модели. Пример:
>>> b = Blog.objects.get(pk=1) # Update all the headlines belonging to this Blog. >>> Entry.objects.filter(blog=b).update(headline="Everything is the same")
Обратите внимание, что метод update() преобразуется непосредственно в оператор SQL. Это операция по пакетному обновлению. Он не выполняет какие-либо методы save() над вашими моделями или не генерирует сигналы pre_save или post_save (которые являются следствием вызова save()), или не учитывает параметр поля auto_now. Если вы хотите сохранить каждый элемент в QuerySet и убедиться, что метод save() вызывается для каждого экземпляра, вам не нужна специальная функция для этого. Просто переберите их и вызовите save():
for item in my_queryset:
item.save()
Вызовы обновления также могут использовать F expressions для обновления одного поля на основе значения другого поля в модели. Это особенно полезно для инкремента счётчиков на основе их текущего значения. Например, для инкремента счётчика pingback для каждой записи в блоге:
>>> Entry.objects.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()
Взаимодействие с предварительной загрузкой
При вызове prefetch_related() с обратным отношением используется менеджер по умолчанию. Если вы хотите предварительно загрузить связанные объекты с помощью пользовательского менеджера обратной связи, используйте Prefetch(). Например:
from django.db.models import Prefetch
prefetch_manager = Prefetch("entry_set", queryset=Entry.entries.all())
Blog.objects.prefetch_related(prefetch_manager)
Дополнительные методы для обработки связанных объектов
-
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/5.0/topics/db/queries/