Django 5.x When updating user from Admin Panel, username value replace email value

Hi Everyone,

I use Django since a while and Im working on a simple project where user login with email but also have a required username on user creation.

Here’s my user models.py

from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
from django.utils import timezone
from django.contrib.auth.models import PermissionsMixin

class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, username, password, **extra_fields):
        # if not username:
        #     raise ValueError('Nom utilisateur requis')
        # if not email:
        #     raise ValueError('Addresse courriel requis')
        email = self.normalize_email(email)
        username = username.strip()
        user = self.model(email=email, username=username, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, username, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, username, password, **extra_fields)

    def create_superuser(self, email, username, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)
        return self._create_user(email, username, password, **extra_fields)


# the custom user model
class User(AbstractBaseUser, PermissionsMixin):

    username = models.CharField(
        'Nom utilisateur', max_length=50, unique=True)
    is_staff = models.BooleanField('staff status', default=False)
    is_active = models.BooleanField('active', default=True)
    is_superuser = models.BooleanField('superuser', default=False)
    date_joined = models.DateTimeField('date joined', default=timezone.now)
    email = models.EmailField("Courriel", max_length=255, unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']
    objects = UserManager()

    def __str__(self):
        return self.username

    def get_username(self):
        return self.username

Here’s my admin.py

from typing import Any
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
from django.utils.translation import gettext_lazy as _

class CustomUserAdmin(UserAdmin):
    model = User
    """Define admin model for custom User model with no username field."""
    fieldsets = (

        (None, {'fields': ('username', 'email', 'password')}),
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                       'groups', 'user_permissions')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),

    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('username', 'email', 'password',),
        }),
    )
    list_display = ('username', 'email', 'is_staff')
    search_fields = ('username', 'email')
    ordering = ('email', 'username')

    def save_model(self, request, obj, form, change):
        super().save_model(request, obj, form, change)


admin.site.register(User, CustomUserAdmin)

Now when I create a super user from shell : email, username, password has to be filled correctly.

Then I log in with these credential on admin panel and it work.

All appear right but for instance, when I add a group to my user and I save, the email value become the email value. I checked in database and effectively the email entry is now same to the username entry.
I tried various way, trying to backtrack the problem and my though is the problem come from the

django/contrib/admin/options.py

but maybe not. I need help.

please focus on positive comment. I’d like to improve my understanding. Thank you

I’m sorry, I’m not understanding the situation you’re trying to describe here.

The email field will always be stored in the email field of the model, even if you’re using the USERNAME_FIELD option. The USERNAME_FIELD option does not change any field names - it’s an indication to the authentication backend to use that field for authentication.

Note: I can see the possibility of confusion with still having a field named username in the model when you’re using email as the “username” for authentication. What I would suggest is that you rename your “username” field as user_name and remove the field named “username”.

Hi KenWhitesell,

Gotcha, gonna give a try in few minute. I will give a follow-up based on result.

I 100% understand. USERNAME_FIELD tag is just a reference for which field is used as login credential.

Hi Ken,

Unfortunately, something went wrong when trying your solution.

I’ll will post here the updated models.py and admin.py and the given error

models.py

from django.db import models
from django.contrib.auth.models import AbstractUser, BaseUserManager, AbstractBaseUser
from django.utils import timezone
from django.contrib.auth.models import PermissionsMixin


class UserManager(AbstractUser):
    use_in_migrations = True

    def _create_user(self, email, username, password, **extra_fields):
        # if not username:
        #     raise ValueError('Nom utilisateur requis')
        # if not email:
        #     raise ValueError('Addresse courriel requis')
        email = self.normalize_email(email)
        username = username.strip()
        user = self.model(email=email, user_name=username, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, username, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, username, password, **extra_fields)

    def create_superuser(self, email, username, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)
        return self._create_user(email, username, password, **extra_fields)


# the custom user model
class User(AbstractBaseUser, PermissionsMixin):

    user_name = models.CharField(
        'Nom utilisateur', max_length=50, unique=True)
    is_staff = models.BooleanField('staff status', default=False)
    is_active = models.BooleanField('active', default=True)
    is_superuser = models.BooleanField('superuser', default=False)
    date_joined = models.DateTimeField('date joined', default=timezone.now)
    email = models.EmailField("Courriel", max_length=255, unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['user_name']
    objects = UserManager()

    def __str__(self):
        return self.user_name

admin.py

# from typing import Any
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
from django.utils.translation import gettext_lazy as _


class CustomUserAdmin(UserAdmin):
    model = User
    """Define admin model for custom User model with no username field."""
    fieldsets = (

        (None, {'fields': ('user_name', 'email', 'password')}),
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                       'groups', 'user_permissions')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),

    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('user_name', 'email', 'password',),
        }),
    )
    list_display = ('user_name', 'email', 'is_staff')
    search_fields = ('user_name', 'email')
    ordering = ('user_name', 'email')

    def save_model(self, request, obj, form, change):
        super().save_model(request, obj, form, change)


admin.site.register(User, CustomUserAdmin)

Errors :

ERRORS:
Application.User.groups: (fields.E304) Reverse accessor 'Group.user_set' for 'Application.User.groups' clashes with reverse accessor for 'Application.UserManager.groups'.
        HINT: Add or change a related_name argument to the definition for 'Application.User.groups' or 'Application.UserManager.groups'.
Application.User.user_permissions: (fields.E304) Reverse accessor 'Permission.user_set' for 'Application.User.user_permissions' clashes with reverse accessor for 'Application.UserManager.user_permissions'.
        HINT: Add or change a related_name argument to the definition for 'Application.User.user_permissions' or 'Application.UserManager.user_permissions'.
Application.UserManager.groups: (fields.E304) Reverse accessor 'Group.user_set' for 'Application.UserManager.groups' clashes with reverse accessor for 'Application.User.groups'.
        HINT: Add or change a related_name argument to the definition for 'Application.UserManager.groups' or 'Application.User.groups'.
Application.UserManager.user_permissions: (fields.E304) Reverse accessor 'Permission.user_set' for 'Application.UserManager.user_permissions' clashes with reverse accessor for 'Application.User.user_permissions'.
        HINT: Add or change a related_name argument to the definition for 'Application.UserManager.user_permissions' or 'Application.User.user_permissions'.


Now I Ctrl+Z to the previous point but the Errors still there. I dont understand what is going on.

Any clue?

EDIT :

I replaced in models.py →

class UserManager(AbstractUser):
    use_in_migrations = True

by

class UserManager(BaseUserManager):
    use_in_migrations = True

now I can perform manage.py makemigrations without error but now

Error :

django.db.utils.OperationalError: (1005, ‘Can't create table jdmq.django_admin_log (errno: 150 “Foreign key constraint is incorrectly formed”)’)

After the makemigrations, did you run migrate?

If so, then the easy solution here is to drop and recreate the database. (There are some other things that could be tried, but are quite a bit more work.)

Here is what I attempt :

Drop DB and recreate it. MakeMigration then Migrate. No success.

next, I opened the documentation : Customizing authentication in Django | Django documentation | Django

just to make sure I was not missing something.

A lot of change have been made since my previous check (maybe Django 3 or early 4).

So I decide to follow step by step the instruction.

In fact, actually, I have a copy of the full example. The only difference is I replaced date_of_birth by user_name in model and made match accordingly the admin.py

When I will try to create a new empty project on previous known version and try it.

1 Like

Hi Ken,

From fresh installment on version 4.0.4 with previous shared code. All went fine to migrate.

I had to add :

def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True

Which I will need to check further what it does. But everything work as expected.

Now, I will make a new fresh installment on latest 5.x Django version and repeat the step I did on 4.0.4

I let you know.

Hi Ken,

On fresh Installment on django 5.0.x (latest version) all is working fine. I just get confused why it’s working now.

Anyway thank you, I guess I made a mistake somewhere I cant figure out.