mirror of
https://github.com/netbox-community/netbox.git
synced 2026-03-21 20:18:38 -06:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f336eee2e | ||
|
|
6030fc383a | ||
|
|
c3c7cf15b2 | ||
|
|
2b7049c39c | ||
|
|
3ededeb0e7 | ||
|
|
753fedf5e7 | ||
|
|
38afed60ef | ||
|
|
66f6b2b6f9 | ||
|
|
61cef9400d | ||
|
|
d57f230f37 | ||
|
|
472dc3882e | ||
|
|
21f78049bc | ||
|
|
e28ed7446c | ||
|
|
2f5543933e | ||
|
|
9b57512b12 | ||
|
|
1fc43026d0 | ||
|
|
5804b53bb1 | ||
|
|
775d6aa936 | ||
|
|
639a739b5b | ||
|
|
b01d92c98b | ||
|
|
da79cc775d | ||
|
|
6f5fd26183 | ||
|
|
10157394ae | ||
|
|
ae0907fb37 | ||
|
|
fea6ad61fd | ||
|
|
675e68f276 | ||
|
|
20b907a8c9 | ||
|
|
8ccb0f7b63 | ||
|
|
068fce4d7c | ||
|
|
2e4bce2dad | ||
|
|
dad96c525f | ||
|
|
625c4eb5bb | ||
|
|
cac3c1221c | ||
|
|
3a9d00a537 | ||
|
|
4040e4f266 | ||
|
|
f938309ed9 | ||
|
|
86f6de40d2 | ||
|
|
83c6149e49 | ||
|
|
98d898aba9 | ||
|
|
07bb6aa365 | ||
|
|
f3c34b30ec | ||
|
|
2281889e9d | ||
|
|
b19d0d61f4 | ||
|
|
d64c4d75f8 | ||
|
|
b5bd8905ca | ||
|
|
cb5521f818 | ||
|
|
3cb854b7d5 | ||
|
|
d980837da0 | ||
|
|
5c19afc07c | ||
|
|
67defb3228 | ||
|
|
cca4cc61b6 | ||
|
|
9b0c6110bb | ||
|
|
758b230403 | ||
|
|
8ea33df148 | ||
|
|
c86210f024 | ||
|
|
685c1afdcf | ||
|
|
d62a0d7d8d | ||
|
|
1c527366c9 | ||
|
|
e1684fb645 | ||
|
|
969ae81574 | ||
|
|
baec71fcaf | ||
|
|
44abeeff5a | ||
|
|
93e01d5b07 | ||
|
|
1be917fb90 |
@@ -23,3 +23,8 @@ The device bay's name. Must be unique to the parent device.
|
||||
### Label
|
||||
|
||||
An alternative physical label identifying the device bay.
|
||||
|
||||
### Enabled
|
||||
|
||||
Whether this device bay is enabled. Disabled device bays are not available for installation.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Module Bays
|
||||
|
||||
Module bays represent a space or slot within a device in which a field-replaceable [module](./module.md) may be installed. A common example is that of a chassis-based switch such as the Cisco Nexus 9000 or Juniper EX9200. Modules in turn hold additional components that become available to the parent device.
|
||||
Module bays represent a space or slot within a device in which a field-replaceable [module](./module.md) may be installed. A common example is that of a chassis-based switch such as the Cisco Nexus 9000 or Juniper EX9200. Modules, in turn, hold additional components that become available to the parent device.
|
||||
|
||||
!!! note
|
||||
If you need to model child devices rather than modules, use a [device bay](./devicebay.md) instead.
|
||||
@@ -29,3 +29,8 @@ An alternative physical label identifying the module bay.
|
||||
### Position
|
||||
|
||||
The numeric position in which this module bay is situated. For example, this would be the number assigned to a slot within a chassis-based switch.
|
||||
|
||||
### Enabled
|
||||
|
||||
Whether this module bay is enabled. Disabled module bays are not available for installation.
|
||||
|
||||
|
||||
@@ -423,27 +423,29 @@ class ModuleBaySerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
_occupied = serializers.BooleanField(required=False, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ModuleBay
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'installed_module', 'label', 'position',
|
||||
'description', 'owner', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'position', 'enabled',
|
||||
'description', 'installed_module', 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'installed_module', 'name', 'description')
|
||||
brief_fields = ('id', 'url', 'display', 'installed_module', 'name', 'enabled', 'description', '_occupied')
|
||||
|
||||
|
||||
class DeviceBaySerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
installed_device = DeviceSerializer(nested=True, required=False, allow_null=True)
|
||||
_occupied = serializers.BooleanField(required=False, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = DeviceBay
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'name', 'label', 'description', 'installed_device',
|
||||
'owner', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display_url', 'display', 'device', 'name', 'label', 'enabled', 'description',
|
||||
'installed_device', 'owner', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description')
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'enabled', 'description', '_occupied',)
|
||||
|
||||
|
||||
class InventoryItemSerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
|
||||
@@ -317,10 +317,10 @@ class ModuleBayTemplateSerializer(ComponentTemplateSerializer):
|
||||
class Meta:
|
||||
model = ModuleBayTemplate
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'position', 'description',
|
||||
'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'position', 'enabled', 'description',
|
||||
'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'enabled', 'description')
|
||||
|
||||
|
||||
class DeviceBayTemplateSerializer(ComponentTemplateSerializer):
|
||||
@@ -331,10 +331,10 @@ class DeviceBayTemplateSerializer(ComponentTemplateSerializer):
|
||||
class Meta:
|
||||
model = DeviceBayTemplate
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'name', 'label', 'description',
|
||||
'id', 'url', 'display', 'device_type', 'name', 'label', 'enabled', 'description',
|
||||
'created', 'last_updated'
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'enabled', 'description')
|
||||
|
||||
|
||||
class InventoryItemTemplateSerializer(ComponentTemplateSerializer):
|
||||
|
||||
@@ -1032,7 +1032,7 @@ class ModuleBayTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeCo
|
||||
|
||||
class Meta:
|
||||
model = ModuleBayTemplate
|
||||
fields = ('id', 'name', 'label', 'position', 'description')
|
||||
fields = ('id', 'name', 'label', 'position', 'enabled', 'description')
|
||||
|
||||
|
||||
@register_filterset
|
||||
@@ -1040,7 +1040,7 @@ class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponent
|
||||
|
||||
class Meta:
|
||||
model = DeviceBayTemplate
|
||||
fields = ('id', 'name', 'label', 'description')
|
||||
fields = ('id', 'name', 'label', 'enabled', 'description')
|
||||
|
||||
|
||||
@register_filterset
|
||||
@@ -2397,7 +2397,7 @@ class ModuleBayFilterSet(ModularDeviceComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = ModuleBay
|
||||
fields = ('id', 'name', 'label', 'position', 'description')
|
||||
fields = ('id', 'name', 'label', 'position', 'enabled', 'description')
|
||||
|
||||
|
||||
@register_filterset
|
||||
@@ -2417,7 +2417,7 @@ class DeviceBayFilterSet(DeviceComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = DeviceBay
|
||||
fields = ('id', 'name', 'label', 'description')
|
||||
fields = ('id', 'name', 'label', 'enabled', 'description')
|
||||
|
||||
|
||||
@register_filterset
|
||||
|
||||
@@ -108,10 +108,13 @@ class RearPortBulkCreateForm(
|
||||
field_order = ('name', 'label', 'type', 'positions', 'mark_connected', 'description', 'tags')
|
||||
|
||||
|
||||
class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
|
||||
class ModuleBayBulkCreateForm(
|
||||
form_from_model(ModuleBay, ['enabled']),
|
||||
DeviceBulkAddComponentForm
|
||||
):
|
||||
model = ModuleBay
|
||||
field_order = ('name', 'label', 'position', 'description', 'tags')
|
||||
replication_fields = ('name', 'label', 'position')
|
||||
field_order = ('name', 'label', 'position', 'enabled', 'description', 'tags')
|
||||
replication_fields = ('name', 'label', 'position', 'enabled')
|
||||
position = ExpandableNameField(
|
||||
label=_('Position'),
|
||||
required=False,
|
||||
@@ -119,9 +122,12 @@ class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
|
||||
)
|
||||
|
||||
|
||||
class DeviceBayBulkCreateForm(DeviceBulkAddComponentForm):
|
||||
class DeviceBayBulkCreateForm(
|
||||
form_from_model(DeviceBay, ['enabled']),
|
||||
DeviceBulkAddComponentForm
|
||||
):
|
||||
model = DeviceBay
|
||||
field_order = ('name', 'label', 'description', 'tags')
|
||||
field_order = ('name', 'label', 'enabled', 'description', 'tags')
|
||||
|
||||
|
||||
class InventoryItemBulkCreateForm(
|
||||
|
||||
@@ -1245,6 +1245,11 @@ class ModuleBayTemplateBulkEditForm(ComponentTemplateBulkEditForm):
|
||||
label=_('Description'),
|
||||
required=False
|
||||
)
|
||||
enabled = forms.NullBooleanField(
|
||||
label=_('Enabled'),
|
||||
required=False,
|
||||
widget=BulkEditNullBooleanSelect,
|
||||
)
|
||||
|
||||
nullable_fields = ('label', 'position', 'description')
|
||||
|
||||
@@ -1263,6 +1268,11 @@ class DeviceBayTemplateBulkEditForm(ComponentTemplateBulkEditForm):
|
||||
label=_('Description'),
|
||||
required=False
|
||||
)
|
||||
enabled = forms.NullBooleanField(
|
||||
label=_('Enabled'),
|
||||
required=False,
|
||||
widget=BulkEditNullBooleanSelect,
|
||||
)
|
||||
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
@@ -1687,23 +1697,23 @@ class RearPortBulkEditForm(
|
||||
|
||||
|
||||
class ModuleBayBulkEditForm(
|
||||
form_from_model(ModuleBay, ['label', 'position', 'description']),
|
||||
form_from_model(ModuleBay, ['label', 'position', 'enabled', 'description']),
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
model = ModuleBay
|
||||
fieldsets = (
|
||||
FieldSet('label', 'position', 'description'),
|
||||
FieldSet('label', 'position', 'enabled', 'description'),
|
||||
)
|
||||
nullable_fields = ('label', 'position', 'description')
|
||||
|
||||
|
||||
class DeviceBayBulkEditForm(
|
||||
form_from_model(DeviceBay, ['label', 'description']),
|
||||
form_from_model(DeviceBay, ['label', 'enabled', 'description']),
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
model = DeviceBay
|
||||
fieldsets = (
|
||||
FieldSet('label', 'description'),
|
||||
FieldSet('label', 'enabled', 'description'),
|
||||
)
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
||||
@@ -1154,7 +1154,13 @@ class ModuleBayImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = ModuleBay
|
||||
fields = ('device', 'name', 'label', 'position', 'description', 'owner', 'tags')
|
||||
fields = ('device', 'name', 'label', 'position', 'enabled', 'description', 'owner', 'tags')
|
||||
|
||||
def clean_enabled(self):
|
||||
# Make sure enabled is True when it's not included in the uploaded data
|
||||
if 'enabled' not in self.data:
|
||||
return True
|
||||
return self.cleaned_data['enabled']
|
||||
|
||||
|
||||
class DeviceBayImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
@@ -1176,7 +1182,7 @@ class DeviceBayImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = DeviceBay
|
||||
fields = ('device', 'name', 'label', 'installed_device', 'description', 'owner', 'tags')
|
||||
fields = ('device', 'name', 'label', 'enabled', 'installed_device', 'description', 'owner', 'tags')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -1204,6 +1210,12 @@ class DeviceBayImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
else:
|
||||
self.fields['installed_device'].queryset = Device.objects.none()
|
||||
|
||||
def clean_enabled(self):
|
||||
# Make sure enabled is True when it's not included in the uploaded data
|
||||
if 'enabled' not in self.data:
|
||||
return True
|
||||
return self.cleaned_data['enabled']
|
||||
|
||||
|
||||
class InventoryItemImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
|
||||
@@ -1870,7 +1870,7 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||
model = ModuleBay
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'position', name=_('Attributes')),
|
||||
FieldSet('name', 'label', 'position', 'enabled', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet(
|
||||
'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
|
||||
@@ -1878,31 +1878,41 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||
),
|
||||
FieldSet('owner_group_id', 'owner_id', name=_('Ownership')),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
position = forms.CharField(
|
||||
label=_('Position'),
|
||||
required=False
|
||||
)
|
||||
enabled = forms.NullBooleanField(
|
||||
label=_('Enabled'),
|
||||
required=False,
|
||||
widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ModuleBayTemplateFilterForm(ModularDeviceComponentTemplateFilterForm):
|
||||
model = ModuleBayTemplate
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'position', name=_('Attributes')),
|
||||
FieldSet('name', 'label', 'position', 'enabled', name=_('Attributes')),
|
||||
FieldSet('device_type_id', 'module_type_id', name=_('Device')),
|
||||
)
|
||||
position = forms.CharField(
|
||||
label=_('Position'),
|
||||
required=False,
|
||||
)
|
||||
enabled = forms.NullBooleanField(
|
||||
label=_('Enabled'),
|
||||
required=False,
|
||||
widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
|
||||
)
|
||||
|
||||
|
||||
class DeviceBayFilterForm(DeviceComponentFilterForm):
|
||||
model = DeviceBay
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', name=_('Attributes')),
|
||||
FieldSet('name', 'label', 'enabled', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet(
|
||||
'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
|
||||
@@ -1910,6 +1920,11 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
|
||||
),
|
||||
FieldSet('owner_group_id', 'owner_id', name=_('Ownership')),
|
||||
)
|
||||
enabled = forms.NullBooleanField(
|
||||
label=_('Enabled'),
|
||||
required=False,
|
||||
widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
@@ -1917,9 +1932,14 @@ class DeviceBayTemplateFilterForm(DeviceComponentTemplateFilterForm):
|
||||
model = DeviceBayTemplate
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', name=_('Attributes')),
|
||||
FieldSet('name', 'label', 'enabled', name=_('Attributes')),
|
||||
FieldSet('device_type_id', name=_('Device')),
|
||||
)
|
||||
enabled = forms.NullBooleanField(
|
||||
label=_('Enabled'),
|
||||
required=False,
|
||||
widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
|
||||
)
|
||||
|
||||
|
||||
class InventoryItemFilterForm(DeviceComponentFilterForm):
|
||||
|
||||
@@ -777,7 +777,7 @@ class ModuleForm(ModuleCommonForm, PrimaryModelForm):
|
||||
'device_id': '$device',
|
||||
},
|
||||
context={
|
||||
'disabled': 'installed_module',
|
||||
'disabled': '_occupied',
|
||||
},
|
||||
)
|
||||
module_type = DynamicModelChoiceField(
|
||||
@@ -1233,26 +1233,26 @@ class ModuleBayTemplateForm(ModularComponentTemplateForm):
|
||||
FieldSet('device_type', name=_('Device Type')),
|
||||
FieldSet('module_type', name=_('Module Type')),
|
||||
),
|
||||
'name', 'label', 'position', 'description',
|
||||
'name', 'label', 'position', 'enabled', 'description',
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ModuleBayTemplate
|
||||
fields = [
|
||||
'device_type', 'module_type', 'name', 'label', 'position', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'position', 'enabled', 'description',
|
||||
]
|
||||
|
||||
|
||||
class DeviceBayTemplateForm(ComponentTemplateForm):
|
||||
fieldsets = (
|
||||
FieldSet('device_type', 'name', 'label', 'description'),
|
||||
FieldSet('device_type', 'name', 'label', 'enabled', 'description'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = DeviceBayTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'description',
|
||||
'device_type', 'name', 'label', 'enabled', 'description',
|
||||
]
|
||||
|
||||
|
||||
@@ -1698,25 +1698,25 @@ class RearPortForm(ModularDeviceComponentForm):
|
||||
|
||||
class ModuleBayForm(ModularDeviceComponentForm):
|
||||
fieldsets = (
|
||||
FieldSet('device', 'module', 'name', 'label', 'position', 'description', 'tags',),
|
||||
FieldSet('device', 'module', 'name', 'label', 'position', 'enabled', 'description', 'tags',),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ModuleBay
|
||||
fields = [
|
||||
'device', 'module', 'name', 'label', 'position', 'description', 'owner', 'tags',
|
||||
'device', 'module', 'name', 'label', 'position', 'enabled', 'description', 'owner', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class DeviceBayForm(DeviceComponentForm):
|
||||
fieldsets = (
|
||||
FieldSet('device', 'name', 'label', 'description', 'tags',),
|
||||
FieldSet('device', 'name', 'label', 'enabled', 'description', 'tags',),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = DeviceBay
|
||||
fields = [
|
||||
'device', 'name', 'label', 'description', 'owner', 'tags',
|
||||
'device', 'name', 'label', 'enabled', 'description', 'owner', 'tags',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -318,6 +318,7 @@ class DeviceFilter(
|
||||
|
||||
@strawberry_django.filter_type(models.DeviceBay, lookups=True)
|
||||
class DeviceBayFilter(ComponentModelFilterMixin, NetBoxModelFilter):
|
||||
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
|
||||
installed_device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
@@ -326,7 +327,7 @@ class DeviceBayFilter(ComponentModelFilterMixin, NetBoxModelFilter):
|
||||
|
||||
@strawberry_django.filter_type(models.DeviceBayTemplate, lookups=True)
|
||||
class DeviceBayTemplateFilter(ComponentTemplateFilterMixin, ChangeLoggedModelFilter):
|
||||
pass
|
||||
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
|
||||
|
||||
|
||||
@strawberry_django.filter_type(models.InventoryItemTemplate, lookups=True)
|
||||
@@ -742,11 +743,13 @@ class ModuleBayFilter(ModularComponentFilterMixin, NetBoxModelFilter):
|
||||
)
|
||||
parent_id: ID | None = strawberry_django.filter_field()
|
||||
position: StrFilterLookup[str] | None = strawberry_django.filter_field()
|
||||
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
|
||||
|
||||
|
||||
@strawberry_django.filter_type(models.ModuleBayTemplate, lookups=True)
|
||||
class ModuleBayTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter):
|
||||
position: StrFilterLookup[str] | None = strawberry_django.filter_field()
|
||||
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
|
||||
|
||||
|
||||
@strawberry_django.filter_type(models.ModuleTypeProfile, lookups=True)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('dcim', '0228_cable_bundle'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='devicebay',
|
||||
name='enabled',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicebaytemplate',
|
||||
name='enabled',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulebay',
|
||||
name='enabled',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulebaytemplate',
|
||||
name='enabled',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
@@ -722,6 +722,10 @@ class ModuleBayTemplate(ModularComponentTemplateModel):
|
||||
blank=True,
|
||||
help_text=_('Identifier to reference when renaming installed components')
|
||||
)
|
||||
enabled = models.BooleanField(
|
||||
verbose_name=_('enabled'),
|
||||
default=True,
|
||||
)
|
||||
|
||||
component_model = ModuleBay
|
||||
|
||||
@@ -734,6 +738,7 @@ class ModuleBayTemplate(ModularComponentTemplateModel):
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.resolve_label(kwargs.get('module')),
|
||||
position=self.position,
|
||||
enabled=self.enabled,
|
||||
**kwargs
|
||||
)
|
||||
instantiate.do_not_call_in_templates = True
|
||||
@@ -743,6 +748,7 @@ class ModuleBayTemplate(ModularComponentTemplateModel):
|
||||
'name': self.name,
|
||||
'label': self.label,
|
||||
'position': self.position,
|
||||
'enabled': self.enabled,
|
||||
'description': self.description,
|
||||
}
|
||||
|
||||
@@ -751,6 +757,11 @@ class DeviceBayTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a DeviceBay to be created for a new parent Device.
|
||||
"""
|
||||
enabled = models.BooleanField(
|
||||
verbose_name=_('enabled'),
|
||||
default=True,
|
||||
)
|
||||
|
||||
component_model = DeviceBay
|
||||
|
||||
class Meta(ComponentTemplateModel.Meta):
|
||||
@@ -761,7 +772,8 @@ class DeviceBayTemplate(ComponentTemplateModel):
|
||||
return self.component_model(
|
||||
device=device,
|
||||
name=self.name,
|
||||
label=self.label
|
||||
label=self.label,
|
||||
enabled=self.enabled,
|
||||
)
|
||||
instantiate.do_not_call_in_templates = True
|
||||
|
||||
@@ -777,6 +789,7 @@ class DeviceBayTemplate(ComponentTemplateModel):
|
||||
return {
|
||||
'name': self.name,
|
||||
'label': self.label,
|
||||
'enabled': self.enabled,
|
||||
'description': self.description,
|
||||
}
|
||||
|
||||
|
||||
@@ -1257,10 +1257,14 @@ class ModuleBay(ModularComponentModel, TrackingModelMixin, MPTTModel):
|
||||
blank=True,
|
||||
help_text=_('Identifier to reference when renaming installed components')
|
||||
)
|
||||
enabled = models.BooleanField(
|
||||
verbose_name=_('enabled'),
|
||||
default=True,
|
||||
)
|
||||
|
||||
objects = TreeManager()
|
||||
|
||||
clone_fields = ('device',)
|
||||
clone_fields = ('device', 'enabled')
|
||||
|
||||
class Meta(ModularComponentModel.Meta):
|
||||
# Empty tuple triggers Django migration detection for MPTT indexes
|
||||
@@ -1299,6 +1303,13 @@ class ModuleBay(ModularComponentModel, TrackingModelMixin, MPTTModel):
|
||||
self.parent = None
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def _occupied(self):
|
||||
"""
|
||||
Indicates whether the module bay is occupied by a module.
|
||||
"""
|
||||
return bool(not self.enabled or hasattr(self, 'installed_module'))
|
||||
|
||||
|
||||
class DeviceBay(ComponentModel, TrackingModelMixin):
|
||||
"""
|
||||
@@ -1311,8 +1322,12 @@ class DeviceBay(ComponentModel, TrackingModelMixin):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
enabled = models.BooleanField(
|
||||
verbose_name=_('enabled'),
|
||||
default=True,
|
||||
)
|
||||
|
||||
clone_fields = ('device',)
|
||||
clone_fields = ('device', 'enabled')
|
||||
|
||||
class Meta(ComponentModel.Meta):
|
||||
verbose_name = _('device bay')
|
||||
@@ -1327,6 +1342,16 @@ class DeviceBay(ComponentModel, TrackingModelMixin):
|
||||
device_type=self.device.device_type
|
||||
))
|
||||
|
||||
# Prevent installing a device into a disabled bay
|
||||
if self.installed_device and not self.enabled:
|
||||
current_installed_device_id = (
|
||||
DeviceBay.objects.filter(pk=self.pk).values_list('installed_device_id', flat=True).first()
|
||||
)
|
||||
if self.pk is None or current_installed_device_id != self.installed_device_id:
|
||||
raise ValidationError({
|
||||
'installed_device': _("Cannot install a device in a disabled device bay.")
|
||||
})
|
||||
|
||||
# Cannot install a device into itself, obviously
|
||||
if self.installed_device and getattr(self, 'device', None) == self.installed_device:
|
||||
raise ValidationError(_("Cannot install a device into itself."))
|
||||
@@ -1341,6 +1366,13 @@ class DeviceBay(ComponentModel, TrackingModelMixin):
|
||||
).format(bay=current_bay)
|
||||
})
|
||||
|
||||
@property
|
||||
def _occupied(self):
|
||||
"""
|
||||
Indicates whether the device bay is occupied by a child device.
|
||||
"""
|
||||
return bool(not self.enabled or self.installed_device_id)
|
||||
|
||||
|
||||
#
|
||||
# Inventory items
|
||||
|
||||
@@ -258,6 +258,14 @@ class Module(TrackingModelMixin, PrimaryModel, ConfigContextModel):
|
||||
)
|
||||
)
|
||||
|
||||
# Prevent module from being installed in a disabled bay
|
||||
if hasattr(self, 'module_bay') and self.module_bay and not self.module_bay.enabled:
|
||||
current_module_bay_id = Module.objects.filter(pk=self.pk).values_list('module_bay_id', flat=True).first()
|
||||
if self.pk is None or current_module_bay_id != self.module_bay_id:
|
||||
raise ValidationError({
|
||||
'module_bay': _("Cannot install a module in a disabled module bay.")
|
||||
})
|
||||
|
||||
# Check for recursion
|
||||
module = self
|
||||
module_bays = []
|
||||
|
||||
@@ -888,6 +888,9 @@ class DeviceBayTable(DeviceComponentTable):
|
||||
'args': [Accessor('device_id')],
|
||||
}
|
||||
)
|
||||
enabled = columns.BooleanColumn(
|
||||
verbose_name=_('Enabled'),
|
||||
)
|
||||
status = tables.TemplateColumn(
|
||||
verbose_name=_('Status'),
|
||||
template_code=DEVICEBAY_STATUS,
|
||||
@@ -925,12 +928,12 @@ class DeviceBayTable(DeviceComponentTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = models.DeviceBay
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'label', 'status', 'description', 'installed_device', 'installed_role',
|
||||
'installed_device_type', 'installed_description', 'installed_serial', 'installed_asset_tag', 'tags',
|
||||
'created', 'last_updated',
|
||||
'pk', 'id', 'name', 'device', 'label', 'enabled', 'status', 'description', 'installed_device',
|
||||
'installed_role', 'installed_device_type', 'installed_description', 'installed_serial',
|
||||
'installed_asset_tag', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'status', 'installed_device', 'description')
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'status', 'installed_device', 'description')
|
||||
|
||||
|
||||
class DeviceDeviceBayTable(DeviceBayTable):
|
||||
@@ -940,6 +943,9 @@ class DeviceDeviceBayTable(DeviceBayTable):
|
||||
'"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
||||
attrs={'td': {'class': 'text-nowrap'}}
|
||||
)
|
||||
enabled = columns.BooleanColumn(
|
||||
verbose_name=_('Enabled'),
|
||||
)
|
||||
actions = columns.ActionsColumn(
|
||||
extra_buttons=DEVICEBAY_BUTTONS
|
||||
)
|
||||
@@ -947,9 +953,9 @@ class DeviceDeviceBayTable(DeviceBayTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = models.DeviceBay
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'status', 'installed_device', 'description', 'tags', 'actions',
|
||||
'pk', 'id', 'name', 'label', 'enabled', 'status', 'installed_device', 'description', 'tags', 'actions',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'label', 'status', 'installed_device', 'description')
|
||||
default_columns = ('pk', 'name', 'label', 'enabled', 'status', 'installed_device', 'description')
|
||||
|
||||
|
||||
class ModuleBayTable(ModularDeviceComponentTable):
|
||||
@@ -960,6 +966,9 @@ class ModuleBayTable(ModularDeviceComponentTable):
|
||||
'args': [Accessor('device_id')],
|
||||
}
|
||||
)
|
||||
enabled = columns.BooleanColumn(
|
||||
verbose_name=_('Enabled'),
|
||||
)
|
||||
parent = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Parent'),
|
||||
@@ -988,11 +997,11 @@ class ModuleBayTable(ModularDeviceComponentTable):
|
||||
class Meta(ModularDeviceComponentTable.Meta):
|
||||
model = models.ModuleBay
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'parent', 'label', 'position', 'installed_module', 'module_status',
|
||||
'pk', 'id', 'name', 'device', 'enabled', 'parent', 'label', 'position', 'installed_module', 'module_status',
|
||||
'module_serial', 'module_asset_tag', 'description', 'tags',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'device', 'parent', 'label', 'installed_module', 'module_status', 'description',
|
||||
'pk', 'name', 'device', 'enabled', 'parent', 'label', 'installed_module', 'module_status', 'description',
|
||||
)
|
||||
|
||||
def render_parent_bay(self, value):
|
||||
@@ -1007,6 +1016,9 @@ class DeviceModuleBayTable(ModuleBayTable):
|
||||
verbose_name=_('Name'),
|
||||
linkify=True,
|
||||
)
|
||||
enabled = columns.BooleanColumn(
|
||||
verbose_name=_('Enabled'),
|
||||
)
|
||||
actions = columns.ActionsColumn(
|
||||
extra_buttons=MODULEBAY_BUTTONS
|
||||
)
|
||||
@@ -1014,10 +1026,10 @@ class DeviceModuleBayTable(ModuleBayTable):
|
||||
class Meta(ModuleBayTable.Meta):
|
||||
model = models.ModuleBay
|
||||
fields = (
|
||||
'pk', 'id', 'parent', 'name', 'label', 'position', 'installed_module', 'module_status', 'module_serial',
|
||||
'module_asset_tag', 'description', 'tags', 'actions',
|
||||
'pk', 'id', 'parent', 'name', 'label', 'enabled', 'position', 'installed_module', 'module_status',
|
||||
'module_serial', 'module_asset_tag', 'description', 'tags', 'actions',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'label', 'installed_module', 'module_status', 'description')
|
||||
default_columns = ('pk', 'name', 'label', 'enabled', 'installed_module', 'module_status', 'description')
|
||||
|
||||
|
||||
class InventoryItemTable(DeviceComponentTable):
|
||||
|
||||
@@ -289,24 +289,30 @@ class RearPortTemplateTable(ComponentTemplateTable):
|
||||
|
||||
|
||||
class ModuleBayTemplateTable(ComponentTemplateTable):
|
||||
enabled = columns.BooleanColumn(
|
||||
verbose_name=_('Enabled'),
|
||||
)
|
||||
actions = columns.ActionsColumn(
|
||||
actions=('edit', 'delete')
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = models.ModuleBayTemplate
|
||||
fields = ('pk', 'name', 'label', 'position', 'description', 'actions')
|
||||
fields = ('pk', 'name', 'label', 'position', 'enabled', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
|
||||
class DeviceBayTemplateTable(ComponentTemplateTable):
|
||||
enabled = columns.BooleanColumn(
|
||||
verbose_name=_('Enabled'),
|
||||
)
|
||||
actions = columns.ActionsColumn(
|
||||
actions=('edit', 'delete')
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = models.DeviceBayTemplate
|
||||
fields = ('pk', 'name', 'label', 'description', 'actions')
|
||||
fields = ('pk', 'name', 'label', 'enabled', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
|
||||
|
||||
@@ -565,7 +565,7 @@ DEVICEBAY_BUTTONS = """
|
||||
<a href="{% url 'dcim:devicebay_depopulate' pk=record.pk %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-danger btn-sm">
|
||||
<i class="mdi mdi-server-minus" aria-hidden="true" title="Remove device"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
{% elif record.enabled %}
|
||||
<a href="{% url 'dcim:devicebay_populate' pk=record.pk %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-success btn-sm">
|
||||
<i class="mdi mdi-server-plus" aria-hidden="true" title="Install device"></i>
|
||||
</a>
|
||||
@@ -579,7 +579,7 @@ MODULEBAY_BUTTONS = """
|
||||
<a href="{% url 'dcim:module_delete' pk=record.installed_module.pk %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-danger btn-sm">
|
||||
<i class="mdi mdi-server-minus" aria-hidden="true" title="Remove module"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
{% elif record.enabled %}
|
||||
<a href="{% url 'dcim:module_add' %}?device={{ record.device_id }}&module_bay={{ record.pk }}&manufacturer={{ object.device_type.manufacturer_id }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-success btn-sm">
|
||||
<i class="mdi mdi-server-plus" aria-hidden="true" title="Install module"></i>
|
||||
</a>
|
||||
|
||||
@@ -1226,7 +1226,7 @@ class RearPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
|
||||
class ModuleBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ModuleBayTemplate
|
||||
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
||||
brief_fields = ['description', 'display', 'enabled', 'id', 'name', 'url']
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
@@ -1243,9 +1243,9 @@ class ModuleBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
)
|
||||
|
||||
module_bay_templates = (
|
||||
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 1'),
|
||||
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 2'),
|
||||
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 3'),
|
||||
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 1', enabled=True),
|
||||
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 2', enabled=False),
|
||||
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 3', enabled=True),
|
||||
)
|
||||
ModuleBayTemplate.objects.bulk_create(module_bay_templates)
|
||||
|
||||
@@ -1253,6 +1253,7 @@ class ModuleBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
{
|
||||
'device_type': devicetype.pk,
|
||||
'name': 'Module Bay Template 4',
|
||||
'enabled': False,
|
||||
},
|
||||
{
|
||||
'device_type': devicetype.pk,
|
||||
@@ -1267,7 +1268,7 @@ class ModuleBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
|
||||
class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
model = DeviceBayTemplate
|
||||
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
||||
brief_fields = ['description', 'display', 'enabled', 'id', 'name', 'url']
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
@@ -1284,9 +1285,9 @@ class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
)
|
||||
|
||||
device_bay_templates = (
|
||||
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 1'),
|
||||
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 2'),
|
||||
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 3'),
|
||||
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 1', enabled=True),
|
||||
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 2', enabled=False),
|
||||
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 3', enabled=True),
|
||||
)
|
||||
DeviceBayTemplate.objects.bulk_create(device_bay_templates)
|
||||
|
||||
@@ -1294,6 +1295,7 @@ class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
{
|
||||
'device_type': devicetype.pk,
|
||||
'name': 'Device Bay Template 4',
|
||||
'enabled': False,
|
||||
},
|
||||
{
|
||||
'device_type': devicetype.pk,
|
||||
@@ -2594,7 +2596,7 @@ class RearPortTest(APIViewTestCases.APIViewTestCase):
|
||||
|
||||
class ModuleBayTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ModuleBay
|
||||
brief_fields = ['description', 'display', 'id', 'installed_module', 'name', 'url']
|
||||
brief_fields = ['_occupied', 'description', 'display', 'enabled', 'id', 'installed_module', 'name', 'url']
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
@@ -2610,9 +2612,9 @@ class ModuleBayTest(APIViewTestCases.APIViewTestCase):
|
||||
device = Device.objects.create(device_type=device_type, role=role, name='Device 1', site=site)
|
||||
|
||||
module_bays = (
|
||||
ModuleBay(device=device, name='Device Bay 1'),
|
||||
ModuleBay(device=device, name='Device Bay 2'),
|
||||
ModuleBay(device=device, name='Device Bay 3'),
|
||||
ModuleBay(device=device, name='Device Bay 1', enabled=True),
|
||||
ModuleBay(device=device, name='Device Bay 2', enabled=False),
|
||||
ModuleBay(device=device, name='Device Bay 3', enabled=True),
|
||||
)
|
||||
for module_bay in module_bays:
|
||||
module_bay.save()
|
||||
@@ -2621,6 +2623,7 @@ class ModuleBayTest(APIViewTestCases.APIViewTestCase):
|
||||
{
|
||||
'device': device.pk,
|
||||
'name': 'Device Bay 4',
|
||||
'enabled': False,
|
||||
},
|
||||
{
|
||||
'device': device.pk,
|
||||
@@ -2635,7 +2638,7 @@ class ModuleBayTest(APIViewTestCases.APIViewTestCase):
|
||||
|
||||
class DeviceBayTest(APIViewTestCases.APIViewTestCase):
|
||||
model = DeviceBay
|
||||
brief_fields = ['description', 'device', 'display', 'id', 'name', 'url']
|
||||
brief_fields = ['_occupied', 'description', 'device', 'display', 'enabled', 'id', 'name', 'url']
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
@@ -2672,9 +2675,9 @@ class DeviceBayTest(APIViewTestCases.APIViewTestCase):
|
||||
Device.objects.bulk_create(devices)
|
||||
|
||||
device_bays = (
|
||||
DeviceBay(device=devices[0], name='Device Bay 1'),
|
||||
DeviceBay(device=devices[0], name='Device Bay 2'),
|
||||
DeviceBay(device=devices[0], name='Device Bay 3'),
|
||||
DeviceBay(device=devices[0], name='Device Bay 1', enabled=True),
|
||||
DeviceBay(device=devices[0], name='Device Bay 2', enabled=False),
|
||||
DeviceBay(device=devices[0], name='Device Bay 3', enabled=True),
|
||||
)
|
||||
DeviceBay.objects.bulk_create(device_bays)
|
||||
|
||||
|
||||
@@ -2247,13 +2247,21 @@ class ModuleBayTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTests,
|
||||
ModuleBayTemplate.objects.bulk_create(
|
||||
(
|
||||
ModuleBayTemplate(
|
||||
device_type=device_types[0], name='Module Bay 1', description='foobar1'
|
||||
device_type=device_types[0], name='Module Bay 1', enabled=True, description='foobar1'
|
||||
),
|
||||
ModuleBayTemplate(
|
||||
device_type=device_types[1], name='Module Bay 2', description='foobar2', module_type=module_types[0]
|
||||
device_type=device_types[1],
|
||||
name='Module Bay 2',
|
||||
enabled=False,
|
||||
description='foobar2',
|
||||
module_type=module_types[0],
|
||||
),
|
||||
ModuleBayTemplate(
|
||||
device_type=device_types[2], name='Module Bay 3', description='foobar3', module_type=module_types[1]
|
||||
device_type=device_types[2],
|
||||
name='Module Bay 3',
|
||||
enabled=True,
|
||||
description='foobar3',
|
||||
module_type=module_types[1],
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -2262,6 +2270,12 @@ class ModuleBayTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTests,
|
||||
params = {'name': ['Module Bay 1', 'Module Bay 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_enabled(self):
|
||||
params = {'enabled': True}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'enabled': False}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_module_type(self):
|
||||
module_types = ModuleType.objects.all()[:2]
|
||||
params = {'module_type_id': [module_types[0].pk, module_types[1].pk]}
|
||||
@@ -2284,16 +2298,30 @@ class DeviceBayTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTests,
|
||||
)
|
||||
DeviceType.objects.bulk_create(device_types)
|
||||
|
||||
DeviceBayTemplate.objects.bulk_create((
|
||||
DeviceBayTemplate(device_type=device_types[0], name='Device Bay 1', description='foobar1'),
|
||||
DeviceBayTemplate(device_type=device_types[1], name='Device Bay 2', description='foobar2'),
|
||||
DeviceBayTemplate(device_type=device_types[2], name='Device Bay 3', description='foobar3'),
|
||||
))
|
||||
DeviceBayTemplate.objects.bulk_create(
|
||||
(
|
||||
DeviceBayTemplate(
|
||||
device_type=device_types[0], name='Device Bay 1', enabled=True, description='foobar1'
|
||||
),
|
||||
DeviceBayTemplate(
|
||||
device_type=device_types[1], name='Device Bay 2', enabled=False, description='foobar2'
|
||||
),
|
||||
DeviceBayTemplate(
|
||||
device_type=device_types[2], name='Device Bay 3', enabled=True, description='foobar3'
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Device Bay 1', 'Device Bay 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_enabled(self):
|
||||
params = {'enabled': True}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'enabled': False}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
|
||||
class InventoryItemTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTests, ChangeLoggedFilterSetTests):
|
||||
queryset = InventoryItemTemplate.objects.all()
|
||||
@@ -5778,11 +5806,11 @@ class ModuleBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
Device.objects.bulk_create(devices)
|
||||
|
||||
module_bays = (
|
||||
ModuleBay(device=devices[0], name='Module Bay 1', label='A', description='First'),
|
||||
ModuleBay(device=devices[1], name='Module Bay 2', label='B', description='Second'),
|
||||
ModuleBay(device=devices[2], name='Module Bay 3', label='C', description='Third'),
|
||||
ModuleBay(device=devices[2], name='Module Bay 4', label='D', description='Fourth'),
|
||||
ModuleBay(device=devices[2], name='Module Bay 5', label='E', description='Fifth'),
|
||||
ModuleBay(device=devices[0], name='Module Bay 1', label='A', enabled=True, description='First'),
|
||||
ModuleBay(device=devices[1], name='Module Bay 2', label='B', enabled=False, description='Second'),
|
||||
ModuleBay(device=devices[2], name='Module Bay 3', label='C', enabled=True, description='Third'),
|
||||
ModuleBay(device=devices[2], name='Module Bay 4', label='D', enabled=False, description='Fourth'),
|
||||
ModuleBay(device=devices[2], name='Module Bay 5', label='E', enabled=True, description='Fifth'),
|
||||
)
|
||||
for module_bay in module_bays:
|
||||
module_bay.save()
|
||||
@@ -5806,6 +5834,12 @@ class ModuleBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
params = {'label': ['A', 'B']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_enabled(self):
|
||||
params = {'enabled': True}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
params = {'enabled': False}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_description(self):
|
||||
params = {'description': ['First', 'Second']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
@@ -5965,6 +5999,7 @@ class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
device=devices[0],
|
||||
name='Device Bay 1',
|
||||
label='A',
|
||||
enabled=True,
|
||||
description='First',
|
||||
_site=devices[0].site,
|
||||
_location=devices[0].location,
|
||||
@@ -5974,6 +6009,7 @@ class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
device=devices[1],
|
||||
name='Device Bay 2',
|
||||
label='B',
|
||||
enabled=False,
|
||||
description='Second',
|
||||
_site=devices[1].site,
|
||||
_location=devices[1].location,
|
||||
@@ -5983,6 +6019,7 @@ class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
device=devices[2],
|
||||
name='Device Bay 3',
|
||||
label='C',
|
||||
enabled=True,
|
||||
description='Third',
|
||||
_site=devices[2].site,
|
||||
_location=devices[2].location,
|
||||
@@ -5999,6 +6036,12 @@ class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
params = {'label': ['A', 'B']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_enabled(self):
|
||||
params = {'enabled': True}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'enabled': False}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_description(self):
|
||||
params = {'description': ['First', 'Second']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
@@ -712,6 +712,112 @@ class DeviceTestCase(TestCase):
|
||||
).full_clean()
|
||||
|
||||
|
||||
class DeviceBayTestCase(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
||||
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
||||
|
||||
# Parent device type must support device bays (is_parent_device=True)
|
||||
parent_device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='Parent Device Type',
|
||||
slug='parent-device-type',
|
||||
subdevice_role=SubdeviceRoleChoices.ROLE_PARENT
|
||||
)
|
||||
# Child device type for installation
|
||||
child_device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='Child Device Type',
|
||||
slug='child-device-type',
|
||||
u_height=0,
|
||||
subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
|
||||
)
|
||||
device_role = DeviceRole.objects.create(name='Test Role 1', slug='test-role-1')
|
||||
|
||||
cls.parent_device = Device.objects.create(
|
||||
name='Parent Device',
|
||||
device_type=parent_device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
cls.child_device = Device.objects.create(
|
||||
name='Child Device',
|
||||
device_type=child_device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
cls.child_device_2 = Device.objects.create(
|
||||
name='Child Device 2',
|
||||
device_type=child_device_type,
|
||||
role=device_role,
|
||||
site=site
|
||||
)
|
||||
|
||||
def test_cannot_install_device_in_disabled_bay(self):
|
||||
"""
|
||||
Test that a device cannot be installed into a disabled DeviceBay.
|
||||
"""
|
||||
# Create a disabled device bay with a device being installed
|
||||
device_bay = DeviceBay(
|
||||
device=self.parent_device,
|
||||
name='Disabled Bay',
|
||||
enabled=False,
|
||||
installed_device=self.child_device
|
||||
)
|
||||
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
device_bay.clean()
|
||||
|
||||
self.assertIn('installed_device', cm.exception.message_dict)
|
||||
self.assertIn('disabled device bay', str(cm.exception.message_dict['installed_device']))
|
||||
|
||||
def test_can_disable_bay_with_existing_device(self):
|
||||
"""
|
||||
Test that disabling a bay that already has a device installed does NOT raise an error
|
||||
(same installed_device_id).
|
||||
"""
|
||||
# First, create an enabled device bay with a device installed
|
||||
device_bay = DeviceBay.objects.create(
|
||||
device=self.parent_device,
|
||||
name='Bay To Disable',
|
||||
enabled=True,
|
||||
installed_device=self.child_device
|
||||
)
|
||||
|
||||
# Now disable the bay while keeping the same installed device
|
||||
device_bay.enabled = False
|
||||
# This should NOT raise a ValidationError
|
||||
device_bay.clean()
|
||||
device_bay.save()
|
||||
|
||||
device_bay.refresh_from_db()
|
||||
self.assertFalse(device_bay.enabled)
|
||||
self.assertEqual(device_bay.installed_device, self.child_device)
|
||||
|
||||
def test_cannot_change_installed_device_in_disabled_bay(self):
|
||||
"""
|
||||
Test that changing the installed device in a disabled bay raises a ValidationError.
|
||||
"""
|
||||
# Create an enabled device bay with a device installed
|
||||
device_bay = DeviceBay.objects.create(
|
||||
device=self.parent_device,
|
||||
name='Bay With Device',
|
||||
enabled=True,
|
||||
installed_device=self.child_device
|
||||
)
|
||||
|
||||
# Disable the bay and try to change the installed device
|
||||
device_bay.enabled = False
|
||||
device_bay.installed_device = self.child_device_2
|
||||
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
device_bay.clean()
|
||||
|
||||
self.assertIn('installed_device', cm.exception.message_dict)
|
||||
|
||||
|
||||
class ModuleBayTestCase(TestCase):
|
||||
|
||||
@classmethod
|
||||
@@ -1011,6 +1117,25 @@ class ModuleBayTestCase(TestCase):
|
||||
self.assertEqual(RearPort.objects.filter(module=module).count(), 1)
|
||||
self.assertEqual(PortMapping.objects.filter(front_port__module=module).count(), 0)
|
||||
|
||||
def test_cannot_install_module_in_disabled_bay(self):
|
||||
"""
|
||||
Test that a Module cannot be installed into a disabled ModuleBay.
|
||||
"""
|
||||
device = Device.objects.first()
|
||||
manufacturer = Manufacturer.objects.first()
|
||||
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Test Module Type Disabled')
|
||||
|
||||
# Create a disabled module bay
|
||||
disabled_bay = ModuleBay.objects.create(device=device, name='Disabled Bay', enabled=False)
|
||||
|
||||
# Attempt to install a module into the disabled bay
|
||||
module = Module(device=device, module_bay=disabled_bay, module_type=module_type)
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
module.clean()
|
||||
|
||||
self.assertIn('module_bay', cm.exception.message_dict)
|
||||
self.assertIn('disabled module bay', str(cm.exception.message_dict['module_bay']))
|
||||
|
||||
|
||||
class CableTestCase(TestCase):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user