Spec-Zone.ru › Django 4.0

Создание запросов

После создания ваших моделей данных, Django автоматически предоставляет вам API абстракции базы данных, который позволяет создавать, получать, обновлять и удалять объекты. В данном документе объясняется, как использовать этот API. Обратитесь к справочнику по модели данных для получения подробных сведений обо всех различных параметрах поиска моделей.

В этом руководстве (и в справке) мы будем ссылаться на следующие модели, которые составляют приложение блога:

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() необязательно будут относиться к одному и тому же элементу.

Например, следующий запрос исключил бы блоги, содержащие оба записи с «Ленноном» в заголовке и записи, опубликованные в 2008 году:

Blog.objects.exclude(
    entry__headline__contains='Lennon',
    entry__pub_date__year=2008,
)

Однако, в отличие от поведения при использовании filter(), это не будет ограничивать блоги на основе записей, удовлетворяющих обоим условиям. Для этого, т. е. для выбора всех блогов, не содержащих записей, опубликованных с «Ленноном», которые были опубликованы в 2008 году, необходимо выполнить два запроса:

Blog.objects.exclude(
    entry__in=Entry.objects.filter(
        headline__contains='Lennon',
        pub_date__year=2008,
    ),
)

Фильтры могут ссылаться на поля модели

В приведенных ранее примерах мы создавали фильтры, сравнивающие значение поля модели с константой. Но что, если вам нужно сравнить значение поля модели с другим полем той же модели?

Django предоставляет F expressions для таких сравнений. Экземпляры F() действуют как ссылка на поле модели в запросе. Затем эти ссылки могут использоваться в фильтрах запросов для сравнения значений двух разных полей одного экземпляра модели.

Например, чтобы найти список всех записей блога, у которых было больше комментариев, чем обращений, мы создаем объект F() для ссылки на количество обращений и используем этот объект F() в запросе:

>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))

Django поддерживает использование арифметических операций сложения, вычитания, умножения, деления, остатка от деления и возведения в степень с объектами F() , как с константами, так и с другими объектами F() . Чтобы найти все записи блога с более чем в два раза большим количеством комментариев, чем обращений, мы изменяем запрос:

>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)

Чтобы найти все записи, где рейтинг записи меньше суммы количества обращений и комментариев, мы выполним запрос:

>>> Entry.objects.filter(rating__lt=F('number_of_comments') + F('number_of_pingbacks'))

Вы также можете использовать обозначение с двумя подчеркиваниями для перехода по отношениям в объекте F() . Объект F() с двумя подчеркиваниями добавит необходимые соединения для доступа к связанному объекту. Например, чтобы получить все записи, где имя автора совпадает с именем блога, мы можем выполнить запрос:

>>> Entry.objects.filter(authors__name=F('blog__name'))

Для полей даты и даты/времени вы можете добавить или вычесть объект timedelta. Следующее вернет все записи, измененные более чем через 3 дня после их публикации:

>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

Объекты F() поддерживают побитовые операции с помощью .bitand(), .bitor(), .bitxor(), .bitrightshift(), и .bitleftshift(). Например:

>>> F('somefield').bitand(16)

Oracle

Oracle не поддерживает операцию побитового XOR.

Выражения могут ссылаться на преобразования

Новое в Django 3.2.

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'),
... )

Явное указание на pk

Для удобства Django предоставляет pk - ярлык поиска «первичный ключ».

В примере Blog модели первичный ключ - поле id , поэтому эти три утверждения эквивалентны:

>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # pk implies id__exact

Использование pk не ограничено запросами __exact – любой термин запроса можно комбинировать с pk для выполнения запроса по первичному ключу модели:

# Get blogs entries with id 1, 4 and 7
>>> Blog.objects.filter(pk__in=[1,4,7])

# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)

pk запросы также работают через соединения. Например, эти три утверждения эквивалентны:

>>> Entry.objects.filter(blog__id__exact=3) # Explicit form
>>> Entry.objects.filter(blog__id=3)        # __exact is implied
>>> Entry.objects.filter(blog__pk=3)        # __pk implies __id__exact

Экранирование знаков процента и подчеркиваний в LIKE выражениях

Поля поиска, эквивалентные LIKE SQL-выражениям (iexact, contains, icontains, startswith, istartswith, endswith и iendswith), автоматически экранируют два специальных символа, используемых в LIKE выражениях - знак процента и знак подчеркивания. (В LIKE выражении знак процента означает символ подстановки нескольких символов, а знак подчеркивания - символ подстановки одного символа.)

Это означает, что все должно работать интуитивно, поэтому абстракция не просачивается. Например, чтобы получить все записи, содержащие знак процента, используйте знак процента как любой другой символ:

>>> Entry.objects.filter(headline__contains='%')

Django позаботится о цитировании за вас; результирующий SQL будет выглядеть примерно так:

SELECT ... WHERE headline LIKE '%\%%';

То же самое касается подчеркиваний. Знаки процента и подчеркивания обрабатываются прозрачно.

