Django Extending User model
Last updated on July 27, 2020
Django only provides bare minimum fields in the User
model to get you started, but it also gives you full power to extend the User
model to suit your application needs.
Recall that by default User
model contains the following fields:
username
first_name
last_name
email
password
last_login
is_active
is_staff
is_superuser
date_joined
The first step in extending a User
model is to create a new model with all the additional fields you want to store. To associate our new model with the User
model define a OneToOneField
containing a reference to the User
model in our new model.
Open blog's models.py
and modify Author
model as follows:
TGDB/django_project/blog/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #...
from django.urls import reverse
from django.contrib.auth.models import User
# Create your models here.
class Author(models.Model):
# required to associate Author model with User model (Important)
user = models.OneToOneField(User, null=True, blank=True)
# additional fields
activation_key = models.CharField(max_length=255, default=1)
email_validated = models.BooleanField(default=False)
def __str__(self):
return self.user.username
class Category(models.Model):
#...
|
Here we have defined two extra fields, namely activation_key
and email_validated
. Each of these fields does the following:
Field | Description |
---|---|
activation_key |
It stores a random unique key sent during email verification. The user must click on the link containing activation_key to activate the account. |
email_validated |
The email_validated field stores boolean data i.e 0 or 1 . If an account is validated it contains 1 . Otherwise, 0 . |
Before we run makemigration
command comment out the AuthorAdmin
class and also remove it from the admin.site.register()
function. At this point, blog's admin.py
should look like this:
TGDB/django_project/blog/admin.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | from django.contrib import admin
from . import models
# Register your models here.
# class AuthorAdmin(admin.ModelAdmin):
# list_display = ('name', 'email', 'created_on')
# search_fields = ['name', 'email']
# ordering = ['-name']
# list_filter = ['active']
# date_hierarchy = 'created_on'
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'pub_date', 'author', 'category',)
search_fields = ['title', 'content']
ordering = ['-pub_date']
list_filter = ['pub_date']
date_hierarchy = 'pub_date'
# filter_horizontal = ('tags',)
raw_id_fields = ('tags',)
# prepopulated_fields = {'slug': ('title',)}
readonly_fields = ('slug',)
fields = ('title', 'slug', 'content', 'author', 'category', 'tags',)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'slug',)
search_fields = ('name',)
class TagAdmin(admin.ModelAdmin):
list_display = ('name', 'slug',)
search_fields = ('name',)
class FeedbackAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'subject','date',)
search_fields = ('name', 'email',)
date_hierarchy = 'date'
admin.site.register(models.Post, PostAdmin)
admin.site.register(models.Category, CategoryAdmin)
# admin.site.register(models.Author, AuthorAdmin)
admin.site.register(models.Author)
admin.site.register(models.Tag, TagAdmin)
admin.site.register(models.Feedback, FeedbackAdmin)
|
In the terminal or command prompt, create new migration file by executing the following command:
(env) C:\Users\Q\TGDB\django_project>python manage.py makemigrations
Did you rename author.active to author.email_validated (a BooleanField)? [y/N] n
Migrations for 'blog':
blog\migrations\0010_auto_20170916_2041.py:
- Remove field active from author
- Remove field created_on from author
- Remove field email from author
- Remove field last_logged_in from author
- Remove field name from author
- Add field activation_key to author
- Add field email_validated to author
- Add field phone to author
- Add field user to author
(env) C:\Users\Q\TGDB\django_project>
When asked for Did you rename author.active to author.email_validated (a BooleanField)? [y/N]
, hit N
or n
.
Finally, commit the changes using python manage.py migrate
command.
(env) C:\Users\Q\TGDB\django_project>python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Rendering model states... DONE
Applying blog.0010_auto_20170916_2041... OK
(env) C:\Users\Q\TGDB\django_project>
Let's update our cadmin's register()
view to send email verification to the newly created accounts.
TGDB/django_project/cadmin/views.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | #...
from .forms import CustomUserCreationForm
from django_project import helpers
from django.core.mail import send_mail
from django.contrib.auth.models import User
from django.conf import settings
#...
def login(request, **kwargs):
#...
def register(request):
if request.method == 'POST':
f = CustomUserCreationForm(request.POST)
if f.is_valid():
# send email verification now
activation_key = helpers.generate_activation_key(username=request.POST['username'])
subject = "TheGreatDjangoBlog Account Verification"
message = '''\n
Please visit the following link to verify your account \n\n{0}://{1}/cadmin/activate/account/?key={2}
'''.format(request.scheme, request.get_host(), activation_key)
error = False
try:
send_mail(subject, message, settings.SERVER_EMAIL, [request.POST['email']])
messages.add_message(request, messages.INFO, 'Account created! Click on the link sent to your email to activate the account')
except:
error = True
messages.add_message(request, messages.INFO, 'Unable to send email verification. Please try again')
if not error:
u = User.objects.create_user(
request.POST['username'],
request.POST['email'],
request.POST['password1'],
is_active = 0
)
author = Author()
author.activation_key = activation_key
author.user = u
author.save()
return redirect('register')
else:
f = CustomUserCreationForm()
return render(request, 'cadmin/register.html', {'form': f})
|
Here is rundown of the changes we have made in register()
view:
In line 18, we are creating an
activation_key
based upon the username entered usinggenerate_activation_key()
helper function. Thegenerate_activation_key()
function is defined inhelpers.py
file as follows:TGDB/django_project/django_project/helpers.py
1 2 3 4 5 6 7 8 9 10 11
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger import hashlib from django.utils.crypto import get_random_string def pg_records(request, list, num): #... def generate_activation_key(username): chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' secret_key = get_random_string(20, chars) return hashlib.sha256((secret_key + username).encode('utf-8')).hexdigest()
In line 29, we are sending email verification using
send_email()
function. The syntax ofsend_mail()
function is:send_mail(subject, message, sender, recipient_list)
On success, it returns 1, otherwise 0.
If an error is encountered while sending email verification we set
error
toTrue
.If no error is encountered while sending an email then we proceed to create new
User
. Notice how we are saving the additional User data into the database.In lines 37-42, we are using
create_user()
method to save built-in fields ofUser
model. At this point, additional user data likeactivation_key
andemail_validated
is not yet saved into the database. In lines 45 and 46, we are setting values toactivation_key
anduser
field. And in line 47, we are saving additional User data by callingsave()
method on theAuthor
object.
Next, add another view called activate_account()
in cadmin's views.py
file, whose job is to set is_active
and email_validated
field to True
when the user clicks on the verification link in the email.
TGDB/django_project/cadmin/views.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | from django.shortcuts import redirect, get_object_or_404, reverse, Http404
#...
from django.conf import settings
#...
def register(request):
#...
def activate_account(request):
key = request.GET['key']
if not key:
raise Http404()
r = get_object_or_404(Author, activation_key=key, email_validated=False)
r.user.is_active = True
r.user.save()
r.email_validated = True
r.save()
return render(request, 'cadmin/activated.html')
|
Create a new template activated.html
for activate_account()
view and add the following code to it:
TGDB/django_project/cadmin/templates/cadmin/activated.html
1 2 3 4 5 6 7 8 9 10 11 12 13 | {% extends "cadmin/base.html" %}
{% block title %}
Create Account - {{ block.super }}
{% endblock %}
{% block content %}
<div class="logout">
<p>Account activated! Please <a href="{% url 'login' %}">login</a></p>
</div>
{% endblock %}
|
Finally, add a URL pattern named activate
just above the register
URL pattern in cadmin's urls.py
file as follows:
TGDB/django_project/cadmin/urls.py
1 2 3 4 5 6 7 | #...
urlpatterns = [
url(r'^activate/account/$', views.activate_account, name='activate'),
url(r'^register/$', views.register, name='register'),
#...
]
|
Our user registration form is now ready. Visit http://127.0.0.1:8000/cadmin/register/
and create a new user.
If registration succeeds, you will be greeted with a success message and email containing a verification link will be sent to the email id provided at the time of registration.
To verify your account click on the verification link and you will be greeted with "Account successfully verified. Please login." message.
Your account is now active and can be used to login. Visit http://127.0.0.1:8000/cadmin/login/
and login with your newly created account.
If user registration failed due to duplicate username/email or unmatching password the form will display validation errors like this:
In case, Django unable to send email verification then you will get an error like this:
Removing obsolete data #
If you now visit post add or update page you will notice an error like this:
The problem is that in the process of committing changes to the Author
model our last migration also has removed all the authors from the blog_authors
table. Only the authors which we created via User Registration form after updating Author
models are valid.
To fix the issue, designate all the posts to author(s) which is connected to the User
model. In my case, I am designating all post to an author whose username is charmander
. To do so, execute the following SQL.
1 2 | update blog_post
set author_id = 12
|
The exact author_id
may vary.
Finally, delete all the posts authors which are not connected to the User
model.
1 2 3 4 5 6 | PRAGMA foreign_keys = OFF;
delete from blog_author
where user_id is null;
PRAGMA foreign_keys = ON;
|
While we are at it, let's update author of category and tags too:
1 2 3 4 5 6 7 8 9 | -- code to update categories
update blog_category
set author_id = 12;
-- code to update tags
update blog_tag
set author_id = 12;
|
Visit post add or post update page and the error would have gone.
Note: To checkout this version of the repository type git checkout 33a
.
Load Comments