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

gm3dmo / cmp / 13723607972

07 Mar 2025 02:19PM UTC coverage: 62.228% (-0.3%) from 62.535%
13723607972

push

github

gm3dmo
formsets look better

3 of 21 new or added lines in 2 files covered. (14.29%)

193 existing lines in 2 files now uncovered.

888 of 1427 relevant lines covered (62.23%)

1.24 hits per line

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

55.24
/cmp/forms.py
1
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
2✔
2

3
from django import forms
2✔
4

5
from .models import CustomUser
2✔
6

7
from .models import Country
2✔
8
from .models import Rank
2✔
9
from .models import Cemetery
2✔
10
from .models import PowCamp
2✔
11
from .models import Soldier
2✔
12
from .models import SoldierDeath
2✔
13
from .models import SoldierImprisonment
2✔
14
from .models import SoldierDecoration
2✔
15
from .models import Company
2✔
16
from .models import Decoration
2✔
17
from .models import Acknowledgement
2✔
18
from .models import ProvostAppointment
2✔
19
from django.forms import inlineformset_factory
2✔
20

21
from crispy_forms.helper import FormHelper
2✔
22
from crispy_forms.layout import Layout, Field, Submit
2✔
23
from crispy_forms.bootstrap import Accordion, AccordionGroup, TabHolder, Tab
2✔
24

25

26
class SoldierImprisonmentForm(forms.ModelForm):
2✔
27
    date_from = forms.DateField(
2✔
28
        initial='1940-01-01',
29
        widget=forms.DateInput(
30
            attrs={
31
                'type': 'date',
32
                'class': 'form-control',
33
                'style': 'width: 20%;'
34
            }
35
        ),
36
        required=False
37
    )
38
    date_to = forms.DateField(
2✔
39
        initial='1940-01-01',
40
        widget=forms.DateInput(
41
            attrs={
42
                'type': 'date',
43
                'class': 'form-control',
44
                'style': 'width: 20%;'
45
            }
46
        ),
47
        required=False
48
    )
49

50
    class Meta:
2✔
51
        model = SoldierImprisonment
2✔
52
        fields = ['pow_camp', 'pow_number', 'date_from', 'date_to', 'notes']
2✔
53
        widgets = {
2✔
54
            'date_to': forms.DateInput(
55
                attrs={
56
                    'type': 'date',
57
                    'class': 'form-control'
58
                }
59
            )
60
        }
61

62
    def __init__(self, *args, **kwargs):
2✔
63
        super().__init__(*args, **kwargs)
×
64
        self.fields['pow_camp'].required = False
×
65
        self.fields['pow_number'].required = False
×
66
        self.fields['date_to'].required = False
×
67
        self.fields['notes'].required = False
×
68

69
class SoldierImprisonmentFormSetHelper(FormHelper):
2✔
70
    def __init__(self, *args, **kwargs):
2✔
71
        super().__init__(*args, **kwargs)
×
72
        self.form_tag = False
×
73
        
74
        # Default to collapsed
75
        has_data = False
×
UNCOV
76
        title = 'Prisoner of War Details (None Recorded)'
×
77
        
UNCOV
78
        self.layout = Layout(
×
79
            Accordion(
80
                AccordionGroup(
81
                    title,
82
                    'pow_camp',
83
                    'pow_number',
84
                    'date_from',
85
                    'date_to',
86
                    'notes',
87
                    active=has_data,  # Collapsed by default
88
                    css_class='bg-info bg-opacity-25 border rounded p-3'
89
                ),
90
                css_id="imprisonment-details-accordion"
91
            )
92
        )
93

94
    def update_title(self):
2✔
95
        """Update the title based on formset data"""
96
        if hasattr(self, 'formset') and self.formset.initial_forms:
×
97
            has_data = any(form.initial for form in self.formset.initial_forms)
×
98
            title = 'Prisoner of War Details' if has_data else 'Prisoner of War Details (None Recorded)'
×
99
            self.layout[0][0].name = title
×
UNCOV
100
            self.layout[0][0].active = has_data
×
101

102
# Create the formset
103
SoldierImprisonmentInlineFormSet = inlineformset_factory(
2✔
104
    Soldier,
105
    SoldierImprisonment,
106
    form=SoldierImprisonmentForm,
107
    extra=1,
108
    can_delete=True
109
)
110

111
class SoldierImprisonmentFormSetWithHelper(SoldierImprisonmentInlineFormSet):
2✔
112
    def __init__(self, *args, **kwargs):
2✔
113
        super().__init__(*args, **kwargs)
×
114
        self.helper = SoldierImprisonmentFormSetHelper()