Кэширование и QuerySet

Каждый QuerySet содержит кэш для минимизации доступа к базе данных. Понимание того, как он работает, позволит вам писать наиболее эффективный код.

В только что созданном QuerySet кэш пуст. В первый раз, когда QuerySet оценивается — и, следовательно, происходит запрос к базе данных — Django сохраняет результаты запроса в кэше QuerySet и возвращает результаты, которые были явно запрошены (например, следующий элемент, если QuerySet итерируется). При последующей оценке QuerySet повторно используются кэшированные результаты.

Помните об этом поведении кэширования, так как оно может запутать, если вы не используете QuerySet правильно. Например, следующее создаст два QuerySet, оценит их и выбросит их:

>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])

Это означает, что один и тот же запрос к базе данных будет выполнен дважды, фактически удвоив нагрузку на базу данных. Кроме того, есть вероятность, что два списка не будут включать одни и те же записи базы данных, так как запись Entry могла быть добавлена или удалена в доли секунды между двумя запросами.

Чтобы избежать этой проблемы, сохраните QuerySet и используйте его повторно:

>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.

Когда QuerySet не кэшируются

Querysets не всегда кэшируют свои результаты. При оценке только части набора результатов, кэш проверяется, но если он не заполнен, то элементы, возвращаемые последующим запросом, не кэшируются. В частности, это означает, что ограничение набора результатов с помощью срезов массива или индексов не заполнит кэш.

Например, повторное получение определенного индекса в объекте набора результатов будет запрашивать базу данных каждый раз:

>>> queryset = Entry.objects.all()
>>> print(queryset[5]) # Queries the database
>>> print(queryset[5]) # Queries the database again

Однако, если весь набор результатов уже был оценен, вместо этого будет проверено наличие кэша:

>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print(queryset[5]) # Uses cache
>>> print(queryset[5]) # Uses cache

Ниже приведены примеры других действий, которые приведут к оценке всего набора результатов и, следовательно, к заполнению кэша:

>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)

Примечание

Простое отображение набора результатов не заполнит кэш. Это связано с тем, что вызов __repr__() возвращает только часть всего набора результатов.

Запрос к 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 в качестве значения поля будет сохранять его как NULL SQL. Хотя это не рекомендуется, возможно хранить скалярное значение JSON null вместо NULL SQL с помощью 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 object.

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 показаны в примерах тестов.

Сравнение объектов

Для сравнения двух экземпляров модели используйте стандартный оператор сравнения 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, {'blog.Entry': 1})

Вы также можете удалять объекты по частям. Каждый QuerySet имеет метод delete(), который удаляет все члены этого QuerySet.

Например, это удаляет все Entry объекты с pub_date годом 2005:

>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

Помните, что это, по возможности, будет выполняться исключительно в SQL, и поэтому методы delete() отдельных экземпляров объектов не обязательно будут вызваны в процессе. Если вы предоставили пользовательский метод delete() в классе модели и хотите гарантировать, что он будет вызван, вам необходимо «вручную» удалить экземпляры этой модели (например, перебирая QuerySet и вызывая delete() для каждого объекта по отдельности), а не используя метод масс-удаления delete() на QuerySet.

При удалении Django объекта, по умолчанию он эмулирует поведение SQL ограничения ON DELETE CASCADE — другими словами, любые объекты, имеющие внешние ключи, указывающие на удаляемый объект, будут удалены вместе с ним. Например:

b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

Это поведение каскадного удаления настраивается с помощью аргумента on_delete для ForeignKey.

Обратите внимание, что delete() — единственный метод QuerySet, который не экспонируется на Manager самом по себе. Это механизм безопасности, чтобы предотвратить случайный запрос Entry.objects.delete(), и удаление всех записей. Если вы действительно хотите удалить все объекты, то вы должны явно запросить полный набор объектов:

Entry.objects.all().delete()

Копирование экземпляров модели

Хотя нет встроенного метода для копирования экземпляров модели, можно легко создать новый экземпляр с копированием значений всех полей. В самом простом случае вы можете установить pk в None и _state.adding в True. Используя наш пример блога:

blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1

blog.pk = None
blog._state.adding = True
blog.save() # blog.pk == 2

Вещи усложняются, если вы используете наследование. Рассмотрим подкласс Blog:

class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)

django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3

Из-за того, как работает наследование, вы должны установить pk и id в None, а _state.adding в True:

django_blog.pk = None
django_blog.id = None
django_blog._state.adding = True
django_blog.save() # django_blog.pk == 4

Этот процесс не копирует отношения, которые не являются частью таблицы базы данных модели. Например, Entry имеет отношение ManyToManyField к Author. После дублирования записи необходимо установить отношения «многие ко многим» для новой записи:

entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry._state.adding = True
entry.save()
entry.authors.set(old_authors)

Для OneToOneField, вы должны дублировать связанный объект и назначить его полю нового объекта, чтобы избежать нарушения ограничения уникальности «один к одному». Например, предположим, что entry уже дублирован, как указано выше:

