kopia lustrzana https://github.com/wagtail/wagtail
Merge pull request #1388 from takeflight/feature/no-username
Add support for user models with no usernamepull/1406/head
commit
0e91aa46fd
|
@ -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,),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
Ładowanie…
Reference in New Issue