Last active
February 15, 2016 02:03
-
-
Save micahhausler/63636f26cc87bb966218 to your computer and use it in GitHub Desktop.
Custom User Model Switch
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
| # -*- 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,), | |
| ), | |
| ] |
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
| 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) |
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
| # 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) |
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
| USERNAME_MAX_LENGTH = 255 |
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
| 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"] |
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
| # 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) |
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
| AUTH_USER_MODEL = 'user.User' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice, couple of things to simplify this even further (tested on 1.8).
admin.py can simply be:
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
Finally I created two migrations. First migration generates the table just like the default
django.contrib.authand 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