Skip to content

Commit 787cb36

Browse files
authored
feat: Make end_of_sale and end_of_support dates optional (#132)
2 parents 29df1b0 + 7be4187 commit 787cb36

File tree

8 files changed

+181
-21
lines changed

8 files changed

+181
-21
lines changed

netbox_lifecycle/api/_serializers/hardware.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ class HardwareLifecycleSerializer(NetBoxModelSerializer):
1515
)
1616
assigned_object_type = ContentTypeField(queryset=ContentType.objects.all())
1717

18-
end_of_sale = serializers.DateField()
19-
end_of_maintenance = serializers.DateField(required=False)
20-
end_of_security = serializers.DateField(required=False)
21-
last_contract_attach = serializers.DateField(required=False)
22-
last_contract_renewal = serializers.DateField(required=False)
23-
end_of_support = serializers.DateField()
18+
end_of_sale = serializers.DateField(required=False, allow_null=True)
19+
end_of_maintenance = serializers.DateField(required=False, allow_null=True)
20+
end_of_security = serializers.DateField(required=False, allow_null=True)
21+
last_contract_attach = serializers.DateField(required=False, allow_null=True)
22+
last_contract_renewal = serializers.DateField(required=False, allow_null=True)
23+
end_of_support = serializers.DateField(required=False, allow_null=True)
2424

2525
class Meta:
2626
model = HardwareLifecycle
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from django.db import migrations, models
2+
3+
4+
class Migration(migrations.Migration):
5+
6+
dependencies = [
7+
('netbox_lifecycle', '0016_add_virtual_machine_support'),
8+
]
9+
10+
operations = [
11+
migrations.AlterField(
12+
model_name='hardwarelifecycle',
13+
name='end_of_sale',
14+
field=models.DateField(blank=True, null=True),
15+
),
16+
migrations.AlterField(
17+
model_name='hardwarelifecycle',
18+
name='end_of_support',
19+
field=models.DateField(blank=True, null=True),
20+
),
21+
]

netbox_lifecycle/models/hardware.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ class HardwareLifecycle(PrimaryModel):
2626
ct_field='assigned_object_type', fk_field='assigned_object_id'
2727
)
2828

29-
end_of_sale = models.DateField()
29+
end_of_sale = models.DateField(blank=True, null=True)
3030
end_of_maintenance = models.DateField(blank=True, null=True)
3131
end_of_security = models.DateField(blank=True, null=True)
3232
last_contract_attach = models.DateField(blank=True, null=True)
3333
last_contract_renewal = models.DateField(blank=True, null=True)
34-
end_of_support = models.DateField()
34+
end_of_support = models.DateField(blank=True, null=True)
3535

3636
notice = models.CharField(max_length=500, blank=True, null=True)
3737
documentation = models.CharField(max_length=500, blank=True, null=True)

netbox_lifecycle/tables/hardware.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class Meta(NetBoxTable.Meta):
3434
'end_of_maintenance',
3535
'end_of_security',
3636
'end_of_support',
37+
'last_contract_attach',
38+
'last_contract_renewal',
3739
'description',
3840
'comments',
3941
)

netbox_lifecycle/templates/netbox_lifecycle/hardwarelifecycle.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,27 @@ <h5 class="card-header">Dates</h5>
3535
<table class="table table-hover attr-table">
3636
<tr>
3737
<td>End of Sale</td>
38-
<td><span {{ object.end_of_sale|date_badge_class }}>{{ object.end_of_sale }}</span></td>
38+
<td><span {{ object.end_of_sale|date_badge_class }}>{{ object.end_of_sale|placeholder }}</span></td>
3939
</tr>
4040
<tr>
4141
<td>End of Maintenance Updates</td>
42-
<td><span {{ object.end_of_maintenance|date_badge_class }}>{{ object.end_of_maintenance }}</span></td>
42+
<td><span {{ object.end_of_maintenance|date_badge_class }}>{{ object.end_of_maintenance|placeholder }}</span></td>
4343
</tr>
4444
<tr>
4545
<td>End of Security Updates</td>
46-
<td><span {{ object.end_of_security|date_badge_class }}>{{ object.end_of_security }}</span></td>
46+
<td><span {{ object.end_of_security|date_badge_class }}>{{ object.end_of_security|placeholder }}</span></td>
4747
</tr>
4848
<tr>
4949
<td>Last Support Contract Attach</td>
50-
<td><span {{ object.last_contract_attach|date_badge_class }}>{{ object.last_contract_attach }}</span></td>
50+
<td><span {{ object.last_contract_attach|date_badge_class }}>{{ object.last_contract_attach|placeholder }}</span></td>
5151
</tr>
5252
<tr>
5353
<td>Last Support Contract Renewal</td>
54-
<td><span {{ object.last_contract_renewal|date_badge_class }}>{{ object.last_contract_renewal }}</span></td>
54+
<td><span {{ object.last_contract_renewal|date_badge_class }}>{{ object.last_contract_renewal|placeholder }}</span></td>
5555
</tr>
5656
<tr>
5757
<td>End of Support</td>
58-
<td><span {{ object.end_of_support|date_badge_class }}>{{ object.end_of_support }}</span></td>
58+
<td><span {{ object.end_of_support|date_badge_class }}>{{ object.end_of_support|placeholder }}</span></td>
5959
</tr>
6060
</table>
6161
</div>

