DEV Community

Cover image for Django - User Profile
Hana Belay
Hana Belay

Posted on

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')
Enter fullscreen mode Exit fullscreen mode
  • 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'),
]
Enter fullscreen mode Exit fullscreen mode

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 %}
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode
  • 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)
Enter fullscreen mode Exit fullscreen mode
  • 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/'
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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

Profile Picture Path

  • 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()

Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode
  • 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.

Latest comments (11)

Collapse
 
sultanovasadbek profile image
Asadbek.

It was a very cool tutorial. I had such a problem, I added new 21 fields to the Profile model. I can't save this data to the db. Where did I make a mistake? Please tell me.

models.py

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    last_name = models.CharField(
        _("Фамилия"),
        max_length=30,
        null=True,
        validators=[
            RegexValidator(
                regex=r"^[a-zA-Z]+$",
                message=_("Введите латинские буквы!"),
                code="invalid_last_name",
            )
        ],
    )
    first_name = models.CharField(
        _("Имя"),
        max_length=40,
        null=True,
        validators=[
            RegexValidator(
                regex=r"^[a-zA-Z]+$",
                message=_("Введите латинские буквы!"),
                code="invalid_first_name",
            )
        ],
    )
    father_name = models.CharField(
        _("Отчество"),
        max_length=50,
        null=True,
        validators=[
            RegexValidator(
                regex=r"^[a-zA-Z]+$",
                message=_("Введите латинские буквы!"),
                code="invalid_father_name",
            )
        ],
    )
    birthday = models.DateField(_("Дата рождения"), null=True,)
    sex = models.CharField(_("Пол"), max_length=3, choices=Sex.choices, default=Sex.M, null=True)
    passport_series = models.CharField(
        _("Серия паспорта"),
        null=True,
        max_length=2,
        validators=[
            RegexValidator(
                regex=r"^[A-Z]+$",
                message=_("Введите латинские заглавные буквы!"),
                code="invalid_passport_series",
            )
        ],
    )
    passport_number = models.CharField(_("Паспорт номер"), max_length=7, null=True,)
    issed_by = models.CharField(_("Кем выдан"), max_length=20, null=True,)
    date_of_issue = models.DateField(_("Дата выдачи"), null=True,)
    iden_number = models.CharField(
        _("Номер идентификатор"),
        null=True,
        max_length=15,
        validators=[
            RegexValidator(
                regex=r"^[A-Z0-9]+$",
                message=_("Введите латинские заглавные буквы и цифры!"),
                code="invalid_iden_number",
            )
        ],
    )
    place_of_birth = models.CharField(
        _("Место рождения"),
        null=True,
        max_length=70,
    )

    city_of_residence = models.CharField(
        _("Город проживания"), max_length=9, choices=CityResidence.choices, null=True,
    )
    residential_address = models.CharField(_("Адрес факт. проживания"), max_length=65, null=True)
    home_phone_number = models.CharField(
        _("Домашний телефон номер"),
        null=True,
        blank=True,
        max_length=10,
    )
    phone_number = models.CharField(
        _("Мобильный телефон номер"),
        null=True,
        blank=True,
        max_length=10,
    )
    family_status = models.CharField(
        _("Семейное положение"), null=True, max_length=40, choices=FamilyStatus.choices
    )

    citizenship = models.CharField(
        _("Гражданство"), max_length=15, null=True, choices=Citizenship.choices
    )
    disability = models.CharField(
        _("Инвалидность"), max_length=40, null=True, choices=Disability.choices
    )
    pensioner = models.BooleanField(_("Пенсионер"), default=False)
    monthly_income = models.DecimalField(
        _("Ежемесячный доход"),
        blank=True,
        null=True,
        max_digits=7,
        decimal_places=0,
    )
    liable_for_military_service = models.BooleanField(
        _("Военнообязанный"), default=True, null=True
    )

    def __str__(self):
        return self.user.username
Enter fullscreen mode Exit fullscreen mode

forms.py

