1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 03:36:48 +00:00

style(accounts): the password pages have the account forms' layout

The page where a user changes their own password and the one where an
administrator sets a user's password shared an old card: a horizontal
form, a blue Cancel and a green Change. The administrator's had Django's
"Password-based authentication: Enabled/Disabled" with a paragraph about
other backends, and its password fields stayed while Disabled was chosen.

Both pages now have the user and group forms' layout, each its own
template. The administrator chooses how the user signs in, With a
password or Without a password (such as LDAP), and the password fields
and "must change it" show only with a password; Cancel goes back to the
user. A user's own page says that their other sessions end, or, when
they must change it, that they choose one before going on, without a
Cancel. A refused password is said at its field only, without the
general "Wrong Data Provided". The section styles of these forms move
into webvirtcloud.css.
This commit is contained in:
catborise 2026-11-04 22:53:40 +03:00
parent 61979be731
commit 84dfbd5884
14 changed files with 323 additions and 193 deletions

View file

@ -1,34 +1,39 @@
{% extends "base.html" %}
{% load django_bootstrap5 %}
{% load i18n %}
{% load bootstrap_icons %}
{% block title %}{%trans "Change Password" %}{% endblock title %}
{% block title %}{% trans "Change Password" %}{% endblock title %}
{% block page_heading %}{% trans "Change Password" %}{% endblock page_heading %}
{% block content %}
<div class="row">
<div class="offset-lg-2 col-lg-8">
<div class="card">
<div class="card-header">
<h4 class="card-title">{%trans "Change Password" %}: {{ user }}</h4>
{# a user changes their own password; one who must (a new or reset account) cannot go elsewhere first #}
<form id="password-change" method="post">
{% csrf_token %}
{% bootstrap_form_errors form type="non_fields" %}
<section class="form-section">
<div class="row g-3">
<div class="col-lg-3">
<h2 class="h6 fw-semibold mb-1 text-break">{{ request.user.username }}</h2>
<p class="small text-body-secondary mb-0">
{% if must_change %}{% trans "Choose a new password before you go on." %}{% else %}{% trans "You stay signed in here; your other sessions end." %}{% endif %}
</p>
</div>
<div class="card-body">
<form method="post" id="password-change">
{% csrf_token %}
{% bootstrap_form form layout='horizontal' horizontal_label_class='col-sm-4' horizontal_field_class='col-sm-8' horizontal_field_offset_class='offset-sm-4' %}
</form>
</div>
<div class="card-footer">
<div class="float-end">
<a class="btn btn-primary" href="#" data-history-back>{% bs_icon 'x' %}
{% trans "Cancel" %}</a>
<button type="submit" form="password-change" class="btn btn-success">
{% bs_icon 'check2' %} {% trans "Change" %}
</button>
<div class="col-lg-9 form-section-fields">
<div class="row g-3">
<div class="col-sm-6">{% bootstrap_field form.old_password %}</div>
<div class="w-100 m-0"></div>
<div class="col-sm-6">{% bootstrap_field form.new_password1 %}</div>
<div class="col-sm-6">{% bootstrap_field form.new_password2 show_help=False %}</div>
</div>
</div>
</div>
</section>
<div class="form-section d-flex justify-content-end gap-2">
{% if not must_change %}<a class="btn btn-outline-secondary" href="{% url 'accounts:profile' %}">{% trans "Cancel" %}</a>{% endif %}
<button type="submit" class="btn btn-primary">{% trans "Change password" %}</button>
</div>
</div>
{% endblock content %}
</form>
{% endblock content %}

View file

@ -34,6 +34,19 @@ class ChangePasswordPermissionTestCase(TestCase):
self.client.force_login(user)
self.assertEqual(self.client.get(reverse("accounts:change_password")).status_code, 403)
def test_a_user_who_must_change_it_has_no_cancel(self):
from accounts.models import UserAttributes
user = get_user_model().objects.create_user("forced", password="pw")
self.client.force_login(user)
page = self.client.get(reverse("accounts:change_password"))
self.assertContains(page, "your other sessions end")
self.assertContains(page, f'href="{reverse("accounts:profile")}">Cancel')
UserAttributes.objects.create(user=user, must_change_password=True)
page = self.client.get(reverse("accounts:change_password"))
self.assertContains(page, "Choose a new password before you go on.")
self.assertNotContains(page, f'href="{reverse("accounts:profile")}">Cancel')
def test_a_user_without_a_local_password_cannot_change_it(self):
user = get_user_model().objects.create_user("ldap-user") # as django-auth-ldap makes them
self.client.force_login(user)

View file

@ -212,7 +212,7 @@ def change_password(request):
messages.success(request, _("Password Changed"))
return redirect("accounts:profile")
return render(request, "accounts/change_password_form.html", {"form": form})
return render(request, "accounts/change_password_form.html", {"form": form, "must_change": must_change})
@superuser_only

View file

@ -232,6 +232,8 @@ class AdminPasswordForm(AdminPasswordChangeForm):
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
# Django's "Password-based authentication: Enabled/Disabled", said as how the user signs in
self.fields["usable_password"].choices = [("true", _("With a password")), ("false", _("Without a password"))]
def clean(self):
cleaned = super().clean()

View file

@ -7,39 +7,30 @@
{% block page_heading %}{{ title }}{% endblock page_heading %}
{% block style %}
<style>
/* as the user form: what a section is about on the left, its fields on the right */
.group-form-section { border-top: 1px solid var(--bs-border-color); padding: 1.5rem 0; }
.group-form-section:first-of-type { border-top: 0; padding-top: .5rem; }
.group-form-fields { max-width: 52rem; }
</style>
{% endblock style %}
{% block content %}
<form id="create-update" method="post">
{% csrf_token %}
{% bootstrap_form_errors form type="non_fields" %}
<section class="group-form-section">
<section class="form-section">
<div class="row g-3">
<div class="col-lg-3">
<h2 class="h6 fw-semibold mb-1">{% trans "Group" %}</h2>
<p class="small text-body-secondary mb-0">{% trans "Users who share a set of permissions." %}</p>
</div>
<div class="col-lg-9 group-form-fields">
<div class="col-lg-9 form-section-fields">
<div class="row"><div class="col-sm-6">{% bootstrap_field form.name %}</div></div>
</div>
</div>
</section>
<section class="group-form-section">
<section class="form-section">
<div class="row g-3">
<div class="col-lg-3">
<h2 class="h6 fw-semibold mb-1">{% trans "Permissions" %}</h2>
<p class="small text-body-secondary mb-0">{% trans "Each member has these permissions besides their own." %}</p>
</div>
<div class="col-lg-9 group-form-fields">
<div class="col-lg-9 form-section-fields">
<div class="border-top">
{% for box, help in form.permission_rows %}
<div class="form-check form-switch border-bottom py-2 ps-5">
@ -57,13 +48,13 @@
</div>
</section>
<section class="group-form-section">
<section class="form-section">
<div class="row g-3">
<div class="col-lg-3">
<h2 class="h6 fw-semibold mb-1">{% trans "Members" %}</h2>
<p class="small text-body-secondary mb-0">{% trans "Who gets these permissions." %}</p>
</div>
<fieldset class="col-lg-9 group-form-fields">
<fieldset class="col-lg-9 form-section-fields">
<legend class="visually-hidden">{% trans "Members" %}</legend>
<input id="filter" class="form-control form-control-sm mb-2" style="max-width: 20rem" type="search" placeholder="{% trans "Search" %}" aria-label="{% trans "Search users" %}" data-forget>
{# the members first, then everyone else; each by username #}
@ -77,7 +68,7 @@
</div>
</section>
<div class="group-form-section d-flex justify-content-end gap-2">
<div class="form-section d-flex justify-content-end gap-2">
<a class="btn btn-outline-secondary" href="{% url 'admin:group_list' %}">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-primary">{% if group %}{% trans "Save changes" %}{% else %}{% trans "Create group" %}{% endif %}</button>
</div>

View file

@ -7,23 +7,13 @@
{% block page_heading %}{{ title }}{% endblock page_heading %}
{% block style %}
<style>
/* each section: what it is about on the left, its fields on the right */
.user-form-section { border-top: 1px solid var(--bs-border-color); padding: 1.5rem 0; }
.user-form-section:first-of-type { border-top: 0; padding-top: .5rem; }
.user-form-fields { max-width: 52rem; }
.user-form-role:has(input:checked) { border-color: var(--bs-primary) !important; box-shadow: inset 0 0 0 1px var(--bs-primary); }
</style>
{% endblock style %}
{% block content %}
{# who the user is, what they manage, what they may use, then what more they may do #}
<form id="create-update" method="post">
{% csrf_token %}
{% bootstrap_form_errors user_form type="non_fields" %}
<section class="user-form-section">
<section class="form-section">
<div class="row g-3">
<div class="col-lg-3">
<h2 class="h6 fw-semibold mb-1">{% trans "Account" %}</h2>
@ -33,7 +23,7 @@
<p class="small text-body-secondary mb-0">{% trans "What the user signs in with." %}</p>
{% endif %}
</div>
<div class="col-lg-9 user-form-fields">
<div class="col-lg-9 form-section-fields">
<div class="row g-3">
<div class="col-sm-6">{% bootstrap_field user_form.username %}</div>
<div class="col-sm-6">
@ -61,20 +51,20 @@
</div>
</section>
<section class="user-form-section">
<section class="form-section">
<div class="row g-3">
<div class="col-lg-3">
<h2 class="h6 fw-semibold mb-1" id="role-heading">{% trans "Role" %}</h2>
<p class="small text-body-secondary mb-0">{% trans "What the user manages." %}</p>
</div>
<div class="col-lg-9 user-form-fields">
<div class="col-lg-9 form-section-fields">
{% trans "Uses the VMs given to them, within their quota." as user_help %}
{% trans "May change and delete the template VMs given to them." as editor_help %}
{% trans "Manages the panel: every permission, no quota." as superuser_help %}
<div class="row row-cols-1 row-cols-md-3 g-2" role="radiogroup" aria-labelledby="role-heading">
{% for choice in user_form.role %}
<div class="col">
<label class="user-form-role border rounded p-3 d-flex gap-2 h-100">
<label class="form-choice border rounded p-3 d-flex gap-2 h-100">
<input class="form-check-input flex-shrink-0 mt-1" type="radio" name="role" value="{{ choice.data.value }}"{% if choice.data.selected %} checked{% endif %}>
<span><span class="d-block fw-semibold">{{ choice.choice_label }}</span>
<span class="small text-body-secondary">{% if choice.data.value == "superuser" %}{{ superuser_help }}{% elif choice.data.value == "template_editor" %}{{ editor_help }}{% else %}{{ user_help }}{% endif %}</span></span>
@ -89,17 +79,17 @@
{# user_form.js hides the sections a superuser does not need while Superuser is chosen #}
{% with superuser=user_form.role.value %}
<p id="superuser-note" class="user-form-section small text-body-secondary mb-0"{% if superuser != "superuser" %} hidden{% endif %}>
<p id="superuser-note" class="form-section small text-body-secondary mb-0"{% if superuser != "superuser" %} hidden{% endif %}>
{% trans "A superuser has every permission and no quota: there is nothing more to set." %}
</p>
<section class="user-form-section" data-not-superuser{% if superuser == "superuser" %} hidden{% endif %}>
<section class="form-section" data-not-superuser{% if superuser == "superuser" %} hidden{% endif %}>
<div class="row g-3">
<div class="col-lg-3">
<h2 class="h6 fw-semibold mb-1">{% trans "Quota" %}</h2>
<p class="small text-body-secondary mb-0">{% trans "The most the user's VMs may have in total." %}</p>
</div>
<div class="col-lg-9 user-form-fields">
<div class="col-lg-9 form-section-fields">
<div class="row row-cols-2 row-cols-md-4 g-3">
{% for field, unlimited, unit in attributes_form.quota_rows %}
<div class="col">
@ -122,13 +112,13 @@
</div>
</section>
<section class="user-form-section" data-not-superuser{% if superuser == "superuser" %} hidden{% endif %}>
<section class="form-section" data-not-superuser{% if superuser == "superuser" %} hidden{% endif %}>
<div class="row g-3">
<div class="col-lg-3">
<h2 class="h6 fw-semibold mb-1">{% trans "Groups and permissions" %}</h2>
<p class="small text-body-secondary mb-0">{% trans "The permissions of the user's groups apply too." %}</p>
</div>
<div class="col-lg-9 user-form-fields">
<div class="col-lg-9 form-section-fields">
<fieldset class="mb-3">
<legend class="visually-hidden">{% trans "Groups" %}</legend>
<div class="d-flex flex-wrap gap-2">
@ -165,7 +155,7 @@
</section>
{% endwith %}
<div class="user-form-section d-flex justify-content-end gap-2">
<div class="form-section d-flex justify-content-end gap-2">
<a class="btn btn-outline-secondary" href="{% url 'admin:user_list' %}">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-primary">{% if edited_user %}{% trans "Save changes" %}{% else %}{% trans "Create user" %}{% endif %}</button>
</div>

View file

@ -0,0 +1,52 @@
{% extends "base.html" %}
{% load django_bootstrap5 %}
{% load i18n %}
{% block title %}{% trans "Change Password" %}{% endblock %}
{% block page_heading %}{% trans "Change Password" %}{% endblock page_heading %}
{% block content %}
{# an administrator sets another user's password, or takes it away (sign-in by LDAP, for one) #}
<form id="password-change" method="post">
{% csrf_token %}
{% bootstrap_form_errors form type="non_fields" %}
<section class="form-section">
<div class="row g-3">
<div class="col-lg-3">
<h2 class="h6 fw-semibold mb-1 text-break">{{ edited_user.username }}</h2>
<p class="small text-body-secondary mb-0">{% trans "How the user signs in." %}</p>
</div>
<div class="col-lg-9 form-section-fields">
{% trans "Signs in with the password set here." as with_help %}
{% trans "Signs in another way, such as LDAP. The current password is removed." as without_help %}
<div class="row row-cols-1 row-cols-md-2 g-2 mb-3" role="radiogroup" aria-label="{% trans "How the user signs in." %}">
{% for choice in form.usable_password %}
<div class="col">
<label class="form-choice border rounded p-3 d-flex gap-2 h-100">
<input class="form-check-input flex-shrink-0 mt-1" type="radio" name="usable_password" value="{{ choice.data.value }}"{% if choice.data.selected %} checked{% endif %}>
<span><span class="d-block fw-semibold">{{ choice.choice_label }}</span>
<span class="small text-body-secondary">{% if choice.data.value == "true" %}{{ with_help }}{% else %}{{ without_help }}{% endif %}</span></span>
</label>
</div>
{% endfor %}
</div>
{# hidden while Without a password is chosen (webvirtcloud.css) #}
<div class="with-password">
<div class="row g-3">
<div class="col-sm-6">{% bootstrap_field form.password1 %}</div>
<div class="col-sm-6">{% bootstrap_field form.password2 show_help=False %}</div>
</div>
{% bootstrap_field form.must_change_password %}
</div>
</div>
</div>
</section>
<div class="form-section d-flex justify-content-end gap-2">
<a class="btn btn-outline-secondary" href="{% url 'admin:user_update' edited_user.id %}">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-primary">{% trans "Change password" %}</button>
</div>
</form>
{% endblock content %}

View file

@ -57,6 +57,17 @@ class UserPagesTests(TestCase):
self.assertIn("Manages the panel: every permission, no quota.", page)
self.assertNotIn("log into this admin site", page)
def test_the_password_page_says_how_the_user_signs_in(self):
url = reverse("admin:user_update_password", args=[self.alice.pk])
page = self.client.get(url).content.decode()
self.assertIn("With a password", page)
self.assertIn("Signs in another way, such as LDAP. The current password is removed.", page)
self.assertIn(f'href="{reverse("admin:user_update", args=[self.alice.pk])}">Cancel', page)
# a mismatch is said at its field only
response = self.client.post(url, {"usable_password": "true", "password1": "Ab-123456x", "password2": "other"})
self.assertContains(response, "The two password fields didn’t match.")
self.assertNotIn("Wrong Data Provided", self.messages(response))
def test_a_refused_choice_is_said_beside_its_field(self):
url = reverse("admin:user_update", args=[self.alice.pk])
response = self.client.post(url, {"username": "up-alice", "is_active": "on", "groups": "99999", "user_permissions": "99999",

View file

@ -209,19 +209,10 @@ def user_update_password(request, pk):
request, _("Password changed for %(user)s") % {"user": user.username}
)
return redirect("admin:user_list")
else:
messages.error(request, _("Wrong Data Provided"))
else:
form = forms.AdminPasswordForm(user)
return render(
request,
"accounts/change_password_form.html",
{
"form": form,
"user": user.username,
},
)
return render(request, "admin/user_password_form.html", {"form": form, "edited_user": user})
@superuser_only

Binary file not shown.

View file

@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-11-04 19:38+0000\n"
"POT-Creation-Date: 2026-11-04 19:51+0000\n"
"PO-Revision-Date: 2020-11-04 12:00+0000\n"
"Last-Translator: catborise <muhammetalisag@gmail.com>, 2020\n"
"Language-Team: Turkish (https://www.transifex.com/catborise/teams/110663/tr/)\n"
@ -99,7 +99,7 @@ msgstr "bir yıl içinde"
#: accounts/forms.py:96 accounts/templates/account.html:29
#: accounts/templates/api_token_list.html:19
#: accounts/templates/api_token_list.html:20 accounts/templates/profile.html:25
#: admin/templates/admin/user_form.html:31
#: admin/templates/admin/user_form.html:21
#: admin/templates/admin/user_list.html:56
#: operations/templates/operations/jobs.html:15
#: operations/templatetags/operations.py:91
@ -180,7 +180,7 @@ msgid "Blocked"
msgstr "Engelli"
#: accounts/templates/account.html:29 accounts/templates/profile.html:25
#: admin/templates/admin/user_form.html:31
#: admin/templates/admin/user_form.html:21
#: admin/templates/admin/user_list.html:56
msgid "Last login"
msgstr "Son oturum"
@ -255,16 +255,27 @@ msgstr "Kullanıcıdan kaldır"
msgid "No VMs."
msgstr "Sanal makine yok."
#: accounts/templates/accounts/change_password_form.html:5
#: accounts/templates/accounts/change_password_form.html:7
#: accounts/templates/accounts/change_password_form.html:14
#: accounts/templates/profile.html:35
#: admin/templates/admin/user_password_form.html:5
#: admin/templates/admin/user_password_form.html:7
msgid "Change Password"
msgstr "Parola Değiştir"
#: accounts/templates/accounts/change_password_form.html:25
#: accounts/templates/accounts/change_password_form.html:20
msgid "Choose a new password before you go on."
msgstr "Devam etmeden önce yeni bir parola seçin."
#: accounts/templates/accounts/change_password_form.html:20
msgid "You stay signed in here; your other sessions end."
msgstr "Burada oturumunuz açık kalır; diğer oturumlarınız kapanır."
#: accounts/templates/accounts/change_password_form.html:35
#: accounts/templates/accounts/email_otp_form.html:25
#: admin/templates/admin/group_form.html:81
#: admin/templates/admin/user_form.html:169
#: admin/templates/admin/group_form.html:72
#: admin/templates/admin/user_form.html:159
#: admin/templates/admin/user_password_form.html:48
#: computes/templates/computes/form.html:45
#: console/templates/console-vnc.html:86
#: instances/templates/add_instance_cdrom.html:38
@ -297,9 +308,11 @@ msgstr "Parola Değiştir"
msgid "Cancel"
msgstr "İptal"
#: accounts/templates/accounts/change_password_form.html:27
msgid "Change"
msgstr "Değiştir"
#: accounts/templates/accounts/change_password_form.html:36
#: admin/templates/admin/user_form.html:34
#: admin/templates/admin/user_password_form.html:49
msgid "Change password"
msgstr "Parolayı değiştir"
#: accounts/templates/accounts/email/otp.html:2
#, python-format
@ -355,7 +368,7 @@ msgid "Enter username"
msgstr "Kullanıcı adını girin"
#: accounts/templates/accounts/otp_login.html:37
#: accounts/templates/login.html:36 admin/templates/admin/user_form.html:41
#: accounts/templates/login.html:36 admin/templates/admin/user_form.html:31
#: console/templates/console-vnc.html:82 instances/forms.py:28
#: instances/templates/instances/access_tab.html:53
msgid "Password"
@ -670,14 +683,14 @@ msgstr "Listelerde tüm sanal makineleri ve sunucuları salt okunur görme"
msgid "User"
msgstr "Kullanıcı"
#: admin/forms.py:80 admin/templates/admin/group_form.html:39
#: admin/forms.py:80 admin/templates/admin/group_form.html:30
msgid "Permissions"
msgstr "İzinler"
#: admin/forms.py:87 admin/templates/admin/group_list.html:6
#: admin/templates/admin/group_list.html:8
#: admin/templates/admin/group_list.html:24
#: admin/templates/admin/user_form.html:133 templates/navbar.html:33
#: admin/templates/admin/user_form.html:123 templates/navbar.html:33
msgid "Groups"
msgstr "Gruplar"
@ -728,34 +741,42 @@ msgstr "Bir sınır girin ya da Sınır yok'u seçin."
msgid "The user must change it at their next login"
msgstr "Kullanıcı bir sonraki girişinde onu değiştirmek zorundadır"
#: admin/forms.py:240
#: admin/forms.py:236
msgid "With a password"
msgstr "Parolayla"
#: admin/forms.py:236
msgid "Without a password"
msgstr "Parolasız"
#: admin/forms.py:242
msgid "A user without a password cannot be made to change it"
msgstr "Parolası olmayan bir kullanıcıya parola değiştirme zorunluluğu konamaz"
#: admin/templates/admin/group_form.html:27
#: admin/templates/admin/group_form.html:18
msgid "Group"
msgstr "Grup"
#: admin/templates/admin/group_form.html:28
#: admin/templates/admin/group_form.html:19
msgid "Users who share a set of permissions."
msgstr "Aynı izinleri paylaşan kullanıcılar."
#: admin/templates/admin/group_form.html:40
#: admin/templates/admin/group_form.html:31
msgid "Each member has these permissions besides their own."
msgstr "Her üye kendi izinlerine ek olarak bu izinlere sahiptir."
#: admin/templates/admin/group_form.html:63
#: admin/templates/admin/group_form.html:67
#: admin/templates/admin/group_form.html:54
#: admin/templates/admin/group_form.html:58
#: interfaces/templates/interface.html:113
#: interfaces/templates/interfaces.html:52
msgid "Members"
msgstr "Üyeler"
#: admin/templates/admin/group_form.html:64
#: admin/templates/admin/group_form.html:55
msgid "Who gets these permissions."
msgstr "Bu izinleri kimlerin alacağı."
#: admin/templates/admin/group_form.html:68
#: admin/templates/admin/group_form.html:59
#: admin/templates/admin/group_list.html:25 admin/templates/admin/logs.html:20
#: admin/templates/admin/logs.html:26
#: admin/templates/admin/tombstone_list.html:20
@ -766,22 +787,22 @@ msgstr "Bu izinleri kimlerin alacağı."
msgid "Search"
msgstr "Ara"
#: admin/templates/admin/group_form.html:68
#: admin/templates/admin/group_form.html:59
#: admin/templates/admin/user_list.html:20
msgid "Search users"
msgstr "Kullanıcı ara"
#: admin/templates/admin/group_form.html:74
#: admin/templates/admin/group_form.html:65
#: admin/templates/admin/user_list.html:86
msgid "No user matches the search."
msgstr "Aramayla eşleşen kullanıcı yok."
#: admin/templates/admin/group_form.html:82
#: admin/templates/admin/user_form.html:170
#: admin/templates/admin/group_form.html:73
#: admin/templates/admin/user_form.html:160
msgid "Save changes"
msgstr "Değişiklikleri kaydet"
#: admin/templates/admin/group_form.html:82
#: admin/templates/admin/group_form.html:73
msgid "Create group"
msgstr "Grup oluştur"
@ -790,7 +811,7 @@ msgid "New group"
msgstr "Yeni grup"
#: admin/templates/admin/group_list.html:19
#: admin/templates/admin/user_form.html:141
#: admin/templates/admin/user_form.html:131
msgid "There are no groups yet."
msgstr "Henüz grup yok."
@ -883,16 +904,16 @@ msgstr "Sonuç"
msgid "IP"
msgstr "IP"
#: admin/templates/admin/logs.html:56 admin/views.py:291 instances/states.py:19
#: admin/templates/admin/logs.html:56 admin/views.py:282 instances/states.py:19
#: logs/views.py:17
msgid "Unknown"
msgstr "Bilinmeyen"
#: admin/templates/admin/logs.html:56 admin/views.py:291 logs/views.py:17
#: admin/templates/admin/logs.html:56 admin/views.py:282 logs/views.py:17
msgid "Done"
msgstr "Tamamlandı"
#: admin/templates/admin/logs.html:56 admin/views.py:291 logs/views.py:17
#: admin/templates/admin/logs.html:56 admin/views.py:282 logs/views.py:17
#: operations/templates/operations/job.html:107
msgid "Failed"
msgstr "Başarısız"
@ -944,88 +965,84 @@ msgstr "sona erer"
msgid "No removed VM matches the search."
msgstr "Aramayla eşleşen kaldırılmış sanal makine yok."
#: admin/templates/admin/user_form.html:29
#: admin/templates/admin/user_form.html:19
msgid "Account"
msgstr "Hesap"
#: admin/templates/admin/user_form.html:33
#: admin/templates/admin/user_form.html:23
msgid "What the user signs in with."
msgstr "Kullanıcının oturum açtığı bilgiler."
#: admin/templates/admin/user_form.html:43
#: admin/templates/admin/user_form.html:33
msgid "Set"
msgstr "Ayarla"
#: admin/templates/admin/user_form.html:43
#: admin/templates/admin/user_form.html:33
msgid "Not set"
msgstr "Ayarlanmadı"
#: admin/templates/admin/user_form.html:44
msgid "Change password"
msgstr "Parolayı değiştir"
#: admin/templates/admin/user_form.html:57
#: admin/templates/admin/user_form.html:47
msgid "May sign in"
msgstr "Oturum açabilir"
#: admin/templates/admin/user_form.html:58
#: admin/templates/admin/user_form.html:48
msgid "Turn off to block the user while keeping the account."
msgstr "Hesabı koruyarak kullanıcıyı engellemek için kapatın."
#: admin/templates/admin/user_form.html:67
#: admin/templates/admin/user_form.html:57
msgid "Role"
msgstr "Rol"
#: admin/templates/admin/user_form.html:68
#: admin/templates/admin/user_form.html:58
msgid "What the user manages."
msgstr "Kullanıcının neyi yönettiği."
#: admin/templates/admin/user_form.html:71
#: admin/templates/admin/user_form.html:61
msgid "Uses the VMs given to them, within their quota."
msgstr "Kotası dahilinde kendisine verilen sanal makineleri kullanır."
#: admin/templates/admin/user_form.html:72
#: admin/templates/admin/user_form.html:62
msgid "May change and delete the template VMs given to them."
msgstr "Kendisine verilen şablon sanal makineleri değiştirebilir ve silebilir."
#: admin/templates/admin/user_form.html:73
#: admin/templates/admin/user_form.html:63
msgid "Manages the panel: every permission, no quota."
msgstr "Paneli yönetir: tüm izinler, kota yok."
#: admin/templates/admin/user_form.html:93
#: admin/templates/admin/user_form.html:83
msgid ""
"A superuser has every permission and no quota: there is nothing more to set."
msgstr ""
"Süperkullanıcının tüm izinleri vardır ve kotası yoktur: ayarlanacak başka bir"
" şey yok."
#: admin/templates/admin/user_form.html:99
#: admin/templates/admin/user_form.html:89
msgid "Quota"
msgstr "Kota"
#: admin/templates/admin/user_form.html:100
#: admin/templates/admin/user_form.html:90
msgid "The most the user's VMs may have in total."
msgstr "Kullanıcının sanal makinelerinin toplamda alabileceği en fazla kaynak."
#: admin/templates/admin/user_form.html:128
#: admin/templates/admin/user_form.html:118
msgid "Groups and permissions"
msgstr "Gruplar ve izinler"
#: admin/templates/admin/user_form.html:129
#: admin/templates/admin/user_form.html:119
msgid "The permissions of the user's groups apply too."
msgstr "Kullanıcının gruplarının izinleri de geçerlidir."
#: admin/templates/admin/user_form.html:147
#: admin/templates/admin/user_form.html:137
msgid "Own permissions"
msgstr "Kendi izinleri"
#: admin/templates/admin/user_form.html:148
#: admin/templates/admin/user_form.html:138
msgid "Off grants nothing here; it takes away nothing a group grants."
msgstr ""
"Kapalı olması burada hiçbir şey vermez; bir grubun verdiği hiçbir şeyi de "
"geri almaz."
#: admin/templates/admin/user_form.html:170
#: admin/templates/admin/user_form.html:160
msgid "Create user"
msgstr "Kullanıcı oluştur"
@ -1075,6 +1092,19 @@ msgstr "Engelle"
msgid "Unblock"
msgstr "Engeli Kaldır"
#: admin/templates/admin/user_password_form.html:19
#: admin/templates/admin/user_password_form.html:24
msgid "How the user signs in."
msgstr "Kullanıcının nasıl oturum açtığı."
#: admin/templates/admin/user_password_form.html:22
msgid "Signs in with the password set here."
msgstr "Burada belirlenen parolayla oturum açar."
#: admin/templates/admin/user_password_form.html:23
msgid "Signs in another way, such as LDAP. The current password is removed."
msgstr "LDAP gibi başka bir yolla oturum açar. Mevcut parola kaldırılır."
#: admin/views.py:57
msgid "Create Group"
msgstr "Grup Oluştur"
@ -1104,26 +1134,22 @@ msgstr "Kullanıcı Güncelle"
msgid "Password changed for %(user)s"
msgstr "%(user)s parolası değiştirildi"
#: admin/views.py:213
msgid "Wrong Data Provided"
msgstr "Hatalı Veri"
#: admin/views.py:242
#: admin/views.py:233
msgid "Their access to VMs, SSH keys and settings go; the VMs themselves stay."
msgstr ""
"Sanal makinelere erişimi, SSH anahtarları ve ayarları silinir; sanal "
"makinelerin kendisi kalır."
#: admin/views.py:337
#: admin/views.py:328
msgid "deleted user"
msgstr "silinmiş kullanıcı"
#: admin/views.py:348
#: admin/views.py:339
#, python-format
msgid "Removed VM %(name)s deleted"
msgstr "Kaldırılan sanal makine %(name)s silindi"
#: admin/views.py:355
#: admin/views.py:346
msgid "Its owners no longer get the VM back if its UUID shows up again."
msgstr ""
"UUID'si yeniden görünse bile sahipleri sanal makineyi artık geri almaz."

Binary file not shown.

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-11-04 19:38+0000\n"
"POT-Creation-Date: 2026-11-04 19:51+0000\n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
@ -96,7 +96,7 @@ msgstr "一年后"
#: accounts/forms.py:96 accounts/templates/account.html:29
#: accounts/templates/api_token_list.html:19
#: accounts/templates/api_token_list.html:20 accounts/templates/profile.html:25
#: admin/templates/admin/user_form.html:31
#: admin/templates/admin/user_form.html:21
#: admin/templates/admin/user_list.html:56
#: operations/templates/operations/jobs.html:15
#: operations/templatetags/operations.py:91
@ -177,7 +177,7 @@ msgid "Blocked"
msgstr "已禁用"
#: accounts/templates/account.html:29 accounts/templates/profile.html:25
#: admin/templates/admin/user_form.html:31
#: admin/templates/admin/user_form.html:21
#: admin/templates/admin/user_list.html:56
msgid "Last login"
msgstr "上次登录"
@ -252,16 +252,27 @@ msgstr "从用户移除"
msgid "No VMs."
msgstr "没有虚拟机。"
#: accounts/templates/accounts/change_password_form.html:5
#: accounts/templates/accounts/change_password_form.html:7
#: accounts/templates/accounts/change_password_form.html:14
#: accounts/templates/profile.html:35
#: admin/templates/admin/user_password_form.html:5
#: admin/templates/admin/user_password_form.html:7
msgid "Change Password"
msgstr "修改密码"
#: accounts/templates/accounts/change_password_form.html:25
#: accounts/templates/accounts/change_password_form.html:20
msgid "Choose a new password before you go on."
msgstr "请先设置新密码再继续。"
#: accounts/templates/accounts/change_password_form.html:20
msgid "You stay signed in here; your other sessions end."
msgstr "您在此处保持登录;您的其他会话将结束。"
#: accounts/templates/accounts/change_password_form.html:35
#: accounts/templates/accounts/email_otp_form.html:25
#: admin/templates/admin/group_form.html:81
#: admin/templates/admin/user_form.html:169
#: admin/templates/admin/group_form.html:72
#: admin/templates/admin/user_form.html:159
#: admin/templates/admin/user_password_form.html:48
#: computes/templates/computes/form.html:45
#: console/templates/console-vnc.html:86
#: instances/templates/add_instance_cdrom.html:38
@ -294,9 +305,11 @@ msgstr "修改密码"
msgid "Cancel"
msgstr "取消"
#: accounts/templates/accounts/change_password_form.html:27
msgid "Change"
msgstr "修改"
#: accounts/templates/accounts/change_password_form.html:36
#: admin/templates/admin/user_form.html:34
#: admin/templates/admin/user_password_form.html:49
msgid "Change password"
msgstr "修改密码"
#: accounts/templates/accounts/email/otp.html:2
#, python-format
@ -352,7 +365,7 @@ msgid "Enter username"
msgstr "输入用户名"
#: accounts/templates/accounts/otp_login.html:37
#: accounts/templates/login.html:36 admin/templates/admin/user_form.html:41
#: accounts/templates/login.html:36 admin/templates/admin/user_form.html:31
#: console/templates/console-vnc.html:82 instances/forms.py:28
#: instances/templates/instances/access_tab.html:53
msgid "Password"
@ -653,14 +666,14 @@ msgstr "在列表中查看所有虚拟机和主机(只读)"
msgid "User"
msgstr "用户"
#: admin/forms.py:80 admin/templates/admin/group_form.html:39
#: admin/forms.py:80 admin/templates/admin/group_form.html:30
msgid "Permissions"
msgstr "权限"
#: admin/forms.py:87 admin/templates/admin/group_list.html:6
#: admin/templates/admin/group_list.html:8
#: admin/templates/admin/group_list.html:24
#: admin/templates/admin/user_form.html:133 templates/navbar.html:33
#: admin/templates/admin/user_form.html:123 templates/navbar.html:33
msgid "Groups"
msgstr "用户组"
@ -710,34 +723,42 @@ msgstr "请输入限制值,或选择“不限”。"
msgid "The user must change it at their next login"
msgstr "用户必须在下次登录时修改密码"
#: admin/forms.py:240
#: admin/forms.py:236
msgid "With a password"
msgstr "使用密码"
#: admin/forms.py:236
msgid "Without a password"
msgstr "不使用密码"
#: admin/forms.py:242
msgid "A user without a password cannot be made to change it"
msgstr "没有密码的用户无法被要求修改密码"
#: admin/templates/admin/group_form.html:27
#: admin/templates/admin/group_form.html:18
msgid "Group"
msgstr "用户组"
#: admin/templates/admin/group_form.html:28
#: admin/templates/admin/group_form.html:19
msgid "Users who share a set of permissions."
msgstr "共享同一组权限的用户。"
#: admin/templates/admin/group_form.html:40
#: admin/templates/admin/group_form.html:31
msgid "Each member has these permissions besides their own."
msgstr "每个成员除自身权限外还拥有这些权限。"
#: admin/templates/admin/group_form.html:63
#: admin/templates/admin/group_form.html:67
#: admin/templates/admin/group_form.html:54
#: admin/templates/admin/group_form.html:58
#: interfaces/templates/interface.html:113
#: interfaces/templates/interfaces.html:52
msgid "Members"
msgstr "成员"
#: admin/templates/admin/group_form.html:64
#: admin/templates/admin/group_form.html:55
msgid "Who gets these permissions."
msgstr "获得这些权限的用户。"
#: admin/templates/admin/group_form.html:68
#: admin/templates/admin/group_form.html:59
#: admin/templates/admin/group_list.html:25 admin/templates/admin/logs.html:20
#: admin/templates/admin/logs.html:26
#: admin/templates/admin/tombstone_list.html:20
@ -748,22 +769,22 @@ msgstr "获得这些权限的用户。"
msgid "Search"
msgstr "搜索"
#: admin/templates/admin/group_form.html:68
#: admin/templates/admin/group_form.html:59
#: admin/templates/admin/user_list.html:20
msgid "Search users"
msgstr "搜索用户"
#: admin/templates/admin/group_form.html:74
#: admin/templates/admin/group_form.html:65
#: admin/templates/admin/user_list.html:86
msgid "No user matches the search."
msgstr "没有与搜索匹配的用户。"
#: admin/templates/admin/group_form.html:82
#: admin/templates/admin/user_form.html:170
#: admin/templates/admin/group_form.html:73
#: admin/templates/admin/user_form.html:160
msgid "Save changes"
msgstr "保存更改"
#: admin/templates/admin/group_form.html:82
#: admin/templates/admin/group_form.html:73
msgid "Create group"
msgstr "创建用户组"
@ -772,7 +793,7 @@ msgid "New group"
msgstr "新建用户组"
#: admin/templates/admin/group_list.html:19
#: admin/templates/admin/user_form.html:141
#: admin/templates/admin/user_form.html:131
msgid "There are no groups yet."
msgstr "还没有用户组。"
@ -863,16 +884,16 @@ msgstr "结果"
msgid "IP"
msgstr "IP"
#: admin/templates/admin/logs.html:56 admin/views.py:291 instances/states.py:19
#: admin/templates/admin/logs.html:56 admin/views.py:282 instances/states.py:19
#: logs/views.py:17
msgid "Unknown"
msgstr "未知"
#: admin/templates/admin/logs.html:56 admin/views.py:291 logs/views.py:17
#: admin/templates/admin/logs.html:56 admin/views.py:282 logs/views.py:17
msgid "Done"
msgstr "完成"
#: admin/templates/admin/logs.html:56 admin/views.py:291 logs/views.py:17
#: admin/templates/admin/logs.html:56 admin/views.py:282 logs/views.py:17
#: operations/templates/operations/job.html:107
msgid "Failed"
msgstr "失败"
@ -922,84 +943,80 @@ msgstr "过期"
msgid "No removed VM matches the search."
msgstr "没有与搜索匹配的已移除虚拟机。"
#: admin/templates/admin/user_form.html:29
#: admin/templates/admin/user_form.html:19
msgid "Account"
msgstr "账户"
#: admin/templates/admin/user_form.html:33
#: admin/templates/admin/user_form.html:23
msgid "What the user signs in with."
msgstr "用户登录所用的信息。"
#: admin/templates/admin/user_form.html:43
#: admin/templates/admin/user_form.html:33
msgid "Set"
msgstr "已设置"
#: admin/templates/admin/user_form.html:43
#: admin/templates/admin/user_form.html:33
msgid "Not set"
msgstr "未设置"
#: admin/templates/admin/user_form.html:44
msgid "Change password"
msgstr "修改密码"
#: admin/templates/admin/user_form.html:57
#: admin/templates/admin/user_form.html:47
msgid "May sign in"
msgstr "可以登录"
#: admin/templates/admin/user_form.html:58
#: admin/templates/admin/user_form.html:48
msgid "Turn off to block the user while keeping the account."
msgstr "关闭后将禁用该用户,但保留账户。"
#: admin/templates/admin/user_form.html:67
#: admin/templates/admin/user_form.html:57
msgid "Role"
msgstr "角色"
#: admin/templates/admin/user_form.html:68
#: admin/templates/admin/user_form.html:58
msgid "What the user manages."
msgstr "用户可以管理的内容。"
#: admin/templates/admin/user_form.html:71
#: admin/templates/admin/user_form.html:61
msgid "Uses the VMs given to them, within their quota."
msgstr "在配额范围内使用分配给其的虚拟机。"
#: admin/templates/admin/user_form.html:72
#: admin/templates/admin/user_form.html:62
msgid "May change and delete the template VMs given to them."
msgstr "可修改和删除分配给他们的模板虚拟机。"
#: admin/templates/admin/user_form.html:73
#: admin/templates/admin/user_form.html:63
msgid "Manages the panel: every permission, no quota."
msgstr "管理面板:拥有所有权限,不受配额限制。"
#: admin/templates/admin/user_form.html:93
#: admin/templates/admin/user_form.html:83
msgid ""
"A superuser has every permission and no quota: there is nothing more to set."
msgstr "超级用户拥有所有权限且不受配额限制:无需再设置其他内容。"
#: admin/templates/admin/user_form.html:99
#: admin/templates/admin/user_form.html:89
msgid "Quota"
msgstr "配额"
#: admin/templates/admin/user_form.html:100
#: admin/templates/admin/user_form.html:90
msgid "The most the user's VMs may have in total."
msgstr "用户的虚拟机总共可使用的最大资源。"
#: admin/templates/admin/user_form.html:128
#: admin/templates/admin/user_form.html:118
msgid "Groups and permissions"
msgstr "用户组和权限"
#: admin/templates/admin/user_form.html:129
#: admin/templates/admin/user_form.html:119
msgid "The permissions of the user's groups apply too."
msgstr "用户所在用户组的权限同样适用。"
#: admin/templates/admin/user_form.html:147
#: admin/templates/admin/user_form.html:137
msgid "Own permissions"
msgstr "自身权限"
#: admin/templates/admin/user_form.html:148
#: admin/templates/admin/user_form.html:138
msgid "Off grants nothing here; it takes away nothing a group grants."
msgstr "关闭不会在此授予任何权限,也不会撤销用户组授予的权限。"
#: admin/templates/admin/user_form.html:170
#: admin/templates/admin/user_form.html:160
msgid "Create user"
msgstr "创建用户"
@ -1048,6 +1065,19 @@ msgstr "禁用"
msgid "Unblock"
msgstr "解除禁用"
#: admin/templates/admin/user_password_form.html:19
#: admin/templates/admin/user_password_form.html:24
msgid "How the user signs in."
msgstr "用户的登录方式。"
#: admin/templates/admin/user_password_form.html:22
msgid "Signs in with the password set here."
msgstr "使用此处设置的密码登录。"
#: admin/templates/admin/user_password_form.html:23
msgid "Signs in another way, such as LDAP. The current password is removed."
msgstr "通过其他方式(如 LDAP)登录。当前密码将被移除。"
#: admin/views.py:57
msgid "Create Group"
msgstr "创建用户组"
@ -1077,24 +1107,20 @@ msgstr "更新用户"
msgid "Password changed for %(user)s"
msgstr "已为 %(user)s 修改密码"
#: admin/views.py:213
msgid "Wrong Data Provided"
msgstr "提供的数据有误"
#: admin/views.py:242
#: admin/views.py:233
msgid "Their access to VMs, SSH keys and settings go; the VMs themselves stay."
msgstr "其对虚拟机、SSH 密钥和设置的访问将被移除;虚拟机本身保留。"
#: admin/views.py:337
#: admin/views.py:328
msgid "deleted user"
msgstr "已删除的用户"
#: admin/views.py:348
#: admin/views.py:339
#, python-format
msgid "Removed VM %(name)s deleted"
msgstr "已删除已移除的虚拟机 %(name)s"
#: admin/views.py:355
#: admin/views.py:346
msgid "Its owners no longer get the VM back if its UUID shows up again."
msgstr "若其 UUID 再次出现,其所有者将不再恢复对该虚拟机的所有权。"

View file

@ -281,3 +281,26 @@ button.vcpu-cell:disabled { opacity: .65; }
fieldset:not(:has(input[value="set"]:checked)) > .console-new-password {
display: none;
}
/* the account forms (user, group, password): each section says what it is
about on the left, its fields on the right, with a rule between sections */
.form-section {
border-top: 1px solid var(--bs-border-color);
padding: 1.5rem 0;
}
.form-section:first-of-type {
border-top: 0;
padding-top: .5rem;
}
.form-section-fields {
max-width: 52rem;
}
/* a choice drawn as a card (the user's role, how a user signs in) */
.form-choice:has(input:checked) {
border-color: var(--bs-primary) !important;
box-shadow: inset 0 0 0 1px var(--bs-primary);
}
/* a user's password fields: only while With a password is chosen */
.form-section-fields:has(input[name="usable_password"][value="false"]:checked) > .with-password {
display: none;
}