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

Django_-_podrobnoe_rukovodstvo

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

160

Глава 7. Формы

request.POST.get(‘e-mail’, ‘noreply@example.com’), [‘siteowner@example.com’],

)

return HttpResponseRedirect(‘/contact/thanks/’) return render_to_response(‘contact_form.html’,

{‘errors’: errors})

Примечание

Следует ли помещать это представление в файл books/views.py? Если оно не имеет никакого отношения к приложению, работающему с книгами, то не стоит ли разместить его где-то еще? Решать вам; Django это безразлично, поскольку адрес этого представления можно указать в конфигурации URL. Лично мы предпочли бы создать отдельный каталог contact на том же уровне, что и books. Он будет содержать файл views.py и пустой файл __init__.py.

Посмотрим, что здесь нового.

•• Мы проверяем атрибут request.method на равенство значению ‘POST’. Это условие будет соблюдено, только если форма была отправлена пользователем. Если же поступил запрос на отображение формы, то атрибут request.method будет содержать значение ‘GET’, поскольку броузер в этом случае посылает запрос GET, а не POST. Это удобный способ различить два случая: отображение формы и обработка формы.

•• Для доступа к данным формы мы пользуемся объектом request.POST, а не request.GET, потому что в HTML-теге <form> в шаблоне­ contact_form. html указан атрибут method=”post”. Если обращение к представлению произошло методом POST, то объект request.GET будет пуст.

•• Поскольку имеется два обязательных поля, то и проверять надо оба. Обратите внимание, что мы воспользовались методом request.POST. get() и указали пустую строку в качестве значения по умолчанию; это удобный и компактный способ обработки случая отсутствия ключей или данных.

•• Хотя поле e-mail необязательное, мы все равно проверяем его значение, если оно не пустое. Правда, проверка очень простая – мы лишь смотрим, содержит ли строка знак @. В действующем приложении следовало бы использовать более строгий алгоритм (он уже реализован в Django, и мы познакомимся с ним в разделе «Ваш первый класс формы» ниже).

•• Для отправки сообщения по электронной почте мы пользуемся функцией django.core.mail.send_mail. Она принимает четыре аргумента: тема, тело, адрес отправителя и список адресов получателей. send_mail – это удобная обертка вокруг класса E-mailMessage, который предоставляет и дополнительные возможности: вложения, сообщения из нескольких частей и полный контроль над почтовыми заголовками.

Создание формы для ввода отзыва

161

Отметим, что для правильной работы функции send_mail() ваш сервер должен быть настроен для отправки электронной почты, а Django следует сообщить адрес сервера исходящей почты. Подробная информация приведена на странице http://docs.djangoproject. com/en/dev/topics/email/.

•• После отправки почты мы возвращаем объект HttpResponseRedirect и тем самым переадресуем броузер на страницу с сообщением об успешном выполнении. Реализацию этой страницы оставляем вам в качестве упражнения (это простая тройка: представление/образец URL/шаблон),­ однако объясним, почему мы решили воспользоваться переадресацией вместо, например, простого вызова метода render_ to_response() с шаблоном­.

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

Следует всегда прибегать к переадресации после успешной обработки POST-запросов. Это повсеместно распространенная практика.

Представление, показанное выше, работает, но функции контроля вызывают некоторое чувство неудовлетворенности. А что если в форме десяток полей? Так и будем вручную выписывать все эти предложения if?

Есть и другая проблема: повторное отображение формы. Если были обнаружены ошибки, то принято повторно выводить форму, помещая в поля данные, уже введенные пользователем, чтобы он мог увидеть, где ошибся (и не вынуждая его повторно вводить правильные данные). Мы могли бы вручную передать полученные данные обратно в шаблон,­ но, чтобы вставить значение в нужное место, пришлось бы заниматься каждым полем формы:

# views.py

def contact(request): errors = []

if request.method == ‘POST’:

if not request.POST.get(‘subject’, ‘’): errors.append(‘Введите тему.’)

if not request.POST.get(‘message’, ‘’): errors.append(‘Введите сообщение.’)

if request.POST.get(‘e-mail’) and ‘@’ not in request.POST[‘e-mail’]: errors.append(Введите правильный адрес e-mail.’)

if not errors: send_mail(

162

Глава 7. Формы

request.POST[‘subject’],

request.POST[‘message’], request.POST.get(‘e-mail’, ‘noreply@example.com’), [‘siteowner@example.com’],

)

