Merge pull request #1388 from takeflight/feature/no-username

Add support for user models with no username
pull/1406/head
Karl Hobley 2015-06-13 13:21:25 +01:00
commit 0e91aa46fd
5 zmienionych plików z 155 dodań i 26 usunięć

Wyświetl plik

@ -46,4 +46,29 @@ class Migration(migrations.Migration):
},
bases=(models.Model,),
),
migrations.CreateModel(
name='EmailUser',
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(null=True, verbose_name='last login', blank=True)
if django.VERSION >= (1, 8) else
models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')
),
('email', models.EmailField(unique=True, max_length=255)),
('is_staff', models.BooleanField(default=True)),
('is_active', models.BooleanField(default=True)),
('first_name', models.CharField(max_length=50, blank=True)),
('last_name', models.CharField(max_length=50, blank=True)),
('is_superuser', models.BooleanField(default=False)),
('groups', models.ManyToManyField(related_name='+', to='auth.Group', blank=True)),
('user_permissions', models.ManyToManyField(related_name='+', to='auth.Permission', blank=True)),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
]

Wyświetl plik

@ -1,6 +1,9 @@
import sys
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.contrib.auth.models import (
Group, Permission, AbstractBaseUser, PermissionsMixin, BaseUserManager)
class CustomUserManager(BaseUserManager):
@ -37,6 +40,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
last_name = models.CharField(max_length=50, blank=True)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
objects = CustomUserManager()
@ -45,3 +49,61 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
def get_short_name(self):
return self.first_name
class EmailUserManager(BaseUserManager):
def _create_user(self, email, password,
is_staff, is_superuser, **extra_fields):
"""
Creates and saves a User with the given email and password.
"""
email = self.normalize_email(email)
user = self.model(email=email, is_staff=is_staff, is_active=True,
is_superuser=is_superuser, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email=None, password=None, **extra_fields):
return self._create_user(email, password, False, False,
**extra_fields)
def create_superuser(self, email, password, **extra_fields):
return self._create_user(email, password, True, True,
**extra_fields)
class EmailUser(AbstractBaseUser):
# Cant inherit from PermissionsMixin because of clashes with
# groups/user_permissions related_names.
email = models.EmailField(max_length=255, unique=True)
is_staff = models.BooleanField(default=True)
is_active = models.BooleanField(default=True)
first_name = models.CharField(max_length=50, blank=True)
last_name = models.CharField(max_length=50, blank=True)
is_superuser = models.BooleanField(default=False)
groups = models.ManyToManyField(Group, related_name='+', blank=True)
user_permissions = models.ManyToManyField(Permission, related_name='+', blank=True)
USERNAME_FIELD = 'email'
objects = EmailUserManager()
def get_full_name(self):
return self.first_name + ' ' + self.last_name
def get_short_name(self):
return self.first_name
def steal_method(name):
func = getattr(PermissionsMixin, name)
if sys.version_info < (3,):
func = func.__func__
setattr(EmailUser, name, func)
methods = ['get_group_permissions', 'get_all_permissions', 'has_perm',
'has_perms', 'has_module_perms']
for method in methods:
steal_method(method)

Wyświetl plik

