Django Registration Not Returning Error Messages

Viewed 22

Registration for this Django 4.0.3 project works well except for failure to present error messages and previously completed form field contents. In other words, registration is working with both user_form and profile_form completed and the database is updated as expected.

What is not working

  • If the user mismatches passwords no error is returned and page returns a blank form (I was under the impression by passing the forms back as a context dictionary that field data would be preserved - not sure what I'm missing in this regard)
  • The error check in forms.py to ensure a unique email is not working either (I do use forms error-checking extensively throughout the project). However, given the failure to present the password mismatch error I am inclined to believe both these symptoms share the same cause.
  • When registration is not valid (password mismatch or non-original email address) blank forms are returned. The fact that forms validation is not working properly suggests that there is something inherently wrong in the register view. However, after looking at this for a couple of days I'm hoping that the bright minds of Stack Overflow will be able to point me in the right direction...

I am including all the relevant code snippets in anticipation of their being requested to support diagnosis. Given that the only element of user registration that is not working is the failure to return error messages and preserve form field contents what I've included may well be over-kill. And, I won't be surprised if I've missed something. If so, please comment with any such requests and I'll update the question.

Code Snippets

settings.py

This snippet is included to show the default password validators.

# Password validation
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

models.py

I have added a one-to-one relationship between the built-in User model and my own Profile model. I am aware that the User model can be extended, but opted for a Profile model (for which the Django Documentation acknowledges as acceptable). I have omitted the COUNTRY_CHOICES constant as, by my estimation, these 200+ lines of code will not support diagnosis.

from django.db import models
from django.contrib.auth.models import User
from phone_field import PhoneField
# ...

# ####################### CHOICE CONSTANTS #######################
PRONOUN_CHOICES = [
    ('---', '---'),
    ('She/her', 'She/her'),
    ('He/him', 'He/him'),
    ('They/them', 'They/them'),
    ('Rather not say', 'Rather not say'),
]
NOTIFICATION_PREFERENCE = [
    ('None', 'None'),
    ('Email', 'EMail'),
    ('Text/SMS', 'Text/SMS'),
]
# Omitted COUNTRY_CHOICES to save space
# ...

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    pronoun = models.CharField(max_length=20, choices=PRONOUN_CHOICES, default='---')
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    full_name = models.CharField(max_length=200, blank=True, null=True)
    address_1 = models.CharField(max_length=100)
    address_2 = models.CharField(max_length=100, blank=True, null=True)
    city = models.CharField(max_length=50)
    state_province_county = models.CharField(max_length=100, verbose_name='State/Province/County')
    postal_zip_code = models.CharField(max_length=10, verbose_name='postal/zip code')
    country = models.CharField(max_length=100, choices=COUNTRY_CHOICES, default='---')
    phone = PhoneField(blank=True, help_text='Contact phone number')
    notification_preference = models.CharField(
        max_length=10,
        choices=NOTIFICATION_PREFERENCE,
        default='Email',
        help_text='''If you would like to receive booking confirmation notifications from us please select your preference.'''
    )

    def __str__(self):
        return f'{self.first_name} {self.last_name}'

forms.py

I'm using forms.py to validate for a unique email. I added this because the original approach (inspired by Corey Schafer) didn't appear to prevent duplicate email addresses.

from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm

from .models import Profile


# ############ Profile form validation ############
def profile_form_validation(form, form_type):
    cleaned_data = super(form_type, form).clean()
    if not cleaned_data.get('phone') and cleaned_data.get('notification_preference') == 'Text/SMS':
        form.add_error(
            'phone',
            'A phone number must be entered in order to be able to receive text messages on your phone.'
        )
    return


# ############ Forms ############
class UserRegisterForm(UserCreationForm):
    email = forms.EmailField()

    class Meta:
        model = User
        fields = [
            'username',
            'email',
            'password1',
            'password2',
        ]

    def clean(self):
        cleaned_data = super(UserRegisterForm, self).clean()
        if User.objects.filter(email=cleaned_data.get('email')).exists():
            self.add_error(
                'email',
                'That email address is already associated with an account. Please use another email address.',
            )
        return self.cleaned_data


class ProfileUpdateForm(forms.ModelForm):

    class Meta:
        model = Profile
        fields = [
            'pronoun',
            'first_name',
            'last_name',
            'address_1',
            'address_2',
            'city',
            'state_province_county',
            'postal_zip_code',
            'country',
            'phone',
            'notification_preference',
        ]

    def clean(self):
        profile_form_validation(self, ProfileUpdateForm)

        return self.cleaned_data

views.py

My instinct is that the cause of the problem is with the view to process registration. Please note that:

  • I am using login() to automatically log the user in once registration is complete.
  • The auto-login works if the both passwords match and the email address entered is unique.
  • The redirect to 'events' works.
  • The only functionality not working is the failure to return error messages or preserve previously completed fields (excepting password1 and password2 obviously).
from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth import login

from .models import Profile

from .forms import (
    UserRegisterForm,
    ProfileUpdateForm,
)


def register(request):
    if request.method == 'POST':
        user_form = UserRegisterForm(request.POST)
        profile_form = ProfileUpdateForm(request.POST)

        if user_form.is_valid() and profile_form.is_valid():
            # Process user form
            user = user_form.save(commit=False)
            user.save()
            # Process profile form
            profile_form.instance.user = user
            profile_form.instance.full_name = f'{profile_form.instance.first_name} {profile_form.instance.last_name}'
            profile_form.save()
            # Log in user after registration
            login(request, user)
            messages.success(request, f'Your account (username:{username}) has been created. '
                                      f'You are now logged in!')
            return redirect('events')

        else:
            user_form = UserRegisterForm()
            profile_form = ProfileUpdateForm()

        return render(request, 'users/register.html', {'user_form': user_form, 'profile_form': profile_form})

    else:
        user_form = UserRegisterForm()
        profile_form = ProfileUpdateForm()
        return render(request, 'users/register.html', {'user_form': user_form, 'profile_form': profile_form})

urls.py

Including urls.py is probably over-kill.

from django.contrib.auth import views as auth_views
from django.urls import path
# ...

urlpatterns = [
    # ...
    path('register/', user_views.register, name='register'),
    # ...
]

register.html

Template inclusion is also likely over-kill. However, this demonstrates the explicit use of form.errors. Again, there is no problem with template rendering (so I'm omitting base.html and the css file).

{% extends 'base.html' %}
{% load crispy_forms_filters %}
{% load crispy_forms_tags %}
{% block title %}Register{% endblock %}
{% block content %}
    <section class="content-section">
        <h3 class="mb-2"><strong>Registration</strong></h3>
    </section>
    {% if form.errors %}
        <section class="content-section-danger alert-section">
            <div>
            <p class="alert-danger">Please review the following:</p>
                <ul>
                    {% for key, value in form.errors.items %}
                        {% for error in value %}
                            {% if error != '' %}
                                <li class="alert-danger">{{ error }}</li>
                            {% endif %}
                        {% endfor %}
                    {% endfor %}
                </ul>
            </div>
        </section>
    {% endif %}
    <section class="content-section">
        <div class="row">
            <div class="col-lg-12 pt-2 pb-2">
                <ul class="list-group">
                    <li class="list-group-item list-group-item-BlueGray">
                        <strong>Your Account Details</strong>
                    </li>
                </ul>
            </div>
        </div>
        <form method="POST">
            {% csrf_token %}
            <fieldset class="form-group">
                <div class="row">
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ user_form.username|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ user_form.email|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ user_form.password1|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ user_form.password2|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                </div>
                <div class="row">
                    <div class="col-lg-12 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-BlueGray">
                                <strong>Your Profile</strong>
                            </li>
                        </ul>
                    </div>
                </div>
                <div class="row">
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ profile_form.pronoun|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ profile_form.first_name|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ profile_form.last_name|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                </div>
                <div class="row">
                    <div class="col-lg-9 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ profile_form.address_1|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ profile_form.address_2|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                </div>
                <div class="row">
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ profile_form.city|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ profile_form.state_province_county|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ profile_form.postal_zip_code|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                        {{ profile_form.country|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                </div>
                <div class="row">
                    <div class="col-lg-3 pt-2 pb-2">
                        <ul class="list-group">
                            <li class="list-group-item list-group-item-white">
                                {{ profile_form.phone|as_crispy_field }}
                            </li>
                        </ul>
                    </div>
                    {% if SMS_MESSAGING_ACTIVE == 'Yes' %}
                        <div class="col-lg-9 pt-2 pb-2">
                            <ul class="list-group">
                                <li class="list-group-item list-group-item-white">
                                    {{ profile_form.notification_preference|as_crispy_field }}
                                </li>
                            </ul>
                        </div>
                    {% else %}
                        {{ profile_form.notification_preference.as_hidden }}
                    {% endif %}
                </div>
                <div class="row">
                    <div class="col-lg-3">
                    </div>
                </div>

            </fieldset>
            <div class="row pt-4 pb-2">
                <div class="col-lg-3">
                    <button class="btn btn-success col-12" type="submit">Register</button>
                </div>
            </div>
        </form>
        <div class="pt-2">
            <small class="text-muted">
                Already have an account? <a href="{% url 'login' %}" class="ml-2 text-BlueGray">Log In</a>
            </small>
        </div>
    </section>
{% endblock content %}
1 Answers

I am not sure how I missed this, but apparently I was re-creating the forms with the else branch. face palm

I'm answering the question in the chance that someone else saves time.

Corrected views.py

from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth import login

from .models import Profile

from .forms import (
    UserRegisterForm,
    ProfileUpdateForm,
)


def register(request):
    if request.method == 'POST':
        user_form = UserRegisterForm(request.POST)
        profile_form = ProfileUpdateForm(request.POST)

        if user_form.is_valid() and profile_form.is_valid():
            # Process user form
            user = user_form.save(commit=False)
            user.save()
            # Process profile form
            profile_form.instance.user = user
            profile_form.instance.full_name = f'{profile_form.instance.first_name} {profile_form.instance.last_name}'
            profile_form.save()
            # Log in user after registration
            login(request, user)
            messages.success(request, f'Your account (username:{username}) has been created. '
                                      f'You are now logged in!')
            return redirect('events')

        return render(request, 'users/register.html', {'user_form': user_form, 'profile_form': profile_form})

    else:
        user_form = UserRegisterForm()
        profile_form = ProfileUpdateForm()
        return render(request, 'users/register.html', {'user_form': user_form, 'profile_form': profile_form})

The moral of this story, if you're validating forms there is no need to return those forms when the forms do not return as valid!

Related