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

Django_-_podrobnoe_rukovodstvo

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

220

Глава 9. Углубленное изучение шаблонов­

щий список книг для заданного объекта Author. Использоваться он будет следующим образом:

{% books_for_author author %}

А результат будет таким:

<ul>

<li>Кошка в шляпке</li> <li>Прыг-скок</li>

<li>О зеленых яйцах и ветчине</li> </ul>

Сначала определим функцию, которая принимает аргумент и возвращает результат в виде словаря с данными. Отметим, что нам требуется вернуть всего лишь словарь, а не что-то более сложное. Он будет использоваться в качестве контекста для фрагмента шаблона:­

def books_for_author(author):

books = Book.objects.filter(authors__id=author.id) return {‘books’: books}

Далее создадим шаблон­ для отображения результатов, возвращаемых данным тегом. В нашем случае шаблон­ совсем простой:

<ul>

{% for book in books %} <li>{{ book.title }}</li>

{% endfor %} </ul>

Наконец, создадим и зарегистрируем включающий тег, обратившись к методу inclusion_tag() объекта Library.

Если допустить, что шаблон­ находится в файле book_snippet.html, то зарегистрировать тег нужно следующим образом:

register.inclusion_tag(‘book_snippet.html’)(books_for_author)

Начиная с версии Python 2.4 работает также синтаксис декораторов, поэтому этот код можно написать и так:

@register.inclusion_tag(‘book_snippet.html’) def books_for_author(author):

# ...

Иногда включающему тегу бывает необходимо получить информацию из контекста родительского шаблона­. Чтобы обеспечить такую возможность, Django предоставляет атрибут takes_context для включающих тегов. Если указать его при создании включающего тега, то у тега не будет обязательных аргументов, а функция реализации будет принимать единственный аргумент – контекст шаблона­ со значениями, определенными на момент вызова тега.

Собственные загрузчики шаблонов­

221

Допустим, например, что вы хотите написать включающий тег, который всегда будет использоваться в контексте, где определены переменные home_link и home_title, указывающие на главную страницу. Тогда функция на Python будет выглядеть следующим образом:

@register.inclusion_tag(‘link.html’, takes_context=True) def jump_link(context):

return {

‘link’: context[‘home_link’], ‘title’: context[‘home_title’],

}

Примечание

Первый параметр функции должен называться context.

Шаблон­ link.html мог бы содержать такой текст:

Перейти прямо на <a href=”{{ link }}”>{{ title }}</a>.

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

{% jump_link %}

Собственные загрузчики шаблонов­

Встроенные в Django загрузчики шаблонов­ (описаны в разделе «Загрузка шаблонов­ – взгляд изнутри») обычно полностью отвечают нашим потребностям, но совсем не сложно написать свой загрузчик, если потребуется какая-то особая логика. Например, можно реализовать загрузку шаблонов­ из базы данных, или непосредственно из репозитория Subversion, используя библиотеки доступа к репозиториям Subversion для языка Python, или (как мы скоро покажем) из ZIP-архива.

Загрузчик шаблонов­ (описываемый строкой в параметре TEMPLATE_ LOADERS) – это вызываемый объект со следующим интерфейсом:

load_template_source(template_name, template_dirs=None)

Аргумент template_name – это имя загружаемого шаблона­ (в том виде, в каком оно передается методу loader.get_template() или loader.select_ template()), а template_dirs – необязательный список каталогов, используемый вместо TEMPLATE_DIRS.

Если загрузчик успешно загрузил шаблон,­ он должен вернуть кортеж (template_source, template_path). Здесь template_source – это строка

стекстом шаблона,­ которая будет передана компилятору шаблонов,­

аtemplate_path – путь к каталогу, откуда был загружен шаблон­. Этот путь можно будет показать пользователю для отладки.

222

Глава 9. Углубленное изучение шаблонов­

Если загрузчик не смог загрузить шаблон,­ он должен возбудить исключение django.template.TemplateDoesNotExist.

