Spec-Zone.ru › Django 4.2

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

После создания ваших моделей данных, 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». Второй — подмножество первого, с дополнительным условием, исключающим записи, дата публикации которых — текущая или будущая. Третий — подмножество первого, с дополнительным условием, выбирающим только записи, дата публикации которых — текущая или будущая. Начальный 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".

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()])

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

Чтобы избежать этой проблемы, сохраните 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 = 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__() возвращает только часть всего набора запросов.

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

Новое в Django 4.1.

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

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

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

Новое в Django 4.1.

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

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

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

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

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

Новое в Django 4.1.

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

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

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

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

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

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

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

Примечание

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

Транзакции

Новое в Django 4.1.

Транзакции не поддерживаются в настоящее время с асинхронными запросами и обновлениями. Вы обнаружите, что попытка использовать её вызовет 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.

Изменено в Django 4.2:

Добавлена поддержка выражения JSON null с помощью Value(None, JSONField()).

Устаревшее с версии 4.2: Передача Value("null") для выражения JSON null устарела.

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

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

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

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

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

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

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

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

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

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

Примечание

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

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

Новое в Django 4.2.
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

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

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

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

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

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

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

contains

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

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

Oracle и SQLite

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

contained_by

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

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

Oracle и SQLite

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

has_key

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

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

has_keys

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

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

has_any_keys

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

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

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

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

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

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

from django.db.models import Q

Q(question__startswith="What")

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

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

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

Это эквивалентно следующему SQL оператору WHERE:

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

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

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

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

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

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

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

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

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

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

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

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

См. также

Примеры использования операций «ИЛИ» Q в тестовых модулях Django показаны в примерах.

Изменено в Django 4.1:

Добавлена поддержка оператора ^ (XOR).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Entry.objects.all().delete()

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

for item in my_queryset:
    item.save()

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

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

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 имеет дополнительные методы, используемые для обработки набора связанных объектов. Краткое описание каждого приведено ниже, а полные сведения можно найти в справочнике по связанным объектам.

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/4.2/topics/db/queries/

Spec-Zone.ru

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