Created
March 12, 2017 23:03
-
-
Save thobroni/eebdd923689b699b39be7f56d3f7d8ec to your computer and use it in GitHub Desktop.
Customizing Django’s Default User Model
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# accounts/models.py | |
from django.db import models | |
from django.core.validators import RegexValidator | |
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager | |
import enums | |
class AuthUserManager(BaseUserManager): | |
""" | |
Custom manager for EmailUser. | |
""" | |
def create_user(self, username, email, password=None): | |
""" | |
Create and save an AuthUser with the given email and password. | |
:param str email: user email | |
:param str password: user password | |
:param bool is_staff: whether user staff or not | |
:param bool is_superuser: whether user admin or not | |
:return custom_user.models.EmailUser user: user | |
:raise ValueError: email is not set | |
""" | |
if not email: | |
raise ValueError('Users must have an email address') | |
if not username: | |
raise ValueError('Users must have a username') | |
user = self.model(username=username, email=self.normalize_email(email), ) | |
user.is_active = True | |
user.set_password(password) | |
user.save(using=self._db) | |
return user | |
def create_superuser(self, username, email, password): | |
user = self.create_user(username=username, email=email, password=password) | |
user.is_staff = True | |
user.is_superuser = True | |
user.save(using=self._db) | |
return user | |
class Users(AbstractBaseUser, PermissionsMixin): | |
""" | |
Abstract User with the same behaviour as Django's default User. | |
AbstractEmailUser does not have username field. Uses email as the | |
USERNAME_FIELD for authentication. | |
Use this if you need to extend EmailUser. | |
Inherits from both the AbstractBaseUser and PermissionMixin. | |
The following attributes are inherited from the superclasses: | |
* password | |
* last_login | |
* is_superuser | |
""" | |
alphanumeric = RegexValidator(r'^[0-9a-zA-Z]*$', message='Only alphanumeric characters are allowed.') | |
# Redefine the basic fields that would normally be defined in User | |
username = models.CharField(unique=True, max_length=20, validators=[alphanumeric]) | |
email = models.EmailField(verbose_name='email address', unique=True, max_length=255) | |
first_name = models.CharField(max_length=30, null=True, blank=True) | |
last_name = models.CharField(max_length=50, null=True, blank=True) | |
date_joined = models.DateTimeField(auto_now_add=True) | |
is_active = models.BooleanField(default=True, null=False) | |
is_staff = models.BooleanField(default=False, null=False) | |
# Our own fields ### | |
profile_image = models.ImageField(upload_to="uploads", blank=False, null=False, default="/static/images/defaultuserimage.png") | |
user_bio = models.CharField(max_length=600, blank=True) | |
gender = models.CharField(verbose_name='Gender', max_length=512, choices=enums.GENDER_CHOICES, default=enums.DEFAULT_GENDER_STATUS, null=True, blank=True) | |
objects = AuthUserManager() | |
USERNAME_FIELD = 'email' | |
REQUIRED_FIELDS = ['username'] | |
def get_full_name(self): | |
fullname = self.first_name+" "+self.last_name | |
return fullname | |
def get_short_name(self): | |
return self.username | |
def __unicode__(self): | |
return self.email | |
class Meta: | |
verbose_name = "Users" | |
verbose_name_plural = "Users" | |
# accounts/forms.py | |
from django import forms | |
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, ReadOnlyPasswordHashField | |
from .models import Users | |
class CustomUserCreationForm(UserCreationForm): | |
""" | |
A form for creating new users. Includes all the required fields, plus a repeated password. | |
""" | |
password1 = forms.CharField(label='Password', widget=forms.PasswordInput) | |
password2 = forms.CharField(label='Password Confirmation', widget=forms.PasswordInput) | |
class Meta(UserCreationForm.Meta): | |
model = Users | |
fields = ('username', 'email') | |
def clean_username(self): | |
username = self.cleaned_data["username"] | |
try: | |
Users._default_manager.get(username=username) | |
except Users.DoesNotExist: | |
return username | |
raise forms.ValidationError(self.error_messages['duplicate_username']) | |
def clean_password2(self): | |
# Check that the two password entries match | |
password1 = self.cleaned_data.get("password1") | |
password2 = self.cleaned_data.get("password2") | |
if password1 and password2 and password1 != password2: | |
raise forms.ValidationError("Passwords do not match.") | |
return password2 | |
def save(self, commit=True): | |
# Save the provided password in hashed format | |
user = super(UserCreationForm, self).save(commit=False) | |
user.set_password(self.cleaned_data["password1"]) | |
if commit: | |
user.save() | |
return user | |
class CustomUserChangeForm(UserChangeForm): | |
password = ReadOnlyPasswordHashField(label="password", | |
help_text="""Raw passwords are not stored, so there is no way to see this | |
user's password, but you can change the password using <a href=\"password/\"> | |
this form</a>.""") | |
class Meta(UserChangeForm.Meta): | |
model = Users | |
fields = ('username', 'email', 'password', 'is_active', 'is_staff', 'is_superuser', 'user_permissions') | |
def clean_password(self): | |
# Regardless of what the user provides, return the initial value. | |
# This is done here, rather than on the field, because the | |
# field does not have access to the initial value | |
return self.initial["password"] | |
# accounts/admin.py | |
from django.contrib import admin | |
from django.contrib.auth.admin import UserAdmin | |
from .forms import CustomUserCreationForm, CustomUserChangeForm | |
from .models import Users | |
# Register your models here. | |
class UsersAdmin(UserAdmin): | |
form = CustomUserChangeForm | |
add_form = CustomUserCreationForm | |
list_display = ('username', 'email', 'is_staff', 'is_superuser') | |
list_filter = ('is_superuser', 'is_staff', ) | |
fieldsets = ( | |
(None, {'fields': ('first_name', 'last_name', 'username', 'email', 'password', 'last_login', )}), | |
('Personal info', {'fields': ('profile_image', 'user_bio', 'gender', )}), | |
('Permissions', {'fields': ('is_active', 'is_superuser', 'is_staff', 'groups', 'user_permissions')}), | |
) | |
add_fieldsets = ( | |
(None, { | |
'classes': ('wide',), | |
'fields': ('username', 'email', 'password1', 'password2', 'is_staff', 'is_superuser')} | |
), | |
) | |
search_fields = ('email', 'username') | |
ordering = ('email',) | |
# filter_vertical = ('groups', 'user_permissions',) | |
admin.site.register(Users, UsersAdmin) | |
# accounts/enums.py | |
Unspecified = 'unspecified' | |
Male = 'male' | |
Female = 'female' | |
GENDER_CHOICES = ((Unspecified, 'Unspecified'), (Male, 'Male'), (Female, 'Female'), ) | |
DEFAULT_GENDER_STATUS = Unspecified | |
# accounts/backends.py | |
from .models import Users | |
class EmailAuthBackend(object): | |
""" | |
Email Authentication Backend | |
Allows a user to sign in using an email/password pair rather than | |
a username/password pair. | |
""" | |
def authenticate(self, username=None, password=None): | |
""" Authenticate a user based on email address as the user name. """ | |
try: | |
user = Users.objects.get(email=username) | |
if user.check_password(password): | |
return user | |
except Users.DoesNotExist: | |
return None | |
def get_user(self, user_id): | |
""" Get a User object from the user_id. """ | |
try: | |
return Users.objects.get(pk=user_id) | |
except Users.DoesNotExist: | |
return None | |
# settings.py | |
AUTH_USER_MODEL = 'accounts.Users' | |
AUTHENTICATION_BACKENDS = ['accounts.backends.EmailAuthBackend', ] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment