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

WPI-LNL / lnldb / 10155059950

30 Jul 2024 02:22AM UTC coverage: 90.975% (-0.1%) from 91.083%
10155059950

Pull #853

github

web-flow
Merge 63b26c840 into 6384c05c8
Pull Request #853: Upgrade to Django 4.2 (Big Django Update)

99 of 115 new or added lines in 42 files covered. (86.09%)

38 existing lines in 8 files now uncovered.

15000 of 16488 relevant lines covered (90.98%)

0.91 hits per line

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

93.5
/events/forms.py
1
import datetime
1✔
2
import decimal
1✔
3
import re
1✔
4
import uuid
1✔
5

6
import six
1✔
7
from ajax_select import make_ajax_field
1✔
8
from ajax_select.fields import (AutoCompleteSelectField,
1✔
9
                                AutoCompleteSelectMultipleField)
10
from crispy_forms.bootstrap import (FormActions, InlineCheckboxes, InlineRadios, PrependedText, Tab, TabHolder)
1✔
11
from crispy_forms.helper import FormHelper
1✔
12
from crispy_forms.layout import (HTML, Div, Row, Column, Field, Hidden, Layout, Reset, Submit)
1✔
13
from django import forms
1✔
14
from django.contrib.auth import get_user_model
1✔
15
from django.core.exceptions import ValidationError
1✔
16
from django.core.validators import RegexValidator
1✔
17
from django.db.models import Model, Q
1✔
18
from django.forms import ModelChoiceField, ModelMultipleChoiceField, ModelForm, SelectDateWidget, TextInput
1✔
19
from django.utils import timezone
1✔
20
# python multithreading bug workaround
21
from easymde.widgets import EasyMDEEditor
1✔
22

23
from data.forms import DynamicFieldContainer, FieldAccessForm, FieldAccessLevel
1✔
24
from events.fields import GroupedModelChoiceField
1✔
25
from events.models import (BaseEvent, Billing, MultiBilling, BillingEmail, MultiBillingEmail,
1✔
26
                           Category, CCReport, Event, Event2019, EventAttachment, EventCCInstance, Extra,
27
                           ExtraInstance, Hours, Lighting, Location, Organization, OrganizationTransfer,
28
                           OrgBillingVerificationEvent, Workshop, WorkshopDate, Projection, Service, ServiceInstance,
29
                           Sound, PostEventSurvey, OfficeHour)
30
from events.widgets import ValueSelectField
1✔
31
from helpers.form_text import markdown_at_msgs
1✔
32
from helpers.util import curry_class
1✔
33

34
LIGHT_EXTRAS = Extra.objects.exclude(disappear=True).filter(category__name="Lighting")
1✔
35
LIGHT_EXTRAS_ID_NAME = LIGHT_EXTRAS.values_list('id', 'name')
1✔
36
LIGHT_EXTRAS_NAMES = LIGHT_EXTRAS.values('name')
1✔
37

38
SOUND_EXTRAS = Extra.objects.exclude(disappear=True).filter(category__name="Sound")
1✔
39
SOUND_EXTRAS_ID_NAME = SOUND_EXTRAS.values_list('id', 'name')
1✔
40
SOUND_EXTRAS_NAMES = SOUND_EXTRAS.values('name')
1✔
41

42
PROJ_EXTRAS = Extra.objects.exclude(disappear=True).filter(category__name="Projection")
1✔
43
PROJ_EXTRAS_ID_NAME = PROJ_EXTRAS.values_list('id', 'name')
1✔
44
PROJ_EXTRAS_NAMES = PROJ_EXTRAS.values('name')
1✔
45

46
JOBTYPES = (
1✔
47
    (0, 'Lighting'),
48
    (1, 'Sound'),
49
    (2, 'Projection'),
50
    (3, 'Other Services'),
51
)
52

53
LIGHT_CHOICES = (
1✔
54
    (1, 'L1'),
55
    (2, 'L2'),
56
    (3, 'L3'),
57
    (4, 'L4'),
58
)
59

60
SOUND_CHOICES = (
1✔
61
    (1, 'S1'),
62
    (2, 'S2'),
63
    (3, 'S3'),
64
    (4, 'S4'),
65
)
66
PROJ_CHOICES = (
1✔
67
    (16, '16mm'),
68
    (35, '35mm'),
69
    ('d', 'Digital'),
70
)
71

72

73
# gets a set of services from a given event
74
def get_qs_from_event(event):
1✔
75
    if isinstance(event, Event):
1✔
76
        if event.lighting:
1✔
77
            lighting_id = event.lighting.id
1✔
78
        else:
79
            lighting_id = None
1✔
80
        if event.sound:
1✔
81
            sound_id = event.sound.id
×
82
        else:
83
            sound_id = None
1✔
84
        if event.projection:
1✔
85
            proj_id = event.projection.id
1✔
86
        else:
87
            proj_id = None
1✔
88

89
        return Service.objects.filter(Q(id__in=[lighting_id]) | Q(id__in=[sound_id]) | Q(id__in=[proj_id]) | Q(
1✔
90
            id__in=[i.id for i in event.otherservices.all()]))
91
    elif isinstance(event, Event2019):
1✔
92
        return Service.objects.filter(pk__in=event.serviceinstance_set.values_list('service', flat=True))
1✔
93

94

95
class CustomAutoCompleteSelectMultipleField(AutoCompleteSelectMultipleField):
1✔
96
    def has_changed(self, initial_value, data_value):
1✔
97
        initial_value = [v.pk if isinstance(v, Model) else v for v in (initial_value or [])]
1✔
98
        return AutoCompleteSelectMultipleField.has_changed(self, initial_value, data_value)
1✔
99

100

101
class CustomEventModelMultipleChoiceField(forms.ModelMultipleChoiceField):
1✔
102
    def label_from_instance(self, event):
1✔
103
        return six.u('%s\u2014%s') % (event.event_name, ', '.join(map(lambda org: org.name, event.org.all())))
1✔
104

105

106
class CustomOrganizationEmailModelMultipleChoiceField(forms.ModelMultipleChoiceField):
1✔
107
    def label_from_instance(self, org):
1✔
108
        return six.u('%s (%s)') % (org.name, org.exec_email)
1✔
109

110

111
class SurveyCustomRadioSelect(forms.widgets.ChoiceWidget):
1✔
112
    input_type = 'radio'
1✔
113
    template_name = 'survey_custom_radio_select.html'
1✔
114
    option_template_name = 'survey_custom_radio_select.html'
1✔
115

116

117
# LNAdmin Forms
118
class WorkorderSubmit(ModelForm):
1✔
119
    class Meta:
1✔
120
        model = Event
1✔
121
        exclude = ('submitted_by', 'submitted_ip', 'approved', 'crew', 'crew_chief',
1✔
122
                   'report', 'closed', 'payment_amount', 'paid')
123

124
    def __init__(self, *args, **kwargs):
1✔
125
        super(WorkorderSubmit, self).__init__(*args, **kwargs)
×
126
        self.fields['datetime_setup_start'].widget = SelectDateWidget()
×
127
        # self.fields['datetime_start'].widget = datetime()
128
        # self.fields['datetime_end'].widget = datetime()
129

130

131
class CrewAssign(forms.ModelForm):
1✔
132
    def __init__(self, *args, **kwargs):
1✔
133
        self.helper = FormHelper()
1✔
134
        self.helper.form_class = "form-horizontal"
1✔
135
        self.helper.layout = Layout(
1✔
136
            Field('crew'),
137
            FormActions(
138
                Submit('save', 'Save Changes'),
139
            )
140
        )
141
        super(CrewAssign, self).__init__(*args, **kwargs)
1✔
142

143
    class Meta:
1✔
144
        model = Event
1✔
145
        fields = ("crew",)
1✔
146

147
    crew = make_ajax_field(Event, 'crew', 'Members', plugin_options={'minLength': 3})
1✔
148

149

150
class CrewChiefAssign(forms.ModelForm):
1✔
151
    crew_chief = make_ajax_field(Event, 'crew_chief', 'Members', plugin_options={'minLength': 3})
1✔
152

153
    class Meta:
1✔
154
        model = Event
1✔
155
        fields = ("crew_chief",)
1✔
156

157

158
class IOrgForm(FieldAccessForm):
1✔
159
    """ Internal Organization Details Form """
160
    def __init__(self, request_user, *args, **kwargs):
1✔
161
        self.helper = FormHelper()
1✔
162
        self.helper.form_class = "form-horizontal"
1✔
163
        tabs = []
1✔
164
        if request_user.has_perm('events.view_org_notes'):
1✔
165
            tabs.append(
1✔
166
                Tab(
167
                    'Notes',
168
                    'notes',
169
                    'delinquent'
170
                )
171
            )
172
        read_only = 'rw'
1✔
173
        instance = kwargs['instance']
1✔
174
        if instance is not None and instance.locked is True:
1✔
175
            read_only = 'r'
×
176
        tabs.extend([
1✔
177
            Tab(
178
                'Contact',
179
                Field('name', css_class=read_only),
180
                'exec_email',
181
                'address',
182
                Field('phone', css_class="bfh-phone", data_format="(ddd) ddd dddd"),
183
            ),
184
            Tab(
185
                'Options',
186
                'associated_orgs',
187
                Field('personal', )
188
            ),
189
            Tab(
190
                'Money',
191
                'workday_fund',
192
                Field('worktag', placeholder='e.g. 1234-CC'),
193
            ),
194
            Tab(
195
                'People',
196
                Field('user_in_charge', css_class=read_only),
197
                'associated_users',
198
            )
199
        ])
200
        self.helper.layout = Layout(
1✔
201
            TabHolder(*tabs),
202
            FormActions(
203
                Submit('save', 'Save Changes'),
204
            )
205
        )
206
        super(IOrgForm, self).__init__(request_user, *args, **kwargs)
1✔
207

208
    def clean_worktag(self):
1✔
209
        pattern = re.compile('[0-9]+-[A-Z][A-Z]')
1✔
210
        if pattern.match(self.cleaned_data['worktag']) is None and self.cleaned_data['worktag'] not in [None, '']:
1✔
211
            raise ValidationError('What you entered is not a valid worktag. Here are some examples of what a worktag '
1✔
212
                                  'looks like: 1234-CC, 123-AG')
213
        return self.cleaned_data['worktag']
1✔
214

215
    class Meta:
1✔
216
        model = Organization
1✔
217
        fields = ('name', 'exec_email', 'address', 'phone', 'associated_orgs', 'personal',
1✔
218
                  'workday_fund', 'worktag', 'user_in_charge', 'associated_users', 'notes', 'delinquent')
219

220
    # associated_orgs = make_ajax_field(Organization,'associated_orgs','Orgs',plugin_options = {'minLength':2})
221
    # associated_users = make_ajax_field(Organization,'associated_users','Users',plugin_options = {'minLength':3})
222
    user_in_charge = AutoCompleteSelectField('Users')
1✔
223
    associated_orgs = AutoCompleteSelectMultipleField('Orgs', required=False)
1✔
224
    associated_users = AutoCompleteSelectMultipleField('Users', required=False)
1✔
225
    worktag = forms.CharField(required=False, help_text='Ends in -AG, -CC, -GF, -GR, or -DE')
1✔
226
    notes = forms.CharField(widget=EasyMDEEditor(), label="Internal Notes", required=False)
1✔
227

228
    class FieldAccess:
1✔
229
        def __init__(self):
1✔
230
            pass
×
231

232
        internal_notes_view = FieldAccessLevel(
1✔
233
            lambda user, instance: not user.has_perm("events.view_org_notes", instance),
234
            exclude=('notes', 'delinquent')
235
        )
236

237
        internal_notes_edit = FieldAccessLevel(
1✔
238
            lambda user, instance: user.has_perm("events.view_org_notes", instance),
239
            enable=('notes', 'delinquent')
240
        )
241

242
        billing_view = FieldAccessLevel(
1✔
243
            lambda user, instance: not user.has_perm("events.show_org_billing", instance),
244
            exclude=('workday_fund', 'worktag')
245
        )
246

247
        billing_edit = FieldAccessLevel(
1✔
248
            lambda user, instance: user.has_perm("events.edit_org_billing", instance),
249
            enable=('workday_fund', 'worktag')
250
        )
251

252
        members_view = FieldAccessLevel(
1✔
253
            lambda user, instance: not user.has_perm("events.list_org_members", instance),
254
            exclude=('user_in_charge', 'associated_users',)
255
        )
256

257
        members_edit = FieldAccessLevel(
1✔
258
            lambda user, instance: user.has_perm("events.edit_org_members", instance),
259
            enable=('associated_users',)
260
        )
261

262
        owner_edit = FieldAccessLevel(
1✔
263
            lambda user, instance: user.has_perm("events.transfer_org_ownership", instance),
264
            enable=('user_in_charge',)
265
        )
266

267
        everything_else_edit = FieldAccessLevel(
1✔
268
            lambda user, instance: user.has_perm("events.edit_org", instance),
269
            enable=('name', 'exec_email', 'address', 'phone', 'associated_orgs', 'personal')
270
        )
271

272

273
class IOrgVerificationForm(forms.ModelForm):
1✔
274
    """ Internal Client Billing Verification Form """
275
    def __init__(self, org, *args, **kwargs):
1✔
276
        self.helper = FormHelper()
1✔
277
        self.helper.form_class = "form-horizontal"
1✔
278
        self.org = org
1✔
279
        self.helper.layout = Layout(
1✔
280
            Field('date', css_class="datepick"),
281
            Field('verified_by'),
282
            Field('note', size="5"),
283
            FormActions(
284
                Submit('save', 'Verify'),
285
            )
286
        )
287
        super(IOrgVerificationForm, self).__init__(*args, **kwargs)
1✔
288

289
    def save(self, commit=True):
1✔
290
        obj = super(IOrgVerificationForm, self).save(commit=False)
1✔
291
        obj.org = self.org
1✔
292
        if commit:
1✔
293
            obj.save()
1✔
294
        return obj
1✔
295

296
    class Meta:
1✔
297
        model = OrgBillingVerificationEvent
1✔
298
        fields = ('date', 'verified_by', 'note')
1✔
299

300
    verified_by = AutoCompleteSelectField('Officers')
1✔
301

302

303
# Flow Forms
304
class EventApprovalForm(forms.ModelForm):
1✔
305
    def __init__(self, *args, **kwargs):
1✔
306
        super(EventApprovalForm, self).__init__(*args, **kwargs)
1✔
307
        tabs = (
1✔
308
            Tab(
309
                "Standard Fields",
310
                Field('description', label="Description (optional)", css_class="col-md-6"),
311
                Field('internal_notes', label="Internal Notes", css_class="col-md-6"),
312
                markdown_at_msgs,
313
                Field('datetime_setup_complete', label="Setup Finish", css_class="dtp"),
314
                Field('datetime_start', label="Event Start", css_class="dtp"),
315
                Field('datetime_end', label="Event End", css_class="dtp"),
316
                'org',
317
                'billing_org',
318
                'billed_in_bulk',
319
                # Field('datetime_setup_start',label="Setup Start",css_class="dtp"),
320
                active=True
321
            ),
322
        )
323
        if isinstance(self.instance, Event):
1✔
324
            tabs += (
1✔
325
                Tab(
326
                    "Services",
327
                    Field('lighting'),
328
                    Field('lighting_reqs', css_class="col-md-8"),
329
                    Field('sound'),
330
                    Field('sound_reqs', css_class="col-md-8"),
331
                    Field('projection'),
332
                    Field('proj_reqs', css_class="col-md-8"),
333
                    Field('otherservices'),
334
                    Field('otherservice_reqs', css_class="col-md-8")
335
                ),
336
            )
337
        self.helper = FormHelper()
1✔
338
        self.helper.form_tag = False
1✔
339
        self.helper.include_media = False
1✔
340
        self.helper.form_class = "form-horizontal"
1✔
341
        self.helper.layout = Layout(*tabs)
1✔
342

343
    class Meta:
1✔
344
        model = Event
1✔
345
        fields = ['description', 'internal_notes', 'datetime_start', 'datetime_end', 'org', 'billing_org',
1✔
346
                  'billed_in_bulk', 'datetime_setup_complete', 'lighting', 'lighting_reqs',
347
                  'sound', 'sound_reqs', 'projection', 'proj_reqs', 'otherservices', 'otherservice_reqs']
348
        widgets = {
1✔
349
            'description': EasyMDEEditor(),
350
            'internal_notes': EasyMDEEditor(),
351
            'lighting_reqs': EasyMDEEditor(),
352
            'sound_reqs': EasyMDEEditor(),
353
            'proj_reqs': EasyMDEEditor(),
354
            'otherservice_reqs': EasyMDEEditor()
355
        }
356

357
    org = AutoCompleteSelectMultipleField('Orgs', required=False, label='Client(s)')
1✔
358
    billing_org = AutoCompleteSelectField('Orgs', required=False, label='Client to bill')
1✔
359
    datetime_start = forms.SplitDateTimeField(initial=timezone.now, label="Event Start")
1✔
360
    datetime_end = forms.SplitDateTimeField(initial=timezone.now, label="Event End")
1✔
361
    # datetime_setup_start =  forms.SplitDateTimeField(initial=timezone.now)
362
    datetime_setup_complete = forms.SplitDateTimeField(initial=timezone.now, label="Setup Completed")
1✔
363

364

365
class EventDenialForm(forms.ModelForm):
1✔
366
    def __init__(self, *args, **kwargs):
1✔
367
        self.helper = FormHelper()
1✔
368
        self.helper.form_class = "form-horizontal"
1✔
369
        self.helper.layout = Layout(
1✔
370
            Field('cancelled_reason', label="Reason For Cancellation (optional)", css_class="col-md-6"),
371
            Field('send_email', css_class=""),
372
            HTML('Please note that the reason will be included in the cancellation email to the event contact. <p />'),
373
            FormActions(
374
                Submit('save', 'Deny Event'),
375
            ),
376
        )
377
        super(EventDenialForm, self).__init__(*args, **kwargs)
1✔
378

379
    send_email = forms.BooleanField(required=False, label="Send Cancellation Email to Event Contact")
1✔
380

381
    class Meta:
1✔
382
        model = BaseEvent
1✔
383
        fields = ['cancelled_reason', 'send_email']
1✔
384
        widgets = {
1✔
385
            'cancelled_reason': EasyMDEEditor()
386
        }
387

388

389
class EventMeetingForm(forms.ModelForm):
1✔
390
    def __init__(self, *args, **kwargs):
1✔
391
        self.helper = FormHelper()
×
392
        self.helper.form_class = "form-horizontal"
×
393
        self.helper.layout = Layout(
×
394
            Field('datetime_setup_start', label="Setup Start", css_class="dtp"),
395
            Field('datetime_setup_complete', label="Setup Finish", css_class="dtp"),
396
            Field('crew_chief', label="Crew Chief"),
397
            Field('crew', label="Crew"),
398
            FormActions(
399
                Submit('save', 'Update Event and Return'),
400
            )
401
        )
402
        super(EventMeetingForm, self).__init__(*args, **kwargs)
×
403

404
    class Meta:
1✔
405
        model = Event
1✔
406
        fields = ['datetime_setup_start', 'datetime_setup_complete', 'crew_chief', 'crew']
1✔
407

408
    datetime_setup_start = forms.SplitDateTimeField(initial=timezone.now)
1✔
409
    datetime_setup_complete = forms.SplitDateTimeField(initial=timezone.now)
1✔
410
    crew_chief = AutoCompleteSelectMultipleField('Users', required=False)
1✔
411
    crew = AutoCompleteSelectMultipleField('3', required=False)
1✔
412

413

414
class InternalEventForm(FieldAccessForm):
1✔
415
    def __init__(self, request_user, *args, **kwargs):
1✔
416
        super(InternalEventForm, self).__init__(request_user, *args, **kwargs)
1✔
417
        tabs = (
1✔
418
            Tab(
419
                'Name And Location',
420
                'event_name',
421
                'event_status',
422
                'location',
423
                'lnl_contact',
424
                Field('description'),
425
                DynamicFieldContainer('internal_notes'),
426
                'billed_in_bulk',
427
                'sensitive',
428
                'test_event',
429
                active=True
430
            ),
431
            Tab(
432
                'Contact',
433
                'contact',
434
                'org',
435
                DynamicFieldContainer('billing_org'),
436
            ),
437
            Tab(
438
                'Scheduling',
439
                Div(
440
                    Div(Field('datetime_setup_complete', css_class='dtp', title="Setup Completed By"),
441
                        css_class="padleft"),
442
                ),
443
                Div(
444
                    HTML(
445
                        '<div class="pull-left pushdown"><br />'
446
                        '<a class="btn btn-primary" href="#" id="samedate1" title="Cascade Dates">'
447
                        '<i class="glyphicon glyphicon-resize-small icon-white"></i>&nbsp;'
448
                        '<i class="glyphicon glyphicon-calendar icon-white"></i></a></div>'),
449
                    Div(Field('datetime_start', css_class='dtp'), css_class="padleft"),
450
                ),
451
                Div(
452
                    HTML(
453
                        '<div class="pull-left pushdown"><br />'
454
                        '<a class="btn btn-primary" href="#" id="samedate2" title="Cascade Dates">'
455
                        '<i class="glyphicon glyphicon-resize-small icon-white"></i>&nbsp;'
456
                        '<i class="glyphicon glyphicon-calendar icon-white"></i></a></div>'),
457
                    Div(Field('datetime_end', css_class='dtp'), css_class="padleft"),
458
                ),
459
            ),
460
            Tab(
461
                'Lighting',
462
                'lighting',
463
                'lighting_reqs'
464
            ),
465
            Tab(
466
                'Sound',
467
                'sound',
468
                'sound_reqs'
469
            ),
470
            Tab(
471
                'Projection',
472
                'projection',
473
                'proj_reqs'
474
            ),
475
            Tab(
476
                'Other Services',
477
                'otherservices',
478
                'otherservice_reqs'
479
            ),
480
        )
481
        self.helper = FormHelper()
1✔
482
        self.helper.form_tag = False
1✔
483
        self.helper.include_media = False
1✔
484
        self.helper.layout = Layout(*tabs)
1✔
485

486
    class FieldAccess:
1✔
487
        def __init__(self):
1✔
488
            pass
×
489

490
        internal_notes_write = FieldAccessLevel(
1✔
491
            lambda user, instance: user.has_perm("events.event_view_sensitive", instance),
492
            enable=('internal_notes',)
493
        )
494

495
        hide_internal_notes = FieldAccessLevel(
1✔
496
            lambda user, instance: not user.has_perm("events.event_view_sensitive", instance),
497
            exclude=('internal_notes',)
498
        )
499

500
        event_times = FieldAccessLevel(
1✔
501
            lambda user, instance: user.has_perm('events.edit_event_times', instance),
502
            enable=('datetime_start', 'datetime_setup_complete', 'datetime_end')
503
        )
504

505
        edit_descriptions = FieldAccessLevel(
1✔
506
            lambda user, instance: user.has_perm('events.edit_event_text', instance),
507
            enable=('event_name', 'location', 'description',
508
                    'lighting_reqs', 'sound_reqs', 'proj_reqs', 'otherservice_reqs')
509
        )
510

511
        change_owner = FieldAccessLevel(
1✔
512
            lambda user, instance: user.has_perm('events.adjust_event_owner', instance),
513
            enable=('contact', 'org')
514
        )
515

516
        change_type = FieldAccessLevel(
1✔
517
            lambda user, instance: user.has_perm('events.adjust_event_charges', instance),
518
            enable=('lighting', 'sound', 'projection', 'otherservices', 'billed_in_bulk')
519
        )
520

521
        billing_edit = FieldAccessLevel(
1✔
522
            lambda user, instance: user.has_perm('events.edit_event_fund', instance),
523
            enable=('billing_org', 'billed_in_bulk')
524
        )
525

526
        billing_view = FieldAccessLevel(
1✔
527
            lambda user, instance: not user.has_perm('events.view_event_billing', instance),
528
            exclude=('billing_org', 'billed_in_bulk')
529
        )
530

531
        change_flags = FieldAccessLevel(
1✔
532
            lambda user, instance: user.has_perm('events.edit_event_flags', instance),
533
            enable=('sensitive', 'test_event')
534
        )
535

536
        change_lnl_contact = FieldAccessLevel(
1✔
537
            lambda user, instance: user.has_perm('events.edit_event_lnl_contact', instance),
538
            enable=('lnl_contact')
539
        )
540
        
541
        change_event_status = FieldAccessLevel(
1✔
542
            lambda user, instance: user.has_perm('events.edit_event_status', instance),
543
            enable=('event_status')
544
        )
545

546
    class Meta:
1✔
547
        model = Event
1✔
548
        fields = ('event_name', 'event_status', 'location', 'lnl_contact', 'description', 'internal_notes', 'billing_org', 'billed_in_bulk', 'contact',
1✔
549
                  'org', 'datetime_setup_complete', 'datetime_start', 'datetime_end', 'lighting', 'lighting_reqs',
550
                  'sound', 'sound_reqs', 'projection', 'proj_reqs', 'otherservices', 'otherservice_reqs', 'sensitive',
551
                  'test_event')
552
        widgets = {
1✔
553
            'description': EasyMDEEditor(),
554
            'internal_notes': EasyMDEEditor(),
555
            'lighting_reqs': EasyMDEEditor(),
556
            'sound_reqs': EasyMDEEditor(),
557
            'proj_reqs': EasyMDEEditor(),
558
            'otherservice_reqs': EasyMDEEditor()
559
        }
560

561
    location = GroupedModelChoiceField(
1✔
562
        queryset=Location.objects.filter(show_in_wo_form=True),
563
        group_by_field="building",
564
        group_label=lambda group: group.name,
565
    )
566
    contact = AutoCompleteSelectField('Users', required=False)
1✔
567
    lnl_contact = AutoCompleteSelectField('Members', required=False)
1✔
568
    org = CustomAutoCompleteSelectMultipleField('Orgs', required=False, label="Client(s)")
1✔
569
    billing_org = AutoCompleteSelectField('Orgs', required=False, label="Client to bill")
1✔
570
    datetime_setup_complete = forms.SplitDateTimeField(initial=timezone.now, label="Setup Completed")
1✔
571
    datetime_start = forms.SplitDateTimeField(initial=timezone.now, label="Event Start")
1✔
572
    datetime_end = forms.SplitDateTimeField(initial=timezone.now, label="Event End")
1✔
573
    otherservices = ModelMultipleChoiceField(queryset=Service.objects.filter(enabled_event2012=True), required=False)
1✔
574

575

576
class InternalEventForm2019(FieldAccessForm):
1✔
577
    def __init__(self, request_user, *args, **kwargs):
1✔
578
        super(InternalEventForm2019, self).__init__(request_user, *args, **kwargs)
1✔
579
        tabs = (
1✔
580
            Tab(
581
                'Name And Location',
582
                'event_name',
583
                'event_status',
584
                'location',
585
                'lnl_contact',
586
                'reference_code',
587
                Field('description'),
588
                DynamicFieldContainer('internal_notes'),
589
                HTML('<div style="width: 50%">'),
590
                'max_crew',
591
                'cancelled_reason',
592
                HTML('</div>'),
593
                'billed_in_bulk',
594
                'sensitive',
595
                'test_event',
596
                'entered_into_workday',
597
                'send_survey',
598
                active=True
599
            ),
600
            Tab(
601
                'Contact',
602
                'contact',
603
                'org',
604
                DynamicFieldContainer('billing_org'),
605
            ),
606
            Tab(
607
                'Scheduling',
608
                Div(
609
                    Div(Field('datetime_setup_complete', css_class='dtp', title="Setup Completed By"),
610
                        css_class="padleft"),
611
                ),
612
                Div(
613
                    HTML(
614
                        '<div class="pull-left pushdown"><br />'
615
                        '<a class="btn btn-primary" href="#" id="samedate1" title="Cascade Dates">'
616
                        '<i class="glyphicon glyphicon-resize-small icon-white"></i>&nbsp;'
617
                        '<i class="glyphicon glyphicon-calendar icon-white"></i></a></div>'),
618
                    Div(Field('datetime_start', css_class='dtp'), css_class="padleft"),
619
                ),
620
                Div(
621
                    HTML(
622
                        '<div class="pull-left pushdown"><br />'
623
                        '<a class="btn btn-primary" href="#" id="samedate2" title="Cascade Dates">'
624
                        '<i class="glyphicon glyphicon-resize-small icon-white"></i>&nbsp;'
625
                        '<i class="glyphicon glyphicon-calendar icon-white"></i></a></div>'),
626
                    Div(Field('datetime_end', css_class='dtp'), css_class="padleft"),
627
                ),
628
            ),
629
        )
630
        self.helper = FormHelper()
1✔
631
        self.helper.form_tag = False
1✔
632
        self.helper.include_media = False
1✔
633
        self.helper.layout = Layout(*tabs)
1✔
634

635
    class FieldAccess:
1✔
636
        def __init__(self):
1✔
637
            pass
×
638

639
        internal_notes_write = FieldAccessLevel(
1✔
640
            lambda user, instance: user.has_perm("events.event_view_sensitive", instance),
641
            enable=('internal_notes',)
642
        )
643

644
        hide_internal_notes = FieldAccessLevel(
1✔
645
            lambda user, instance: not user.has_perm("events.event_view_sensitive", instance),
646
            exclude=('internal_notes',)
647
        )
648

649
        event_times = FieldAccessLevel(
1✔
650
            lambda user, instance: user.has_perm('events.edit_event_times', instance),
651
            enable=('datetime_start', 'datetime_setup_complete', 'datetime_end',
652
                'reference_code')
653
        )
654

655
        edit_descriptions = FieldAccessLevel(
1✔
656
            lambda user, instance: user.has_perm('events.edit_event_text', instance),
657
            enable=('event_name', 'location', 'description',
658
                    'lighting_reqs', 'sound_reqs', 'proj_reqs', 'otherservice_reqs', 'max_crew')
659
        )
660

661
        change_owner = FieldAccessLevel(
1✔
662
            lambda user, instance: user.has_perm('events.adjust_event_owner', instance),
663
            enable=('contact', 'org', 'reference_code')
664
        )
665

666
        change_type = FieldAccessLevel(
1✔
667
            lambda user, instance: user.has_perm('events.adjust_event_charges', instance),
668
            enable=('lighting', 'sound', 'projection', 'otherservices', 'billed_in_bulk')
669
        )
670

671
        billing_edit = FieldAccessLevel(
1✔
672
            lambda user, instance: user.has_perm('events.edit_event_fund', instance),
673
            enable=('billing_org', 'billed_in_bulk')
674
        )
675

676
        billing_view = FieldAccessLevel(
1✔
677
            lambda user, instance: not user.has_perm('events.view_event_billing', instance),
678
            exclude=('billing_org', 'billed_in_bulk', 'entered_into_workday')
679
        )
680

681
        change_flags = FieldAccessLevel(
1✔
682
            lambda user, instance: user.has_perm('events.edit_event_flags', instance),
683
            enable=('sensitive', 'test_event')
684
        )
685

686
        change_lnl_contact = FieldAccessLevel(
1✔
687
            lambda user, instance: user.has_perm('events.edit_event_lnl_contact', instance),
688
            enable=('lnl_contact')
689
        )
690
        
691
        change_event_status = FieldAccessLevel(
1✔
692
            lambda user, instance: user.has_perm('events.edit_event_status', instance),
693
            enable=('event_status')
694
        )
695

696
        change_entered_into_workday = FieldAccessLevel(
1✔
697
            lambda user, instance: user.has_perm('events.bill_event', instance),
698
            enable=('entered_into_workday',)
699
        )
700

701
        survey_edit = FieldAccessLevel(
1✔
702
            lambda user, instance: user.has_perm('events.view_posteventsurveyresults') and \
703
                                   (instance is None or not instance.survey_sent), enable=('send_survey',)
704
        )
705

706
        survey_view = FieldAccessLevel(
1✔
707
            lambda user, instance: not user.has_perm('events.view_posteventsurveyresults'),
708
            exclude=('send_survey',)
709
        )
710

711
        cancelled_reason_edit = FieldAccessLevel(
1✔
712
            lambda user, instance: user.has_perm('events.cancel_event') and \
713
                                   (instance is not None and instance.cancelled), enable=('cancelled_reason',)
714
        )
715

716
    class Meta:
1✔
717
        model = Event2019
1✔
718
        fields = ('event_name', 'event_status', 'location', 'lnl_contact', 'description', 'internal_notes', 'billing_org',
1✔
719
                  'billed_in_bulk', 'contact', 'org', 'datetime_setup_complete', 'datetime_start',
720
                  'datetime_end', 'sensitive', 'test_event',
721
                  'entered_into_workday', 'send_survey', 'max_crew','cancelled_reason',
722
                  'reference_code')
723
        widgets = {
1✔
724
            'description': EasyMDEEditor(),
725
            'internal_notes': EasyMDEEditor(),
726
            'cancelled_reason': TextInput(),
727
        }
728

729
    location = GroupedModelChoiceField(
1✔
730
        queryset=Location.objects.filter(show_in_wo_form=True),
731
        group_by_field="building",
732
        group_label=lambda group: group.name,
733
    )
734
    contact = AutoCompleteSelectField('Users', required=False)
1✔
735
    lnl_contact = AutoCompleteSelectField('Members', label="LNL Contact (PM)", required=False)
1✔
736
    org = CustomAutoCompleteSelectMultipleField('Orgs', required=False, label="Client(s)")
1✔
737
    billing_org = AutoCompleteSelectField('Orgs', required=False, label="Client to bill")
1✔
738
    datetime_setup_complete = forms.SplitDateTimeField(initial=timezone.now, label="Setup Completed")
1✔
739
    datetime_start = forms.SplitDateTimeField(initial=timezone.now, label="Event Start")
1✔
740
    datetime_end = forms.SplitDateTimeField(initial=timezone.now, label="Event End")
1✔
741
    max_crew = forms.IntegerField(label="Maximum Crew", help_text="Include this to enforce an occupancy limit",
1✔
742
                                  required=False)
743
    cancelled_reason = forms.CharField(label="Reason for Cancellation", required=False),
1✔
744
    # Regex will match valid 25Live reservation codes in the format
745
    # `2022-ABNXQQ`
746
    reference_code =forms.CharField(validators=[RegexValidator(regex=r"[0-9]{4}-[A-Z]{6}")],
1✔
747
            required = False)
748

749

750
class EventReviewForm(forms.ModelForm):
1✔
751
    def __init__(self, *args, **kwargs):
1✔
752
        event = kwargs.pop('event')
1✔
753
        self.helper = FormHelper()
1✔
754
        self.helper.layout = Layout(
1✔
755
            'org',
756
            'billing_org',
757
            Field('internal_notes', css_class="col-md-6", size="15"),
758
            FormActions(
759
                HTML('<h4> Does this look good to you?</h4>'),
760
                Submit('save', 'Yes!', css_class='btn btn-lg btn-success'),
761
                HTML(
762
                    '<a class="btn btn-lg btn-danger" href="{%% url "events:detail" %s %%}"> Cancel </a>'
763
                    % event.id),
764
            ),
765
        )
766
        super(EventReviewForm, self).__init__(*args, **kwargs)
1✔
767

768
    class Meta:
1✔
769
        model = Event
1✔
770
        fields = ('org', 'billing_org', 'internal_notes')
1✔
771
        widgets = {
1✔
772
            'internal_notes': EasyMDEEditor()
773
        }
774

775
    org = AutoCompleteSelectMultipleField('Orgs', required=True, label="Client(s)")
1✔
776
    billing_org = AutoCompleteSelectField('Orgs', required=False, label="Client to bill")
1✔
777

778

779
class InternalReportForm(FieldAccessForm):
1✔
780
    """ Crew Chief Report Form """
781
    def __init__(self, event, *args, **kwargs):
1✔
782
        self.event = event
1✔
783
        self.helper = FormHelper()
1✔
784
        self.helper.form_class = "form-horizontal"
1✔
785
        self.helper.form_method = "post"
1✔
786
        self.helper.form_action = ""
1✔
787
        self.helper.layout = Layout(
1✔
788
            DynamicFieldContainer('crew_chief'),
789
            HTML('<h4>What you might put in the report:</h4>'
790
                 '<ul><li>How was the event set up?</li>'
791
                 '<li>Roughly what equipment was used?</li>'
792
                 '<li>Were there any last minute changes?</li>'
793
                 '<li>Did you come across any issues?</li>'
794
                 '<li>Would you classify this event as the level it was booked under?</li>'
795
                 '<li>What information would be useful for somebody next year?</li></ul>'),
796
            Field('report', css_class="col-md-10"),
797
            markdown_at_msgs,
798
            FormActions(
799
                Submit('save', 'Save Changes'),
800
                # Reset('reset','Reset Form'),
801
            )
802
        )
803
        super(InternalReportForm, self).__init__(*args, **kwargs)
1✔
804
        if 'crew_chief' in self.fields and not self.fields['crew_chief'].initial:
1✔
805
            self.fields['crew_chief'].initial = self.user.pk
1✔
806

807
    def save(self, commit=True):
1✔
808
        obj = super(InternalReportForm, self).save(commit=False)
1✔
809
        if 'crew_chief' not in self.cleaned_data:
1✔
810
            self.instance.crew_chief = self.user  # user field from FAF
1✔
811
        obj.event = self.event
1✔
812
        if commit:
1✔
813
            obj.save()
1✔
814
            self.save_m2m()
1✔
815
        return obj
1✔
816

817
    class Meta:
1✔
818
        model = CCReport
1✔
819
        fields = ('crew_chief', 'report')
1✔
820
        widgets = {
1✔
821
            'report': EasyMDEEditor()
822
        }
823

824
    crew_chief = AutoCompleteSelectField('Members', required=False)
1✔
825

826
    class FieldAccess:
1✔
827
        def __init__(self):
1✔
828
            pass
×
829

830
        avg_user = FieldAccessLevel(
1✔
831
            lambda user, instance: not user.has_perm('events.add_event_report'),
832
            exclude=('crew_chief',)
833
        )
834
        admin = FieldAccessLevel(
1✔
835
            lambda user, instance: user.has_perm('events.add_event_report'),
836
            enable=('crew_chief',)
837
        )
838
        all = FieldAccessLevel(lambda user, instance: True, enable=('report',))
1✔
839

840

841
# External Organization forms
842

843
class ExternalOrgUpdateForm(forms.ModelForm):
1✔
844
    """ Organization Details Form for Client """
845
    def __init__(self, *args, **kwargs):
1✔
846
        self.helper = FormHelper()
1✔
847
        self.helper.form_class = "form-horizontal"
1✔
848
        self.helper.form_method = 'post'
1✔
849
        self.helper.form_action = ''
1✔
850
        self.helper.layout = Layout(
1✔
851
            'exec_email',
852
            'address',
853
            Field('phone', css_class="bfh-phone", data_format="(ddd) ddd dddd"),
854
            'associated_users',
855
            'workday_fund',
856
            Field('worktag', placeholder='e.g. 1234-CC'),
857
            FormActions(
858
                Submit('save', 'Save Changes'),
859
            )
860
        )
861
        super(ExternalOrgUpdateForm, self).__init__(*args, **kwargs)
1✔
862

863
    associated_users = AutoCompleteSelectMultipleField('Users', required=True)
1✔
864
    worktag = forms.CharField(required=False, help_text='Ends in -AG, -CC, -GF, -GR, or -DE')
1✔
865

866
    def clean_worktag(self):
1✔
867
        pattern = re.compile('[0-9]+-[A-Z][A-Z]')
1✔
868
        if pattern.match(self.cleaned_data['worktag']) is None and self.cleaned_data['worktag'] not in [None, '']:
1✔
869
            raise ValidationError('What you entered is not a valid worktag. Here are some examples of what a worktag '
1✔
870
                                  'looks like: 1234-CC, 123-AG')
871
        return self.cleaned_data['worktag']
1✔
872

873
    class Meta:
1✔
874
        model = Organization
1✔
875
        fields = ('exec_email', 'address', 'phone', 'associated_users', 'workday_fund', 'worktag')
1✔
876

877

878
class OrgXFerForm(forms.ModelForm):
1✔
879
    """ Organization Transfer Form """
880
    def __init__(self, org, user, *args, **kwargs):
1✔
881
        self.user = user
1✔
882
        self.org = org
1✔
883
        self.helper = FormHelper()
1✔
884
        self.helper.form_class = "form-horizontal"
1✔
885
        self.helper.form_method = 'post'
1✔
886
        self.helper.form_action = ''
1✔
887
        self.helper.layout = Layout(
1✔
888
            'new_user_in_charge',
889
            HTML(
890
                '<p class="text-muted">'
891
                'This form will transfer ownership of this Organization to another user associated '
892
                'with the Organization. A confirmation E-Mail will be sent with a link to confirm the '
893
                'transfer.</p>'),
894
            FormActions(
895
                Submit('save', 'Submit Transfer'),
896
            )
897
        )
898
        super(OrgXFerForm, self).__init__(*args, **kwargs)
1✔
899

900
        self.fields['new_user_in_charge'].queryset = org.associated_users.exclude(id=org.user_in_charge_id)
1✔
901

902
    def save(self, commit=True):
1✔
903
        obj = super(OrgXFerForm, self).save(commit=False)
1✔
904
        obj.initiator = self.user
1✔
905
        obj.old_user_in_charge = self.org.user_in_charge
1✔
906
        obj.org = self.org
1✔
907
        if not obj.uuid:
1✔
908
            obj.uuid = uuid.uuid4()
1✔
909
        if commit:
1✔
910
            obj.save()
×
911
        return obj
1✔
912

913
    # new_user_in_charge = AutoCompleteSelectField('Users', required=True,
914
    # plugin_options={'position':"{ my : \"right top\", at: \"right bottom\",
915
    # of: \"#id_person_name_text\"},'minlength':4"})
916
    class Meta:
1✔
917
        model = OrganizationTransfer
1✔
918
        fields = ('new_user_in_charge',)
1✔
919

920

921
class SelfServiceOrgRequestForm(forms.Form):
1✔
922
    def __init__(self, *args, **kwargs):
1✔
923
        self.helper = FormHelper()
1✔
924
        self.helper.form_class = "form-horizontal"
1✔
925
        self.helper.form_method = 'post'
1✔
926
        self.helper.form_action = ''
1✔
927
        self.helper.help_text_inline = True
1✔
928
        self.helper.layout = Layout(
1✔
929
            Field('client_name', help_text_inline=True),
930
            'email',
931
            'address',
932
            Field('phone', css_class="bfh-phone", data_format="(ddd) ddd dddd"),
933
            FormActions(
934
                Submit('save', 'Submit Request'),
935
            )
936
        )
937
        super(SelfServiceOrgRequestForm, self).__init__(*args, **kwargs)
1✔
938

939
    client_name = forms.CharField(max_length=128, label="Client Name", help_text="EX: Lens & Lights")
1✔
940
    email = forms.EmailField(help_text="EX: lnl@wpi.edu (This should be your exec board alias)")
1✔
941
    address = forms.CharField(widget=forms.Textarea, help_text="EX: Campus Center 339")
1✔
942
    phone = forms.CharField(max_length=15, help_text="EX: (508) - 867 - 5309")
1✔
943

944

945
class WorkdayForm(forms.ModelForm):
1✔
946
    """ Workday Bill Pay Form """
947
    def __init__(self, *args, **kwargs):
1✔
948
        self.helper = FormHelper()
1✔
949
        self.helper.layout = Layout(
1✔
950
            'workday_fund',
951
            Field('worktag', placeholder='e.g. 1234-CC'),
952
            'workday_form_comments',
953
            FormActions(
954
                Submit('submit', 'Pay'),
955
            )
956
        )
957
        super(WorkdayForm, self).__init__(*args, **kwargs)
1✔
958
        self.fields['workday_fund'].label = 'Funding source'
1✔
959
        self.fields['workday_fund'].required = True
1✔
960
        self.fields['workday_form_comments'].label = 'Comments'
1✔
961

962
    def clean_worktag(self):
1✔
963
        pattern = re.compile('[0-9]+-[A-Z][A-Z]')
1✔
964
        if pattern.match(self.cleaned_data['worktag']) is None:
1✔
965
            raise ValidationError('What you entered is not a valid worktag. Here are some examples of what a worktag '
1✔
966
                                  'looks like: 1234-CC, 123-AG')
967
        return self.cleaned_data['worktag']
1✔
968

969
    class Meta:
1✔
970
        model = Event2019
1✔
971
        fields = 'workday_fund', 'worktag', 'workday_form_comments'
1✔
972

973
    worktag = forms.CharField(required=True, help_text='Ends in -AG, -CC, -GF, -GR, or -DE')
1✔
974

975

976
# Internal Billing forms
977

978
class BillingForm(forms.ModelForm):
1✔
979
    def __init__(self, event, *args, **kwargs):
1✔
980
        self.helper = FormHelper()
1✔
981
        self.helper.form_class = "form-horizontal"
1✔
982
        self.helper.form_method = "post"
1✔
983
        self.helper.form_action = ""
1✔
984
        self.event = event
1✔
985
        self.helper.layout = Layout(
1✔
986
            PrependedText('date_billed', '<i class="glyphicon glyphicon-calendar"></i>', css_class="datepick"),
987
            PrependedText('amount', '<strong>$</strong>'),
988
            FormActions(
989
                Submit('save-and-return', 'Save and Return'),
990
                Submit('save-and-make-email', 'Save and Make Email'),
991
                Reset('reset', 'Reset Form'),
992
            )
993
        )
994
        super(BillingForm, self).__init__(*args, **kwargs)
1✔
995

996
        self.fields['amount'].initial = "%.2f" % event.cost_total
1✔
997
        self.fields['date_billed'].initial = datetime.date.today()
1✔
998

999
    def save(self, commit=True):
1✔
1000
        obj = super(BillingForm, self).save(commit=False)
1✔
1001
        obj.event = self.event
1✔
1002
        if commit:
1✔
1003
            obj.save()
1✔
1004
        return obj
1✔
1005

1006
    class Meta:
1✔
1007
        model = Billing
