1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/admin/forms.py
catborise 2e5ef34473 fix(accounts): an email address belongs to one user, so "Lost OTP?" reaches it
"Lost OTP?" mails the QR code only when exactly one active user has the
address. Any user could enter another user's address in their profile and
silently switch the recovery off for both. The profile and the admin user
forms now refuse an address another user has, ignoring case; an empty
address stays allowed for everyone.
2026-10-26 11:19:42 +03:00

143 lines
4.7 KiB
Python

from django import forms
from django.contrib.auth import password_validation
from django.contrib.auth.forms import AdminPasswordChangeForm
from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from accounts.forms import UniqueEmailMixin
from accounts.models import UserAttributes
from .models import Permission
class GroupForm(forms.ModelForm):
permissions = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=Permission.objects.filter(content_type__model="permissionset"),
required=False,
)
users = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=User.objects.all(),
required=False,
)
def __init__(self, *args, **kwargs):
super(GroupForm, self).__init__(*args, **kwargs)
instance = getattr(self, "instance", None)
if instance and instance.id:
self.fields["users"].initial = self.instance.user_set.all()
def save_m2m(self):
self.instance.user_set.set(self.cleaned_data["users"])
def save(self, *args, **kwargs):
instance = super(GroupForm, self).save()
self.save_m2m()
return instance
class Meta:
model = Group
fields = "__all__"
class UserForm(UniqueEmailMixin, forms.ModelForm):
user_permissions = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=Permission.objects.filter(content_type__model="permissionset"),
label=_("Permissions"),
required=False,
)
groups = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=Group.objects.all(),
label=_("Groups"),
required=False,
)
class Meta:
model = User
fields = [
"username",
"groups",
"first_name",
"last_name",
"email",
"user_permissions",
"is_staff",
"is_active",
"is_superuser",
]
labels = {"is_staff": _("Template editor")}
help_texts = {"is_staff": _("May change and delete the template VMs given to them")}
class UserCreateForm(UserForm):
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# New users may change their own password unless unticked here
self.fields["user_permissions"].initial = Permission.objects.filter(
content_type__app_label="accounts", codename="change_password"
)
def _post_clean(self):
# The validators in AUTH_PASSWORD_VALIDATORS, as on the password change
# forms; after the instance has the form's values (username, email).
super()._post_clean()
password = self.cleaned_data.get("password")
if password:
try:
password_validation.validate_password(password, self.instance)
except ValidationError as error:
self.add_error("password", error)
def save(self, commit=True):
# hashed before the user is first written: the raw password never
# reaches the database
user = super().save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
self.save_m2m()
return user
class Meta(UserForm.Meta):
fields = [
"username",
"password",
"groups",
"first_name",
"last_name",
"email",
"user_permissions",
"is_staff",
"is_active",
"is_superuser",
]
class UserAttributesForm(forms.ModelForm):
class Meta:
model = UserAttributes
exclude = ["user", "can_clone_instances", "must_change_password"]
class AdminPasswordForm(AdminPasswordChangeForm):
"""A password an administrator sets; the user may have to change it at their next login."""
must_change_password = forms.BooleanField(label=_("The user must change it at their next login"), required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["must_change_password"] = self.fields.pop("must_change_password") # last, after Django's own
def clean(self):
cleaned = super().clean()
# the change form asks for the old password: a user without one could never pass it
if cleaned.get("must_change_password") and not cleaned.get("set_usable_password", True):
raise ValidationError(_("A user without a password cannot be made to change it"))
return cleaned