×
115
        self.helper.formset = self
×
UNCOV
116
        self.helper.update_title()
×
117

118

119
class CustomUserCreationForm(UserCreationForm):
2✔
120
    class Meta:
2✔
121
        model = CustomUser
2✔
122
        fields = ("email",)
2✔
123

124

125
class CustomUserChangeForm(UserChangeForm):
2✔
126
    class Meta:
2✔
127
        model = CustomUser
2✔
128
        fields = ("email",)
2✔
129

130

131
class editPowCampForm(forms.ModelForm):
2✔
132
    def __init__(self, *args, **kwargs):
2✔
133
        super().__init__(*args, **kwargs)
×
UNCOV
134
        self.helper = FormHelper()
×
135
        
136
        # Determine header class and active state based on whether form has data
137
        header_class = 'bg-light' if self.instance and self.instance.pk else 'bg-light-blue'
×
UNCOV
138
        is_active = bool(self.instance and self.instance.pk)
×
139
        
UNCOV
140
        self.helper.layout = Layout(
×
141
            Field('name'),
142
            Accordion(
143
                AccordionGroup(
144
                    'POW Details',
145
                    Field('nearest_city'),
146
                    Field('notes'),
147
                    Field('wartime_country'),
148
                    Field('latitude'),
149
                    Field('longitude'),
150
                    active=is_active,
151
                    button_class=header_class
152
                ),
153
                css_id="powcamp-details-accordion"
154
            )
155
        )
156

157
    name = forms.CharField(
2✔
158
        widget=forms.TextInput(attrs={
159
            'class': 'wide-input'
160
        })
161
    )
162
    nearest_city = forms.CharField(
2✔
163
        widget=forms.TextInput(attrs={
164
            'class': 'wide-input'
165
        }),
166
        required=False
167
    )
168
    notes = forms.CharField(
2✔
169
        widget=forms.Textarea(attrs={
170
            'class': 'wide-input'
171
        }),
172
        required=False
173
    )
174
    wartime_country = forms.CharField(
2✔
175
        widget=forms.TextInput(attrs={
176
            'class': 'wide-input'
177
        }),
178
        required=False
179
    )
180
    latitude = forms.CharField(
2✔
181
        widget=forms.TextInput(attrs={
182
            'class': 'wide-input'
183
        }),
184
        required=False
185
    )
186
    longitude = forms.CharField(
2✔
187
        widget=forms.TextInput(attrs={
188
            'class': 'wide-input'
189
        }),
190
        required=False
191
    )
192

193
    class Meta:
2✔
194
        model = PowCamp
2✔
195
        fields = '__all__'
2✔
196

197

198
class editCemeteryForm(forms.ModelForm):
2✔
199
    name = forms.CharField(
2✔
200
        widget=forms.TextInput(attrs={
201
            'class': 'wide-input',
202
            'style': 'width: 500px;'
203
        })
204
    )
205
    country = forms.ModelChoiceField(
2✔
206
        queryset=Country.objects.all().order_by('name'),
207
        empty_label="Select a country"
208
    )
209

210
    class Meta:
2✔
211
        model = Cemetery
2✔
212
        fields = ['name', 'country', 'latitude', 'longitude']
2✔
213

214

215
class editCountryForm(forms.ModelForm):
2✔
216
    def __init__(self, *args, **kwargs):
2✔
217
        super().__init__(*args, **kwargs)
×
218
        self.helper = FormHelper()
×
UNCOV
219
        self.helper.label_class = 'form-label'  
×
220
    class Meta:
2✔
221
        model = Country
2✔
222
        fields = "__all__"
2✔
223

224
class AcknowledgementForm(forms.ModelForm):
2✔
225
    def __init__(self, *args, **kwargs):
2✔
226
        super().__init__(*args, **kwargs)
2✔
227
        self.helper = FormHelper()
2✔
228
        self.helper.label_class = 'form-label'  
2✔
229
    class Meta:
2✔
230
        model = Acknowledgement
2✔
231
        created_at = forms.DateTimeField(disabled=True, required=False)
2✔
232
        exclude = ['created_at']  # This will hide created_at from the form
2✔
233
        fields = '__all__'
2✔
234

235

236
class editCompanyForm(forms.ModelForm):
2✔
237
    def __init__(self, *args, **kwargs):
2✔
238
        super().__init__(*args, **kwargs)
×
239
        self.helper = FormHelper()
×
UNCOV
240
        self.helper.label_class = 'form-label'  
×
241
    class Meta:
2✔
242
        model = Company 
2✔
243
        fields = "__all__"
2✔
244

245
class editDecorationForm(forms.ModelForm):
2✔
246
    def __init__(self, *args, **kwargs):
2✔
247
        super().__init__(*args, **kwargs)
×
248
        self.helper = FormHelper()
×
UNCOV
249
        self.helper.label_class = 'form-label'  
×
250
    class Meta:
2✔
251
        model = Decoration
2✔
252
        fields = "__all__"
2✔
253

254

255
class editRankForm(forms.ModelForm):
2✔
256
    name = forms.CharField(
2✔
257
        widget=forms.TextInput(attrs={
258
            'class': 'wide-input',
259
        })
260
    )
261

262
    class Meta:
2✔
263
        model = Rank
2✔
264
        fields = '__all__'
2✔
265

266

267
class editSoldierDeathForm(forms.ModelForm):
2✔
268
    class Meta:
2✔
269
        model = SoldierDeath
2✔
270
        fields = ['date', 'company', 'cemetery', 'cwgc_id', 'image']
2✔
271
        widgets = {
2✔
272
            'date': forms.DateInput(
273
                attrs={
274
                    'type': 'date',
275
                    'class': 'form-control',
276
                }
277
            ),
278
            'image': forms.FileInput(
279
                attrs={
280
                    'class': 'form-control',
281
                    'accept': 'image/*'
282
                }
283
            )
284
        }
285

286
    def __init__(self, *args, **kwargs):
2✔
287
        super().__init__(*args, **kwargs)
×
288
        self.helper = FormHelper()
×
UNCOV
289
        self.helper.form_tag = False
×
290
        
291
        # Determine header class and active state based on whether form has data
292
        header_class = 'bg-primary text-white'
×
UNCOV
293
        is_active = bool(self.instance and self.instance.pk)
×
294
        
UNCOV
295
        self.helper.layout = Layout(
×
296
            Accordion(
297
                AccordionGroup(
298
                    'Death Details',
299
                    'date',
300
                    'company',
301
                    'cemetery',
302
                    'cwgc_id',
303
                    'image',
304
                    active=is_active,
305
                    css_id="death-details-accordion",
306
                    button_class=header_class
307
                )
308
            )
309
        )
310

311

312
class editSoldierForm(forms.ModelForm):
2✔
313
    def __init__(self, *args, **kwargs):
2✔
314
        super().__init__(*args, **kwargs)
×
315
        self.helper = FormHelper()
×
316
        self.helper.label_class = 'form-label'  
×
UNCOV
317
        self.fields['provost_officer'].disabled = True
×
318

319
        # Initialize both formsets with helpers
320
        self.imprisonment_formset = SoldierImprisonmentFormSetWithHelper(
×
321
            instance=self.instance,
322
            prefix='imprisonment'
323
        )
324
        
NEW
325
        self.decoration_formset = SoldierDecorationFormSetWithHelper(
×
326
            instance=self.instance,
327
            prefix='decoration'
328
        )
329

330
        # Determine header classes and active states
UNCOV
331
        header_class = 'bg-light' if self.instance and self.instance.pk else 'bg-light-blue'
×
332
        
333
        # Check for existing imprisonments
334
        has_imprisonment = False
×
335
        if self.instance and self.instance.pk:
×
UNCOV
336
            has_imprisonment = SoldierImprisonment.objects.filter(soldier=self.instance).exists()
×
337
        
338
        # Check for existing decorations
NEW
339
        has_decorations = False
×
NEW
340
        if self.instance and self.instance.pk:
×
NEW
341
            has_decorations = SoldierDecoration.objects.filter(soldier=self.instance).exists()
×
342
        
NEW
343
        imprisonment_title = 'Prisoner of War Details' if has_imprisonment else 'Prisoner of War Details (None Recorded)'
×
NEW
344
        decoration_title = 'Decoration Details' if has_decorations else 'Decoration Details (None Recorded)'
×
345
        
UNCOV
346
        self.helper.layout = Layout(
×
347
            Field('surname'),
348
            Field('initials'),
349
            Field('army_number'),
350
            Field('rank'),
351
            Field('provost_officer'),
352
            Field('notes'),
353
            Accordion(
354
                AccordionGroup(
355
                    imprisonment_title,
356
                    'imprisonment_formset',
357
                    active=has_imprisonment,
358
                    button_class=header_class
359
                ),
360
                AccordionGroup(
361
                    decoration_title,
362
                    'decoration_formset',
363
                    active=has_decorations,
364
                    button_class=header_class
365
                ),
366
                css_id="soldier-details-accordion"
367
            )
368
        )
369

370
    class Meta:
2✔
371
        model = Soldier
2✔
372
        fields = ['surname', 'initials', 'army_number', 'rank', 'notes', 'provost_officer']
2✔
373
        exclude = ['created_at']
2✔
374

375

376
class ProvostOfficerSearchForm(forms.Form):
2✔
377
    q = forms.CharField(
2✔
378
        required=False,
379
        label='Search',
380
        widget=forms.TextInput(attrs={
381
            'class': 'form-control',
382
            'placeholder': 'Search by surname or army number...'
383
        })
384
    )
385

386
class ProvostOfficerForm(forms.ModelForm):
2✔
387
    def __init__(self, *args, **kwargs):
2✔
388
        super().__init__(*args, **kwargs)
×
389
        self.helper = FormHelper()
×
390
        self.helper.label_class = 'form-label'
×
391
        
392
        # Filter rank choices to only show officer ranks
393
        self.fields['rank'].queryset = Rank.objects.filter(rank_class="OF").order_by('name')
×
394
        
395
        # Set provost_officer field
396
        self.fields['provost_officer'] = forms.BooleanField(
×
397
            initial=True,
398
            disabled=True,
399
            required=False,
400
            help_text="All officers created through this form are automatically marked as Provost Officers"
401
        )
402
        
403
        # Determine header class and active state
404
        header_class = 'bg-light' if self.instance and self.instance.pk else 'bg-light-blue'
×
405
        is_active = bool(self.instance and self.instance.pk)
×
406
        
407
        self.helper.layout = Layout(
×
408
            Field('surname'),
409
            Field('initials'),
410
            Field('army_number'),
411
            Field('rank'),
412
            Field('provost_officer'),
413
            Accordion(
414
                AccordionGroup(
415
                    'Appointment Details',
416
                    'appointment_formset',
417
                    active=is_active,
418
                    button_class=header_class
419
                ),
420
                css_id="appointment-details-accordion"
421
            ),
422
            Field('notes')
423
        )
424

425
    class Meta:
2✔
426
        model = Soldier
2✔
427
        fields = ['surname', 'initials', 'army_number', 'rank', 'notes']
2✔
428
        widgets = {
2✔
429
            'notes': forms.Textarea(attrs={'rows': 3}),
430
        }
431

432
    def save(self, commit=True):
2✔
433
        soldier = super().save(commit=False)
×
434
        soldier.provost_officer = True
×
435
        if commit:
×
436
            soldier.save()
×
437
        return soldier
×
438

439
class ProvostAppointmentForm(forms.ModelForm):
2✔
440
    def __init__(self, *args, **kwargs):
2✔
441
        super().__init__(*args, **kwargs)
×
442
        self.helper = FormHelper()
×
443
        self.helper.label_class = 'form-label'
×
444
        
445
        # Filter rank choices to only show officer ranks
446
        self.fields['rank'].queryset = Rank.objects.filter(rank_class="OF").order_by('name')
×
447
        
448
        self.helper.layout = Layout(
×
449
            Field('rank'),
450
            Field('date'),
451
            Field('notes')
452
        )
453

454
    date = forms.DateField(
2✔
455
        widget=forms.DateInput(
456
            attrs={
457
                'type': 'date',
458
                'class': 'form-control',
459
                'style': 'width: 20%;'
460
            }
461
        ),
462
        required=False
463
    )
464

465
    class Meta:
2✔
466
        model = ProvostAppointment
2✔
467
        fields = ['rank', 'date', 'notes']
2✔
468
        widgets = {
2✔
469
            'notes': forms.Textarea(attrs={'rows': 3}),
470
        }
471

472
# Create the formset
473
ProvostAppointmentInlineFormSet = inlineformset_factory(
2✔
474
    Soldier,
475
    ProvostAppointment,
476
    form=ProvostAppointmentForm,
477
    extra=1,
478
    can_delete=True
479
)
480

481
class ProvostAppointmentFormSetHelper(FormHelper):
2✔
482
    def __init__(self, *args, **kwargs):
2✔
483
        super().__init__(*args, **kwargs)
×
484
        self.form_tag = False
×
485
        
486
        # Default to collapsed
487
        has_data = False
×
488
        title = 'Appointment Details (None Recorded)'
×
489
        
490
        self.layout = Layout(
×
491
            Accordion(
492
                AccordionGroup(
493
                    title,
494
                    'rank',
495
                    'date',
496
                    'notes',
497
                    active=has_data,
498
                    css_class='bg-info bg-opacity-25 border rounded p-3'
499
                ),
500
                css_id="appointment-details-accordion"
501
            )
502
        )
503

504
    def update_title(self):
2✔
505
        if hasattr(self, 'formset') and self.formset.initial_forms:
×
506
            has_data = any(form.initial for form in self.formset.initial_forms)
×
507
            title = 'Appointment Details' if has_data else 'Appointment Details (None Recorded)'
×
508
            self.layout[0][0].name = title
×
509
            self.layout[0][0].active = has_data
×
510

511
class ProvostAppointmentFormSetWithHelper(ProvostAppointmentInlineFormSet):
2✔
512
    def __init__(self, *args, **kwargs):
2✔
513
        super().__init__(*args, **kwargs)
×
514
        self.helper = ProvostAppointmentFormSetHelper()
×
515
        self.helper.formset = self
×
516
        self.helper.update_title()
×
517

518

519
class SoldierDecorationForm(forms.ModelForm):
2✔
520
    country = forms.ModelChoiceField(
2✔
521
        queryset=Country.objects.all(),
522
    )
523

524
    class Meta:
2✔
525
        model = SoldierDecoration
2✔
526
        fields = ['decoration', 'gazette_issue', 'gazette_page', 'gazette_date', 'country', 'citation', 'notes']
2✔
527
        widgets = {
2✔
528
            'gazette_date': forms.DateInput(
529
                attrs={
530
                    'type': 'date',
531
                    'class': 'form-control',
532
                    'style': 'width: 20%;'
533
                }
534
            )
535
        }
536

537
class SoldierDecorationFormSetHelper(FormHelper):
2✔
538
    def __init__(self, *args, **kwargs):
2✔
539
        super().__init__(*args, **kwargs)
×
540
        self.form_tag = False
×
541
        
542
        # Default to collapsed
543
        has_data = False
×
UNCOV
544
        title = 'Decoration Details (None Recorded)'
×
545
        
UNCOV
546
        self.layout = Layout(
×
547
            Accordion(
548
                AccordionGroup(
549
                    title,
550
                    'decoration',
551
                    'gazette_issue',
552
                    'gazette_page',
553
                    'gazette_date',
554
                    'country',
555
                    'citation',
556
                    'notes',
557
                    active=has_data,
558
                    css_class='bg-info bg-opacity-25 border rounded p-3'
559
                ),
560
                css_id="decoration-details-accordion"
561
            )
562
        )
563

564
    def update_title(self):
2✔
565
        if hasattr(self, 'formset') and self.formset.initial_forms:
×
566
            has_data = any(form.initial for form in self.formset.initial_forms)
×
567
            title = 'Decoration Details' if has_data else 'Decoration Details (None Recorded)'
×
UNCOV
568
            self.layout[0][0].name = title
×
UNCOV
569
            self.layout[0][0].active = has_data
×
570

571
# Create the formset
572
SoldierDecorationInlineFormSet = inlineformset_factory(
2✔
573
    Soldier,
574
    SoldierDecoration,
575
    form=SoldierDecorationForm,
576
    extra=1,
577
    can_delete=True
578
)
579

580
# Add the helper to the formset
581
class SoldierDecorationFormSetWithHelper(SoldierDecorationInlineFormSet):
2✔
582
    def __init__(self, *args, **kwargs):
2✔
583
        super().__init__(*args, **kwargs)
×
584
        self.helper = SoldierDecorationFormSetHelper()
×
UNCOV
585
        self.helper.formset = self
×
UNCOV
586
        self.helper.update_title()
×
587

588

589
class SoldierDeathFormHelper(FormHelper):
2✔
590
    def __init__(self, *args, **kwargs):
2✔
UNCOV
591
        super().__init__(*args, **kwargs)
×
UNCOV
592
        self.form_tag = False
×
593
        
594
        # Default to collapsed
595
        has_data = False
×
UNCOV
596
        title = 'Death Details (None Recorded)'
×
597
        
UNCOV
598
        self.layout = Layout(
×
599
            Accordion(
600
                AccordionGroup(
601
                    title,
602
                    'date',
603
                    'company',
604
                    'cemetery',
605
                    'cwgc_id',
606
                    'image',
607
                    active=has_data,  # Collapsed by default
608
                    css_class='bg-info bg-opacity-25 border rounded p-3'
609
                ),
610
                css_id="death-details-accordion"
611
            )
612
        )
613

614
    def update_title(self):
2✔
615
        """Update the title based on form data"""
616
        if hasattr(self, 'form') and self.form.initial:
×
617
            has_data = any(self.form.initial.values())
×
UNCOV
618
            title = 'Death Details' if has_data else 'Death Details (None Recorded)'
×
UNCOV
619
            self.layout[0][0].name = title
×
UNCOV
620
            self.layout[0][0].active = has_data
×
621

622

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