Django Authentication
Django Authentication#
- Authentication is connecting credentials to an incoming request. A person is who they say they are.
- Authorization is ensuring a user has permission to do what they want to do
Decorators#
Django provides the login_required
decorator but they are iffy with class-based views.
So for class based views, django
provides mixins (small classes)
from django.contrib.auth.mixins import LoginRequiredMixin
Then add as inherited from:
class CreatePost(LoginRequiredMixin, generic.CreateView):
...
Creating a Login View#
Might be worth it to make an accounts
app:
./manage.py startapp accounts
Then add to INSTALLED_APPS
:
accounts
For the actual view in views.py
:
class LoginView(generic.FormView):
form_class = AuthenticationForm
success_url = reverse_lazy("posts:all")
template_name = "accounts/login.html"
def get_form(self, form_class=None):
if form_class is None:
form_class = self.get_form_class()
return form_class(self.request, **self.get_form_kwargs())
def form_valid(self, form):
login(self.request, form.get_user())
return super().form_valid(form)
In urls.py
:
urlpatterns = [
url(r'^login/$', views.LoginView.as_view(), name="login"),
]
Add the template is accounts/templates/accounts/login.html
:
{% extends 'layout.html' %}
{% load bootstrap3 %}
{% block title_tag %}Login | {{ block.super }}{% endblock %}
{% block body_content %}
<div class="container">
<h1>Login</h1>
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" lue="Login" class="btn btn-default">
</form>
</div>
{% endblock %}
The Easier Way#
Alas, there is an even easier way that uses the existing django auth
In urlpatterns
for prject use:
url(r'^accounts/', include("django.contrib.auth.urls"))
At accounts/login
you will get the TemplateDoesNotExist at /accounts/login/ - registration/login.html
error
So create that file in the project level templates with our exisitng template code
Now when you login it will go to accounts/profile
if you don’t have a template for this add a setting:
LOGIN_REDIRECT_URL = "posts:all"
This can be a url
or a url name
Logout#
Import logout:
from django.contrib.auth import login, logout
Now we just want it to redirect after logout
Code:
class LogoutView(generic.RedirectView):
url = reverse_lazy('home')
def get(self, request, *args, **kwargs):
logout(request)
return super().get(request, *args, **kwargs)
url:
url(r'^logout/$', view.LogoutView.as_view(), name="logout"),
Signing Up#
Signing up is usually very site specific so there is no generic
signup view:
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
class SignupView(generic.CreateView):
form_class = UserCreationForm
success_url = reverse_lazy("login")
template_name = "accounts/signup.html"
template:
{% extends 'layout.html' %}
{% load bootstrap3 %}
{% block title_tag %}Signup | {{ block.super }}{% endblock %}
{% block body_content %}
<div class="container">
<h1>Signup</h1>
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" lue="Signup" class="btn btn-default">
</form>
</div>
{% endblock %}
url:
url(r'^signup/$', views.SignupView.as_view(), name="signup")
Make changes to default user create form in forms.py
:
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User
class UserCreateForm(UserCreationForm):
class Mate:
fields = ['username', 'email', 'password1', 'password2']
model = User
def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs)
self.fields['username'].label = "Display Name"
self.fields['email'].label = "Email Address"
Mailed Activation#
There is a good library that works on the wprkflow of your app
Specifically sending activation email etc.
Check it out at django-registration
Autologin after registration#
class SignUp(generic.CreateView):
form_class = forms.UserCreateForm
template_name = 'accounts/signup.html'
success_url = reverse_lazy('products:list')
def form_valid(self, form):
res = super().form_valid(form)
user = authenticate(username=form.cleaned_data['username'], password=form.cleaned_data['password1'])
if user is not None:
if user.is_active:
login(self.request, user)
return res
Password Reset#
Django has built-in except the tmeplates look like django admin
So to change that template you can override it at <root_templates>/registration/password_reset_form.html
Then to change the email template change: <root_templates>/registration/password_reset_done.html
Then the password reset page: <root_templates>/registration/password_reset_confirm.html
Update password reset complete: <root_templates>/registration/password_reset_complete.html
To catch the email during development use: the Django debug bar mail panel
Customising Users#
- Create a custom model that has a 1-to-1 relationship with Django’s
User
model - extra non-critical data - Extend the abstract
User
model in an absract form - Replace the User model and extend
AbstractBaseUser
Replacing the existing user model#
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin
)
from django.db import models
from django.utils import timezone
class UserManager(BaseUserManager):
def create_user(self, email, username, display_name=None, password=None):
if not email:
raise ValueError("Users must have an email address")
if not display_name:
display_name = username
user = self.model(
email=self.normalize_email(email),
username=username,
display_name=display_name
)
user.set_password(password)
user.save()
return user
def create_super_user(self, email, username, display_name, password):
'''Create a custom super user
mainly run through the commandline
'''
user = self.create_user(email, username, display_name, password)
user.is_staff = True
user.is_superuser = True
user.save()
return user
Also create the actual User
class with:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
username = models.Charfield(max_length=40, unique=True)
display_name = models.CharField(max_length=140)
bio = models.CharField(max_length=140, blank=True, default="")
avatar = models.ImageField(blank=True, null=True)
date_joined = models.DateTimeField(default=timezone.now)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
# So calling User.object.all()
objects = UserManager()
# Unique identifier to search user
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['display_name', 'username']
def __str__(self):
return "@{}".format(self.username)
def get_short_name(self):
return self.display_name
def get_long_name(self):
return "{} @({})".format(display_name, username)
You need to tell django in settings.py
what User model to use:
# Tells django what model to use for the user model
AUTH_USER_MODEL = "accounts.user"
Then in related models user use this instead of normal User
from django.contrb.auth.models
:
from django.conf import settings
settings.AUTH_USER_MODEL
In other places where you need the actual model:
from django.contrib.auth import get_user_model
get_user_model()
Permissions#
For every new Non-Abstract model you create with django, it creates a content_type instance. Stores model and model_name. So you can link to model without knowing where it is defined.
Add, Change, delete permissions
Permissions are about models, not the instances. So user does not have permission for objects just belonging to them.
Groups can also be used to clumping together permissions
Adding a permission#
In a model add to class Meta:
permissions = (
('ban_member', 'Can ban members'),
)
When you add a permission you need to migrate, as adds to permissions table
Check if a user has a permission:
if self.request.user.has_perm("products.can_give_discount")
So it uses model plural name
+ permission_name
Permissions#
add
- View the add form and add an objectchange
- View the change form and change an objectdelete
- View the delete form
Checking if a user has a specific permission
App name foo
, model name Bar
- add:
user.has_perm('foo.add_bar')
- change:
user.has_perm('foo.change_bar')
- delete:
user.has_perm('foo.delete_bar')
Creating groups#
from django.contrib.auth.models import (
Permission,
Group
)
new_group, group = Group.objects.get_or_create(name="Editors")
Creating permissions#
content_type = ContentType.objects.get_for_model(models.Product)
permission = Permission.objects.get_or_create(
codename='can_give_discount',
name='Can Give Discount',
content_type=content_type
)
group.permissions.add(permission)
user.groups.add(group)
group.user_set.add(user)
Create group, create permission and add to permission#
def create_editor(self, email, dob, password):
user = self.create_user(
email,
dob,
accepted_tos=True,
password=password
)
try:
editors = Group.objects.get(name__iexact="Editors")
except Group.DoesNotExist:
editors = Group.objects.create(name="Editors")
editors.permissions.add(Permission.objects.get(codename="can_give_discount"))
# add can_give_discount permission
user.groups.add(editors)
user.save()
return user