Creating a custom user model in Django 1.6 - Part 3

Published byGoogle on

Hello everyone and welcome back to the third part of this tutorial series. If you haven't done so yet, make sure to checkout part 1 and 2 first.
Last week we saw how to create and use admin forms with a custom user model. This week we will go through the process of creating a frontend login form. During this process we will also cover (part of) template inheritancecsrfthe messages framework, accessing the user from the template and Crispy forms.

I know last week I announced that this week's post would also cover the admin login form but finally the admin login form will be covered next week.

A frontend login form is a regular Django form with 2 fields. As we said in the first part of this tutorial, it would make things easier to allow people to login using either their username either their e-mail address.
So our 2 fields will be username or email and password.

If you are not familiar with Django forms, you might be tempted to validate that the username or e-mail entered matches a user with the given password inside your view. While this will work, Django provides an easier (as well as recommended) way to do that: form validation.
By overriding the clean() method of your form, you can do extra validation directly inside the form itself.

Also, to display forms, check their validity and redirect the user to a specific page on success, Django provides a handy generic class-based view: django.views.generic.edit.FormView. This is what we are going to use to build our login form.
Let's get started with the form itself and add the following class to your myauth/forms.py.

## Add this import at the top of th file
from django.db.models import Q

##Here is the login form
class LoginForm(forms.Form):                           
        
  login = forms.CharField(label = 'Username or e-mail', required=True)                                         
  password  = forms.CharField(label = 'Password', widget = forms.PasswordInput, required = True)
      
  def clean(self):                                     
    login = self.cleaned_data.get('login', '')         
    password = self.cleaned_data.get('password', '')
    self.user = None
    users = User.objects.filter(Q(username=login)|Q(email=login))
    for user in users:
      if user.is_active and user.check_password(password):
        self.user = user
    if self.user is None:
      raise forms.ValidationError('Invalid username or password')
    return self.cleaned_data

You might have noticed the use of Q which allows us to build more complex filters on models (in this case a OR).
Some of you may also wonder why we are using filter and looping through hypotethical users matching the query instead of just using get(). This is just to cover the (very unlikely) case where a user would have as username the email address of another one.

Let's now get started on the view, we will be using Django's FormView. FormViews are pretty easy to use, all they need to work is a template_name, a form_class and a success_url.
Unfortunately, since we will be importing our view into urls.py, we are unable to initialize its success_url property using a reverse(). The way to work around that (unless you have a really good reason to hardcode a url into that property) is to use the get_success_url() method instead of the success_url property.

By default, Django's FormView will redirect you to the success url directly after submitting a valid form. In our case, before being redirected, we also need to log the user in. This is done thanks to the form_success() method.

Let's dive in, here is what your myauth/views.py should look like:

from django.core.urlresolvers import reverse           
from django.views.generic.edit import FormView         
from django.contrib.auth import login
      
from myauth.forms import LoginForm
  
class LoginView(FormView):
    
  template_name = 'myauth/login.html'
  form_class = LoginForm
  
  def get_success_url(self):                           
   return reverse('homepage')
  
  def form_valid(self, form):
    form.user.backend = 'django.contrib.auth.backends.ModelBackend'
    login(self.request, form.user)
    return super(LoginView, self).form_valid(form)

Note that we set the backend property of the user before calling the login() method, this is because Django requires the authentication to be provided by a valid authentication backend. The only backend active by default is django.contrib.auth.backends.ModelBackend. There are others available, we will talk about that some more in a few weeks when we cover Social auth.

Now, we did provide a template_name for our view but that template doesn't exist yet. We currently only have one template in our application, the one used for our status list. Now this existing template does have some items which should be present on all pages (the site title, its menu as well as the left column) and since Django provides template inheritance we can move everything which is common into a base template which we will extend and only have the parts which change in our specific templates (babbler/status_list.html and myauth/login.html).
This base template should be re-used project-wide, so it would be wise to place it in a subdirectory of the project's mybaseproject directory (the one containing urls.py and settings.py) but by default Django looks for templates only inside the directories of the applications listed inside INSTALLED_APPS. So in order for Django to be able to find our new base template we will have to specify an extra setting: TEMPLATE_DIRS.
Add these lines to your settings.py file:

