3839: Add airflow field to DeviceType

This commit is contained in:
jeremystretch 2021-10-14 15:38:29 -04:00
parent 176bd2396b
commit 2c2c2e9060
13 changed files with 81 additions and 10 deletions

View File

@ -288,13 +288,14 @@ class DeviceTypeSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
manufacturer = NestedManufacturerSerializer() manufacturer = NestedManufacturerSerializer()
subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False) subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False)
airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False)
device_count = serializers.IntegerField(read_only=True) device_count = serializers.IntegerField(read_only=True)
class Meta: class Meta:
model = DeviceType model = DeviceType
fields = [ fields = [
'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
'subdevice_role', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields', 'created', 'subdevice_role', 'airflow', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields', 'created',
'last_updated', 'device_count', 'last_updated', 'device_count',
] ]

View File

@ -174,6 +174,25 @@ class DeviceStatusChoices(ChoiceSet):
} }
class DeviceAirflowChoices(ChoiceSet):
AIRFLOW_FRONT_TO_REAR = 'front-to-rear'
AIRFLOW_REAR_TO_FRONT = 'rear-to-front'
AIRFLOW_LEFT_TO_RIGHT = 'left-to-right'
AIRFLOW_RIGHT_TO_LEFT = 'right-to-left'
AIRFLOW_SIDE_TO_REAR = 'side-to-rear'
AIRFLOW_PASSIVE = 'passive'
CHOICES = (
(AIRFLOW_FRONT_TO_REAR, 'Front to rear'),
(AIRFLOW_REAR_TO_FRONT, 'Rear to front'),
(AIRFLOW_LEFT_TO_RIGHT, 'Left to right'),
(AIRFLOW_RIGHT_TO_LEFT, 'Right to left'),
(AIRFLOW_SIDE_TO_REAR, 'Side to rear'),
(AIRFLOW_PASSIVE, 'Passive'),
)
# #
# ConsolePorts # ConsolePorts
# #

View File

@ -441,7 +441,7 @@ class DeviceTypeFilterSet(PrimaryModelFilterSet):
class Meta: class Meta:
model = DeviceType model = DeviceType
fields = [ fields = [
'id', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'id', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
] ]
def search(self, queryset, name, value): def search(self, queryset, name, value):

View File

@ -335,6 +335,11 @@ class DeviceTypeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModel
widget=BulkEditNullBooleanSelect(), widget=BulkEditNullBooleanSelect(),
label='Is full depth' label='Is full depth'
) )
airflow = forms.ChoiceField(
choices=add_blank_choice(DeviceAirflowChoices),
required=False,
widget=StaticSelect()
)
class Meta: class Meta:
nullable_fields = [] nullable_fields = []

View File

