Spec-Zone.ru › Django 1.10

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

После создания ваших моделей данных, 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):              # __unicode__ on Python 2
        return self.name

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

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    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):              # __unicode__ on Python 2
        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 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 всех объектов в базе данных.

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

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

Для создания такого подмножества уточните исходный 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(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 определённым количеством результатов. Это эквивалентно условиям 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]

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

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

Для обработки обоих этих ситуаций 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(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(), например:

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

Поиск по 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

Поисковые операции полей, соответствующие операторам 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))
)

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

См. также

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

>>> e.delete()
(1, {'weblog.Entry': 1})
Изменено в Django 1.9:

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

Также можно удалять объекты группами. Каждый 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 1.9:

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

При удалении 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)

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

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

Однако, в отличие от F() объектов в предложениях filter и exclude, вы не можете вводить соединения, когда используете F() объекты в update — вы можете ссылаться только на поля, локальные для обновляемой модели. Если вы попытаетесь ввести соединение с помощью 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() с итерируемым объектом экземпляров или списком значений первичных ключей. Например:

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

В этом примере e1 и e2 могут быть полными экземплярами Entry или целыми значениями первичных ключей.

Если метод 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.

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

Отношения «один к одному» очень похожи на отношения «один ко многим». Если вы определите 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/1.10/topics/db/queries/

Spec-Zone.ru

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