Spec-Zone.ru › Django 5.1

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

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

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

from datetime import date

from django.db import models


class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):
        return self.name


class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):
        return self.name


class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField(default=date.today)
    authors = models.ManyToManyField(Author)
    number_of_comments = models.IntegerField(default=0)
    number_of_pingbacks = models.IntegerField(default=0)
    rating = models.IntegerField(default=5)

    def __str__(self):
        return self.headline

Создание объектов

Для представления данных таблиц базы данных в объектах Python Django использует интуитивную систему: класс модели представляет таблицу базы данных, а экземпляр этого класса представляет конкретную запись в таблице базы данных.

Для создания объекта создайте его экземпляр, используя ключевые аргументы для класса модели, затем вызовите save() для сохранения его в базе данных.

Предполагая, что модели находятся в файле mysite/blog/models.py, вот пример:

>>> from blog.models import Blog
>>> b = Blog(name="Beatles Blog", tagline="All the latest Beatles news.")
>>> b.save()

Это выполняет INSERT операцию SQL за кулисами. Django не обращается к базе данных до тех пор, пока вы явно не вызовете save().

Метод save() не возвращает значения.

См. также

save() принимает ряд расширенных параметров, которые здесь не описаны. Обратитесь к документации для save() для получения полной информации.

Чтобы создать и сохранить объект в одной операции, используйте метод create().

Сохранение изменений в объектах

Для сохранения изменений в объекте, который уже находится в базе данных, используйте save().

Учитывая Blog экземпляр b5, который уже был сохранён в базе данных, этот пример изменяет его имя и обновляет запись в базе данных:

>>> b5.name = "New name"
>>> b5.save()

Это выполняет UPDATE операцию SQL за кулисами. Django не обращается к базе данных до тех пор, пока вы явно не вызовете save().

Сохранение ForeignKey и ManyToManyField полей

Обновление поля ForeignKey работает точно так же, как и сохранение обычного поля — присвойте объекту нужного типа соответствующее поле. В этом примере обновляется атрибут blog экземпляра Entry экземпляра entry, предполагая, что соответствующие экземпляры Entry и Blog уже сохранены в базе данных (чтобы мы могли их извлечь ниже):

>>> from blog.models import Blog, Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()

Обновление поля ManyToManyField работает немного по-другому — используйте метод add() для поля, чтобы добавить запись к связи. В этом примере добавляется экземпляр Author joe к объекту entry.

>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)

Для добавления нескольких записей в ManyToManyField за один раз, включите несколько аргументов в вызов add(), как показано здесь:

>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)

Django выдаст ошибку, если вы попытаетесь присвоить или добавить объект неправильного типа.

Извлечение объектов

Для извлечения объектов из базы данных создайте QuerySet через Manager в вашем классе модели.

QuerySet представляет собой коллекцию объектов из базы данных. Она может содержать ноль, один или несколько фильтров. Фильтры сужают результаты запроса на основе заданных параметров. В терминах SQL QuerySet эквивалентно оператору SELECT, а фильтр — это ограничивающее условие, такое как WHERE или LIMIT.

Вы получаете QuerySet с помощью Manager вашей модели. Каждая модель имеет по крайней мере один Manager, и по умолчанию он называется objects. Обратитесь к нему напрямую через класс модели следующим образом:

>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name="Foo", tagline="Bar")
>>> b.objects
Traceback:
    ...
AttributeError: "Manager isn't accessible via Blog instances."

Примечание

Managers доступны только через классы моделей, а не через экземпляры моделей, чтобы обеспечить разделение между операциями на уровне таблицы и операциями на уровне записей.

Менеджер Manager является основным источником QuerySets для модели. Например, Blog.objects.all() возвращает QuerySet, который содержит все Blog объекты в базе данных.

Извлечение всех объектов

Самый простой способ извлечь объекты из таблицы — получить все из них. Для этого используйте метод all() на Manager:

>>> all_entries = Entry.objects.all()

Метод all() возвращает QuerySet всех объектов в базе данных.

Извлечение конкретных объектов с фильтрами

QuerySet, возвращаемый all(), описывает все объекты в таблице базы данных. Однако обычно вам потребуется выбрать только подмножество полного набора объектов.

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

