From afd82fd9d3e1f7ea5f7a851a1eef1af1ef35bc36 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 18 Nov 2019 22:03:25 -0500 Subject: [PATCH] DeviceType.subdevice_role to slug (#3569) --- netbox/dcim/api/serializers.py | 2 +- netbox/dcim/choices.py | 20 ++++++++++++ netbox/dcim/constants.py | 9 ------ netbox/dcim/forms.py | 10 +++--- .../0081_devicetype_subdevicerole_to_slug.py | 31 +++++++++++++++++++ netbox/dcim/models.py | 22 +++++++------ netbox/dcim/tables.py | 8 ----- netbox/dcim/tests/test_api.py | 5 +-- 8 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 netbox/dcim/migrations/0081_devicetype_subdevicerole_to_slug.py diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 71b53e8d4..11450250c 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -187,7 +187,7 @@ class ManufacturerSerializer(ValidatedModelSerializer): class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer): manufacturer = NestedManufacturerSerializer() - subdevice_role = ChoiceField(choices=SUBDEVICE_ROLE_CHOICES, required=False, allow_null=True) + subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, required=False, allow_null=True) tags = TagListSerializerField(required=False) device_count = serializers.IntegerField(read_only=True) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 819b1678f..58504ab34 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -66,6 +66,26 @@ class RackStatusChoices(ChoiceSet): } +# +# DeviceTypes +# + +class SubdeviceRoleChoices(ChoiceSet): + + ROLE_PARENT = 'parent' + ROLE_CHILD = 'child' + + CHOICES = ( + (ROLE_PARENT, 'Parent'), + (ROLE_CHILD, 'Child'), + ) + + LEGACY_MAP = { + ROLE_PARENT: True, + ROLE_CHILD: False, + } + + # # Devices # diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index c25502bcb..bbe8d7c5e 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -1,12 +1,3 @@ -# Parent/child device roles -SUBDEVICE_ROLE_PARENT = True -SUBDEVICE_ROLE_CHILD = False -SUBDEVICE_ROLE_CHOICES = ( - (None, 'None'), - (SUBDEVICE_ROLE_PARENT, 'Parent'), - (SUBDEVICE_ROLE_CHILD, 'Child'), -) - # # Numeric interface types # diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index aec0769d6..5ab66bc47 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -879,12 +879,10 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm): value_field="slug", ) ) - subdevice_role = forms.NullBooleanField( + subdevice_role = forms.MultipleChoiceField( + choices=add_blank_choice(SubdeviceRoleChoices), required=False, - label='Subdevice role', - widget=StaticSelect2( - choices=add_blank_choice(SUBDEVICE_ROLE_CHOICES) - ) + widget=StaticSelect2Multiple() ) console_ports = forms.NullBooleanField( required=False, @@ -3382,7 +3380,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form): rack=device_bay.device.rack, parent_bay__isnull=True, device_type__u_height=0, - device_type__subdevice_role=SUBDEVICE_ROLE_CHILD + device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD ).exclude(pk=device_bay.device.pk) diff --git a/netbox/dcim/migrations/0081_devicetype_subdevicerole_to_slug.py b/netbox/dcim/migrations/0081_devicetype_subdevicerole_to_slug.py new file mode 100644 index 000000000..edcfc9e4b --- /dev/null +++ b/netbox/dcim/migrations/0081_devicetype_subdevicerole_to_slug.py @@ -0,0 +1,31 @@ +from django.db import migrations, models + +SUBDEVICE_ROLE_CHOICES = ( + ('true', 'parent'), + ('false', 'child'), +) + + +def devicetype_subdevicerole_to_slug(apps, schema_editor): + DeviceType = apps.get_model('dcim', 'DeviceType') + for boolean, slug in SUBDEVICE_ROLE_CHOICES: + DeviceType.objects.filter(subdevice_role=boolean).update(subdevice_role=slug) + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ('dcim', '0080_device_face_to_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='devicetype', + name='subdevice_role', + field=models.CharField(blank=True, default='', max_length=50), + ), + migrations.RunPython( + code=devicetype_subdevicerole_to_slug + ), + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 42b10a102..93aa8c8fb 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -919,12 +919,12 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): verbose_name='Is full depth', help_text='Device consumes both front and rear rack faces' ) - subdevice_role = models.NullBooleanField( - default=None, + subdevice_role = models.CharField( + max_length=50, + choices=SubdeviceRoleChoices, verbose_name='Parent/child status', - choices=SUBDEVICE_ROLE_CHOICES, - help_text='Parent devices house child devices in device bays. Select ' - '"None" if this device type is neither a parent nor a child.' + help_text='Parent devices house child devices in device bays. Leave blank ' + 'if this device type is neither a parent nor a child.' ) comments = models.TextField( blank=True @@ -968,7 +968,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): self.part_number, self.u_height, self.is_full_depth, - self.get_subdevice_role_display() if self.subdevice_role else None, + self.get_subdevice_role_display(), self.comments, ) @@ -988,13 +988,15 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): "{}U".format(d, d.rack, self.u_height) }) - if self.subdevice_role != SUBDEVICE_ROLE_PARENT and self.device_bay_templates.count(): + if ( + self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT + ) and self.device_bay_templates.count(): raise ValidationError({ 'subdevice_role': "Must delete all device bay templates associated with this device before " "declassifying it as a parent device." }) - if self.u_height and self.subdevice_role == SUBDEVICE_ROLE_CHILD: + if self.u_height and self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD: raise ValidationError({ 'u_height': "Child device types must be 0U." }) @@ -1005,11 +1007,11 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): @property def is_parent_device(self): - return bool(self.subdevice_role) + return self.subdevice_role == SubdeviceRoleChoices.ROLE_PARENT @property def is_child_device(self): - return bool(self.subdevice_role is False) + return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD class ConsolePortTemplate(ComponentTemplateModel): diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index a8a4fc227..a5edd1f06 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -156,10 +156,6 @@ DEVICE_PRIMARY_IP = """ {{ record.primary_ip4.address.ip|default:"" }} """ -SUBDEVICE_ROLE_TEMPLATE = """ -{% if record.subdevice_role == True %}Parent{% elif record.subdevice_role == False %}Child{% else %}—{% endif %} -""" - DEVICETYPE_INSTANCES_TEMPLATE = """ {{ record.instance_count }} """ @@ -391,10 +387,6 @@ class DeviceTypeTable(BaseTable): verbose_name='Device Type' ) is_full_depth = BooleanColumn(verbose_name='Full Depth') - subdevice_role = tables.TemplateColumn( - template_code=SUBDEVICE_ROLE_TEMPLATE, - verbose_name='Subdevice Role' - ) instance_count = tables.TemplateColumn( template_code=DEVICETYPE_INSTANCES_TEMPLATE, verbose_name='Instances' diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 9c873c886..2963f7329 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -3,6 +3,7 @@ from netaddr import IPNetwork from rest_framework import status from circuits.models import Circuit, CircuitTermination, CircuitType, Provider +from dcim.choices import SubdeviceRoleChoices from dcim.constants import * from dcim.models import ( Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, @@ -2590,11 +2591,11 @@ class DeviceBayTest(APITestCase): manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') self.devicetype1 = DeviceType.objects.create( manufacturer=manufacturer, model='Parent Device Type', slug='parent-device-type', - subdevice_role=SUBDEVICE_ROLE_PARENT + subdevice_role=SubdeviceRoleChoices.ROLE_PARENT ) self.devicetype2 = DeviceType.objects.create( manufacturer=manufacturer, model='Child Device Type', slug='child-device-type', - subdevice_role=SUBDEVICE_ROLE_CHILD + subdevice_role=SubdeviceRoleChoices.ROLE_CHILD ) devicerole = DeviceRole.objects.create( name='Test Device Role 1', slug='test-device-role-1', color='ff0000'