--- title: "Extending built-in Django User with a custom Model" date: 2023-05-01T19:28:40-03:00 tags: ["django", "user", "python"] slug: extending-django-user-with-custom-model --- The [user authentication](https://docs.djangoproject.com/en/4.2/topics/auth/#user-authentication-in-django) system provided by Django is extremely powerful and handles most of the authentication (Am I who I say I am?) and authorization (Am I authorized to do what I want to do?) needs of an web project. User accounts, groups and permissions, methods for handling passwords securely are part of this system. Generally, this is adequate for most projects; however, there are situations where it becomes necessary to modify the behavior of a **User** or alter how their data is stored in the database. It should be noted that modifying the authorization and/or authentication process of a **User** will not be covered in this post. Creating a user profile model is one solution to extend and modify the behavior of a **User**, as [discussed before]({{< ref "20230424-extending-django-user-profile-model" >}}), but that approach has some caveats that are solved with the use of a custom user model. # Custom User model We can tell Django which is the model that will replace the built-in `django.contrib.auth.models.User` model in the system. This is done by providing a value to `AUTH_USER_MODEL` in your project. {{}} # myproject/myproject/settings.py AUTH_USER_MODEL = "users.CustomUser" {{}} > Usually I create a `users` application to group all custom user related > code to keep everything in the same context It is highly recommended that you do this configuration at the beginning of the project, as after you have created your database tables, it will require you to fix your schema, moving data between tables and reapplying some migrations manually (see [#25313](https://code.djangoproject.com/ticket/25313) for an overview of the steps involved). Now we need to create our custom model. This new user model can handle different authentication and authorization schemes, can use different fields (e.g. email) to identify the user or any other requirement that is not satisfied using the default Django user model. ## Adding new fields to default User model If the default **[User](https://docs.djangoproject.com/en/4.2/ref/contrib/auth/#django.contrib.auth.models.User)** model has everything we need and we want to add some extra profile fields keeping authentication and authorization as it is, create a custom model inheriting from `AbstractUser` would be enough. {{}} # myproject/users/models.py from django.contrib.auth.models import AbstractUser class CustomUser(AbstractUser): date_of_birth = models.DateField(null=True) {{}} {{}} # myproject/myproject/settings.py AUTH_USER_MODEL = "users.CustomUser" {{}} As we will not create a new table to store the user related information, there is no need for additional database queries to retrieve related models. We also don't need to worry if a related model is created or not reducing the complexity of the system. ## AbstractBaseUser If we want to use a different field as the identifier (other than `email`), use a different authorization system and/or having more control on what fields we have in our user model, a custom model inheriting from `AbstractBaseUser` is the choice. `AbstractBaseUser` is the core implementation of a **User**. It has a `password` field and methods to store and validate it securely. A `last_login` datetime field and everything else is up to us to provide. If using the default authentication backend, our model must have a single unique field that will be the identifier of our user such as email address, username or any other unique attribute. You configure that attribute using `USERNAME_FIELD` that defines what is the field that will uniquely identify the user. `EMAIL_FIELD` `REQUIRED_FIELDS` `is_active` is an attribute on `AbstractBaseUser` defaulting to `True` As an example, we will define a custom user that will use the email address as the identifier, has the date of birth as one required field can store the phone number and has a flag field in the database to indicate if the user is active or not. {{}} # myproject/users/models.py from django.contrib.auth.models import AbstractBaseUser class CustomUser(AbstractBaseUser): USERNAME_FIELD = "email" EMAIL_FIELD = "email" REQUIRED_FIELDS = ["date_of_birth", ] date_of_birth = models.EmailField(unique=True) email = models.EmailField(unique=True) is_active = models.BooleanField(default=True) phone_number = models.CharField(max_length=16, blank=True) {{}} FROM DOCS Authentication backends provide an extensible system for when a username and password stored with the user model need to be authenticated against a different service than Django’s default. TODO: list all fields that are created with this Any other fields needs to be created. is_active is a property set to True, but you can define if you want USERNAME_FIELD is needed identifier unique = True EMAIL_FIELD to specifi what is the email field REQUIRED_FIELDS fields required when createsuperuser If we want to use `email` as the identifier of an user, we need to set `USERNAME_FIELD` property {{}} # myproject/users/models.py class CustomUser(AbstractBaseUser): USERNAME_FIELD = "email" email = models.EmailField() {{}} One drawback is you need to specify a Custom Manager implementing two methods: `create_user` and `create_superuser`. This is required so we willll be able to run `python manage.py createsuperuser` and `python manage.py create-user`. If some fields (TODO list what fields) from `ABstractUser` are there, you can use the built-in UserManager FROM DOCS username, email, is_staff, is_active, is_superuser, last_login, and date_joined fields the same as Django’s default user, you can install Django’s UserManager {{}} # myproject/users/models.py class CustomUser(AbstractBaseUser): USERNAME_FILED = "email" email = models.EmailField() objects = UserManager() {{}} If you don't want to use Django Admin (very unlikely), you're done, and you can create/manage your users and authentication backend will be able. However, if you want to use this custom user model with Django admin, there are some extra steps. We need some fields and methods: `is_staff` so you have access to django admin `is_active` inactive users can't access django admin to manage permissions to edit registered models `has_perm` `has_module_perms()` ***we don't need to use the built-in permission system (see PermissionMixin)*** And `UserCreationForm`< `UserChangeForm` and `PasswordResetForm` (if we don't have email field) needs to be customized so we can use that. Also we need a custom UserAdmin to use our custom forms. This is a lot of things, and sometimes difficult to remember. The docs are ok to describe the process, but IMO this should be much simpler :-P If by any chance you want to use django model permission, you can use PermissionMixin, so you have access to groups, etc (to be honest, I never saw anything using permission, but groups) Sometimes the username may not be the way you want to identify an user (an email or social number would be more suitable for your project) or we have different requirements for authorization and permission. In those cases creating a custom user model is a option. This has advatanges over the user of a user profile model and solve some of the caveats of that approach such as: - We won't need an extra database table to store your custom fields, so there is no performance problems to access them; - As the table that we store the User data required for Django authentication is the same, we don't need to bother if your profile fields will be available or not When creating a custom user model, we have two different options If you are going to this path, you can create your custom user inheriting from two different classes: If using Django Admin, we also need to register this new model. {{}} # myproject/users/admin.py from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .models import CustomUser admin.site.register(CustomUser, UserAdmin) {{}}