Skip to content

User Profile

sachin soman edited this page Jul 4, 2020 · 5 revisions

Now that we implemented User registration, login, and logout Its time we worked on user profile specifically maybe add a profile picture, leap card information, retrieve balance info, etc. So we will start with the default users model. The default users model doesn't have a field for the profile pictures. So we will need to extend it and create a new profile model which will have a one to one relation with user model. So we will make the profile model in our users app in models.py. So first extend the users model into our models.py by importing it using. from django.contrib.auth.models import User. Now make a 'Profile' class extending models and have a field called user which has a one to one relationship with User and delete option set to cascade so that if 'user' is deleted the profile is also deleted with it.

from django.db import models
# user model
from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User,on_delete=models.CASCADE)
    image = models.ImageField(default='default.jpg',upload_to='profile_pics')


    def __str__(self):
        return f'{self.user.username} Profile'

next we added a field called image with a default value and location where it will store. Now that we made the class lets Add in a str method to see the objects. Now we made modifications to bd so we need to do migrations. So in terminal go to our projects and type python manage.py makemigrations This will create an error asking us to install pillow a library for images in python. so do a conda install pillow. And do a make migration. This will create a migrations file in users migration folder. We haven't actually done any migrations. To do that run python manage.py migrate. Now that we made the changes to DB we want to view them in our admin page for that we need to register it so go to the users admin.py

from django.contrib import admin

# Register your models here.
from .models import Profile

admin.site.register(Profile)

After that, if we go to the admin page and check if have something called profiles then we see it on the page. Now go there and add a picture to one of the profile after that go to django shell using python manage.py shell. Import our user models using from django.contrib.auth.models import User after that make a query on one of the user for which you saved the profile picture example user = User.objects.filter(username='sachinsoman').first() to get a user object since it has a one to one relation we can do user.profile.image to get the image. Now if we go to projects root directory a new directory called 'profile_pics' is generated with all our profile pics. Having a directory like this in project root directory is a bad idea as it can clutter our directories because if we have another app with some media it will create an another folder in root directory instead we need all such folder in a single folder called 'media'. We can configure django to save and look for images somewhere else. Go to PROJECTS settings.py and add the following two stuff. MEDIA_ROOT and MEDIA_URL. MEDIA_ROOT is where django will store all data and MEDIA_URL is where we type in the browser to get the data.

set them as

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

So MEDIA_ROOT is pointing to a folder called media which is appended using join method so that path is independent of os and MEDIAL_URL will have '/media/' so in our link browser if we use /media in url we will access the images. Now go delete our old profiles with images in admin page and delete the folder in django app and create a new profile. We see that a folder called 'media' is made and in it we have the profile_pic folder.

Now lets update our profile template

{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
    <div class="content-section">
      <div class="media">
        <img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
        <div class="media-body">
          <h2 class="account-heading">{{ user.username }}</h2>
          <p class="text-secondary">{{ user.email }}</p>
        </div>
      </div>
    </div>
{% endblock content %}

Now interms of serving the files we need to refer to django docs for production and testing as both are different serving static files docs in the docs they tell us to copy the following codes

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

urlpatterns = [
    # ... the rest of your URLconf goes here ...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

and add it to our Projects urls.py

But we will modify it a bit in such a way that the code will be active only in debug mode so our file will look like this

"""django_project URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path,include
# register user app
from users import views as user_views
# login
from django.contrib.auth import views as auth_views
# media
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('register',user_views.register,name='register'),
    path('profile/',user_views.profile,name='profile'),
    path('login/', auth_views.LoginView.as_view(template_name='users/login.html',redirect_authenticated_user=True),name='login'),
    path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'),name='logout'),
    path('',include('blog.urls')),
]


if settings.DEBUG:

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

Now if we run server and look at the profile section we can see our profile. COOOLLLL.

Next we will add the default image. If we go to a profile without image and right click to view image in browser we get an error message saying that image not found in media/default.jpg so put a default image in that folder note it's not in profile_pic folder.

Django Signals

Right now we need to go to admin page to create a profile and add images. Let's make it in website to have a profile automatically. For that first make a file called signals.py in our user app. This is the recomended way in Django. We need to do a series of long imports

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

the first one is from django signals and it sends a signal when we save something in the database. The next import is our User model. The User model here is called send as when we save it. We get a signal. So when we create a signal we need something to receive it. For that, we need to import the receiver which is a dunder which will make a function have receiver properties and do some tasks. And finally, we import the Profile class which we made as we will use it here to save to db. So we are trying to automatically create a user profile when a new user is created.

With that objective we make a function called make_profile with following arguments: sender, instance, created, **kwargs

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

so if we get a signal called 'created' we make a db creation. Now to run this function every time a user is created we will use the receiver that we just imported in form of a decorator. The decorator takes two arguments.

It basically means the following when a User is saved send the signal post_save the signal will be passed to a receiver and the receiver function here will be set as create_profile it accepts the sender, instance and created. If created then execute the lines of code. Now that we created the profile instance we need to save it. To make the code more verbose we can have an another function to save it.

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

Or everything in one function like this

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

Finally, we need to import our signals in our user apps ready function.So go to users app and add a function called read()

from django.apps import AppConfig


class UsersaConfig(AppConfig):
    name = 'users'

    def ready(self):
        import users.signals

Why we do this? Because Django said so to avoid anomalies with import.