Arsalan Shahid

Extending Django Default User Model to add User Profiles and More – DjangoBlog Part 5

My dear reader, how are you? السلام عليكم

I do not think that there is any other quality so essential to success of any kind as the quality of perseverance. It overcomes almost everything, even nature – John D. Rockefeller

This is the fifth part of DjangoBlog tutorial series. This post extends our application with user profiles. We will use Django messaging framework to show notifications such as successful profile creation. We will also create a simple view to add a new post once the user logged-in.


Few useful links to practically follow the project:

  1. DjangoBlog GitHub repository — DirectMe
  2. All other tutorials on Django-Blog — DirectMe
  3. Set your GitHub repo to DjangoBlog Part 4 using the following command:
git fetch origin daed963f95ee994bff7f31f582cc8bc89c0730c0

Extending the Default User Model by Adding Profiles

Django default user model contains fields to be used for many simple situations. But, if you want to extend it and add more fields then a simple solution can be to adding user models as a one-to-one relationship in a new model say Profile and add more fields into it. I show it as below:

# open accounts/models.py and add the following

from django.db import models
from django.conf import settings

class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL,
            on_delete=models.CASCADE)
    date_of_birth = models.DateField(blank=True, null=True)
    photo = models.ImageField(upload_to='users/%Y/%m/%d/',
            blank=True)

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

Since, we now have a new field where we take inputs photos from users to upload on their profiles, we will be using a Django tool called Pillow to help us in saving correct image files and reject any other uploaded files which are not images.

(blogenv)Django-Blog-Application$ pip3 install pillow

# let us now apply migrations 

(blogenv)Django-Blog-Application$ python3 manage.py makemigrations 
(blogenv)Django-Blog-Application$ python3 manage.py migrate

We will now add a path to MEDIA files in our settings.py as shown below

# adding path for external media files in config/settings.py

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

Adding a route to MEDIA files in config/urls.py as shown below

# open config/urls.py and add the following 

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

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

Let us now add the profile model on Django admin by registering it using admin.py

# open accounts/admin.py and add the following

from django.contrib import admin
from .models import Profile

@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
    list_display = ['user', 'date_of_birth', 'photo']

We will allow the users on the web to edit their profiles. For that, we may need to add forms.py as shown below

# open account/forms.py and add the following

from .models import Profile

class UserEditForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ('first_name', 'last_name', 'email')

class ProfileEditForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ('date_of_birth', 'photo')

We now need to add views for User Profiles as shown below

# open account/views.py and add the following 

from .models import Profile

# In order to create a User profile on registration. We will add a line in register view as shown below under new_user.save()

Profile.objects.create(user=new_user)

# let us now add a profile edit view as shown below 

from .forms import LoginForm, UserRegistrationForm, UserEditForm, ProfileEditForm

@login_required
def edit(request):
    if request.method == 'POST':
        user_form = UserEditForm(instance=request.user,
                data=request.POST)
        profile_form = ProfileEditForm(
                instance=request.user.profile,
                data=request.POST,
                files=request.FILES)
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
        else:
            user_form = UserEditForm(instance=request.user)
            profile_form = ProfileEditForm(
                    instance=request.user.profile)
            return render(request,
                    'account/edit.html',
                    {'user_form': user_form,
                    'profile_form': profile_form})

Add a route path to edit view in account/urls.py

path('edit/', views.edit, name='edit'),

Finally add a template for a profile edit view as shown below

# Create account/templates/account/edit.html and add the following program into it

{% extends "base.html" %}
{% block title %}Edit your account{% endblock %}
{% block content %}
<h1>Edit your account</h1>
<p>You can edit your account using the following form:</p>
<form action="." method="post" enctype="multipart/form-data">
        {{ user_form.as_p }}
        {{ profile_form.as_p }}
        {% csrf_token %}
        <p><input type="submit" value="Save changes"></p>
</form>
{% endblock %}

Using Django messaging framework in profile edit view

Let us now use Django Messaging framework to show messages on profile creation and error in update as shown below

# open account/view.py and add the following

from django.contrib import messages

@login_required
def edit(request):
    if request.method == 'POST':
        ...
        ...
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, 'Profile updated successfully')
        else:
            messages.error(request, 'Error updating your profile')
         ...
         ...

We will add the messging framework template in our base template as shown below

# Open Django-Blog/templates/base.html and add the following program

        {% if messages %}
        <ul class="messages">
                {% for message in messages %}
                <li class="{{ message.tags }}">
                        {{ message|safe }}
                        <a href="#" class="close">x</a>
                </li>
                {% endfor %}
        </ul>
        {% endif %}

Add a Blog Post Using Django Class-based CreateView

Django supports function and class-based views. it contains a variety of generic class-based editing views for application models. We will now use one of them to allow a user to add a blog post from the web as shown below.

First of all create a form to let users add a post as shown below:

# Open blog/forms.py and add the following program 

from django.contrib.auth import get_user_model

class PostForm(forms.ModelForm):
    author = forms.ModelChoiceField(
            widget=forms.HiddenInput,
            queryset=get_user_model().
            objects.all(),
            disabled=True,
            )

    class Meta:
        model = Post
            fields = ('title', 'slug', 'author', 'body', 'status', 'tags')
                widgets = {
                        'status': forms.RadioSelect(choices=Post.STATUS_CHOICES),
                            }

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('author')
        super(PostForm, self).__init__(*args, **kwargs)

Let us now add a view using Django generic class-based CreateView

# Open blog/views.py and add the following program

from .forms import PostForm
from django.views.generic import CreateView
from django.shortcuts import redirect
from django.urls import reverse

class PostCreate(CreateView):
    template_name = 'blog/post_form.html'
    form_class = PostForm
    success_url = 'blog:post_list'

    def get_initial(self):
        initial = super().get_initial()
        initial['author'] = self.request.user.id
        return initial

    def form_valid(self, form):
        self.object = form.save()
        self.object.save()
        post_list_url = reverse('blog:post_list')
        return redirect(
                to=post_list_url)

    def get_form_kwargs(self, *args, **kwargs):
        kwargs = super(PostCreate, self).get_form_kwargs(*args, **kwargs)
        kwargs['author'] = self.request.user
        return kwargs

Add a route to create view

# open blog/urls.py and add 

path('create/', views.PostCreate.as_view(), name='post_create'),

Finally, we will add a template to create blog as shown below

# create blog/templates/blog/post_form.html and add the following program 

{% extends "base.html" %}

{% block title %}Create Post{% endblock %}

{% block content %}
<form action="." method="post">
        {{ form.as_p }}
        {% csrf_token %}
        <p><input type="submit" value="Add post"></p>
</form>

{% endblock %}

If you run the development server at this point in time you should be able to see the added functionalities at http://127.0.0.1:8000/blog/create


I hope you find this tutorial useful. If you find any errors or feel any need for improvement, let me know in your comments below.

Signing off for today. Stay tuned and I will see you next week! Happy learning.

Exit mobile version