Несмотря на то, что мы несколько раз изменяли форму поиска по
книгам и значительно её улучшили, она осталась достаточно
простой: просто одно поле, q. Из-за её простоты мы даже не воспользовались библиотекой форм Django. Более сложные формы потребуют более сложную обработку — сделаем что-нибудь посложнее: форму для передачи
контактной информации.
Это будет форма, которая позволит пользователям отправлять отклик, с необязательным указанием своего адреса электронной почты. После передачи данных формы и их проверки, мы автоматически отправим сообщение по электронной почте сотрудникам сайта.
Начнём с шаблона, contact_form.html:
<html>
<head>
<title>Contact us</title>
</head>
<body>
<h1>Contact us</h1>
{% if errors %}
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<form action="/contact/" method="post">{% csrf_token %}
<p>Subject: <input type="text" name="subject"></p>
<p>Your e-mail (optional): <input type="text" name="email"></p>
<p>Message: <textarea name="message" rows="10" cols="50"></textarea></p>
<input type="submit" value="Submit">
</form>
</body>
</html>
Мы определили три поля: заголовок, адрес электронной почты и
сообщение. Второе поле необязательное. Следует отметить, что мы
используем method="post", так как форма имеет побочный эффект — она отправляет сообщение. Также мы
скопировали код, отвечающий за отображение ошибки из нашего
предыдущего шаблона
search_form.html.
Формы и защита от Cross Site Request Forgery
Использовать защиту от Cross Site Request Forgeries в Django очень просто. Оправляя форму, используя метод POST при включеной защите от CSRF, добвляйте в форму тег csrf_token как в примере выше. Однако, так как защита от CSRF непосредственно не связана с формами в шаблонах, этот тег опущен в последующих примерах.
Если продолжать методику, начатую при работе с нашим
представлением search(), первой наивной
версией нашего представления contact()
будет:
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('Enter a subject.')
if not request.POST.get('message', ''):
errors.append('Enter a message.')
if request.POST.get('email') and '@' not in request.POST['email']:
errors.append('Enter a valid e-mail address.')
if not errors:
send_mail(
request.POST['subject'],
request.POST['message'],
request.POST.get('email', '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 Он будет содержать
пустой __init__.py и
views.py.)
Рассмотрим отличия:
Мы проверяем, что request.method равен 'POST'. Это условие будет верным только в случае отправки формы, иначе считаем, что происходит простое отображение формы. (В последнем случае, request.method будет установлен в 'GET', так как для отображения формы браузеры используют GET, а не POST.) Такой подход обеспечивает отличный способ изоляции случая «отображения формы» от «обработки формы».
Вместо использования request.GET мы применяем request.POST для доступа к переданным данным. Это необходимо, так как <form> в
contact_form.htmlиспользует method="post". Если данное представление вызывается через POST, значит request.GET будет пустым.В этот раз у нас есть два обязательных поля, subject и message, нам следует проверять оба. Следует отметить, что для получения значения из POST мы используем метод
get()и подставляем пустую строку в качестве значения по умолчанию. Это хороший, краткий способ обработки двух проблем: отсутствие ключа и отсутствие данных.Несмотря на то, что поле email является необязательным, мы всё равно проверяем его, правда только при наличии в нём данных. Наша проверка достаточно простая — мы проверяем наличие в строке символа @. В реальной ситуации вам потребуется более устойчивая проверка (и Django предоставляет её, мы очень скоро её покажем).
Мы используем функцию
send_mailиз модуля django.core.mail для отправки сообщения по электронной почте. Эта функция принимает четыре обязательных аргумента: тема сообщения, тело сообщения, адрес «от» и список адресов получателей. Функцияsend_mailявляется удобной оболочкой над классом DjangoEmailMessage, который предоставляет дополнительные возможности, например, вложение файлов, multipart сообщения и полный контроль над заголовками сообщения.Следует отметить, что для отправки сообщения с помощью
send_mailваш сервер должен быть соответственно сконфигурирован и Django следует указать на сервер исходящей почты. Обратитесь к http://docs.djangoproject.com/en/dev/topics/email/ для подробностей.После отправки сообщения мы перенаправляем пользователя на страницу с сообщением об успешном выполнении операции, возвращая объект
HttpResponseRedirect. Мы оставляем реализацию такой страницы вам в качестве домашнего задания (это просто представление/привязка URL/шаблон), но нам следует объяснить причину, почему мы инициировали перенаправление вместо, например, простого вызоваrender_to_response()прямо здесь.Причина: если пользователь нажмёт на кнопку Обновить на странице, которая была загружена через метод POST, это приведёт к повторному выполнению запроса. В большинстве случаев это может привести к нежелаемому результату, например, к повторной передаче данных в базу данных или к повторной отправке сообщения по электронной почте. Если же пользователь был перенаправлен на другую страницу, тогда такого точно не произойдёт.
Вы всегда должны осуществлять перенаправление после успешной обработки POST запроса.
Данное представление работает, но представьте, что вам придётся обработать форму с дюжиной полей. Вы действительно желаете самостоятельно расписывать проверки для каждого поля?
Следующей проблемой является повторное отображение формы. В случае возникновения ошибок при проверке полей, лучше всего будет отобразить форму повторно, внеся в поля переданные данные, так пользователь сможет увидеть, что именно он сделал неправильно (и также ему не потребуется вводить эти данные повторно). Мы можем вручную передать POST данные обратно в шаблон, но нам потребуется отредактировать каждое HTML поле для вставки соответствующего значения на соответствующее место.
views.py:
def contact(request):
errors = []
if request.method == 'POST':
if not request.POST.get('subject', ''):
errors.append('Enter a subject.')
if not request.POST.get('message', ''):
errors.append('Enter a message.')
if request.POST.get('email') and '@' not in request.POST['email']:
errors.append('Enter a valid e-mail address.')
if not errors:
send_mail(
request.POST['subject'],
request.POST['message'],
request.POST.get('email', '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', ''),
'email': request.POST.get('email', ''),
})
contact_form.html:
<html>
<head>
<title>Contact us</title>
</head>
<body>
<h1>Contact us</h1>
{% if errors %}
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<form action="/contact/" method="post">
<p>Subject: <input type="text" name="subject" value="{{ subject }}"></p>
<p>Your e-mail (optional): <input type="text" name="email" value="{{ email }}"></p>
<p>Message: <textarea name="message" rows="10" cols="50">**{{ message }}**</textarea></p>
<input type="submit" value="Submit">
</form>
</body>
</html>
Этот подход очень трудоёмкий и привносит множество возможностей для ошибки. Мы надеемся, что вы начали искать возможность воспользоваться некой высокоуровневой библиотекой, которая будет выполнять такие формо-проверочные задачи.
| Пред. | Уровень выше | След. |
| Простая проверка данных | Начало | Ваш первый класс формы |
21 comments | Make a comment
обработки "/contact/thanks/" нету?
в оригинале нету, но можно было хотя бы сказать об этом.
Может пришлёте патч на книгу? Нам бы пригодилась ваша помошь.
url.py
...
(r'^contact/thanks/$', views.contact_thanks),
...
book/views.py
def contact_thanks(request):
return render_to_response('thanks.html')
thanks.html
<html>
<body>
Большое спасибо за контакты.
</body>
</html>
должна работать.
стоило бы упомянуть, что (как минимум в 1.2.4) для POST-форм нужны доп. телодвижения: "CSRF verification failed. Request aborted."
Решение - добавить в настройки миддлевари 'django.middleware.csrf.CsrfResponseMiddleware'.
answer to voan
стоило бы упомянуть, что (как минимум в 1.2.4) для POST-форм нужны доп. телодвижения: "CSRF verification failed. Request aborted."
Решение - добавить в настройки миддлевари 'django.middleware.csrf.CsrfResponseMiddleware'.
В 1.2.* нужны. Это глава о старой версии.
error at /contact/
(10061, 'Connection refused')
Почему оно может не отправлять сообщения и как это можна исправить??
answer to solyony
error at /contact/
(10061, 'Connection refused')
Почему оно может не отправлять сообщения и как это можна исправить??
Для вопросов создан форум.
answer to alerion
В 1.2.* нужны. Это глава о старой версии.
Учитывая, что в примерах используется render_to_response, стоит добавить, из-за использования в нем Context по умолчанию, решением будет не только добавление мидлвара в настройках, {% csrf_token %} в форму в шаблоне, но и
from django.template import RequestContext, а также затем и дополнительного аргумента в render_to_response - context_instance=RequestContext(request)
answer to strider
Учитывая, что в примерах используется render_to_response, стоит добавить, из-за использования в нем Context по умолчанию, решением будет не только добавление мидлвара в настройках, {% csrf_token %} в форму в шаблоне, но и
from django.template import RequestContext, а также затем и дополнительного аргумента в render_to_response - context_instance=RequestContext(request)
Вы правы. Можно использовать django.views.generic.simple.direct_to_template. Не уверен что в осталось 1.3, но там точно есть аналог.
Там теперь [TemplateResponse](http://docs.djangoproject.com/en/dev/ref/template-response/). Про декоратор render_to можно забыть навсегда.
К тому же render_to не кошерный декоратор - меняет формат ответа.
так... все предыдущие главы читались запоем и трудностей не возникало. через эту же пробиться не могу.
вопрос: что я должен сделать, чтоб примеры из этой главы заработали на тестовом сервере django 1.2.5?
из комментариев не понял, что для какой версии нужно прописывать и для какой версии джанго эта глава писалась.
вообще за книгу огромное спасибо
и за будущий ответ на мой вопрос тоже :)
да... и интересно, как по разумению авторов сейчас должна выглядеть urls.py (ведь пока-что усовершенствованные схемы урлов не изучали ) :)
особенно интересуют импорты (учитывая последовательность обучения)
answer to multlurk
так... все предыдущие главы читались запоем и трудностей не возникало. через эту же пробиться не могу.
вопрос: что я должен сделать, чтоб примеры из этой главы заработали на тестовом сервере django 1.2.5?
из комментариев не понял, что для какой версии нужно прописывать и для какой версии джанго эта глава писалась.
вообще за книгу огромное спасибо
и за будущий ответ на мой вопрос тоже :)
Добавить {% csrf_token %} в тело формы.
>{% csrf_token %} - добавил
>'django.middleware.csrf.CsrfViewMiddleware', - расскоментировал
Вопросы - на форуме.
Для версии 1.3.1 подключить:
from django.template.response import TemplateResponse
и заменить:
return render_to_response('contact_form.html', {....})
на:
return TemplateResponse(request ,'contact_form.html', {....})
Для отправки почты в файл settings.py добавляем настройки:
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'user@gmail.com'
EMAIL_HOST_PASSWORD = 'pw'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
посоветовали бы какую нибудь высокоуровневую библиотеку, которая будет выполнять такие формо-проверочные задачи
Установил локальный почтовый сервер, установил почтовый клиент, сам себе письма присылаю :-) Проблема только в методе get(), если не указать адрес отправителя, он почему то не подставляет noreply@example.com, т.е. указанный в коде адрес отправителя. По видимому он находит ключ email, а значение пустое, и выдает ошибку
SMTPSenderRefused at /contact/
(550, 'The address is not valid.', 'webmaster@localhost')
Добрый день. Этот урок должен работать на django 1.4?
В случае когда в форме есть {% csrf_token %} (в последнем случае он отсутствует) у меня выбивало ошибку 403 "CSRF verification failed. Request aborted" Там же были варианты причин. Мне помог вариант добавления `context_instance=RequestContext(request)` в `render to response()`. Т.к. почтовые настройки в settings.py не настраивал, то отправка всё равно не увенчалась успехом, но это лирика.
python 2.7.3, django 1.6.2