Spec-Zone.ru › Django 1.9

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

После создания ваших моделей данных, 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=50)
    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 всех объектов в базе данных.

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

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

>>> 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 предоставляет сокращение для поиска по первичному ключу – pk.

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

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

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

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

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

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

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

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

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

>>> 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 имеет поле «многие ко многим» к Author:

entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors = old_authors # saves new many2many relations

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

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

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

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

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

# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))

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

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

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

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

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

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

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

Прямое

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

Пример:

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

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

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

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

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

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

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

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

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

Обратные связанные объекты

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

Пример:

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

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

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

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

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

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

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

from django.db import models

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

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

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

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

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

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

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

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

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

b = Blog.objects.get(id=1)
b.entry_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.9/topics/db/queries/

Spec-Zone.ru

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