netbox_lifecycle/templates/netbox_lifecycle/inc/hardware_lifecycle_info.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,27 @@ <h5 class="card-header">Lifecycle Dates
1313
<table class="table table-hover attr-table">
1414
<tr>
1515
<td>End of Sale</td>
16-
<td><span {{ lifecycle_info.end_of_sale|date_badge_class }}>{{ lifecycle_info.end_of_sale }}</span></td>
16+
<td><span {{ lifecycle_info.end_of_sale|date_badge_class }}>{{ lifecycle_info.end_of_sale|placeholder }}</span></td>
1717
</tr>
1818
<tr>
1919
<td>End of Maintenance Updates</td>
20-
<td><span {{ lifecycle_info.end_of_maintenance|date_badge_class }}>{{ lifecycle_info.end_of_maintenance }}</span></td>
20+
<td><span {{ lifecycle_info.end_of_maintenance|date_badge_class }}>{{ lifecycle_info.end_of_maintenance|placeholder }}</span></td>
2121
</tr>
2222
<tr>
2323
<td>End of Security Updates</td>
24-
<td><span {{ lifecycle_info.end_of_security|date_badge_class }}>{{ lifecycle_info.end_of_security }}</span></td>
24+
<td><span {{ lifecycle_info.end_of_security|date_badge_class }}>{{ lifecycle_info.end_of_security|placeholder }}</span></td>
2525
</tr>
2626
<tr>
2727
<td>Last Support Contract Attach</td>
28-
<td><span {{ lifecycle_info.last_contract_attach|date_badge_class }}>{{ lifecycle_info.last_contract_attach }}</span></td>
28+
<td><span {{ lifecycle_info.last_contract_attach|date_badge_class }}>{{ lifecycle_info.last_contract_attach|placeholder }}</span></td>
2929
</tr>
3030
<tr>
3131
<td>Last Support Contract Renewal</td>
32-
<td><span {{ lifecycle_info.last_contract_renewal|date_badge_class }}>{{ lifecycle_info.last_contract_renewal }}</span></td>
32+
<td><span {{ lifecycle_info.last_contract_renewal|date_badge_class }}>{{ lifecycle_info.last_contract_renewal|placeholder }}</span></td>
3333
</tr>
3434
<tr>
3535
<td>End of Support</td>
36-
<td><span {{ lifecycle_info.end_of_support|date_badge_class }}>{{ lifecycle_info.end_of_support }}</span></td>
36+
<td><span {{ lifecycle_info.end_of_support|date_badge_class }}>{{ lifecycle_info.end_of_support|placeholder }}</span></td>
3737
</tr>
3838
</table>
3939
{% else %}

