Как отрендерить форму вручную
S0mebodyРабота с пользовательским вводом - очень распространенная задача в любом веб-приложении или веб-сайте. Стандартный способ сделать это - использовать HTML-формы, где пользователь вводит некоторые данные, отправляет их на сервер, а затем сервер что-то с ними делает. Возможно, вы уже слышали эту цитату: «Всякий ввод - зло!» Я не знаю, кто это сказал первым, но это было сказано очень хорошо. По правде говоря, каждый вход в ваше приложение - это дверь, потенциальная атака. Так что лучше запереть все двери! Чтобы облегчить вам жизнь и дать вам некоторое душевное спокойствие, Django предлагает очень богатый, надежный и безопасный API форм. И вы обязательно должны его использовать, независимо от того, насколько проста ваша HTML-форма.
Управление пользовательским вводом, обработка форм - довольно сложная задача, поскольку предполагает взаимодействие со многими уровнями вашего приложения. Он должен иметь доступ к базе данных; очищать, проверять, преобразовывать и гарантировать целостность данных; иногда ему необходимо взаимодействовать с несколькими моделями, передавать удобочитаемые сообщения об ошибках, а затем, наконец, он также должен переводить весь код Python, представляющий ваши модели, во входные данные HTML. В некоторых случаях эти входные данные HTML могут включать код JavaScript и CSS (например, настраиваемое средство выбора даты или поле автозаполнения).
Дело в том, что Django очень хорошо справляется со стороны сервера. Но это не сильно влияет на клиентскую часть. HTML-формы, автоматически создаваемые Django, полностью функциональны и могут использоваться как есть. Но это очень грубо, это простой HTML, без CSS и без JavaScripts. Это было сделано для того, чтобы вы могли полностью контролировать, как представлять формы, чтобы они соответствовали веб-дизайну вашего приложения. На стороне сервера все немного по-другому, поскольку все более стандартизировано, поэтому большинство функций, предлагаемых API форм, работают «из коробки». А для особых случаев он предоставляет множество способов настройки.
В этом уроке я покажу вам, как работать с частью рендеринга, используя собственный CSS и делая ваши формы красивее.
Рабочий пример
На протяжении всего урока я буду использовать следующее определение формы, чтобы проиллюстрировать примеры:
forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=30)
email = forms.EmailField(max_length=254)
message = forms.CharField(
max_length=2000,
widget=forms.Textarea(),
help_text='Write here your message!'
)
source = forms.CharField(
max_length=50,
widget=forms.HiddenInput()
)
def clean(self):
cleaned_data = super(ContactForm, self).clean()
name = cleaned_data.get('name')
email = cleaned_data.get('email')
message = cleaned_data.get('message')
if not name and not email and not message:
raise forms.ValidationError('You have to write something!')
И следующее представление просто для загрузки формы и запуска процесса проверки, чтобы мы могли иметь форму в разных состояниях:
views.py
from django.shortcuts import render
from .forms import ContactForm
def home(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
pass # ничего не делает, просто запускает проверку
else:
form = ContactForm()
return render(request, 'home.html', {'form': form})
Понимание процесса рендеринга
Во многих руководствах или в официальной документации Django очень часто встречаются такие шаблоны форм:
<form method="post" novalidate>
{% csrf_token %}
{{ form }}
<button type="submit">Submit</button>
</form>
Похоже на магию, правда? Поскольку эта конкретная форма может содержать 50 полей, и простая команда {{form}} отобразит их все в шаблоне.
Когда мы пишем {{form}} в шаблоне, он фактически обращается к методу __str__ из класса BaseForm. Метод __str__ используется для предоставления строкового представления объекта. Если вы посмотрите исходный код, вы увидите, что он возвращает метод as_table(). Итак, в основном {{form}} и {{form.as_table}} - это одно и то же.
API форм предлагает три метода автоматической визуализации HTML-формы:
- as_table()
- as_ul()
- as_p()
Все они работают более или менее одинаково, разница заключается в HTML-коде, который обертывает входные данные.
Ниже приведен результат предыдущего фрагмента кода:

Но если {{form}} и {{form.as_table}} - это одно и то же, результат определенно не будет похож на таблицу, верно? Это потому, что as_table() и as_ul() не создают теги <table> и <ul>, поэтому мы должны добавить их сами.
Итак, правильный способ сделать это:
<form method="post" novalidate>
{% csrf_token %}
<table border="1">
{{ form }}
</table>
<button type="submit">Submit</button>
</form>

Теперь это имеет смысл, правда? Без тега <table> браузер на самом деле не знает, как отображать вывод HTML, поэтому он просто представляет все видимые поля в строке, поскольку у нас еще нет CSS.
Если вы посмотрите на закрытый метод _html_output, определенный в BaseForm, который используется всеми методами as _ * (), вы увидите, что это довольно сложный метод, который выполняет множество функций. Это нормально, потому что этот метод хорошо протестирован и является частью ядра API форм, лежащих в основе механизмов, которые заставляют все работать. При работе над собственной логикой рендеринга формы вам не нужно писать код Python для выполнения этой работы. Гораздо лучше делать это с помощью движка шаблонов Django, так как вы можете добиться более чистого и простого в сопровождении кода.
Я упоминаю здесь метод _html_output, потому что мы можем использовать его для анализа того, какой код он генерирует, что он на самом деле делает, чтобы мы могли имитировать его с помощью механизма шаблонов. Несмотря на то, что документация Django очень подробная и обширная, кое-где всегда есть кое-какие скрытые моменты.
Во всяком случае, вот вкратце, что делает _html_output:
- Агрегируйте ошибки, которые не привязаны к определенным полям (ошибки, не относящиеся к полям), и ошибки из скрытых полей;
- Поместите ошибки, не связанные с полями, и ошибки со скрытыми полями в верхней части формы;
- Перебрать все поля формы;
- Визуализируйте поля формы одно за другим;
Вот как выглядит второе состояние формы, вызывающее все ошибки проверки:

Теперь, когда мы знаем, что он делает, мы можем попытаться имитировать то же поведение с помощью механизма шаблонов. Таким образом, у нас будет гораздо больше контроля над процессом рендеринга:
<form method="post" novalidate>
{% csrf_token %}
{{ form.non_field_errors }}
{% for hidden_field in form.hidden_fields %}
{{ hidden_field.errors }}
{{ hidden_field }}
{% endfor %}
<table border="1">
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }}</th>
<td>
{{ field.errors }}
{{ field }}
{{ field.help_text }}
</td>
</tr>
{% endfor %}
</table>
<button type="submit">Submit</button>
</form>
Вы заметите, что результат немного отличается, но все элементы присутствуют. Дело в том, что автоматическое создание HTML только с использованием {{form}} использует преимущества языка Python, поэтому он может играть с конкатенацией строк, объединением списков (ошибки без полей + ошибки со скрытыми полями) и тому подобное. . Механизм шаблонов более ограничен, но это не проблема. Мне нравится движок шаблонов Django, потому что он не позволяет выполнять большую часть логики кода в шаблоне.

