Django_-_podrobnoe_rukovodstvo
.pdf430 |
Приложение B. Справочник по API доступа к базе данных |
Чтобы вернуть единственный объект, а не список (например, SELECT foo FROM bar LIMIT 1), вместо операции извлечения сегмента следует использовать обычный доступ по индексу. Например, следующий вызов вернет первый объект Entry после сортировки записей по полю headline
валфавитном порядке:
>>>Entry.objects.order_by(‘headline’)[0]
Это приблизительный аналог предыдущего примера:
>>> Entry.objects.order_by(‘headline’)[0:1].get()
Отметим, однако, что если не существует ни одного объекта, удовлетворяющего заданному условию, то в первом случае будет возбуждено исключение IndexError, а во втором – DoesNotExist.
Методы, возвращающие новые объекты QuerySet
В классе QuerySet имеется целый ряд методов фильтрации, которые модифицируют либо типы возвращаемых результатов, либо способ выполнения SQL-команды. Некоторые методы принимают аргументы, определяющие критерии поиска по полям, эта тема обсуждается в разделе «Поиск по полям» ниже.
Метод filter(**lookup)
Этот метод возвращает новый объект QuerySet, содержащий объекты, которые удовлетворяют заданным параметрам поиска.
Метод exclude(**lookup)
Этот метод возвращает новый объект QuerySet, содержащий объекты, которые не удовлетворяют заданным параметрам поиска.
Метод order_by(*fields)
По умолчанию результаты, возвращаемые в объекте QuerySet, отсортированы в порядке, указанном в параметре ordering в метаданных модели (см. приложение A). Для конкретного запроса порядок сортировки можно изменить с помощью метода order_by():
>> Entry.objects.filter(pub_date__year=2005).order_by(‘-pub_date’, ‘headline’)
Результат будет отсортирован сначала в порядке убывания значений pub_date, а затем в порядке возрастания значений headline. Знак минус в -pub_date означает по убыванию. Если минус отсутствует, подразумевается порядок по возрастанию. Для сортировки в случайном порядке указывайте ?, например:
>>> Entry.objects.order_by(‘?’)
Сортировка в случайном порядке выполняется медленно, поэтому не стоит пользоваться этой возможностью для больших наборов.
Фильтрация объектов |
431 |
Если в классе Meta модели порядок сортировки не задан и для QuerySet не вызывался метод order_by(), то порядок результатов не определен и может изменяться от запроса к запросу.
Метод distinct()
Возвращает новый объект QuerySet, для которого в SQL-запросе используется предложение SELECT DISTINCT, устраняющее дубликаты.
По умолчанию дубликаты в QuerySet не устраняются. На практике это редко приводит к проблемам, потому что результат простых запросов типа Blog.objects.all() в принципе не может содержать дубликатов. Но если в запросе участвует несколько таблиц, то появление дубликатов возможно. Тогда имеет смысл использовать distinct().
Метод values(*fields)
Возвращает специальный объект QuerySet, при обращении к которому получается список словарей, а не список экземпляров модели. Ключи в каждом словаре соответствуют именам атрибутов модели:
# Этот список содержит объект класса Blog.
>>> Blog.objects.filter(name__startswith=’Beatles’) [Beatles Blog]
# А этот список содержит словарь.
>>> Blog.objects.filter(name__startswith=’Beatles’).values()
[{‘id’: 1, ‘name’: ‘Beatles Blog’, ‘tagline’: ‘All the latest Beatles news.’}]
Метод values() принимает необязательные позиционные аргументы *fields, которые определяют имена полей, выбираемых командой SELECT. Если имена полей заданы, то каждый словарь будет содержать только соответствующие им ключи и значения. В противном случае в словаре будут представлены все поля таблицы базы данных:
>>> Blog.objects.values()
[{‘id’: 1, ‘name’: ‘Beatles Blog’, ‘tagline’: ‘All the latest Beatles news.’}],
>>> Blog.objects.values(‘id’, ‘name’) [{‘id’: 1, ‘name’: ‘Beatles Blog’}]
Этот метод полезен, когда требуется получить значения лишь небольшого подмножества полей, а полная функциональность объекта модели не нужна. Вообще говоря, эффективнее выбирать лишь необходимые поля.
Метод dates(field, kind, order)
Этот метод возвращает специальный объект QuerySet, при вычислении которого получается список объектов datetime.datetime, представляющих даты, которые присутствуют в отобранных объектах модели.
Аргумент field должен быть именем одного из полей типа DateField или DateTimeField вашей модели. Аргумент kind может принимать толь-
432 |
Приложение B. Справочник по API доступа к базе данных |
ко значения “year”, “month” или “day”. Каждый объект datetime.datetime, представленный в списке результатов, загрубляется до заданной единицы измерения:
•• Если задано значение “year”, то возвращается список всех различающихся годов в указанном поле.
•• Если задано значение “month”, то возвращается список всех различающихся пар год/месяц в указанном поле.
•• Если задано значение “day”, то возвращается список всех различающихся троек год/месяц/день в указанном поле.
Аргумент order может принимать значение ‘ASC’ или ‘DESC’ и по умолчанию равен ‘ASC’. Он определяет порядок сортировки результатов – по возрастанию или по убыванию. Примеры:
>>>Entry.objects.dates(‘pub_date’, ‘year’) [datetime.datetime(2005, 1, 1)]
>>>Entry.objects.dates(‘pub_date’, ‘month’) [datetime.datetime(2005, 2, 1), datetime.datetime(2005, 3, 1)]
>>>Entry.objects.dates(‘pub_date’, ‘day’) [datetime.datetime(2005, 2, 20), datetime.datetime(2005, 3, 20)]
>>>Entry.objects.dates(‘pub_date’, ‘day’, order=’DESC’) [datetime.datetime(2005, 3, 20), datetime.datetime(2005, 2, 20)]
>>>Entry.objects.filter(headline__contains=’Lennon’).dates(‘pub_date’, ‘day’) [datetime.datetime(2005, 3, 20)]
Метод select_related()
Этот метод возвращает объект QuerySet, который автоматически проверяет отношения по внешнему ключу и при выполнении запроса отбирает дополнительные данные из связанных объектов. В результате объем возвращаемого набора результатов увеличивается (иногда существенно), зато если в дальнейшем понадобятся записи, на которые указывают внешние ключи, то снова обращаться к базе не придется. За счет этого нередко удается повысить общую производительность.
В следующих примерах показана разница между простым поиском
ипоиском с применением метода select_related(). Вот стандартный поиск:
# Обращение к базе данных.
>>> e = Entry.objects.get(id=5)
#Еще одно обращение к базе данных за связанным объектом Blog.
>>> b = e.blog
Авот поиск методом select_related:
#Обращение к базе данных.
>>> e = Entry.objects.select_related().get(id=5)
Фильтрация объектов |
433 |
#Второго обращения к базе данных нет, т.к. e.blog уже был
#заполнен предыдущим запросом.
>>> b = e.blog
Метод select_related() следует по всей цепочке внешних ключей. Если имеются такие модели:
class City(models.Model):
# ...
class Person(models.Model):
# ...
hometown = models.ForeignKey(City)
class Book(models.Model):
# ...
author = models.ForeignKey(Person)
то вызов Book.objects.select_related().get(id=4) поместит в кэш как связанный объект Person, так и связанный объект City:
>>> b = Book.objects.select_related().get(id=4)
>>> |
p |
= |
b.author |
# |
Нет |
обращения |
к |
базе |
данных. |
>>> |
c |
= |
p.hometown |
# |
Нет |
обращения |
к |
базе |
данных. |
>>> b = Book.objects.get(id=4) |
# Здесь не используется |
select_related(). |
||||||
>>> |
p |
= |
b.author |
# Есть |
обращение |
к |
базе |
данных. |
>>> |
c |
= |
p.hometown |
# Есть |
обращение |
к |
базе |
данных. |
Отметим, что select_related() не следует по внешним ключам, для которых null=True.
Обычно применение select_related() ощутимо повышает производительность приложения за счет сокращения количества обращений к базе данных. Однако при наличии глубокой вложенности отношений этот метод иногда порождает слишком сложные запросы, которые выполняются очень медленно.
Методы, не возвращающие новые объекты QuerySet
Следующие методы объекта QuerySet выполняют запрос, но возвращают не другой объект QuerySet, а что-то иное: одиночный объект, скалярное значение и т. п.
Метод get(**lookup)
Возвращает объект, соответствующий заданным параметрам поиска, которые должны быть представлены в формате, описанном в разделе «Поиск по полям». Если найдено более одного объекта, возбуждает исключение AssertionError.
Метод get()возбуждает исключение DoesNotExist, если не найдено ни одного объекта, соответствующего заданным параметрам. Исключение DoesNotExist является атрибутом класса модели. Рассмотрим пример:
434 |
Приложение B. Справочник по API доступа к базе данных |
>>> Entry.objects.get(id=’foo’) # возбуждает Entry.DoesNotExist
Исключение DoesNotExist наследует класс django.core.exceptions.ObjectDoesNotExist, поэтому в одном блоке try можно перехватить сразу несколько типов таких исключений:
>>>from django.core.exceptions import ObjectDoesNotExist
>>>try:
... e = Entry.objects.get(id=3)
... b = Blog.objects.get(id=1)
... except ObjectDoesNotExist:
... print “Либо запись, либо блог отсутствуют.”
Метод create(**kwargs)
Это вспомогательный метод, позволяющий создать и сохранить объект за одну операцию. В нем следующие два шага:
>>> p = Person(first_name=”Bruce”, last_name=”Springsteen”)
>>>p.save()
объединены в один:
>>>p = Person.objects.create(first_name=”Bruce”, last_name=”Springsteen”)
Метод get_or_create(**kwargs)
Этот вспомогательный метод ищет объект, а если не находит, то создает новый. Он возвращает кортеж (object, created), где object – найденный или созданный объект, а created – булевский флаг, равный True, если был создан новый объект.
Метод задуман для упрощения показанной ниже типичной ситуации и наиболее полезен в сценариях импорта данных:
try:
obj = Person.objects.get(first_name=’John’, last_name=’Lennon’) except Person.DoesNotExist:
obj = Person(first_name=’John’, last_name=’Lennon’, birthday=date(1940, 10, 9))
obj.save()
С ростом количества полей в модели такая запись становится чрезмерно громоздкой. С помощью метода get_or_create() ее можно заметно сократить:
obj, created = Person.objects.get_or_create(
first_name |
= ‘John’, |
|
last_name |
= |
‘Lennon’, |
defaults |
= |
{‘birthday’: date(1940, 10, 9)} |
)
Методу get() передаются все именованные аргументы, указанные при вызове get_or_create(), за исключением необязательного аргумента de-
Фильтрация объектов |
435 |
faults. Если объект найден, то get_or_create() возвращает кортеж, состоящий из этого объекта и флага False. Если же объект не найден, то get_or_create() создаст новый объект, сохранит его и вернет кортеж, состоящий из нового объекта и флага True. При создании нового объекта применяется следующий алгоритм:
defaults = kwargs.pop(‘defaults’, {})
params = dict([(k, v) for k, v in kwargs.items() if ‘__’ not in k]) params.update(defaults)
obj = self.model(**params) obj.save()
На обычном языке его можно описать так. Сначала помещаем в словарь все именованные аргументы, кроме defaults и тех, в именах которых содержатся два символа подчеркивания, следующие подряд (так обозначается поиск по неточному совпадению). Затем добавляем содержимое аргумента defaults, замещая совпадающие ключи. Получившийся словарь становится набором именованных аргументов для конструктора класса модели.
Если в модели имеется поле с именем defaults и вы хотели бы учитывать его при поиске по полям в методе get_or_create(), то назовите соответствующий аргумент ‘defaults__exact’:
Foo.objects.get_or_create( defaults__exact = ‘bar’, defaults={‘defaults’: ‘bar’}
)
Примечание
Как отмечалось выше, метод get_or_create() особенно полезен в сценариях, которые анализируют входные данные и создают новую запись, если ее еще не существует. Метод get_or_create() можно использовать и в представлении, но лишь при обработке POST-запросов (если нет очень веских причин нарушить это правило). Дело в том, что GET-запросы не должны изменять состояние данных; запросы с побочными эффектами следует отправлять методом POST.
Метод count()
Возвращает целое число, равное количеству объектов, удовлетворяющих запросу в объекте QuerySet. Метод count() никогда не возбуждает исключений. Например:
#Возвращает количество всех объектов Entry в базе данных.
>>> Entry.objects.count() 4
#Возвращает количество объектов Entry, в поле headline которых
#встречается слово ‘Lennon’
436 |
Приложение B. Справочник по API доступа к базе данных |
>>> Entry.objects.filter(headline__contains=’Lennon’).count() 1
При вызове count() базе данных посылается запрос SELECT COUNT(*), поэтому для подсчета объектов всегда следует использовать этот метод, а не загружать все записи в объекты Python и затем вызывать метод len() результирующего списка.
Для некоторых СУБД (например, PostgreSQL или MySQL) метод count() возвращает длинное целое, а не обычное целое в смысле Python. На практике эта особенность вряд ли станет источником проблем.
Список in_bulk(id_list)
Этот метод принимает список значений первичного ключа и возвращает словарь, в котором каждому значению первичного ключа сопоставлен объект с таким идентификатором, например:
>>>Blog.objects.in_bulk([1]) {1: Beatles Blog}
>>>Blog.objects.in_bulk([1, 2]) {1: Beatles Blog, 2: Cheddar Talk}
>>>Blog.objects.in_bulk([])
{}
Идентификаторы несуществующих объектов в результирующий словарь не включаются. Передав методу in_bulk() пустой список, вы получите пустой словарь.
Метод latest(field_name=None)
Возвращает объект в таблице с самой поздней датой в поле, имя которого определяется аргументом field_name. В следующем примере мы находим самый недавний объект Entry в таблице, считая, что «давность» определяется полем pub_date:
>>> Entry.objects.latest(‘pub_date’)
Если в классе Meta модели определен атрибут get_latest_by, то аргумент field_name можно опустить. В этом случае Django по умолчанию будет использовать значение get_latest_by.
Как и get(), метод latest() возбуждает исключение DoesNotExist, если объект с указанными параметрами не существует.
Поиск по полям
Поиск по полям – это механизм определения предложения WHERE в SQLкомандах. Он задействуется при передаче именованных аргументов методам filter(), exclude() и get() объекта QuerySet.
Поиск по полям |
437 |
Именованные аргументы, описывающие простой поиск, имеют вид field__lookuptype=value (обратите внимание на два символа подчеркивания, идущие подряд). Следующая инструкция:
>>> Entry.objects.filter(pub_date__lte=’2006-01-01’)
превратится в такой SQL-запрос:
SELECT * FROM blog_entry WHERE pub_date <= ‘2006-01-01’;
При передаче недопустимого именованного аргумента будет возбуждено исключение TypeError.
Поддерживаются следующие виды поиска.
exact
Поиск по точному совпадению.
>>> Entry.objects.get(headline__exact=”Man bites dog”)
Будут найдены все объекты, в которых значение поля headline точно совпадает со строкой «Man bites dog».
Если вид поиска не указан, то есть, когда именованный аргумент не содержит двух символов подчеркивания, идущих подряд, предполагается точное совпадение (exact). Так, следующие два предложения эквивалентны:
>>> |
Blog.objects.get(id__exact=14) |
# |
Явная форма |
>>> |
Blog.objects.get(id=14) |
# |
подразумевается __exact |
Это сделано для удобства, потому что поиск по точному совпадению встречается чаще всего.
iexact
Поиск по точному совпадению без учета регистра.
>>> Blog.objects.get(name__iexact=’beatles blog’)
Будут найдены названия блогов ‘Beatles Blog’, ‘beatles blog’, ‘BeAtLes BLoG’ и т. д.
contains
Поиск вхождений с учетом регистра.
Entry.objects.get(headline__contains=’Lennon’)
Запись с заголовком ‘Today Lennon honored’ будет найдена, а запись с заголовком ‘today lennon honored’ – нет.
SQLite не поддерживает оператор LIKE с учетом регистра, поэтому для этой СУБД вид поиска contains в точности эквивалентен icontains.
438 |
Приложение B. Справочник по API доступа к базе данных |
Экранирование знаков процента и подчеркивания в операторе LIKE
Для тех видов поиска по полям, которые транслируются в инструкции SQL с оператором LIKE (iexact, contains, icontains, start swith, istartswith, endswith и iendswith), Django автоматически экранирует два специальных символа: знак процента и подчеркивания. (В операторе LIKE процент означает совпадение с произвольным количеством символов, а подчеркивание – с одним символом.)
Поэтому все должно работать в соответствии с интуитивными ожиданиями, то есть абстракция не имеет исключений. Например, чтобы выбрать все записи, в поле headline которых встречается знак процента, нужно просто написать:
Entry.objects.filter(headline__contains=’%’)
Об экранировании позаботится Django. Результирующий SQL-за прос будет выглядеть примерно так:
SELECT ... WHERE headline LIKE ‘%\%%’;
То же самое относится и к символам подчеркивания.
icontains
Поиск вхождений без учета регистра.
>>>Entry.objects.get(headline__icontains=’Lennon’)
Вотличие от contains, этот запрос найдет записи, в которых поле headline содержит строку ‘today lennon honored’.
gt, gte, lt, lte
«Больше», «больше или равно», «меньше», «меньше или равно».
>>>Entry.objects.filter(id__gt=4)
>>>Entry.objects.filter(id__lt=15)
>>>Entry.objects.filter(id__lte=3)
>>>Entry.objects.filter(id__gte=0)
Эти запросы возвращают соответственно объекты, для которых значение поля id больше 4, меньше 15, меньше или равно 3 и больше или равно 0.
Обычно эти виды поиска применяются к числовым полям. К символьным их следует применять с осторожностью, так как порядок в этом случае не всегда интуитивно очевиден (например, строка “4” больше строки “10”).
Поиск по полям |
439 |
in
Отбирает объекты, для которых значение указанного поля встречается в указанном списке:
Entry.objects.filter(id__in=[1, 3, 4])
Этот запрос возвращает все объекты с идентификатором (значением поля id), равным 1, 3 или 4.
startswith
Выполняет поиск по началу значения поля с учетом регистра:
>>> Entry.objects.filter(headline__startswith=’Will’)
Будут найдены записи с заголовками «Will he run?» и «Willbur named judge», но не записи с заголовками «Who is Will» или «will found in crypt».
istartswith
Выполняет поиск по началу значения поля без учета регистра:
>>> Entry.objects.filter(headline__istartswith=’will’)
Будут найдены записи с заголовками «Will he run?», «Willbur named judge» и «will found in crypt», но не запись с заголовком «Who is Will».
endswith и iendswith
Поиск по окончанию значения поля с учетом и без учета регистра соответственно. Аналогичны startswith и istartswith:
>>>Entry.objects.filter(headline__endswith=’cats’)
>>>Entry.objects.filter(headline__iendswith=’cats’)
range
Поиск по диапазону, включая границы:
>>>start_date = datetime.date(2005, 1, 1)
>>>end_date = datetime.date(2005, 3, 31)
>>>Entry.objects.filter(pub_date__range=(start_date, end_date))
Метод range можно использовать всюду, где в SQL разрешен оператор BETWEEN – для сравнения с диапазоном дат, чисел и даже символов.
year, month, day
Для полей типа date и datetime производится точное сравнение с годом, месяцем и днем соответственно:
# Вернуть все записи, опубликованные в 2005 году
>>>Entry.objects.filter(pub_date__year=2005)
