Django_-_podrobnoe_rukovodstvo
.pdf230 |
Глава 10. Углубленное изучение моделей |
1.Удалить класс модели из файла models.py и перезапустить веб-сервер.
2.Удалить таблицу из базы данных командой, такой как
DROP TABLE books_book;
Отметим, что может понадобиться сначала удалить из базы данных зависимые таблицы, например, ссылающиеся на books_book по внешнему ключу.
Опять же не забудьте, что действовать надо в указанном порядке.
Менеджеры
Винструкции Book.objects.all() objects – это специальный атрибут, посредством которого выполняется запрос к базе данных. В главе 5 мы кратко остановились на нем, назвав менеджером модели. Теперь пришло время более детально изучить, что такое менеджеры и как с ними работать.
Вдвух словах, менеджер модели – это объект, с помощью которого Django выполняет запросы к базе данных. Каждая модель Django имеет по меньшей мере один менеджер, и вы можете создавать свои менеджеры для организации специализированных видов доступа.
Потребность создания собственного менеджера может быть вызвана двумя причинами: необходимостью добавить менеджеру дополнительные методы и/или необходимостью модифицировать исходный объект QuerySet, возвращаемый менеджером.
Добавление методов в менеджер
Добавление дополнительных методов в менеджер – это рекомендуемый способ включить в модель функциональность на уровне таблицы. Функциональность уровня таблицы доступна не в одном, а сразу в нескольких экземплярах модели. (Для реализации функциональности на уровне строк, то есть функций, применяемых к единственному объекту модели, используются методы модели, которые мы рассмотрим ниже в этой главе.)
В качестве примера, добавим в модель Book метод менеджера title_ count(), принимающий ключевое слово и возвращающий количество книг, в названиях которых встречается это слово. (Пример немного искусственный, но удобный для иллюстрации работы менеджеров.)
# models.py
from django.db import models
# ... Модели Author и Publisher опущены ...
class BookManager(models.Manager): def title_count(self, keyword):
Менеджеры |
231 |
return self.filter(title__icontains=keyword).count()
class Book(models.Model):
title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) publication_date = models.DateField()
num_pages = models.IntegerField(blank=True, null=True) objects = BookManager()
def __unicode__(self): return self.title
Имея такой менеджер, мы можем использовать его следующим образом:
>>>Book.objects.title_count(‘django’)
4
>>>Book.objects.title_count(‘python’)
18
Отметим следующие моменты:
•• Мы создали класс BookManager, который расширяет класс django.db. models.Manager. В нем определен единственный метод title_count(), выполняющий вычисление. Обратите внимание на вызов self.filter(), где self – ссылка на сам объект менеджера.
•• Мы присвоили значение BookManager() атрибуту objects модели. Тем самым мы заменили менеджер по умолчанию, который называется objects и создается автоматически, если не задан никакой другой менеджер. Назвав наш менеджер objects, а не как-то иначе, мы сохранили совместимость с автоматически создаваемыми менеджерами.
Зачем может понадобиться добавлять такие методы, как title_count()? Чтобы инкапсулировать часто употребляемые запросы и не дублировать код.
Модификация исходных объектов QuerySet
Стандартный объект QuerySet, возвращаемый менеджером, содержит все объекты, хранящиеся в таблице. Например, Book.objects.all() возвращает все книги в базе данных.
Стандартный объект QuerySet можно переопределить, заместив метод Manager.get_query_set(). Этот метод должен вернуть объект QuerySet, обладающий нужными вам свойствами.
Например, в следующей модели имеется два менеджера – один возвращает все объекты, а другой только книги Роальда Даля.
from django.db import models
# Сначала определяем подкласс класса Manager. class DahlManager(models.Manager):
232 Глава 10. Углубленное изучение моделей
def get_query_set(self):
return super(DahlManager, self).get_query_set().filter( author=’Roald Dahl’)
# Затем явно присоединяем его к модели Book. class Book(models.Model):
title = models.CharField(max_length=100) author = models.CharField(max_length=50)
# ... |
|
|
|
objects = models.Manager() |
# |
Менеджер по |
умолчанию. |
dahl_objects = DahlManager() # |
Специальный |
менеджер. |
Вэтой модели вызов Book.objects.all() вернет все книги в базе данных,
авызов Book.dahl_objects.all() – только книги, написанные Роальдом Далем. Отметим, что мы явно присвоили атрибуту objects экземпляр стандартного менеджера Manager, потому что в противном случае у нас оказался бы только менеджер dahl_objects.
Разумеется, поскольку get_query_set() возвращает объект QuerySet, к нему можно применять методы filter(), exclude() и все остальные методы QuerySet. Поэтому каждая из следующих инструкций является допустимой:
Book.dahl_objects.all()
Book.dahl_objects.filter(title=’Matilda’)
Book.dahl_objects.count()
В этом примере демонстрируется еще один интересный прием: использование нескольких менеджеров в одной модели. К любой модели можно присоединить сколько угодно экземпляров класса Manager(). Таким способом легко можно определить фильтры, часто применяемые к модели.
Рассмотрим следующий пример.
class MaleManager(models.Manager): def get_query_set(self):
return super(MaleManager, self).get_query_set().filter(sex=’M’)
class FemaleManager(models.Manager): def get_query_set(self):
return super(FemaleManager, self).get_query_set().filter(sex=’F’)
class Person(models.Model):
first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50)
sex = models.CharField(max_length=1, choices=((‘M’, ‘Male’), (‘F’, ‘Female’)))
people = models.Manager() men = MaleManager() women = FemaleManager()
Теперь можно обращаться к методам Person.men.all(), Person.women.all()
и Person.people.all() и получать предсказуемые результаты.
Методы модели |
233 |
Следует отметить, что первый менеджер, определенный в классе модели, имеет особый статус. Django считает его менеджером по умолчанию, и в некоторых частях фреймворка (но не в административном интерфейсе) только этот менеджер и будет использоваться. Поэтому нужно тщательно продумывать, какой менеджер назначать по умалчанию, чтобы вдруг не оказаться в ситуации, когда переопределенный метод get_query_ set() возвращает не те объекты, которые вам нужны.
Методы модели
Вы можете определять в модели собственные методы и тем самым наделять свои объекты дополнительной функциональностью на уровне строк. Если менеджеры предназначены для выполнения операций над таблицей в целом, то методы модели применяются к одному экземпляру модели.
Методы модели хорошо подходят для инкапсуляции всей бизнес-логики в одном месте, а именно в модели. Проще всего объяснить это на примере. Рассмотрим модель, в которой имеется несколько пользовательских методов:
from django.contrib.localflavor.us.models import USStateField from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) birth_date = models.DateField()
address = models.CharField(max_length=100) city = models.CharField(max_length=50)
state = USStateField() # Да, это относится только к США...
def baby_boomer_status(self):
“””Показывает, родился ли человек во время, до или после бума рождаемости.”””
import datetime
if datetime.date(1945, 8, 1) <= self.birth_date \
and self.birth_date <= datetime.date(1964, 12, 31): return “Baby boomer”
if self.birth_date < datetime.date(1945, 8, 1): return “Pre-boomer”
return “Post-boomer”
def is_midwestern(self):
“Возвращает True, если человек родом со Среднего Запада.” return self.state in (‘IL’, ‘WI’, ‘MI’, ‘IN’, ‘OH’, ‘IA’, ‘MO’)
def _get_full_name(self): “Возвращает полное имя.”
return u’%s %s’ % (self.first_name, self.last_name) full_name = property(_get_full_name)
234 |
Глава 10. Углубленное изучение моделей |
Последний метод в этом примере является свойством. (Подробнее о свойствах можно прочитать на странице http://www.python.org/download/ releases/2.2/descrintro/#property)1. Вот как используются эти методы:
>>>p = Person.objects.get(first_name=’Barack’, last_name=’Obama’)
>>>p.birth_date
datetime.date(1961, 8, 4)
>>>p.baby_boomer_status() ‘Baby boomer’
>>>p.is_midwestern()
True
>>>p.full_name # Обратите внимание, что этот метод выглядит как
#атрибут
u’Barack Obama’
Прямое выполнение SQL-запросов
Интерфейс доступа к базе данных в Django имеет определенные огра ничения, поэтому иногда возникает необходимость напрямую обра титься к базе данных с SQL-запросом. Это легко сделать с помощью объекта django.db.connection, который представляет текущее соединение с базой данных. Чтобы воспользоваться им, вызовите метод connection. cursor() для получения объекта-курсора, затем метод cursor.execute(sql, [params]) – для выполнения SQL-запроса и, наконец, один из методов cursor.fetchone() или cursor.fetchall() для получения записей. Например:
>>>from django.db import connection
>>>cursor = connection.cursor()
>>>cursor.execute(“””
... SELECT DISTINCT first_name
... FROM people_person
... WHERE last_name = %s”””, [‘Lennon’])
>>>row = cursor.fetchone()
>>>print row
[‘John’]
Объекты connection и cursor реализуют в языке Python значительную часть стандартного API баз данных, о котором можно прочитать на странице http://www.python.org/peps/pep-0249.html2. Для тех, кто не знаком с API баз данных, скажем, что SQL-команду в методе cursor. execute() лучше записывать, используя символы подстановки “%s”, а не вставлять параметры непосредственно в SQL-код. В этом случае библи-
1Аналогичную информацию на русском языке можно найти на странице http://www.ibm.com/developerworks/ru/library/l-python-elegance-2/. – Прим. науч. ред.
2Аналогичную информацию на русском языке можно найти на странице http://www.intuit.ru/department/pl/python/10/. – Прим. науч. ред.
Что дальше? |
235 |
отека, реализующая API доступа к базе, автоматически добавит при необходимости кавычки и символы экранирования.
Но лучше не загромождать код представлений инструкциями вызова django.db.connection, а поместить их в отдельные методы модели или методы менеджеров. Например, приведенный выше запрос можно было бы реализовать в виде метода менеджера:
from django.db import connection, models
class PersonManager(models.Manager): def first_names(self, last_name): cursor = connection.cursor() cursor.execute(“””
SELECT DISTINCT first_name FROM people_person
WHERE last_name = %s”””, [last_name]) return [row[0] for row in cursor.fetchone()]
class Person(models.Model):
first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) objects = PersonManager()
Аиспользовать так:
>>>Person.objects.first_names(‘Lennon’) [‘John’, ‘Cynthia’]
Что дальше?
В следующей главе мы познакомимся с механизмом обобщенных представлений, который позволяет сэкономить время при создании типичных сайтов.
11
Обобщенные представления
Здесь снова возникает тема, к которой мы все время возвращаемся в этой книге: при неправильном подходе разработка веб-приложений становится скучным и однообразным занятием. До сих пор мы видели, как Django пытается устранить хотя бы частично это однообразие на уровне моделей и шаблонов, но оно поджидает веб-разработчиков и на уровне представлений тоже.
Обобщенные представления как раз и были придуманы, чтобы прекратить эти страдания. Абстрагируя общеупотребительные идиомы и шаб лоны проектирования, они позволяют быстро создавать типичные представления данных с минимумом кода. На самом деле почти все представления, с которыми мы встречались в предыдущих главах, можно переписать с помощью обобщенных представлений.
Вглаве 8 мы уже касались вопроса создания обобщенных представлений. Напомним, речь шла о том, чтобы выделить общую задачу, например отображение списка объектов, и написать код, способный отображать список любых объектов. А модель, которая описывает конкретные объекты, передавать в образец URL дополнительным параметром.
Всостав Django входят следующие обобщенные представления:
•• Для решения типичных простых задач: переадресация на другую страницу или отображение заданного шаблона;
•• Отображение страниц со списками или с подробной информацией об одном объекте. Представления event_list и entry_list из главы 8 – это примеры списковых представлений. Страница с описанием одного события – пример так называемого детального представления.
•• Вывод объектов по дате создания на страницах архива за указанный день, месяц и год. Предусмотрены также страницы детализации и страницы последних поступлений. С помощью таких представлений построены страницы блога Django (http://www.djangoproject.
Использование обобщенных представлений |
237 |
com/weblog/) с архивами за год, месяц и день. Архив типичной газеты устроен точно так же.
В совокупности эти представления обеспечивают простой интерфейс для решения многих типичных задач, с которыми сталкивается разработчик.
Использование обобщенных представлений
Для использования любого из этих представлений нужно создать словарь параметров в файлах конфигурации URL и передать его в третьем элементе кортежа, описывающего образец URL. (Об этом приеме рассказывается в разделе «Передача дополнительных параметров функциям представления» в главе 8.) В качестве примера ниже приводится простая конфигурация URL, с помощью которой можно построить статическую страницу «О программе»:
from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
urlpatterns = patterns(‘’, (r’^about/$’, direct_to_template, {
‘template’: ‘about.html’
})
)
На первый взгляд такая конфигурация выглядит довольно необычно – как же так, представление вообще без кода! – но на самом деле она ничем не отличается от примеров из главы 8. Представление direct_to_ template просто извлекает информацию из словаря в дополнительном параметре и на ее основе генерирует страницу.
Поскольку это обобщенное представление (как и все прочие) является обычной функцией представления, мы можем повторно использовать ее в собственных представлениях. Попробуем обобщить предыдущий пример, так чтобы URL вида /about/<whatever>/ отображались на статические страницы about/<whatever>.html. Для этого сначала изменим конфигурацию URL, добавив шаблон URL, ссылающийся на функцию представления:
from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template from mysite.books.views import about_pages
urlpatterns = patterns(‘’, (r’^about/$’, direct_to_template, {
‘template’: ‘about.html’
}),
(r’^about/(\w+)/$’, about_pages),
)
Затем напишем представление about_pages:
238 |
Глава 11. |
Обобщенные представления |
|
from django.http import Http404 |
|
|
from django.template import TemplateDoesNotExist |
|
|
from django.views.generic.simple import direct_to_template |
|
|
def about_pages(request, page): |
|
try:
return direct_to_template(request, template=»about/%s.html» % page) except TemplateDoesNotExist:
raise Http404()
Здесь функция представления direct_to_template вызывается как самая обычная функция. Поскольку она возвращает готовый объект HttpResponse, мы можем вернуть полученный результат без дальнейшей обработки. Нужно лишь решить, что делать в случае, когда шаб лон не будет найден. Для нас нежелательно, чтобы отсутствие шаблона приводило к ошибке сервера, поэтому мы перехватываем исключение TemplateDoesNotExist и вместо него возвращаем ошибку 404.
А нет ли здесь уязвимости?
Внимательный читатель, вероятно, заметил потенциальную брешь в защите: при конструировании имени шаблона мы включаем данные, полученные от клиента (template=”about/%s.html” % page). На первый взгляд, это классическая уязвимость с обходом каталогов (подробно обсуждается в главе 20). Но так ли это в действительности?
Не совсем. Да, специально подготовленное значение page могло бы привести к переходу в другой каталог, но приложением принимается не всякое значение, полученное из URL. Все дело в том, что в образце URL, с которым сопоставляется название страницы в URL, находится регулярное выражение \w+, а \w совпадает только с буквами и цифрами1. Поэтому небезопасные символы (точки и символы слеша) отвергаются еще до того, как попадут в представление.
Обобщенные представления объектов
Представление direct_to_template, конечно, полезно, но блистать понастоящему обобщенные представления начинают, когда возникает потребность отобразить содержимое базы данных. Поскольку эта задача встречается очень часто, в Django встроено несколько обобщенных представлений, превращающих создание списков и детальных описаний объектов в тривиальное упражнение.
1Точнее, с «символами слов», в число которых, помимо алфавитных символов и цифр, также входит символ подчеркивания. – Прим. науч. ред.
Обобщенные представления объектов |
239 |
Рассмотрим одно из таких обобщенных представлений: список объ ектов. Проиллюстрируем его на примере объекта Publisher из главы 5.
class Publisher(models.Model):
name = models.CharField(max_length=30) address = models.CharField(max_length=50) city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField()
def __unicode__(self): return self.name
class Meta:
ordering = [‘name’]
Для построения страницы со списком всех издательств составим такую конфигурацию URL:
from django.conf.urls.defaults import *
from django.views.generic import list_detail from mysite.books.models import Publisher
publisher_info = {
‘queryset’: Publisher.objects.all(),
}
urlpatterns = patterns(‘’,
(r’^publishers/$’, list_detail.object_list, publisher_info)
)
И больше ничего на Python писать не нужно. Однако шаблон придется написать. Мы можем явно сообщить представлению object_list имя шаблона, добавив ключ template_name в словарь, который передается в качестве дополнительного аргумента:
from django.conf.urls.defaults import *
from django.views.generic import list_detail from mysite.books.models import Publisher
publisher_info = {
‘queryset’: Publisher.objects.all(), ‘template_name’: ‘publisher_list_page.html’,
}
urlpatterns = patterns(‘’,
(r’^publishers/$’, list_detail.object_list, publisher_info)
)
В случае отсутствия ключа template_name представление object_list сконструирует имя шаблона из имени объекта. В данном случае будет образовано имя books/publisher_list.html, где часть books соответствует