django tutorial - Part 3. User Profile

Categories:   web development  
Tags:   python   django  

This is part 3 of my “django study Notes”.

You can check the previous post here.

Credits:

I repost content I study, this helps me learn quicker.

This post covers parts 8 ~ in Corey Schafer’s django tutorial.

Side Note

Before we begin, make sure your project is up to date if using git:

$ git remote update
$ git status
$ git pull

Also make sure your python virtual environment is activated.

  • virtualenv:
$ source .venv/scripts/activate

Also, when you install new packages like we’ll do in this post, it’s a good idea to add it to your requirements.txt file so it’s easier to get started on other machines.

To save all installed packages:

$ pip freeze > requirements.txt

To install all packages based on the requirements.txt file:

$ pip install -r requirements.txt

User Profile and Picture

  • Create a new Profile model that extends the User model.
  • users/model.py file:
from django.db import models
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'
  • This will be a one to one relationship with the on_delete set to CASCADE.
  • The dunder method will also allow us to display more information about this model.
  • You need to install the pillow package:
$ pip install pillow
$ pip freeze > requirements.txt

make the migration and run it:

$ python manage.py makemigrations
$ python manage.py migrate
  • Register the new model in users/admin.py:
from django.contrib import admin
from .models import Profile

admin.site.register(Profile)
  • The Profile section should have been added to the Admin page.

  • run the python shell to check out the Profile model:

$ python manage.py shell
$ from django.contrib.auth.models import User
$ user = User.objects.filter(username='john').first()
$ user
$ user.profile
$ user.profile.image
$ user.profile.image.width
$ user.profile.image.height
$ user.profile.image.size
$ user.profile.image.url
$ exit
  • The image folder has been created in the root as profile_pics.
  • We will change the location of this image folder.
  • add and define MEDIA_ROOT and MEDIA_URL at the bottom of the main settings.py file:
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
  • BASE_DIR is The projects base directory.
  • MEDIA_ROOT = the full path to the directory.
  • MEDIA_URL = the public url of the directory.
  • We can add create some profile’s in the admin backend.

Update users/profile.html to add more content to the page:

{% 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>
        <!-- FORM HERE -->
    </div>
{% endblock content %}
  • We also need to edit the main urls.py file to serve the static images. This will need to be adjusted again for production use:
from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from users import views as user_views

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'), 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)
  • the uploaded profile image should now be visible in the profile page.
  • We also need to upload a ‘default.jpg’ image to the media folder.

a django signal

  • create a signals.py file inside the users app folder:
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()
  • users/apps.py:
from django.apps import AppConfig

class UsersConfig(AppConfig):
    name = 'users'

    def ready(self):
        import users.signals

User Profile Forms

Create model form in users/forms.py.

  • We’ll import Profile from the models.
  • We’ll create a UserUpdateForm.
  • We’ll also create a ProfileUpdateForm.

The file will look like:

from django import forms
from django .contrib.auth.models import User
from django .contrib.auth.forms import UserCreationForm
from .models import Profile

# extends UserCreationForm and add the email field
class UserRegisterForm(UserCreationForm):
    email = forms.EmailField()

    # configurations 
    class Meta:
        model = User
        fields = ['username', 'email', 'password1', 'password2']

class UserUpdateForm(forms.ModelForm):
    email = forms.EmailField()

    # configurations 
    class Meta:
        model = User
        fields = ['username', 'email']

class ProfileUpdateForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['image']

Update our Views to add these forms

  • First import the UserUpdateForm & ProfileUpdateForm.
  • We will add the forms in the profile view and pass it with the template.
  • The users/views.py file will look like:
from django.shortcuts import render, redirect
# from django.contrib.auth.forms import UserCreationForm
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import UserRegisterForm, UserUpdateForm, ProfileUpdateForm

def register(request):
    if request.method == 'POST':
        # form = UserCreationForm(request.POST) # POST => form with the users input
        form = UserRegisterForm(request.POST)
        if form.is_valid():
            form.save() # saves to database
            username = form.cleaned_data.get('username')
            messages.success(request, f'Your account has been created! You can now log in.')
            return redirect('login')
    else:
        # form = UserCreationForm()
        form = UserRegisterForm()
    return render(request, 'users/register.html', {'form': form})

@login_required
def profile(request):
    if request.method == 'POST':
        u_form = UserUpdateForm(request.POST, instance=request.user)
        p_form = ProfileUpdateForm(request.POST, request.FILES, instance=request.user.profile)
        if u_form.is_valid() and p_form.is_valid():
            u_form.save()
            p_form.save()
            messages.success(request, f'Your account has been updated!')
            return redirect('profile')
    else:
        u_form = UserUpdateForm(instance=request.user)
        p_form = ProfileUpdateForm(instance=request.user.profile)
    context = {
        'u_form': u_form,
        'p_form': p_form
    }

    return render(request, 'users/profile.html', context)

Update the profile template

  • We’ll update the users/profile.html template.
  • We’ll add a form template and load the u_form and p_form.
  • The users/profile.html will look like:
{% 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>
        <form method="POST" enctype="multipart/form-data">
            {% csrf_token %}
            <fieldset class="form-group">
                <legend class="border-bottom mb-4">Profile Info</legend>
                {{ u_form|crispy }}
                {{ p_form|crispy }}
            </fieldset>
            <div class="form-group">
                <button class="btn btn-outline-info" type="submit">Update</button>
            </div>
        </form>
    </div>
{% endblock content %}

Resize uploaded images

  • In users/models.py file:
  • import Image from the pillow package.
  • Set a size for the image.
  • Override the save method with the resize:
from django.db import models
from django.contrib.auth.models import User
from PIL import Image

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'

    def save(self):
        super().save()

        img = Image.open(self.image.path)

        if img.height > 300 or img.width > 300:
            output_size = (300, 300)
            img.thumbnail(output_size)
            img.save(self.image.path)

Display thumbnail of author for each post

  • In the blogs templates home.html we will add an :
{% extends "blog/base.html" %}

{% block content %}
    {% for post in posts %}
    <article class="media content-section">
        <img src="{{ post.author.profile.image.url }}" class="rounded-circle article-img">
        <div class="media-body">
            <div class="article-metadata">
                <a class="mr-2" href="#">{{ post.author }}</a>
                <small class="text-muted">{{ post.date_posted|date:"F d, Y" }}</small>
            </div>
            <h2><a class="article-title" href="#">{{ post.title }}</a></h2>
            <p class="article-content">{{ post.content }}</p>
        </div>
    </article>
    {% endfor %}
{% endblock content %}

I think we will end this section here.

Summary

In this article we covered:

  • We added a User’s profile.
  • We can edit a User’s profile.
  • We can upload an image for the user.
  • Uploaded images will be resized.

In the next post we’ll cover the CRUD functions.

Related Products



Categories:   web development  
Tags:   python   django