/
user_form.py
285 lines (253 loc) · 9.77 KB
/
user_form.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import (
password_validators_help_texts,
validate_password,
)
from django.utils.translation import gettext_lazy as _
if TYPE_CHECKING:
from typing import Any
from ...models import User
from ...models import Role
from ...utils.translation_utils import gettext_many_lazy as __
from ..custom_model_form import CustomModelForm
from .organization_field import OrganizationField
logger = logging.getLogger(__name__)
class UserForm(CustomModelForm):
"""
Form for creating and modifying user objects
"""
role = forms.ModelChoiceField(
queryset=Role.objects.filter(staff_role=False),
required=False,
label=_("Role"),
help_text=_("This grants the user all permissions of the selected role"),
)
staff_role = forms.ModelChoiceField(
queryset=Role.objects.filter(staff_role=True),
required=False,
label=_("Team"),
help_text=_("This grants the user all permissions of the selected team"),
)
password = forms.CharField(
widget=forms.PasswordInput,
validators=[validate_password],
help_text=password_validators_help_texts,
required=False,
)
send_activation_link = forms.BooleanField(
initial=True,
required=False,
label=_("Send activation link"),
help_text=__(
_(
"Select this option to create an inactive user account and send an activation link per email to the user."
),
_(
"This link allows the user to choose a password and activates the account after confirmation."
),
),
)
class Meta:
"""
This class contains additional meta configuration of the form class, see the :class:`django.forms.ModelForm`
for more information.
"""
#: The model of this :class:`django.forms.ModelForm`
model = get_user_model()
#: The fields of the model which should be handled by this form
fields = [
"username",
"first_name",
"last_name",
"email",
"is_staff",
"is_active",
"is_superuser",
"organization",
"expert_mode",
"regions",
"role",
"send_activation_link",
"passwordless_authentication_enabled",
]
field_classes = {"organization": OrganizationField}
def __init__(self, **kwargs: Any) -> None:
r"""
Initialize user form
:param \**kwargs: The supplied keyword arguments
"""
# Instantiate CustomModelForm
super().__init__(**kwargs)
# check if user instance already exists
if self.instance.id:
# set initial role data
if self.instance.is_staff:
self.fields["staff_role"].initial = self.instance.role
else:
self.fields["role"].initial = self.instance.role
# don't require password if user already exists
self.fields["password"].required = False
# adapt placeholder of password input field
self.fields["password"].widget.attrs.update(
{"placeholder": _("Leave empty to keep unchanged")}
)
else:
self.fields["is_active"].initial = False
# fix labels
self.fields["password"].label = _("Password")
if "is_staff" in self.fields:
self.fields["is_staff"].label = _("Integreat team member")
self.fields["email"].required = True
# Check if passwordless authentication is possible for the user
if "passwordless_authentication_enabled" in self.fields and (
not self.instance.pk or not self.instance.fido_keys.exists()
):
self.fields["passwordless_authentication_enabled"].disabled = True
def save(self, commit: bool = True) -> User:
"""
This method extends the default ``save()``-method of the base :class:`~django.forms.ModelForm` to set attributes
which are not directly determined by input fields.
:param commit: Whether or not the changes should be written to the database
:return: The saved user object
"""
# Save CustomModelForm
user = super().save(commit=commit)
# check if password field was changed
if self.cleaned_data["password"]:
# change password
user.set_password(self.cleaned_data["password"])
user.save()
role = (
self.cleaned_data["staff_role"]
if user.is_staff
else self.cleaned_data["role"]
)
for removed_group in user.groups.exclude(id=role.id):
# Remove unselected roles
removed_group.user_set.remove(user)
logger.info(
"%r was removed from %r",
removed_group.role,
user,
)
if role.group not in user.groups.all():
# Assign the selected role
role.group.user_set.add(user)
logger.info("%r was assigned to %r", role, user)
return user
def clean_email(self) -> str:
"""
Make the email lower case (see :ref:`overriding-modelform-clean-method`)
:return: The email in lower case
"""
if email := self.cleaned_data.get("email"):
email = email.lower()
return email
def clean(self) -> dict[str, Any]:
"""
Validate form fields which depend on each other, see :meth:`django.forms.Form.clean`
:return: The cleaned form data
"""
cleaned_data = super().clean()
if cleaned_data.get("is_superuser") and not cleaned_data.get("is_staff"):
logger.warning("Superuser %r is not a staff member", self.instance)
self.add_error(
"is_superuser",
forms.ValidationError(
_("Only staff members can be superusers"),
code="invalid",
),
)
if cleaned_data.get("is_staff"):
if cleaned_data.get("role"):
logger.warning(
"Staff member %r can only have staff roles", self.instance
)
self.add_error(
"staff_role",
forms.ValidationError(
_("Staff members can only have staff roles"),
code="invalid",
),
)
if not cleaned_data.get("staff_role"):
logger.warning("Staff member %r needs a staff role", self.instance)
self.add_error(
"staff_role",
forms.ValidationError(
_("Staff members have to be member of a team"),
code="required",
),
)
else:
if cleaned_data.get("staff_role"):
logger.warning(
"Non-staff member %r cannot have staff roles", self.instance
)
self.add_error(
"role",
forms.ValidationError(
_("Only staff members can be member of a team"),
code="invalid",
),
)
if not cleaned_data.get("role"):
logger.warning("Non-staff member %r needs a role", self.instance)
self.add_error(
"role",
forms.ValidationError(
_("Please select a role"),
code="required",
),
)
if not (
cleaned_data.get("send_activation_link")
or cleaned_data.get("password")
or self.instance
):
self.add_error(
"send_activation_link",
forms.ValidationError(
_(
"Please choose either to send an activation link or set a password."
),
code="required",
),
)
# If this is the global user form, validate the given organization
# (In the region user form, this is already ensured by the field's choices)
if "regions" in self.fields and cleaned_data.get("organization"):
if cleaned_data.get("is_superuser") or cleaned_data.get("is_staff"):
logger.warning(
"Staff member %r cannot be member of an organization", self.instance
)
self.add_error(
"organization",
forms.ValidationError(
_("Staff members cannot be member of an organization"),
code="invalid",
),
)
elif cleaned_data["organization"].region not in cleaned_data.get(
"regions", []
):
logger.warning(
"User %r cannot be member of organization %r of other region",
self.instance,
cleaned_data["organization"],
)
self.add_error(
"organization",
forms.ValidationError(
_(
"Users can only be members of organizations in regions they have access to"
),
code="invalid",
),
)
logger.debug("UserForm validated [2] with cleaned data %r", cleaned_data)
return cleaned_data