Создание запросов
После создания ваших моделей данных, 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. В этом случае ожидается, что параметр 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 (Blog к Entry — это отношение один ко многим). Мы можем заинтересоваться в поиске блогов, которые содержат записи, у которых в заголовке есть «Lennon» и которые были опубликованы в 2008 году. Или мы можем найти блоги, которые содержат запись с «Lennon» в заголовке, а также запись, опубликованную в 2008 году. Поскольку с одним Blog связаны несколько записей, оба этих запроса возможны и имеют смысл в некоторых ситуациях.
Такая же ситуация возникает с ManyToManyField. Например, если у Entry есть ManyToManyField с именем tags, мы можем захотеть найти записи, связанные с тегами «music» и «bands», или мы можем захотеть запись, содержащую тег с именем «music» и статусом «public».
Чтобы справиться с обеими этими ситуациями, Django имеет согласованный способ обработки вызовов filter(). Все внутри одного вызова filter() применяется одновременно для фильтрации элементов, соответствующих всем этим требованиям. Последующие вызовы filter() дополнительно сужают набор объектов, но для многозначных связей они применяются ко всем связанным объектам основной модели, а не обязательно к тем объектам, которые были выбраны предыдущим вызовом filter().
Это может показаться немного запутанным, поэтому, надеюсь, пример прояснит ситуацию. Чтобы выбрать все блоги, которые содержат записи с «Lennon» в заголовке и которые были опубликованы в 2008 году (одна и та же запись удовлетворяет обоим условиям), мы напишем:
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
Чтобы выбрать все блоги, которые содержат запись с «Lennon» в заголовке и запись, опубликованную в 2008 году, мы напишем:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
Предположим, что есть только один блог, который содержит обе записи, содержащие «Lennon», и записи от 2008 года, но ни одна из записей от 2008 года не содержит «Lennon». Первый запрос не вернёт никаких блогов, а второй вернёт тот единственный блог.
Во втором примере первый фильтр сужает набор запросов до всех тех блогов, которые связаны с записями, содержащими «Lennon» в заголовке. Второй фильтр ещё больше сужает набор блогов до тех, которые также связаны с записями, опубликованными в 2008 году. Записи, выбранные вторым фильтром, могут или не могут быть теми же, что и в первом фильтре. Мы фильтруем Blog элементы с каждой инструкцией фильтра, а не Entry элементы.
Примечание
Поведение filter() для запросов, охватывающих многозначные отношения, как описано выше, не реализовано аналогичным образом для exclude(). Вместо этого условия в одном вызове exclude() необязательно будут ссылаться на один и тот же элемент.
Например, следующий запрос исключит блоги, содержащие оба записи с «Lennon» в заголовке и записи, опубликованные в 2008 году:
Blog.objects.exclude(
entry__headline__contains='Lennon',
entry__pub_date__year=2008,
)
Однако, в отличие от поведения при использовании filter(), это не будет ограничивать блоги на основе записей, удовлетворяющих обоим условиям. Для этого, то есть для выбора всех блогов, не содержащих записей, опубликованных с «Lennon», которые были опубликованы в 2008 году, необходимо выполнить два запроса:
Blog.objects.exclude(
entry__in=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
Фильтры могут ссылаться на поля модели
В приведенных ранее примерах мы создали фильтры, которые сравнивают значение поля модели со значением константы. Но что, если вы хотите сравнить значение поля модели с другим полем в той же модели?
Django предоставляет F expressions для таких сравнений. Экземпляры F() действуют как ссылка на поле модели в запросе. Эти ссылки затем могут использоваться в фильтрах запросов для сравнения значений двух разных полей одного и того же экземпляра модели.
Например, чтобы найти список всех записей блога, у которых было больше комментариев, чем пингбеков, мы создаём объект F() для ссылки на счёт пингбеков и используем этот объект F() в запросе:
>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))
Django поддерживает использование арифметических операций сложения, вычитания, умножения, деления, остатка от деления и возведения в степень с объектами F() как с константами, так и с другими объектами F(). Чтобы найти все записи блога с более чем в два раза большим количеством комментариев, чем пингбеков, мы модифицируем запрос:
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)
Чтобы найти все записи, где рейтинг записи меньше суммы подсчёта пингбеков и комментариев, мы выполним запрос:
>>> Entry.objects.filter(rating__lt=F('number_of_comments') + F('number_of_pingbacks'))
Вы также можете использовать нотацию с двойным подчёркиванием для перехода по отношениям в объекте F() . Объект F() с двойным подчёркиванием добавит необходимые соединения для доступа к связанному объекту. Например, чтобы получить все записи, где имя автора совпадает с именем блога, мы могли бы выполнить запрос:
>>> Entry.objects.filter(authors__name=F('blog__name'))
Для полей даты и даты/времени вы можете добавить или вычесть объект timedelta. Следующее вернёт все записи, которые были изменены более чем через 3 дня после их публикации:
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
Объекты F() поддерживают побитовые операции с помощью .bitand(), .bitor(), .bitxor(), .bitrightshift(), и .bitleftshift(). Например:
>>> F('somefield').bitand(16)
Oracle
Oracle не поддерживает побитовую операцию XOR.
Была добавлена поддержка .bitxor().
Выражения могут ссылаться на преобразования
Django поддерживает использование преобразований в выражениях.
Например, чтобы найти все Entry объекты, опубликованные в том же году, что и их последнее изменение:
>>> Entry.objects.filter(pub_date__year=F('mod_date__year'))
Чтобы найти самый ранний год публикации записи, мы можем выполнить запрос:
>>> Entry.objects.aggregate(first_published_year=Min('pub_date__year'))
Этот пример находит значение записи с наивысшим рейтингом и общее количество комментариев ко всем записям для каждого года:
>>> 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 предоставляет сокращение поиска по первичному ключу, которое обозначает «первичный ключ».
В примере модели Blog первичный ключ — это поле id , поэтому эти три утверждения эквивалентны:
>>> Blog.objects.get(id__exact=14) # Explicit form >>> Blog.objects.get(id=14) # __exact is implied >>> Blog.objects.get(pk=14) # pk implies id__exact
Использование pk не ограничивается запросами __exact — любой термин запроса может быть объединён с pk для выполнения запроса по первичному ключу модели:
# Get blogs entries with id 1, 4 and 7 >>> Blog.objects.filter(pk__in=[1,4,7]) # Get all blog entries with id > 14 >>> Blog.objects.filter(pk__gt=14)
Поиски pk также работают через соединения. Например, эти три утверждения эквивалентны:
>>> Entry.objects.filter(blog__id__exact=3) # Explicit form >>> Entry.objects.filter(blog__id=3) # __exact is implied >>> Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
Обработка символов процента и подчёркивания в операторах LIKE
Поисковые поля, эквивалентные операторам LIKE SQL (iexact, contains, icontains, startswith, istartswith, endswith и iendswith ), автоматически обрабатывают два специальных символа, используемых в операторах LIKE — знак процента и подчёркивание. (В операторе LIKE, знак процента обозначает многосимвольный подстановочный символ, а подчёркивание — односимвольный подстановочный символ.)
Это означает, что вещи должны работать интуитивно, поэтому абстракция не нарушается. Например, чтобы получить все записи, содержащие знак процента, используйте знак процента как любой другой символ:
>>> Entry.objects.filter(headline__contains='%')
Django обрабатывает цитирование за вас; результирующий SQL будет примерно таким:
SELECT ... WHERE headline LIKE '%\%%';
То же самое касается подчёркиваний. И знак процента, и подчёркивание обрабатываются прозрачно.
Кэширование и QuerySet
Каждый QuerySet содержит кэш для минимизации доступа к базе данных. Понимание того, как это работает, позволит вам писать наиболее эффективный код.
В только что созданном QuerySet кэш пуст. При первом вычислении QuerySet (и, следовательно, происходит запрос к базе данных) Django сохраняет результаты запроса в кэше QuerySet и возвращает те результаты, которые были явно запрошены (например, следующий элемент, если QuerySet перебирается). При последующих вычислениях QuerySet используются кэшированные результаты.
Помните об этом поведении кэширования, так как оно может повлиять на вас, если вы неправильно используете свои QuerySet. Например, следующее создаст два QuerySet, вычислит их и удалит:
>>> print([e.headline for e in Entry.objects.all()]) >>> print([e.pub_date for e in Entry.objects.all()])
Это означает, что один и тот же запрос к базе данных будет выполнен дважды, что фактически удвоит нагрузку на вашу базу данных. Кроме того, существует вероятность, что два списка могут не содержать одни и те же записи в базе данных, так как Entry могли быть добавлены или удалены за доли секунды между двумя запросами.
Чтобы избежать этой проблемы, сохраните QuerySet и повторно используйте его:
>>> queryset = Entry.objects.all() >>> print([p.headline for p in queryset]) # Evaluate the query set. >>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
Когда QuerySet не кэшируются
Множества запросов не всегда кэшируют свои результаты. При вычислении только части множества запросов кэш проверяется, но если он не заполнен, то элементы, возвращаемые последующим запросом, не кэшируются. В частности, это означает, что ограничение множества запросов с помощью срезов массивов или индексов не заполнит кэш.
Например, многократный запрос определённого индекса в объекте множества запросов будет каждый раз обращаться к базе данных:
>>> 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__() возвращает только фрагмент всего множества запросов.
Запрос к 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').
Независимо от того, какое из значений сохранено, при извлечении из базы данных, представление 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('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))
)
… не будет допустимым.
См. также
Примеры использования Q в тестах Django находятся по адресу OR lookups examples.
Сравнение объектов
Для сравнения двух экземпляров модели используйте стандартный оператор сравнения Python: двойное равно: ==. За кулисами сравниваются значения первичных ключей двух моделей.
Используя пример Entry выше, следующие два утверждения эквивалентны:
>>> some_entry == other_entry >>> some_entry.id == other_entry.id
Если первичный ключ модели называется не id, это не проблема. Сравнения всегда используют первичный ключ, как бы он ни назывался. Например, если поле первичного ключа модели называется name, эти два утверждения эквивалентны:
>>> some_obj == other_obj >>> some_obj.name == other_obj.name
Удаление объектов
Метод удаления, удобно, называется delete(). Этот метод немедленно удаляет объект и возвращает количество удаленных объектов и словарь с количеством удалений по типу объекта. Пример:
>>> e.delete()
(1, {'weblog.Entry': 1})
Также можно удалять объекты группами. У каждого QuerySet есть метод delete(), который удаляет всех членов этого QuerySet.
Например, это удаляет все объекты Entry с годом pub_date 2005:
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})
Обратите внимание, что этот запрос, когда это возможно, будет выполняться чисто на уровне SQL, и, следовательно, методы delete() отдельных экземпляров объектов не обязательно будут вызываться в процессе. Если вы определили пользовательский метод delete() в классе модели и хотите убедиться, что он вызывается, вам нужно «вручную» удалить экземпляры этой модели (например, перебирая QuerySet и вызывая delete() для каждого объекта по отдельности), а не использовать метод пакетного удаления delete() набора QuerySet.
При удалении Django объекта по умолчанию он эмулирует поведение SQL-ограничения ON DELETE CASCADE — другими словами, любые объекты, имеющие внешние ключи, указывающие на объект, который должен быть удален, будут удалены вместе с ним. Например:
b = Blog.objects.get(pk=1) # This will delete the Blog and all of its Entry objects. b.delete()
Это поведение каскадного удаления настраивается с помощью аргумента on_delete для ForeignKey.
Обратите внимание, что delete() — единственный метод QuerySet, который не доступен через Manager сам по себе. Это механизм безопасности, чтобы предотвратить случайный запрос Entry.objects.delete(), и удаление всех записей. Если вы действительно хотите удалить все объекты, вам необходимо явно запросить весь набор:
Entry.objects.all().delete()
Копирование экземпляров модели
Хотя встроенного метода для копирования экземпляров моделей нет, легко создать новые экземпляры со всеми значениями полей, скопированными. В простейшем случае вы можете установить pk в None и _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.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() в обновлении — вы можете ссылаться только на поля, локальные для обновляемой модели. Если вы попытаетесь ввести соединение с объектом F() , будет возбуждено исключение FieldError:
# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))
Связанные объекты
Например, используя модели вверху этой страницы, объект Entry e может получить связанный объект Blog с помощью атрибута blog: e.blog.
(За кулисами эта функциональность реализована с помощью Python дескрипторов. Это не должно сильно беспокоить вас, но мы указываем на это для любознательных.)
Django также создаёт API-доступы для «другой» стороны связи — ссылки от связанной модели к модели, определяющей связь. Например, объект Blog b имеет доступ к списку всех связанных объектов Entry через атрибут entry_set: b.entry_set.all().
Все примеры в этом разделе используют примерные модели Blog, Author и Entry, определённые вверху этой страницы.
Связи один-ко-многим
Прямой доступ
Если модель имеет ForeignKey, экземпляры этой модели получат доступ к связанному (внешнему) объекту через атрибут модели.
Пример:
>>> e = Entry.objects.get(id=2) >>> e.blog # Returns the related Blog object.
Вы можете получить и установить значения через атрибут внешнего ключа. Как вы ожидаете, изменения внешнего ключа не сохраняются в базе данных до вызова save(). Пример:
>>> e = Entry.objects.get(id=2) >>> e.blog = some_blog >>> e.save()
Если поле ForeignKey имеет null=True установленным (т. е. позволяет значения NULL), вы можете назначить None для удаления связи. Пример:
>>> e = Entry.objects.get(id=2) >>> e.blog = None >>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
Прямой доступ к связи один-ко-многим кэшируется в первый раз при доступе к связанному объекту. Последующие обращения к внешнему ключу для того же экземпляра объекта кэшируются. Пример:
>>> e = Entry.objects.get(id=2) >>> print(e.blog) # Hits the database to retrieve the associated Blog. >>> print(e.blog) # Doesn't hit the database; uses cached version.
Обратите внимание, что метод select_related() QuerySet рекурсивно предварительно заполняет кэш всех связей один-ко-многим.
>>> e = Entry.objects.select_related().get(id=2) >>> print(e.blog) # Doesn't hit the database; uses cached version. >>> print(e.blog) # Doesn't hit the database; uses cached version.
Обратный доступ к связанным объектам
Если у модели есть ForeignKey, экземпляры модели внешнего ключа получат доступ к Manager, который возвращает все экземпляры первой модели. По умолчанию этот Manager называется FOO_set, где FOO — имя исходной модели в нижнем регистре. Этот Manager возвращает QuerySets, которое можно фильтровать и обрабатывать, как описано в разделе «Получение объектов» выше.
Пример:
>>> b = Blog.objects.get(id=1) >>> b.entry_set.all() # Returns all Entry objects related to Blog. # b.entry_set is a Manager that returns QuerySets. >>> b.entry_set.filter(headline__contains='Lennon') >>> b.entry_set.count()
Вы можете переопределить имя FOO_set, установив параметр related_name в определении ForeignKey. Например, если модель Entry была изменена на blog = ForeignKey(Blog, on_delete=models.CASCADE,
related_name='entries'), код примера выше будет выглядеть так:
>>> b = Blog.objects.get(id=1) >>> b.entries.all() # Returns all Entry objects related to Blog. # b.entries is a Manager that returns QuerySets. >>> b.entries.filter(headline__contains='Lennon') >>> b.entries.count()
Использование пользовательского обратного менеджера
По умолчанию RelatedManager, используемый для обратных связей, является подклассом менеджера по умолчанию для этой модели. Если вы хотите указать другой менеджер для данного запроса, вы можете использовать следующий синтаксис:
from django.db import models
class Entry(models.Model):
#...
objects = models.Manager() # Default Manager
entries = EntryManager() # Custom Manager
b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()
Если EntryManager выполнял фильтрацию по умолчанию в методе get_queryset(), эта фильтрация будет применяться к вызову all().
Указание пользовательского обратного менеджера также позволяет вызывать его пользовательские методы:
b.entry_set(manager='entries').is_published()
Дополнительные методы для обработки связанных объектов
-
add(obj1, obj2, ...) - Добавляет указанные объекты модели в набор связанных объектов.
-
create(**kwargs) - Создаёт новый объект, сохраняет его и помещает в набор связанных объектов. Возвращает созданный объект.
-
remove(obj1, obj2, ...) - Удаляет указанные объекты модели из набора связанных объектов.
-
clear() - Удаляет все объекты из набора связанных объектов.
-
set(objs) - Заменяет набор связанных объектов.
Чтобы назначить элементы связанного набора, используйте метод set() с итерируемым объектом экземпляров. Например, если e1 и e2 — экземпляры Entry:
b = Blog.objects.get(id=1) b.entry_set.set([e1, e2])
Если метод clear() доступен, все существующие объекты будут удалены из entry_set перед добавлением всех объектов в итерируемом объекте (в этом случае, список) в набор. Если метод clear() не доступен, все объекты в итерируемом объекте будут добавлены без удаления каких-либо существующих элементов.
Каждое «обратное» действие, описанное в этом разделе, имеет непосредственное влияние на базу данных. Каждое добавление, создание и удаление немедленно и автоматически сохраняется в базе данных.
Связи многие-ко-многим
Оба конца связи многие-ко-многим получают автоматический доступ к API другого конца. API работает аналогично обратной связи один-ко-многим, описанной выше.
Различие заключается в именовании атрибутов: модель, которая определяет ManyToManyField, использует имя атрибута этого поля, тогда как «обратная» модель использует имя модели исходной модели в нижнем регистре, плюс '_set' (как и при обратных связях один-ко-многим).
Пример поможет понять это:
e = Entry.objects.get(id=3) e.authors.all() # Returns all Author objects for this Entry. e.authors.count() e.authors.filter(name__contains='John') a = Author.objects.get(id=5) a.entry_set.all() # Returns all Entry objects for this Author.
Как и ForeignKey, ManyToManyField может указать related_name. В примере выше, если ManyToManyField в Entry указал related_name='entries', каждый экземпляр Author будет иметь атрибут entries вместо entry_set.
Другое отличие от связей один-ко-многим заключается в том, что помимо экземпляров модели, методы add(), set(), и remove() для связей многие-ко-многим принимают значения первичных ключей. Например, если e1 и e2 являются экземплярами Entry модели, следующие вызовы set() работают идентично:
a = Author.objects.get(id=5) a.entry_set.set([e1, e2]) a.entry_set.set([e1.pk, e2.pk])
Связи один-к-одному
Связи один-к-одному очень похожи на связи один-ко-многим. Если вы определили OneToOneField в своей модели, экземпляры этой модели получат доступ к связанному объекту через атрибут модели.
Например:
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
Различие заключается в обратных запросах. Связанная модель в связи один-к-одному также имеет доступ к объекту Manager, но этот Manager представляет один объект, а не коллекцию объектов:
e = Entry.objects.get(id=2) e.entrydetail # returns the related EntryDetail object
Если объекту не был назначен объект в этом отношении, Django выведет исключение DoesNotExist.
Экземпляры могут быть назначены обратной связи так же, как и прямая связь:
e.entrydetail = ed
Как возможны обратные связи?
Другие объектно-реляционные мапперы требуют определения отношений с обеих сторон. Разработчики Django считают, что это нарушение принципа DRY (Не повторяйся), поэтому 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/3.2/topics/db/queries/