Custom add user form in Django Admin?

How can you use a custom “add user” form along with a custom “user model”? After defining a custom User model for email login, it is important that the “add user” form asks for an email and not a username to create a user.

This is the form that appears after clicking “add user” in Django admin. I need to turn the Username field into an Email field since I am using email for authentication in my custom user model.

image

Changing the User’s email field to unique=True, blank=False and USERNAME_FIELD = ‘email’ doesn’t work. I have also tried attaching custom "UserCreationForm"s to UserAdmin but that didn’t help either.

Can you share the contents of the admin.py and models.py?

What specifically do you mean by “doesn’t work”?

If you have your model defined correctly, setting USERNAME_FIELD should result in the form field named “Username” storing the entry there into the email field.

My admin.py:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserCreationForm
from django.forms import EmailField
from .models import Account


class UserCreateForm2(UserCreationForm):
    class Meta:
        model = Account
        fields = ('email',)
        field_classes = {"email": EmailField}

    # placeholders = ['example@email.com', 'Username', ]
    
    
class AccountAdmin(UserAdmin):
    form = UserCreateForm2
    add_form = UserCreateForm2
    list_display = ('email', 'username', 'date_joined', 'last_login', 'is_staff', )
    search_fields = ('email', 'username')
    readonly_fields = ('date_joined', 'last_login')

    def get_form(self, request, obj=None, **kwargs):
        """
        Use special form during user creation
        """
        return self.form


admin.site.register(Account, AccountAdmin)

My models.py:

from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractUser


class MyAccountManager(BaseUserManager):
    def create_user(self, email, username, password=None):
        if not email:
            raise ValueError("Users must have an email address")
        if not username:
            raise ValueError("Users must have a username")
        user = self.model(
            email=self.normalize_email(email),
            username=username
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, username, password):
        user = self.create_user(
            email=self.normalize_email(email),
            password=password,
            username=username,
        )
        user.is_staff = True
        user.is_superuser = True
        user.save(using=self._db)
        return user


class Account(AbstractUser):
    email = models.EmailField(verbose_name="email", max_length=60, unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

    objects = MyAccountManager()

    def __str__(self):
        return self.email

    def has_module_perms(self, app_label):
        return True

The result is this form when I add users from within the admin:

image

When the form is saved, the value of the "Username: " field is saved as the user’s username and the user object is created having no email! The email can be added afterwards though. I was looking for a way to substitute the "Username: " field in that form with an "Email: " field since I am using email as my USERNAME_FIELD and it seems more sensible to have it that way.

Note that if you’re working from the example at Customizing authentication in Django | Django documentation | Django, you’re building from the wrong base class. You want to inherit your model from AbstractBaseUser and not AbstractUser.

You also won’t want to include any references to an independent field named username - that’s only going to confuse the issue. (Notice the lack of references to a username field in the MyUserManager class.)

I am inheriting from the AbstractUser and not the AbstractBaseUser because it comes with all the default fields and usability. The reason I am overriding the username field here is because I was trying to define it as “blank=True” or make it optional since I want to use USERNAME_FIELD = ‘email’. I don’t think these actions should stand in the way of changing that “Username:” field in the “Add new” form.

Quoting directly from the docs:

The easiest way to construct a compliant custom user model is to inherit from AbstractBaseUser. AbstractBaseUser provides the core implementation of a user model, including hashed passwords and tokenized password resets. You must then provide some key implementation details:

and

If you’re entirely happy with Django’s User model, but you want to add some additional profile information, you could subclass django.contrib.auth.models.AbstractUser and add your custom profile fields, although we’d recommend a separate model as described in Specifying a custom user model. AbstractUser provides the full implementation of the default User as an abstract model.

Regarding this second quote, you’re going well beyond “add some additional profile information”.

So yes, you can continue to try and make this work this way, or you can take the advice and guidance of the documentation and replace your base class.

1 Like

I finally managed to customize the add user form in the admin by overriding the “add_fields” attribute of the UserAdmin class. Here is how my UserAdmin looks like now:

class AccountAdmin(UserAdmin):
    # form = UserCreateForm2
    # add_form = UserCreateForm2
    list_display = ('email', 'date_joined', 'last_login', 'is_staff', )
    search_fields = ('email', )
    readonly_fields = ('date_joined', 'last_login')
    fieldsets = (
        (None, {"fields": ("email", "password")}),
        (_("Personal info"), {"fields": ("first_name", "last_name")}),
        (
            _("Permissions"),
            {
                "fields": (
                    "is_active",
                    "is_staff",
                    "is_superuser",
                    "company",
                    "groups",
                    "user_permissions",
                ),
            },
        ),
        (_("Important dates"), {"fields": ("last_login", "date_joined")}),
    )
    add_fieldsets = (
            (
                None,
                {
                    'classes': ('wide',),
                    'fields': ('email', 'first_name', 'last_name', 'password1', 'password2'),
                },
            ),
        )

And the result is:
image

2 Likes