Spec-Zone.ru › Django 4.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, содержащий все записи с заголовком, начинающимся с «Что», опубликованные между 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, содержащий все записи, у которых заголовок начинается с «Что». Второй — подмножество первого с дополнительным критерием, исключающим записи, у которых 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]

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

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

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

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

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

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

Поиск по полям

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

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

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

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

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

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

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

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

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

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

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

exact

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

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

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

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

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

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

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

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

iexact

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

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

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

contains

Поиск по содержанию (учитывая регистр). Например:

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

Приблизительно переводится в такой SQL:

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

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

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

startswith, endswith
Поиск по началу и концу строки соответственно. Также существуют варианты поиска без учёта регистра, istartswith и iendswith.

Это лишь малая часть возможностей. Полную справку можно найти в справочнике по поиску по полям.

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

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

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

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

Такое «расширение» может быть сколь угодно глубоким.

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

Этот пример извлекает все Blog объекты, которые имеют по крайней мере один Entry , чьё поле headline содержит 'Lennon':

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

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

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

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

Blog.objects.filter(entry__authors__name__isnull=True)

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

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

Обработка многозначных связей

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

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

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

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

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

Предположим, есть только один блог, содержащий и записи с «Ленноном» и записи от 2008 года, но ни одна из записей от 2008 года не содержит «Леннона». Первый запрос не вернет ни одного блога, но второй запрос вернет этот единственный блог. (Это потому, что записи, выбранные вторым фильтром, могут или не могут совпадать с записями в первом фильтре. Мы фильтруем 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() не обязательно будут относиться к одному и тому же элементу.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Oracle

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

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

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

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

>>> Entry.objects.filter(pub_date__year=F('mod_date__year'))

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

>>> Entry.objects.aggregate(first_published_year=Min('pub_date__year'))

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

>>> Entry.objects.values('pub_date__year').annotate(
...     top_rating=Subquery(
...         Entry.objects.filter(
...             pub_date__year=OuterRef('pub_date__year'),
...         ).order_by('-rating').values('rating')[:1]
...     ),
...     total_comments=Sum('number_of_comments'),
... )

Сокращение поиска по pk

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

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

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

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

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

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

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

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

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

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

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

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

Методы набора запросов и менеджеров

Новое в Django 4.1.

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

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

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

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

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

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

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

Примечание

Если вы забудете добавить часть await, вы можете увидеть ошибки, такие как «объект корутины не имеет атрибута x» или «<coroutine …>» вместо ваших экземпляров модели. Если вы увидите такие ошибки, значит, вы где-то пропустили 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('null').

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

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

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

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

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

Примечание

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

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

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

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

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

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

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

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

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

Чтобы выполнить поиск по отсутствующим ключам, используйте поиск isnull:

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

Примечание

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

Примечание

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

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

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

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

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

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

В запросе Oracle Database с None в качестве значения поиска в exclude() будут возвращены объекты, у которых null не является значением по заданному пути, включая объекты, у которых нет этого пути. На других базах данных backend запрос вернет объекты, у которых есть путь, и значение не 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))
)

… будет некорректным.

См. также

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

Изменено в 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 для обновления одного поля на основе значения другого поля в модели. Это особенно полезно для инкремента счётчиков на основе их текущего значения. Например, чтобы увеличить счётчик pingback для каждой записи в блоге:

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

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

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

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

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

Например, объект Entry e может получить связанный объект Blog, обратившись к атрибуту blog: e.blog.

(За кулисами эта функциональность реализуется с помощью Python дескрипторов. Это не должно быть вам особенно важно, но мы указываем это для любопытных.)

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

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

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

Прямой доступ

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

Пример:

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

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

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

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

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

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

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

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

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

Обращение к отношениям «назад»

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

Пример:

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

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

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

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

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

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

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

from django.db import models

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Например:

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

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

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

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

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

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

e.entrydetail = ed

Как возможны обратные отношения?

Другие ORM требуют определения отношений с обеих сторон. Разработчики Django считают, что это нарушение принципа DRY (Don't Repeat Yourself), поэтому в Django необходимо определять отношения только с одной стороны.

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

Ответ лежит в app registry. Когда Django запускается, он импортирует каждую приложение, перечисленные в INSTALLED_APPS, а затем модуль models внутри каждого приложения. Всякий раз, когда создается новый класс модели, Django добавляет обратные отношения ко всем связанным моделям. Если связанные модели еще не импортированы, Django отслеживает отношения и добавляет их, когда связанные модели в конечном итоге будут импортированы.

По этой причине особенно важно, чтобы все используемые модели были определены в приложениях, указанных в INSTALLED_APPS. В противном случае обратные связи могут работать некорректно.

Запросы к связанным объектам

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

Например, если у вас есть объект Blog b с id=5, следующие три запроса были бы идентичны:

Entry.objects.filter(blog=b) # Query using object instance
Entry.objects.filter(blog=b.id) # Query using id from instance
Entry.objects.filter(blog=5) # Query using id directly

Возврат к исходному SQL

Если вы обнаружите необходимость написания SQL-запроса, слишком сложного для обработки Django-маппером базы данных, вы можете вернуться к написанию SQL вручную. Django предлагает несколько вариантов написания исходных SQL-запросов; см. Выполнение исходных SQL-запросов.

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

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.1/topics/db/queries/

Spec-Zone.ru

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