djangodjango-modelsdjango-viewsdjango-formsdjango-templates

Password field in Signup/Login form not appearing properly | Django | HTML | Bootstrap


I am trying to create a custom signup form using HTML, CSS, Bootstrap as front end, and Django as backend, when I create the form, firstname, lastname, and email are appearing as required, but the password and confirm_password fields are not appearing as required, from forms.py, the widget's placeholder and class=form-control is also not being applied to it.

I asked ChatGPT, it is asking to clear Cache, switching to incognito or a different browser, I tried all things, and still it is appearing the same. I am providing all the files associated with the signup form I created.

  1. models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.db import models

class CustomUserManager(BaseUserManager):
    def create_user(self, email, first_name, last_name=None, password=None, **extra_fields):
        if not email:
            raise ValueError('The Email field must be set.')
        if not first_name:
            raise ValueError('The First Name field must be set.')
        if not password:
            raise ValueError('Password cannot be left blank.')
        
        email = self.normalize_email(email)
        user = self.model(email=email, first_name=first_name, last_name=last_name, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

class CustomUser(AbstractBaseUser):
    email = models.EmailField(unique=True)
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50, blank=True, null=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    objects = CustomUserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name']

    def __str__(self):
        return self.email
  1. forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser

class SignUpForm(UserCreationForm):

    class Meta:
        model = CustomUser
        fields = ['first_name', 'last_name', 'email', 'password1', 'password2']
        widgets = {
            'first_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter your first name'}),
            'last_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter your last name'}),
            'email': forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'Enter your email'}),
            'password1': forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'Enter password'}),
            'password2': forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'Confirm password'}),
        }
  1. views.py
from django.shortcuts import render
from .forms import SignUpForm

def SignupView(request):
    if request.method == 'POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            user = form.save()
            login(request, user)
            return redirect('home')
    else:
        form = SignUpForm()
    return render(request, 'signup.html', {'form': form})

def LoginView(request):
    return render(request, 'login.html')

  1. signup.html
{% extends 'base.html' %}

{% block content %}
    <!-- Top Section with Banner -->
    <section class="container my-5 py-5 bg-light">
        <div class="row mt-5 py-5">
            <div class="col-12 text-center">
                <h1 style="color:#333;">Join Us!</h1>
                <p class="lead lh-lg" style="color:#555;">We'd love to hear from you!</p>
            </div>
        </div>
    </section>
    
    <!-- Sign Up Section -->
    <section class="container my-5 py-5 bg-light">
        <div class="row justify-content-center">
            <div class="col-md-6">
                <div class="card">
                    <h5 class="card-header">Sign Up</h5>
                    <div class="card-body">
                        <form method="post">
                            {% csrf_token %}
                            {{ form.non_field_errors }}
                            <div class="mb-3 row">
                                <div class="col">
                                    {{ form.first_name }}
                                </div>
                                <div class="col">
                                    {{ form.last_name }}
                                </div>
                            </div>
                            <div class="mb-3">
                                {{ form.email }}
                            </div>
                            <div class="mb-3">
                                {{ form.password1 }}
                            </div>
                            <div class="mb-3">
                                {{ form.password2 }}
                            </div>
                            <button type="submit" class="btn btn-primary w-100">Sign Up</button>
                            <!-- Divider and Social Login Intro -->
                            <hr>
                            <p class="text-muted text-center">or sign up with</p>
                            <!-- Social Media Login Buttons -->
                            <div class="d-grid gap-2">
                                <button type="button" class="btn btn-primary btn-block mb-2" style="background-color: #3b5998;">
                                    <i class="fab fa-facebook-f me-2"></i> Facebook
                                </button>
                                <button type="button" class="btn btn-primary btn-block" style="background-color: #db4437;">
                                    <i class="fab fa-google me-2"></i> Google
                                </button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </section>
{% endblock %}

When the mentioned code is run I get the following output for signup.html

enter image description here

and following is the desired output that I want:

enter image description here

Notice the difference in the password and confirm password fields, that is the issue.


Solution

  • The BaseUserCreationForm model form explicitly declares the password fields (has to, because they are not part of the model), and you can't use Meta.widgets to overwrite the widget of a form field that was explicitly declared. You can only overwrite fields that were implicitly added by the model form factory.
    (yeah, I agree that is weird)

    Fields defined declaratively are left as-is, therefore any customizations made to Meta attributes such as widgets, labels, help_texts, or error_messages are ignored; these only apply to fields that are generated automatically.

    Links: django docs and a a related SO question

    If you want to set your own widgets, you will need to declare the fields again:

    class SignUpForm(UserCreationForm):
        password1 = forms.CharField(
            label=_("Password"),
            strip=False,
            widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'Enter password'}),
            help_text=password_validation.password_validators_help_text_html(),
        )
        password2 = forms.CharField(
            label=_("Password confirmation"),
            widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'Confirm password'}),
            strip=False,
            help_text=_("Enter the same password as before, for verification."),
        )
    
        class Meta:
            model = CustomUser
            fields = ['first_name', 'last_name', 'email', 'password1', 'password2']
            widgets = {
                'first_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter your first name'}),
                'last_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter your last name'}),
                'email': forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'Enter your email'}),
            }