TEMPLATE_DIRS = (
    os.path.join(BASE_DIR,'mybaseproject/templates'),
)

when extending an existing template you need to:

  1. specify the name of the template you are extending
  2. put your content inside named blocks which will override the content of the corresponding blocks from your base template

So let's get started and move everything which is common from babbler/templates/babbler/status_list.html to our new mybaseproject/templates/base.html

### mybaseproject/templates/base.html

{% load staticfiles %}
<html>
  <head>
    <link href="{% url 'themed-bootstrap' 'my-theme' %}" rel="stylesheet" type="text/css">
  </head>
  <body>
    <div class="container">
      <header class="navbar navbar-top" role="banner">
        <a href="/" class="navbar-brand">BaBbLeR</a>
        <div class="clearfix"></div>
        <ul class="nav nav-justified">
          <li class="active">Home</li>
          <li><a href="#">Menu item 1</a></li>
          <li><a href="#">Menu item 2</a></li>
          <li><a href="#">Menu item 3</a></li>
          <li><a href="#">Menu item 4</a></li>
        </ul>
      </header>
      <div class="clearfix"></div>
      <div class="row">
        <div class="col-md-3">
          <div class="nav-right">
            <img src="{% static 'images/me.png'%}" class="pull-left profile-pic"/>
            <p>Hello,<br/>here are a few slices of my life.</p>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non elit nec tortor aliquet condimentum. Suspendisse euismod nisl orci, sit amet posuere justo tristique ut. Sed porttitor, nisi a imperdiet commodo, neque ante bibendum mi, nec suscipit dolor est a lectus. Donec accumsan sodales rhoncus. Duis congue vehicula metus, nec tempus tellus gravida sed. Praesent feugiat volutpat mauris quis scelerisque.</p>
          </div>
        </div>
        <div class="col-md-9">
          {% block content %}{% endblock %}
        </div>
      </div>
      <div class="clearfix"></div>
      <footer>
        <p class="pull-right">Usual footer blah</p>
      </footer>
    </div>
  </body>
</html>


### babbler/templates/babbler/status_list.html

{% extends 'base.html'%}
{% block content %}
  <h1>My moods:</h1>
  {% for object in object_list %}
    <div class="status">
      <span>{{ object.status }}</span>
    </div>
  {% endfor %}
{% endblock %}

As you can see, our specific template which extends base.html and overrides the content block is now quite small.
Let's now create our new template myauth/templates/myauth/login.html, it should look like this:

{% extends 'base.html'%}
{% block content %}
  <h1>Login</h1>
  <form action="{% url 'login'%}" method="POST">
    {% csrf_token %}
    {{form.as_p}}
    <input type="submit">
  </form>
{% endblock %}

In this template, you'll notice somthing new: a csrf_token. Csrf stands for cross-site request forgery. Basically what this token does is that is prohibits other sites to sumit forms on your own site, it is a security feature and, unless explicitly disabled, every django form requires it.

The last thing we need to do in order to let us log in through our frontend is to add the new url to our urls.py file

## Add this line at the top of the file with the other imports
from myauth.views import LoginView


urlpatterns = pattern('',
  .
  .
  .
  url(r'^login\.html$', LoginView.as_view(), name='login'),
)

Now go to your local server and log in! http://localhost:8000/login.html

It doesn't look great (yet) but it works. Now if you wanted to toy a bit around and see the different outcomes of the form, chances are you logged-in successfully and cannot log out anymore. That's our first problem (if you really want to log out, use the admin).
The second one is that a successfull login will redirect you to the homepage but nothing will tell you whether you are actually logged-in or not.

To fix those 2 problems we are going to add a logout view, add a conditional template item that will show a "Log out" link to logged-in users and a "Log in" link to anonymous users.
We are also going to load and enable the messages framework in order to inform the user on successfull logins/logouts.

