Django Модель формы, кажется, есть аномалия, где мой метод clean_username формы создания пользователя не бросает ValidationError, когда он должен
Фон
У меня есть пользовательская модель User, которую я расширил из BaseUserManager
и AbstractBaseUser
. Аналогично, у меня есть пользовательская форма регистрации, которую я расширил из UserCreationForm
(все эти пользовательские элементы расширены из django.contrib.auth
).
В созданной мной пользовательской форме UserCreationForm есть метод clean_username()
, который проверяет имена пользователей, не содержащие буквенно-цифровых символов (-@
, --_
, +@-_
и т. д.), и в этом случае выбрасывает ошибку ValidationError (кроме того, метод также проверяет имена пользователей, которые при сглаживании приведут к неуникальным сглаживаниям).
Проблема
Когда я проверяю вышеупомянутую ValidationError, она все равно каким-то образом возвращает форму как валидную,
- , что приводит к запуску остальной логики моего представления,
- , что приводит к запуску метода
clean()
модели пользователя,- и в методе
clean()
модели пользователя у меня есть те же проверки, что и в методеclean_username()
формы (для пользователей, созданных без использования этой конкретной формы). Эти проверки выбрасываютValueError
, что не идеально, потому что мне нужно, чтобы форма была переоткрыта с правильнымValidationError
сообщением, чтобы пользователи могли исправить ошибку и зарегистрироваться.
- и в методе
- , что приводит к запуску метода
Все это не имеет смысла. Я поместил сообщения печати в метод clean_username()
, чтобы точно знать, что ветвь условной логики с ValidationError
, которую я хочу выбросить, выполняется, но она либо не выбрасывает ошибку, либо форма неправильно оценивается как валидная
Потенциальная проблема
Метод clean_username()
на форме - это то, что уже было в коде из django.contrib.auth.forms
, так что, возможно, я неправильно его расширяю?
Сообщение об ошибке
[18/May/2024 02:05:46] "POST /signup/ HTTP/1.1" 200 10330
-----------method called .clean_username() is running-----------
-----------user slug is gonna be empty-----------
Internal Server Error: /signup/
Traceback (most recent call last):
File "/Users/TRON/Documents/SCIENCE/CMPTRS/HTML/webProjectsForPublishing/terp/terpBackend/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
File "/Users/TRON/Documents/SCIENCE/CMPTRS/HTML/webProjectsForPublishing/terp/terpBackend/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/Users/TRON/Documents/SCIENCE/CMPTRS/HTML/webProjectsForPublishing/terp/terpBackend/djangoTerp/home/views.py", line 85, in signup
if form.is_valid():
File "/Users/TRON/Documents/SCIENCE/CMPTRS/HTML/webProjectsForPublishing/terp/terpBackend/venv/lib/python3.8/site-packages/django/forms/forms.py", line 201, in is_valid
return self.is_bound and not self.errors
File "/Users/TRON/Documents/SCIENCE/CMPTRS/HTML/webProjectsForPublishing/terp/terpBackend/venv/lib/python3.8/site-packages/django/forms/forms.py", line 196, in errors
self.full_clean()
File "/Users/TRON/Documents/SCIENCE/CMPTRS/HTML/webProjectsForPublishing/terp/terpBackend/venv/lib/python3.8/site-packages/django/forms/forms.py", line 435, in full_clean
self._post_clean()
File "/Users/TRON/Documents/SCIENCE/CMPTRS/HTML/webProjectsForPublishing/terp/terpBackend/venv/lib/python3.8/site-packages/django/contrib/auth/forms.py", line 129, in _post_clean
super()._post_clean()
File "/Users/TRON/Documents/SCIENCE/CMPTRS/HTML/webProjectsForPublishing/terp/terpBackend/venv/lib/python3.8/site-packages/django/forms/models.py", line 486, in _post_clean
self.instance.full_clean(exclude=exclude, validate_unique=False)
File "/Users/TRON/Documents/SCIENCE/CMPTRS/HTML/webProjectsForPublishing/terp/terpBackend/venv/lib/python3.8/site-packages/django/db/models/base.py", line 1477, in full_clean
self.clean()
File "/Users/TRON/Documents/SCIENCE/CMPTRS/HTML/webProjectsForPublishing/terp/terpBackend/djangoTerp/home/models.py", line 177, in clean
raise ValueError("Username must have at least one alpha-numerical character")
ValueError: Username must have at least one alpha-numerical character
Код
models.py
forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
# error handling
from django.core.exceptions import ValidationError
class SignUpForm(UserCreationForm):
# fields such as email, first_name, last_name, that I defined manually
class Meta:
model = User
fields = [
'email', 'username', 'first_name', 'last_name', 'password1', 'password2',
]
help_texts = {
'username': 'Letters, digits, and @ . + - _ only',
}
def clean_username(self):
"""Reject usernames that differ only in case or whose resulting slugs differ only in case
This is modified from django.contrib.auth.forms-----------------------------."""
print("-----------method called .clean_username() is running-----------")
username = self.cleaned_data.get("username")
potential_user_slug = slugify(username)
# adding validation for 1) no empty slugs allowed
if potential_user_slug == "":
print("-----------user slug is gonna be empty-----------")
raise ValidationError("Username must have at least one alpha-numerical character")
# adding validation for 2) making sure username doesn't result in identical slug to another user
elif (
potential_user_slug
and self._meta.model.objects.filter(slug__iexact=potential_user_slug).exists()
):
self._update_errors(
ValidationError(
{
"username": self.instance.unique_error_message(
self._meta.model, ["username"]
)
}
)
)
elif (
username
and self._meta.model.objects.filter(username__iexact=username).exists()
):
self._update_errors(
ValidationError(
{
"username": self.instance.unique_error_message(
self._meta.model, ["username"]
)
}
)
)
else:
return username
Обратите внимание на операторы print() в методе clean_username()
выше. Эти операторы печати выполняются в приведенном выше сообщении об ошибке, поэтому я знаю, что ветвь кода с ошибкой ValidationError должна выполняться
views.py
from django.conf import settings
import requests
from home.forms import SignUpForm
def signup(request):
"""View for registering a new user in the system"""
recaptcha_site_key=settings.GOOGLE_RECAPTCHA_SITE_KEY
if request.user.is_authenticated:
return HttpResponseRedirect(('nope'))
else:
# If this is a POST request, then process the form data
if request.method == 'POST':
# Create a form instance and populate it with data from the request (binding):
form = SignUpForm(request.POST)
# Check if the form is valid:
if form.is_valid():
"""logic for reCAPTCHA validation"""
if result['success']:
# do stuff cuz gonna email confirm first
user = form.save(commit=False)
# check if user's email is academic or not
if bool(re.match(r".*\.edu$", str(user.email))):
user.has_edu_email = True
else:
user.has_edu_email = False
user.save()
"""
----- Buncha stuff needed to send email through SendGrid ------------
"""
"""
Done sending email
"""
return HttpResponseRedirect(('account-activation-email-sent'))
else:
messages.error(request, 'Invalid reCAPTCHA. Please try again.')
else:
form = SignUpForm()
return render(request, 'registration/signup.html', {
'form': form, 'recaptcha_site_key': recaptcha_site_key,
})
Обратите внимание на утверждение if form.is_valid()
выше. Мы видим в моем трассировочном окне ошибок, что это утверждение оценивается как true. Почему оно оценивается в true, когда метод clean_username()
моего пользовательского UserCreationForm должен выбрасывать ValidationError
?
"Закапывать себя в яму?"
Я понимаю, что, скорее всего, сам залез в яму, создав URL-шлюзы для каждого пользователя на основе его имени пользователя. Вероятно, это было плохое решение. В то время оно казалось ужасно умным.
Решение
Оказалось, что мое представление signup
не обрабатывало ошибки формы таким образом, чтобы они работали с измененным методом формы clean_username()
. Я добавил в представление следующее:
views.py
from django.conf import settings
import requests
from home.forms import SignUpForm
from django.core.exceptions import ValidationError
def signup(request):
"""View for registering a new user in the system"""
recaptcha_site_key=settings.GOOGLE_RECAPTCHA_SITE_KEY
if request.user.is_authenticated:
return HttpResponseRedirect(('nope'))
else:
# If this is a POST request, then process the form data
if request.method == 'POST':
# Create a form instance and populate it with data from the request (binding):
form = SignUpForm(request.POST)
try:
# Check if the form is valid:
if form.is_valid():
"""logic for reCAPTCHA validation"""
if result['success']:
# do stuff cuz gonna email confirm first
user = form.save(commit=False)
# check if user's email is academic or not
if bool(re.match(r".*\.edu$", str(user.email))):
user.has_edu_email = True
else:
user.has_edu_email = False
user.save()
"""
----- Buncha stuff needed to send email through SendGrid ------------
"""
"""
Done sending email
"""
return HttpResponseRedirect(('account-activation-email-sent'))
else:
messages.error(request, 'Invalid reCAPTCHA. Please try again.')
except ValidationError as e:
form.add_error(None, e)
except Exception as e:
print("A form validation error occurred: ", e)
else:
form = SignUpForm()
return render(request, 'registration/signup.html', {
'form': form, 'recaptcha_site_key': recaptcha_site_key,
})
Поэтому добавление блока try
с приведенными ниже блоками исключений оказалось решающим.
except ValidationError as e:
form.add_error(None, e)
except Exception as e:
print("A form validation error occurred: ", e)
Я также упростил код в своем методе clean_username()
формы:
forms.py
def clean_username(self):
"""Reject usernames that differ only in case or whose resulting slugs differ only in case."""
username = self.cleaned_data.get("username")
potential_user_slug = slugify(username)
# Check for empty slug
if potential_user_slug == "":
raise ValidationError("Username must have at least one alpha-numerical character.")
# Check for non-unique slug
if self._meta.model.objects.filter(slug__iexact=potential_user_slug).exists():
raise ValidationError("Username is not unique enough (differs from another user only by one or more non-alphanumerical character).")
# Check for non-unique username
if self._meta.model.objects.filter(username__iexact=username).exists():
raise ValidationError("A user with that username already exists.")
return username