Вы уже поработали с моделями и формами, созданными на основе Generic Views. Чтобы увидеть полную картину — научимся обрабатывать формы во view-функциях.
В самом общем случае работа с формами происходит в таком порядке:
- Разработчик написал модель, на её основе создал форму, эту форму вывел в шаблон. Можно создать форму и без модели, если хранить полученные данные не нужно.
- Пользователь заходит на страницу с формой, заполняет её и нажимает «Отправить», браузер пользователя отправляет POST-запрос с данными на URL, указанный в параметре
action
формы. - На сервере данные из формы проверяются и обрабатываются во view-функции.
- Если данные успешно прошли проверку — они передаются для дальнейшей обработки и сохранения в базе, а пользователь перенаправляется на страницу с информацией об успешной отправке формы.
- Если отправленные данные не прошли проверку, пользователю отправляется уведомление об ошибке.
В Django есть полная инфраструктура для работы с формами:
- Формы Django можно связывать с объектами модели.
- На основе моделей можно быстро, буквально несколькими строками кода, создавать новые формы.
- Данные, полученные от пользователя, автоматически разбираются обработчиком запроса.
- Django умеет передавать данные, полученные от пользователя, в объект формы.
- У форм есть система валидации — проверка переданных данных. Пользователь может ошибиться или раньше времени нажать кнопку «Отправить» — в этих случаях данные не пройдут проверку и пользователь получит сообщение об этом.
- Во все формы Django по умолчанию встроена защита от злонамеренной отправки данных из другого источника, Cross Site Request Forgery (CSRF).
- В Django предусмотрен набор встроенных виджетов — шаблонов, формирующих HTML-код формы: поля для ввода текста (HTML-тег
<input type="text">
), поля многострочного ввода<textarea>
, чекбоксы<input type="checkbox">
, выпадающие списки<select>
и множество других, стандартных и нестандартных, элементов.
Проще всего создать форму на основе существующей модели: достаточно написать класс формы, ссылающийся на модель.
В уроке «Объект формы и Generic Views» вы написали форму для отправки сообщений администратору. На её примере и разберёмся.
Модель, в которой будут храниться данные формы:
# models.py
class Contact(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
subject = models.CharField(max_length=100)
body = models.TextField()
is_answered = models.BooleanField(default=False)
На основе этой модели создаётся класс формы:
# forms.py
from django import forms
from .models import Contact
class ContactForm(forms.ModelForm):
class Meta:
# На основе какой модели создаётся класс формы
model = Contact
# Укажем, какие поля будут в форме
fields = ('name', 'email', 'subject', 'body')
Теперь во view-функции можно создать объект формы на основе класса ContactForm
.
# views.py
def user_contact(request):
...
# Создаём объект формы
form = ContactForm()
# И в словаре контекста передаём эту форму в HTML-шаблон
return render(request, 'users/contact.html', {'form': form})
Если в объект формы ContactForm
передать объект модели Contact
, с которой связана эта форма, то в HTML-форму будут выведены данные этого объекта. Это работает, например, в случаях, когда вы щелкаете по ссылке «Отредактировать» в социальной сети или в админ-зоне сайта.
# views.py
def user_contact(request):
...
# Запрашиваем объект модели Contact
contact = Contact.objects.get(pk=3)
# Создаём объект формы и передаём в него объект модели с pk=3
form = ContactForm(instance=contact)
# Передаём эту форму в HTML-шаблон
return render(request, 'users/contact.html', {'form': form})
В результате пользователь увидит на странице заполненную HTML-форму: в неё будет выведено сообщение с pk=3. Сообщение можно будет изменить, а затем сохранить отредактированный вариант на сервере.
При выводе формы в HTML-шаблон для каждого типа поля модели Django автоматически подберёт подходящие виджеты формы:
- если в модели есть поле типа ForeignKey, то в форме будет отрисовано поле выбора ModelChoiceField,
<select>
для выбора объекта связанной модели; - для поля модели ManyToManyField в форме будет применён виджет ModelMultipleChoiceField, поле для множественного выбора из списка;
- для поля модели SlugField в форме отобразится поле SlugField. Это специальное текстовое поле
<input type="text">
умеет создавать красивые URL для объектов и проверять, что строка состоит только из разрешённых для URL символов.
Валидация форм и моделей
Данные, переданные из форм, на сервере проверяются функциями-валидаторами. Валидатор обращается к данным (или получает их в качестве аргумента) и проверяет их значение. В случае ошибки вызывается исключение ValidationError
. В Django есть множество встроенных валидаторов, но можно написать и собственный.
Валидация поля формы может быть многоэтапной. Значения, полученные после валидации каждого из полей, будут доступны в словаре form.cleaned_data
. Ключи в этом словаре — названия полей формы.Есть два основных способа валидации.
Первый способ валидации: функции-валидаторы
Функции-валидаторы обычно пишут в файле validators.py, в них описывают условия, которым должны соответствовать данные из формы. Затем для полей модели или формы указывают параметр validators
, перечисляя в нём функции-валидаторы, которые должны проверить это поле.
Когда создаётся объект модели или формы, в функцию-валидатор автоматически передаётся значение нужного поля; при ошибке валидации вызывается исключение ValidationError:
# validators.py
# Функция-валидатор:
def validate_not_empty(value):
# Проверка "а заполнено ли поле?"
if value == '':
raise forms.ValidationError(
'А кто поле будет заполнять, Пушкин?',
params={'value': value},
)
# models.py
class Contact(models.Model):
# К полю name подключаем валидатор, проверяющий, что поле не пустое.
name = models.CharField(max_length=100, validators=[validate_not_empty])
email = models.EmailField()
subject = models.CharField(max_length=100)
# К полю body тоже подключаем валидатор, проверяющий, что поле не пустое.
body = models.TextField(validators=[validate_not_empty])
is_answered = models.BooleanField(default=False)
Второй способ валидации: метод-валидатор
В классе формы для каждого отдельного поля можно создать метод clean_<имя поля>
, он вызовется автоматически при создании объекта этого класса..Этот метод не получает никаких дополнительных параметров: он самостоятельно запросит значение из словаря cleaned_data
.
Такой протокол создан для того, чтобы можно было совместить работу нескольких валидаторов — например, если необходимо провести дополнительную проверку поля.Значение, которое вернёт метод-валидатор, обновит значение соответствующего элемента словаря cleaned_data
.
Для класса ContactForm
напишем валидатор, который вернёт пользователю ошибку, если в поле «Тема» (Subject) нет благодарности в адрес администратора сайта.Зачем нужно письмо, если в нём тебе не говорят «спасибо»?
# forms.py
from django import forms
from .models import Contact
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
fields = ('name', 'email', 'subject', 'body')
# Метод-валидатор для поля subject
def clean_subject(self):
data = self.cleaned_data['subject']
# Если пользователь не поблагодарил администратора - считаем это ошибкой
if 'спасибо' not in data.lower():
raise forms.ValidationError('Вы обязательно должны нас поблагодарить!')
# Метод-валидатор обязательно должен вернуть очищенные данные,
# даже если не изменил их
return data
Классам полей формы и модели тоже можно присвоить методы валидации. Например, можно создать поле для ввода цвета ColorField
с валидацией — и валидация этого поля будет работать во всех формах, где это поле применяется.
После валидации
Если все валидаторы формы вернули True
— форма считается валидной, и метод формы .is_valid()
вернёт True
.
После валидации все данные будут переданы в словарь form.cleaned_data
, и для дальнейшей работы данные формы берут именно из этого словаря.
# views.py
from django.shortcuts import redirect
def user_contact(request):
# Проверяем, получен POST-запрос или какой-то другой:
if request.method == 'POST':
# Создаём объект формы класса ContactForm
# и передаём в него полученные данные
form = ContactForm(request.POST)
# Если все данные формы валидны - работаем с "очищенными данными" формы
if form.is_valid():
# Берём валидированные данные формы из словаря form.cleaned_data
name = form.cleaned_data['name']
email = form.cleaned_data['email']
subject = form.cleaned_data['subject']
message = form.cleaned_data['body']
# При необходимости обрабатываем данные
# ...
# сохраняем объект в базу
form.save()
# Функция redirect перенаправляет пользователя
# на другую страницу сайта, чтобы защититься
# от повторного заполнения формы
return redirect('/thank-you/')
# Если условие if form.is_valid() ложно и данные не прошли валидацию -
# передадим полученный объект в шаблон,
# чтобы показать пользователю информацию об ошибке
# Заодно заполним все поля формы данными, прошедшими валидацию,
# чтобы не заставлять пользователя вносить их повторно
return render(request, 'contact.html', {'form': form})
# Если пришёл не POST-запрос - создаём и передаём в шаблон пустую форму
# пусть пользователь напишет что-нибудь
form = ContactForm()
return render(request, 'contact.html', {'form': form})
Формы без модели
Формы, содержимое которых не нужно сохранять в базе данных (например, это формы, которые отправляют электронные письма), не привязываются к моделям и наследуются от класса Form
. Они работают точно так же, как и ModelForm
.
Отличие есть в вызове функций-валидаторов: аргумент validators
указывается в описании поля формы, а не модели.
# validators.py
# Функция-валидатор:
def validate_not_empty(value):
if value == '':
raise forms.ValidationError(
'А кто поле будет заполнять, Пушкин?',
params={'value': value},
)
# forms.py
class ContactForm(forms.Form):
name = forms.CharField(max_length=100, validators=[validate_not_empty])
email = forms.EmailField()
subject = forms.CharField(max_length=100)
body = forms.CharField(widget=forms.Textarea)
is_answered = forms.BooleanField(default=False)
Спасибо за доступное объяснение, изучаю Django, у Вас написано очень хорошо, логично и понятно!