Skip to content

Users and Profiles#

Introduction#

Time to work on our users and profiles.

The Django docs sayit may be more suitable to store app-specific user information in a model that has a relation with your custom user model. That allows each app to specify its own user data requirements without potentially conflicting or breaking assumptions by other apps. It also means that you would keep your user model as simple as possible, focused on authentication, and following the minimum requirements Django expects custom user models to meet.”.

This is why we'll have the authentication logic in a User model and the profile logic in a Profile model.

User model#

Creating the User model#

The User model will contain everything related to authentication.

We need an email, a username, and a password. Let's add the following to the User model in users/models.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from django.contrib.auth.models import AbstractUser
from django.db import models


class User(AbstractUser):
    """User model"""

    username = models.CharField(max_length=255, unique=True)
    email = models.EmailField(unique=True)

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

    def __str__(self):
        self.email

The username field is the unique human-readable identifier that we can represent users with in our app. The email field holds the email users will be logging in with. We specify this in USERNAME_FIELD. The password field is already provided by AbstractUser. REQUIRED_FIELDS is the list of field users will be prompted for at sign up: because the USERNAME_FIELD and the password are already required by Django, we only need to specify username. More information about the fields can be found in the docs for the default Django User model.

Creating the UserManager#

We also need a UserManager, as advised by the docs. In models.py, we add the following (make sure to place the class definition BEFORE the class definition for the User model):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# ...
from django.contrib.auth.models import AbstractUser, UserManager

# ...
class CustomUserManager(UserManager):
    """custom UserManager with unique identifier is email instead of username"""

    def create_user(self, username, email, password=None):
        """Create and return a User with username, email, and password"""

        if email is None:
            raise ValueError("Email is required.")
        if username is None:
            raise ValueError("Username is required")

        email = self.normalize_email(email)
        user = self.model(username=username, email=email)
        user.set_password(password)
        user.save()

        return user

    def create_superuser(self, username, email, password=None):
        """Create and return a SuperUser with admin permissions."""

        user = self.create_user(username, email, password)
        user.is_staff = True
        user.is_superuser = True
        user.is_active = True
        user.save()

        return user

create_user and create_superuser are self-explanatory.

We now need to go back to the User model in users/models.py and indicate to Django that the UserManager defined above will manage objects of type User:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# ...
class User(AbstractUser):
    """User model"""

    username = models.CharField(max_length=255, unique=True)
    email = models.EmailField(unique=True)

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

    objects = CustomUserManager()               # new

    def __str__(self):
        return self.email

Make sure to makemigrations and migrate, so that Django is aware of your new model.

Registering our new model#

We need to register this new User model in users/admins.py, to have access to it in our admin app.

1
2
3
4
from django.contrib import admin
from .models import User

admin.site.register(User)

Profile model#

Creating the Profile model#

We are following the instructions in the Django docs about extending a User model. We need to store some information about our users in the database. Each User object should be related to a single Profile, and vice-versa: we'll use a OneToOneField relationship.

Our Profile needs the following fields:

  • image
  • bio
  • articles
  • comments

We have already taken care of the two last fields in the Article and Comment models through the ForeignKey relationships.

We will allow users to specify a URL to their avatar and to write a short bio. This is optional, so we make sure to have blank=True. Let's add the following to the Profile model in users/models.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Profile(models.Model):
    """Profile model"""

    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    image = models.URLField(
        default="https://static.productionready.io/images/smiley-cyrus.jpg"
    )
    bio = models.TextField(max_length=1000, blank=True)

    def __str__(self):
        return self.user.username

As always, whenever you change a model, you should makemigrations and migrate.

Automating the creation of profiles for each new user#

Since we're defining the Profile outside of the User model, a profile won't be created automatically whenever a user signs up.

What we want is to be notified when a new User instance is created (generally at sign-up), so that we can create a Profile model. To achieve this, we need to use signals.

Django already takes care of sending the signal for new User instances, so we only need to define a receiver function. We want to be notified after a new instance is saved, so we'll use the post_save signal.

Create a signals.py file in the users folder and add the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from .models import Profile


@receiver(post_save, sender=get_user_model())
def create_profile_for_user(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)


@receiver(post_save, sender=get_user_model())
def save_profile_for_user(sender, instance, **kwargs):
    instance.profile.save()

In order to activate this signal, we will modify users/apps.py:

1
2
3
4
5
6
7
8
9
from django.apps import AppConfig


class UsersConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "conduit.users"

    def ready(self):                        # new
        import conduit.users.signals        # new

This signal runs whenever a User is saved. By checking for created, we make sure to only initiate a Profile for the User instance if the User has just been created, instead of whenever the instance is updated.

Registering our new model#

We need to register this new Profile model in users/admins.py, to have access to it in our admin app, but we want to be able to view User and Profile information for a given user in the same place.

For this, we subclass a StackedInline class and define a custom ModelAdmin:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from django.contrib import admin
from .models import User, Profile


class ProfileInline(admin.StackedInline):
    model = Profile
    can_delete = False


class UserAdmin(admin.ModelAdmin):
    inlines = [ProfileInline]


admin.site.register(User, UserAdmin)

You'll notice that this code is much shorter than what the docs say: we're trying to keep it simple, so we'll do without some of the quality of life improvements that a more intricate code would allow.

When you go to Django admin, the User model fields will seem out of order: this is because the AbstractUser fields will be shown before the fields explicitly defined in the User model, but you can order them by adding a ModelAdmin.fields option.