1✔
1008
        fields = ('date_billed', 'amount')
1✔
1009

1010

1011
class BillingUpdateForm(forms.ModelForm):
1✔
1012
    def __init__(self, event, *args, **kwargs):
1✔
1013
        self.event = event
1✔
1014
        self.helper = FormHelper()
1✔
1015
        self.helper.form_class = "form-horizontal"
1✔
1016
        self.helper.form_method = "post"
1✔
1017
        self.helper.form_action = ""
1✔
1018

1019
        self.helper.layout = Layout(
1✔
1020
            PrependedText('date_paid', '<i class="glyphicon glyphicon-calendar"></i>', css_class="datepick"),
1021
            PrependedText('amount', '<strong>$</strong>'),
1022
            FormActions(
1023
                Submit('save', 'Save Changes'),
1024
                Reset('reset', 'Reset Form'),
1025
            )
1026
        )
1027
        super(BillingUpdateForm, self).__init__(*args, **kwargs)
1✔
1028

1029
        self.fields['amount'].initial = str(event.cost_total)
1✔
1030
        self.fields['date_paid'].initial = datetime.date.today()
1✔
1031

1032
    def save(self, commit=True):
1✔
1033
        obj = super(BillingUpdateForm, self).save(commit=False)
1✔
1034
        obj.event = self.event
1✔
1035
        if commit:
1✔
1036
            obj.save()
1✔
1037
        return obj
1✔
1038

1039
    class Meta:
1✔
1040
        model = Billing
1✔
1041
        fields = ('date_paid', 'amount')
1✔
1042

1043

1044
class MultiBillingForm(forms.ModelForm):
1✔
1045
    def __init__(self, *args, **kwargs):
1✔
1046
        show_nonbulk_events = kwargs.pop('show_nonbulk_events')
1✔
1047
        self.helper = FormHelper()
1✔
1048
        self.helper.form_class = "form-horizontal"
1✔
1049
        self.helper.form_method = "post"
1✔
1050
        self.helper.form_action = ""
1✔
1051
        layout = []
1✔
1052
        if show_nonbulk_events:
1✔
1053
            layout += [
×
1054
                HTML(
1055
                    '<a href="?show_nonbulk_events=false">Switch back to showing only events marked for '
1056
                    'semester billing</a>')
1057
            ]
1058
        else:
1059
            layout += [
1✔
1060
                HTML('<a href="?show_nonbulk_events=true">Show events not marked for semester billing</a>')
1061
            ]
1062
        layout += [
1✔
1063
            'events',
1064
            PrependedText('date_billed', '<i class="glyphicon glyphicon-calendar"></i>', css_class="datepick"),
1065
            PrependedText('amount', '<strong>$</strong>'),
1066
            FormActions(
1067
                Submit('save-and-return', 'Save and Return'),
1068
                Submit('save-and-make-email', 'Save and Make Email'),
1069
                Reset('reset', 'Reset Form'),
1070
            )
1071
        ]
1072
        self.helper.layout = Layout(*layout)
1✔
1073
        super(MultiBillingForm, self).__init__(*args, **kwargs)
1✔
1074
        if show_nonbulk_events is True:
1✔
1075
            self.fields['events'].queryset = BaseEvent.objects.filter(closed=False, reviewed=True,
×
1076
                                                                      billings__isnull=True) \
1077
                .exclude(multibillings__isnull=False, multibillings__date_paid__isnull=False)
1078
            self.fields['events'].help_text = 'Only unbilled, reviewed events are listed above.'
×
1079
        self.fields['date_billed'].initial = datetime.date.today()
1✔
1080

1081
    def clean(self):
1✔
1082
        cleaned_data = super(MultiBillingForm, self).clean()
1✔
1083
        events = cleaned_data.get('events', [])
1✔
1084
        if events:
1✔
1085
            org = events[0].org_to_be_billed
1✔
1086
            for event in events[1:]:
1✔
1087
                if event.org_to_be_billed != org:
1✔
1088
                    raise ValidationError('All events you select must have the same \'Client to bill\'.')
1✔
1089
        return cleaned_data
1✔
1090

1091
    def save(self, commit=True):
1✔
1092
        instance = super(MultiBillingForm, self).save(commit=False)
1✔
1093
        instance.org = self.cleaned_data['events'].first().org_to_be_billed
1✔
1094
        if commit:
1✔
1095
            instance.save()
1✔
1096
            self.save_m2m()
1✔
1097
        return instance
1✔
1098

1099
    class Meta:
1✔
1100
        model = MultiBilling
1✔
1101
        fields = ('events', 'date_billed', 'amount')
1✔
1102

1103
    events = CustomEventModelMultipleChoiceField(
1✔
1104
        queryset=BaseEvent.objects.filter(closed=False, reviewed=True, billings__isnull=True, billed_in_bulk=True) \
1105
            .exclude(multibillings__isnull=False, multibillings__date_paid__isnull=False),
1106
        widget=forms.CheckboxSelectMultiple(attrs={'class': 'checkbox'}),
1107
        help_text="Only unbilled, reviewed events that are marked for semester billing are listed above."
1108
    )
1109

1110

1111
class MultiBillingUpdateForm(forms.ModelForm):
1✔
1112
    def __init__(self, *args, **kwargs):
1✔
1113
        super(MultiBillingUpdateForm, self).__init__(*args, **kwargs)
1✔
1114
        self.helper = FormHelper()
1✔
1115
        self.helper.form_class = "form-horizontal"
1✔
1116
        self.helper.form_method = "post"
1✔
1117
        self.helper.form_action = ""
1✔
1118
        events_str = ', '.join(map(lambda event: event.event_name, self.instance.events.all()))
1✔
1119
        self.helper.layout = Layout(
1✔
1120
            HTML('<p>Events: ' + events_str + '</p>'),
1121
            PrependedText('date_paid', '<i class="glyphicon glyphicon-calendar"></i>', css_class="datepick"),
1122
            PrependedText('amount', '<strong>$</strong>'),
1123
            FormActions(
1124
                Submit('save', 'Save Changes'),
1125
                Reset('reset', 'Reset Form'),
1126
            )
1127
        )
1128
        self.fields['date_paid'].initial = datetime.date.today()
1✔
1129

1130
    class Meta:
1✔
1131
        model = MultiBilling
1✔
1132
        fields = ('date_paid', 'amount')
1✔
1133

1134

1135
class BillingEmailForm(forms.ModelForm):
1✔
1136
    def __init__(self, billing, *args, **kwargs):
1✔
1137
        super(BillingEmailForm, self).__init__(*args, **kwargs)
1✔
1138
        self.billing = billing
1✔
1139
        orgs = billing.event.org.all()
1✔
1140
        self.fields["email_to_orgs"].queryset = orgs
1✔
1141
        self.fields["email_to_orgs"].initial = orgs
1✔
1142
        if billing.event.contact:
1✔
1143
            self.fields["email_to_users"].initial = [billing.event.contact.pk]
×
1144
        self.helper = FormHelper()
1✔
1145
        self.helper.form_class = 'form-horizontal'
1✔
1146
        self.helper.label_class = 'col-lg-2'
1✔
1147
        self.helper.field_class = 'col-lg-10'
1✔
1148
        self.helper.layout = Layout(
1✔
1149
            HTML('<p class="text-muted">The bill PDF for this event will be attached to the email. The message you '
1150
                 'type below is not the entire contents of the email; a large "PAY NOW" button and identifying '
1151
                 'information about the event being billed will be added to the email below your message.</p>'),
1152
            'subject',
1153
            'message',
1154
            'email_to_users',
1155
            'email_to_orgs',
1156
            FormActions(
1157
                Submit('save', 'Send Email'),
1158
            )
1159
        )
1160

1161
    def clean(self):
1✔
1162
        cleaned_data = super(BillingEmailForm, self).clean()
1✔
1163
        if not cleaned_data.get('email_to_users', None) and not cleaned_data.get('email_to_orgs', None):
1✔
1164
            raise ValidationError('No recipients')
×
1165
        for user in map(lambda id: get_user_model().objects.get(id=id), cleaned_data.get('email_to_users', [])):
1✔
1166
            if not user.email:
1✔
1167
                raise ValidationError('User %s has no email address on file' % user.get_full_name())
×
1168
        for org in cleaned_data.get('email_to_orgs', []):
1✔
1169
            if not org.exec_email:
×
1170
                raise ValidationError('Organization %s has no email address on file' % org.name)
×
1171
        return cleaned_data
1✔
1172

1173
    def save(self, commit=True):
1✔
1174
        instance = super(BillingEmailForm, self).save(commit=False)
1✔
1175
        instance.billing = self.billing
1✔
1176
        if commit:
1✔
1177
            instance.save()
1✔
1178
            self.save_m2m()
1✔
1179
        return instance
1✔
1180

1181
    class Meta:
1✔
1182
        model = BillingEmail
1✔
1183
        fields = ('subject', 'message', 'email_to_users', 'email_to_orgs')
1✔
1184

1185
    email_to_users = AutoCompleteSelectMultipleField('Users', required=False, label="User Recipients")
1✔
1186
    email_to_orgs = CustomOrganizationEmailModelMultipleChoiceField(
1✔
1187
        queryset=Organization.objects.all(),
1188
        widget=forms.CheckboxSelectMultiple(attrs={'class': 'checkbox'}),
1189
        required=False, label="Client Recipients",
1190
        help_text="The email address listed is the client's exec email alias on file."
1191
    )
1192

1193

1194
class MultiBillingEmailForm(forms.ModelForm):
1✔
1195
    def __init__(self, multibilling, *args, **kwargs):
1✔
1196
        super(MultiBillingEmailForm, self).__init__(*args, **kwargs)
1✔
1197
        self.multibilling = multibilling
1✔
1198
        orgsets = map(lambda event: event.org.all(), multibilling.events.all())
1✔
1199
        orgs = next(iter(orgsets))
1✔
1200
        for orgset in orgsets:
1✔
1201
            orgs |= orgset
1✔
1202
        orgs = orgs.distinct()
1✔
1203
        contacts = map(lambda event: event.contact.pk, multibilling.events.filter(contact__isnull=False))
1✔
1204
        self.fields["email_to_orgs"].queryset = orgs
1✔
1205
        self.fields["email_to_orgs"].initial = multibilling.org
1✔
1206
        self.fields["email_to_users"].initial = contacts
1✔
1207
        self.helper = FormHelper()
1✔
1208
        self.helper.form_class = 'form-horizontal'
1✔
1209
        self.helper.label_class = 'col-lg-2'
1✔
1210
        self.helper.field_class = 'col-lg-10'
1✔
1211
        self.helper.layout = Layout(
1✔
1212
            HTML('<p class="text-muted">The bill PDF will be attached to the email.</p>'),
1213
            'subject',
1214
            'message',
1215
            'email_to_users',
1216
            'email_to_orgs',
1217
            FormActions(
1218
                Submit('save', 'Send Email'),
1219
            )
1220
        )
1221

1222
    def clean(self):
1✔
1223
        cleaned_data = super(MultiBillingEmailForm, self).clean()
1✔
1224
        if not cleaned_data.get('email_to_users', None) and not cleaned_data.get('email_to_orgs', None):
1✔
1225
            raise ValidationError('No recipients')
×
1226
        for user in map(lambda id: get_user_model().objects.get(id=id), cleaned_data.get('email_to_users', [])):
1✔
1227
            if not user.email:
1✔
1228
                raise ValidationError('User %s has no email address on file' % user.get_full_name())
×
1229
        for org in cleaned_data.get('email_to_orgs', []):
1✔
1230
            if not org.exec_email:
×
1231
                raise ValidationError('Organization %s has no email address on file' % org.name)
×
1232
        return cleaned_data
1✔
1233

1234
    def save(self, commit=True):
1✔
1235
        instance = super(MultiBillingEmailForm, self).save(commit=False)
1✔
1236
        instance.multibilling = self.multibilling
1✔
1237
        if commit:
1✔
1238
            instance.save()
1✔
1239
            self.save_m2m()
1✔
1240
        return instance
1✔
1241

1242
    class Meta:
1✔
1243
        model = MultiBillingEmail
1✔
1244
        fields = ('subject', 'message', 'email_to_users', 'email_to_orgs')
1✔
1245

1246
    email_to_users = AutoCompleteSelectMultipleField('Users', required=False, label="User Recipients")
1✔
1247
    email_to_orgs = CustomOrganizationEmailModelMultipleChoiceField(
1✔
1248
        queryset=Organization.objects.all(),
1249
        widget=forms.CheckboxSelectMultiple(attrs={'class': 'checkbox'}),
1250
        required=False, label="Client Recipients",
1251
        help_text="The email address listed is the client's exec email alias on file."
1252
    )
1253

1254

1255
# CC Facing Forms
1256
class MKHoursForm(forms.ModelForm):
1✔
1257
    """ Event Hours Form """
1258
    def __init__(self, event, *args, **kwargs):
1✔
1259
        self.event = event
1✔
1260
        self.helper = FormHelper()
1✔
1261
        self.helper.form_class = "form-horizontal"
1✔
1262
        self.helper.form_method = "post"
1✔
1263
        self.helper.form_action = ""
1✔
1264
        self.helper.layout = Layout(
1✔
1265
            Field('user'),
1266
            Field('hours'),
1267
            Field('service'),
1268
            FormActions(
1269
                Submit('save', 'Save Changes'),
1270
                # Reset('reset','Reset Form'),
1271
            )
1272
        )
1273
        super(MKHoursForm, self).__init__(*args, **kwargs)
1✔
1274
        self.fields['service'].queryset = get_qs_from_event(event)
1✔
1275
        if isinstance(event, Event2019):
1✔
1276
            self.fields['category'].queryset = Category.objects.filter(
1✔
1277
                pk__in=event.serviceinstance_set.values_list('service__category', flat=True))
1278

1279
    def clean(self):
1✔
1280
        super(MKHoursForm, self).clean()
1✔
1281
        category = self.cleaned_data.get('category')
1✔
1282
        service = self.cleaned_data.get('service')
1✔
1283
        user = self.cleaned_data.get('user')
1✔
1284
        if user is None:
1✔
1285
            # this problem will raise an error elsewhere
1286
            return
×
1287
        if self.event.hours.filter(user=user, category=category, service=service).exists() and not self.instance.pk:
1✔
1288
            raise ValidationError("User already has hours for this category/service. Edit those instead")
×
1289

1290
    def save(self, commit=True):
1✔
1291
        obj = super(MKHoursForm, self).save(commit=False)
1✔
1292
        if obj.category is None and obj.service is not None:
1✔
1293
            obj.category = obj.service.category
1✔
1294
        obj.event = self.event
1✔
1295
        if commit:
1✔
1296
            obj.save()
1✔
1297
        return obj
1✔
1298

1299
    class Meta:
1✔
1300
        model = Hours
1✔
1301
        fields = ('user', 'hours', 'category', 'service')
1✔
1302

1303
    user = AutoCompleteSelectField('Users', required=True)
1✔
1304
    hours = forms.DecimalField(min_value=decimal.Decimal("0.00"))
1✔
1305
    category = ModelChoiceField(queryset=Category.objects.all(), required=False)
1✔
1306
    service = ModelChoiceField(queryset=Service.objects.all(), required=False)  # queryset gets changed in constructor
1✔
1307

1308

1309
class EditHoursForm(forms.ModelForm):
1✔
1310
    def __init__(self, *args, **kwargs):
1✔
1311
        self.helper = FormHelper()
1✔
1312
        self.helper.form_class = "form-horizontal"
1✔
1313
        self.helper.form_method = "post"
1✔
1314
        self.helper.form_action = ""
1✔
1315
        self.helper.layout = Layout(
1✔
1316
            Field('hours'),
1317
            FormActions(
1318
                Submit('save', 'Save Changes'),
1319
            )
1320
        )
1321
        super(EditHoursForm, self).__init__(*args, **kwargs)
1✔
1322

1323
    class Meta:
1✔
1324
        model = Hours
1✔
1325
        fields = ('hours',)
1✔
1326

1327

1328
class CCIForm(forms.ModelForm):
1✔
1329
    """ Crew Chief Instance form """
1330
    def __init__(self, event, *args, **kwargs):
1✔
1331
        self.event = event
1✔
1332
        self.helper = FormHelper()
1✔
1333
        self.helper.form_class = "form-inline"
1✔
1334
        self.helper.template = 'bootstrap/table_inline_formset.html'
1✔
1335
        self.helper.form_tag = False
1✔
1336
        self.helper.layout = Layout(
1✔
1337
            Field('crew_chief', placeholder="Crew Chief", title=""),
1338
            Field('service' if isinstance(event, Event) else 'category'),
1339
            Field('setup_location'),
1340
            Field('setup_start', css_class="dtp"),
1341
            HTML('<hr>'),
1342
        )
1343
        super(CCIForm, self).__init__(*args, **kwargs)
1✔
1344

1345
        # x = self.instance.event.lighting
1346
        self.fields['service'].queryset = get_qs_from_event(event)
1✔
1347
        if isinstance(event, Event2019):
1✔
1348
            self.fields['category'].queryset = Category.objects.filter(
1✔
1349
                pk__in=event.serviceinstance_set.values_list('service__category', flat=True))
1350
        self.fields['setup_start'].initial = self.fields['setup_start'].prepare_value(
1✔
1351
            self.event.datetime_setup_complete.replace(second=0, microsecond=0)
1352
        )
1353

1354
    def clean(self):
1✔
1355
        cleaned_data = super(CCIForm, self).clean()
1✔
1356
        if cleaned_data.get('category') is None and cleaned_data.get('service') is None:
1✔
1357
            self.add_error('category', 'category/service is a required field')
×
1358
            self.add_error('service', 'category/service is a required field')
×
1359
        return cleaned_data
1✔
1360

1361
    def save(self, commit=True):
1✔
1362
        obj = super(CCIForm, self).save(commit=False)
1✔
1363
        try:
1✔
1364
            obj.category
1✔
1365
        except Category.DoesNotExist:
1✔
1366
            try:
1✔
1367
                obj.category = obj.service.category
1✔
1368
            except Service.DoesNotExist:
×
1369
                pass
×
1370
        obj.event = self.event
1✔
1371
        if commit:
1✔
1372
            obj.save()
1✔
1373
        return obj
1✔
1374

1375
    class Meta:
1✔
1376
        model = EventCCInstance
1✔
1377
        fields = ('category', 'crew_chief', 'service', 'setup_location', 'setup_start')
1✔
1378

1379
    crew_chief = AutoCompleteSelectField('Members', required=True)
1✔
1380
    setup_start = forms.SplitDateTimeField(initial=timezone.now)
1✔
1381
    setup_location = GroupedModelChoiceField(
1✔
1382
        queryset=Location.objects.filter(setup_only=True).select_related('building'),
1383
        group_by_field="building",
1384
        group_label=lambda group: group.name,
1385
    )
1386
    category = ModelChoiceField(queryset=Category.objects.all(), required=False)
1✔
1387
    service = ModelChoiceField(queryset=Service.objects.all(), required=False)  # queryset gets changed in constructor
1✔
1388

1389

1390
# Forms for Inline Formsets
1391
class AttachmentForm(forms.ModelForm):
1✔
1392
    def __init__(self, event, externally_uploaded=False, *args, **kwargs):
1✔
1393
        self.event = event
1✔
1394
        self.helper = FormHelper()
1✔
1395
        self.helper.form_class = "form-inline"
1✔
1396
        self.helper.template = 'bootstrap/table_inline_formset.html'
1✔
1397
        self.helper.form_tag = False
1✔
1398
        self.helper.layout = Layout(
1✔
1399
            Field('for_service'),
1400
            Field('attachment'),
1401
            Field('note', size="2"),
1402
            Hidden('externally_uploaded', externally_uploaded),
1403
            HTML('<hr>'),
1404
        )
1405
        super(AttachmentForm, self).__init__(*args, **kwargs)
1✔
1406

1407
        # x = self.instance.event.lighting
1408
        self.fields['for_service'].queryset = get_qs_from_event(event)
1✔
1409

1410
    def save(self, commit=True):
1✔
1411
        obj = super(AttachmentForm, self).save(commit=False)
1✔
1412
        obj.event = self.event
1✔
1413
        if commit:
1✔
1414
            obj.save()
1✔
1415
            self.save_m2m()
1✔
1416
        return obj
1✔
1417

1418
    class Meta:
1✔
1419
        model = EventAttachment
1✔
1420
        fields = ('for_service', 'attachment', 'note')
1✔
1421

1422

1423
class ExtraForm(forms.ModelForm):
1✔
1424
    class Meta:
1✔
1425
        model = ExtraInstance
1✔
1426
        fields = ('extra', 'quant')
1✔
1427

1428
    extra = GroupedModelChoiceField(
1✔
1429
        queryset=Extra.objects.filter(disappear=False),
1430
        group_by_field="category",
1431
        group_label=lambda group: group.name,
1432
    )
1433

1434

1435
class ServiceInstanceForm(forms.ModelForm):
1✔
1436
    def __init__(self, event, *args, **kwargs):
1✔
1437
        self.event = event
1✔
1438
        super(ServiceInstanceForm, self).__init__(*args, **kwargs)
1✔
1439

1440
    def save(self, commit=True):
1✔
1441
        obj = super(ServiceInstanceForm, self).save(commit=False)
1✔
1442
        obj.event = self.event
1✔
1443
        if commit:
1✔
1444
            obj.save()
1✔
1445
        return obj
1✔
1446

1447
    class Meta:
1✔
1448
        model = ServiceInstance
1✔
1449
        fields = ('service', 'detail')
1✔
1450
        widgets = {
1✔
1451
            'detail': EasyMDEEditor(attrs={"show_preview":False}),
1452
        }
1453

1454
    service = ModelChoiceField(queryset=Service.objects.filter(enabled_event2019=True))
1✔
1455

1456

1457
# CrewChiefFS = inlineformset_factory(Event,EventCCInstance,extra=3,form=CCIForm, exclude=[])
1458

1459

1460
# usage
1461
# CrewChiefFS = inlineformset_factory(Event,EventCCInstance,extra=3, exclude=[])
1462
# CrewChiefFS.form = staticmethod(curry(CCIForm, event=request.event))
1463

1464
# Workorder Forms
1465

1466
# Workorder Repeat Form
1467
class WorkorderRepeatForm(forms.ModelForm):
1✔
1468
    def __init__(self, *args, **kwargs):
1✔
1469
        self.helper = FormHelper()
×
1470
        self.helper.form_class = "form-horizontal"
×
1471
        self.helper.layout = Layout(
×
1472
            Field('location'),
1473
            Field('event_name'),
1474
            TabHolder(
1475
                Tab(
1476
                    "Main Information",
1477
                    Field('description', label="Description (optional)", css_class="col-md-6"),
1478
                    Field('datetime_setup_complete', label="Setup Finish", css_class="dtp"),
1479
                    Field('datetime_start', label="Event Start", css_class="dtp"),
1480
                    Field('datetime_end', label="Event End", css_class="dtp"),
1481
                ),
1482
                Tab(
1483
                    "Lighting",
1484
                    InlineRadios('lighting', title="Lighting", empty_label=None),
1485
                    Field('lighting_reqs', css_class="col-md-8"),
1486
                ),
1487
                Tab(
1488
                    "Sound",
1489
                    InlineRadios('sound', title="Sound"),
1490
                    Field('sound_reqs', css_class="col-md-8"),
1491
                ),
1492
                Tab(
1493
                    "Projection",
1494
                    InlineRadios('projection', title="Projection"),
1495
                    Field('proj_reqs', css_class="col-md-8"),
1496
                ),
1497
                Tab(
1498
                    "Additional Information",
1499
                    InlineCheckboxes('otherservices'),
1500
                    Field('otherservice_reqs', css_class="col-md-8")
1501
                ),
1502
            ),
1503
            FormActions(
1504
                Submit('save', 'Repeat Event'),
1505
            ),
1506
        )
1507
        super(WorkorderRepeatForm, self).__init__(*args, **kwargs)
×
1508

1509
    datetime_setup_complete = forms.SplitDateTimeField(label="Setup Completed By", required=True)
1✔
1510
    datetime_start = forms.SplitDateTimeField(label="Event Starts")
1✔
1511
    datetime_end = forms.SplitDateTimeField(label="Event Ends")
1✔
1512
    location = GroupedModelChoiceField(
1✔
1513
        queryset=Location.objects.filter(show_in_wo_form=True),
1514
        group_by_field="building",
1515
        group_label=lambda group: group.name,
1516
    )
1517
    lighting = forms.ModelChoiceField(
1✔
1518
        empty_label=None,
1519
        queryset=Lighting.objects.all(),
1520
        widget=forms.RadioSelect(attrs={'class': 'radio itt'}),
1521
        required=False
1522
    )
1523
    sound = forms.ModelChoiceField(
1✔
1524
        empty_label=None,
1525
        queryset=Sound.objects.all(),
1526
        widget=forms.RadioSelect(attrs={'class': 'radio itt'}),
1527
        required=False
1528
    )
1529
    projection = forms.ModelChoiceField(
1✔
1530
        empty_label=None,
1531
        queryset=Projection.objects.all(),
1532
        widget=forms.RadioSelect(attrs={'class': 'radio itt'}),
1533
        required=False
1534
    )
1535
    otherservices = forms.ModelMultipleChoiceField(
1✔
1536
        queryset=Service.objects.filter(category__name__in=["Misc", "Power"]),
1537
        widget=forms.CheckboxSelectMultiple(attrs={'class': 'checkbox'}),
1538
        required=False
1539
    )
1540

1541
    class Meta:
1✔
1542
        model = Event
1✔
1543
        fields = ['location', 'event_name', 'description', 'datetime_start', 'datetime_end', 'datetime_setup_complete',
1✔
1544
                  'lighting', 'lighting_reqs', 'sound', 'sound_reqs', 'projection', 'proj_reqs', 'otherservices',
1545
                  'otherservice_reqs']
1546

1547
    def clean(self):  # custom validation
1✔
1548
        cleaned_data = super(WorkorderRepeatForm, self).clean()
×
1549

1550
        # time validation
1551
        setup_complete = cleaned_data.get("datetime_setup_complete")
×
1552
        event_start = cleaned_data.get("datetime_start")
×
1553
        event_end = cleaned_data.get("datetime_end")
×
1554

1555
        if not setup_complete or not event_start or not event_end:
×
1556
            raise ValidationError('Please enter in a valid time in each field')
×
1557
        if event_start > event_end:
×
1558
            raise ValidationError('You cannot start after you finish')
×
1559
        if setup_complete > event_start:
×
1560
            raise ValidationError('You cannot setup after you finish')
×
NEW
1561
        if setup_complete < datetime.datetime.now(datetime.timezone.utc):
×
1562
            raise ValidationError('Stop trying to time travel')
×
1563

1564
        # service exists validation
1565
        lighting = cleaned_data.get("lighting", None)
×
1566
        sound = cleaned_data.get("sound", None)
×
1567
        projection = cleaned_data.get("projection", None)
×
1568
        otherservices = cleaned_data.get("otherservices", None)
×
1569

1570
        if not (lighting or sound or projection or not otherservices):
×
1571
            raise ValidationError(
×
1572
                'Please select at least one service, %s %s %s %s' % (lighting, sound, projection, otherservices))
1573

1574
        return cleaned_data
×
1575

1576

1577
class PostEventSurveyForm(forms.ModelForm):
1✔
1578

1579
    def __init__(self, event, *args, **kwargs):
1✔
1580
        self.event = event
1✔
1581
        self.helper = FormHelper()
1✔
1582
        self.helper.form_class = "custom-survey-form"
1✔
1583
        self.helper.layout = Layout(
1✔
1584
            Div(
1585
                Div(
1586
                    HTML('<div class="striped">'),
1587
                    'services_quality',
1588
                    HTML('</div>'),
1589
                    'lighting_quality',
1590
                    HTML('<div class="striped">'),
1591
                    'sound_quality',
1592
                    HTML('</div>'),
1593
                    HTML(
1594
                        '<div class="row" style="padding-top: 1%; padding-bottom: 1%; padding-left: 1%">'
1595
                        '<div class="col-md-4">'
1596
                    ),
1597
                    'work_order_method',
1598
                    HTML('</div></div>'),
1599
                    HTML('<div style="border-bottom: 2px solid gray; padding-bottom: 1%; margin: 0"></div>'),
1600
                    HTML('<div id="workorder" style="display: none"><div class="striped">'),
1601
                    'work_order_experience',
1602
                    HTML('</div>'),
1603
                    'work_order_ease',
1604
                    HTML('<div class="striped" style="padding-bottom: 1%; margin-bottom: 1%">'),
1605
                    Field('work_order_comments', style='max-width: 75%; margin: 1%;'),
1606
                    HTML('</div></div>'),
1607
                    css_class='col'
1608
                ),
1609
                css_class='row'
1610
            ),
1611
            Div(
1612
                Div(
1613
                    HTML('<br><br><h2>Please rate your level of agreement with the following statements.</h2><br>'),
1614
                    HTML('<div class="striped">'),
1615
                    'communication_responsiveness',
1616
                    HTML('</div>'),
1617
                    'pricelist_ux',
1618
                    HTML('<div class="striped">'),
1619
                    'setup_on_time',
1620
                    HTML('</div>'),
1621
                    'crew_respectfulness',
1622
                    HTML('<div class="striped">'),
1623
                    'price_appropriate',
1624
                    HTML('</div>'),
1625
                    'customer_would_return',
1626
                    HTML('<br>'),
1627
                    css_class='col'
1628
                ),
1629
                css_class='row'
1630
            ),
1631
            Div(
1632
                Div(
1633
                    HTML('<h2>Additional Comments</h2>'),
1634
                    'comments',
1635
                    css_class='col'
1636
                ),
1637
                css_class='row'
1638
            ),
1639
            FormActions(
1640
                Submit('save', 'Submit'),
1641
            ),
1642
        )
1643
        super(PostEventSurveyForm, self).__init__(*args, **kwargs)
1✔
1644

1645
    def save(self, commit=True):
1✔
1646
        obj = super(PostEventSurveyForm, self).save(commit=False)
1✔
1647
        obj.event = self.event
1✔
1648
        if commit:
1✔
1649
            obj.save()
1✔
1650
        return obj
1✔
1651

1652
    class Meta:
1✔
1653
        model = PostEventSurvey
1✔
1654
        fields = ('services_quality', 'lighting_quality', 'sound_quality', 'work_order_method', 'work_order_experience',
1✔
1655
                  'work_order_ease', 'work_order_comments', 'communication_responsiveness', 'pricelist_ux',
1656
                  'setup_on_time', 'crew_respectfulness', 'price_appropriate', 'customer_would_return', 'comments')
1657

1658
    services_quality = forms.ChoiceField(
1✔
1659
        label=PostEventSurvey._meta.get_field('services_quality').verbose_name,
1660
        widget=SurveyCustomRadioSelect,
1661
        choices=PostEventSurvey._meta.get_field('services_quality').choices,
1662
    )
1663

1664
    lighting_quality = forms.ChoiceField(
1✔
1665
        label=PostEventSurvey._meta.get_field('lighting_quality').verbose_name,
1666
        widget=SurveyCustomRadioSelect,
1667
        choices=PostEventSurvey._meta.get_field('lighting_quality').choices,
1668
    )
1669

1670
    sound_quality = forms.ChoiceField(
1✔
1671
        label=PostEventSurvey._meta.get_field('sound_quality').verbose_name,
1672
        widget=SurveyCustomRadioSelect,
1673
        choices=PostEventSurvey._meta.get_field('sound_quality').choices,
1674
    )
1675

1676
    work_order_method = forms.ChoiceField(
1✔
1677
        label=PostEventSurvey._meta.get_field('work_order_method').verbose_name,
1678
        widget=forms.Select,
1679
        choices=PostEventSurvey._meta.get_field('work_order_method').choices,
1680
    )
1681

1682
    work_order_ease = forms.ChoiceField(
1✔
1683
        label=PostEventSurvey._meta.get_field('work_order_ease').verbose_name,
1684
        widget=SurveyCustomRadioSelect,
1685
        choices=PostEventSurvey._meta.get_field('work_order_ease').choices,
1686
    )
1687

1688
    work_order_experience = forms.ChoiceField(
1✔
1689
        label=PostEventSurvey._meta.get_field('work_order_experience').verbose_name,
1690
        widget=SurveyCustomRadioSelect,
1691
        choices=PostEventSurvey._meta.get_field('work_order_experience').choices,
1692
    )
1693

1694
    communication_responsiveness = forms.ChoiceField(
1✔
1695
        label=PostEventSurvey._meta.get_field('communication_responsiveness').verbose_name,
1696
        widget=SurveyCustomRadioSelect,
1697
        choices=PostEventSurvey._meta.get_field('communication_responsiveness').choices,
1698
    )
1699

1700
    pricelist_ux = forms.ChoiceField(
1✔
1701
        label=PostEventSurvey._meta.get_field('pricelist_ux').verbose_name,
1702
        widget=SurveyCustomRadioSelect,
1703
        choices=PostEventSurvey._meta.get_field('pricelist_ux').choices,
1704
    )
1705

1706
    setup_on_time = forms.ChoiceField(
1✔
1707
        label=PostEventSurvey._meta.get_field('setup_on_time').verbose_name,
1708
        widget=SurveyCustomRadioSelect,
1709
        choices=PostEventSurvey._meta.get_field('setup_on_time').choices,
1710
    )
1711

1712
    crew_respectfulness = forms.ChoiceField(
1✔
1713
        label=PostEventSurvey._meta.get_field('crew_respectfulness').verbose_name,
1714
        widget=SurveyCustomRadioSelect,
1715
        choices=PostEventSurvey._meta.get_field('crew_respectfulness').choices,
1716
    )
1717

1718
    price_appropriate = forms.ChoiceField(
1✔
1719
        label=PostEventSurvey._meta.get_field('price_appropriate').verbose_name,
1720
        widget=SurveyCustomRadioSelect,
1721
        choices=PostEventSurvey._meta.get_field('price_appropriate').choices,
1722
    )
1723

1724
    customer_would_return = forms.ChoiceField(
1✔
1725
        label=PostEventSurvey._meta.get_field('customer_would_return').verbose_name,
1726
        widget=SurveyCustomRadioSelect,
1727
        choices=PostEventSurvey._meta.get_field('customer_would_return').choices,
1728
    )
1729

1730

1731
class OfficeHoursForm(forms.ModelForm):
1✔
1732
    def __init__(self, *args, **kwargs):
1✔
1733
        self.helper = FormHelper()
1✔
1734
        self.helper.form_class = "form-horizontal"
1✔
1735
        self.helper.form_method = "post"
