Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import latest code changes from Django 4.0 #65

Merged
merged 1 commit into from
Feb 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions src/custom_user/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
from .models import EmailUser


@admin.register(EmailUser)
class EmailUserAdmin(UserAdmin):

"""EmailUser Admin model."""
"""
EmailUser Admin model.
"""

fieldsets = (
(None, {"fields": ("email", "password")}),
Expand All @@ -22,13 +24,19 @@ class EmailUserAdmin(UserAdmin):
"is_superuser",
"groups",
"user_permissions",
)
),
},
),
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
)
add_fieldsets = (
(None, {"classes": ("wide",), "fields": ("email", "password1", "password2")}),
(
None,
{
"classes": ("wide",),
"fields": ("email", "password1", "password2"),
},
),
)

# The forms to add and change user instances
Expand All @@ -46,7 +54,3 @@ class EmailUserAdmin(UserAdmin):
"groups",
"user_permissions",
)


# Register the new EmailUserAdmin
admin.site.register(EmailUser, EmailUserAdmin)
5 changes: 3 additions & 2 deletions src/custom_user/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@


class CustomUserConfig(AppConfig):

"""Default configuration for custom_user."""
"""
Default configuration for custom_user.
"""

name = "custom_user"
verbose_name = "Custom User"
Expand Down
82 changes: 42 additions & 40 deletions src/custom_user/forms.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
"""EmailUser forms."""
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth import get_user_model, password_validation
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _


class EmailUserCreationForm(forms.ModelForm):

"""
A form for creating new users.

Includes all the required fields, plus a repeated password.

"""

error_messages = {
"duplicate_email": _("A user with that email already exists."),
"password_mismatch": _("The two password fields didn't match."),
}

password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
password1 = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
help_text=password_validation.password_validators_help_text_html(),
)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput,
help_text=_("Enter the same password as above, for verification."),
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
strip=False,
help_text=_("Enter the same password as before, for verification."),
)

class Meta: # noqa: D101
class Meta:
model = get_user_model()
fields = ("email",)

Expand All @@ -35,8 +40,7 @@ def clean_email(self):
Clean form email.

:return str email: cleaned email
:raise forms.ValidationError: Email is duplicated

:raise ValidationError: Email is duplicated
"""
# Since EmailUser.email is unique, this check is redundant,
# but it sets a nicer error message than the ORM. See #13147.
Expand All @@ -45,7 +49,7 @@ def clean_email(self):
get_user_model()._default_manager.get(email=email)
except get_user_model().DoesNotExist:
return email
raise forms.ValidationError(
raise ValidationError(
self.error_messages["duplicate_email"],
code="duplicate_email",
)
Expand All @@ -55,28 +59,37 @@ def clean_password2(self):
Check that the two password entries match.

:return str password2: cleaned password2
:raise forms.ValidationError: password2 != password1

:raise ValidationError: password2 != password1
"""
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError(
raise ValidationError(
self.error_messages["password_mismatch"],
code="password_mismatch",
)
return password2

def _post_clean(self):
super()._post_clean()
# Validate the password after self.instance is updated with form data
# by super().
password = self.cleaned_data.get("password2")
if password:
try:
password_validation.validate_password(password, self.instance)
except ValidationError as error:
self.add_error("password2", error)

def save(self, commit=True):
"""
Save user.

Save the provided password in hashed format.

:return custom_user.models.EmailUser: user

"""
user = super(EmailUserCreationForm, self).save(commit=False)
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
Expand All @@ -90,39 +103,28 @@ class EmailUserChangeForm(forms.ModelForm):

Includes all the fields on the user, but replaces the password field
with admin's password hash display field.

"""

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="%(url)s">this form</a>.'
)
% {"url": "../password/"},
"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="{}">this form</a>.'
),
)

class Meta: # noqa: D101
class Meta:
model = get_user_model()
exclude = ()

def __init__(self, *args, **kwargs):
"""Init the form."""
super(EmailUserChangeForm, 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):
"""
Clean password.

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 str password:

"""
return self.initial["password"]
super().__init__(*args, **kwargs)
password = self.fields.get("password")
if password:
password.help_text = password.help_text.format("../password/")
user_permissions = self.fields.get("user_permissions")
if user_permissions:
user_permissions.queryset = user_permissions.queryset.select_related(
"content_type"
)
33 changes: 18 additions & 15 deletions src/custom_user/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@


class EmailUserManager(BaseUserManager):

"""Custom manager for EmailUser."""
"""
Custom manager for EmailUser.
"""

def _create_user(self, email, password, is_staff, is_superuser, **extra_fields):
"""
Expand All @@ -24,7 +25,6 @@ def _create_user(self, email, password, is_staff, is_superuser, **extra_fields):
:param bool is_superuser: whether user admin or not
:return custom_user.models.EmailUser user: user
:raise ValueError: email is not set

"""
now = timezone.now()
if not email:
Expand All @@ -51,25 +51,31 @@ def create_user(self, email, password=None, **extra_fields):
:param str email: user email
:param str password: user password
:return custom_user.models.EmailUser user: regular user

"""
is_staff = extra_fields.pop("is_staff", False)
return self._create_user(email, password, is_staff, False, **extra_fields)
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
return self._create_user(email, password, **extra_fields)

def create_superuser(self, email, password, **extra_fields):
def create_superuser(self, email, password=None, **extra_fields):
"""
Create and save an EmailUser with the given email and password.

:param str email: user email
:param str password: user password
:return custom_user.models.EmailUser user: admin user

"""
return self._create_user(email, password, True, True, **extra_fields)
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)

if extra_fields.get("is_staff") is not True:
raise ValueError("Superuser must have is_staff=True.")
if extra_fields.get("is_superuser") is not True:
raise ValueError("Superuser must have is_superuser=True.")

return self._create_user(email, password, **extra_fields)

class AbstractEmailUser(AbstractBaseUser, PermissionsMixin):

class AbstractEmailUser(AbstractBaseUser, PermissionsMixin):
"""
Abstract User with the same behaviour as Django's default User.

Expand All @@ -84,7 +90,6 @@ class AbstractEmailUser(AbstractBaseUser, PermissionsMixin):
* password
* last_login
* is_superuser

"""

email = models.EmailField(
Expand All @@ -110,7 +115,7 @@ class AbstractEmailUser(AbstractBaseUser, PermissionsMixin):
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []

class Meta: # noqa: D101
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
abstract = True
Expand All @@ -129,13 +134,11 @@ def email_user(self, subject, message, from_email=None, **kwargs):


class EmailUser(AbstractEmailUser):

"""
Concrete class of AbstractEmailUser.

Use this if you don't need to extend EmailUser.

"""

class Meta(AbstractEmailUser.Meta): # noqa: D101
class Meta(AbstractEmailUser.Meta):
swappable = "AUTH_USER_MODEL"
Loading