netbox_lifecycle/tests/test_api.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,3 +485,76 @@ def setUpTestData(cls):
485485
'end_of_support': '2050-01-01',
486486
},
487487
]
488+
489+
# Additional device types for null date tests
490+
cls.device_type_for_null_test = DeviceType.objects.create(
491+
model='Device Type Null Test',
492+
manufacturer=manufacturer,
493+
slug='device-type-null',
494+
)
495+
cls.device_type_for_explicit_null = DeviceType.objects.create(
496+
model='Device Type Explicit Null',
497+
manufacturer=manufacturer,
498+
slug='device-type-explicit-null',
499+
)
500+
cls.device_type_for_update_null = DeviceType.objects.create(
501+
model='Device Type Update Null',
502+
manufacturer=manufacturer,
503+
slug='device-type-update-null',
504+
)
505+
506+
def test_create_lifecycle_with_omitted_dates(self):
507+
"""Test creating a hardware lifecycle with omitted date fields."""
508+
self.add_permissions('netbox_lifecycle.add_hardwarelifecycle')
509+
url = reverse('plugins-api:netbox_lifecycle-api:hardwarelifecycle-list')
510+
data = {
511+
'assigned_object_id': self.device_type_for_null_test.pk,
512+
'assigned_object_type': 'dcim.devicetype',
513+
}
514+
response = self.client.post(url, data, format='json', **self.header)
515+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
516+
self.assertIsNone(response.data['end_of_sale'])
517+
self.assertIsNone(response.data['end_of_support'])
518+
self.assertIsNone(response.data['end_of_maintenance'])
519+
self.assertIsNone(response.data['end_of_security'])
520+
521+
def test_create_lifecycle_with_explicit_null_dates(self):
522+
"""Test creating a hardware lifecycle with explicit null values for dates."""
523+
self.add_permissions('netbox_lifecycle.add_hardwarelifecycle')
524+
url = reverse('plugins-api:netbox_lifecycle-api:hardwarelifecycle-list')
525+
data = {
526+
'assigned_object_id': self.device_type_for_explicit_null.pk,
527+
'assigned_object_type': 'dcim.devicetype',
528+
'end_of_sale': None,
529+
'end_of_support': None,
530+
'end_of_maintenance': None,
531+
'end_of_security': None,
532+
}
533+
response = self.client.post(url, data, format='json', **self.header)
534+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
535+
self.assertIsNone(response.data['end_of_sale'])
536+
self.assertIsNone(response.data['end_of_support'])
537+
self.assertIsNone(response.data['end_of_maintenance'])
538+
self.assertIsNone(response.data['end_of_security'])
539+
540+
def test_update_lifecycle_dates_to_null(self):
541+
"""Test updating a hardware lifecycle to set dates to null."""
542+
self.add_permissions('netbox_lifecycle.change_hardwarelifecycle')
543+
# Create lifecycle with dates
544+
lifecycle = HardwareLifecycle.objects.create(
545+
assigned_object=self.device_type_for_update_null,
546+
end_of_sale='2030-01-01',
547+
end_of_support='2035-01-01',
548+
)
549+
url = reverse(
550+
'plugins-api:netbox_lifecycle-api:hardwarelifecycle-detail',
551+
kwargs={'pk': lifecycle.pk},
552+
)
553+
data = {
554+
'end_of_sale': None,
555+
'end_of_support': None,
556+
}
557+
response = self.client.patch(url, data, format='json', **self.header)
558+
self.assertHttpStatus(response, status.HTTP_200_OK)
559+
self.assertIsNone(response.data['end_of_sale'])
560+
self.assertIsNone(response.data['end_of_support'])

netbox_lifecycle/tests/test_forms.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.test import TestCase
22

3-
from dcim.models import Device, Manufacturer
3+
from dcim.models import Device, DeviceType, Manufacturer
44
from utilities.testing import create_test_device
55

66
from netbox_lifecycle.forms import *
@@ -164,3 +164,67 @@ def test_assignment_with_device_and_license_with_different_device(self):
164164
self.assertFalse(form.is_valid())
165165
with self.assertRaises(ValueError):
166166
form.save()
167+
168+
169+
class HardwareLifecycleTestCase(TestCase):
170+
171+
@classmethod
172+
def setUpTestData(cls):
173+
manufacturer = Manufacturer.objects.create(
174+
name='Manufacturer', slug='manufacturer'
175+
)
176+
cls.device_type = DeviceType.objects.create(
177+
manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
178+
)
179+
cls.device_type_2 = DeviceType.objects.create(
180+
manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'
181+
)
182+
cls.device_type_3 = DeviceType.objects.create(
183+
manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'
184+
)
185+
186+
def test_lifecycle_with_all_dates(self):
187+
"""Test creating a hardware lifecycle with all date fields populated."""
188+
form = HardwareLifecycleForm(
189+
data={
190+
'device_type': self.device_type.pk,
191+
'end_of_sale': '2030-01-01',
192+
'end_of_support': '2035-01-01',
193+
'end_of_maintenance': '2032-01-01',
194+
'end_of_security': '2033-01-01',
195+
'last_contract_attach': '2025-01-01',
196+
'last_contract_renewal': '2028-01-01',
197+
}
198+
)
199+
self.assertTrue(form.is_valid())
200+
instance = form.save()
201+
self.assertEqual(str(instance.end_of_sale), '2030-01-01')
202+
self.assertEqual(str(instance.end_of_support), '2035-01-01')
203+
204+
def test_lifecycle_with_no_dates(self):
205+
"""Test creating a hardware lifecycle with no date fields (all null)."""
206+
form = HardwareLifecycleForm(
207+
data={
208+
'device_type': self.device_type_2.pk,
209+
}
210+
)
211+
self.assertTrue(form.is_valid())
212+
instance = form.save()
213+
self.assertIsNone(instance.end_of_sale)
214+
self.assertIsNone(instance.end_of_support)
215+
self.assertIsNone(instance.end_of_maintenance)
216+
self.assertIsNone(instance.end_of_security)
217+
218+
def test_lifecycle_with_partial_dates(self):
219+
"""Test creating a hardware lifecycle with only some date fields."""
220+
form = HardwareLifecycleForm(
221+
data={
222+
'device_type': self.device_type_3.pk,
223+
'end_of_sale': '2030-01-01',
224+
# end_of_support intentionally omitted
225+
}
226+
)
227+
self.assertTrue(form.is_valid())
228+
instance = form.save()
229+
self.assertEqual(str(instance.end_of_sale), '2030-01-01')
230+
self.assertIsNone(instance.end_of_support)

0 commit comments

Comments
 (0)