mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
Fixes #9653 - Add default_platform to DeviceType
This commit is contained in:
parent
a1c9f7a2c6
commit
81b8046d1d
@ -309,6 +309,7 @@ class ManufacturerSerializer(NetBoxModelSerializer):
|
|||||||
class DeviceTypeSerializer(NetBoxModelSerializer):
|
class DeviceTypeSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
|
||||||
manufacturer = NestedManufacturerSerializer()
|
manufacturer = NestedManufacturerSerializer()
|
||||||
|
default_platform = NestedPlatformSerializer(required=False, allow_null=True)
|
||||||
u_height = serializers.DecimalField(
|
u_height = serializers.DecimalField(
|
||||||
max_digits=4,
|
max_digits=4,
|
||||||
decimal_places=1,
|
decimal_places=1,
|
||||||
@ -324,7 +325,7 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
|
|||||||
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', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
|
||||||
'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'description',
|
'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'description',
|
||||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
|
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
|
||||||
]
|
]
|
||||||
|
@ -436,6 +436,16 @@ class DeviceTypeFilterSet(NetBoxModelFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label=_('Manufacturer (slug)'),
|
label=_('Manufacturer (slug)'),
|
||||||
)
|
)
|
||||||
|
default_platform_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=Platform.objects.all(),
|
||||||
|
label=_('Default platform (ID)'),
|
||||||
|
)
|
||||||
|
default_platform = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='default_platform__slug',
|
||||||
|
queryset=Platform.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label=_('Default platform (slug)'),
|
||||||
|
)
|
||||||
has_front_image = django_filters.BooleanFilter(
|
has_front_image = django_filters.BooleanFilter(
|
||||||
label=_('Has a front image'),
|
label=_('Has a front image'),
|
||||||
method='_has_front_image'
|
method='_has_front_image'
|
||||||
|
@ -374,6 +374,10 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
default_platform = DynamicModelChoiceField(
|
||||||
|
queryset=Platform.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
part_number = forms.CharField(
|
part_number = forms.CharField(
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@ -412,7 +416,7 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Device Type', ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow', 'description')),
|
('Device Type', ('manufacturer', 'default_platform', 'part_number', 'u_height', 'is_full_depth', 'airflow', 'description')),
|
||||||
('Weight', ('weight', 'weight_unit')),
|
('Weight', ('weight', 'weight_unit')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
|
nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
|
||||||
|
@ -281,12 +281,17 @@ class DeviceTypeImportForm(NetBoxModelImportForm):
|
|||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
|
default_platform = forms.ModelChoiceField(
|
||||||
|
queryset=Platform.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = [
|
fields = [
|
||||||
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
|
'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
|
||||||
'description', 'comments',
|
'subdevice_role', 'airflow', 'description', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -378,7 +378,7 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
model = DeviceType
|
model = DeviceType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
('Hardware', ('manufacturer_id', 'part_number', 'subdevice_role', 'airflow')),
|
('Hardware', ('manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow')),
|
||||||
('Images', ('has_front_image', 'has_rear_image')),
|
('Images', ('has_front_image', 'has_rear_image')),
|
||||||
('Components', (
|
('Components', (
|
||||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||||
@ -391,6 +391,11 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_('Manufacturer')
|
label=_('Manufacturer')
|
||||||
)
|
)
|
||||||
|
default_platform_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Platform.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Default platform')
|
||||||
|
)
|
||||||
part_number = forms.CharField(
|
part_number = forms.CharField(
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
@ -378,13 +378,17 @@ class DeviceTypeForm(NetBoxModelForm):
|
|||||||
manufacturer = DynamicModelChoiceField(
|
manufacturer = DynamicModelChoiceField(
|
||||||
queryset=Manufacturer.objects.all()
|
queryset=Manufacturer.objects.all()
|
||||||
)
|
)
|
||||||
|
default_platform = DynamicModelChoiceField(
|
||||||
|
queryset=Platform.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
slug = SlugField(
|
slug = SlugField(
|
||||||
slug_source='model'
|
slug_source='model'
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Device Type', ('manufacturer', 'model', 'slug', 'description', 'tags')),
|
('Device Type', ('manufacturer', 'model', 'slug', 'description', 'tags', 'default_platform')),
|
||||||
('Chassis', (
|
('Chassis', (
|
||||||
'u_height', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow', 'weight', 'weight_unit',
|
'u_height', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow', 'weight', 'weight_unit',
|
||||||
)),
|
)),
|
||||||
@ -395,7 +399,7 @@ class DeviceTypeForm(NetBoxModelForm):
|
|||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = [
|
fields = [
|
||||||
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
|
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
|
||||||
'weight', 'weight_unit', 'front_image', 'rear_image', 'description', 'comments', 'tags',
|
'weight', 'weight_unit', 'front_image', 'rear_image', 'description', 'comments', 'tags', 'default_platform'
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'airflow': StaticSelect(),
|
'airflow': StaticSelect(),
|
||||||
|
19
netbox/dcim/migrations/0169_devicetype_default_platform.py
Normal file
19
netbox/dcim/migrations/0169_devicetype_default_platform.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 4.1.6 on 2023-02-10 18:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0168_interface_template_enabled'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicetype',
|
||||||
|
name='default_platform',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.platform'),
|
||||||
|
),
|
||||||
|
]
|
@ -82,6 +82,14 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
slug = models.SlugField(
|
slug = models.SlugField(
|
||||||
max_length=100
|
max_length=100
|
||||||
)
|
)
|
||||||
|
default_platform = models.ForeignKey(
|
||||||
|
to='dcim.Platform',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='+',
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name='Default platform'
|
||||||
|
)
|
||||||
part_number = models.CharField(
|
part_number = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -121,7 +129,7 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = (
|
clone_fields = (
|
||||||
'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
|
'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
|
||||||
)
|
)
|
||||||
prerequisite_models = (
|
prerequisite_models = (
|
||||||
'dcim.Manufacturer',
|
'dcim.Manufacturer',
|
||||||
@ -165,6 +173,7 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
'manufacturer': self.manufacturer.name,
|
'manufacturer': self.manufacturer.name,
|
||||||
'model': self.model,
|
'model': self.model,
|
||||||
'slug': self.slug,
|
'slug': self.slug,
|
||||||
|
'default_platform': self.default_platform.name if self.default_platform else None,
|
||||||
'part_number': self.part_number,
|
'part_number': self.part_number,
|
||||||
'u_height': float(self.u_height),
|
'u_height': float(self.u_height),
|
||||||
'is_full_depth': self.is_full_depth,
|
'is_full_depth': self.is_full_depth,
|
||||||
@ -801,6 +810,10 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
if is_new and not self.airflow:
|
if is_new and not self.airflow:
|
||||||
self.airflow = self.device_type.airflow
|
self.airflow = self.device_type.airflow
|
||||||
|
|
||||||
|
# Inherit default_platform from DeviceType if not set
|
||||||
|
if is_new and not self.platform:
|
||||||
|
self.platform = self.device_type.default_platform
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
# If this is a new Device, instantiate all the related components per the DeviceType definition
|
# If this is a new Device, instantiate all the related components per the DeviceType definition
|
||||||
|
@ -77,6 +77,9 @@ class DeviceTypeTable(NetBoxTable):
|
|||||||
manufacturer = tables.Column(
|
manufacturer = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
|
default_platform = tables.Column(
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
is_full_depth = columns.BooleanColumn(
|
is_full_depth = columns.BooleanColumn(
|
||||||
verbose_name='Full Depth'
|
verbose_name='Full Depth'
|
||||||
)
|
)
|
||||||
@ -100,7 +103,7 @@ class DeviceTypeTable(NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = models.DeviceType
|
model = models.DeviceType
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
|
'pk', 'id', 'model', 'manufacturer', 'default_platform', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
|
||||||
'airflow', 'weight', 'description', 'comments', 'instance_count', 'tags', 'created', 'last_updated',
|
'airflow', 'weight', 'description', 'comments', 'instance_count', 'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
|
@ -699,9 +699,16 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
)
|
)
|
||||||
Manufacturer.objects.bulk_create(manufacturers)
|
Manufacturer.objects.bulk_create(manufacturers)
|
||||||
|
|
||||||
|
platforms = (
|
||||||
|
Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturers[0]),
|
||||||
|
Platform(name='Platform 2', slug='platform-2', manufacturer=manufacturers[1]),
|
||||||
|
Platform(name='Platform 3', slug='platform-3', manufacturer=manufacturers[2]),
|
||||||
|
)
|
||||||
|
Platform.objects.bulk_create(platforms)
|
||||||
|
|
||||||
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, front_image='front.png', rear_image='rear.png', weight=10, weight_unit=WeightUnitChoices.UNIT_POUND),
|
DeviceType(manufacturer=manufacturers[0], default_platform=platforms[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True, front_image='front.png', rear_image='rear.png', weight=10, weight_unit=WeightUnitChoices.UNIT_POUND),
|
||||||
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, weight=20, weight_unit=WeightUnitChoices.UNIT_POUND),
|
DeviceType(manufacturer=manufacturers[1], default_platform=platforms[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, weight=20, weight_unit=WeightUnitChoices.UNIT_POUND),
|
||||||
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, weight=30, weight_unit=WeightUnitChoices.UNIT_KILOGRAM),
|
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, weight=30, weight_unit=WeightUnitChoices.UNIT_KILOGRAM),
|
||||||
)
|
)
|
||||||
DeviceType.objects.bulk_create(device_types)
|
DeviceType.objects.bulk_create(device_types)
|
||||||
@ -785,6 +792,13 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
|
params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_default_platform(self):
|
||||||
|
platforms = Platform.objects.all()[:2]
|
||||||
|
params = {'default_platform_id': [platforms[0].pk, platforms[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'default_platform': [platforms[0].slug, platforms[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_has_front_image(self):
|
def test_has_front_image(self):
|
||||||
params = {'has_front_image': True}
|
params = {'has_front_image': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
@ -503,6 +503,12 @@ class DeviceTypeTestCase(
|
|||||||
)
|
)
|
||||||
Manufacturer.objects.bulk_create(manufacturers)
|
Manufacturer.objects.bulk_create(manufacturers)
|
||||||
|
|
||||||
|
platforms = (
|
||||||
|
Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturers[0]),
|
||||||
|
Platform(name='Platform 2', slug='platform-3', manufacturer=manufacturers[1]),
|
||||||
|
)
|
||||||
|
Platform.objects.bulk_create(platforms)
|
||||||
|
|
||||||
DeviceType.objects.bulk_create([
|
DeviceType.objects.bulk_create([
|
||||||
DeviceType(model='Device Type 1', slug='device-type-1', manufacturer=manufacturers[0]),
|
DeviceType(model='Device Type 1', slug='device-type-1', manufacturer=manufacturers[0]),
|
||||||
DeviceType(model='Device Type 2', slug='device-type-2', manufacturer=manufacturers[0]),
|
DeviceType(model='Device Type 2', slug='device-type-2', manufacturer=manufacturers[0]),
|
||||||
@ -513,6 +519,7 @@ class DeviceTypeTestCase(
|
|||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'manufacturer': manufacturers[1].pk,
|
'manufacturer': manufacturers[1].pk,
|
||||||
|
'default_platform': platforms[0].pk,
|
||||||
'model': 'Device Type X',
|
'model': 'Device Type X',
|
||||||
'slug': 'device-type-x',
|
'slug': 'device-type-x',
|
||||||
'part_number': '123ABC',
|
'part_number': '123ABC',
|
||||||
@ -525,6 +532,7 @@ class DeviceTypeTestCase(
|
|||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'manufacturer': manufacturers[1].pk,
|
'manufacturer': manufacturers[1].pk,
|
||||||
|
'default_platform': platforms[1].pk,
|
||||||
'u_height': 3,
|
'u_height': 3,
|
||||||
'is_full_depth': False,
|
'is_full_depth': False,
|
||||||
}
|
}
|
||||||
@ -673,6 +681,7 @@ class DeviceTypeTestCase(
|
|||||||
"""
|
"""
|
||||||
IMPORT_DATA = """
|
IMPORT_DATA = """
|
||||||
manufacturer: Generic
|
manufacturer: Generic
|
||||||
|
default_platform: Platform
|
||||||
model: TEST-1000
|
model: TEST-1000
|
||||||
slug: test-1000
|
slug: test-1000
|
||||||
u_height: 2
|
u_height: 2
|
||||||
@ -755,8 +764,11 @@ inventory-items:
|
|||||||
manufacturer: Generic
|
manufacturer: Generic
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Create the manufacturer
|
# Create the manufacturer and platform
|
||||||
Manufacturer(name='Generic', slug='generic').save()
|
manufacturer = Manufacturer(name='Generic', slug='generic')
|
||||||
|
manufacturer.save()
|
||||||
|
platform = Platform(name='Platform', slug='test-platform', manufacturer=manufacturer)
|
||||||
|
platform.save()
|
||||||
|
|
||||||
# Add all required permissions to the test user
|
# Add all required permissions to the test user
|
||||||
self.add_permissions(
|
self.add_permissions(
|
||||||
@ -783,6 +795,7 @@ inventory-items:
|
|||||||
|
|
||||||
device_type = DeviceType.objects.get(model='TEST-1000')
|
device_type = DeviceType.objects.get(model='TEST-1000')
|
||||||
self.assertEqual(device_type.comments, 'Test comment')
|
self.assertEqual(device_type.comments, 'Test comment')
|
||||||
|
self.assertEqual(device_type.default_platform.pk, platform.pk)
|
||||||
|
|
||||||
# Verify all of the components were created
|
# Verify all of the components were created
|
||||||
self.assertEqual(device_type.consoleporttemplates.count(), 3)
|
self.assertEqual(device_type.consoleporttemplates.count(), 3)
|
||||||
|
@ -27,6 +27,10 @@
|
|||||||
<td>Part Number</td>
|
<td>Part Number</td>
|
||||||
<td>{{ object.part_number|placeholder }}</td>
|
<td>{{ object.part_number|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Default Platform</td>
|
||||||
|
<td>{{ object.default_platform|linkify }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ object.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
Loading…
Reference in New Issue
Block a user