filter(**kwargs)
Возвращает новый QuerySet, содержащий объекты, соответствующие заданным параметрам поиска.
exclude(**kwargs)
Возвращает новый QuerySet, содержащий объекты, которые не соответствуют заданным параметрам поиска.

Параметры поиска (**kwargs в определениях функций выше) должны быть в формате, описанном в разделе Поиск по полям ниже.

Например, чтобы получить QuerySet записей блога за 2006 год, используйте filter() следующим образом:

Entry.objects.filter(pub_date__year=2006)

При использовании менеджера по умолчанию это эквивалентно:

Entry.objects.all().filter(pub_date__year=2006)

Цепочки фильтров

Результат уточнения QuerySet сам по себе является QuerySet, поэтому можно объединять уточнения. Например:

>>> Entry.objects.filter(headline__startswith="What").exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(pub_date__gte=datetime.date(2005, 1, 30))

Это берет начальный QuerySet всех записей в базе данных, добавляет фильтр, затем исключение, затем еще один фильтр. Конечный результат — QuerySet, содержащий все записи с заголовком, начинающимся с «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 вычисляется путём обращения к базе данных. Более подробную информацию о том, когда происходит вычисление, см. в разделе Когда QuerySet вычисляются.

Получение одной записи с помощью get()

filter() всегда вернёт QuerySet, даже если только одна запись соответствует запросу — в этом случае это будет QuerySet, содержащий один элемент.

Если вам известно, что существует только одна запись, соответствующая вашему запросу, вы можете использовать метод get() на Manager, который возвращает запись напрямую:

>>> one_entry = Entry.objects.get(pk=1)

Вы можете использовать любое выражение запроса с get(), так же как и с filter() — опять же, см. Поля запросов ниже.

Обратите внимание на разницу между использованием get() и использованием filter() с подмножеством [0]. Если нет результатов, соответствующих запросу, get() вызовет исключение DoesNotExist. Это исключение является атрибутом класса модели, на которой выполняется запрос — поэтому в приведенном выше коде, если нет объекта Entry с первичным ключом 1, Django вызовет Entry.DoesNotExist.

Аналогично, Django будет жаловаться, если несколько записей соответствуют запросу get(). В этом случае он вызовет MultipleObjectsReturned, что также является атрибутом самого класса модели.

Другие методы QuerySet

Большую часть времени вы будете использовать all(), get(), filter() и exclude(), когда вам нужно искать записи в базе данных. Однако это далеко не всё; см. Справочник по API QuerySet для полного списка всех методов QuerySet.

Ограничение QuerySet

Используйте подмножество синтаксиса срезов массивов Python, чтобы ограничить ваш QuerySet определённым количеством результатов. Это эквивалентно предложениям LIMIT и OFFSET в SQL.

Например, это возвращает первые 5 объектов (LIMIT 5):

>>> Entry.objects.all()[:5]

Это возвращает объекты с шестого по десятый (OFFSET 5 LIMIT 5):

>>> Entry.objects.all()[5:10]

Отрицательный индексирование (т.е. Entry.objects.all()[-1]) не поддерживается.

В целом, использование срезов на QuerySet возвращает новый QuerySet — запрос не выполняется. Исключением является использование параметра «шаг» синтаксиса срезов Python. Например, это фактически выполнит запрос, чтобы вернуть список каждого второго объекта из первых 10:

>>> Entry.objects.all()[:10:2]

Дополнительная фильтрация или сортировка срезованного объекта запроса запрещена из-за неоднозначности того, как это может работать.

Для получения одного объекта, а не списка (например, SELECT foo FROM bar LIMIT 1), используйте индекс вместо среза. Например, это возвращает первый Entry в базе данных после сортировки записей по алфавиту по заголовку:

>>> Entry.objects.order_by("headline")[0]

Это примерно эквивалентно:

>>> Entry.objects.order_by("headline")[0:1].get()

Обратите внимание, однако, что первое из этих выражений вызовет IndexError, а второе вызовет DoesNotExist, если ни один объект не соответствует заданным критериям. См. get() для получения более подробной информации.

Поля запросов

Поля запросов — это способ указания существенного части SQL-запроса WHERE. Они указываются в качестве аргументов ключевых слов методам QuerySet filter(), exclude() и get().

Аргументы ключевых слов основных запросов имеют вид field__lookuptype=value. (Это двойное подчеркивание). Например:

>>> Entry.objects.filter(pub_date__lte="2006-01-01")