Единственная реальная проблема - это случайное This field is required вверху, которое относится к исходному полю. Но мы можем это исправить. Давайте продолжим расширять рендеринг формы, чтобы мы могли лучше контролировать его:
<form method="post" novalidate>
{% csrf_token %}
{% if form.non_field_errors %}
<ul>
{% for error in form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% for hidden_field in form.hidden_fields %}
{% if hidden_field.errors %}
<ul>
{% for error in hidden_field.errors %}
<li>(Hidden field {{ hidden_field.name }}) {{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{{ hidden_field }}
{% endfor %}
<table border="1">
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }}</th>
<td>
{% if field.errors %}
<ul>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{{ field }}
{% if field.help_text %}
<br />{{ field.help_text }}
{% endif %}
</td>
</tr>
{% endfor %}
</table>
<button type="submit">Submit</button>
</form>

Гораздо ближе, правда?
Теперь, когда мы знаем, как «расширить» разметку {{form}}, давайте попробуем сделать ее красивее. Возможно, используя библиотеку Bootstrap 4.
Доступ к полям формы по отдельности
Нам не нужен цикл for для отображения полей формы. Но это очень удобный способ, особенно если у вас нет особых требований к позиционированию элементов.
Вот как мы можем обращаться к полям формы одно за другим:
<form method="post" novalidate>
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.source.errors }}
{{ form.source }}
<table border="1">
<tr>
<th>{{ form.name.label_tag }}</th>
<td>
{{ form.name.errors }}
{{ form.name }}
</td>
</tr>
<tr>
<th>{{ form.email.label_tag }}</th>
<td>
{{ form.email.errors }}
{{ form.email }}
</td>
</tr>
<tr>
<th>{{ form.message.label_tag }}</th>
<td>
{{ form.message.errors }}
{{ form.message }}
<br />
{{ form.message.help_text }}
</td>
</tr>
</table>
<button type="submit">Submit</button>
</form>
Это не очень DRY решение. Но хорошо знать, как это делать. Иногда у вас может быть очень специфический вариант использования, когда вам нужно будет самостоятельно позиционировать поля в HTML.
Далее в следующей части (завтра)