Запросы

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

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

from django.db import models

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

    def __str__(self):
        return self.name

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

    def __str__(self):
        return self.name

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

    def __str__(self):
        return self.headline

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

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

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

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

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

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

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

Смотрите также

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

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

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

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

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

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

Это выполняет оператор UPDATESQL за кулисами. 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экземпляр :joeentry

>>> 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на вашу модель класса.

A 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, добавляя условия фильтрации. Двумя наиболее распространенными способами усовершенствования a 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является a QuerySet, поэтому уточнения можно объединить в цепочку. Например:

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

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

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

Каждый раз, когда вы дорабатываете a 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сегодняшнему или будущему. На initial QuerySet( q1) процесс уточнения не влияет.

QuerySetленивы

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

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

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

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

filter()всегда будет выдавать вам a 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методов.

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

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

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

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

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

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

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

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

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

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

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

>>> 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 предлагает мощный и интуитивно понятный способ «следить» за отношениями при поиске, JOINавтоматически заботясь о SQL за кулисами. Чтобы охватить взаимосвязь, используйте имя поля связанных полей в моделях, разделенное двойным подчеркиванием, пока не дойдете до нужного поля.

В этом примере извлекаются все Entryобъекты с Blogкоторого name является :'Beatles Blog'

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

Этот охват может быть сколь угодно глубоким.

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

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

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

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

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

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

Blog.objects.filter(entry__authors__name__isnull=True)

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

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

Составные многозначные отношения

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

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

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

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

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

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

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

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

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

Примечание

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

Например, следующий запрос исключает блоги, содержащие как записи со словом «Леннон» в заголовке, так и записи, опубликованные в 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 expressionsF()

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

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

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

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

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

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

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

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

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

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

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

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

Oracle

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

Изменено в Django 3.1:

Поддержка .bitxor()была добавлена.

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

Новое в Django 3.2.

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

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

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

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

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

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

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

pkПоиска ярлык

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

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

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

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

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

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

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

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

Использование знаков процента и подчеркивания в LIKEоператорах

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

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

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

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

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

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

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

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

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

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

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

Когда 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__()возвращает только часть всего набора запросов.

Запрос JSONField

Реализация поиска отличается, в JSONFieldосновном, из-за наличия ключевых преобразований. Для демонстрации мы будем использовать следующий пример модели:

from django.db import models

class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = models.JSONField(null=True)

    def __str__(self):
        return self.name

Хранение и запрос None

Как и в случае с другими полями, при сохранении Noneзначения поля оно будет сохранено как SQL NULL. Хотя это не рекомендуется, но можно хранить скаляр JSON nullвместо SQL NULL, используя Value('null').

Какой из значений сохраняются, когда извлекается из базы данных, представление Python скаляра JSON nullтакого же , как SQL NULL, то есть None. Следовательно, их бывает сложно различить.

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

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

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

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

Примечание

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

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

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

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

Несколько ключей можно объединить в цепочку для поиска пути:

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

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

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

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

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

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

Примечание

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

Примечание

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

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

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

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

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

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

В Oracle Database при использовании Noneв качестве значения поиска в exclude()запросе будут возвращены объекты, которые не имеют nullзначения по заданному пути, включая объекты, не имеющие пути. На других серверах базы данных запрос вернет объекты, у которых есть путь, а у значения нет null.

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

В PostgreSQL, если используется только один ключ или индекс, используется оператор SQL ->. Если используется несколько операторов, используется #>оператор.

Сдерживание и поиск ключей

contains

containsПоиск переопределяется на JSONField. Возвращаемые объекты - это те, в которых все данные dictпары ключ-значение содержатся на верхнем уровне поля. Например:

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

Oracle и SQLite

contains не поддерживается в Oracle и SQLite.

contained_by

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

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

Oracle и SQLite

contained_by не поддерживается в Oracle и SQLite.

has_key

Возвращает объекты, в которых данный ключ находится на верхнем уровне данных. Например:

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

has_keys

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

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

has_any_keys

Возвращает объекты, в которых любой из заданных ключей находится на верхнем уровне данных. Например:

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

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

Запросы с аргументами ключевых слов - in filter()и т. Д. - объединяются оператором AND. Если вам нужно выполнять более сложные запросы (например, запросы с ORоператорами), вы можете использовать .Q objects

A ( ) - это объект, используемый для инкапсуляции коллекции аргументов ключевого слова. Эти аргументы ключевого слова указаны, как в разделе «Поиск полей» выше.Q objectdjango.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')

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

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

Вы можете составить заявления произвольной сложности путем объединения Qобъектов с &и |операторами и использование вводной группировкой. Кроме того, Q объекты могут быть инвертированы с помощью ~оператора, что позволяет выполнять комбинированный поиск, который объединяет как обычный запрос, так и запрос negated ( 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))
)

… Не будет действительным.

Смотрите также

В или 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_date2005 годом:

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

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

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

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

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

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

Entry.objects.all().delete()

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

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

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

blog.pk = None
blog._state.adding = True
blog.save() # blog.pk == 2

Все усложняется, если вы используете наследование. Рассмотрим подкласс Blog:

class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)

django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3

Из - за того , как работает наследование, вы должны установить , как pkи idк Noneи _state.addingк True:

django_blog.pk = None
django_blog.id = None
django_blog._state.adding = True
django_blog.save() # django_blog.pk == 4

Этот процесс не копирует отношения, которые не являются частью таблицы базы данных модели. Например, Entryесть ManyToManyFieldTo Author. После дублирования записи вы должны установить для новой записи отношения «многие ко многим»:

entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry._state.adding = True
entry.save()
entry.authors.set(old_authors)

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

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

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

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

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

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

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

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

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

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

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

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

for item in my_queryset:
    item.save()

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

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

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

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

Возврат к необработанному SQL

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

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

Copyright ©2021 All rights reserved