примерно переводится на следующий SQL:

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

Как это возможно

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

Указанное в запросе поле должно быть именем поля модели. Однако есть одно исключение: в случае ForeignKey вы можете указать имя поля, дополненное _id. В этом случае ожидается, что параметр value будет содержать исходное значение первичного ключа связанной модели. Например:

>>> Entry.objects.filter(blog_id=4)

Если вы передадите некорректное ключевое слово, функция поиска вызовет TypeError.

API базы данных поддерживает около двух десятков типов поиска; полная справка находится в справочнике по поиску по полям. Чтобы дать вам представление о доступных возможностях, вот некоторые из наиболее распространённых поисков, которые вам, вероятно, понадобятся:

exact

Полное совпадение. Например:

>>> Entry.objects.get(headline__exact="Cat bites dog")

Это сгенерирует SQL примерно такого вида:

SELECT ... WHERE headline = 'Cat bites dog';

Если вы не укажете тип поиска — то есть, если ваше ключевое слово не содержит двойного подчеркивания — тип поиска предполагается exact.

Например, следующие два оператора эквивалентны:

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

Это для удобства, так как exact запросы — наиболее распространённый случай.

iexact

Поиск без учёта регистра. Таким образом, запрос:

>>> Blog.objects.get(name__iexact="beatles blog")

Будет соответствовать заголовку Blog с названием "Beatles Blog", "beatles blog", или даже "BeAtlES blOG".

contains

Поиск по содержанию, учитывающий регистр. Например:

Entry.objects.get(headline__contains="Lennon")

Приближённый перевод на SQL:

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

Обратите внимание, что это будет соответствовать заголовку 'Today Lennon honored' но не 'today lennon honored'.

Также существует вариант без учёта регистра, icontains.

startswith, endswith
Поиск, начинающийся и заканчивающийся, соответственно. Существуют также варианты без учёта регистра, называемые istartswith и iendswith.

Ещё раз, это лишь поверхностный обзор. Полная справка находится в справочнике по поиску по полям.

Поиск через связи

Django предлагает мощный и интуитивный способ «следовать» связям в запросах, автоматически обрабатывая SQL JOIN за вас, за кулисами. Для перехода по связи используйте имена полей связанных полей между моделями, разделённые двойными подчеркиваниями, пока не дойдёте до нужного поля.

Этот пример извлекает все Entry объекты со Blog, у которых name равно 'Beatles Blog':

>>> Entry.objects.filter(blog__name="Beatles Blog")

Этот поиск может быть сколь угодно глубоким.

Он также работает в обратном направлении. Хотя он can be customized, по умолчанию вы ссылаетесь на «обратную» связь в запросе, используя имя модели в нижнем регистре.

В этом примере извлекаются все Blog объекты, у которых есть хотя бы один Entry, у которого headline содержит 'Lennon':

>>> Blog.objects.filter(entry__headline__contains="Lennon")

Если вы фильтруете по нескольким связям, и одна из промежуточных моделей не имеет значения, соответствующего условию фильтра, Django будет обрабатывать её так, как будто там пустой (все значения NULL), но допустимый объект. Всё это означает, что ошибка не будет поднята. Например, в этом фильтре:

Blog.objects.filter(entry__authors__name="Lennon")

(если существует связанная Author модель), если у записи нет связанного author, это будет обрабатываться так, как будто нет и name объекта, а не вызывая ошибку из-за отсутствующего author. Обычно именно этого вы и хотите. Единственный случай, когда это может быть запутанно, если вы используете isnull. Таким образом:

Blog.objects.filter(entry__authors__name__isnull=True)

будет возвращать Blog объекты, у которых пустое name в author, а также те, у которых пустое author в entry. Если вы не хотите эти последние объекты, вы можете написать:

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

Переход по многозначным связям

При переходе по ManyToManyField или обратной ForeignKey (например, от Blog до Entry), фильтрация по нескольким атрибутам ставит вопрос о том, необходимо ли, чтобы каждый атрибут совпадал в одном и том же связанном объекте. Мы можем искать блоги, содержащие запись из 2008 года, в заголовке которой есть «Lennon», или мы можем искать блоги, в которых есть любая запись из 2008 года, а также некоторые более новые или более старые записи с «Lennon» в заголовке.

