Skip to content

Instantly share code, notes, and snippets.

@thobroni
Created March 12, 2017 23:03
Show Gist options
  • Save thobroni/eebdd923689b699b39be7f56d3f7d8ec to your computer and use it in GitHub Desktop.
Save thobroni/eebdd923689b699b39be7f56d3f7d8ec to your computer and use it in GitHub Desktop.
Customizing Django’s Default User Model
# 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