Written by Super User.

12- Django User Registration, Profile Editing, Password Reset

In this post, we will actually implement 3 different things including User Registration, User Profile Edit and User Password Reset. We need 3 different forms for these functionalities and I will start by creating these forms.

My forms will inherit from Django Forms named  UserCreationForm, UserChangeForm, PasswordChangeForm .

I actually created 2 forms while extending the user model,now I am going to modify them.

REGISTRATION:

Open forms.py and import UserCreationForm and UserChangeForm

The form I am creating inherits from UserCreationForm.

I created MyDateInput because I want to use a datepicker on my form for the birthdate field.

Also I want to use radio selection for Sex(Gender) selection, Therefore I created SexOptions.

from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import MyCustomUser

class MyDateInput(forms.DateInput):
    input_type ='date'


class MyCustomUserCreationForm(UserCreationForm):
    #ChoiceField Choices For User Sex
    M = 'Male'
    F = 'Female'
    SexOptions = ((M, u'Male'),(F, u'Female'))

    first_name = forms.CharField(label='', required=False, max_length=50, widget=forms.TextInput(attrs={ 'class':'form-control', 'placeholder': 'Enter Your First Name'}))
    last_name = forms.CharField(label='', required=False, max_length=50, widget=forms.TextInput(attrs={ 'class':'form-control', 'placeholder': 'Enter Your Last Name'}))
    dateofbirth = forms.DateField(required=False, widget=MyDateInput)
    sex = forms.ChoiceField(choices=SexOptions, required=False, widget=forms.RadioSelect())
    city = forms.CharField(label='', max_length=50, required=False, widget=forms.TextInput(attrs={ 'class':'form-control', 'placeholder': 'Enter Your City'}))
    country = forms.CharField(label='', max_length=50, required=False, widget=forms.TextInput(attrs={ 'class':'form-control', 'placeholder': 'Enter Your Country'}))

    class Meta:
        model = MyCustomUser
        fields = ['username', 'email', 'first_name', 'last_name', 'password1', 'password2', 'dateofbirth', 'sex', 'city', 'country']
    #here, we customize the form elements of the abstracted MyCustomUserCreationForm 
    def __init__(self, *args, **kwargs):
        super(MyCustomUserCreationForm, self).__init__(*args, **kwargs)
        self.fields['username'].widget.attrs['class'] = 'form-control'
        self.fields['username'].widget.attrs['placeholder'] = 'Enter a Username'
        self.fields['username'].label=''
        self.fields['username'].help_text='<div class="form-text text-muted"><small>Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</small></div>'

        self.fields['password1'].widget.attrs['class'] = 'form-control'
        self.fields['password1'].widget.attrs['placeholder'] = 'Enter a Password'
        self.fields['password1'].label=''
        self.fields['password1'].help_text='<ul class="form-text text-muted small"><li>Your password can\'t be too similar to your other personal information.</li><li>Your password must contain at least 8 characters.</li><li>our password can\'t be a commonly used password.</li><li>Your password can\'t be entirely numeric.</li></ul>'

        self.fields['password2'].widget.attrs['class'] = 'form-control'
        self.fields['password2'].widget.attrs['placeholder'] = 'Verify the Password'
        self.fields['password2'].label=''
        self.fields['password2'].help_text='<div class="form-text text-muted"><small>Enter the same password as before, for verification.</small></div>'
        
        self.fields['email'].widget.attrs['class'] = 'form-control'
        self.fields['email'].widget.attrs['placeholder'] = 'Enter Your Email'
        self.fields['email'].label=''
        self.fields['email'].help_text=''

 

 

Now I need to create a url for registration in moviesapp urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('', views.moviesapp, name='moviesapp'),
    path('loginuser', views.loginuser, name='loginuser'),
    path('logoutuser', views.logoutuser, name='logoutuser'),
    path('registeruser/', views.registeruser, name='registeruser'),
]

 

 

Then create a view in views.py for registration. Import MyCustomUserCreationForm from moviesapp.forms

from moviesapp.forms import MyCustomUserCreationForm

def registeruser(request):
    if request.method == 'POST':
        form = MyCustomUserCreationForm(request.POST)
        if form.is_valid():
            form.save()
            #form.cleaned_data returns a dictionary of validated form input fields and their values as objects
            user_name = form.cleaned_data['username']
            pass_word = form.cleaned_data['password1']
            userobj= authenticate(request, username= user_name, password=pass_word)
            login(request, userobj)
            messages.success(request, "Registered Successfully", extra_tags='green')
            return redirect('moviesapp')
    else:
        form = MyCustomUserCreationForm()
    context = {'form':form}
    return render(request, 'registeruser.html', context)

 

Now we need to create a template (registeruser.html) in crmapp's template folder

{% extends 'base.html' %}

{% block title%}
    <title>Registeration Page</title>
{% endblock title%}

{% block content %}
<div class="row justify-content-center">
    <div class="col-4">
        <div class="card">
            <div class="card-body">

            
            <h2 class="text-center">Register</h2>
            <div class="col-md-6 offset-md-3">
            <form method="POST" action="{% url 'registeruser' %}">
            {% csrf_token %}
            {% if form.errors %}
                        <div class="alert alert-danger alert-dismissible fade show" role="alert">
                            <strong>Your Form has errors</strong>
                            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
                        </div>
            </div>                  
            {% endif %}
            {{form.as_p}}
            <input type="submit" value="Register" class="btn btn-secondary mb-2">
            </form>
            </div> 
        </div> 
    </div> 
</div> 
{% endblock content %}

 

In our Navbar we have a button for registration already, we just need to give a link to registeruser.

base.html

<a class="btn btn-outline-light" href="/{% url 'registeruser' %}">Register</a> 

 

 

 

 

Restart Apache service. This is how it looks:

 

 create a few accounts and check if there are any problems. 

 

 

USER PROFILE EDIT:

Let's create our form first to edit user profiles.

Open forms.py and import  UserChangeForm. 

from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import MyCustomUser

class MyCustomUserChangeForm(UserChangeForm):
    email = forms.EmailField(max_length=100, widget=forms.TextInput(attrs={'class':'form-control'}))
    first_name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class':'form-control'}))
    last_name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class':'form-control'}))
    city = forms.CharField(max_length=50, required=False, widget=forms.TextInput(attrs={'class':'form-control'}))
    country = forms.CharField(max_length=50, required=False, widget=forms.TextInput(attrs={'class':'form-control'}))

    class Meta:
        model = MyCustomUser
        fields = ['username', 'first_name', 'last_name', 'email', 'city', 'country']
    def __init__(self, *args, **kwargs):
        super(MyCustomUserChangeForm, self).__init__(*args, **kwargs)
        self.fields['username'].widget.attrs['class'] = 'form-control'
        self.fields['username'].help_text='<div class="form-text text-muted"><small>Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</small></div>'

 

 Create a url in moviesapp urls.py.

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

 

In base.html. Give a link for editprofile on the NavBar

            {% if user.is_authenticated %}
            <ul class="navbar-nav" >
              <li class="nav-item dropdown">
                <a class="nav-link dropdown-toggle text-white" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                  {{user.username}}'s Profile
                </a>
                <ul class="dropdown-menu" style="background-color: #86aac4;">
                  <li><a class="dropdown-item text-white" href="#">My List</a></li>
                  <li><a class="dropdown-item text-white" href="/{% url 'editprofile' %}">My Account</a></li>
                  <li><hr class="dropdown-divider"></li>
                  <li><a class="dropdown-item text-white" href="/{% url 'logoutuser' %}">Log Out</a></li>
                </ul>
              </li>
              <li class="nav-item">
                <a class="nav-link disabled">Disabled</a>
              </li>
            </ul>

 

 

We need to create a view. Import MyCustomUserChangeForm from moviesapp and create a view named editprofile in views.py

from moviesapp.forms import MyCustomUserChangeForm, MyCustomUserCreationForm

def editprofile(request):
    if request.method == 'POST':
        form = MyCustomUserChangeForm(request.POST, instance=request.user)
        if form.is_valid():
            form.save()
            messages.success(request, "Your profile is updated", extra_tags='green')
            return redirect('editprofile')
    else:
#User data will be fetched when user access his/her profile
        form = MyCustomUserChangeForm(instance=request.user)
    context = {'form':form}
    return render(request, 'editprofile.html', context)

 

 

And Finally create a template (editprofile.html) for it.

{% extends 'base.html' %}

{% block title%}
    <title>Edit User Profile</title>
{% endblock title%}

{% block content %}
    <h2 class="text-center">Edit User Profile</h2>
    <div class="col-md-6 offset-md-3">
        <form method="POST" action="{% url 'editprofile' %}">
            {% csrf_token %}
            {% if form.errors %}
            <div class="alert alert-danger alert-dismissible fade show" role="alert">
                <strong>Your Form has errors</strong>
                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
            </div>
            {% endif %}
            {{form.as_p}}
            <input type="submit" value="Save" class="btn btn-secondary mb-2">
        </form>
    </div>
{% endblock content %}

 

Restart Apache service.

 

 

 

 

PASSWORD RESET:

As you can see above, the Edit Profile Form has a build-in link to reset password but the link it provides is not the url I defined. So, I will I hide this message and link and create my own link. To do that I will just modify MyCustomChangeForm and add password field as a hidden field.