Чтобы выбрать все блоги, содержащие хотя бы одну запись из 2008 года, в заголовке которой есть «Lennon» (одна и та же запись, удовлетворяющая обоим условиям), мы напишем:

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». Первый запрос не вернёт никаких блогов, а второй вернёт этот один блог. (Это потому, что записи, выбранные вторым фильтром, могут или не могут совпадать с записями в первом фильтре. Мы фильтруем Blog элементы с каждой инструкцией фильтра, а не Entry элементы.) Короче говоря, если каждое условие должно соответствовать одному и тому же связанному объекту, то каждое должно находиться в одном вызове filter().

Примечание

Поскольку второй (более гибкий) запрос цепляет несколько фильтров, он выполняет несколько соединений с основной моделью, потенциально приводя к дубликатам.

>>> from datetime import date
>>> beatles = Blog.objects.create(name="Beatles Blog")
>>> pop = Blog.objects.create(name="Pop Music Blog")
>>> Entry.objects.create(
...     blog=beatles,
...     headline="New Lennon Biography",
...     pub_date=date(2008, 6, 1),
... )
<Entry: New Lennon Biography>
>>> Entry.objects.create(
...     blog=beatles,
...     headline="New Lennon Biography in Paperback",
...     pub_date=date(2009, 6, 1),
... )
<Entry: New Lennon Biography in Paperback>
>>> Entry.objects.create(
...     blog=pop,
...     headline="Best Albums of 2008",
...     pub_date=date(2008, 12, 15),
... )
<Entry: Best Albums of 2008>
>>> Entry.objects.create(
...     blog=pop,
...     headline="Lennon Would Have Loved Hip Hop",
...     pub_date=date(2020, 4, 1),
... )
<Entry: Lennon Would Have Loved Hip Hop>
>>> Blog.objects.filter(
...     entry__headline__contains="Lennon",
...     entry__pub_date__year=2008,
... )
<QuerySet [<Blog: Beatles Blog>]>
>>> Blog.objects.filter(
...     entry__headline__contains="Lennon",
... ).filter(
...     entry__pub_date__year=2008,
... )
<QuerySet [<Blog: Beatles Blog>, <Blog: Beatles Blog>, <Blog: Pop Music Blog]>

Примечание

Поведение filter() для запросов, которые охватывают многозначные отношения, как описано выше, не реализовано эквивалентно для exclude(). Вместо этого условия в одном вызове exclude() не обязательно будут ссылаться на один и тот же элемент.

Например, следующий запрос исключил бы блоги, содержащие и записи с «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.

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

Django поддерживает использование преобразований в выражениях.

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

>>> from django.db.models import F
>>> Entry.objects.filter(pub_date__year=F("mod_date__year"))

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

>>> from django.db.models import Min
>>> Entry.objects.aggregate(first_published_year=Min("pub_date__year"))

Этот пример находит значение наивысшего рейтинга записи и общее количество комментариев ко всем записям для каждого года:

>>> from django.db.models import OuterRef, Subquery, Sum
>>> Entry.objects.values("pub_date__year").annotate(
...     top_rating=Subquery(
...         Entry.objects.filter(
...             pub_date__year=OuterRef("pub_date__year"),
...         )
...         .order_by("-rating")
...         .values("rating")[:1]
...     ),
...     total_comments=Sum("number_of_comments"),
... )

Сокращение поиска по 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])  # Reuse the cache from the evaluation.

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

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

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

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

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

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

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

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

Примечание

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

Асинхронные запросы

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

К счастью, вы можете выполнить многие запросы, используя асинхронные API запросов Django. Каждый метод, который может заблокировать — например, get() или delete() — имеет асинхронный вариант (aget() или adelete()), и когда вы итерируетесь по результатам, вы можете использовать асинхронную итерацию (async for) вместо этого.

Итерация по запросу

Стандартный способ итерации по запросу — с помощью for — приводит к блокирующему запросу к базе данных в фоновом режиме, поскольку Django загружает результаты во время итерации. Чтобы исправить это, вы можете переключиться на async for:

async for entry in Authors.objects.filter(name__startswith="A"):
    ...

Обратите внимание, что вы также не можете выполнять другие действия, которые могут итерировать по queryset, например, оборачивание list() вокруг него для принудительной оценки (вы можете использовать async for в понимании, если хотите).

