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

Django_-_podrobnoe_rukovodstvo

.pdf
Скачиваний:
306
Добавлен:
01.03.2016
Размер:
4.88 Mб
Скачать

230

Глава 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 соответствует

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]