class UpdateProfileForm(forms.ModelForm):
    # avatar = forms.ImageField(widget=forms.FileInput(attrs={'class': 'form-control-file'}))

    class Meta:
        model = Profile
        fields = '__all__'

        widgets = {
            "last_name": forms.TextInput(
                attrs={"placeholder": "Фамилия", "class": "myfield"},
            ),
            "first_name": forms.TextInput(
                attrs={"placeholder": "Имя", "class": "myfield"}
            ),
            "father_name": forms.TextInput(
                attrs={"placeholder": "Отчество", "class": "myfield"}
            ),
            "birthday": forms.DateInput(
                attrs={
                    "placeholder": "Дата рождение",
                    "type": "date",
                    "class": "myfield",
                    "onfocus": "(this.type='date')",
                    "onblur": "(this.type='text')",
                }
            ),
            "sex": forms.RadioSelect(
                attrs={"label": "Пол", "type": "radio", "class": "RadioButton"}
            ),
            "passport_series": forms.TextInput(
                attrs={"placeholder": "Серия паспорта", "class": "myfield"}
            ),
            "passport_number": forms.NumberInput(
                attrs={"placeholder": "Паспорт номер", "class": "myfield"}
            ),
            "issed_by": forms.TextInput(
                attrs={"placeholder": "Кем выдан", "class": "myfield"}
            ),
            "date_of_issue": forms.DateInput(
                attrs={
                    "placeholder": "Дата выдачи",
                    "type": "date",
                    "class": "myfield",
                    "onfocus": "(this.type='date')",
                    "onblur": "(this.type='text')",
                }
            ),
            "iden_number": forms.TextInput(
                attrs={"placeholder": "Номер идентификатор", "class": "myfield"}
            ),
            "place_of_birth": forms.TextInput(
                attrs={"placeholder": "Место рождение", "class": "myfield"}
            ),
            "city_of_residence": forms.Select(
                attrs={"placeholder": "Город факт. проживания", "class": "myfield"}
            ),
            "residential_address": forms.TextInput(
                attrs={"placeholder": "Адрес факт. проживания", "class": "myfield"}
            ),
            "home_phone_number": forms.TextInput(
                attrs={"placeholder": "Домашний телефон номер", "class": "myfield"}
            ),
            "phone_number": forms.TextInput(
                attrs={"placeholder": "Мобильный телефон номер", "class": "myfield"}
            ),
            "family_status": forms.Select(
                attrs={"placeholder": "Семейное положение", "class": "myfield"}
            ),
            "citizenship": forms.Select(
                attrs={"placeholder": "Гражданство", "class": "myfield"}
            ),
            "disability": forms.Select(
                attrs={"placeholder": "Инвалидность", "class": "myfield"}
            ),
            "pensioner": forms.CheckboxInput(
                attrs={"placeholder": "Пенсионер", "class": ""}
            ),
            "monthly_income": forms.NumberInput(
                attrs={"placeholder": "Ежемесячный доход", "class": "myfield"}
            ),
            "liable_for_military_service": forms.CheckboxInput(
                attrs={"placeholder": "Военнообязанный", "class": ""}
            ),
        }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
earthcomfy profile image
Hana Belay

Thanks, can you tell me what error you are getting?

Collapse
 
sultanovasadbek profile image
Asadbek. • Edited

The data received from form 21 is not being entered into the database. With one word, the profile does not expand.

Thread Thread
 
earthcomfy profile image
Hana Belay

Line numbers are not shown in the code. Can you be more descriptive please. And also send me a screenshot of the error. Thanks.

Thread Thread
 
isalameh95 profile image
Israa Salameh

I have the same issue, no errors in the application at all, but I have check the user_profile table in the database and I have notice that the added fields have null values, even I entered them in register.
I think you should do profile.save() in the register view.

Collapse
 
davidroberthoare profile image
davidroberthoare

thank you!!!

Collapse
 
earthcomfy profile image
Hana Belay

My pleasure.

Collapse
 
hootjhiang profile image
HT Han

This is a really great tutorial.
I jumped over to your Password Reset Tutorial (dev.to/earthcomfy/django-reset-pas...) and ran into a series of issues, but eventually figured it out using SendGrid.
Thanks for creating these tutorials.

Collapse
 
earthcomfy profile image
Hana Belay

Hi, thanks for the kind feedback.

The issues might have been caused if you used the less secure apps setting to allow your app to send email. That setting is no longer available. If you want to send emails from your Google account, you should rather enable 2FA for your Google account and create app password.

Collapse
 
ilkhan1901 profile image
ilkhan1901

Nice tutorial, like it :)

Collapse
 
earthcomfy profile image
Hana Belay

Thanks, glad you enjoyed it.