Skip to content

Instantly share code, notes, and snippets.

@micahhausler
Last active February 15, 2016 02:03
Show Gist options
  • Select an option

  • Save micahhausler/63636f26cc87bb966218 to your computer and use it in GitHub Desktop.

Select an option

Save micahhausler/63636f26cc87bb966218 to your computer and use it in GitHub Desktop.
Custom User Model Switch
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
import django.core.validators
#### SEE LINE 25!
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
### Since we've already used longerusername to modify this field, we need to match the database schema in this initial migration, max_length of 255
('username', models.CharField(help_text='Required. 255 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=255, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])),
('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups')),
('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
],
options={
'db_table': 'auth_user',
},
bases=(models.Model,),
),
]
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
from .forms import UserCreationForm, UserChangeForm
class MyUserAdmin(UserAdmin):
form = UserChangeForm
add_form = UserCreationForm
# The following two templates are to use `django-su`
change_list_template = 'admin/auth/user/change_list.html'
change_form_template = 'admin/auth/user/change_form.html'
list_display = [
'username',
'email',
'first_name',
'last_name',
'is_staff',
]
list_filter = [
'groups',
'is_staff',
'is_active',
]
search_fields = [
'first_name',
'last_name',
'email',
'username',
]
admin.site.register(User, MyUserAdmin)
# from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.conf import settings
from django.db import models
class Something(object):
def get_user(self, username):
#return User.objects.get(username=username)
return get_user_model().objects.get(username=username)
class Account(models.Model):
#user = models.OneToOneField(User, on_delete=models.PROTECT)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
USERNAME_MAX_LENGTH = 255
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from .constants import USERNAME_MAX_LENGTH
class UserCreationForm(forms.ModelForm):
"""
A form that creates a user, with no privileges, from the given username and
password.
"""
error_messages = {
'duplicate_username': _("A user with that username already exists."),
'password_mismatch': _("The two password fields didn't match."),
}
username = forms.RegexField(
label=_("Username"),
max_length=USERNAME_MAX_LENGTH,
regex=r'^[\w.@+-]+$',
help_text=_("Required. {0} characters or fewer. Letters, digits and "
"@/a./+/-/_ only.".format(USERNAME_MAX_LENGTH)),
error_messages={
'invalid': _("This value may contain only letters, numbers and "
"@/./+/-/_ characters.")})
password1 = forms.CharField(
label=_("Password"),
widget=forms.PasswordInput)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput,
help_text=_("Enter the same password as above, for verification."))
class Meta:
model = User
fields = ("username",)
def clean_username(self):
# Since User.username is unique, this check is redundant,
# but it sets a nicer error message than the ORM. See #13147.
username = self.cleaned_data["username"]
try:
User._default_manager.get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError(
self.error_messages['duplicate_username'],
code='duplicate_username',
)
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
username = forms.RegexField(
label=_("Username"),
max_length=USERNAME_MAX_LENGTH,
regex=r"^[\w.@+-]+$",
help_text=_("Required. {0} characters or fewer. Letters, digits and "
"@/./+/-/_ only.".format(USERNAME_MAX_LENGTH)),
error_messages={
'invalid': _("This value may contain only letters, numbers and "
"@/./+/-/_ characters.")})
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:
model = User
fields = '__all__'
def __init__(self, *args, **kwargs):
super(UserChangeForm, self).__init__(*args, **kwargs)
f = self.fields.get('user_permissions', None)
if f is not None:
f.queryset = f.queryset.select_related('content_type')
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"]
# In a new `user` app. This MUST be in a new app, so any other migrations
# that depend on the user model can reference this correctly.
from django.core import validators
from django.core.mail import send_mail
from django.contrib.auth.models import UserManager, PermissionsMixin, AbstractBaseUser
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from .constants import USERNAME_MAX_LENGTH
class User(AbstractBaseUser, PermissionsMixin):
objects = UserManager()
class Meta:
db_table = 'auth_user'
verbose_name = _('user')
verbose_name_plural = _('users')
username = models.CharField(_('username'), max_length=255, unique=True,
help_text=_(
'Required. {0} characters or fewer. Letters, digits and '
'@/./+/-/_ only.'.format(USERNAME_MAX_LENGTH)),
validators=[
validators.RegexValidator(r'^[\w.@+-]+$', _('Enter a valid username.'), 'invalid')
]
)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"Returns the short name for the user."
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email], **kwargs)
AUTH_USER_MODEL = 'user.User'
@timsavage

Copy link
Copy Markdown

Nice, couple of things to simplify this even further (tested on 1.8).

admin.py can simply be:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

admin.site.register(User, UserAdmin)

Customising forms.py is not necessary as the creation and change forms are generated from the Swapped model and obtain the correct field length and validation messages.

Add an apps.py eg

from django.apps import AppConfig

class MyAuthAppConfig(AppConfig):
    name = 'myapp.auth'
    label = 'my_auth'
    verbose_name = 'Authentication and Authorization (*)'

Finally I created two migrations. First migration generates the table just like the default django.contrib.auth and a second one applies the username change. When you migrate use the --fake-initial option and Django will fake apply the first migration if the table already exists.

Added a GIST https://gist.github.com/timsavage/0cabec3e384b2dffa389

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment