Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Django_-_podrobnoe_rukovodstvo

.pdf
Скачиваний:
373
Добавлен:
01.03.2016
Размер:
5 Мб
Скачать
☆

440

Приложение B. Справочник по API доступа к базе данных

#Вернуть все записи, опубликованные в декабре

>>> Entry.objects.filter(pub_date__month=12)

#Вернуть все записи, опубликованные третьего числа месяца

>>> Entry.objects.filter(pub_date__day=3)

#Сочетание: вернуть все записи, опубликованные в Рождество

#независимо от года

>>> Entry.objects.filter(pub_date__month=12, pub_date_day=25)

isnull

Принимает True или False, что соответствует операторам IS NULL и IS NOT NULL в SQL.

>>> Entry.objects.filter(pub_date__isnull=True)

search

Булевский полнотекстовый поиск, требующий наличия полнотекстовых индексов. Напоминает contains, но работает значительно быстрее благодаря использованию индексов.

Этот вид поиска доступен только для MySQL и требует ручного добавления полнотекстовых индексов в базу данных.

Сокращение pk

Для удобства Django предлагает вид поиска pk – сокращение «primary_ key». В случае модели Blog первичным ключом является поле id, поэтому следующие три вызова эквивалентны:

>>> Blog.objects.get(id__exact=14)

# Явная форма

>>>

Blog.objects.get(id=14)

# подразумевается __exact

>>>

Blog.objects.get(pk=14)

# pk подразумевает id__exact

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

#Получить блоги с идентификаторами 1, 4 и 7

>>> Blog.objects.filter(pk__in=[1,4,7])

#Получить блоги, для которых id > 14

>>> Blog.objects.filter(pk__gt=14)

Сокращение pk можно использовать и при работе с соединениями таблиц. Так, следующие три предложения эквивалентны:

>>>Entry.objects.filter(blog__id__exact=3) # Явная форма

>>>Entry.objects.filter(blog__id=3) # подразумевается __exact

>>>Entry.objects.filter(blog__pk=3) # __pk подразумевает __id__exact

Сложный поиск с использованием Q-объектов

441

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

Сложный поиск с использованием Q-объектов

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

Q-объектом (django.db.models.Q) называется объект, инкапсулирующий набор именованных аргументов, которые определяются, как описано в разделе «Поиск по полям».

Например, следующий Q-объект инкапсулирует одиночный запрос с оператором LIKE:

Q(question__startswith=’What’)

Q-объекты можно комбинировать с помощью операторов & и |. Оператор, примененный к двум Q-объектам, порождает новый Q-объект. Например, следующая инструкция порождает Q-объект, описывающий дизъюнкцию (OR) двух запросов вида “question__startswith”:

Q(question__startswith=’Who’) | Q(question__startswith=’What’)

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

WHERE question LIKE ‘Who%’ OR question LIKE ‘What%’

Комбинируя Q-объекты с помощью операторов & и |, можно составлять запросы произвольной сложности. Допускается также группировка с помощью скобок.

Любому методу поиска, принимающему именованные аргументы (например, filter(), exclude(), get()), можно передать также один или несколько Q-объектов в виде позиционных аргументов. Если методу поиска передано несколько Q объектов, то они объединяются оператором AND, например:

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-объекты и именованные аргументы. Все аргументы (вне зависимости от вида)

442

Приложение B. Справочник по API доступа к базе данных

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

Poll.objects.get(

Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), question__startswith=’Who’)

и эквивалентен предыдущему. А такой – недопустим:

# НЕДОПУСТИМЫЙ ЗАПРОС Poll.objects.get(

question__startswith=’Who’,

Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))

Дополнительные примеры можно найти на странице http://www.django­ project.com/documentation/models/or_lookups/.

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

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

Например, объект e класса Entry может получить ассоциированный с ним объект Blog, обратившись к атрибуту e.blog.

Кроме того, Django создает методы доступа для противоположного конца отношения – от связанной модели к той, где это отношение определено. Например, объект b класса Blog может получить список связанных с ним объектов Entry из атрибута entry_set: b.entry_set.all().

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

Поиск по связанным таблицам

Django предлагает мощный и интуитивно понятный способ «следования» по отношениям при поиске. В этом случае за кулисами конструируются SQL-запросы с оператором JOIN. Чтобы соединить две модели, нужно лишь указать цепочку имен связанных полей, разделяя их двумя символами подчеркивания. Так, в следующем примере выполняется поиск всех объектов Entry, для которых поле name в связанном объекте Blog содержит строку ‘Beatles Blog’:

>>> Entry.objects.filter(blog__name__exact=’Beatles Blog’)

Цепочка имен может быть сколь угодно длинной.

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

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

443

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

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

Связи внешнего ключа

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

e = Entry.objects.get(id=2)

e.blog # возвращает связанный объект Blog.

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

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

e.save()

Если в определении поля типа ForeignKey указан атрибут null=True (то есть оно допускает значения NULL), то в него можно записать 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 # Обращение к базе для выборки ассоциированного Blog. print e.blog # Обращения к базе нет; используется версия из кэша

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