Поскольку методы QuerySet, такие как filter() и exclude(), на самом деле не выполняют запрос — они настраивают queryset для выполнения, когда по нему выполняется итерация — вы можете свободно использовать их в асинхронном коде. Чтобы получить руководство о том, какие методы можно по-прежнему использовать таким образом, и какие имеют асинхронные версии, прочитайте следующий раздел.

QuerySet и методы менеджера

Некоторые методы менеджеров и querysets — например, get() и first() — принуждают к выполнению queryset и являются блокирующими. Некоторые, например filter() и exclude(), не принуждают к выполнению и поэтому безопасны для запуска из асинхронного кода. Но как вы должны различать?

Хотя вы могли бы покопаться и посмотреть, есть ли метод с префиксом a (например, у нас есть aget() , но нет afilter()), есть более логичный способ — посмотреть, какой тип метода он имеет в Справочнике по QuerySet.

Там вы найдете методы QuerySets, сгруппированные в два раздела:

  • Методы, возвращающие новые querysets: это неблокирующие методы, и у них нет асинхронных версий. Вы можете свободно использовать их в любой ситуации, хотя перед использованием ознакомьтесь с замечаниями к defer() и only().
  • Методы, не возвращающие querysets: это блокирующие методы, и у них есть асинхронные версии — асинхронное имя для каждого указано в его документации, хотя наш стандартный шаблон заключается в добавлении префикса a.

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

user = await User.objects.filter(username=my_input).afirst()

filter() возвращает queryset, поэтому это нормально, если вы будете продолжать цепочку внутри асинхронной среды, тогда как first() оценивает и возвращает экземпляр модели — поэтому мы переходим к afirst(), и используем await в начале всего выражения, чтобы вызвать его асинхронно.

Примечание

Если вы забудете добавить часть await, вы можете увидеть ошибки, такие как «объект корутины не имеет атрибута x» или строки «<корутина …>” вместо ваших экземпляров модели. Если вы увидите их, вам нужно где-то добавить await, чтобы преобразовать эту корутину в реальное значение.

Транзакции

Транзакции не поддерживаются в настоящее время с асинхронными запросами и обновлениями. Вы обнаружите, что попытка использовать её вызывает SynchronousOnlyOperation.

Если вы хотите использовать транзакцию, мы рекомендуем написать ваш код ORM внутри отдельной синхронной функции и затем вызвать её с помощью sync_to_async — см. Поддержка асинхронности для получения дополнительной информации.

Запрос JSONField

Реализация поиска отличается в JSONField, главным образом из-за существования преобразований ключей. Для демонстрации мы будем использовать следующую примерную модель:

from django.db import models


class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = models.JSONField(null=True)

    def __str__(self):
        return self.name

Хранение и поиск значения None

Как и в других полях, хранение None в качестве значения поля будет хранить его как SQL NULL. Хотя это не рекомендуется, можно хранить скалярное JSON значение null вместо SQL значения NULL с помощью Value(None, JSONField()).

Какой бы из значений ни хранился, при получении из базы данных, Python-представление скалярного JSON значения null такое же, как SQL значение NULL, т. е. None. Поэтому их трудно различить.

Это относится только к None в качестве значения верхнего уровня поля. Если None находится внутри list или dict, оно всегда будет интерпретироваться как JSON null.

При запросе значение None всегда будет интерпретироваться как JSON null. Для запроса к SQL NULL, используйте isnull:

>>> Dog.objects.create(name="Max", data=None)  # SQL NULL.
<Dog: Max>
>>> Dog.objects.create(name="Archie", data=Value(None, JSONField()))  # JSON null.
<Dog: Archie>
>>> Dog.objects.filter(data=None)
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data=Value(None, JSONField()))
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data__isnull=True)
<QuerySet [<Dog: Max>]>
>>> Dog.objects.filter(data__isnull=False)
<QuerySet [<Dog: Archie>]>

Если вы не уверены, что хотите работать со значениями SQL NULL, рассмотрите возможность установки null=False и предоставления подходящего значения по умолчанию для пустых значений, например default=dict.

Примечание

Хранение скалярного JSON null не нарушает null=False.

Преобразования ключей, индексов и путей

Для запроса на основе заданного ключа словаря используйте этот ключ в качестве имени поиска:

>>> Dog.objects.create(
...     name="Rufus",
...     data={
...         "breed": "labrador",
...         "owner": {
...             "name": "Bob",
...             "other_pets": [
...                 {
...                     "name": "Fishy",
...                 }
...             ],
...         },
...     },
... )
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": None})
<Dog: Meg>
>>> Dog.objects.filter(data__breed="collie")
<QuerySet [<Dog: Meg>]>

Несколько ключей могут быть объединены вместе для формирования поиска по пути:

>>> Dog.objects.filter(data__owner__name="Bob")
<QuerySet [<Dog: Rufus>]>

Если ключ является целым числом, он будет интерпретирован как преобразование индекса в массиве:

>>> Dog.objects.filter(data__owner__other_pets__0__name="Fishy")
<QuerySet [<Dog: Rufus>]>

Если ключ, по которому вы хотите выполнить запрос, совпадает с именем другого поиска, используйте contains поиск вместо этого.

Для запроса отсутствующих ключей используйте isnull поиск:

>>> Dog.objects.create(name="Shep", data={"breed": "collie"})
<Dog: Shep>
>>> Dog.objects.filter(data__owner__isnull=True)
<QuerySet [<Dog: Shep>]>

Примечание

Примеры поисков выше неявно используют поиск exact. Преобразования ключей, индексов и путей также могут быть объединены с: icontains, endswith, iendswith, iexact, regex, iregex, startswith, istartswith, lt, lte, gt и gte, а также с Включения и поиски по ключам.

KT() выражения

class KT(lookup)

Представляет текстовое значение преобразования ключа, индекса или пути JSONField. Вы можете использовать обозначение с двумя подчеркиваниями в lookup для объединения преобразований ключа и индекса словаря.

Например:

>>> from django.db.models.fields.json import KT
>>> Dog.objects.create(
...     name="Shep",
...     data={
...         "owner": {"name": "Bob"},
...         "breed": ["collie", "lhasa apso"],
...     },
... )
<Dog: Shep>
>>> Dogs.objects.annotate(
...     first_breed=KT("data__breed__1"), owner_name=KT("data__owner__name")
... ).filter(first_breed__startswith="lhasa", owner_name="Bob")
<QuerySet [<Dog: Shep>]>

Примечание

Из-за того, как работают запросы по пути ключей, exclude() и filter() не гарантируют получения полных наборов. Если вы хотите включить объекты, у которых нет пути, добавьте isnull поиск.

Предупреждение

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

Пользователи MariaDB и Oracle

Использование order_by() для преобразований ключей, индексов или путей будет сортировать объекты, используя строковое представление значений. Это связано с тем, что MariaDB и Oracle Database не предоставляют функции преобразования значений JSON в их эквивалентные значения SQL.

Пользователи Oracle

В запросе exclude() на Oracle Database использование None в качестве значения поиска вернёт объекты, у которых null не является значением по указанному пути, включая объекты, у которых нет этого пути. В других базах данных, запрос вернёт объекты, у которых есть путь, и значение не равно null.

Пользователи PostgreSQL

В PostgreSQL, если используется только один ключ или индекс, используется оператор SQL ->. Если используются несколько операторов, используется оператор #>.

Пользователи SQLite

В SQLite, "true", "false", и "null" строковые значения всегда будут интерпретироваться как True, False, и JSON null соответственно.

Включения и поиски по ключам

contains

Поиск contains переопределён для JSONField. Возвращаемые объекты — это те, где указанные dict пар ключ-значение содержатся в верхнем уровне поля. Например:

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contains={"owner": "Bob"})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={"breed": "collie"})
<QuerySet [<Dog: Meg>]>

Oracle и SQLite

contains не поддерживается в Oracle и SQLite.

contained_by

Это обратное contains поиску — возвращаемые объекты те, где пары ключ-значение объекта являются подмножеством пар в переданном значении. Например:

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contained_by={"breed": "collie", "owner": "Bob"})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={"breed": "collie"})
<QuerySet [<Dog: Fred>]>

Oracle и SQLite

contained_by не поддерживается в Oracle и SQLite.

has_key

Возвращает объекты, где данный ключ присутствует на верхнем уровне данных. Например:

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_key="owner")
<QuerySet [<Dog: Meg>]>

has_keys

Возвращает объекты, где все указанные ключи присутствуют на верхнем уровне данных. Например:

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_keys=["breed", "owner"])
<QuerySet [<Dog: Meg>]>

has_any_keys

Возвращает объекты, где любой из указанных ключей присутствует на верхнем уровне данных. Например:

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_any_keys=["owner", "breed"])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

Сложные запросы с объектами Q

Запросы с именованными аргументами — в filter() и т. д. — объединяются по принципу «И». Если вам нужны более сложные запросы (например, запросы с OR операторами), вы можете использовать Q objects.

Объект Q object (django.db.models.Q) используется для инкапсуляции набора именованных аргументов. Эти аргументы задаются так же, как в «Поисках по полям» выше.

Например, этот Q объект инкапсулирует один запрос LIKE:

from django.db.models import Q

Q(question__startswith="What")

Q объекты могут быть объединены с помощью операторов &, |, и ^. Когда оператор используется на двух Q объектах, он создаёт новый Q объект.

Например, это выражение создаёт единый Q объект, представляющий «ИЛИ» двух запросов "question__startswith":

Q(question__startswith="Who") | Q(question__startswith="What")

Это эквивалентно следующей SQL WHERE части:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

Вы можете создавать выражения любой сложности, комбинируя Q объекты операторами &, |, и ^, а также использовать скобки для группировки. Также Q объекты могут быть инвертированы с помощью оператора ~, позволяя комбинировать запросы, включающие как обычный запрос, так и отрицательный (NOT) запрос:

Q(question__startswith="Who") | ~Q(pub_date__year=2005)

Каждая функция поиска, которая принимает именованные аргументы (например, filter(), exclude(), get()) также может принимать один или несколько Q объектов в качестве позиционных (не именованных) аргументов. Если вы предоставляете несколько Q объектов в качестве аргументов функции поиска, эти аргументы будут объединены по принципу «И». Например:

Poll.objects.get(
    Q(question__startswith="Who"),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)

… примерно переводится на SQL:

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

Функции поиска могут комбинировать использование объектов Q и ключевых аргументов. Все аргументы, предоставленные функции поиска (будь то ключевые аргументы или объекты Q), объединяются с помощью «И». Однако, если предоставляется объект Q, он должен предшествовать определению любых ключевых аргументов. Например:

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith="Who",
)

… будет допустимым запросом, эквивалентным предыдущему примеру; но:

# INVALID QUERY
Poll.objects.get(
    question__startswith="Who",
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)

… не будет допустимым.

См. также

Примеры поиска по OR в тестовых примерах Django показывают возможные варианты использования Q.

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

Для сравнения двух экземпляров модели используйте стандартный оператор сравнения Python, двойной знак равенства: ==. За кулисами это сравнивает значения первичного ключа двух моделей.

Используя пример Entry выше, следующие два оператора эквивалентны:

>>> some_entry == other_entry
>>> some_entry.id == other_entry.id

Если первичный ключ модели не называется id, это не проблема. Сравнения всегда будут использовать первичный ключ, как бы он ни назывался. Например, если поле первичного ключа модели называется name, эти два оператора эквивалентны:

>>> some_obj == other_obj
>>> some_obj.name == other_obj.name

Удаление объектов

Метод delete, удобно, имеет имя delete(). Этот метод немедленно удаляет объект и возвращает количество удаленных объектов и словарь с количеством удалений по типу объекта. Пример:

>>> e.delete()
(1, {'blog.Entry': 1})

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

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

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

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

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

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

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

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

Entry.objects.all().delete()

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.update(blog=b)

Метод update() применяется мгновенно и возвращает количество строк, соответствующих запросу (что может не совпадать с количеством обновлённых строк, если некоторые строки уже имеют новое значение). Единственное ограничение на обновляемый QuerySet заключается в том, что он может обращаться только к одной таблице базы данных: основной таблице модели. Вы можете фильтровать по связанным полям, но вы можете обновлять только столбцы в основной таблице модели. Пример:

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

# Update all the headlines belonging to this Blog.
>>> Entry.objects.filter(blog=b).update(headline="Everything is the same")

Обратите внимание, что метод update() преобразуется непосредственно в SQL-запрос. Это операция пакетной обработки для непосредственных обновлений. Он не выполняет методы save() на ваших моделях, или не излучает сигналы pre_save или post_save (что является следствием вызова save()), или не учитывает опцию поля auto_now. Если вы хотите сохранить каждый элемент в QuerySet и убедиться, что метод save() вызывается для каждого экземпляра, вам не нужна специальная функция для этого. Пройдите по ним и вызовите save():

