Spec-Zone.ru › Django 2.1

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

После создания ваших моделей данных, 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)
    n_comments = models.IntegerField()
    n_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 оценивается с доступом к базе данных. Более подробную информацию о том, когда происходит оценка, см. в разделе Когда 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(), когда вам нужно найти объекты в базе данных. Однако это далеко не все; см. Список методов QuerySet для полного списка различных QuerySet методов.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Поля поиска

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

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

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

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

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

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

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

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

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

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

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

exact

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

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

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

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

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

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

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

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

iexact

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

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

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

contains

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

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

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

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

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

Также есть вариант без учета регистра — icontains.

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

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

Поиск, охватывающий отношения

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

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

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

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

Это может продолжаться сколь угодно глубоко.

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

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

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

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

Это может показаться немного запутанным, поэтому, надеюсь, пример прояснит ситуацию. Чтобы выбрать все блоги, которые содержат записи с «Ленноном» в заголовке и опубликованные в 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 года не содержала «Леннона». Первый запрос не вернёт никаких блогов, а второй вернёт этот один блог.

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

Примечание

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

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

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

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

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

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

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

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

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

>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

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

>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

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

>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_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 предоставляет сокращение поиска по первичному ключу, которое обозначается как «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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Примечание

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

Сложные условия поиска с помощью объектов 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 здесь.

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

Для сравнения двух экземпляров модели используйте стандартный оператор сравнения 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, {'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(n_pingbacks=F('n_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(), 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 отслеживает отношения и добавляет их, когда связанные модели будут импортированы.

По этой причине особенно важно, чтобы все используемые модели были определены в приложениях, перечисленных в 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/2.1/topics/db/queries/

Spec-Zone.ru

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