diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index c696e4d2a..71b53e8d4 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -324,7 +324,7 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer): platform = NestedPlatformSerializer(required=False, allow_null=True) site = NestedSiteSerializer() rack = NestedRackSerializer(required=False, allow_null=True) - face = ChoiceField(choices=RACK_FACE_CHOICES, required=False, allow_null=True) + face = ChoiceField(choices=DeviceFaceChoices, required=False, allow_null=True) status = ChoiceField(choices=DEVICE_STATUS_CHOICES, required=False) primary_ip = NestedIPAddressSerializer(read_only=True) primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 4ebeac047..819b1678f 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -66,6 +66,26 @@ class RackStatusChoices(ChoiceSet): } +# +# Devices +# + +class DeviceFaceChoices(ChoiceSet): + + FACE_FRONT = 'front' + FACE_REAR = 'rear' + + CHOICES = ( + (FACE_FRONT, 'Front'), + (FACE_REAR, 'Rear'), + ) + + LEGACY_MAP = { + FACE_FRONT: 0, + FACE_REAR: 1, + } + + # # Console port type values # diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index 6a6e9e521..c25502bcb 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -1,17 +1,3 @@ -# Device rack faces -RACK_FACE_FRONT = 0 -RACK_FACE_REAR = 1 -RACK_FACE_CHOICES = [ - [RACK_FACE_FRONT, 'Front'], - [RACK_FACE_REAR, 'Rear'], -] - -# Device rack position -DEVICE_POSITION_CHOICES = [ - # Rack.u_height is limited to 100 - (i, 'Unit {}'.format(i)) for i in range(1, 101) -] - # Parent/child device roles SUBDEVICE_ROLE_PARENT = True SUBDEVICE_ROLE_CHILD = False diff --git a/netbox/dcim/fixtures/dcim.json b/netbox/dcim/fixtures/dcim.json index ece19a83c..b9f41edb5 100644 --- a/netbox/dcim/fixtures/dcim.json +++ b/netbox/dcim/fixtures/dcim.json @@ -1910,7 +1910,7 @@ "site": 1, "rack": 1, "position": 1, - "face": 0, + "face": "front", "status": true, "primary_ip4": 1, "primary_ip6": null, @@ -1931,7 +1931,7 @@ "site": 1, "rack": 1, "position": 17, - "face": 0, + "face": "rear", "status": true, "primary_ip4": 5, "primary_ip6": null, @@ -1952,7 +1952,7 @@ "site": 1, "rack": 1, "position": 33, - "face": 0, + "face": "rear", "status": true, "primary_ip4": null, "primary_ip6": null, @@ -1973,7 +1973,7 @@ "site": 1, "rack": 1, "position": 34, - "face": 0, + "face": "rear", "status": true, "primary_ip4": null, "primary_ip6": null, @@ -1994,7 +1994,7 @@ "site": 1, "rack": 2, "position": 34, - "face": 0, + "face": "rear", "status": true, "primary_ip4": null, "primary_ip6": null, @@ -2015,7 +2015,7 @@ "site": 1, "rack": 2, "position": 33, - "face": 0, + "face": "rear", "status": true, "primary_ip4": null, "primary_ip6": null, @@ -2036,7 +2036,7 @@ "site": 1, "rack": 2, "position": 1, - "face": 0, + "face": "rear", "status": true, "primary_ip4": 3, "primary_ip6": null, @@ -2057,7 +2057,7 @@ "site": 1, "rack": 2, "position": 17, - "face": 0, + "face": "rear", "status": true, "primary_ip4": 19, "primary_ip6": null, @@ -2078,7 +2078,7 @@ "site": 1, "rack": 1, "position": 42, - "face": 0, + "face": "rear", "status": true, "primary_ip4": null, "primary_ip6": null, @@ -2099,7 +2099,7 @@ "site": 1, "rack": 1, "position": null, - "face": null, + "face": "", "status": true, "primary_ip4": null, "primary_ip6": null, @@ -2120,7 +2120,7 @@ "site": 1, "rack": 2, "position": null, - "face": null, + "face": "", "status": true, "primary_ip4": null, "primary_ip6": null, diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index b487fb0a6..aec0769d6 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1716,7 +1716,7 @@ class DeviceCSVForm(BaseDeviceCSVForm): help_text='Name of parent rack' ) face = CSVChoiceField( - choices=RACK_FACE_CHOICES, + choices=DeviceFaceChoices, required=False, help_text='Mounted rack face' ) diff --git a/netbox/dcim/migrations/0078_rack_choicefields_to_slugs.py b/netbox/dcim/migrations/0078_rack_choicefields_to_slugs.py index 7c9b32310..274b23812 100644 --- a/netbox/dcim/migrations/0078_rack_choicefields_to_slugs.py +++ b/netbox/dcim/migrations/0078_rack_choicefields_to_slugs.py @@ -49,7 +49,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='rack', name='status', - field=models.CharField(blank=True, max_length=50), + field=models.CharField(blank=True, default='active', max_length=50), ), migrations.RunPython( code=rack_status_to_slug diff --git a/netbox/dcim/migrations/0079_device_choicefields_to_slugs.py b/netbox/dcim/migrations/0079_device_choicefields_to_slugs.py new file mode 100644 index 000000000..bc8785dae --- /dev/null +++ b/netbox/dcim/migrations/0079_device_choicefields_to_slugs.py @@ -0,0 +1,31 @@ +from django.db import migrations, models + +DEVICE_FACE_CHOICES = ( + (0, 'front'), + (1, 'rear'), +) + + +def rack_type_to_slug(apps, schema_editor): + Device = apps.get_model('dcim', 'Device') + for id, slug in DEVICE_FACE_CHOICES: + Device.objects.filter(face=str(id)).update(face=slug) + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0078_rack_choicefields_to_slugs'), + ] + + operations = [ + # Device.face + migrations.AlterField( + model_name='device', + name='face', + field=models.CharField(blank=True, max_length=50), + ), + migrations.RunPython( + code=rack_type_to_slug + ), + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 23532efa8..42b10a102 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -655,7 +655,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel): def get_status_class(self): return self.STATUS_CLASS_MAP.get(self.status) - def get_rack_units(self, face=RACK_FACE_FRONT, exclude=None, remove_redundant=False): + def get_rack_units(self, face=DeviceFaceChoices.FACE_FRONT, exclude=None, remove_redundant=False): """ Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'} Each key 'device' is either a Device or None. By default, multi-U devices are repeated for each U they occupy. @@ -687,10 +687,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel): return [u for u in elevation.values()] def get_front_elevation(self): - return self.get_rack_units(face=RACK_FACE_FRONT, remove_redundant=True) + return self.get_rack_units(face=DeviceFaceChoices.FACE_FRONT, remove_redundant=True) def get_rear_elevation(self): - return self.get_rack_units(face=RACK_FACE_REAR, remove_redundant=True) + return self.get_rack_units(face=DeviceFaceChoices.FACE_REAR, remove_redundant=True) def get_available_units(self, u_height=1, rack_face=None, exclude=list()): """ @@ -1535,10 +1535,10 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): verbose_name='Position (U)', help_text='The lowest-numbered unit occupied by the device' ) - face = models.PositiveSmallIntegerField( + face = models.CharField( + max_length=50, blank=True, - null=True, - choices=RACK_FACE_CHOICES, + choices=DeviceFaceChoices, verbose_name='Rack face' ) status = models.PositiveSmallIntegerField( @@ -1634,7 +1634,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): }) if self.rack is None: - if self.face is not None: + if self.face: raise ValidationError({ 'face': "Cannot select a rack face without assigning a rack.", }) @@ -1644,7 +1644,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): }) # Validate position/face combination - if self.position and self.face is None: + if self.position and not self.face: raise ValidationError({ 'face': "Must specify rack face when defining rack position.", }) diff --git a/netbox/dcim/tests/test_forms.py b/netbox/dcim/tests/test_forms.py index 2f333ea69..8ac9aa84e 100644 --- a/netbox/dcim/tests/test_forms.py +++ b/netbox/dcim/tests/test_forms.py @@ -21,7 +21,7 @@ class DeviceTestCase(TestCase): 'device_type': get_id(DeviceType, 'qfx5100-48s'), 'site': get_id(Site, 'test1'), 'rack': '1', - 'face': RACK_FACE_FRONT, + 'face': DeviceFaceChoices.FACE_FRONT, 'position': 41, 'platform': get_id(Platform, 'juniper-junos'), 'status': DEVICE_STATUS_ACTIVE, @@ -38,7 +38,7 @@ class DeviceTestCase(TestCase): 'device_type': get_id(DeviceType, 'qfx5100-48s'), 'site': get_id(Site, 'test1'), 'rack': '1', - 'face': RACK_FACE_FRONT, + 'face': DeviceFaceChoices.FACE_FRONT, 'position': 1, 'platform': get_id(Platform, 'juniper-junos'), 'status': DEVICE_STATUS_ACTIVE, @@ -54,7 +54,7 @@ class DeviceTestCase(TestCase): 'device_type': get_id(DeviceType, 'cwg-24vym415c9'), 'site': get_id(Site, 'test1'), 'rack': '1', - 'face': None, + 'face': '', 'position': None, 'platform': None, 'status': DEVICE_STATUS_ACTIVE, @@ -71,7 +71,7 @@ class DeviceTestCase(TestCase): 'device_type': get_id(DeviceType, 'cwg-24vym415c9'), 'site': get_id(Site, 'test1'), 'rack': '1', - 'face': RACK_FACE_REAR, + 'face': DeviceFaceChoices.FACE_REAR, 'position': None, 'platform': None, 'status': DEVICE_STATUS_ACTIVE, diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 2b5bed283..5d1099029 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -87,7 +87,7 @@ class RackTestCase(TestCase): site=self.site1, rack=rack1, position=43, - face=RACK_FACE_FRONT, + face=DeviceFaceChoices.FACE_FRONT, ) device1.save() @@ -117,7 +117,7 @@ class RackTestCase(TestCase): site=self.site1, rack=self.rack, position=10, - face=RACK_FACE_REAR, + face=DeviceFaceChoices.FACE_REAR, ) device1.save() @@ -146,7 +146,7 @@ class RackTestCase(TestCase): site=self.site1, rack=self.rack, position=None, - face=None, + face='', ) self.assertTrue(pdu)