FossilRepo

fossilrepo / organization / forms.py
Blame History Raw 231 lines
1
import contextlib
2
3
from django import forms
4
from django.contrib.auth.models import Permission, User
5
from django.contrib.auth.password_validation import validate_password
6
from django.core.exceptions import ValidationError
7
8
from .models import Organization, OrgRole, Team
9
10
tw = "w-full rounded-md border-gray-300 shadow-sm focus:border-brand focus:ring-brand sm:text-sm"
11
12
13
class OrganizationSettingsForm(forms.ModelForm):
14
class Meta:
15
model = Organization
16
fields = ["name", "description", "website"]
17
widgets = {
18
"name": forms.TextInput(attrs={"class": tw, "placeholder": "Organization name"}),
19
"description": forms.Textarea(attrs={"class": tw, "rows": 3, "placeholder": "Description"}),
20
"website": forms.URLInput(attrs={"class": tw, "placeholder": "https://example.com"}),
21
}
22
23
24
class MemberAddForm(forms.Form):
25
user = forms.ModelChoiceField(
26
queryset=User.objects.none(),
27
widget=forms.Select(attrs={"class": tw}),
28
label="User",
29
)
30
31
def __init__(self, *args, org=None, **kwargs):
32
super().__init__(*args, **kwargs)
33
if org:
34
existing_member_ids = org.members.filter(deleted_at__isnull=True).values_list("member_id", flat=True)
35
self.fields["user"].queryset = User.objects.filter(is_active=True).exclude(id__in=existing_member_ids)
36
37
38
class TeamForm(forms.ModelForm):
39
class Meta:
40
model = Team
41
fields = ["name", "description"]
42
widgets = {
43
"name": forms.TextInput(attrs={"class": tw, "placeholder": "Team name"}),
44
"description": forms.Textarea(attrs={"class": tw, "rows": 3, "placeholder": "Description"}),
45
}
46
47
48
class TeamMemberAddForm(forms.Form):
49
user = forms.ModelChoiceField(
50
queryset=User.objects.none(),
51
widget=forms.Select(attrs={"class": tw}),
52
label="User",
53
)
54
55
def __init__(self, *args, team=None, **kwargs):
56
super().__init__(*args, **kwargs)
57
if team:
58
existing_member_ids = team.members.values_list("id", flat=True)
59
self.fields["user"].queryset = User.objects.filter(is_active=True).exclude(id__in=existing_member_ids)
60
61
62
class UserCreateForm(forms.ModelForm):
63
password1 = forms.CharField(
64
label="Password",
65
widget=forms.PasswordInput(attrs={"class": tw, "placeholder": "Password"}),
66
strip=False,
67
)
68
password2 = forms.CharField(
69
label="Confirm Password",
70
widget=forms.PasswordInput(attrs={"class": tw, "placeholder": "Confirm password"}),
71
strip=False,
72
)
73
role = forms.ModelChoiceField(
74
queryset=OrgRole.objects.filter(deleted_at__isnull=True),
75
required=False,
76
empty_label="No role",
77
widget=forms.Select(attrs={"class": tw}),
78
)
79
80
class Meta:
81
model = User
82
fields = ["username", "email", "first_name", "last_name"]
83
widgets = {
84
"username": forms.TextInput(attrs={"class": tw, "placeholder": "Username"}),
85
"email": forms.EmailInput(attrs={"class": tw, "placeholder": "[email protected]"}),
86
"first_name": forms.TextInput(attrs={"class": tw, "placeholder": "First name"}),
87
"last_name": forms.TextInput(attrs={"class": tw, "placeholder": "Last name"}),
88
}
89
90
def clean_password1(self):
91
password = self.cleaned_data.get("password1")
92
try:
93
validate_password(password)
94
except ValidationError as e:
95
raise ValidationError(e.messages) from None
96
return password
97
98
def clean(self):
99
cleaned_data = super().clean()
100
p1 = cleaned_data.get("password1")
101
p2 = cleaned_data.get("password2")
102
if p1 and p2 and p1 != p2:
103
self.add_error("password2", "Passwords do not match.")
104
return cleaned_data
105
106
def save(self, commit=True):
107
user = super().save(commit=False)
108
user.set_password(self.cleaned_data["password1"])
109
if commit:
110
user.save()
111
return user
112
113
114
class UserEditForm(forms.ModelForm):
115
role = forms.ModelChoiceField(
116
queryset=OrgRole.objects.filter(deleted_at__isnull=True),
117
required=False,
118
empty_label="No role",
119
widget=forms.Select(attrs={"class": tw}),
120
)
121
122
class Meta:
123
model = User
124
fields = ["email", "first_name", "last_name", "is_active", "is_staff"]
125
widgets = {
126
"email": forms.EmailInput(attrs={"class": tw, "placeholder": "[email protected]"}),
127
"first_name": forms.TextInput(attrs={"class": tw, "placeholder": "First name"}),
128
"last_name": forms.TextInput(attrs={"class": tw, "placeholder": "Last name"}),
129
"is_active": forms.CheckboxInput(attrs={"class": "rounded border-gray-300 text-brand focus:ring-brand"}),
130
"is_staff": forms.CheckboxInput(attrs={"class": "rounded border-gray-300 text-brand focus:ring-brand"}),
131
}
132
133
def __init__(self, *args, editing_self=False, **kwargs):
134
super().__init__(*args, **kwargs)
135
if editing_self:
136
# Prevent self-lockout: cannot toggle own is_active
137
self.fields["is_active"].disabled = True
138
self.fields["is_active"].help_text = "You cannot deactivate your own account."
139
140
141
class UserPasswordForm(forms.Form):
142
new_password1 = forms.CharField(
143
label="New Password",
144
widget=forms.PasswordInput(attrs={"class": tw, "placeholder": "New password"}),
145
strip=False,
146
)
147
new_password2 = forms.CharField(
148
label="Confirm New Password",
149
widget=forms.PasswordInput(attrs={"class": tw, "placeholder": "Confirm new password"}),
150
strip=False,
151
)
152
153
def clean_new_password1(self):
154
password = self.cleaned_data.get("new_password1")
155
try:
156
validate_password(password)
157
except ValidationError as e:
158
raise ValidationError(e.messages) from None
159
return password
160
161
def clean(self):
162
cleaned_data = super().clean()
163
p1 = cleaned_data.get("new_password1")
164
p2 = cleaned_data.get("new_password2")
165
if p1 and p2 and p1 != p2:
166
self.add_error("new_password2", "Passwords do not match.")
167
return cleaned_data
168
169
170
# App labels whose permissions appear in the role permission picker
171
ROLE_APP_LABELS = ["organization", "projects", "pages", "fossil"]
172
173
ROLE_APP_DISPLAY = {
174
"organization": "Organization",
175
"projects": "Projects",
176
"pages": "Pages",
177
"fossil": "Fossil",
178
}
179
180
181
class OrgRoleForm(forms.ModelForm):
182
permissions = forms.ModelMultipleChoiceField(
183
queryset=Permission.objects.none(),
184
required=False,
185
widget=forms.CheckboxSelectMultiple,
186
)
187
188
class Meta:
189
model = OrgRole
190
fields = ["name", "description", "is_default"]
191
widgets = {
192
"name": forms.TextInput(attrs={"class": tw, "placeholder": "Role name"}),
193
"description": forms.Textarea(attrs={"class": tw, "rows": 3, "placeholder": "Description"}),
194
"is_default": forms.CheckboxInput(attrs={"class": "rounded border-gray-300 text-brand focus:ring-brand"}),
195
}
196
197
def __init__(self, *args, **kwargs):
198
super().__init__(*args, **kwargs)
199
self.fields["permissions"].queryset = (
200
Permission.objects.filter(content_type__app_label__in=ROLE_APP_LABELS)
201
.select_related("content_type")
202
.order_by("content_type__app_label", "codename")
203
)
204
205
if self.instance.pk:
206
self.fields["permissions"].initial = self.instance.permissions.values_list("pk", flat=True)
207
208
def grouped_permissions(self):
209
"""Return permissions grouped by app label for the template."""
210
grouped = {}
211
selected_ids = set()
212
if self.instance.pk:
213
selected_ids = set(self.instance.permissions.values_list("pk", flat=True))
214
elif self.data:
215
# Handle POST data (validation errors, re-render)
216
with contextlib.suppress(ValueError, TypeError):
217
selected_ids = set(int(v) for v in self.data.getlist("permissions"))
218
219
for perm in self.fields["permissions"].queryset:
220
app = perm.content_type.app_label
221
label = ROLE_APP_DISPLAY.get(app, app.title())
222
grouped.setdefault(label, [])
223
grouped[label].append({"perm": perm, "checked": perm.pk in selected_ids})
224
return grouped
225
226
def save(self, commit=True):
227
role = super().save(commit=commit)
228
if commit:
229
role.permissions.set(self.cleaned_data["permissions"])
230
return role
231

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button