Spec-Zone.ru › Django 3.0

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

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

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

from django.db import models

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

    def __str__(self):
        return self.name

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

    def __str__(self):
        return self.name

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

    def __str__(self):
        return self.headline

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

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

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

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

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

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

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

См. также

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Получение объектов

Чтобы извлечь объекты из базы данных, создайте QuerySet с помощью Manager в вашем классе модели.

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

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

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

Примечание

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

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

Получение всех объектов

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

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

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

Получение определенных объектов с фильтрами

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

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

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

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

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

Entry.objects.filter(pub_date__year=2006)

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

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

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

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

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

Это берет начальное QuerySet всех записей в базе данных, добавляет фильтр, затем исключение, затем другой фильтр. Конечный результат — QuerySet, содержащий все записи с заголовком, начинающимся с «What», опубликованные между 30 января 2005 года и текущей датой.

Отфильтрованные QuerySet уникальны

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

Пример:

>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())

Эти три QuerySets раздельные. Первый — базовый QuerySet, содержащий все записи, содержащие заголовок, начинающийся с «What». Второй — подмножество первого, с дополнительным условием, которое исключает записи, чья pub_date дата сегодня или в будущем. Третий — подмножество первого, с дополнительным условием, которое выбирает только записи, чья pub_date дата сегодня или в будущем. Начальное QuerySet (q1) не затрагивается процессом уточнения.

QuerySet ленивые

QuerySets ленивые — создание QuerySet не предполагает никаких действий с базой данных. Вы можете добавлять фильтры весь день, и Django не выполнит запрос до тех пор, пока QuerySet не будет оценён. Посмотрите этот пример:

>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)

Хотя это выглядит как три обращения к базе данных, на самом деле база данных обращается только один раз, в последней строке (print(q)). В общем, результаты QuerySet не извлекаются из базы данных до тех пор, пока вы их не «запросите». Когда вы это сделаете, QuerySet оценивается с доступом к базе данных. Более подробную информацию о том, когда происходит оценка, см. в разделе Когда QuerySets оцениваются.

Получение отдельного объекта с помощью get()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

exact

Точное совпадение. Например:

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

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

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

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

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

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

Это сделано для удобства, так как exact поиски являются наиболее распространенным случаем.

iexact

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

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

Найдёт запись с заголовком Blog, "Beatles Blog", или даже "BeAtlES blOG".

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

Такие связи могут быть сколь угодно глубокими.

Это работает и в обратную сторону. Для ссылки на обратную связь используйте имя модели в нижнем регистре.

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

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

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

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

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

Blog.objects.filter(entry__authors__name__isnull=True)

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

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

Поиск через многозначные связи

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

Такая же ситуация возникает с ManyToManyField. Например, если у Entry есть ManyToManyField под названием tags, мы можем найти записи, связанные с тегами «музыка» и «группы», или запись, содержащую тег с именем «музыка» и статусом «публично».

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

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

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

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

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

Предположим, существует только один блог, содержащий и записи с «Lennon» и записи из 2008 года, но ни одна из записей из 2008 года не содержала «Lennon». Первый запрос не вернёт никаких блогов, а второй запрос вернёт этот один блог.

Во втором примере первый фильтр ограничивает результат до всех тех блогов, которые связаны с записями, содержащими «Lennon» в заголовке. Второй фильтр дополнительно ограничивает набор до тех блогов, которые также связаны с записями, опубликованными в 2008 году. Записи, выбранные вторым фильтром, могут или не могут совпадать с записями в первом фильтре. Мы фильтруем Blog элементы с каждым оператором фильтра, а не Entry элементы.

Примечание

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Для удобства Django предоставляет краткую запись для поиска по первичному ключу (pk, сокращённо от «primary key»).

В примере модели 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)

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

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

Обработка символов процента и подчёркивания в операторах LIKE

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Примечание

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

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

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

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

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

from django.db.models import Q
Q(question__startswith='What')

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

См. также

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Entry.objects.all().delete()

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

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

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

blog.pk = None
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:

django_blog.pk = None
django_blog.id = None
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.save()
entry.authors.set(old_authors)

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

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

Обновление нескольких объектов сразу

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

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

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

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

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

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

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

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

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

for item in my_queryset:
    item.save()

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

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

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

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

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

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

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

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

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

END_OF_DOCUMENT_MARKER

Все примеры в этом разделе используют образцы 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()

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

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

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

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

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

from django.db import models

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

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

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

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

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

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

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

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

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

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

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

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

Связи многие ко многим

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

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

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

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

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

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

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

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

Связи один к одному

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

Например:

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

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

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

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

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

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

e.entrydetail = ed

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

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

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

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

END_OF_DOCUMENT_MARKER

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

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

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

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

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

Обращение к SQL-запросам

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

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

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

Spec-Zone.ru

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