Django Custom User Manager is not called when registering users on the admin panel

Viewed 273

When a user is created on Django admin panel, objects = CustomUserManager() is not being executed. However, when I create a superuser from the CLI using python manage.py createsuperuser, objects = CustomUserManager() is executed.

Here is the model file

class CustomUser(AbstractUser):
    username = None
    email = models.EmailField(_('email address'), unique=True)
    name = models.CharField(verbose_name=_("first name"), max_length=50)
    stripe_customer_id = models.CharField(max_length=120)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name']

    objects = CustomUserManager()

    def __str__(self):
        return self.name

Here is the customUserManager

class CustomUserManager(BaseUserManager):

    def create_user(self, email, password, name,**extra_fields):

        if not email:
            raise ValueError(_('The Email must be set'))
        email = self.normalize_email(email)
        customer = stripe.Customer.create(
            email=email,
            name=name,
        )     
        user = self.model(email=email,name=name, stripe_customer_id=customer.id ,**extra_fields)


        user.set_password(password)
        user.save()
        return user

def create_superuser(self, email, password,name,**extra_fields):
    """
    Create and save a SuperUser with the given email and password.
    """
    extra_fields.setdefault('is_staff', True)
    extra_fields.setdefault('is_superuser', True)
    extra_fields.setdefault('is_active', True)

    if extra_fields.get('is_staff') is not True:
        raise ValueError(_('Superuser must have is_staff=True.'))
    if extra_fields.get('is_superuser') is not True:
        raise ValueError(_('Superuser must have is_superuser=True.'))
    return self.create_user(email, password, name,**extra_fields)

Below is how I registered the admin panel.

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser
from django.utils.translation import gettext_lazy as _


class CustomUserAdmin(UserAdmin):
    add_form = CustomUserCreationForm
    form = CustomUserChangeForm
    model = CustomUser
    ....

admin.site.register(CustomUser, CustomUserAdmin)

Below is the CustomUserCreationForm

class CustomUserCreationForm(UserCreationForm):

    class Meta:
        model = CustomUser
        fields = "__all__"
2 Answers

UserAdmin relies on the implementation in ModelAdmin to construct an instance, validate the form, and then save the instance, rather than calling a custom method on the model's manager.

Call chain: ModelAdmin.add_view() → ... → self._changeform_view(), which calls:

  • (i) form.is_valid() (of ModelForm) → ... → self._post_clean() → self.instance = construct_instance(...),
  • (ii) self.save_form(...) → new_object = form.save(commit=False) (of BaseModelForm) returning self.instance, and
  • (iii) self.save_model(..., new_object, ...) → obj.save().

You can override ModelAdmin.save_form to call the custom user manager's create_user method and replace form.instance:

class CustomUserAdmin(UserAdmin):
    model = CustomUser
    fieldsets = (
        (None, {'fields': (model.USERNAME_FIELD, 'name', 'password')}),
    ) + UserAdmin.fieldsets[2:]
    add_fieldsets = (
        (None, {'fields': (model.USERNAME_FIELD, 'name', 'password1', 'password2')}),
    )
    ordering = (model.USERNAME_FIELD,)

    def save_form(self, request, form, change):
        if not change:
            form.instance = self.model.objects.create_user(
                form.cleaned_data['email'],
                form.cleaned_data['password1'],
                form.cleaned_data['name'],
            )
        return super().save_form(request, form, change)

⚠️ Notice in the usual call chain that save_form() does not call obj.save(), as ModelAdmin handles multiple formsets and m2m models, while CustomUserManager.create_user does call user.save(). It is ok to change this behaviour in UserAdmin with known scope, though you may consider splitting CustomUserManager.create_user into CustomUserManager.construct_instance and CustomUserManager.save_new_model (move stripe.Customer.create(...) and user.save() here).

As a matter of fact, your custom methods create_user (and create_superuser, which you probably overwrote as well) are not called when you create a new user from the admin panel. I'm not sure neither to understand why but I think the create_user function is just a shortcut.

Why not overriding the save() method of your CustomUser? From the Django documentation:

A classic use-case for overriding the built-in methods is if you want something to happen whenever you save an object.

Isn't exactly what you're looking for? Check the Django documentation here (to do so at the form level) and here (if you prefer change the model itself directly, no matter which form is used) for more information.

For instance, if you prefer the second option, you can add this method to CustomUser:

from django.apps.apps import get_model
from django.db import transaction

class CustomUser(AbstractUser):

    # your initial code goes here ...

    def save(self, *args, **kwargs):
        # make the distinction between the creation and the update of the instance
        if not self._state.adding:
               # doesn't change the initial method if the user has already been saved in DB
               return super(CustomUser, self).save(*args, **kwargs)
        # guarantee of atomicity
        with transaction.atomic():
                # dynamic resolution to avoid a circular import, might not be necessary
                customer = get_model("stripe", "Customer").create(
                    email=self.email,
                    name=self.name,
                )
                self.stripe_customer_id = customer.id
                super(CustomUser, self).save(*args, **kwargs)
                return self

Don't forget to make the distinction between the creation and the update of an instance.

Related