Django - User Profile

In Today's part of the series, we are going to create profile for a user by including additional information such as profile picture, and bio.

First of all, lets create a profile view inside views.py

views.py

from django.shortcuts import render
from django.contrib.auth.decorators import login_required


@login_required
def profile(request):
    return render(request, 'users/profile.html')
  • We will modify this view later to let users update their profile.
  • The login_required decorator limits access to logged in users.
  • A user who isn't logged in can not access the profile page. If the user tries to do so, login_required() will redirect him/her to settings.LOGIN_URL(which we set up in the login/logout part of the series) by passing the current absolute path in the query string. Example: /login/?next=/profile/
  • As we can see from the example path, the function keeps track of which page the user is trying to access. Therefore, it will redirect the user to the profile page that they asked to in the first place after a successful authentication.

Open users app urls.py and add the route for profile view.

users/urls.py

from django.urls import path
from .views import profile

urlpatterns = [
    # Add this
    path('profile/', profile, name='users-profile'),
]

Create the template for the view inside the users app template directory.

profile.html

{% extends "users/base.html" %}
{% block title %}Profile Page{% endblock title %}
{% block content %}
    <div class="row my-3 p-3">
        <h1>This is the profile page for {{user.username}}</h1>
    </div>

{% endblock content %}
  • We will modify this template later to display profile of the user, but first there are a couple of things we need to do.

Extending User Model Using a One-To-One Link

It's time to model our profile so that it will have the user's profile picture and bio fields stored in the database.

When we want to store additional information about a user which is not related to authentication, we can create a new model which has a one-to-one link with the user.

In django we can create one-to-one relationship between models by using the OneToOneField model field.

  • In a one-to-one relationship, one record in a table is associated with one and only one record in another table using a foreign key. Example - a user model instance is associated with one and only one profile instance.

Alright let's create the profile model with 2 fields, avatar(profile picture) and bio. You can add more if you want.

models.py

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


# Extending User Model Using a One-To-One Link
class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)

    avatar = models.ImageField(default='default.jpg', upload_to='profile_images')
    bio = models.TextField()

    def __str__(self):
        return self.user.username
  • The first argument of OneToOneField specifies which model the current model will be related to, which in our case is the User model. The second argument on_delete=models.CASCADE means that if a user is deleted, delete his/her profile as well.
  • The first argument of ImageField, default='default.jpg' is the default image to use for a user if they don't upload one themselves. The second argument upload_to='profile_images' is the directory where images get uploaded to.
  • The bio is just a text field where some information about users is stored.
  • The dunder __str__ method converts an object into its string representation which makes it more descriptive and readable when an instance of the profile is printed out. So, whenever we print out the profile of a user, it will display his/her username.

Alright, there is a library we need in order for this to work. Ever worked with images in python before? if so, you probably know about pillow which is one of the most common image processing library that lets us do different kinds of image manipulations in python.

Django requires us to install this library whenever working with ImageField, so go to your terminal and type the following.

pip install pillow

For the changes to take effect inside our database, let's run the migrations.

python manage.py makemigrations

python manage.py migrate

  • One other important step is to register the profile model inside the users app admin.py.

admin.py

from django.contrib import admin
from .models import Profile

admin.site.register(Profile)
  • The above code imports the profile model, and then calls admin.site.register to register it.

You can now login to the admin panel and see the model we created.

User Uploaded Files/ Working With Images

When working with media files in django, we have to change some settings to store files locally and serve them upon need.

In particular we need to set MEDIA_URL and MEDIA_ROOT in the settings.

  • MEDIA_ROOT is full path to a directory where uploaded files are stored. Usually we store such files by creating a directory inside the project's base directory.
  • MEDIA_URL is the base URL to serve media files. This is what lets us access the media through our web browser.

settings.py

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

Now we should configure the project's urls.py to serve user-uploaded media files during development( when debug=True).

user_management/urls.py

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    # ... 
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

We can now add files in the media root folder and django will serve them from the media url.

  • As you can see from the image above, the profile picture that users upload will live inside /media/profile_images/your_image. Till we create the frontend where uses will upload pictures, you can go to the admin panel and create images for registered users to see if all this is working well.

  • The default profile picture that is given to users lives inside /media/ therefore, put any default image you want inside this directory with the name default.jpg.

Signals in Django

Notice that we have to go to the admin page to create a profile whenever a user is created, but we don't wanna do that every now and then. It would be great if we can create the profiles automatically when a new user is created. To do this we use signals.

But what are signals??

Signals are used to perform some action on modification/creation of a particular entry in database.

In a signal, we have the following basic concepts.

  • Sender: is usually a model that notifies the receiver when an event occurs.
  • Receiver: The receiver is usually a function that works on the data once it is notified of some action that has taken place for instance when a user instance is just about to be saved inside the database.
  • The connection between the senders and the receivers is done through “signal dispatchers”.

Use Case:- using signals we can create a profile instance right when a new user instance is created inside the database.

Django recommends us to put signals in the app's directory as a single module. Therefore, create a signals.py file inside the users app.

signals.py

from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver

from .models import Profile


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


@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
    instance.profile.save()
  • create_profile is the receiver function which is run every time a user is created.
  • User is the sender which is responsible for making the notification.
  • post_save is the signal that is sent at the end of the save method.
  • In general, what the above code does is after the User model's save() method has finished executing, it sends a signal(post_save) to the receiver function (create_profile) then this function will receive the signal to create and save a profile instance for that user.

Next step is to connect the receivers in the ready() method of the app's configuration by importing the signals module. Open apps.py where we can include any application configuration for the users app.

apps.py

from django.apps import AppConfig


class UserConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'users'

    # add this
    def ready(self):
        import users.signals  # noqa
  • What we did is override the ready() method of the users app config to perform initialization task which is registering signals.

Now that we have the above things under our belt, in the next part we will create a form where users will update their profile and display the information inside the template.

You can find the finished app in github.

Any comments and suggestions are welcome.

13