For our logout view,we will once again use a generic class-based view: django.views.generic.base.RedirectView.
A bit like FormView, RedirectView takes a url parameter which we once again cannot initialize so we will need to override the get_redirect_url() method.
By default, RedirectView's are permanent redirects which is not what we want since we want to log the user out before redirecting them. Permanent redirects are usually stored by modern browsers on the first hit and don't get queried anymore afterwards. Luckily Django's RedirectView offers a permanent attribute.
Add this code to your myauth/views.py:

## Add this import at the top of the file
from django.views.generic.base import RedirectView
## And update this one
from django.contrib.auth import login, logout

## Finally, add this new class at the bottom of the file
class LogoutView(RedirectView):
                                                       
  permanent = False 
                                                       
  def get_redirect_url(self):
    return reverse('login')

  def dispatch(self, request, *args, **kwargs):
    try:
      logout(request)
    except:
      ## if we can't log the user out, it probably means they we're not logged-in to begin with, so we do nothing
      pass
    return super(LogoutView, self).dispatch(request, *args, **kwargs)

And update your urls.py:

## Update this import
from myauth.views import LoginView, LogoutView

## And add this pattern after the login url line
    url(r'^logout\.html$', LogoutView.as_view(), name='logout'),

Some of you are probably wondering why I use a class-based view to redirect to another url instead of simply using the redirect() shortcut. Well, watch this video and you'll know.

Now users have urls to log in and out of the site but they still don't know which state they are in and there is also no link to point them to those urls.
Let's fix that by adding a conditional section to our base template (mybaseproject/templates/base.html)

      <header class="navbar navbar-top" role="banner">
        <a href="/" class="navbar-brand">BaBbLeR</a>
        <!-- start of new code -->
        {% if user.is_authenticated %}
          <div class="navbar-right">Welcome {{user.get_short_name}} - <a href="{% url 'logout' %}">Log out</a></div>
        {% else %}
          <a href="{% url 'login' %}" class="navbar-right">Log in</a>
        {% endif %}
        <!-- end of new code -->
        <div class="clearfix"></div>
        <ul class="nav nav-justified">
          <li class="active">Home</li>
          <li><a href="#">Menu item 1</a></li>

This is better but a nice informative message on the page to tell the user they successfully logged in or out would be nice. For this, Django provides the messages framework which makes things quite easy.

To use it, you simply have to import it (usually in your view) and either use the add_message() method or one of its shortcuts (info, debug, success, warning, error). In our case, we will use the info() method.
Messages are stored inside the session until they are read (from inside a template).
Let's add those messages to our Login/LogoutView:

## Add this line to the imports
from django.contrib import messages

class LoginView(FormView):
  .
  .
  .
  def form_valid(self, form):
    form.user.backend = 'django.contrib.auth.backends.ModelBackend'
    login(self.request, form.user)
    ## This line is new
    messages.info(self.request, 'Login successfull')
    return super(LoginView, self).form_valid(form)


class LogoutView(RedirectView):
  .
  .
  .
  def dispatch(self, request, *args, **kwargs):
    try:
      logout(request)
    except:
      pass
    ## This line is also new
    messages.info(request, 'You have successfully logged out. Come back soon.')
    return super(LogoutView, self).dispatch(request, *args, **kwargs)

And lets check for those messages inside our base template:

        </div>
        <div class="col-md-9">
          <!-- start of new code -->
          {% if messages %}
            <div class="alert alert-info">
              {% for message in messages %}
                <p>{{message}}</p>
              {% endfor %}
            </div>
          {% endif %}
          <!-- end of new code -->
          {% block content %}{% endblock %}
        </div>

This is much better Smile
Now the last thing we will cover today is Crispy forms. Since the start of this tutorial series, we have been using Bootstrap and Bootstrap comes with pre-configured form formatting provided you use the right classes.
Now you could format all your templates which include forms by hand or you could even your own form formatter but there is a library which does it very well, it's Crispy forms.
Crispy forms can also create the <form> tags for you as well as help you add a submit button.
So let's install it.