@ -385,7 +385,7 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
model = DeviceType model = DeviceType
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
['manufacturer_id', 'subdevice_role'], ['manufacturer_id', 'subdevice_role', 'airflow'],
['console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports'], ['console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports'],
] ]
q = forms.CharField( q = forms.CharField(
@ -404,6 +404,11 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
required=False, required=False,
widget=StaticSelectMultiple() widget=StaticSelectMultiple()
) )
airflow = forms.MultipleChoiceField(
choices=add_blank_choice(DeviceAirflowChoices),
required=False,
widget=StaticSelectMultiple()
)
console_ports = forms.NullBooleanField( console_ports = forms.NullBooleanField(
required=False, required=False,
label='Has console ports', label='Has console ports',

View File

@ -367,12 +367,15 @@ class DeviceTypeForm(BootstrapMixin, CustomFieldModelForm):
class Meta: class Meta:
model = DeviceType model = DeviceType
fields = [ fields = [
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
'front_image', 'rear_image', 'comments', 'tags', 'front_image', 'rear_image', 'comments', 'tags',
] ]
fieldsets = ( fieldsets = (
('Device Type', ( ('Device Type', (
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'tags', 'manufacturer', 'model', 'slug', 'part_number', 'tags',
)),
('Chassis', (
'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
)), )),
('Images', ('front_image', 'rear_image')), ('Images', ('front_image', 'rear_image')),
) )

View File

@ -26,7 +26,7 @@ class DeviceTypeImportForm(BootstrapMixin, forms.ModelForm):
class Meta: class Meta:
model = DeviceType model = DeviceType
fields = [ fields = [
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
'comments', 'comments',
] ]

View File

@ -179,6 +179,9 @@ class DeviceTypeType(PrimaryObjectType):
def resolve_subdevice_role(self, info): def resolve_subdevice_role(self, info):
return self.subdevice_role or None return self.subdevice_role or None
def resolve_airflow(self, info):
return self.airflow or None
class FrontPortType(ComponentObjectType): class FrontPortType(ComponentObjectType):

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.8 on 2021-10-14 19:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0135_location_tenant'),
]
operations = [
migrations.AddField(
model_name='devicetype',
name='airflow',
field=models.CharField(blank=True, max_length=50),
),
]

View File

@ -115,6 +115,12 @@ class DeviceType(PrimaryModel):
help_text='Parent devices house child devices in device bays. Leave blank ' help_text='Parent devices house child devices in device bays. Leave blank '
'if this device type is neither a parent nor a child.' 'if this device type is neither a parent nor a child.'
) )
airflow = models.CharField(
max_length=50,
choices=DeviceAirflowChoices,
blank=True,
verbose_name='Airflow direction'
)
front_image = models.ImageField( front_image = models.ImageField(
upload_to='devicetype-images', upload_to='devicetype-images',
blank=True blank=True
@ -130,7 +136,7 @@ class DeviceType(PrimaryModel):
objects = RestrictedQuerySet.as_manager() objects = RestrictedQuerySet.as_manager()
clone_fields = [ clone_fields = [
'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
] ]
class Meta: class Meta:
@ -165,6 +171,7 @@ class DeviceType(PrimaryModel):
('u_height', self.u_height), ('u_height', self.u_height),
('is_full_depth', self.is_full_depth), ('is_full_depth', self.is_full_depth),
('subdevice_role', self.subdevice_role), ('subdevice_role', self.subdevice_role),
('airflow', self.airflow),
('comments', self.comments), ('comments', self.comments),
)) ))

View File

@ -77,7 +77,7 @@ class DeviceTypeTable(BaseTable):
model = DeviceType model = DeviceType
fields = ( fields = (
'pk', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'pk', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
'comments', 'instance_count', 'tags', 'airflow', 'comments', 'instance_count', 'tags',
) )
default_columns = ( default_columns = (
'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count', 'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count',

View File

@ -638,8 +638,8 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
device_types = ( device_types = (
DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True), DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True),
DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2', u_height=2, is_full_depth=True, subdevice_role=SubdeviceRoleChoices.ROLE_PARENT), DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2', u_height=2, is_full_depth=True, subdevice_role=SubdeviceRoleChoices.ROLE_PARENT, airflow=DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR),
DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3', u_height=3, is_full_depth=False, subdevice_role=SubdeviceRoleChoices.ROLE_CHILD), DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3', u_height=3, is_full_depth=False, subdevice_role=SubdeviceRoleChoices.ROLE_CHILD, airflow=DeviceAirflowChoices.AIRFLOW_REAR_TO_FRONT),
) )
DeviceType.objects.bulk_create(device_types) DeviceType.objects.bulk_create(device_types)
@ -704,6 +704,10 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'subdevice_role': SubdeviceRoleChoices.ROLE_PARENT} params = {'subdevice_role': SubdeviceRoleChoices.ROLE_PARENT}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_airflow(self):
params = {'airflow': DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_manufacturer(self): def test_manufacturer(self):
manufacturers = Manufacturer.objects.all()[:2] manufacturers = Manufacturer.objects.all()[:2]
params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]} params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]}

View File

@ -90,6 +90,12 @@
{{ object.get_subdevice_role_display|placeholder }} {{ object.get_subdevice_role_display|placeholder }}
</td> </td>
</tr> </tr>
<tr>
<td>Airflow direction</td>
<td>
{{ object.get_airflow_display|placeholder }}
</td>
</tr>
<tr> <tr>
<td>Front Image</td> <td>Front Image</td>
<td> <td>