e = Entry.objects.select_related().get(id=2)

print e.blog # Обращения к базе нет; используются данные из кэша. print e.blog # Обращения к базе нет; используются данные из кэша.

Метод select_related() описан в одноименном разделе выше.

Обратные связи внешнего ключа

Отношения внешнего ключа автоматически являются симметричными – обратное отношение устанавливается по наличию поля типа ForeignKey, указывающего на другую модель.

444

Приложение B. Справочник по API доступа к базе данных

Если в модели имеется поле типа ForeignKey, то любой объект модели, на которую указывает это поле, будет иметь доступ к менеджеру, возвращающему экземпляры первой модели, связанные с данным объектом. По умолчанию этот менеджер называется FOO_set, где FOO – имя моделиисточника, записанное строчными буквами. Этот менеджер возвращает объекты QuerySet, которыми можно манипулировать (в частности, фильтровать), как описано в разделе «Выборка объектов» выше. Например:

b = Blog.objects.get(id=1)

b.entry_set.all() # Возвращает все объекты Entry, связанные с Blog.

# b.entry_set ссылается на объект Manager, возвращающий QuerySet. b.entry_set.filter(headline__contains=’Lennon’) b.entry_set.count()

Имя FOO_set можно переопределить с помощью параметра related_name в определении поля типа ForeignKey. Например, если в определении класса модели Entry написать blog = ForeignKey(Blog, related_name=’entries’), то предыдущий пример следовало бы изменить так:

b = Blog.objects.get(id=1)

b.entries.all() # Возвращает все объекты Entry, связанные с Blog.

# b.entries ссылается на объект Manager, возвращающий QuerySet. b.entries.filter(headline__contains=’Lennon’) b.entries.count()

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

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

Blog.entry_set # Возбудит исключение

# AttributeError: “Manager must be accessed via instance”.

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

•• add(obj1, obj2, ...): добавляет указанные объекты модели в набор связанных объектов, например:

b = Blog.objects.get(id=1)

e = Entry.objects.get(id=234)

b.entry_set.add(e) # Ассоциирует Entry e с Blog b.

•• create(**kwargs): создает новый объект, сохраняет его и помещает в набор связанных объектов. Возвращает вновь созданный объект:

b = Blog.objects.get(id=1)

e = b.entry_set.create(headline=’Hello’, body_text=’Hi’, pub_date=datetime.date(2005, 1, 1))

# Вызывать e.save() в этом месте не нужно – объект уже сохранен.

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

445

Ниже приводится эквивалентный (но более простой) способ:

b = Blog.objects.get(id=1)

e = Entry(blog=b, headline=’Hello’, body_text=’Hi’, pub_date=datetime.date(2005, 1, 1))

e.save()

Отметим, что нет необходимости указывать в модели именованный аргумент, который определяет связь. В примере выше мы не передавали методу create() параметр blog. Django сам понимает, что поле blog в новом объекте Entry должно содержать b.

•• remove(obj1, obj2, ...): удаляет указанные объекты модели из набора связанных объектов:

b = Blog.objects.get(id=1)

e = Entry.objects.get(id=234)

b.entry_set.remove(e) # Разрывает связь между Entry e и Blog b.

Во избежание рассогласования базы данных этот метод существует только для объектов типа ForeignKey с атрибутом null=True. Если в связанное поле нельзя записать значение None (NULL), то объект нельзя просто так удалить из отношения. В примере выше удаление e из b.entry_set() эквивалентно операции e.blog = None, а поскольку объект blog не имеет атрибута null=True, то такое действие недопустимо.

•• clear(): удаляет все объекты из связанного набора:

b = Blog.objects.get(id=1) b.entry_set.clear()

Отметим, что сами связанные объекты не удаляются из базы, разрывается лишь их связь с родительским объектом.

Как и remove(), метод clear() доступен только для объектов типа ForeignKey с атрибутом null=True.

Чтобы осуществить массовое изменение набора связанных объектов, произведите присваивание этому набору некоторого итерируемого объекта, например:

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

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

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

446

Приложение B. Справочник по API доступа к базе данных

Отношения многие-ко-многим

Каждая сторона отношения многие-ко-многим автоматически получает доступ к противоположной стороне. Этот API работает точно так же, как обратное отношение один-ко-многим (см. предыдущий раздел). Единственное различие заключается в именовании атрибутов: модель, в которой определено поле типа ManyToManyField, пользуется именем атрибута самого этого поля, тогда как обратная модель пользуется именем исходной модели в нижнем регистре, к которому добавляется суффикс ‘_set’ (так же как для обратных связей один-ко-многим).

Поясним эти соглашения на примере:

e = Entry.objects.get(id=3)

e.authors.all() # Возвращает все объекты Author для данного Entry e.authors.count()

e.authors.filter(name__contains=’John’)

a = Author.objects.get(id=5)

a.entry_set.all() # Возвращает все объекты Entry для данного Author

Как и в случае ForeignKey, для поля типа ManyToManyField можно указать параметр related_name. Если бы в предыдущем примере для поля типа ManyToManyField в объекте Entry был указан параметр related_name=’entries’, то у каждого экземпляра Author был бы атрибут entries_set, а не entry_set.

Как реализуются обратные связи?

В некоторых системах­ объектно-реляционного отображения требуется явно определять обе стороны связи. Разработчики Django считают это нарушением принципа DRY (Не повторяйся), поэтому в Django достаточно определить только одну сторону связи. Но как такое возможно? Ведь класс модели ничего не знает о тех классах, с которыми связан, до тех пор пока они не будут загружены.

Ответ кроется в параметре INSTALLED_APPS. При первой загрузке любой модели Django обходит все модели, определенные в приложениях, которые перечислены в INSTALLED_APPS, и создает в памяти необходимые обратные связи. Стало быть, одна из функций параметра INSTALLED_APPS – сообщить Django обо всех существующих моделях.

Запросы к связанным объектам

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

Удаление объектов

447

можно использовать как сам объект, так и значение первичного ключа объекта.

Например, если имеется объект b класса Blog со значением id=5, то следующие три запроса будут эквивалентны:

Entry.objects.filter(blog=b)

# Запрос

с

участием экземпляра объекта

Entry.objects.filter(blog=b.id) # Запрос

с

участием id экземпляра

Entry.objects.filter(blog=5)

# В этом

запросе значение id задано явно

Удаление объектов

Метод удаления называется delete(). Он производит немедленное удаление объекта и не имеет возвращаемого значения:

e.delete()

Разрешено также групповое удаление. В каждом объекте QuerySet имеется метод delete(), который удаляет все объекты из данного набора. Например, в следующем примере мы удаляем все объекты Entry, для которых год в дате pub_date равен 2005:

Entry.objects.filter(pub_date__year=2005).delete()

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

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

# Будет удален объект Blog и все связанные с ним объекты Entry. b.delete()

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

Entry.objects.all().delete()

Вспомогательные функции

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

448

Приложение B. Справочник по API доступа к базе данных

Функция get_object_or_404()

Одна из распространенных идиом – вызвать метод get() и возбудить исключение Http404, если объект не существует. Она инкапсулирована в функции get_object_or_404(), которая принимает в первом аргументе модель Django, а также произвольное количество именованных аргументов, которые передает функции get() менеджера, подразумеваемого по умолчанию. Если объект не существует, функция возбуждает исключение Http404. Например:

# Получить объект Entry с первичным ключом 3 e = get_object_or_404(Entry, pk=3)

Когда этой функции передается модель, для выполнения запроса get() она использует менеджер, подразумеваемый по умолчанию. Если вас это не устраивает или вы хотите произвести поиск в списке связанных объектов, то можно передать функции get_object_or_404() нужный объект Manager:

#Получить автора записи e в блоге по имени ‘Fred’ a = get_object_or_404(e.authors, name=’Fred’)

#Воспользоваться нестандартным менеджером ‘recent_entries’ для

#поиска записи с первичным ключом 3

e = get_object_or_404(Entry.recent_entries, pk=3)

Функция get_list_or_404()

Эта функция ведет себя так же, как get_object_or_404(), но вместо get() вызывает filter(). Если возвращается пустой список, она возбуждает исключение Http404.

Работа с SQL напрямую

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

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

Наконец, важно отметить, что уровень доступа к базе данных в Django – не более чем интерфейс к вашей СУБД. К базе данных можно обращаться и с помощью других инструментов, языков программирования и фреймворков, ничего уникального Django в нее не привносит.

С

Справочник по обобщенным представлениям

В главе 11 мы познакомились с обобщенными представлениями, но оставили за кадром многие любопытные детали. В этом приложении мы опишем все имеющиеся обобщенные представления вместе с их параметрами. Но не пытайтесь вникнуть в приведенный ниже материал, не прочитав предварительно главу 11. Возможно, вам также понадобится освежить в памяти определенные в ней классы Book, Publisher и Author, поскольку они встречаются в примерах.

Аргументы, общие для всех обобщенных представлений

Большинство описываемых представлений принимают аргументы, которые изменяют их поведение. Многие аргументы используются одинаково во всех представлениях. В табл. C.1 описаны такие общие аргументы; в любом обобщенном представлении они интерпретируются в точности так, как указано в таблице.

Таблица C.1. Аргументы, общие для всех обобщенных представлений

Аргумент

Описание

 

 

allow_empty

Булевский флаг, определяющий необходимость отобра-

 

жения страницы в случае отсутствия объектов. Если

 

он равен False и объектов нет, то вместо вывода пустой

 

страницы представление отправляет ошибку 404.

 

По умолчанию равен True.

context_processors

Список дополнительных контекстных процессоров

 

(помимо­ подразумеваемых по умолчанию), приме­

 

няемых­ к шаблону­ представления. Подробнее о кон­

 

текстных процессорах см. главу 9.