Валидация форм в Django

Вы уже поработали с моделями и формами, созданными на основе 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) 


Комментарии: 1
  1. Максим

    Спасибо за доступное объяснение, изучаю Django, у Вас написано очень хорошо, логично и понятно!

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

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