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
Вернуться на верх