1✔
1736
        self.helper.form_action = ""
1✔
1737
        self.helper.form_show_labels = False
1✔
1738
        self.helper.layout = Layout(
1✔
1739
            Field('day'),
1740
            Field('location'),
1741
            Field('hour_start'),
1742
            Field('hour_end'),
1743
            FormActions(
1744
                Submit('save', 'Save Changes'),
1745
            )
1746
        )
1747
        super(OfficeHoursForm, self).__init__(*args, **kwargs)
1✔
1748
        self.fields['location'].queryset = Location.objects.filter(setup_only=True)
1✔
1749

1750
    class Meta:
1✔
1751
        model = OfficeHour
1✔
1752
        fields = ('day', 'location', 'hour_start', 'hour_end')
1✔
1753

1754

1755
class WorkshopForm(forms.ModelForm):
1✔
1756
    def __init__(self, *args, **kwargs):
1✔
1757
        self.helper = FormHelper()
1✔
1758
        self.helper.form_class = "form-horizontal col-md-6"
1✔
1759
        self.helper.form_method = "post"
1✔
1760
        self.helper.form_action = ""
1✔
1761
        self.helper.layout = Layout(
1✔
1762
            Field('name'),
1763
            Field('instructors'),
1764
            Field('description'),
1765
            Field('location'),
1766
            Field('notes'),
1767
            FormActions(
1768
                Submit('save', 'Save')
1769
            )
1770
        )
1771
        super(WorkshopForm, self).__init__(*args, **kwargs)
1✔
1772

1773
    class Meta:
1✔
1774
        model = Workshop
1✔
1775
        fields = ('name', 'instructors', 'description', 'location', 'notes')
1✔
1776

1777

1778
class WorkshopDatesForm(forms.ModelForm):
1✔
1779
    def __init__(self, *args, **kwargs):
1✔
1780
        self.helper = FormHelper()
1✔
1781
        self.helper.form_class = "form-horizontal col-md-6"
1✔
1782
        self.helper.form_method = "post"
1✔
1783
        self.helper.form_action = ""
1✔
1784
        self.helper.layout = Layout(
1✔
1785
            Field('workshop'),
1786
            Field('date', css_class="form-control"),
1787
            FormActions(
1788
                Submit('save', 'Save')
1789
            )
1790
        )
1791
        super(WorkshopDatesForm, self).__init__(*args, **kwargs)
1✔
1792

1793
    date = forms.SplitDateTimeField(required=False)
1✔
1794

1795
    class Meta:
1✔
1796
        model = WorkshopDate
1✔
1797
        fields = ('workshop', 'date')
1✔
1798

1799

1800
class CrewCheckinForm(forms.Form):
1✔
1801
    def __init__(self, *args, **kwargs):
1✔
1802
        events = kwargs.pop('events')
1✔
1803
        title = kwargs.pop('title')
1✔
1804
        self.helper = FormHelper()
1✔
1805
        self.helper.form_class = "form-horizontal col-md-6 m-auto"
1✔
1806
        self.helper.form_method = "post"
1✔
1807
        self.helper.form_action = ""
1✔
1808
        self.helper.layout = Layout(
1✔
1809
            HTML('<h2 class="h3">' + title + '</h2><br>'),
1810
            Field('event'),
1811
            HTML('<hr>'),
1812
            FormActions(
1813
                Submit('save', 'Submit')
1814
            )
1815
        )
1816
        super(CrewCheckinForm, self).__init__(*args, **kwargs)
1✔
1817

1818
        options = []
1✔
1819
        for event in events:
1✔
1820
            if event.max_crew:
1✔
1821
                options.append(
1✔
1822
                    (event.pk, " %s (%i spots remaining)" %
1823
                     (event.event_name, event.max_crew - event.crew_attendance.filter(active=True).count())))
1824
            else:
1825
                options.append((event.pk, " %s" % event.event_name))
×
1826

1827
        self.fields['event'] = forms.ChoiceField(choices=options, label="Select Event", widget=forms.RadioSelect)
1✔
1828

1829

1830
class CrewCheckoutForm(forms.Form):
1✔
1831
    checkin = forms.SplitDateTimeField(required=True, label="Verify Checkin Time")
1✔
1832
    checkout = forms.SplitDateTimeField(required=True, label="Confirm Checkout Time")
1✔
1833

1834
    def __init__(self, *args, **kwargs):
1✔
1835
        self.helper = FormHelper()
1✔
1836
        self.helper.form_class = "form-horizontal container"
1✔
1837
        self.helper.form_method = "post"
1✔
1838
        self.helper.form_action = ""
1✔
1839
        self.helper.layout = Layout(
1✔
1840
            HTML('<br><h2 class="h3">Does this look correct?</h2><p>Review the checkin and checkout times listed '
1841
                 'below and verify that they are accurate. Once you submit, you will not be able to edit this '
1842
                 'information.<br><br></p>'),
1843
            Field('checkin', css_class="control mb-2"),
1844
            Field('checkout', css_class="control mb-2"),
1845
            FormActions(
1846
                Submit('save', 'Confirm')
1847
            )
1848
        )
1849
        super(CrewCheckoutForm, self).__init__(*args, **kwargs)
1✔
1850

1851
    def clean(self):
1✔
1852
        cleaned_data = super(CrewCheckoutForm, self).clean()
1✔
1853
        if cleaned_data['checkout'] > timezone.now():
1✔
1854
            raise ValidationError('Ha, nice try. Unless you\'ve figured out time travel, you cannot submit a '
1✔
1855
                                  'checkout time in the future.')
1856

1857

1858
class CheckoutHoursForm(forms.Form):
1✔
1859
    disabled_widget = forms.NumberInput(attrs={'readonly': True, 'step': 0.25})
1✔
1860
    total = forms.DecimalField(label="Total", decimal_places=2, widget=disabled_widget, required=False)
1✔
1861

1862
    def __init__(self, *args, **kwargs):
1✔
1863
        categories = kwargs.pop('categories')
1✔
1864
        self.total_hrs = kwargs.pop('total_hrs')
1✔
1865
        self.helper = FormHelper()
1✔
1866
        self.helper.form_class = "form-horizontal container"
1✔
1867
        self.helper.form_method = "post"
1✔
1868
        self.helper.form_action = ""
1✔
1869
        hour_set = Row(css_class="justify-between")
1✔
1870
        self.helper.layout = Layout(
1✔
1871
            HTML('<br><h2 class="h3">Submit Hours</h2><p>If you\'d like to log the hours you have worked, please '
1872
                 'indicate which services you helped provide.<br><br></p>'),
1873
            hour_set,
1874
            HTML('<hr>'),
1875
            FormActions(
1876
                Submit('save', 'Submit')
1877
            )
1878
        )
1879
        super(CheckoutHoursForm, self).__init__(*args, **kwargs)
1✔
1880

1881
        self.fields['total'].initial = self.total_hrs
1✔
1882
        for category in categories:
1✔
1883
            self.fields['hours_%s' % category.name] = forms.DecimalField(label=category.name, required=False,
1✔
1884
                                                                         min_value=0)
1885
            hour_set.fields.append(Column('hours_%s' % category.name, css_class="mx-2"))
1✔
1886
        hour_set.fields.append(Column('total', css_class="mx-2"))
1✔
1887

1888
    def clean(self):
1✔
1889
        cleaned_data = super(CheckoutHoursForm, self).clean()
1✔
1890
        hour_fields = []
1✔
1891
        for field in self.fields:
1✔
1892
            if "hour" in field:
1✔
1893
                hour_fields.append(field)
1✔
1894
        total = 0
1✔
1895
        for field in hour_fields:
1✔
1896
            if cleaned_data[field]:
1✔
1897
                total += self.cleaned_data[field]
1✔
1898
        if total != self.total_hrs and total > 0:
1✔
1899
            raise ValidationError('Hour totals do not match')
1✔
1900

1901

1902
class BulkCheckinForm(forms.Form):
1✔
1903
    secure_widget = forms.PasswordInput(attrs={'autofocus': 'autofocus'})
1✔
1904
    id = forms.IntegerField(required=True, label="Scan / Swipe Student ID", widget=secure_widget)
1✔
1905

1906
    def __init__(self, *args, **kwargs):
1✔
1907
        event = kwargs.pop('event')
1✔
1908
        result = kwargs.pop('result')
1✔
1909
        user_name = kwargs.pop('user_name')
1✔
1910
        self.helper = FormHelper()
1✔
1911
        self.helper.form_class = "form-horizontal col-md-8 m-auto"
1✔
1912
        self.helper.form_method = 'post'
1✔
1913
        self.helper.form_action = ""
1✔
1914
        output = HTML("")
1✔
1915
        if result == 'checkin-success':
1✔
1916
            output = HTML("<div class='alert alert-success w-75 m-auto'>Welcome " + user_name + "</div>")
1✔
1917
        elif result == 'checkin-fail':
1✔
1918
            output = HTML("<div class='alert alert-danger w-75 m-auto'>Checkin Failed - User is checked into "
1✔
1919
                          "another event</div>")
1920
        elif result == 'checkin-limit':
1✔
1921
            output = HTML("<div class='alert alert-warning w-100 m-auto'>Checkin Failed - This event has reached its "
1✔
1922
                          "crew member or occupancy limit</div>")
1923
        elif result == 'checkout-success':
1✔
1924
            output = HTML("<div class='alert alert-success w-75 m-auto'>Bye. Thanks for your help!</div>")
1✔
1925
        elif result == 'fail':
1✔
1926
            output = HTML("<div class='alert alert-danger w-75 m-auto'>Invalid ID</div>")
1✔
1927
        elif result:
1✔
1928
            output = HTML("<div class='alert w-75 m-auto' style='background-color: black; color: white'>"
×
1929
                          "An unknown error occurred.</div>")
1930
        self.helper.layout = Layout(
1✔
1931
            HTML('<h2 class="h6"><strong>Event:</strong> ' + event + '</h2><br>'),
1932
            Field('id'),
1933
            FormActions(
1934
                Submit('save', 'Enter', css_class="d-none")
1935
            ),
1936
            Div(output, css_class="mt-4 text-center")
1937
        )
1938
        super(BulkCheckinForm, self).__init__(*args, **kwargs)
1✔
1939

1940

1941
# __        __         _                 _
1942
# \ \      / /__  _ __| | _____  _ __ __| | ___ _ __
1943
#  \ \ /\ / / _ \| '__| |/ / _ \| '__/ _` |/ _ \ '__|
1944
#   \ V  V / (_) | |  |   < (_) | | | (_| |  __/ |
1945
#    \_/\_/ \___/|_|  |_|\_\___/|_|  \__,_|\___|_|
1946

1947
#  _____                            _                  _
1948
# |  ___|__  _ __ _ __ _____      _(_)______ _ _ __ __| |
1949
# | |_ / _ \| '__| '_ ` _ \ \ /\ / / |_  / _` | '__/ _` |
1950
# |  _| (_) | |  | | | | | \ V  V /| |/ / (_| | | | (_| |
1951
# |_|  \___/|_|  |_| |_| |_|\_/\_/ |_/___\__,_|_|  \__,_|
1952

1953
#  _____
1954
# |  ___|__  _ __ _ __ ___  ___
1955
# | |_ / _ \| '__| '_ ` _ \/ __|
1956
# |  _| (_) | |  | | | | | \__ \
1957
# |_|  \___/|_|  |_| |_| |_|___/
1958

1959
SERVICE_INFO_HELP_TEXT = """
1✔
1960
Note: Any riders or documentation provided to you from the artist/performer which may help LNL
1961
determine the technical needs of your event may be attached to this request once it is submitted by
1962
going to your LNL account and selecting "Previous Workorders".
1963
"""
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