Создание запросов
После создания ваших моделей данных, 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». Второй — подмножество первого, с дополнительным условием, которое исключает записи, чья pub_date дата сегодня или в будущем. Третий — подмножество первого, с дополнительным условием, которое выбирает только записи, чья pub_date дата сегодня или в будущем. Начальное QuerySet (q1) не затрагивается процессом уточнения.
QuerySet ленивые
QuerySets ленивые — создание QuerySet не предполагает никаких действий с базой данных. Вы можете добавлять фильтры весь день, и Django не выполнит запрос до тех пор, пока QuerySet не будет оценён. Посмотрите этот пример:
>>> q = Entry.objects.filter(headline__startswith="What") >>> q = q.filter(pub_date__lte=datetime.date.today()) >>> q = q.exclude(body_text__icontains="food") >>> print(q)
Хотя это выглядит как три обращения к базе данных, на самом деле база данных обращается только один раз, в последней строке (print(q)). В общем, результаты QuerySet не извлекаются из базы данных до тех пор, пока вы их не «запросите». Когда вы это сделаете, QuerySet оценивается с доступом к базе данных. Более подробную информацию о том, когда происходит оценка, см. в разделе Когда QuerySets оцениваются.
Получение отдельного объекта с помощью get()
filter() всегда вернёт QuerySet, даже если соответствует только один объект — в этом случае это будет QuerySet содержащий один элемент.
Если вы знаете, что существует только один объект, соответствующий вашему запросу, вы можете использовать метод get() на Manager, который возвращает объект напрямую:
>>> one_entry = Entry.objects.get(pk=1)
Вы можете использовать любое выражение запроса с get(), как и с filter() — опять же, см. Поиск по полям ниже.
Обратите внимание на разницу между использованием get() и использованием filter() с фрагментом [0]. Если результатов, соответствующих запросу, нет, get() вызовет исключение DoesNotExist. Это исключение — атрибут класса модели, на которой выполняется запрос — так что в приведенном выше коде, если нет объекта Entry с первичным ключом 1, Django вызовет Entry.DoesNotExist.
Аналогично, Django пожалуется, если более одного элемента соответствует запросу get(). В этом случае будет вызвано MultipleObjectsReturned, что также является атрибутом самого класса модели.
Другие QuerySet методы
Большую часть времени вы будете использовать all(), get(), filter() и exclude(), когда вам нужно искать объекты в базе данных. Однако это далеко не всё; обратитесь к Справочнику API QuerySet для полного списка всех методов QuerySet.
Ограничение QuerySet
Используйте подмножество синтаксиса срезов массивов Python, чтобы ограничить ваш QuerySet определенным количеством результатов. Это эквивалентно условиям SQL LIMIT и OFFSET.
Например, это возвращает первые 5 объектов (LIMIT 5):
>>> Entry.objects.all()[:5]
Это возвращает объекты с шестого по десятый (OFFSET 5 LIMIT 5):
>>> Entry.objects.all()[5:10]
Отрицательные индексы (т. е. Entry.objects.all()[-1]) не поддерживаются.
Как правило, взятие среза QuerySet возвращает новый QuerySet — запрос не оценивается. Исключение — если вы используете параметр «шаг» синтаксиса среза Python. Например, это фактически выполнит запрос, чтобы вернуть список каждого второго объекта из первых 10:
>>> Entry.objects.all()[:10:2]
Дальнейшая фильтрация или сортировка среза 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. В этом случае ожидается, что параметр значения будет содержать исходное значение первичного ключа модели внешнего ключа. Например:
>>> 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 (Blog к Entry — это отношение один-ко-многим). Мы можем заинтересоваться в поиске блогов, которые содержат запись, где в заголовке есть «Lennon» и она была опубликована в 2008 году. Или мы можем найти блоги, которые содержат запись с «Lennon» в заголовке, а также запись, опубликованную в 2008 году. Поскольку с одним Blog ассоциировано несколько записей, оба этих запроса возможны и осмыслены в некоторых ситуациях.
Такая же ситуация возникает с ManyToManyField. Например, если у Entry есть ManyToManyField с названием tags, мы можем искать записи, связанные с тегами «music» и «bands», или мы можем искать запись, содержащую тег с именем «music» и статусом «public».
Для обработки обеих этих ситуаций Django имеет согласованный способ обработки вызовов filter(). Все, что находится внутри одного вызова filter(), применяется одновременно для фильтрации элементов, соответствующих всем этим требованиям. Последующие вызовы filter() дополнительно ограничивают набор объектов, но для многозначных связей они применяются ко всем объектам, связанным с основной моделью, а не обязательно к тем объектам, которые были выбраны предыдущим вызовом filter().
Это может показаться немного запутанным, поэтому, надеюсь, пример прояснит ситуацию. Чтобы выбрать все блоги, которые содержат записи с «Lennon» в заголовке и были опубликованы в 2008 году (одна и та же запись удовлетворяет обоим условиям), мы напишем:
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
Чтобы выбрать все блоги, которые содержат запись с «Lennon» в заголовке и запись, опубликованную в 2008 году, мы напишем:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
Предположим, есть только один блог, который содержал и записи с «Lennon» и записи из 2008 года, но ни одна из записей из 2008 года не содержала «Lennon». Первый запрос не вернет никаких блогов, но второй запрос вернет тот единственный блог.
Во втором примере первый фильтр ограничивает набор результатов всеми блогами, связанными с записями, содержащими «Lennon» в заголовке. Второй фильтр дополнительно ограничивает набор результатов теми блогами, которые также связаны с записями, опубликованными в 2008 году. Записи, выбранные вторым фильтром, могут быть или не быть теми же, что и в первом фильтре. Мы фильтруем Blog элементы с помощью каждого оператора фильтра, а не Entry элементы.
Примечание
Поведение filter() для запросов, охватывающих многозначные отношения, как описано выше, не реализовано аналогичным образом для exclude(). Вместо этого условия в одном вызове exclude() не обязательно будут относиться к одному и тому же элементу.
Например, следующий запрос исключит блоги, которые содержат оба записи с «Ленноном» в заголовке и записи, опубликованные в 2008 году:
Blog.objects.exclude(
entry__headline__contains='Lennon',
entry__pub_date__year=2008,
)
Однако, в отличие от поведения при использовании filter(), это не будет ограничивать блоги на основе записей, которые удовлетворяют обоим условиям. Для этого, то есть для выбора всех блогов, которые не содержат записи, опубликованные с «Ленноном», опубликованные в 2008 году, необходимо выполнить два запроса:
Blog.objects.exclude(
entry__in=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
Фильтры могут ссылаться на поля модели
В приведенных до сих пор примерах мы построили фильтры, которые сравнивают значение поля модели со значением константы. Но что, если вы хотите сравнить значение поля модели с другим полем в той же модели?
Django предоставляет F expressions для таких сравнений. Экземпляры F() действуют как ссылка на поле модели в запросе. Эти ссылки могут затем использоваться в фильтрах запросов для сравнения значений двух различных полей одного и того же экземпляра модели.
Например, чтобы найти список всех записей блога, у которых было больше комментариев, чем пинбеков, мы создаём объект 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.
Добавлена поддержка .bitxor().
Сокращение записи по первичному ключу
Для удобства 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)
Записи по первичному ключу также работают через соединения. Например, эти три утверждения эквивалентны:
>>> Entry.objects.filter(blog__id__exact=3) # Explicit form >>> Entry.objects.filter(blog__id=3) # __exact is implied >>> Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
Экранирование знаков процента и нижних подчеркиваний в операциях LIKE
Полевые запросы, которые эквивалентны операциям SQL LIKE (iexact, contains, icontains, startswith, istartswith, endswith и iendswith) будут автоматически экранировать два специальных символа, используемых в операциях LIKE — знак процента и нижнее подчеркивание. (В операциях LIKE, знак процента обозначает подстановочный знак для нескольких символов, а нижнее подчеркивание — для одного символа.)
Это означает, что всё должно работать интуитивно, поэтому абстракция не нарушается. Например, чтобы получить все записи, которые содержат знак процента, используйте знак процента как любой другой символ:
>>> Entry.objects.filter(headline__contains='%')
Django позаботится о цитировании за вас; результирующий SQL будет выглядеть примерно так:
SELECT ... WHERE headline LIKE '%\%%';
То же самое относится и к нижним подчеркиваниям. Знаки процента и нижние подчеркивания обрабатываются прозрачно.
Кэширование и QuerySet
Каждый QuerySet содержит кэш для минимизации доступа к базе данных. Понимание, как это работает, позволит вам писать наиболее эффективный код.
В только что созданном QuerySet кэш пуст. В первый раз, когда QuerySet оценивается — и, следовательно, происходит запрос к базе данных — Django сохраняет результаты запроса в кэше QuerySet и возвращает результаты, которые были явно запрошены (например, следующий элемент, если QuerySet итерируется). Последующие оценки QuerySet повторно используют кэшированные результаты.
Помните об этом поведении кэширования, потому что оно может укусить вас, если вы не используете QuerySet правильно. Например, следующее создаст два QuerySet, оценит их и выбросит их:
>>> print([e.headline for e in Entry.objects.all()]) >>> print([e.pub_date for e in Entry.objects.all()])
Это означает, что один и тот же запрос к базе данных будет выполнен дважды, эффективно удваивая нагрузку на базу данных. Кроме того, существует вероятность, что два списка могут не содержать одни и те же записи базы данных, потому что Entry могла быть добавлена или удалена в долю секунды между двумя запросами.
Чтобы избежать этой проблемы, сохраните QuerySet и используйте его повторно:
>>> queryset = Entry.objects.all() >>> print([p.headline for p in queryset]) # Evaluate the query set. >>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
Когда QuerySet не кэшируются
Querysets не всегда кэшируют свои результаты. При оценке только части queryset, кэш проверяется, но если он не заполнен, то элементы, возвращаемые последующим запросом, не кэшируются. В частности, это означает, что ограничение queryset с помощью среза массива или индекса не заполнит кэш.
Например, многократное получение определённого индекса в объекте queryset будет запрашивать базу данных каждый раз:
>>> queryset = Entry.objects.all() >>> print(queryset[5]) # Queries the database >>> print(queryset[5]) # Queries the database again
Однако, если весь queryset уже был оценён, вместо этого будет проверен кэш:
>>> queryset = Entry.objects.all() >>> [entry for entry in queryset] # Queries the database >>> print(queryset[5]) # Uses cache >>> print(queryset[5]) # Uses cache
Вот некоторые примеры других действий, которые приведут к оценке всего queryset и, следовательно, к заполнению кэша:
>>> [entry for entry in queryset] >>> bool(queryset) >>> entry in queryset >>> list(queryset)
Примечание
Простое отображение queryset не заполнит кэш. Это связано с тем, что вызов __repr__() возвращает только срез всего queryset.
Запрос к 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('null').
В любом случае, когда значение извлекается из базы данных, представление JSON-скаляра null в Python такое же, как и 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('null')) # JSON null.
<Dog: Archie>
>>> Dog.objects.filter(data=None)
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data=Value('null'))
<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.
Преобразования ключа, индекса и пути
Чтобы выполнить запрос по заданному ключу словаря, используйте этот ключ в качестве имени поиска:
>>> 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, а также с Поисками вхождения и ключей.
Примечание
Из-за того, как работают запросы по пути к ключу, exclude() и filter() не гарантируют получения полных наборов. Если вы хотите включить объекты, у которых нет пути, добавьте поиск isnull.
Предупреждение
Так как любой строковый ключ может быть ключом в объекте JSON, любой поиск, кроме перечисленных ниже, будет интерпретирован как поиск по ключу. Ошибки не генерируются. Будьте предельно внимательны к ошибкам ввода и всегда проверяйте, что ваши запросы работают так, как вы намеревались.
Пользователи MariaDB и Oracle
Использование order_by() с преобразованиями ключа, индекса или пути приведет к сортировке объектов с использованием строкового представления значений. Это происходит потому, что MariaDB и Oracle Database не предоставляют функцию, которая преобразует JSON-значения в их эквиваленты SQL.
Пользователи Oracle
В запросе Oracle Database с использованием None в качестве значения поиска в exclude() будут возвращены объекты, которые не имеют null в качестве значения по заданному пути, включая объекты, у которых нет этого пути. В других базах данных запрос вернет объекты, у которых есть путь, и значение не равно null.
Пользователи PostgreSQL
В PostgreSQL, если используется только один ключ или индекс, используется оператор SQL -> . Если используются несколько операторов, то используется оператор #>.
Поиски вхождения и ключей
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))
)
… не будет допустимым.
См. также
Примеры OR lookups в тестах Django показывают некоторые возможные варианты использования Q.
Сравнение объектов
Для сравнения двух экземпляров модели используйте стандартный оператор сравнения Python, двойной знак равенства: ==. Под капотом это сравнивает значения первичных ключей двух моделей.
Используя пример Entry выше, следующие два утверждения эквивалентны:
>>> some_entry == other_entry >>> some_entry.id == other_entry.id
Если первичный ключ модели не называется id, это не проблема. Сравнения всегда будут использовать первичный ключ, как бы он ни назывался. Например, если поле первичного ключа модели называется name, эти два утверждения эквивалентны:
>>> some_obj == other_obj >>> some_obj.name == other_obj.name
Удаление объектов
Метод delete, удобно, называется 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.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() в update – вы можете ссылаться только на поля, локальные для обновляемой модели. Если вы попытаетесь ввести соединение с объектом F(), будет поднято исключение FieldError:
# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))
Связанные объекты
Используя модели в начале этой страницы, например, объект Entry e может получить связанный объект Blog с помощью атрибута blog: e.blog.
(Под капотом эта функциональность реализуется с помощью Python дескрипторов. Это на самом деле не должно быть важно для вас, но мы указываем это здесь для любопытных.)
END_OF_DOCUMENT_MARKERDjango также создает API-доступы к объектам для «другой» стороны отношения — связи от связанной модели к модели, которая определяет отношение. Например, объект Blog b имеет доступ к списку всех связанных объектов Entry через атрибут entry_set: b.entry_set.all().
Все примеры в этом разделе используют примерные модели Blog, Author и Entry, определённые в начале этой страницы.
Одно-ко-многим отношения
Прямое
Если у модели есть ForeignKey, экземпляры этой модели будут иметь доступ к связанному (внешнему) объекту через атрибут модели.
Пример:
>>> e = Entry.objects.get(id=2) >>> e.blog # Returns the related Blog object.
Вы можете получить и установить значение через атрибут внешнего ключа. Как можно ожидать, изменения внешнего ключа не сохраняются в базе данных до тех пор, пока вы не вызовете save(). Пример:
>>> e = Entry.objects.get(id=2) >>> e.blog = some_blog >>> e.save()
Если поле ForeignKey имеет null=True установленным (т.е., допускает NULL значения), вы можете присвоить None для удаления отношения. Пример:
>>> e = Entry.objects.get(id=2) >>> e.blog = None >>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
Прямой доступ к отношениям «один-ко-многим» кэшируется при первом доступе к связанному объекту. Последующие обращения к внешнему ключу на том же экземпляре объекта кэшируются. Пример:
>>> e = Entry.objects.get(id=2) >>> print(e.blog) # Hits the database to retrieve the associated Blog. >>> print(e.blog) # Doesn't hit the database; uses cached version.
Обратите внимание, что метод select_related() набора запросов QuerySet рекурсивно предварительно заполняет кэш всех отношений «один-ко-многим» заранее. Пример:
>>> e = Entry.objects.select_related().get(id=2) >>> print(e.blog) # Doesn't hit the database; uses cached version. >>> print(e.blog) # Doesn't hit the database; uses cached version.
Следование отношениям «обратно»
Если у модели есть ForeignKey, экземпляры модели внешнего ключа будут иметь доступ к менеджеру Manager, который возвращает все экземпляры первой модели. По умолчанию этот менеджер Manager называется FOO_set, где FOO — это имя исходной модели, приведённое к нижнему регистру. Этот менеджер Manager возвращает QuerySets, которое можно фильтровать и обрабатывать, как описано в разделе «Получение объектов» выше.
Пример:
>>> b = Blog.objects.get(id=1) >>> b.entry_set.all() # Returns all Entry objects related to Blog. # b.entry_set is a Manager that returns QuerySets. >>> b.entry_set.filter(headline__contains='Lennon') >>> b.entry_set.count()
Вы можете переопределить имя FOO_set путём установки параметра related_name в определении ForeignKey. Например, если модель Entry была изменена на blog = ForeignKey(Blog, on_delete=models.CASCADE,
related_name='entries'), код примера будет выглядеть так:
>>> b = Blog.objects.get(id=1) >>> b.entries.all() # Returns all Entry objects related to Blog. # b.entries is a Manager that returns QuerySets. >>> b.entries.filter(headline__contains='Lennon') >>> b.entries.count()
Использование пользовательского обратного менеджера
По умолчанию RelatedManager, используемый для обратных отношений, является подклассом менеджера по умолчанию для данной модели. Если вам нужно указать другой менеджер для данного запроса, вы можете использовать следующий синтаксис:
from django.db import models
class Entry(models.Model):
#...
objects = models.Manager() # Default Manager
entries = EntryManager() # Custom Manager
b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()
Если EntryManager выполнял фильтрацию по умолчанию в своём методе get_queryset(), эта фильтрация применялась бы к вызову all().
Указание пользовательского обратного менеджера также позволяет вызывать его пользовательские методы:
b.entry_set(manager='entries').is_published()
Дополнительные методы для обработки связанных объектов
-
add(obj1, obj2, ...) - Добавляет указанные объекты модели в набор связанных объектов.
-
create(**kwargs) - Создаёт новый объект, сохраняет его и помещает в набор связанных объектов. Возвращает только что созданный объект.
-
remove(obj1, obj2, ...) - Удаляет указанные объекты модели из набора связанных объектов.
-
clear() - Удаляет все объекты из набора связанных объектов.
-
set(objs) - Заменяет набор связанных объектов.
Для присвоения членов связанного набора используйте метод set() с итерируемым объектом экземпляров. Например, если e1 и e2 являются экземплярами Entry:
b = Blog.objects.get(id=1) b.entry_set.set([e1, e2])
Если метод clear() доступен, все ранее существовавшие объекты будут удалены из entry_set перед добавлением всех объектов в итерируемый набор (в данном случае — список) в набор. Если метод clear() не доступен, все объекты в итерируемом наборе будут добавлены без удаления каких-либо существующих элементов.
Каждое «обратное» действие, описанное в этом разделе, имеет непосредственное влияние на базу данных. Каждое добавление, создание и удаление немедленно и автоматически сохраняются в базе данных.
Множество-ко-множеству отношений
Обе стороны отношения «множество-ко-множеству» получают автоматический API-доступ к другой стороне. API работает аналогично отношению «обратному» «один-ко-многим», описанному выше.
Различие заключается в именовании атрибутов: модель, которая определяет ManyToManyField, использует имя атрибута этого поля, а «обратная» модель использует имя исходной модели в нижнем регистре, плюс '_set' (как и при обратных отношениях «один-ко-многим»).
Пример поможет это понять:
e = Entry.objects.get(id=3) e.authors.all() # Returns all Author objects for this Entry. e.authors.count() e.authors.filter(name__contains='John') a = Author.objects.get(id=5) a.entry_set.all() # Returns all Entry objects for this Author.
Как и ForeignKey, ManyToManyField может указывать related_name. В примере выше, если ManyToManyField в Entry указал related_name='entries', каждый экземпляр Author будет иметь атрибут entries вместо entry_set.
Другое различие от отношений «один-ко-многим» заключается в том, что помимо экземпляров модели, методы add(), set(), и remove() отношений «множество-ко-множеству» принимают значения первичных ключей. Например, если e1 и e2 являются экземплярами Entry , следующие вызовы set() работают идентично:
a = Author.objects.get(id=5) a.entry_set.set([e1, e2]) a.entry_set.set([e1.pk, e2.pk])
Одно-к-одному отношения
Отношения «один-к-одному» очень похожи на отношения «один-ко-многим». Если вы определите OneToOneField в вашей модели, экземпляры этой модели получат доступ к связанному объекту через атрибут модели.
Например:
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
Различие заключается в обратных запросах. Связанная модель в отношении «один-к-одному» также имеет доступ к объекту менеджера Manager, но этот менеджер Manager представляет собой один объект, а не коллекцию объектов:
e = Entry.objects.get(id=2) e.entrydetail # returns the related EntryDetail object
Если ни одному объекту не было назначено это отношение, Django вызовет исключение DoesNotExist.
Экземпляры могут быть назначены обратным отношениям так же, как вы назначаете прямое отношение:
e.entrydetail = ed
Как работают обратные отношения?
Другие объектно-реляционные отображения требуют определения отношений с обеих сторон. Разработчики Django считают, что это нарушает принцип DRY (Не повторяйся), поэтому Django требует определения отношений только с одной стороны.
Но как это возможно, учитывая, что класс модели не знает, какие другие классы моделей связаны с ним, пока эти другие классы моделей не будут загружены?
END_OF_DOCUMENT_MARKERОтвет кроется в 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/3.1/topics/db/queries/