Каждый загрузчик должен также иметь атрибут is_usable. Это булевское значение, которое сообщает системе­ шаблонов,­ доступен ли данный загрузчик в имеющейся инсталляции Python. Например, загрузчик шаблонов­ из eggs-пакетов устанавливает is_usable в False, если не установлен модуль pkg_resources, поскольку без него невозможно читать содержимое eggs-пакетов.

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

from django.conf import settings

from django.template import TemplateDoesNotExist import zipfile

def load_template_source(template_name, template_dirs=None): “Загрузчик шаблонов­ из ZIP-файла.”

template_zipfiles = getattr(settings, “TEMPLATE_ZIP_FILES”, [])

# Исследовать каждый ZIP-файл в TEMPLATE_ZIP_FILES. for fname in template_zipfiles:

try:

z = zipfile.ZipFile(fname) source = z.read(template_name)

except (IOError, KeyError): continue

z.close()

# Шаблон­ найден, вернем его содержимое. template_path = “%s:%s” % (fname, template_name) return (source, template_path)

# Сюда попадаем, только если шаблон­ не удалось загрузить raise TemplateDoesNotExist(template_name)

#Этот загрузчик всегда доступен (т. к. модуль zipfile включен в

#дистрибутив Python)

load_template_source.is_usable = True

Чтобы воспользоваться этим загрузчиком, осталось лишь добавить его в параметр TEMPLATE_LOADERS. Если допустить, что представленный выше код находится в пакете mysite.zip_loader, то в параметр TEMPLATE_LOADERS следует добавить строку mysite.zip_loader.load_template_source.

Настройка системы­ шаблонов­ для работы в автономном режиме

223

Настройка системы­ шаблонов­ для работы в автономном режиме

Примечание

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

Обычно Django загружает всю необходимую ему конфигурационную информацию из своего конфигурационного файла, а также из параметров в модуле, на который указывает переменная окружения DJANGO_ SETTINGS_MODULE. (Об этом рассказывалось во врезке «Специальное приглашение Python» в главе 4.) Но если вы используете систему­ шаблонов­ независимо от Django, то зависимость от этой переменной окружения начинает доставлять неудобства, так как, скорее всего, вы захотите настроить систему­ шаблонов­ как часть своего приложения, а не заводить какие-то посторонние файлы параметров и ссылаться на них с помощью переменных окружения.

Для решения этой проблемы необходимо задействовать режим ручной настройки, который полностью описан в приложении D. Если в двух словах, то вам потребуется импортировать необходимые части системы­ шаблонов,­ а затем, еще до обращения к какой-либо функции, связанной с шаблонами,­ вызвать метод django.conf.settings.configure(), передав ему все необходимые параметры настройки.

Вам может потребоваться по меньшей мере определить параметры TEMPLATE_DIRS (если вы собираетесь пользоваться загрузчиками шаб­ лонов), DEFAULT_CHARSET (хотя подразумеваемой по умолчанию кодировки utf-8 обычно достаточно) и TEMPLATE_DEBUG. Все имеющиеся параметры описаны в приложении D; обращайте особое внимание на параметры, начинающиеся с TEMPLATE_.

Что дальше?

В следующей главе мы столь же подробно рассмотрим работу с моделями в Django.

10

Углубленное изучение моделей

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

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

Напомним, как выглядят модели для базы данных с информацией о книгах, авторах и издательствах из главы 5.

from django.db import models

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 Author(models.Model):

first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) e-mail = models.E-mailField()

def __unicode__(self):

return u’%s %s’ % (self.first_name, self.last_name)

class Book(models.Model):

title = models.CharField(max_length=100) authors = models.ManyToManyField(Author)

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

225

publisher = models.ForeignKey(Publisher) publication_date = models.DateField()

def __unicode__(self): return self.title

Как мы показали в главе 5, доступ к значению конкретного поля объекта базы данных сводится к использованию атрибута. Например, чтобы узнать название книги с идентификатором 50, мы пишем:

>>>from mysite.books.models import Book

>>>b = Book.objects.get(id=50)

>>>b.title

u’The Django Book’

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

Доступ к значениям внешнего ключа

При обращении к полю типа ForeignKey возвращается связанный объект модели. Рассмотрим пример:

>>>b = Book.objects.get(id=50)

>>>b.publisher

<Publisher: Apress Publishing>

>>> b.publisher.website u’http://www.apress.com/’

Для полей типа ForeignKey API доступа работает и в обратном направлении, но несколько иначе вследствие несимметричной природы отношения. Чтобы получить список книг, опубликованных данным издательством, нужно воспользоваться методом publisher.book_set.all():

>>>p = Publisher.objects.get(name=’Apress Publishing’)

>>>p.book_set.all()

[<Book: The Django Book>, <Book: Dive Into Python>, ...]

В действительности book_set – просто объект QuerySet (см. главу 5), поэтому к нему могут применяться обычные операции фильтрации и извлечения подмножества записей, например:

>>>p = Publisher.objects.get(name=’Apress Publishing’)

>>>p.book_set.filter(name__icontains=’django’) [<Book: The Django Book>, <Book: Pro Django>]

Имя атрибута book_set образуется путем добавления суффикса _set к имени модели, записанному строчными буквами.

Доступ к полям типа многие-ко-многим

С полями типа многие-ко-многим дело обстоит так же, как с внешними ключами, только в данном случае мы работаем не с экземплярами мо-

226

Глава 10. Углубленное изучение моделей

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

>>>b = Book.objects.get(id=50)

>>>b.authors.all()

[<Author: Adrian Holovaty>, <Author: Jacob Kaplan-Moss>]

>>>b.authors.filter(first_name=’Adrian’) [<Author: Adrian Holovaty>]

>>>b.authors.filter(first_name=’Adam’)

[]

Также возможно выполнить обратный запрос. Чтобы получить все книги, написанные данным автором, следует воспользоваться атрибутом author.book_set:

>>>a = Author.objects.get(first_name=’Adrian’, last_name=’Holovaty’)

>>>a.book_set.all()

[<Book: The Django Book>, <Book: Adrian’s Other Book>]

Как и для полей типа ForeignKey, имя атрибута book_set образуется путем добавления суффикса _set к имени модели, записанному строчными буквами.

Изменение схемы базы данных

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

Но сначала упомянем некоторые особенности работы уровня доступа к базе данных в Django.

•• Django будет «громко возмущаться», если модель содержит поле, отсутствующее в таблице базы данных. Ошибка произойдет при первой же попытке воспользоваться API для выполнения запроса к данной таблице (то есть на этапе выполнения, а не компиляции).

•• Django безразлично, что в таблице могут быть столбцы, не определенные в модели.

•• Django безразлично, что в базе данных могут быть таблицы, не представленные моделью.

Изменение схемы сводится к изменению различных частей – кода на Python и самой базы данных – в определенном порядке. Каком именно, описано в следующих разделах.

Изменение схемы базы данных

227

Добавление полей

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

Однако тут мы сталкиваемся с проблемой курицы и яйца – чтобы узнать, как описать новый столбец базы данных на языке SQL, нам нужно взглянуть на результат команды manage.py sqlall, а для этого необходимо, чтобы поле уже существовало в модели. (Отметим, что необязательно создавать столбец точно той же командой SQL, которую использовал бы Django, но все же разумно поддерживать единообразие.)

Решение проблемы состоит в том, чтобы сначала внести изменения в среде разработки, а не сразу на действующем сервере. (У вас ведь настроена среда для разработки и тестирования, правда?) Ниже подробно описывается последовательность действий.

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

1.Добавить поле в модель.

2.Выполнить команду manage.py sqlall [ваше приложение] и посмотреть на созданную ею команду CREATE TABLE для интересующей вас модели. Записать, как выглядит определение нового столбца.