@ -1,7 +1,6 @@
from django import forms
from django.contrib.auth.forms import UserCreationForm as BaseUserCreationForm
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import Group, Permission
from django.forms.models import inlineformset_factory
@ -13,9 +12,37 @@ from wagtail.wagtailcore.models import UserPagePermissionsProxy, GroupPagePermis
User = get_user_model()
# The standard fields each user model is expected to have, as a minimum.
standard_fields = set(['email', 'first_name', 'last_name', 'is_superuser', 'groups'])
# extend Django's UserCreationForm with an 'is_superuser' field
class UserCreationForm(BaseUserCreationForm):
class UsernameForm(forms.ModelForm):
"""
Intelligently sets up the username field if it is infact a username. If the
User model has been swapped out, and the username field is an email or
something else, dont touch it.
"""
def __init__(self, *args, **kwargs):
super(UsernameForm, self).__init__(*args, **kwargs)
if User.USERNAME_FIELD == 'username':
field = self.fields['username']
field.regex = r"^[\w.@+-]+$"
field.help_text = _("Required. 30 characters or fewer. Letters, "
"digits and @/./+/-/_ only.")
field.error_messages = field.error_messages.copy()
field.error_messages.update({
'invalid': _("This value may contain only letters, numbers "
"and @/./+/-/_ characters.")})
@property
def username_field(self):
return self[User.USERNAME_FIELD]
def separate_username_field(self):
return User.USERNAME_FIELD not in standard_fields
class UserCreationForm(UsernameForm):
required_css_class = "required"
is_superuser = forms.BooleanField(
@ -24,26 +51,32 @@ class UserCreationForm(BaseUserCreationForm):
help_text=_("If ticked, this user has the ability to manage user accounts.")
)
password1 = forms.CharField(
label=_("Password"),
required=False,
widget=forms.PasswordInput,
help_text=_("Leave blank if not changing."))
password2 = forms.CharField(
label=_("Password confirmation"), required=False,
widget=forms.PasswordInput,
help_text=_("Enter the same password as above, for verification."))
email = forms.EmailField(required=True, label=_("Email"))
first_name = forms.CharField(required=True, label=_("First Name"))
last_name = forms.CharField(required=True, label=_("Last Name"))
class Meta:
model = User
fields = ("username", "email", "first_name", "last_name", "is_superuser", "groups")
fields = set([User.USERNAME_FIELD]) | standard_fields
widgets = {
'groups': forms.CheckboxSelectMultiple
}
def clean_username(self):
# Method copied from parent
username = self.cleaned_data["username"]
username_field = User.USERNAME_FIELD
username = self.cleaned_data[username_field]
try:
# When called from BaseUserCreationForm, the method fails if using a AUTH_MODEL_MODEL,
# This is because the following line tries to perform a lookup on
# the default "auth_user" table.
User._default_manager.get(username=username)
User._default_manager.get(**{username_field: username})
except User.DoesNotExist:
return username
raise forms.ValidationError(
@ -51,8 +84,19 @@ class UserCreationForm(BaseUserCreationForm):
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"])
# users can access django-admin iff they are a superuser
user.is_staff = user.is_superuser
@ -65,21 +109,13 @@ class UserCreationForm(BaseUserCreationForm):
# Largely the same as django.contrib.auth.forms.UserCreationForm, but with enough subtle changes
# (to make password non-required) that it isn't worth inheriting...
class UserEditForm(forms.ModelForm):
class UserEditForm(UsernameForm):
required_css_class = "required"
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=30,
regex=r'^[\w.@+-]+$',
help_text=_("Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."),
error_messages={
'invalid': _("This value may contain only letters, numbers and @/./+/-/_ characters.")
})
email = forms.EmailField(required=True, label=_("Email"))
first_name = forms.CharField(required=True, label=_("First Name"))
@ -103,7 +139,7 @@ class UserEditForm(forms.ModelForm):
class Meta:
model = User
fields = ("username", "email", "first_name", "last_name", "is_active", "is_superuser", "groups")
fields = set([User.USERNAME_FIELD, "is_active"]) | standard_fields
widgets = {
'groups': forms.CheckboxSelectMultiple
}
@ -112,8 +148,10 @@ class UserEditForm(forms.ModelForm):
# 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"]
username_field = User.USERNAME_FIELD
try:
User._default_manager.exclude(id=self.instance.id).get(username=username)
User._default_manager.exclude(id=self.instance.id).get(**{
username_field: username})
except User.DoesNotExist:
return username
raise forms.ValidationError(self.error_messages['duplicate_username'])

Wyświetl plik

@ -18,7 +18,9 @@
{% csrf_token %}
<section id="account" class="active nice-padding">
<ul class="fields">
{% include "wagtailadmin/shared/field_as_li.html" with field=form.username %}
{% if form.separate_username_field %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.username_field %}
{% endif %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.email %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.first_name %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.last_name %}

Wyświetl plik

@ -19,7 +19,9 @@
<section id="account" class="active nice-padding">
<ul class="fields">
{% include "wagtailadmin/shared/field_as_li.html" with field=form.username %}
{% if form.separate_username_field %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.username_field %}
{% endif %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.email %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.first_name %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.last_name %}