Django Model Form, seems like there is an anomaly where my user creation form’s clean_username method is not throwing a ValidationError when it should

Background

I have a custom User model that I have extended from BaseUserManager and AbstractBaseUser. Similarly, I have a custom sign-up form that I’ve extended from UserCreationForm (all these custom items are extended from django.contrib.auth).

The custom UserCreationForm I’ve made has a method called clean_username() that checks for usernames that do not have any alpha-numerical characters (-@, --_, +@-_, etc.) and throws a ValidationError if that is the case (aside: the method also checks for usernames that, when slugified, would result in non-unique slugs).

Problem

When I test the aforementioned ValidationError, it still is somehow returning the form as valid,

  • which causes the rest of my view’s logic to run,
    • which causes the User Model’s clean() method to run,
      • and in the User Model’s clean() method, I have the same checks I have in the form’s clean_username() method (for User’s created without using this particular form). These checks throw a ValueError, which is not ideal because I need the form to be re-rendered with the proper ValidationError message so that users can fix what’s wrong and still sign up.

None of this makes sense. I’ve put print messages in my clean_username() method so that I know for sure the branch of conditional logic with the ValidationError I want it to throw is running, but it’s either not throwing the error or the form is being incorrectly evaluated as valid

Potential Problem

The clean_username() method on the form is something that was already in the code from django.contrib.auth.forms, so maybe I’m not extending it correctly?

Error Message

[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

Code

models.py

from django.db import models
from django.contrib.auth.base_user import BaseUserManager, AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.validators import ASCIIUsernameValidator

class TerpUserManager(BaseUserManager):
    def create_user(self, username, email, category, password=None):
        """
        Creates and saves a User with the given email, category, username and password.
        """
        if not email:
            raise ValueError("Users must have an email address")

        user = self.model(
            # added .casefold() to make it so users can really only sign up with case-insensitive usernames. DB will still allow case-sensitive usernames, but the create_user and clean method ain't gonna allow it.
            username=str(username).casefold(),
            # added .casefold() to make it so users can really only sign up with case-insensitive local-portion email addresses. i know it ain't RCF 5322 spec, but I don't want duplicate users. DB will still allow case-sensitive local-portion email addresses, but the create_user and clean method ain't gonna allow it.
            email=str(self.normalize_email(email)).casefold(),
            category=category,
        )

        user.set_password(password)
        user.clean()
        user.save(using=self._db)
        return user

class TerpUser(AbstractBaseUser, PermissionsMixin):
    username_validator = ASCIIUsernameValidator()

    username = models.CharField(
        _("username"),
        max_length=150,
        unique=True,
        db_index=True,
        help_text=_(
            "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
        ),
        validators=[username_validator],
        error_messages={
            "unique": _("A user with that username already exists."),
        },
    )
    slug = models.SlugField(_("URL slug"), null=False, unique=True)
    first_name = models.CharField(_("first name"), max_length=70, blank=True)
    last_name = models.CharField(_("last name"), max_length=70, blank=True)
    email = models.EmailField(
        _("email address"),
        unique=True,
        db_index=True,
        help_text=_(
            "Required."
        ),
        error_messages={
            "unique": _("A user with that email address already exists."),
        },
    )
    # bunch of other fields, such as is_staff, is_active, date_joined, last_login, has_edu_email

    objects = TerpUserManager()

    EMAIL_FIELD = "email"
    USERNAME_FIELD = "username"
    REQUIRED_FIELDS = ["email", "category"]

    def get_absolute_url(self):
        return reverse("member-detail", kwargs={"slug": self.slug})
    
    def clean(self):
        super().clean()
        # added .casefold() to make it so users can really only sign up with case-insensitive local-portion email addresses. i know it ain't RCF 5322 spec, but I don't want duplicate users. DB will still allow case-sensitive local-portion email addresses, but the create_user and clean method ain't gonna allow it.
        self.email = str(self.__class__.objects.normalize_email(self.email)).casefold()
        self.username = str(self.username).casefold()
        # adding validation for 1) making sure username doesn't result in empty-string slug
        potential_user_slug = slugify(self.username)
        if potential_user_slug == "":
            raise ValueError("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
        if TerpUser.objects.filter(slug=potential_user_slug).exists():
            raise ValueError("Username is not unique enough")

        # set user's slug
        self.slug = slugify(self.username)

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

Notice the print() statements in the clean_username() method above. These print statements are executing in the Error readout above so I know that the branch of code with the ValidationError should be executing

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,
        })

Notice the if form.is_valid() statement above. We can see in my error traceback that this statement is evaluating to true. Why is it evaluating to true when my custom UserCreationForm’s clean_username() method should be throwing a ValidationError?

Dug Myself into a Hole?

I realize that I likely have dug myself into a hole by creating URL slugs for each user from their username. This was probably a bad decision. It seemed terribly clever at the time.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật