Difference between AbstractUser and AbstractBaseUser in Django?

Question:

The use of AbstractUser and AbstractBaseUser looks quite similar.

from django.contrib.auth.models import AbstractUser, AbstractBaseUser

What is the difference between the two?

Asked By: Pranjal Mittal

||

Answers:

The documentation explains this fully. AbstractUser is a full User model, complete with fields, as an abstract class so that you can inherit from it and add your own profile fields and methods. AbstractBaseUser only contains the authentication functionality, but no actual fields: you have to supply them when you subclass.

Answered By: Daniel Roseman

The AbstractUser is basically just the “User” class you’re probably already used to. AbstractBaseUser makes fewer assumptions and you have to tell it what field represents the username, what fields are required, and how to manage those users.

If you’re just adding things to the existing user (i.e. profile data with extra fields), then use AbstractUser because it’s simpler and easier. If you want to rethink some of Django’s assumptions about authentication, then AbstractBaseUser gives you the power to do so.

Answered By: Piyush S. Wanare

First of all, I explain AbstractUser then AbstractBaseUser.

<AbstractUser>

"AbstractUser" class initially has 11 fields same as default "User" class as shown below and for the subclass of "AbstractUser" class, you can add new fields, change and remove initial fields. *Keep it in mind that "username" and "email" fields in the initial fields of "AbstractUser" class are special and only "username" field has Unique Constraint.

These are the initial fields of "AbstractUser" class which default "User" class has as shown below:

id
password
last_login
is_superuser
username (Special, Unique Constraint)
first_name
last_name
email (Special)
is_staff
is_active
date_joined

Now, set "pass" for the class "CustomUser(AbstractUser)" as shown below:

# "account/models.py"

from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    pass

Then, run this command below:

python manage.py makemigrations && python manage.py migrate

Then, the initial fields of "AbstractUser" class are created in SQLite as shown below:

enter image description here

Next, set "age" and "gender" fields for the class "CustomUser(AbstractUser)" as shown below:

# "account/models.py"

from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    age = models.IntegerField()
    gender = models.CharField(max_length=100)

Then, run this command below:

python manage.py makemigrations && python manage.py migrate

Then, "age" and "gender" fields are created with the initial fields of "AbstractUser" class as shown below:

enter image description here

Next, change all the initial fields of "AbstractUser" class by setting "models.CharField(max_length=100)" to them but "id" field needs "primary_key=True" to have Primary Key otherwise there is an error and "username" field needs "unique=True" to have Unique Constraint otherwise there is a warning:

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

class CustomUser(AbstractUser):           # ↓ Here ↓
    id = models.CharField(max_length=100, primary_key=True)
    password = models.CharField(max_length=100)
    last_login = models.CharField(max_length=100)
    is_superuser = models.CharField(max_length=100) # ↓ Here
    username = models.CharField(max_length=100, unique=True)
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    email = models.CharField(max_length=100)
    is_staff = models.CharField(max_length=100)
    is_active = models.CharField(max_length=100)
    date_joined = models.CharField(max_length=100)

Then, run this command below:

python manage.py makemigrations && python manage.py migrate

Then, all the initial fields of "AbstractUser" class are changed as shown below:

enter image description here

Next, remove "password", "last_login", "is_superuser" and "username" fields by setting "None" to them as shown below. *Keep it in mind that "id" field can never ever be removed even if setting "None" to it and USERNAME_FIELD must have one existing field and by default, "username" field which has Unique Constraint is set to USERNAME_FIELD so if removing "username" field by setting "None" to it, you also need to remove "username" field from USERNAME_FIELD by setting one existing field as shown below otherwise there is an error so in this example below, there are 7 existing fields "id", "first_name", "last_name", "email", "is_staff", "is_active" and "date_joined" so change USERNAME_FIELD from "username" field to "last_name" field by settiing "last_name" field with "unique=True" to "USERNAME_FIELD" as shown below. *Keep it in mind that like "last_name" field, the existing field set to "USERNAME_FIELD" needs "unique=True" to have Unique Constraint as shown below otherwise there is a warning but when setting "id" field which has Primary Key to "USERNAME_FIELD", it doesn’t need "unique=True" to have Unique Constraint:

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

class CustomUser(AbstractUser):
    password = None
    last_login = None
    is_superuser = None
    username = None                              # Here
    last_name = models.CharField(max_length=150, unique=True)
    
    USERNAME_FIELD = 'last_name' # Here

Then, run this command below:

python manage.py makemigrations && python manage.py migrate

Then, as shown below, "password", "last_login", "is_superuser" and "username" fields are removed and "last_name" field has Unique Constraint:

enter image description here

Next again, remove "password", "last_login", "is_superuser" and "username" fields by setting "None" to them as shown below but this time, change "USERNAME_FIELD" from "username" field to "email" field by settiing "email" field with "unique=True" to "USERNAME_FIELD" as shown below. *Keep it in mind that by default, "email" field is also set to "REQUIRED_FIELDS" and it’s not allowed to set the same field to both "USERNAME_FIELD" and "REQUIRED_FIELDS" at the same time otherwise there is an error so to remove "email" field from "REQUIRED_FIELDS", set no fields to "REQUIRED_FIELDS" as shown below. *Keep it in mind that it’s ok that no fields are set to "REQUIRED_FIELDS" as shown below:

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

class CustomUser(AbstractUser):
    password = None
    last_login = None
    is_superuser = None
    username = None           # Here               
    email = models.EmailField(unique=True)

    USERNAME_FIELD = 'email' # Here
    REQUIRED_FIELDS = [] # Here

Then, run this command below:

python manage.py makemigrations && python manage.py migrate

Then, as shown below, "password", "last_login", "is_superuser" and "username" fields are removed and "email" field has Unique Constraint:

enter image description here

This code below is the part of "AbstractUser" class in Django on Github. You can see the defined fields, USERNAME_FIELD = "username", REQUIRED_FIELDS = ["email"] and "AbstractUser" class is actually the subclass of "AbstractBaseUser" class which I’m going to explain next:

# "django/contrib/auth/models.py"

class AbstractUser(AbstractBaseUser, PermissionsMixin):

    username_validator = UnicodeUsernameValidator()

    username = models.CharField(
        _("username"),
        max_length=150,
        unique=True,
        help_text=_(
            "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
        ),
        validators=[username_validator],
        error_messages={
            "unique": _("A user with that username already exists."),
        },
    )
    first_name = models.CharField(_("first name"), max_length=150, blank=True)
    last_name = models.CharField(_("last name"), max_length=150, blank=True)
    email = models.EmailField(_("email address"), blank=True)
    is_staff = models.BooleanField(
        _("staff status"),
        default=False,
        help_text=_("Designates whether the user can log into this admin site."),
    )
    is_active = models.BooleanField(
        _("active"),
        default=True,
        help_text=_(
            "Designates whether this user should be treated as active. "
            "Unselect this instead of deleting accounts."
        ),
    )
    date_joined = models.DateTimeField(_("date joined"), default=timezone.now)

    objects = UserManager()

    EMAIL_FIELD = "email"
    USERNAME_FIELD = "username"
    REQUIRED_FIELDS = ["email"]

<AbstractBaseUser>

"AbstractBaseUser" class initially has 3 fields as shown below and for the subclass of "AbstractBaseUser" class, you can add new fields, change and remove initial fields same as "AbstractUser" class.

These are the initial fields of "AbstractBaseUser" class as shown below:

id
password
last_login

Now, set "password" field with "unique=True" to "USERNAME_FIELD" in the class "CustomUser(AbstractBaseUser)" as shown below. *Keep it in mind that as you’ve already noticed, "AbstractBaseUser" class also has "USERNAME_FIELD" and by default, no field is set to "USERNAME_FIELD" so you need to set one existing field to it as shown below otherwise there is an error. In addition, no fields are set to "REQUIRED_FIELDS":

# "account/models.py"

from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class CustomUser(AbstractBaseUser):        # ↓ Here ↓ 
    password = models.CharField(max_length=128, unique=True)
    # ↓ Here ↓
    USERNAME_FIELD = 'password'

Then, run this command below:

python manage.py makemigrations && python manage.py migrate

Then, the initial fields of "AbstractBaseUser" class are created in SQLite as shown below:

enter image description here

Next, set "age" and "gender" fields for the class "CustomUser(AbstractBaseUser)" and set "age" field with "unique=True" to "USERNAME_FIELD" as shown below:

# "account/models.py"

from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class CustomUser(AbstractBaseUser):
    age = models.IntegerField(unique=True)
    gender = models.CharField(max_length=100)

    USERNAME_FIELD = 'age'

Then, run this command below:

python manage.py makemigrations && python manage.py migrate

Then, "age" and "gender" fields are created with the initial fields of "AbstractBaseUser" class and Unique Constraint is set to "age" field as shown below:

enter image description here

Next, change all the initial fields of "AbstractBaseUser" class by setting "models.CharField(max_length=100)" to them and set "password" field with "unique=True" to "USERNAME_FIELD" and same as "AbstractUser" class, "id" field needs "primary_key=True" to have Primary Key otherwise there is an error:

from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class CustomUser(AbstractBaseUser):       # ↓ Here ↓
    id = models.CharField(max_length=100, primary_key=True)
    password = models.CharField(max_length=100, unique=True)
    last_login = models.CharField(max_length=100)
    
    USERNAME_FIELD = 'password'

Then, run this command below:

python manage.py makemigrations && python manage.py migrate

Then, all the initial fields of "AbstractBaseUser" class are changed and Unique Constraint is set to "password" field as shown below:

enter image description here

Next, remove "password" and "last_login" fields by setting "None" to them and set only one existing field "id" to "USERNAME_FIELD" as shown below. *Keep it in mind that same as "AbstractUser", "id" field can never ever be removed even if setting "None" to it and when setting "id" field which has Primary Key to "USERNAME_FIELD", it doesn’t need "unique=True" to have Unique Constraint:

from django.contrib.auth.models import AbstractBaseUser

class CustomUser(AbstractBaseUser):
    password = None
    last_login = None
        
    USERNAME_FIELD = 'id'

Then, run this command below:

python manage.py makemigrations && python manage.py migrate

Then, as shown below, "password" and "last_login" fields are removed:

enter image description here

This code below is the part of "AbstractBaseUser" class in Django on Github. You can see the defined fields, "USERNAME_FIELD" is not defined and REQUIRED_FIELDS = []:

# "django/contrib/auth/base_user.py"

class AbstractBaseUser(models.Model):
    password = models.CharField(_("password"), max_length=128)
    last_login = models.DateTimeField(_("last login"), blank=True, null=True)

    is_active = True

    REQUIRED_FIELDS = []
Answered By: Kai – Kazuya Ito

"AbstractUser" class is the subclass of "AbstractBaseUser" class and "AbstractUser" class has 11 fields same as default "User" class as shown below:

id
password
last_login
is_superuser
username
first_name
last_name
email
is_staff
is_active
date_joined

"AbstractBaseUser" class is the superclass of "AbstractUser" class and "AbstractBaseUser" class has 3 fields as shown below:

id
password
last_login
Answered By: Kai – Kazuya Ito

Main difference basically lies on Usecase. Say for example you no longer need the existing User class provided by Django and you only care about authentication functionalities provided by the User class and your own custom fields. In that case, you should use AbstractBaseUser. In another case, you want to use existing User Fields and functionalities but on top of that you would like to ad some extra fields and methods. In that case, you should use AbstractUser.

Answered By: Rabeul Hasan