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

Django_-_podrobnoe_rukovodstvo

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

150

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

и дают доступ к данным, отправленным соответственно методом GET или POST.

POST-данные обычно поступают из HTML-формы (тег <form>), а GET-дан­ ные – из формы или строки запроса, указанной в гиперссылке на странице.

Объекты, подобные словарю

Говоря, что объекты request.GET и request.POST похожи на словарь, мы имеем в виду, что они ведут себя как стандартные словари Python, хотя технически таковыми не являются. Так, объекты request.GET и request.POST обладают методами get(), keys() и values() и позволяют перебрать все ключи с помощью конструкции for key in request.GET.

Тогда почему мы говорим об «объектах, подобных словарю», а не просто о словарях? Потому что у объектов request.GET и request. POST имеются методы, отсутствующие у обычных словарей.

Возможно, вы и раньше встречались с такими «объектамиимитаторами», то есть объектами Python, обладающими некоторыми базовыми методами, например read(), что позволяет использовать их вместо «настоящих» файловых объектов.

Пример обработки простой формы

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

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

from django.shortcuts import render_to_response

def search_form(request):

return render_to_response(‘search_form.html’)

В главе 3 мы сказали, что представление может находиться в любом каталоге, указанном в пути Python. В данном случае поместим его в файл books/views.py.

Соответствующий шаблон­ search_form.html мог бы выглядеть так:

<html>

<head>

<title>Поиск</title>

Пример обработки простой формы

151

</head>

<body>

<form action=”/search/” method=”get”> <input type=”text” name=”q”>

<input type=”submit” value=”Найти”> </form>

</body>

</html>

Авот образец URL в файле urls.py: from mysite.books import views

urlpatterns = patterns(‘’,

# ...

(r’^search-form/$’, views.search_form),

# ...

)

Отметим, что модуль views импортируется напрямую, а не с помощью предложения from mysite.views import search_form, поскольку так короче. Этот важный подход мы рассмотрим более подробно в главе 8.

Если теперь запустить сервер разработки и зайти на страницу по адресу http://127.0.0.1:8000/search-form/, то мы увидим интерфейс поиска. Все просто.

Но если вы отправите эту форму, то получите от Django ошибку 404. Форма указывает на URL /search/, который еще не реализован. Исправим это, написав вторую функцию представления:

# urls.py

urlpatterns = patterns(‘’,

# ...

(r’^search-form/$’, views.search_form), (r’^search/$’, views.search),

# ...

)

# views.py

def search(request):

if ‘q’ in request.GET:

message = ‘Вы искали: %r’ % request.GET[‘q’] else:

message = ‘Вы отправили пустую форму.’ return HttpResponse(message)

Пока что мы просто выводим отправленный пользователем поисковый запрос, чтобы убедиться в том, что данные пришли к Django правильно,

152

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

и показать вам, как запрос проходит через различные компоненты сис­ темы. В двух словах происходит вот что:

1.В HTML-форме <form> определена переменная q. При отправке формы значение q посылается методом GET (method=”get”) на URL

/search/.

2.Представление Django, которое обрабатывает URL /search/ (search()), получает значение q из GET-запроса.

Отметим, что мы явно проверяем наличие ключа ‘q’ в объекте request. GET. Выше мы уже говорили, что нельзя доверять никаким данным, поступившим от пользователя, и даже предполагать, что данные вообще поступили. Если опустить эту проверку, то при отправке пустой формы в представлении возникнет исключение KeyError.

# ПЛОХО!

def bad_search(request):

#В следующей строчке возникнет исключение KeyError, если

#поле ‘q’ не было отправлено!

message = ‘Вы искали: %r’ % request.GET[‘q’] return HttpResponse(message)

Параметры в строке запроса

Поскольку GET-данные передаются в строке запроса (например, /search/?q=django), то для доступа к указанным в этой строке параметрам можно воспользоваться объектом request.GET. В главе 3, рассказывая о механизме конфигурации URL, мы сравнивали красивые URL в Django с более традиционными URL, принятыми в PHP/Java, такими как /time/plus?hours=3, и пообещали, что в главе 7 покажем, как можно работать с последними. Теперь вы знаете, что для доступа из представления к параметрам в строке запроса (в примере выше hours=3) нужно использовать объект request.GET.