for item in my_queryset:
    item.save()

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

>>> Entry.objects.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 дескрипторов. Это не должно быть существенно для вас, но мы указываем на это для любопытных.)

END_OF_DOCUMENT_MARKER

Django также создает аксессоры API для «другой» стороны отношения — ссылки от связанной модели к модели, определяющей отношение. Например, объект Blog b имеет доступ к списку всех связанных объектов Entry через атрибут entry_set: %%%CODE_BLOCK_526%%.

Все примеры в этом разделе используют примерные модели Blog, Author и Entry, определённые в начале этой страницы.

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

Прямое

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

Пример:

>>> e = Entry.objects.get(id=2)
>>> e.blog  # Returns the related Blog object.

Вы можете получать и устанавливать значения через атрибут внешнего ключа. Как можно ожидать, изменения в внешнем ключе не сохраняются в базе данных до тех пор, пока вы не вызовете save(). Пример:

>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()

Если поле ForeignKey имеет null=True установленным (то есть, оно допускает NULL значения), вы можете присвоить None для удаления отношения. Пример:

>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save()  # "UPDATE blog_entry SET blog_id = NULL ...;"

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

>>> e = Entry.objects.get(id=2)
>>> print(e.blog)  # Hits the database to retrieve the associated Blog.
>>> print(e.blog)  # Doesn't hit the database; uses cached version.

Обратите внимание, что метод select_related() QuerySet рекурсивно предварительно заполняет кэш всех отношений один ко многим заранее. Пример:

>>> e = Entry.objects.select_related().get(id=2)
>>> print(e.blog)  # Doesn't hit the database; uses cached version.
>>> print(e.blog)  # Doesn't hit the database; uses cached version.

Следование отношениям «обратно»

Если модель имеет ForeignKey, экземпляры модели внешнего ключа получат доступ к Manager, который возвращает все экземпляры первой модели. По умолчанию этот Manager называется FOO_set, где FOO — имя исходной модели, приведённое к нижнему регистру. Этот Manager возвращает QuerySets, которое можно фильтровать и изменять, как описано в разделе «Получение объектов» выше.

Пример:

>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all()  # Returns all Entry objects related to Blog.

# b.entry_set is a Manager that returns QuerySets.
>>> b.entry_set.filter(headline__contains="Lennon")
>>> b.entry_set.count()

Вы можете переопределить имя FOO_set путём установки параметра related_name в определении ForeignKey. Например, если модель Entry была изменена на blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries'), код примера выше будет выглядеть так:

>>> b = Blog.objects.get(id=1)
>>> b.entries.all()  # Returns all Entry objects related to Blog.

# b.entries is a Manager that returns QuerySets.
>>> b.entries.filter(headline__contains="Lennon")
>>> b.entries.count()

Использование пользовательского обратного менеджера

По умолчанию RelatedManager, используемый для обратных отношений, является подклассом стандартного менеджера для этой модели. Если вы хотите указать другой менеджер для данного запроса, вы можете использовать следующий синтаксис:

from django.db import models


class Entry(models.Model):
    # ...
    objects = models.Manager()  # Default Manager
    entries = EntryManager()  # Custom Manager


b = Blog.objects.get(id=1)
b.entry_set(manager="entries").all()

Если EntryManager выполнял стандартную фильтрацию в своём методе get_queryset(), эта фильтрация применялась бы к вызову all().

Указание пользовательского обратного менеджера также позволяет вызывать его пользовательские методы:

b.entry_set(manager="entries").is_published()

Взаимодействие с предварительной выборкой

При вызове prefetch_related() с обратным отношением, будет использован менеджер по умолчанию. Если вы хотите предварительно выбрать связанные объекты, используя пользовательский обратный менеджер, используйте Prefetch(). Например:

from django.db.models import Prefetch

prefetch_manager = Prefetch("entry_set", queryset=Entry.entries.all())
Blog.objects.prefetch_related(prefetch_manager)

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

В дополнение к методам 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

Как реализуются обратные отношения?

Другие объектно-реляционные мапперы требуют определения отношений с обеих сторон. Разработчики Django считают это нарушением принципа DRY (Don't Repeat Yourself), поэтому 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/5.1/topics/db/queries/

Spec-Zone.ru

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