Django — доработка шаблона формы регистрации

Зарегистрироваться на Yatube теперь может каждый! Но не каждый захочет: страница работает, но выглядит непрезентабельно:

Посмотрим, что можно сделать.

Шаблон страницы регистрации сейчас выглядит так:

<!-- templates/users/signup.html -->

{% extends "base.html" %}
{% block title %}Зарегистрироваться{% endblock %}
{% block content %}

  <form method="post" action="{% url 'users:signup' %}">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Зарегистрироваться">
  </form>

{% endblock %} 

Переменная form передаётся в шаблон, на её основе генерируется HTML-код формы. Метод as_p() выводит код формы, обрамляя каждую строку формы в HTML-тег <p>. А в начале кода формы стоит странный тег шаблонизатора {% csrf_token %}

Что такое {% csrf_token %} ?

CSRF (от англ. cross-site request forgery — межсайтовая подделка запроса) — вид атаки на сайт или на аккаунт пользователя на сайте, где пользователь авторизован. Логика подобной атаки довольно проста:

  • предположим, пользователь залогинен на сайте своего банка;
  • пользователь заходит на некую стороннюю веб-страницу и нажимает на заражённую ссылку;
  • при клике отправляется запрос на сайт банка (и так совпало, что это именно банк нашего пользователя: если банк популярен — вероятность такого совпадения велика);
  • браузер помнит данные пользователя, и поэтому отправленный запрос будет воспринят сайтом банка как запрос авторизованного пользователя;
  • посредством такого запроса злоумышленники могут вывести деньги с аккаунта пользователя или сделать заказ от его имени;
  • профит!

Конечно, в этом сценарии должен присутствовать элемент невезения: запрос должен отправляться на сайт именно того банка, где залогинен пользователь. Но если банк крупный, а число пользователей, кликающих на хакерскую ссылку, велико — такая схема сработает.

Для защиты от таких атак сайты генерируют специальный csrf-токен (или csrf-ключ), который встраивается в «доверенную» веб-страницу и отправляется вместе с каждым запросом от пользователя к серверу. Этот ключ — уникальная для каждого пользователя строка, последовательность символов. Угадать её практически невозможно.

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

Django серьёзно относится к безопасности, и все формы с POST-запросами по умолчанию должны быть защищены таким ключом.

Работа с формами

Возьмём для экспериментов несложную форму.В ней есть два текстовых поля — «Имя» и «Фамилия», и кнопка «Отправить». При нажатии на кнопку браузер отправит данные из формы на страницу, указанную в атрибуте action.

<!-- Форма оборачивается тегом form, 
     в открывающем теге указывается тип и адрес запроса для отправки данных -->
<form method="post" action="/example-action/">
  <!-- Тег label - заголовок для поля ввода -->
  <label>Введите имя:</label>
  <!-- type - тип поля ввода (здесь - текстовое поле), 
       name - имя поля в POST-запросе, в котором будет отправлена информация,
       введённая пользователем -->
  <input type="text" name="first_name" id="id_first_name">
  <label for="id_last_name">Введите фамилию:</label>
  <input type="text" name="last_name" id="id_last_name">
  <!-- Этот код отрисует кнопку с надписью "Отправить" -->
  <input type="submit" value="Отправить">
</form> 

Устройство форм в Django

Чтобы было проще понимать примеры и код, разберём устройство форм на высоком уровне.Форма в Django не обязательно должна работать с моделью: можно создать форму, не связанную с моделью. Поля формы и их типы описывают при создании класса формы; такие классы наследуют от встроенного класса Form.

from django import forms

class ContactForm(forms.Form):
    first_name = forms.CharField(label='Введите имя', initial='Лев')
    last_name = forms.CharField(label='Введите фамилию', initial='Толстой') 

Форма может содержать поля разных типов, все они описаны в документации. Когда в шаблоне поле превращается в HTML-код, то применяется виджет, определённый параметром widgetВиджет — это шаблон, по которому генерируется HTML-код поля формы.