return HttpResponseRedirect(‘/contact/thanks/’) return render_to_response(‘contact_form.html’, {

‘errors’: errors,

‘subject’: request.POST.get(‘subject’, ‘’), ‘message’: request.POST.get(‘message’, ‘’), ‘e-mail’: request.POST.get(‘e-mail’, ‘’),

})

# contact_form.html

<html>

<head>

<title>Свяжитесь с нами</title> </head>

<body>

<h1>Свяжитесь с нами</h1>

{% if errors %} <ul>

{% for error in errors %} <li>{{ error }}</li>

{% endfor %} </ul>

{% endif %}

<form action=”/contact/” method=”post”>

<p>Тема: <input type=”text” name=”subject” value=”{{ subject }}”></p> <p>Ваш e-mail (необязательно):

<input type=”text” name=”e-mail” value=”{{ e-mail }}”>

</p>

<p>Сообщение:

<textarea name=”message” rows=”10” cols=”50”> **{{ message }}**

</textarea>

</p>

<input type=”submit” value=”Отправить”> </form>

</body>

</html>

Слишком много ручной работы, а следовательно, и возможностей допустить ошибку. Надеемся, что вы уже задумались о том, что можно было бы воспользоваться какой-нибудь высокоуровневой библиотекой для обработки форм и контроля данных.

Ваш первый класс формы

163

Ваш первый класс формы

В состав Django входит библиотека django.forms, предназначенная для решения многих проблем, с которыми мы столкнулись в этой главе: от вывода HTML-форм до контроля данных. Давайте переработаем приложение для ввода отзывов с использованием этой библиотеки.

Библиотека «newforms» в Django

Всообществе Django ходят разговоры о некоей библиотеке django. newforms. Но при этом имеется в виду именно библиотека django. forms, рассматриваемая в этой главе.

Впервую официально выпущенную версию Django входила запутанная и сложная система­ для работы с формами – django.forms. Позже она была полностью переписана, и новая версия названа django.newforms, чтобы ее не путали со старой. Но из выпуска Djan- go 1.0 старая django.forms была исключена, а django.newforms стала называться django.forms.

Чтобы воспользоваться библиотекой форм, нужно прежде всего определить класс Form для каждой HTML-формы (тега <form>) в приложении. У нас имеется всего один тег <form>, поэтому мы определим один класс Form. Этот класс может находиться где угодно, в том числе и прямо в файле views.py, но в сообществе принято соглашение помещать все классы Form в отдельный файл forms.py. Создайте этот файл в том же каталоге, где находится views.py, и введите в него такой код:

from django import forms

class ContactForm(forms.Form): subject = forms.CharField()

e-mail = forms.EmailField(required=False) message = forms.CharField()

Здесь все интуитивно понятно и напоминает синтаксис моделей Django. Каждое поле формы представлено подклассом класса Field, в данном случае встречаются только поля типа CharField и EmailField, и сами поля являются атрибутами класса Form. По умолчанию каждое поле является обязательным, поэтому, чтобы сделать поле e-mail необязательным, мы добавили атрибут required=False.

Теперь откроем интерактивный интерпретатор Python и посмотрим, что этот класс умеет делать. Прежде всего, он может представить себя в виде HTML:

>>>from contact.forms import ContactForm

>>>f = ContactForm()

164 Глава 7. Формы

>>> print f

<tr><th><label for=”id_subject”>Тема:</label></th><td>

<input type=”text” name=”subject” id=”id_subject” /></td></tr> <tr><th><label for=”id_e-mail”>E-mail:</label></th><td> <input type=”text” name=”e-mail” id=”id_e-mail” /></td></tr> <tr><th><label for=”id_message”>Сообщение:</label></th><td> <input type=”text” name=”message” id=”id_message” /></td></tr>

Django добавляет к каждому полю метку, а также теги <label> для пользователей с ограниченными возможностями. Идея в том, чтобы поведение по умолчанию было оптимальным.

По умолчанию содержимое класса выводится в виде HTML-таблицы (тег <table>), но есть и другие встроенные варианты, например:

>>> print f.as_ul()

<li><label for=”id_subject”>Тема:</label>

<input type=”text” name=”subject” id=”id_subject” /></li> <li><label for=”id_e-mail”>E-mail:</label>

<input type=”text” name=”e-mail” id=”id_e-mail” /></li> <li><label for=”id_message”>Сообщение:</label>

<input type=”text” name=”message” id=”id_message” /></li>

>>> print f.as_p()

<p><label for=”id_subject”>Тема:</label>

<input type=”text” name=”subject” id=”id_subject” /></p> <p><label for=”id_e-mail”>E-mail:</label>