class MyCustomUserChangeForm(UserChangeForm):
    password = forms.CharField(required=False, label="", widget=forms.TextInput(attrs={'type':'hidden'}))
    email = forms.EmailField(max_length=100, widget=forms.TextInput(attrs={'class':'form-control'}))
    first_name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class':'form-control'}))
    last_name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class':'form-control'}))
    city = forms.CharField(max_length=50, required=False, widget=forms.TextInput(attrs={'class':'form-control'}))
    country = forms.CharField(max_length=50, required=False, widget=forms.TextInput(attrs={'class':'form-control'}))

    class Meta:
        model = MyCustomUser
        fields = ['username', 'first_name', 'last_name', 'email', 'city', 'country']
    def __init__(self, *args, **kwargs):
        super(MyCustomUserChangeForm, self).__init__(*args, **kwargs)
        self.fields['username'].widget.attrs['class'] = 'form-control'
        self.fields['username'].help_text='<div class="form-text text-muted"><small>Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</small></div>'

 

Now I am adding my own link to editprofile.html

{% extends 'base.html' %}

{% block title%}
    <title>Edit User Profile</title>
{% endblock title%}

{% block content %}
    <h2 class="text-center">Edit User Profile</h2>
    <div class="col-md-6 offset-md-3">
        <form method="POST" action="{% url 'editprofile' %}">
            {% csrf_token %}
            {% if form.errors %}
            <div class="alert alert-danger alert-dismissible fade show" role="alert">
                <strong>Your Form has errors</strong>
                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
            </div>
            {% endif %}
            {{form.as_p}}
            <input type="submit" value="Save" class="btn btn-secondary mb-2">
        </form>
        <div>
            <a href="/{% url 'changepassword' %}">Change Password</a>
        </div>
    </div>

 

 

 

 

Open forms.py and import  PasswordChangeForm and create a new form from PasswordChangeForm 

from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, PasswordChangeForm
from .models import MyCustomUser

class MyCustomUserPasswordChangeForm(PasswordChangeForm):
    def __init__(self, *args, **kwargs):
        super(PasswordChangeForm, self).__init__(*args, **kwargs)
        self.fields['old_password'].widget.attrs['class'] = 'form-control'
        self.fields['new_password1'].widget.attrs['class'] = 'form-control'
        self.fields['new_password2'].widget.attrs['class'] = 'form-control'

 

In views.py, import MyCustomPasswordChangeForm

from moviesapp.forms import MyCustomUserChangeForm, MyCustomUserCreationForm, MyCustomUserPasswordChangeForm

Create a view in views.py

def changepassword(request):
    if request.method == 'POST':
        form = MyCustomUserPasswordChangeForm(data=request.POST, user=request.user)
        if form.is_valid():
            form.save()
            messages.success(request, "Your password is updated", extra_tags='green')
            return redirect('changepassword')
    else:
        form = MyCustomUserPasswordChangeForm(user=request.user)
    context = {'form':form}
    return render(request, 'changepassword.html', context)

 

Create a url in urls.py

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

 

 

Create a template and name it changepassword.html in moviesapp's template folder

{% extends 'base.html' %}

{% block title%}
    <title>Change Password</title>
{% endblock title%}

{% block content %}
    <h2 class="text-center">Change Password</h2>
    <div class="col-md-6 offset-md-3">
        <form method="POST" action="{% url 'changepassword' %}">
            {% csrf_token %}
            {% if form.errors %}
            <div class="alert alert-danger alert-dismissible fade show" role="alert">
                <strong>Your Form has errors</strong>
                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
            </div>
            {% endif %}
            {{form.as_p}}
            <input type="submit" value="Save" class="btn btn-secondary mb-2">
        </form>
    </div>
{% endblock content %}

  

Restart Apache. Our password change form looks like this. 

 

 

After changing the password, it logs off the user by default. You might want to keep this behavior or you may want to keep the user logged on. I want my users stay logged on after they change the password. To do that, I will import, update_session_auth_hash in views.py first.

from django.contrib.auth import authenticate, login, logout, update_session_auth_hash

 

Then modify the changepassword view like this

def changepassword(request):
    if request.method == 'POST':
        form = MyCustomUserPasswordChangeForm(data=request.POST, user=request.user)
        if form.is_valid():
            form.save()
            update_session_auth_hash(request, form.user)
            messages.success(request, "Your password is updated", extra_tags='green')
            return redirect('changepassword')
    else:
        form = MyCustomUserPasswordChangeForm(user=request.user)
    context = {'form':form}
    return render(request, 'changepassword.html', context)

 

 Restart Apache service

That's all.