Хостинг Django от «Джино»
Table of contents

Создание формы для контактной информации

Несмотря на то, что мы несколько раз изменяли форму поиска по книгам и значительно её улучшили, она осталась достаточно простой: просто одно поле, 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">
        <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.

Если продолжать методику, начатую при работе с нашим представлением 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 является удобной оболочкой над классом Django EmailMessage, который предоставляет дополнительные возможности, например, вложение файлов, 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>

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

Alexkey 4 months, 2 weeks ago
Answer Link

Для версии 1.3.1 подключить:

from django.template.response import TemplateResponse

и заменить:

return render_to_response('contact_form.html', {....})

на:

return TemplateResponse(request ,'contact_form.html', {....})

alerion 6 months, 2 weeks ago
Answer Link

>{% csrf_token %} - добавил
>'django.middleware.csrf.CsrfViewMiddleware', - расскоментировал
Вопросы - на форуме.

alerion 6 months, 2 weeks ago
Answer Link

reply to multlurk
так... все предыдущие главы читались запоем и трудностей не возникало. через эту же пробиться не могу.
вопрос: что я должен сделать, чтоб примеры из этой главы заработали на тестовом сервере django 1.2.5?

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

вообще за книгу огромное спасибо
и за будущий ответ на мой вопрос тоже :)

Добавить {% csrf_token %} в тело формы.

multlurk 6 months, 3 weeks ago
Answer Link

да... и интересно, как по разумению авторов сейчас должна выглядеть urls.py (ведь пока-что усовершенствованные схемы урлов не изучали ) :)

особенно интересуют импорты (учитывая последовательность обучения)

multlurk 6 months, 3 weeks ago
Answer Link

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

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

вообще за книгу огромное спасибо
и за будущий ответ на мой вопрос тоже :)

alerion 10 months ago
Answer Link

К тому же render_to не кошерный декоратор - меняет формат ответа.

rad 10 months ago
Answer Link

Там теперь [TemplateResponse](http://docs.djangoproject.com/en/dev/ref/template-response/). Про декоратор render_to можно забыть навсегда.

alerion 10 months ago
Answer Link

reply 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, но там точно есть аналог.

strider 10 months ago
Answer Link

reply to alerion
В 1.2.* нужны. Это глава о старой версии.

Учитывая, что в примерах используется render_to_response, стоит добавить, из-за использования в нем Context по умолчанию, решением будет не только добавление мидлвара в настройках, {% csrf_token %} в форму в шаблоне, но и
from django.template import RequestContext, а также затем и дополнительного аргумента в render_to_response - context_instance=RequestContext(request)

rad 11 months, 1 week ago
Answer Link

reply to solyony
error at /contact/
(10061, 'Connection refused')

Почему оно может не отправлять сообщения и как это можна исправить??

Для вопросов создан форум.

solyony 11 months, 1 week ago
Answer Link

error at /contact/
(10061, 'Connection refused')

Почему оно может не отправлять сообщения и как это можна исправить??

alerion 1 year ago
Answer Link

reply to tv
стоило бы упомянуть, что (как минимум в 1.2.4) для POST-форм нужны доп. телодвижения: "CSRF verification failed. Request aborted."
Решение - добавить в настройки миддлевари 'django.middleware.csrf.CsrfResponseMiddleware'.

В 1.2.* нужны. Это глава о старой версии.

tv 1 year, 1 month ago
Answer Link

стоило бы упомянуть, что (как минимум в 1.2.4) для POST-форм нужны доп. телодвижения: "CSRF verification failed. Request aborted."
Решение - добавить в настройки миддлевари 'django.middleware.csrf.CsrfResponseMiddleware'.

maks123 1 year, 1 month ago
Answer Link

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>

должна работать.

rad 1 year, 1 month ago
Answer Link

Может пришлёте патч на книгу? Нам бы пригодилась ваша помошь.

maks123 1 year, 1 month ago
Answer Link

обработки "/contact/thanks/" нету?

в оригинале нету, но можно было хотя бы сказать об этом.


Ищем Python программистов

Found misprint?
Select it with the mouse and hit Enter
Ctrl-Enter
Processed:
33 1 199 25


The full repository of DjangoBook translation you can get on GitHub.
We appreciate your patches!

We are glad to hear your questions, comments or suggestions!
(Open in new tab)

Users number: 601

Русская группа

на поддержку перевода
Яндекс Яндекс.Деньги Хочу такую же кнопку
Ускорить процесс перевода!
ЯМ:41001223475816


© 2008-2012 Ruslan Popov @ gmail.com Powered by Django 1.2.5