<input type=”text” name=”e-mail” id=”id_e-mail” /></p> <p><label for=”id_message”>Сообщение:</label>

<input type=”text” name=”message” id=”id_message” /></p>

Обратите внимание, что открывающие и закрывающие теги <table>, <ul> и <form> не включаются в результат, чтобы при необходимости можно было добавить дополнительные строки и выполнить иную настройку.

Все эти методы представляют собой вспомогательные функции для общего случая «вывода формы целиком». Можно также вывести HTMLразметку отдельного поля:

>>> print f[‘subject’]

<input type=”text” name=”subject” id=”id_subject” />

>>> print f[‘message’]

<input type=”text” name=”message” id=”id_message” />

Объекты Form могут также выполнять проверку данных. Чтобы продемонстрировать этот аспект, создадим еще один объект Form и передадим ему словарь, отображающий имена полей на сами данные:

>>>f = ContactForm({‘subject’: ‘Привет’, ‘e-mail’: ‘adrian@example.com’,

‘message’: ‘Отличный сайт!’})

Ассоциировав данные с экземпляром класса Form, вы создали связанную форму:

Ваш первый класс формы

165

>>> f.is_bound True

Чтобы узнать, корректны ли данные в связанной форме, вызовите ее метод is_valid(). Поскольку мы передали правильные значения для всех полей, форма успешно проходит проверку:

>>> f.is_valid() True

Если не передать поле e-mail вообще, то данные все равно будут корректны, так как для этого поля задан атрибут required=False:

>>>f = ContactForm({‘subject’: ‘Привет’, ‘message’: ‘Отличный сайт!’})

>>>f.is_valid()

True

Но если опустить subject или message, то форма уже не пройдет проверку:

>>>f = ContactForm({‘subject’: ‘Привет’})

>>>f.is_valid()

False

>>>f = ContactForm({‘subject’: ‘Привет’, ‘message’: ‘’})

>>>f.is_valid()

False

Можно получить сообщения об ошибках для конкретных полей:

>>>f = ContactForm({‘subject’: ‘Привет’, ‘message’: ‘’})

>>>f[‘message’].errors

[u’This field is required.’]

>>>f[‘subject’].errors

[]

>>>f[‘e-mail’].errors

[]

Улюбой связанной формы имеется атрибут errors, в котором хранится словарь, отображающий имена полей на списки сообщений об ошибках:

>>>f = ContactForm({‘subject’: ‘Привет’, ‘message’: ‘’})

>>>f.errors

{‘message’: [u’This field is required.’]}

Наконец, у экземпляра Form, для которого все связанные данные правильны, имеется атрибут cleaned_data. Это словарь «конвертированных» данных формы. Django не только проверяет данные, но и конвертирует их, преобразуя в подходящие типы Python:

>>>f = ContactForm({‘subject’: ‘Привет’, ‘e-mail’: ‘adrian@example.com’,

‘message’: ‘Отличный сайт!’})

>>>f.is_valid()

True

>>> f.cleaned_data

166

Глава 7. Формы

{‘message’: u’Отличный сайт!’, ‘e-mail’: u’adrian@example.com’, ‘subject’: u’Привет’}

В нашей форме имеются только строки, результатом конвертирования которых являются объекты Unicode, но если бы присутствовали поля типа IntegerField или DateField, то библиотека поместила бы в словарь cleaned_data соответственно целые числа или объекты datetime.date.

Использование объектов Form в представлениях

Немного познакомившись с классами Form, мы можем воспользоваться ими, чтобы заменить ручные проверки в представлении contact(). Ниже приводится новая версия представления contact(), использующая библиотеку forms:

# views.py

from django.shortcuts import render_to_response from mysite.contact.forms import ContactForm

def contact(request):

if request.method == ‘POST’: form = ContactForm(request.POST) if form.is_valid():

cd = form.cleaned_data send_mail(

cd[‘subject’],

cd[‘message’],

cd.get(‘e-mail’, ‘noreply@example.com’), [‘siteowner@example.com’],

)

return HttpResponseRedirect(‘/contact/thanks/’) else:

form = ContactForm()

return render_to_response(‘contact_form.html’, {‘form’: form})

# contact_form.html

<html>

<head>

<title>Свяжитесь с нами</title> </head>

<body>

<h1>Свяжитесь с нами</h1> {% if form.errors %}

<p style=”color: red;”>

Исправьте следующие ошибки{{ form.errors|pluralize }}1.

1В данном случае применение фильтра pluralize излишне. При использовании русифицированной версии фильтра (rupluralize), упоминавшейся выше, эта строкамоглабывыглядетьтак:Исправьте {{ form.errors|rupluralize:”следующую ошибку,следующие ошибки”}}. – Прим. науч. ред.

Ваш первый класс формы

167

</p> {% endif %}

<form action=”” method=”post”> <table>

{{ form.as_table }} </table>

<input type=”submit” value=”Отправить”> </form>

</body>

</html>

Только посмотрите, сколько удалось убрать лишнего! Библиотека форм в Django берет на себя создание HTML-разметки, проверку и преобра­ зование данных и повторное отображение формы с сообщениями об ошибках.

Поэкспериментируйте с этим механизмом. Загрузите форму, отправьте ее, не заполняя никаких полей, неправильно заполнив адрес электронной почты и, наконец, правильно заполнив все поля. (Разумеется, в зависимости от конфигурации почтового сервера вы можете получить сообщение об ошибке при вызове send_mail(), но это уже совсем другая история.)

Изменение способа отображения полей

Во время экспериментов вы, наверное, сразу же обратили внимание, что поле message представлено тегом <input type=”text”>, хотя должно было бы выводиться в виде тега <textarea>. Это можно поправить, определив для поля атрибут widget:

from django import forms

class ContactForm(forms.Form): subject = forms.CharField()

e-mail = forms.EmailField(required=False) message = forms.CharField(widget=forms.Textarea)

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

Можно считать, что подклассы Field описывают логику проверки, а виджеты – логику визуализации.

Определение максимальной длины поля

Один из самых типичных видов проверки – проверка размера поля. Например, мы могли бы усовершенствовать форму ContactForm, ограничив длину поля subject 100 символами. Для этого достаточно определить для поля типа CharField атрибут max_length:

168 Глава 7. Формы

from django import forms

class ContactForm(forms.Form):

subject = forms.CharField(max_length=100) e-mail = forms.EmailField(required=False) message = forms.CharField(widget=forms.Textarea)

Имеется также необязательный атрибут min_length.

Определение начальных значений

В качестве еще одного улучшения добавим начальное значение поля subject: «Мне очень нравится ваш сайт!» (небольшая подсказка не повредит). Для этого служит аргумент initial при создании экземпляра

Form:

def contact(request):

if request.method == ‘POST’:

form = ContactForm(request.POST) if form.is_valid():

cd = form.cleaned_data send_mail(

cd[‘subject’],

cd[‘message’],

cd.get(‘e-mail’, ‘noreply@example.com’), [‘siteowner@example.com’],

)

return HttpResponseRedirect(‘/contact/thanks/’) else:

form = ContactForm(

initial={‘subject’: ‘Мне очень нравится ваш сайт!’}

)

return render_to_response(‘contact_form.html’, {‘form’: form})

Теперь это сообщение появится в поле subject.

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

Добавление собственных правил проверки

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

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

Ваш первый класс формы

169

таких правил одноразовые, и их можно включить непосредственно в класс Form.

Нам нужна дополнительная проверка поля message, поэтому добавим в класс Form метод clean_message():

from django import forms

class ContactForm(forms.Form):

subject = forms.CharField(max_length=100) e-mail = forms.EmailField(required=False) message = forms.CharField(widget=forms.Textarea)

def clean_message(self):

message = self.cleaned_data[‘message’] num_words = len(message.split())

if num_words < 4:

raise forms.ValidationError(“Слишком мало слов!”) return message

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

Наш метод clean_message() будет вызван после стандартных проверок данного поля (в данном случае после проверок, предусмотренных для обязательного поля типа CharField). Поскольку данные поля уже частично обработаны, мы выбираем значение из словаря self.cleaned_data. Кроме того, можно не думать о том, что поле не существует или пусто; об этом уже позаботился стандартный обработчик.

Для подсчета слов мы бесхитростно воспользовались методами len() и split(). Если пользователь ввел слишком мало слов, мы возбуждаем исключение ValidationError. Указанная в конструкторе исключения строка появится в списке ошибок.

Важно, что в конце метода мы явно возвращаем конвертированное значение поля. Это позволяет модифицировать значение (или преобразовать его в другой тип Python) внутри нашего метода проверки. Если забыть об инструкции return, то метод вернет None, и исходное значение будет потеряно.

Определение меток

По умолчанию метки в HTML-разметке, автоматически сгенерированной Django, образуются из имен полей путем замены знаков подчеркивания пробелами и перевода первой буквы в верхний регистр; например, для поля e-mail будет сформирована метка “E-mail”. (Знакомо, да? Точно такой же простой алгоритм применяется в моделях Django для формирования значений verbose_name для полей, см. главу 5.)

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

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