Представьте, что вы охранник на входе в клуб. Каждый раз, когда кто-то пытается войти, вы проверяете его документы, чтобы убедиться, что всё в порядке. Аналогично, валидация помогает программе убедиться, что данные, введённые пользователем, соответствуют ожидаемым требованиям.
Django предоставляет нам как встроенные инструменты для валидации, так и возможность создавать свои собственные валидаторы. В этой лекции мы разберём оба подхода.
Встроенные валидаторы Django
Django из коробки предоставляет множество удобных и мощных валидаторов, которые облегчают жизнь разработчику. Эти валидаторы можно использовать прямо в моделях или формах.
Примеры встроенных валидаторов
Проверка уникальности данных (например,
usernameиemail): Django автоматически проверяет уникальность полей, если вы указали это в модели.Проверка на минимальную и максимальную длину: такие валидаторы используются для проверки длины текста, вводимого пользователем.
Проверка формата email: Django предоставляет валидатор для проверки корректности формата email.
Валидация пароля: встроенные валидаторы пароля помогают убедиться, что придуманный пользователем пароль достаточно сложен.
Пример использования встроенных валидаторов
Давайте добавим дополнительную проверку уникальности username и корректности формата email в форму регистрации:
from django import forms
from django.contrib.auth.models import User
class RegistrationForm(forms.ModelForm):
class Meta:
model = User
fields = ['username', 'email', 'password']
def clean_username(self):
username = self.cleaned_data.get('username')
if User.objects.filter(username=username).exists():
raise forms.ValidationError("Такое имя пользователя уже занято. Пожалуйста, выберите другое.")
return username
def clean_email(self):
email = self.cleaned_data.get('email')
if User.objects.filter(email=email).exists():
raise forms.ValidationError("Email уже используется. Пожалуйста, укажите другой.")
return email
- Метод
clean_<field>вызывается для каждого поля. Здесь мы проверяем уникальностьusernameиemail. - Если данные некорректны, вызывается
forms.ValidationError, который автоматически передаёт сообщение об ошибке в шаблон.
Кастомные валидаторы
Но что делать, если встроенные валидаторы не покрывают всех наших нужд? Например, если мы хотим, чтобы имя пользователя начиналось с буквы и содержало только буквы и цифры. Для таких случаев Django позволяет создавать кастомные валидаторы.
Пример кастомного валидатора
Мы создадим валидатор, который проверяет, чтобы имя пользователя начиналось с буквы.
import re
from django.core.exceptions import ValidationError
def validate_username(value):
if not re.match("^[A-Za-z][A-Za-z0-9]*$", value):
raise ValidationError(
"Имя пользователя должно начинаться с буквы и содержать только буквы и цифры."
)
Теперь мы можем подключить этот валидатор в нашей форме регистрации:
class RegistrationForm(forms.ModelForm):
username = forms.CharField(validators=[validate_username])
class Meta:
model = User
fields = ['username', 'email', 'password']
И, вуаля, наш пользователь больше не сможет регистрироваться как "123abc".
Предотвращение уязвимостей
Данные от пользователя всегда должны быть под подозрением, особенно если они поступают через веб-формы. Валидация — это первый рубеж обороны. Без неё ваш сайт может стать жертвой:
- SQL-инъекций: злоумышленники могут пытаться передать SQL-код вместо данных.
- XSS-атак: вставка скриптов для выполнения на стороне клиента.
- Дубликатов данных: повторяющиеся записи в базе данных.
Когда вы пишете валидацию, подумайте, что может пойти не так, и закройте все дыры.
Обработка ошибок валидации
Ошибки валидации — это не катастрофа, а просто сигнал пользователю, что он сделал что-то не так. В Django это обрабатывается через объект form.errors.
Пример отображения ошибок в шаблоне:
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
{% if form.errors %}
<ul>
{% for field, errors in form.errors.items %}
<li>{{ field }}: {{ errors|join:", " }}</li>
{% endfor %}
</ul>
{% endif %}
<button type="submit">Регистрация</button>
</form>
Теперь, если у пользователя что-то пошло не так, он увидит вежливое сообщение об ошибке.
Проверка паролей
Пароли — это отдельная песня. Django предоставляет PasswordValidator для проверки сложности пароля. Например, вы можете добавить проверку длины пароля или его схожести с именем пользователя.
Пример настройки валидаторов пароля в settings.py:
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 8,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
Таким образом, пользователи не смогут использовать "12345678" или "password" в качестве своих паролей.
Практическая задача
Допустим, нам нужно реализовать следующую логику валидации:
- Имя пользователя должно быть уникальным и начинаться с буквы.
- Email не должен уже существовать в базе данных.
- Пароль должен быть не менее 8 символов, содержать хотя бы одну цифру и одну букву.
Попробуем реализовать это:
from django import forms
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
import re
def validate_password_strength(password):
if len(password) < 8:
raise ValidationError("Пароль должен содержать не менее 8 символов.")
if not re.search(r"\d", password):
raise ValidationError("Пароль должен содержать хотя бы одну цифру.")
if not re.search(r"[A-Za-z]", password):
raise ValidationError("Пароль должен содержать хотя бы одну букву.")
class RegistrationForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput, validators=[validate_password_strength])
class Meta:
model = User
fields = ['username', 'email', 'password']
def clean_username(self):
username = self.cleaned_data.get('username')
if not re.match("^[A-Za-z][A-Za-z0-9]*$", username):
raise forms.ValidationError("Имя пользователя должно начинаться с буквы и содержать только буквы и цифры.")
if User.objects.filter(username=username).exists():
raise forms.ValidationError("Такое имя пользователя уже занято.")
return username
def clean_email(self):
email = self.cleaned_data.get('email')
if User.objects.filter(email=email).exists():
raise forms.ValidationError("Этот email уже используется.")
return email
Теперь наши формы готовы любой атаке, ну или почти любой!
Типичные ошибки при валидации
- Забвение об использовании метода
cleaned_data. В итоговой форме обрабатываются только данные, прошедшие валидацию. - Отсутствие проверки уникальности на уровне модели. Даже если валидатор пропустит данные, база данных не должна.
- Слишком общие сообщения об ошибках. Пользователи не должны догадываться, что они сделали не так.
Теперь, когда вы знаете, как работать с валидацией, ваши проекты смогут справляться с любой пользовательской "креативностью". Не забывайте тестировать свои валидаторы и регулярно обновлять их в зависимости от требований приложения.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