$ pip install django-crispy-forms
Downloading/unpacking django-crispy-forms
  Downloading django-crispy-forms-1.4.0.tar.gz (47kB): 47kB downloaded
  Running setup.py egg_info for package django-crispy-forms
    
    warning: no files found matching '*' under directory 'crispy_forms/static'
Installing collected packages: django-crispy-forms
  Running setup.py install for django-crispy-forms
    
    warning: no files found matching '*' under directory 'crispy_forms/static'
Successfully installed django-crispy-forms
Cleaning up...
$ pip freeze > requirements.txt

And configure it inside settings.py:

.
.
.
INSTALLED_APPS = (     
## Default configuration
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
## Additional apps 
    'south',    
    'crispy_forms',
## Home made apps
    'babbler',
    'theme', 
    'myauth',                            
)
.
.
.
# CRISPY SETTINGS

CRISPY_TEMPLATE_PACK = 'bootstrap3'

Crispy forms comes with a template tag which will format the form according to the settings initialized inside the form's __init__() method. Let's take a look at it (myauth/forms.py)
 

## Add those imports
from crispy_forms.helper import FormHelper                                                
from crispy_forms.layout import Layout, HTML, Submit

## And edit your form
class LoginForm(forms.Form):
  .
  .
  .
  def __init__(self, *args, **kwargs):                                                    
    super(LoginForm, self).__init__(*args, **kwargs)                                      
    self.helper = FormHelper()
    self.helper.form_method = 'post'
    self.helper.form_action = 'login'                                                     
    self.helper.form_class = 'form-horizontal'                                            
    self.helper.label_class = 'col-md-5'                                                  
    self.helper.field_class = 'col-md-6'
    self.helper.layout = Layout(
     'login', 'password',
     HTML('<div class="form-group">
       <div class="col-md-5"> </div>
       <div class="col-md-6">'),
     Submit('submit', 'Log in'),
     HTML('</div></div>'),
    )

And let's load it inside our template (myauth/templates/myauth/login.html)

{% extends 'base.html'%}    
{% load crispy_forms_tags %}                                                              
{% block content %}                                                                       
  <h1>Login</h1>                                                                          
  {% crispy form %}                                                                       
{% endblock %}

As you can see by going to http://localhost:8000/login.html, the form is now formatted "bootstrap-style" and looks nicer. Some of you may say that using the HTML() helper inside a form (which is not a template so should not have html inside it) is not really Kosher. As I have often say, this doesn't illustrate the best nor the only way of doing things, just our way. This is apparently also the way Crispy forms meant it but if you have constructive suggestions, don't be affraid to leave a comment.

As you can see, our template became quite shorter and simpler to write. You've also probably noted that the {% csrf_token %} tag is gone. Csrf protection is still there but it's taken care of by the {% crispy %} tag as well.

Well, I guess this is the wrap-up of this week's tutorial, I hope you enjoyed it and see you all next week Wink

Class-based views Crispy forms Tutorial Django Custom user model

Comments

You helped me so much with this tutorial, thanks a lot!

By Mathijs - 3 years, 4 months ago Reply 

Could you help me out with this one? I completed all the steps until part 3, when I want to add some users in my admin panel, I get the following error:

OperationalError at /admin/myauth/user/add/

no such table: auth_user

I have no idea where I should fix this, did I do something wrong in the tutorials?

By Mathijs - 3 years, 4 months ago Reply 

Thanks :-)

I don't know which path you followed. Did you have to migrate existing users? If so my guess would be that you forgot to uncomment or reset AUTH_USER_MODEL in your settings.py

By Emmanuelle Delescolle - 3 years, 4 months ago Reply 

You might also want to check the source code availaible at http://vc.lasolution.be/projects/babbler-tutorial/repository?utf8=%E2%9C%93&rev=creating-custom-user-model-django-16-part-3-end and see what are the differences with your own code

By Emmanuelle Delescolle - 3 years, 4 months ago Reply 

Post a comment