3.Запустить интерактивный клиент СУБД (например, psql или mysql, либо просто команду manage.py dbshell). Добавить столбец командой

ALTER TABLE.

4.Запустить интерактивный интерпретатор Python командой manage. py shell и убедиться, что новое поле добавлено правильно. Для этого следует импортировать модель и выбрать записи из таблицы (например, MyModel.objects.all()[:5]). Если все было сделано правильно, то это предложение отработает без ошибок.

Затем можно выполнить следующие действия на действующем сервере:

1.Запустить интерактивный клиент СУБД.

2.Выполнить ту же команду ALTER TABLE, которая использовалась на третьем шаге при добавлении столбца в среде разработки.

3.Добавить поле в модель. Если вы пользуетесь системой­ управления версиями и на шаге 1 при добавлении столбца в среде разработки вы вернули измененную модель в репозиторий, то теперь самое время синхронизировать локальную копию кода на действующем сервере (в случае Subversion это делается командой svn update).

4.Перезапустить веб-сервер, чтобы изменения вступили в силу.

228

Глава 10. Углубленное изучение моделей

Проиллюстрируем эту процедуру на примере добавления поля num_pages в модель Book из главы 5. Сначала изменим модель в среде разработки:

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)

def __unicode__(self): return self.title

Примечание

Если хотите знать, зачем включены атрибуты blank=True и null=True, прочитайте раздел «Как сделать поле необязательным» в главе 6 и врезку «Добавление полей со спецификатором NOT NULL».

Добавление полей со спецификатором NOT NULL

Мы хотели привлечь ваше внимание к одной тонкости. При добавлении в модель поля num_pages мы указали атрибуты blank=True

и null=True, поэтому сразу после создания новый столбец будет содержать NULL во всех записях таблицы.

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

ALTER TABLE books_book ADD COLUMN num_pages integer;

UPDATE books_book SET num_pages=0;

ALTER TABLE books_book ALTER COLUMN num_pages SET NOT NULL;

COMMIT;

Но если вы решите идти этим путем, не забудьте убрать атрибуты blank=True и null=True из описания столбца в модели.

Далее следует выполнить команду manage.py sqlall и взглянуть на созданную команду CREATE TABLE. Она должно выглядеть примерно так (точный вид зависит от СУБД):

CREATE TABLE “books_book” (

“id” serial NOT NULL PRIMARY KEY, “title” varchar(100) NOT NULL,

“publisher_id” integer NOT NULL REFERENCES “books_publisher” (“id”),

Изменение схемы базы данных

229

“publication_date” date NOT NULL, “num_pages” integer NULL

);

Новому столбцу соответствует строка:

“num_pages” integer NULL

Теперь запустим интерактивный клиент для тестовой базы данных, набрав psql (в случае PostgreSQL) и выполним команду:

ALTER TABLE books_book ADD COLUMN num_pages integer;

Выполнив команду ALTER TABLE, проверим, что все работает нормально. Для этого запустим интерпретатор Python и выполним такой код:

>>>from mysite.books.models import Book

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

Если все прошло без ошибок, то перейдем на действующий сервер и выполним там команду ALTER TABLE. Затем обновим модель в действующей среде и перезапустим веб-сервер.

Удаление полей

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

1.Удалить описание поля из класса модели и перезапустить веб-сервер.

2.Удалить столбец из базы данных, выполнив команду, такую как

ALTER TABLE books_book DROP COLUMN num_pages;

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

Удаление полей типа многие-ко-многим

Поскольку поля, описывающие отношения типа многие-ко-многим, отличаются от обычных, то и процедура их удаления выглядит иначе.

1.Удалить описание поля типа ManyToManyField из класса модели и перезапустить веб-сервер.

2.Удалить связующую таблицу из базы данных командой, такой как

DROP TABLE books_book_authors;

Иснова подчеркнем, что действовать надо именно в таком порядке.

Удаление моделей

Удалить модель так же просто, как и поле. Требуется выполнить следующие действия:

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