С POST-данными можно обращаться так же, как с GET-данными, только вместо request.GET следует использовать request.POST. В чем разница между методами GET и POST? Метод GET применяется, когда единственная цель отправки формы – получить какие-то данные, а метод POST – когда с отправкой формы связан какой-то побочный эффект – изменение данных, отправка сообщения по электронной почте и вообще все, что угодно, помимо простого отображения данных. В примере поиска книги мы использовали метод GET, потому что запрос не изменяет данные на сервере. (Если вы хотите лучше разобраться в различиях между GET и POST, обратитесь к странице http://www.w3.org/2001/tag/doc/whenToUseGet. html.)

Пример обработки простой формы

153

Убедившись, что в объекте request.GET оказались ожидаемые данные, обратимся к нашей базе данных для удовлетворения запроса пользователя (код находится все в том же файле views.py):

from django.http import HttpResponse

from django.shortcuts import render_to_response from mysite.books.models import Book

def search(request):

if ‘q’ in request.GET and request.GET[‘q’]: q = request.GET[‘q’]

books = Book.objects.filter(title__icontains=q) return render_to_response(‘search_results.html’,

{‘books’: books, ‘query’: q})

else:

return HttpResponse(‘Введите поисковый запрос.’)

Опишем, что мы сделали.

•• Прежде чем обращаться к базе данных, мы убедились, что параметр ‘q’ не только существует в request.GET, но и содержит непустое значение.

•• МывоспользовалисьфильтромBook.objects.filter(title__icontains=q), чтобы найти все книги, в названии которых встречается введенное пользователем значение. icontains – это тип поиска (объясняется в главе 5 и приложении B), а все предложение можно сформулировать так: «Получить книги, название которых содержит q без учета регистра».

Это очень примитивный способ поиска книг. Мы не рекомендуем применять запрос типа icontains в больших промышленных базах данных, так как это может оказаться очень медленно. (В действующих приложениях лучше использовать какую-нибудь специализированную поисковую систему­. Поискав в сети open-source full-text search (полнотекстовый поиск с открытым исходным кодом), вы получите некоторое представление об имеющихся возможностях.)

•• Мы передали список books объектов Book в шаблон­. Код шаблона­ search_results.html мог бы выглядеть так:

<p>Вы искали: <strong>{{ query }}</strong></p>

{% if books %}

<p>Найдено {{ books|length }} книг{{ books|pluralize }}.</p> <ul>

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

</ul> {% else %}

154

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

<p>Книг, удовлетворяющих заданному критерию, не найдено.</p> {% endif %}

Обратите внимание на фильтр pluralize, который выводит окончание ‘s’1, если найдено более одной книги.2

Усовершенствование примера обработки формы

Как обычно, мы начали с простейшего работающего примера. А теперь рассмотрим некоторые проблемы и покажем, как их можно решить.

Во-первых, обработка пустого запроса в представлении search() явно недостаточна – мы просто выводим сообщение «Введите поисковый запрос», заставляя пользователя нажать кнопку «Назад» в броузере. Это крайне непрофессионально, и, если вы сделаете нечто подобное в действующем приложении, вас отлучат от Django.

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

from django.http import HttpResponse

from django.shortcuts import render_to_response from mysite.books.models import Book

def search_form(request):

return render_to_response(‘search_form.html’)

def search(request):

if ‘q’ in request.GET and request.GET[‘q’]: q = request.GET[‘q’]

books = Book.objects.filter(title__icontains=q) return render_to_response(‘search_results.html’,

{‘books’: books, ‘query’: q})

else:

return render_to_response(‘search_form.html’, {‘error’: True})

(Мы также включили в пример представление search_form(), чтобы вы могли видеть оба представления одновременно.)

Здесь мы улучшили метод search(), и теперь он повторно отображает шаблон­ search_form.html, если запрос пуст. А поскольку на этот раз нам нужно вывести сообщение об ошибке, то мы передаем в шаблон­ пере-

1Естественно, в русскоязычной версии окончание ‘s’ выводить не нужно, так что pluralize, пожалуй, излишне. – Прим. перев.

2Интересное решение проблемы русификации фильтра pluralize можно найти по адресу: http://vas3k.ru/work/django_ru_pluralize. – Прим. науч. ред.

Усовершенствование примера обработки формы

155

менную. Сам же шаблон­ search_form.html следует изменить так, чтобы он проверял переменную error.

<html>

<head>

<title>Поиск</title>

</head>

<body>

{% if error %}

<p style=”color: red;”>Введите поисковый запрос.</p> {% endif %}

<form action=”/search/” method=”get”> <input type=”text” name=”q”>

<input type=”submit” value=”Найти”> </form>

</body>

</html>

Мы можем использовать этот шаблон­ и в первоначальном варианте представления search_form(), потому что search_form() не передает error в шаблон,­ следовательно, сообщение об ошибке выводиться не будет.

После этого изменения приложение стало лучше, но возникает вопрос: а так ли необходимо отдельное представление search_form()? Сейчас запрос к URL /search/ (без GET-параметров) приводит к выводу пустой формы (но с сообщением об ошибке). Мы можем удалить представление search_form() вместе с соответствующим шаблоном­ URL при условии, что изменим search() так, чтобы при обращении к URL /search/ без параметров сообщение об ошибке не выводилось:

def search(request): error = False

if ‘q’ in request.GET: q = request.GET[‘q’] if not q:

error = True else:

books = Book.objects.filter(title__icontains=q) return render_to_response(‘search_results.html’,

{‘books’: books, ‘query’: q}) return render_to_response(‘search_form.html’,

{‘error’: error})

Теперь, обратившись к URL /search/ без параметров, пользователь увидит форму поиска без сообщения об ошибке. Если же он отправит форму с пустым значением ‘q’, то увидит ту же форму, но уже с сообщением об ошибке. И наконец, при наличии в отправленной форме непустого значения ‘q’ будут выведены результаты поиска.

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

156

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

(и два образца URL) слились в одно, а представление /search/ стало обрабатывать не только вывод формы поиска, но и вывод результатов, отпала необходимость «зашивать» URL в HTML-тег <form> в файле search_ form.html. Вместо

<form action=”/search/” method=”get”>

можно написать:

<form action=”” method=”get”>

Атрибут action=”” означает «Отправить форму на URL текущей страницы». При таком изменении не потребуется изменять action, если с представлением search() будет ассоциирован другой URL.

Простая проверка данных

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

•• Введите допустимый адрес электронной почты. ‘foo’ – недопустимый адрес.

•• Введите правильный почтовый индекс США, состоящий из пяти цифр. ‘123’ не является почтовым индексом.

•• Введите дату в формате ДД.ММ.ГГГГ.

•• Пароль должен содержать по меньшей мере 8 символов и хотя бы одну цифру.

Замечание о проверке с помощью JavaScript

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

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

Простая проверка данных

157

Давайте изменим представление search() и будем проверять, что длина поискового запроса составляет не более 20 символов. (Будем считать, что более длинные запросы выполняются слишком медленно.) Как это сделать? Проще всего включить проверку непосредственно в код представления:

def search(request): error = False

if ‘q’ in request.GET: q = request.GET[‘q’] if not q:

error = True elif len(q) > 20:

error = True else:

books = Book.objects.filter(title__icontains=q) return render_to_response(‘search_results.html’,

{‘books’: books, ‘query’: q}) return render_to_response(‘search_form.html’,

{‘error’: error})

Если теперь отправить запрос длиной более 20 символов, то появится сообщение об ошибке. Но сейчас в шаблоне­ search_form.html текст сообщения звучит так: «Введите поисковый запрос», поэтому изменим его так, чтобы он был применим к обеим ситуациям (пустой или слишком длинный запрос):

<html>

<head>

<title>Поиск</title>

</head>

<body>

{% if error %}

<p style=”color: red;”>

Введите поисковый запрос не длиннее 20 символов.

</p>

{% endif %}

<form action=”/search/” method=”get”> <input type=”text” name=”q”>

<input type=”submit” value=”Найти”> </form>

</body>

</html>

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

Проблема в том, что переменная error – булевская, а должна была бы содержать список строк с текстами сообщений. Вот как можно это исправить:

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

def search(request): errors = []

if ‘q’ in request.GET: q = request.GET[‘q’] if not q:

errors.append(Введите поисковый запрос.’) elif len(q) > 20:

errors.append(Введите не более 20 символов.’) else:

books = Book.objects.filter(title__icontains=q) return render_to_response(‘search_results.html’,

{‘books’: books, ‘query’: q}) return render_to_response(‘search_form.html’,

{‘errors’: errors})

И еще понадобится чуть подправить шаблон­ search_form.html, отразив тот факт, что мы передаем список errors, а не просто булевское значение error:

<html>

<head>

<title>Поиск</title>

</head>

<body>

{% if errors %} <ul>

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

{% endfor %} </ul>

{% endif %}

<form action=”/search/” method=”get”> <input type=”text” name=”q”>

<input type=”submit” value=”Найти”> </form>

</body>

</html>

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

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

Начнем с шаблона­ contact_form.html.

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

159

<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”></p>

<p>Ваш e-mail (необязательно): <input type=”text” name=”e-mail”></p> <p>Сообщение:

<textarea name=”message” rows=”10” cols=”50”></textarea>

</p>

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

</body>

</html>

Мы определили три поля: тема, адрес e-mail и сообщение. Второе поле является необязательным, а остальные два должны быть заполнены. Заметим, что в этой форме указан method=”post”, а не method=”get”, поскольку при ее обработке производится дополнительное действие – отправка сообщения по электронной почте. Что касается кода обработки ошибок, то мы скопировали его из шаблона­ search_form.html.

Если идти путем, разработанным для представления search() из предыдущего раздела, то мы получим примерно такую наивную версию:

from django.core.mail import send_mail

from django.http import HttpResponseRedirect from django.shortcuts import render_to_response

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(

request.POST[‘subject’],

request.POST[‘message’],

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