• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

chiefonboarding / ChiefOnboarding / 18481185920

13 Oct 2025 11:53PM UTC coverage: 89.613% (-0.006%) from 89.619%
18481185920

Pull #572

github

web-flow
Merge f5b8970dc into f01e5ecbf
Pull Request #572: Add permissions based on department

7057 of 7875 relevant lines covered (89.61%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.36
back/admin/sequences/forms.py
1
from crispy_forms.helper import FormHelper
1✔
2
from crispy_forms.layout import Div, Field, Layout
1✔
3
from django import forms
1✔
4
from django.core.exceptions import ValidationError
1✔
5
from django.utils.translation import gettext_lazy as _
1✔
6

7
from admin.templates.forms import MultiSelectField, WYSIWYGField
1✔
8
from admin.to_do.models import ToDo
1✔
9
from admin.to_do.selectors import get_to_do_templates_for_user
1✔
10
from misc.mixins import FilterDepartmentsFieldByUserMixin
1✔
11
from users.models import User
1✔
12

13
from .models import (
1✔
14
    Condition,
15
    ExternalMessage,
16
    PendingAdminTask,
17
    PendingEmailMessage,
18
    PendingSlackMessage,
19
    PendingTextMessage,
20
    Sequence,
21
)
22

23

24
class DepartmentForm(FilterDepartmentsFieldByUserMixin, forms.ModelForm):
1✔
25
    class Meta:
1✔
26
        model = Sequence
1✔
27
        fields = [
1✔
28
            "departments",
29
        ]
30

31

32
class ConditionForm(forms.ModelForm):
1✔
33
    condition_to_do = forms.ModelMultipleChoiceField(
1✔
34
        queryset=ToDo.objects.none(),
35
        to_field_name="id",
36
        required=False,
37
    )
38
    condition_admin_tasks = forms.ModelMultipleChoiceField(
1✔
39
        queryset=PendingAdminTask.objects.none(),
40
        to_field_name="id",
41
        required=False,
42
    )
43

44
    def __init__(self, *args, **kwargs):
1✔
45
        sequence = kwargs.pop("sequence")
1✔
46
        user = kwargs.pop("user")
1✔
47
        super().__init__(*args, **kwargs)
1✔
48
        self.helper = FormHelper()
1✔
49
        self.helper.form_tag = False
1✔
50
        self.helper.layout = Layout(
1✔
51
            Field("condition_type"),
52
            Div(
53
                MultiSelectField("condition_to_do"),
54
                css_class="" if self.instance.based_on_to_do else "d-none",
55
            ),
56
            Div(
57
                Field("days"),
58
                Field("time"),
59
                css_class="" if self.instance.based_on_time else "d-none",
60
            ),
61
            Div(
62
                Field("condition_admin_tasks"),
63
                css_class="" if self.instance.based_on_admin_task else "d-none",
64
            ),
65
        )
66
        self.fields["time"].required = False
1✔
67
        self.fields["days"].required = False
1✔
68
        self.fields["condition_to_do"].queryset = get_to_do_templates_for_user(
1✔
69
            user=user
70
        )
71
        pending_tasks = PendingAdminTask.objects.filter(condition__sequence=sequence)
1✔
72
        self.fields["condition_admin_tasks"].queryset = pending_tasks
1✔
73
        # Remove last option, which will only be one of
74
        self.fields["condition_type"].choices = tuple(
1✔
75
            x for x in Condition.Type.choices if x[0] != 3
76
        )
77

78
    class Meta:
1✔
79
        model = Condition
1✔
80
        fields = [
1✔
81
            "condition_type",
82
            "days",
83
            "time",
84
            "condition_to_do",
85
            "condition_admin_tasks",
86
        ]
87
        widgets = {
1✔
88
            "time": forms.TimeInput(attrs={"type": "time", "step": 300}),
89
        }
90
        help_texts = {
1✔
91
            "time": _("Must be in a 5 minute interval."),
92
        }
93

94
    def clean_days(self):
1✔
95
        day = self.cleaned_data["days"]
1✔
96
        if day is None:
1✔
97
            # Handled in clean() function
98
            return day
1✔
99
        if (
1✔
100
            self.cleaned_data["condition_type"]
101
            in [Condition.Type.AFTER, Condition.Type.BEFORE]
102
            and day <= 0
103
        ):
104
            raise ValidationError(
1✔
105
                _(
106
                    "You cannot use 0 or less. The day before starting is 1 and the "
107
                    "first workday is 1"
108
                )
109
            )
110

111
        return day
1✔
112

113
    def clean_time(self):
1✔
114
        time = self.cleaned_data["time"]
1✔
115
        if time is None:
1✔
116
            # Handled in clean() function
117
            return time
1✔
118
        if time.minute % 10 not in [0, 5] and self.cleaned_data["condition_type"] in [
1✔
119
            Condition.Type.BEFORE,
120
            Condition.Type.AFTER,
121
        ]:
122
            raise ValidationError(
1✔
123
                _(
124
                    "Time must be in an interval of 5 minutes. %(minutes)s must end in "
125
                    "0 or 5."
126
                )
127
                % {"minutes": time.minute}
128
            )
129

130
        return time
1✔
131

132
    def clean(self):
1✔
133
        cleaned_data = super().clean()
1✔
134
        condition_type = cleaned_data.get("condition_type", None)
1✔
135
        time = cleaned_data.get("time", None)
1✔
136
        days = cleaned_data.get("days", None)
1✔
137
        condition_to_do = cleaned_data.get("condition_to_do", None)
1✔
138
        condition_admin_tasks = cleaned_data.get("condition_admin_tasks", None)
1✔
139
        if condition_type == Condition.Type.TODO and (
1✔
140
            condition_to_do is None or len(condition_to_do) == 0
141
        ):
142
            raise ValidationError(_("You must add at least one to do item"))
1✔
143
        if condition_type == Condition.Type.ADMIN_TASK and (
1✔
144
            condition_admin_tasks is None or len(condition_admin_tasks) == 0
145
        ):
146
            raise ValidationError(_("You must add at least one admin task"))
1✔
147
        if condition_type in [Condition.Type.AFTER, Condition.Type.BEFORE] and (
1✔
148
            time is None or days is None
149
        ):
150
            raise ValidationError(_("Both the time and days have to be filled in."))
1✔
151
        return cleaned_data
1✔
152

153

154
class OffboardingConditionForm(ConditionForm):
1✔
155
    def __init__(self, *args, **kwargs):
1✔
156
        super().__init__(*args, **kwargs)
1✔
157
        # use different labels for the type
158
        self.fields["condition_type"].choices = [
1✔
159
            (2, _("On/Before employee's last day")),
160
            (1, _("Based on one or more to do items")),
161
            (4, _("Based on one or more admin tasks")),
162
            (5, _("When all integrations have been revoked")),
163
        ]
164
        self.fields["days"].help_text = _("Enter 0 for the last day")
1✔
165
        self.fields["days"].label = _("Amount of days before")
1✔
166

167
    def clean_days(self):
1✔
168
        day = self.cleaned_data["days"]
1✔
169
        if day is None:
1✔
170
            # Handled in clean() function
171
            return day
1✔
172
        if self.cleaned_data["condition_type"] == Condition.Type.BEFORE and day < 0:
1✔
173
            raise ValidationError(_("Their last day is 0. You cannot go below that."))
1✔
174
        return day
×
175

176

177
class PendingAdminTaskForm(forms.ModelForm):
1✔
178
    assigned_to = forms.ModelChoiceField(
1✔
179
        queryset=User.managers_and_admins.all(), required=False
180
    )
181
    slack_user = forms.ModelChoiceField(
1✔
182
        queryset=User.objects.exclude(slack_user_id=""),
183
        required=False,
184
    )
185
    date = forms.DateField(
1✔
186
        label=_("Due date"),
187
        required=False,
188
        widget=forms.DateInput(attrs={"type": "date"}, format=("%Y-%m-%d")),
189
    )
190

191
    def __init__(self, user, *args, **kwargs):
1✔
192
        super().__init__(*args, **kwargs)
1✔
193
        self.fields["option"].initial = PendingAdminTask.Notification.NO
1✔
194
        self.fields["comment"].required = True
1✔
195
        self.helper = FormHelper()
1✔
196
        self.helper.form_tag = False
1✔
197

198
        # Check if assigned_to field should be hidden
199
        hide_assigned_to = "d-none"
1✔
200
        if (
1✔
201
            self.instance is not None
202
            and self.instance.person_type == PendingAdminTask.PersonType.CUSTOM
203
        ):
204
            hide_assigned_to = ""
1✔
205

206
        self.helper.layout = Layout(
1✔
207
            Div(
208
                Field("name"),
209
                Field("person_type"),
210
                Div(
211
                    Field("assigned_to"),
212
                    css_class=hide_assigned_to,
213
                ),
214
                Field("date"),
215
                Field("priority"),
216
                Field("comment"),
217
                Field("option"),
218
                Field("slack_user"),
219
                Field("email"),
220
            ),
221
        )
222

223
    class Meta:
1✔
224
        model = PendingAdminTask
1✔
225
        fields = [
1✔
226
            "name",
227
            "person_type",
228
            "assigned_to",
229
            "date",
230
            "priority",
231
            "comment",
232
            "option",
233
            "slack_user",
234
            "email",
235
        ]
236

237
    def clean(self):
1✔
238
        cleaned_data = super().clean()
×
239
        assigned_to = cleaned_data.get("assigned_to", None)
×
240
        person_type = cleaned_data["person_type"]
×
241
        if person_type == PendingAdminTask.PersonType.CUSTOM and assigned_to is None:
×
242
            self.add_error("assigned_to", _("This field is required"))
×
243
        return cleaned_data
×
244

245

246
class PendingSlackMessageForm(forms.ModelForm):
1✔
247
    def __init__(self, user, *args, **kwargs):
1✔
248
        super().__init__(*args, **kwargs)
1✔
249
        self.helper = FormHelper()
1✔
250
        self.helper.form_tag = False
1✔
251

252
        # Check if send_to field should be hidden
253
        hide_send_to = "d-none"
1✔
254
        if (
1✔
255
            self.instance is not None
256
            and self.instance.person_type == ExternalMessage.PersonType.CUSTOM
257
        ):
258
            hide_send_to = ""
×
259

260
        hide_send_to_channel = "d-none"
1✔
261
        if (
1✔
262
            self.instance is not None
263
            and self.instance.person_type == ExternalMessage.PersonType.SLACK_CHANNEL
264
        ):
265
            hide_send_to_channel = ""
×
266

267
        self.helper.layout = Layout(
1✔
268
            Div(
269
                Field("name"),
270
                WYSIWYGField("content_json"),
271
                Field("person_type"),
272
                Div(
273
                    Field("send_to"),
274
                    css_class=hide_send_to,
275
                ),
276
                Div(
277
                    Field("send_to_channel"),
278
                    css_class=hide_send_to_channel,
279
                ),
280
            ),
281
        )
282

283
    class Meta:
1✔
284
        model = PendingSlackMessage
1✔
285
        fields = [
1✔
286
            "name",
287
            "content_json",
288
            "person_type",
289
            "send_to",
290
            "send_to_channel",
291
        ]
292

293

294
class PendingTextMessageForm(forms.ModelForm):
1✔
295
    def __init__(self, user, *args, **kwargs):
1✔
296
        super().__init__(*args, **kwargs)
1✔
297
        self.helper = FormHelper()
1✔
298
        self.helper.form_tag = False
1✔
299

300
        # Check if send_to field should be hidden
301
        hide_send_to = "d-none"
1✔
302
        if (
1✔
303
            self.instance is not None
304
            and self.instance.person_type == ExternalMessage.PersonType.CUSTOM
305
        ):
306
            hide_send_to = ""
×
307

308
        # Remove the Slack channel options
309
        self.fields["person_type"].choices = PendingAdminTask.PersonType.choices
1✔
310
        self.fields["person_type"].widget.choices = PendingAdminTask.PersonType.choices
1✔
311

312
        self.helper.layout = Layout(
1✔
313
            Div(
314
                Field("name"),
315
                Field("content"),
316
                Field("person_type"),
317
                Div(
318
                    Field("send_to"),
319
                    css_class=hide_send_to,
320
                ),
321
            ),
322
        )
323

324
    class Meta:
1✔
325
        model = PendingTextMessage
1✔
326
        fields = [
1✔
327
            "name",
328
            "content",
329
            "person_type",
330
            "send_to",
331
        ]
332

333

334
class PendingEmailMessageForm(forms.ModelForm):
1✔
335
    def __init__(self, user, *args, **kwargs):
1✔
336
        super().__init__(*args, **kwargs)
1✔
337
        self.helper = FormHelper()
1✔
338
        self.helper.form_tag = False
1✔
339

340
        # Check if send_to field should be hidden
341
        hide_send_to = "d-none"
1✔
342
        if (
1✔
343
            self.instance is not None
344
            and self.instance.person_type == ExternalMessage.PersonType.CUSTOM
345
        ):
346
            hide_send_to = ""
×
347

348
        # Remove the Slack channel options
349
        self.fields["person_type"].choices = PendingAdminTask.PersonType.choices
1✔
350
        self.fields["person_type"].widget.choices = PendingAdminTask.PersonType.choices
1✔
351

352
        self.helper.layout = Layout(
1✔
353
            Div(
354
                Field("name"),
355
                Field("subject"),
356
                WYSIWYGField("content_json"),
357
                Field("person_type"),
358
                Div(
359
                    Field("send_to"),
360
                    css_class=hide_send_to,
361
                ),
362
            ),
363
        )
364

365
    class Meta:
1✔
366
        model = PendingEmailMessage
1✔
367
        fields = [
1✔
368
            "name",
369
            "subject",
370
            "content_json",
371
            "person_type",
372
            "send_to",
373
        ]
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc