mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
The pages many views share were a horizontal form whose Cancel went back in a primary colour beside a green Save, and a delete question in a yellow alert squeezed into half the width, which wrapped word by word on a phone. Both now sit in a centred column with their heading: the form's fields with their labels above in a card, Cancel outlined and Save the primary action; the delete page asks "Delete <name>?" in a card with a red edge, says what deleting does, and offers Cancel and Delete. The form for a user's rights on a VM names them as the VM's Users tab does: Change settings, Delete the VM, and Change console settings, which only counts together with Change settings.
116 lines
4.2 KiB
Python
116 lines
4.2 KiB
Python
from appsettings.settings import app_settings
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.auth.forms import AuthenticationForm
|
|
from django.core.validators import MaxLengthValidator
|
|
from django.forms import CharField, EmailField, Form, ModelForm, TypedChoiceField, ValidationError
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from .models import UserInstance, UserSSHKey
|
|
from .utils import validate_ssh_key
|
|
|
|
|
|
class UserInstanceForm(ModelForm):
|
|
def __init__(self, *args, **kwargs):
|
|
super(UserInstanceForm, self).__init__(*args, **kwargs)
|
|
|
|
# Make user and instance fields not editable after creation
|
|
instance = getattr(self, "instance", None)
|
|
if instance and instance.id is not None:
|
|
self.fields["user"].disabled = True
|
|
self.fields["instance"].disabled = True
|
|
|
|
def clean_instance(self):
|
|
instance = self.cleaned_data["instance"]
|
|
if app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER == "False":
|
|
# the ownership being edited is not another owner
|
|
exists = UserInstance.objects.filter(instance=instance).exclude(pk=self.instance.pk)
|
|
if exists:
|
|
raise ValidationError(_("Instance owned by another user"))
|
|
|
|
return instance
|
|
|
|
class Meta:
|
|
model = UserInstance
|
|
fields = "__all__"
|
|
# the rights beyond power and console, as the VM's Users tab names them
|
|
labels = {"is_change": _("Change settings"), "is_delete": _("Delete the VM"),
|
|
"is_vnc": _("Change console settings")}
|
|
help_texts = {"is_vnc": _("Only together with Change settings")}
|
|
|
|
|
|
class UniqueEmailMixin:
|
|
""""Lost OTP?" mails only an address that is one user's: a second user
|
|
with the same address would switch it off for both."""
|
|
|
|
def clean_email(self):
|
|
email = self.cleaned_data["email"]
|
|
if email and get_user_model().objects.filter(email__iexact=email).exclude(pk=self.instance.pk).exists():
|
|
raise ValidationError(_("Another user has this email address"))
|
|
return email
|
|
|
|
|
|
class ProfileForm(UniqueEmailMixin, ModelForm):
|
|
class Meta:
|
|
model = get_user_model()
|
|
fields = ("first_name", "last_name", "email")
|
|
|
|
|
|
class UserSSHKeyForm(ModelForm):
|
|
def __init__(self, *args, **kwargs):
|
|
self.user = kwargs.pop("user", None)
|
|
self.publickeys = UserSSHKey.objects.filter(user=self.user)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def clean_keyname(self):
|
|
for key in self.publickeys:
|
|
if self.cleaned_data["keyname"] == key.keyname:
|
|
raise ValidationError(_("Key name already exist"))
|
|
|
|
return self.cleaned_data["keyname"]
|
|
|
|
def clean_keypublic(self):
|
|
for key in self.publickeys:
|
|
if self.cleaned_data["keypublic"] == key.keypublic:
|
|
raise ValidationError(_("Public key already exist"))
|
|
|
|
if not validate_ssh_key(self.cleaned_data["keypublic"]):
|
|
raise ValidationError(_("Invalid key"))
|
|
return self.cleaned_data["keypublic"]
|
|
|
|
def save(self, commit=True):
|
|
ssh_key = super().save(commit=False)
|
|
ssh_key.user = self.user
|
|
if commit:
|
|
ssh_key.save()
|
|
return ssh_key
|
|
|
|
class Meta:
|
|
model = UserSSHKey
|
|
fields = ("keyname", "keypublic")
|
|
|
|
|
|
class ApiTokenForm(Form):
|
|
name = CharField(label=_("Name"), max_length=64, help_text=_("What uses it, such as a script or a host"))
|
|
days = TypedChoiceField(
|
|
label=_("Expires"), coerce=int, empty_value=None, required=False, initial=90,
|
|
choices=[(30, _("in 30 days")), (90, _("in 90 days")), (365, _("in a year")), ("", _("never"))],
|
|
)
|
|
|
|
|
|
class EmailOTPForm(Form):
|
|
email = EmailField(label=_("Email"))
|
|
|
|
|
|
class BoundedUsernameMixin:
|
|
"""Django's login form gives the username its max_length after the field
|
|
is built, so a longer one is not refused and reaches authentication;
|
|
django-axes then cannot store it (PostgreSQL refuses it: 500)."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
username = self.fields["username"]
|
|
username.validators.append(MaxLengthValidator(username.max_length))
|
|
|
|
|
|
class LoginForm(BoundedUsernameMixin, AuthenticationForm):
|
|
pass
|