Основные типы полей, которые вам будут встречаться:

  • BooleanField — соответствует типу bool. Виджет по умолчанию отрисовывает чекбокс <input type="checkbox">;
  • CharField — поле для ввода текста, по умолчанию используется виджет однострочного поля ввода <input type="text">. Виджет можно заменить: если указать в параметрах widget=forms.Textarea, будет отрисовано поле многострочного ввода, <textarea>;
  • ChoiceField — поле выбора из выпадающего списка, <select>;
  • EmailField — однострочное поле ввода текста, но с обязательной проверкой введённой строки на соответствие формату email;
  • FileField — поле для отправки файла, в шаблоне отрисует тег <input type="file">. Есть аналогичное поле для отправки только файлов изображений: ImageField;
  • IntegerField — поле для ввода чисел: <input type="number">.

Можно самостоятельно создавать новые типы полей и новые виджеты. В Django есть множество готовых виджетов: например, есть виджет для превращения поля ввода в визуальный редактор.

Работа с формами из кода

Запустите в консоли интерактивный режим Django, дальше работать будем в нём: (venv) ... $ python manage.py shell

Импортируем модуль forms и создадим класс формы с двумя полями:

>>> from django import forms
>>> class Registration(forms.Form):
...     first_name = forms.CharField(label='Введите имя', initial='Лев')
...     last_name = forms.CharField(label='Введите фамилию', initial='Толстой')
... 
>>> form = Registration()
>>> #напечатаем результат, чтобы увидеть HTML-код, который выведет метод as_p()
>>> print(form.as_p())
<p><label for="id_first_name">Введите имя:</label> <input type="text" name="first_name" value="Лев" required id="id_first_name"></p>
<p><label for="id_last_name">Введите фамилию:</label> <input type="text" name="last_name" value="Толстой" required id="id_last_name"></p> 

Сгенерированный HTML-код содержит код полей ввода <input type="text" ...> с необходимыми атрибутами и теги <label> заголовки полей, видимые пользователям.

Метод as_p(), унаследованный от класса Form, обрамляет каждую пару тегов «label + поле» в HTML-тег <p>

По умолчанию форма выводится в HTML-таблицу, в элемент <table>. Такой же код будет выведен и методом as_table():

>>> print(form.as_table())
<tr><th><label for="id_first_name">Введите имя:</label></th><td><input type="text" name="first_name" value="Лев" required id="id_first_name"></td></tr>
<tr><th><label for="id_last_name">Введите фамилию:</label></th><td><input type="text" name="last_name" value="Толстой" required id="id_last_name"></td></tr> 

Поля можно вывести HTML-списком, методом as_ul():

>>> print(form.as_ul())
<li><label for="id_first_name">Введите имя:</label> <input type="text" name="first_name" value="Лев" required id="id_first_name"></li>
<li><label for="id_last_name">Введите фамилию:</label> <input type="text" name="last_name" value="Толстой" required id="id_last_name"></li> 

Каждый из этих методов можно вызывать из шаблона командами form.as_table form.as_p или form.as_ul.

Когда форма заполнена и отправлена, Django получит данные и проверит их на корректность. В случае, если отправленная информация не прошла валидацию, то объект form получит список ошибок в атрибуте form.errors .

Работа с полями формы

С объектом формы можно работать через цикл for:

>>> for field in form:
...     print(field)
... 
# поля формы будут напечатаны по очереди
<input type="text" name="first_name" value="Лев" required id="id_first_name">
<input type="text" name="last_name" value="Толстой" required id="id_last_name"> 

В шаблоне этот же код выглядит так:

{% for field in form %}
  {{ field }}
{% endfor %} 

Доступ к полям формы по именам

Иногда удобно вывести в шаблон поля формы не циклом, а отдельным кодом.В шаблоне для доступа к полю применяют точечную нотацию: form.field_name.