detail = EntryDetail.objects.all()[0]
detail.pk = None
detail._state.adding = True
detail.entry = entry
detail.save()

Обновление нескольких объектов одновременно

Иногда вы хотите установить поле в определенное значение для всех объектов в QuerySet. Это можно сделать с помощью метода update(). Например:

# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

С помощью этого метода можно устанавливать только поля, не связанные с отношениями, и поля ForeignKey. Для обновления поля, не связанного с отношениями, укажите новое значение как константу. Для обновления полей ForeignKey задайте новое значение как новый экземпляр модели, к которому нужно установить указание.

>>> b = Blog.objects.get(pk=1)

# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.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()

Вызовы обновления также могут использовать F expressions для обновления одного поля на основе значения другого поля в модели. Это особенно полезно для инкремента счетчиков на основе их текущего значения. Например, чтобы увеличить счетчик обращений к странице для каждой записи в блоге:

>>> Entry.objects.all().update(number_of_pingbacks=F('number_of_pingbacks') + 1)

Однако, в отличие от объектов F() в фильтрах и исключениях, вы не можете вводить соединения при использовании объектов F() в обновлении — вы можете только ссылаться на поля, локальные для обновляемой модели. Если вы попытаетесь ввести соединение с объектом F() , будет поднято исключение FieldError:

# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))

Связанные объекты

Когда вы определяете отношение в модели (т.е., ForeignKey, OneToOneField или ManyToManyField), экземпляры этой модели будут иметь удобный API для доступа к связанному(ым) объекту(ам).

Используя модели в верхней части этой страницы, например, объект 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()

Дополнительные методы для обработки связанных объектов

В дополнение к методам QuerySet, определённым в разделе «Получение объектов» выше, ForeignKey Manager имеет дополнительные методы для обработки набора связанных объектов. Краткое описание каждого приведено ниже, а полные сведения можно найти в справочнике по связанным объектам.

add(obj1, obj2, ...)
Добавляет указанные объекты модели в набор связанных объектов.
create(**kwargs)
Создаёт новый объект, сохраняет его и помещает в набор связанных объектов. Возвращает только что созданный объект.
remove(obj1, obj2, ...)
Удаляет указанные объекты модели из набора связанных объектов.
clear()
Удаляет все объекты из набора связанных объектов.
set(objs)
Заменяет набор связанных объектов.

Для присвоения элементов связанного набора используйте метод set() с итерируемым объектом экземпляров. Например, если e1 и e2 — экземпляры Entry:

b = Blog.objects.get(id=1)
b.entry_set.set([e1, e2])

Если метод clear() доступен, все существующие объекты будут удалены из entry_set , прежде чем все объекты в итерируемом объекте (в данном случае, списке) будут добавлены в набор. Если метод clear() не доступен, все объекты в итерируемом объекте будут добавлены без удаления существующих элементов.

Каждое «обратное» действие, описанное в этом разделе, имеет непосредственное влияние на базу данных. Каждое добавление, создание и удаление немедленно и автоматически сохраняется в базе данных.

Отношения многие ко многим

Обе стороны отношения многие ко многим получают автоматический API-доступ к другой стороне. API работает аналогично отношению «обратно» один ко многим, как описано выше.

Различие заключается в именовании атрибутов: Модель, которая определяет ManyToManyField, использует само имя атрибута этого поля, в то время как «обратная» модель использует имя исходной модели в нижнем регистре плюс '_set' (как и обратные отношения один ко многим).

Пример прояснит это:

e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')

a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.

Как и ForeignKey, ManyToManyField может указать related_name. В приведённом выше примере, если ManyToManyField в Entry указал related_name='entries', каждый экземпляр Author имел бы атрибут entries вместо entry_set.

Ещё одно различие от отношений один ко многим состоит в том, что, помимо экземпляров модели, методы add(), set(), и remove() отношений многие ко многим принимают значения первичных ключей. Например, если e1 и e2 — экземпляры Entry , вызовы set() работают идентично:

a = Author.objects.get(id=5)
a.entry_set.set([e1, e2])
a.entry_set.set([e1.pk, e2.pk])

Отношения один к одному

Отношения один к одному очень похожи на отношения один ко многим. Если вы определите OneToOneField в своей модели, экземпляры этой модели получат доступ к связанному объекту через атрибут модели.

Например:

class EntryDetail(models.Model):
    entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
    details = models.TextField()

ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.

Различие заключается в обратных запросах. Связанная модель в отношении один к одному также имеет доступ к объекту Manager, но этот Manager представляет собой один объект, а не коллекцию объектов:

e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object

Если объекту не присвоен объект для этого отношения, Django поднимет исключение DoesNotExist.

Экземпляры могут быть назначены к обратному отношению аналогичным образом, как вы назначаете прямое отношение:

e.entrydetail = ed

Как работают обратные отношения?

Другие ORM требуют определения отношений с обеих сторон. Разработчики 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/4.0/topics/db/queries/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API