<form method="post">
  {% csrf_token %}
  {{ form.first_name }}
  {{ form.last_name }}
  <input type="submit" value="Отправить">
</form> 

В Python-коде доступ к полям можно получить, обратившись к объекту form как к словарю, где ключом является имя поля.

>>> print(form['first_name'])
<input type="text" name="first_name" value="Лев" required id="id_first_name"> 

Атрибуты полей формы

При выводе формы в шаблон доступны атрибуты объекта field:

  • field.label — метка поля, параметр label из описания поля в классе: label="Введите имя";
  • field.label_tag — этот атрибут даёт доступ к полному тегу label для поля: <label for="id_first_name">Введите имя:</label>;
  • field.id_for_label — здесь хранится значение, которое в HTML-теге label указывает, для какого именно поля формы создан этот label. В примере <label for="id_first_name">Введите имя:</label> значением тега field.id_for_label будет id_first_name;
  • field.value — значение, которое ввёл пользователь;
  • field.html_name — атрибут name тега input;
  • field.help_text — текст подсказки, который можно передать в коде;
  • field.errors — этот атрибут будет заполнен, если при проверке отправленных данных произошла ошибка.

Создание нового шаблона

В документации по Bootstrap даётся такой пример HTML-кода формы:

<form>
  <div class="form-group">
    <label for="exampleInputEmail1">Email address</label>
    <input 
      type="email"
      class="form-control"
      id="exampleInputEmail1"
      aria-describedby="emailHelp"
      placeholder="Enter email"
    >
    <small id="emailHelp" class="form-text text-muted">
      We'll never share your email with anyone else.
    </small>
  </div>
  <div class="form-group">
    <label for="exampleInputPassword1">Password</label>
    <input 
      type="password"
      class="form-control"
      id="exampleInputPassword1"
      placeholder="Password"
    >
  </div>
  <div class="form-group form-check">
    <input type="checkbox" class="form-check-input" id="exampleCheck1">
    <label class="form-check-label" for="exampleCheck1">Check me out</label>
  </div>
  <button type="submit" class="btn btn-primary">Submit</button>
</form> 

Измените код шаблона templates/users/signup.html так, чтобы он соответствовал стандарту Bootstrap.

{% extends "base.html" %}
{% block title %}Зарегистрироваться{% endblock %}
{% block content %}
  <div class="row justify-content-center">
    <div class="col-md-8 p-5">
      <div class="card">
        <div class="card-header">
          Зарегистрироваться
        </div>
        <div class="card-body">
          {# Этот блок будет показан, если в форме есть ошибки #}
          {% if form.errors %}
            {% for field in form %}
              {% for error in field.errors %}            
                <div class="alert alert-danger">
                  {{ error|escape }}
                </div>
              {% endfor %}
            {% endfor %}
            {% for error in form.non_field_errors %}
              <div class="alert alert-danger">
                {{ error|escape }}
              </div>
            {% endfor %}
          {% endif %}

            <form method="post" action="{% url 'users:signup' %}">
              {% csrf_token %}
              {% for field in form %}
              <div class="form-group row my-3 p-3">
                <label for="{{ field.id_for_label }}">
                  {{ field.label }}
                  {% if field.field.required %}
                    <span class="required text-danger">*</span>
                  {% endif %}
                </label>    
                {{ field }}
                {% if field.help_text %}
                  <small id="{{ field.id_for_label }}-help" class="form-text text-muted">
                    {{ field.help_text|safe }}
                  </small>
                {% endif %}
              </div>
              {% endfor %}
              <div class="col-md-6 offset-md-4">
                <button type="submit" class="btn btn-primary">
                  Зарегистрироваться
                </button>
              </div>
            </form>
          </div> <!-- card body -->
        </div> <!-- card -->
      </div> <!-- col -->
  </div> <!-- row -->
{% endblock %} 

Форма регистрации теперь выглядит гораздо лучше:





Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: