mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-18 21:16:27 -06:00
Add ModuleBay and ModuleBayTemplate models
This commit is contained in:
parent
5f9f0e3ed3
commit
e529d7fd3b
@ -20,6 +20,8 @@ __all__ = [
|
||||
'NestedInterfaceTemplateSerializer',
|
||||
'NestedInventoryItemSerializer',
|
||||
'NestedManufacturerSerializer',
|
||||
'NestedModuleBaySerializer',
|
||||
'NestedModuleBayTemplateSerializer',
|
||||
'NestedPlatformSerializer',
|
||||
'NestedPowerFeedSerializer',
|
||||
'NestedPowerOutletSerializer',
|
||||
@ -195,6 +197,14 @@ class NestedFrontPortTemplateSerializer(WritableNestedSerializer):
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedModuleBayTemplateSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebaytemplate-detail')
|
||||
|
||||
class Meta:
|
||||
model = models.ModuleBayTemplate
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedDeviceBayTemplateSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebaytemplate-detail')
|
||||
|
||||
@ -298,6 +308,15 @@ class NestedFrontPortSerializer(WritableNestedSerializer):
|
||||
fields = ['id', 'url', 'display', 'device', 'name', 'cable', '_occupied']
|
||||
|
||||
|
||||
class NestedModuleBaySerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebay-detail')
|
||||
# module = NestedModuleSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.DeviceBay
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedDeviceBaySerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
|
@ -409,6 +409,15 @@ class FrontPortTemplateSerializer(ValidatedModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class ModuleBayTemplateSerializer(ValidatedModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebaytemplate-detail')
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
|
||||
class Meta:
|
||||
model = ModuleBayTemplate
|
||||
fields = ['id', 'url', 'display', 'device_type', 'name', 'label', 'description', 'created', 'last_updated']
|
||||
|
||||
|
||||
class DeviceBayTemplateSerializer(ValidatedModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebaytemplate-detail')
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
@ -707,6 +716,19 @@ class FrontPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer):
|
||||
]
|
||||
|
||||
|
||||
class ModuleBaySerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebay-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
# installed_module = NestedModuleSerializer(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = ModuleBay
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'description', 'tags', 'custom_fields', 'created',
|
||||
'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class DeviceBaySerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
|
@ -28,6 +28,7 @@ router.register('power-outlet-templates', views.PowerOutletTemplateViewSet)
|
||||
router.register('interface-templates', views.InterfaceTemplateViewSet)
|
||||
router.register('front-port-templates', views.FrontPortTemplateViewSet)
|
||||
router.register('rear-port-templates', views.RearPortTemplateViewSet)
|
||||
router.register('module-bay-templates', views.ModuleBayTemplateViewSet)
|
||||
router.register('device-bay-templates', views.DeviceBayTemplateViewSet)
|
||||
|
||||
# Devices
|
||||
@ -43,6 +44,7 @@ router.register('power-outlets', views.PowerOutletViewSet)
|
||||
router.register('interfaces', views.InterfaceViewSet)
|
||||
router.register('front-ports', views.FrontPortViewSet)
|
||||
router.register('rear-ports', views.RearPortViewSet)
|
||||
router.register('module-bays', views.ModuleBayViewSet)
|
||||
router.register('device-bays', views.DeviceBayViewSet)
|
||||
router.register('inventory-items', views.InventoryItemViewSet)
|
||||
|
||||
|
@ -329,6 +329,12 @@ class RearPortTemplateViewSet(ModelViewSet):
|
||||
filterset_class = filtersets.RearPortTemplateFilterSet
|
||||
|
||||
|
||||
class ModuleBayTemplateViewSet(ModelViewSet):
|
||||
queryset = ModuleBayTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||
serializer_class = serializers.ModuleBayTemplateSerializer
|
||||
filterset_class = filtersets.ModuleBayTemplateFilterSet
|
||||
|
||||
|
||||
class DeviceBayTemplateViewSet(ModelViewSet):
|
||||
queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||
serializer_class = serializers.DeviceBayTemplateSerializer
|
||||
@ -569,15 +575,22 @@ class RearPortViewSet(PassThroughPortMixin, ModelViewSet):
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class ModuleBayViewSet(ModelViewSet):
|
||||
queryset = ModuleBay.objects.prefetch_related('tags')
|
||||
serializer_class = serializers.ModuleBaySerializer
|
||||
filterset_class = filtersets.ModuleBayFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class DeviceBayViewSet(ModelViewSet):
|
||||
queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags')
|
||||
queryset = DeviceBay.objects.prefetch_related('installed_device', 'tags')
|
||||
serializer_class = serializers.DeviceBaySerializer
|
||||
filterset_class = filtersets.DeviceBayFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class InventoryItemViewSet(ModelViewSet):
|
||||
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
|
||||
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer', 'tags')
|
||||
serializer_class = serializers.InventoryItemSerializer
|
||||
filterset_class = filtersets.InventoryItemFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
@ -41,6 +41,8 @@ __all__ = (
|
||||
'InventoryItemFilterSet',
|
||||
'LocationFilterSet',
|
||||
'ManufacturerFilterSet',
|
||||
'ModuleBayFilterSet',
|
||||
'ModuleBayTemplateFilterSet',
|
||||
'PathEndpointFilterSet',
|
||||
'PlatformFilterSet',
|
||||
'PowerConnectionFilterSet',
|
||||
@ -447,6 +449,10 @@ class DeviceTypeFilterSet(PrimaryModelFilterSet):
|
||||
method='_pass_through_ports',
|
||||
label='Has pass-through ports',
|
||||
)
|
||||
module_bays = django_filters.BooleanFilter(
|
||||
method='_module_bays',
|
||||
label='Has module bays',
|
||||
)
|
||||
device_bays = django_filters.BooleanFilter(
|
||||
method='_device_bays',
|
||||
label='Has device bays',
|
||||
@ -490,6 +496,9 @@ class DeviceTypeFilterSet(PrimaryModelFilterSet):
|
||||
rearporttemplates__isnull=value
|
||||
)
|
||||
|
||||
def _module_bays(self, queryset, name, value):
|
||||
return queryset.exclude(modulebaytemplates__isnull=value)
|
||||
|
||||
def _device_bays(self, queryset, name, value):
|
||||
return queryset.exclude(devicebaytemplates__isnull=value)
|
||||
|
||||
@ -576,6 +585,13 @@ class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentF
|
||||
fields = ['id', 'name', 'type', 'color', 'positions']
|
||||
|
||||
|
||||
class ModuleBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = ModuleBayTemplate
|
||||
fields = ['id', 'name']
|
||||
|
||||
|
||||
class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
@ -760,6 +776,10 @@ class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContex
|
||||
method='_pass_through_ports',
|
||||
label='Has pass-through ports',
|
||||
)
|
||||
module_bays = django_filters.BooleanFilter(
|
||||
method='_module_bays',
|
||||
label='Has module bays',
|
||||
)
|
||||
device_bays = django_filters.BooleanFilter(
|
||||
method='_device_bays',
|
||||
label='Has device bays',
|
||||
@ -811,6 +831,9 @@ class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContex
|
||||
rearports__isnull=value
|
||||
)
|
||||
|
||||
def _module_bays(self, queryset, name, value):
|
||||
return queryset.exclude(modulebays__isnull=value)
|
||||
|
||||
def _device_bays(self, queryset, name, value):
|
||||
return queryset.exclude(devicebays__isnull=value)
|
||||
|
||||
@ -1104,6 +1127,13 @@ class RearPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTe
|
||||
fields = ['id', 'name', 'label', 'type', 'color', 'positions', 'description']
|
||||
|
||||
|
||||
class ModuleBayFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = ModuleBay
|
||||
fields = ['id', 'name', 'label', 'description']
|
||||
|
||||
|
||||
class DeviceBayFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
|
@ -13,6 +13,7 @@ __all__ = (
|
||||
# 'FrontPortBulkCreateForm',
|
||||
'InterfaceBulkCreateForm',
|
||||
'InventoryItemBulkCreateForm',
|
||||
'ModuleBayBulkCreateForm',
|
||||
'PowerOutletBulkCreateForm',
|
||||
'PowerPortBulkCreateForm',
|
||||
'RearPortBulkCreateForm',
|
||||
@ -95,6 +96,11 @@ class RearPortBulkCreateForm(
|
||||
field_order = ('name_pattern', 'label_pattern', 'type', 'positions', 'mark_connected', 'description', 'tags')
|
||||
|
||||
|
||||
class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
|
||||
model = ModuleBay
|
||||
field_order = ('name_pattern', 'label_pattern', 'description', 'tags')
|
||||
|
||||
|
||||
class DeviceBayBulkCreateForm(DeviceBulkAddComponentForm):
|
||||
model = DeviceBay
|
||||
field_order = ('name_pattern', 'label_pattern', 'description', 'tags')
|
||||
|
@ -7,7 +7,6 @@ from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import *
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||
from ipam.constants import BGP_ASN_MIN, BGP_ASN_MAX
|
||||
from ipam.models import VLAN, ASN
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
@ -33,6 +32,8 @@ __all__ = (
|
||||
'InventoryItemBulkEditForm',
|
||||
'LocationBulkEditForm',
|
||||
'ManufacturerBulkEditForm',
|
||||
'ModuleBayBulkEditForm',
|
||||
'ModuleBayTemplateBulkEditForm',
|
||||
'PlatformBulkEditForm',
|
||||
'PowerFeedBulkEditForm',
|
||||
'PowerOutletBulkEditForm',
|
||||
@ -823,6 +824,23 @@ class RearPortTemplateBulkEditForm(BulkEditForm):
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class ModuleBayTemplateBulkEditForm(BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ModuleBayTemplate.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
label = forms.CharField(
|
||||
max_length=64,
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
||||
class DeviceBayTemplateBulkEditForm(BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=DeviceBayTemplate.objects.all(),
|
||||
@ -1076,6 +1094,20 @@ class RearPortBulkEditForm(
|
||||
nullable_fields = ['label', 'description']
|
||||
|
||||
|
||||
class ModuleBayBulkEditForm(
|
||||
form_from_model(DeviceBay, ['label', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm
|
||||
):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ModuleBay.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'description']
|
||||
|
||||
|
||||
class DeviceBayBulkEditForm(
|
||||
form_from_model(DeviceBay, ['label', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
|
@ -26,6 +26,7 @@ __all__ = (
|
||||
'InventoryItemCSVForm',
|
||||
'LocationCSVForm',
|
||||
'ManufacturerCSVForm',
|
||||
'ModuleBayCSVForm',
|
||||
'PlatformCSVForm',
|
||||
'PowerFeedCSVForm',
|
||||
'PowerOutletCSVForm',
|
||||
@ -678,6 +679,17 @@ class RearPortCSVForm(CustomFieldModelCSVForm):
|
||||
}
|
||||
|
||||
|
||||
class ModuleBayCSVForm(CustomFieldModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ModuleBay
|
||||
fields = ('device', 'name', 'label', 'description')
|
||||
|
||||
|
||||
class DeviceBayCSVForm(CustomFieldModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
|
@ -29,6 +29,7 @@ __all__ = (
|
||||
'InventoryItemFilterForm',
|
||||
'LocationFilterForm',
|
||||
'ManufacturerFilterForm',
|
||||
'ModuleBayFilterForm',
|
||||
'PlatformFilterForm',
|
||||
'PowerConnectionFilterForm',
|
||||
'PowerFeedFilterForm',
|
||||
@ -970,6 +971,16 @@ class RearPortFilterForm(DeviceComponentFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||
model = ModuleBay
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class DeviceBayFilterForm(DeviceComponentFilterForm):
|
||||
model = DeviceBay
|
||||
field_groups = [
|
||||
|
@ -39,6 +39,8 @@ __all__ = (
|
||||
'InventoryItemForm',
|
||||
'LocationForm',
|
||||
'ManufacturerForm',
|
||||
'ModuleBayForm',
|
||||
'ModuleBayTemplateForm',
|
||||
'PlatformForm',
|
||||
'PopulateDeviceBayForm',
|
||||
'PowerFeedForm',
|
||||
@ -984,6 +986,17 @@ class RearPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ModuleBayTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = ModuleBayTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'description',
|
||||
]
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = DeviceBayTemplate
|
||||
@ -1222,6 +1235,22 @@ class RearPortForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ModuleBayForm(CustomFieldModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ModuleBay
|
||||
fields = [
|
||||
'device', 'name', 'label', 'description', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'device': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
class DeviceBayForm(CustomFieldModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
|
@ -25,6 +25,8 @@ __all__ = (
|
||||
'InterfaceCreateForm',
|
||||
'InterfaceTemplateCreateForm',
|
||||
'InventoryItemCreateForm',
|
||||
'ModuleBayCreateForm',
|
||||
'ModuleBayTemplateCreateForm',
|
||||
'PowerOutletCreateForm',
|
||||
'PowerOutletTemplateCreateForm',
|
||||
'PowerPortCreateForm',
|
||||
@ -327,6 +329,10 @@ class RearPortTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
)
|
||||
|
||||
|
||||
class ModuleBayTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'description')
|
||||
|
||||
|
||||
class DeviceBayTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'description')
|
||||
|
||||
@ -619,6 +625,11 @@ class RearPortCreateForm(ComponentCreateForm):
|
||||
)
|
||||
|
||||
|
||||
class ModuleBayCreateForm(ComponentCreateForm):
|
||||
model = ModuleBay
|
||||
field_order = ('device', 'name_pattern', 'label_pattern', 'description', 'tags')
|
||||
|
||||
|
||||
class DeviceBayCreateForm(ComponentCreateForm):
|
||||
model = DeviceBay
|
||||
field_order = ('device', 'name_pattern', 'label_pattern', 'description', 'tags')
|
||||
|
@ -11,6 +11,7 @@ __all__ = (
|
||||
'DeviceTypeImportForm',
|
||||
'FrontPortTemplateImportForm',
|
||||
'InterfaceTemplateImportForm',
|
||||
'ModuleBayTemplateImportForm',
|
||||
'PowerOutletTemplateImportForm',
|
||||
'PowerPortTemplateImportForm',
|
||||
'RearPortTemplateImportForm',
|
||||
@ -139,6 +140,15 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm):
|
||||
]
|
||||
|
||||
|
||||
class ModuleBayTemplateImportForm(ComponentTemplateImportForm):
|
||||
|
||||
class Meta:
|
||||
model = ModuleBayTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'description',
|
||||
]
|
||||
|
||||
|
||||
class DeviceBayTemplateImportForm(ComponentTemplateImportForm):
|
||||
|
||||
class Meta:
|
||||
|
@ -56,6 +56,12 @@ class DCIMQuery(graphene.ObjectType):
|
||||
manufacturer = ObjectField(ManufacturerType)
|
||||
manufacturer_list = ObjectListField(ManufacturerType)
|
||||
|
||||
module_bay = ObjectField(ModuleBayType)
|
||||
module_bay_list = ObjectListField(ModuleBayType)
|
||||
|
||||
module_bay_template = ObjectField(ModuleBayTemplateType)
|
||||
module_bay_template_list = ObjectListField(ModuleBayTemplateType)
|
||||
|
||||
platform = ObjectField(PlatformType)
|
||||
platform_list = ObjectListField(PlatformType)
|
||||
|
||||
|
@ -27,6 +27,8 @@ __all__ = (
|
||||
'InventoryItemType',
|
||||
'LocationType',
|
||||
'ManufacturerType',
|
||||
'ModuleBayType',
|
||||
'ModuleBayTemplateType',
|
||||
'PlatformType',
|
||||
'PowerFeedType',
|
||||
'PowerOutletType',
|
||||
@ -254,6 +256,22 @@ class ManufacturerType(OrganizationalObjectType):
|
||||
filterset_class = filtersets.ManufacturerFilterSet
|
||||
|
||||
|
||||
class ModuleBayType(ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ModuleBay
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ModuleBayFilterSet
|
||||
|
||||
|
||||
class ModuleBayTemplateType(ComponentTemplateObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ModuleBayTemplate
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ModuleBayTemplateFilterSet
|
||||
|
||||
|
||||
class PlatformType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
|
53
netbox/dcim/migrations/0145_modules.py
Normal file
53
netbox/dcim/migrations/0145_modules.py
Normal file
@ -0,0 +1,53 @@
|
||||
import django.core.serializers.json
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import taggit.managers
|
||||
import utilities.fields
|
||||
import utilities.ordering
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0066_customfield_name_validation'),
|
||||
('dcim', '0144_site_remove_deprecated_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ModuleBayTemplate',
|
||||
fields=[
|
||||
('created', models.DateField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64)),
|
||||
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
|
||||
('label', models.CharField(blank=True, max_length=64)),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modulebaytemplates', to='dcim.devicetype')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('device_type', '_name'),
|
||||
'unique_together': {('device_type', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ModuleBay',
|
||||
fields=[
|
||||
('created', models.DateField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64)),
|
||||
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
|
||||
('label', models.CharField(blank=True, max_length=64)),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modulebays', to='dcim.device')),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('device', '_name'),
|
||||
'unique_together': {('device', 'name')},
|
||||
},
|
||||
),
|
||||
]
|
@ -27,6 +27,8 @@ __all__ = (
|
||||
'InventoryItem',
|
||||
'Location',
|
||||
'Manufacturer',
|
||||
'ModuleBay',
|
||||
'ModuleBayTemplate',
|
||||
'Platform',
|
||||
'PowerFeed',
|
||||
'PowerOutlet',
|
||||
|
@ -9,7 +9,7 @@ from netbox.models import ChangeLoggedModel
|
||||
from utilities.fields import ColorField, NaturalOrderingField
|
||||
from utilities.ordering import naturalize_interface
|
||||
from .device_components import (
|
||||
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, PowerOutlet, PowerPort, RearPort,
|
||||
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, ModuleBay, PowerOutlet, PowerPort, RearPort,
|
||||
)
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ __all__ = (
|
||||
'DeviceBayTemplate',
|
||||
'FrontPortTemplate',
|
||||
'InterfaceTemplate',
|
||||
'ModuleBayTemplate',
|
||||
'PowerOutletTemplate',
|
||||
'PowerPortTemplate',
|
||||
'RearPortTemplate',
|
||||
@ -360,6 +361,23 @@ class RearPortTemplate(ComponentTemplateModel):
|
||||
)
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class ModuleBayTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a ModuleBay to be created for a new parent Device.
|
||||
"""
|
||||
class Meta:
|
||||
ordering = ('device_type', '_name')
|
||||
unique_together = ('device_type', 'name')
|
||||
|
||||
def instantiate(self, device):
|
||||
return ModuleBay(
|
||||
device=device,
|
||||
name=self.name,
|
||||
label=self.label
|
||||
)
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class DeviceBayTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
|
@ -30,6 +30,7 @@ __all__ = (
|
||||
'FrontPort',
|
||||
'Interface',
|
||||
'InventoryItem',
|
||||
'ModuleBay',
|
||||
'PathEndpoint',
|
||||
'PowerOutlet',
|
||||
'PowerPort',
|
||||
@ -229,7 +230,7 @@ class PathEndpoint(models.Model):
|
||||
|
||||
|
||||
#
|
||||
# Console ports
|
||||
# Console components
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
@ -260,10 +261,6 @@ class ConsolePort(ComponentModel, LinkTermination, PathEndpoint):
|
||||
return reverse('dcim:consoleport', kwargs={'pk': self.pk})
|
||||
|
||||
|
||||
#
|
||||
# Console server ports
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class ConsoleServerPort(ComponentModel, LinkTermination, PathEndpoint):
|
||||
"""
|
||||
@ -293,7 +290,7 @@ class ConsoleServerPort(ComponentModel, LinkTermination, PathEndpoint):
|
||||
|
||||
|
||||
#
|
||||
# Power ports
|
||||
# Power components
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
@ -389,10 +386,6 @@ class PowerPort(ComponentModel, LinkTermination, PathEndpoint):
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Power outlets
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class PowerOutlet(ComponentModel, LinkTermination, PathEndpoint):
|
||||
"""
|
||||
@ -866,9 +859,24 @@ class RearPort(ComponentModel, LinkTermination):
|
||||
|
||||
|
||||
#
|
||||
# Device bays
|
||||
# Bays
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class ModuleBay(ComponentModel):
|
||||
"""
|
||||
An empty space within a Device which can house a child device
|
||||
"""
|
||||
clone_fields = ['device']
|
||||
|
||||
class Meta:
|
||||
ordering = ('device', '_name')
|
||||
unique_together = ('device', 'name')
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:modulebay', kwargs={'pk': self.pk})
|
||||
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class DeviceBay(ComponentModel):
|
||||
"""
|
||||
|
@ -786,6 +786,9 @@ class Device(PrimaryModel, ConfigContextModel):
|
||||
FrontPort.objects.bulk_create(
|
||||
[x.instantiate(self) for x in self.device_type.frontporttemplates.all()]
|
||||
)
|
||||
ModuleBay.objects.bulk_create(
|
||||
[x.instantiate(self) for x in self.device_type.modulebaytemplates.all()]
|
||||
)
|
||||
DeviceBay.objects.bulk_create(
|
||||
[x.instantiate(self) for x in self.device_type.devicebaytemplates.all()]
|
||||
)
|
||||
|
@ -2,8 +2,8 @@ import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from dcim.models import (
|
||||
ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, FrontPort, Interface, InventoryItem, Platform,
|
||||
PowerOutlet, PowerPort, RearPort, VirtualChassis,
|
||||
ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, FrontPort, Interface, InventoryItem, ModuleBay,
|
||||
Platform, PowerOutlet, PowerPort, RearPort, VirtualChassis,
|
||||
)
|
||||
from tenancy.tables import TenantColumn
|
||||
from utilities.tables import (
|
||||
@ -25,6 +25,7 @@ __all__ = (
|
||||
'DeviceImportTable',
|
||||
'DeviceInterfaceTable',
|
||||
'DeviceInventoryItemTable',
|
||||
'DeviceModuleBayTable',
|
||||
'DevicePowerPortTable',
|
||||
'DevicePowerOutletTable',
|
||||
'DeviceRearPortTable',
|
||||
@ -33,6 +34,7 @@ __all__ = (
|
||||
'FrontPortTable',
|
||||
'InterfaceTable',
|
||||
'InventoryItemTable',
|
||||
'ModuleBayTable',
|
||||
'PlatformTable',
|
||||
'PowerOutletTable',
|
||||
'PowerPortTable',
|
||||
@ -716,6 +718,35 @@ class DeviceDeviceBayTable(DeviceBayTable):
|
||||
)
|
||||
|
||||
|
||||
class ModuleBayTable(DeviceComponentTable):
|
||||
device = tables.Column(
|
||||
linkify={
|
||||
'viewname': 'dcim:device_modulebays',
|
||||
'args': [Accessor('device_id')],
|
||||
}
|
||||
)
|
||||
tags = TagColumn(
|
||||
url_name='dcim:modulebay_list'
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ModuleBay
|
||||
fields = ('pk', 'id', 'name', 'device', 'label', 'description', 'tags')
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'description')
|
||||
|
||||
|
||||
class DeviceModuleBayTable(ModuleBayTable):
|
||||
actions = ButtonsColumn(
|
||||
model=ModuleBay,
|
||||
buttons=('edit', 'delete')
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ModuleBay
|
||||
fields = ('pk', 'id', 'name', 'label', 'description', 'tags', 'actions')
|
||||
default_columns = ('pk', 'name', 'label', 'description', 'actions')
|
||||
|
||||
|
||||
class InventoryItemTable(DeviceComponentTable):
|
||||
device = tables.Column(
|
||||
linkify={
|
||||
|
@ -2,7 +2,7 @@ import django_tables2 as tables
|
||||
|
||||
from dcim.models import (
|
||||
ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, DeviceType, FrontPortTemplate, InterfaceTemplate,
|
||||
Manufacturer, PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
|
||||
Manufacturer, ModuleBayTemplate, PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
|
||||
)
|
||||
from utilities.tables import (
|
||||
BaseTable, BooleanColumn, ButtonsColumn, ColorColumn, LinkedCountColumn, MarkdownColumn, TagColumn, ToggleColumn,
|
||||
@ -16,6 +16,7 @@ __all__ = (
|
||||
'FrontPortTemplateTable',
|
||||
'InterfaceTemplateTable',
|
||||
'ManufacturerTable',
|
||||
'ModuleBayTemplateTable',
|
||||
'PowerOutletTemplateTable',
|
||||
'PowerPortTemplateTable',
|
||||
'RearPortTemplateTable',
|
||||
@ -207,6 +208,19 @@ class RearPortTemplateTable(ComponentTemplateTable):
|
||||
empty_text = "None"
|
||||
|
||||
|
||||
class ModuleBayTemplateTable(ComponentTemplateTable):
|
||||
actions = ButtonsColumn(
|
||||
model=ModuleBayTemplate,
|
||||
buttons=('edit', 'delete'),
|
||||
return_url_extra='%23tab_modulebays'
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = ModuleBayTemplate
|
||||
fields = ('pk', 'name', 'label', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
|
||||
class DeviceBayTemplateTable(ComponentTemplateTable):
|
||||
actions = ButtonsColumn(
|
||||
model=DeviceBayTemplate,
|
||||
|
@ -778,6 +778,46 @@ class RearPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
]
|
||||
|
||||
|
||||
class ModuleBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ModuleBayTemplate
|
||||
brief_fields = ['display', 'id', 'name', 'url']
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
||||
devicetype = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='Device Type 1',
|
||||
slug='device-type-1',
|
||||
subdevice_role=SubdeviceRoleChoices.ROLE_PARENT
|
||||
)
|
||||
|
||||
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.objects.bulk_create(module_bay_templates)
|
||||
|
||||
cls.create_data = [
|
||||
{
|
||||
'device_type': devicetype.pk,
|
||||
'name': 'Module Bay Template 4',
|
||||
},
|
||||
{
|
||||
'device_type': devicetype.pk,
|
||||
'name': 'Module Bay Template 5',
|
||||
},
|
||||
{
|
||||
'device_type': devicetype.pk,
|
||||
'name': 'Module Bay Template 6',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
model = DeviceBayTemplate
|
||||
brief_fields = ['display', 'id', 'name', 'url']
|
||||
@ -1369,6 +1409,45 @@ class RearPortTest(APIViewTestCases.APIViewTestCase):
|
||||
]
|
||||
|
||||
|
||||
class ModuleBayTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ModuleBay
|
||||
brief_fields = ['display', 'id', 'name', 'url']
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
||||
site = Site.objects.create(name='Site 1', slug='site-1')
|
||||
devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
|
||||
|
||||
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||
device = Device.objects.create(device_type=device_type, device_role=devicerole, name='Device 1', site=site)
|
||||
|
||||
device_bays = (
|
||||
ModuleBay(device=device, name='Device Bay 1'),
|
||||
ModuleBay(device=device, name='Device Bay 2'),
|
||||
ModuleBay(device=device, name='Device Bay 3'),
|
||||
)
|
||||
ModuleBay.objects.bulk_create(device_bays)
|
||||
|
||||
cls.create_data = [
|
||||
{
|
||||
'device': device.pk,
|
||||
'name': 'Device Bay 4',
|
||||
},
|
||||
{
|
||||
'device': device.pk,
|
||||
'name': 'Device Bay 5',
|
||||
},
|
||||
{
|
||||
'device': device.pk,
|
||||
'name': 'Device Bay 6',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class DeviceBayTest(APIViewTestCases.APIViewTestCase):
|
||||
model = DeviceBay
|
||||
brief_fields = ['device', 'display', 'id', 'name', 'url']
|
||||
|
@ -678,6 +678,10 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
FrontPortTemplate(device_type=device_types[0], name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0]),
|
||||
FrontPortTemplate(device_type=device_types[1], name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[1]),
|
||||
))
|
||||
ModuleBayTemplate.objects.bulk_create((
|
||||
ModuleBayTemplate(device_type=device_types[0], name='Module Bay 1'),
|
||||
ModuleBayTemplate(device_type=device_types[1], name='Module Bay 2'),
|
||||
))
|
||||
DeviceBayTemplate.objects.bulk_create((
|
||||
DeviceBayTemplate(device_type=device_types[0], name='Device Bay 1'),
|
||||
DeviceBayTemplate(device_type=device_types[1], name='Device Bay 2'),
|
||||
@ -762,6 +766,12 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
params = {'device_bays': 'false'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_module_bays(self):
|
||||
params = {'module_bays': 'true'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'module_bays': 'false'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
|
||||
class ConsolePortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = ConsolePortTemplate.objects.all()
|
||||
@ -1036,6 +1046,38 @@ class RearPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ModuleBayTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = ModuleBayTemplate.objects.all()
|
||||
filterset = ModuleBayTemplateFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||
|
||||
device_types = (
|
||||
DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'),
|
||||
DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'),
|
||||
DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'),
|
||||
)
|
||||
DeviceType.objects.bulk_create(device_types)
|
||||
|
||||
ModuleBayTemplate.objects.bulk_create((
|
||||
ModuleBayTemplate(device_type=device_types[0], name='Module Bay 1'),
|
||||
ModuleBayTemplate(device_type=device_types[1], name='Module Bay 2'),
|
||||
ModuleBayTemplate(device_type=device_types[2], name='Module Bay 3'),
|
||||
))
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Module Bay 1', 'Module Bay 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_devicetype_id(self):
|
||||
device_types = DeviceType.objects.all()[:2]
|
||||
params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class DeviceBayTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = DeviceBayTemplate.objects.all()
|
||||
filterset = DeviceBayTemplateFilterSet
|
||||
@ -1280,6 +1322,10 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
FrontPort(device=devices[0], name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0]),
|
||||
FrontPort(device=devices[1], name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[1]),
|
||||
))
|
||||
ModuleBay.objects.bulk_create((
|
||||
ModuleBay(device=devices[0], name='Module Bay 1'),
|
||||
ModuleBay(device=devices[1], name='Module Bay 2'),
|
||||
))
|
||||
DeviceBay.objects.bulk_create((
|
||||
DeviceBay(device=devices[0], name='Device Bay 1'),
|
||||
DeviceBay(device=devices[1], name='Device Bay 2'),
|
||||
@ -1465,6 +1511,12 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
params = {'pass_through_ports': 'false'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_module_bays(self):
|
||||
params = {'module_bays': 'true'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'module_bays': 'false'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_device_bays(self):
|
||||
params = {'device_bays': 'true'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
@ -2508,6 +2560,109 @@ class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ModuleBayTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = ModuleBay.objects.all()
|
||||
filterset = ModuleBayFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
regions = (
|
||||
Region(name='Region 1', slug='region-1'),
|
||||
Region(name='Region 2', slug='region-2'),
|
||||
Region(name='Region 3', slug='region-3'),
|
||||
)
|
||||
for region in regions:
|
||||
region.save()
|
||||
|
||||
groups = (
|
||||
SiteGroup(name='Site Group 1', slug='site-group-1'),
|
||||
SiteGroup(name='Site Group 2', slug='site-group-2'),
|
||||
SiteGroup(name='Site Group 3', slug='site-group-3'),
|
||||
)
|
||||
for group in groups:
|
||||
group.save()
|
||||
|
||||
sites = Site.objects.bulk_create((
|
||||
Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
|
||||
Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
|
||||
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
|
||||
Site(name='Site X', slug='site-x'),
|
||||
))
|
||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
|
||||
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
|
||||
|
||||
locations = (
|
||||
Location(name='Location 1', slug='location-1', site=sites[0]),
|
||||
Location(name='Location 2', slug='location-2', site=sites[1]),
|
||||
Location(name='Location 3', slug='location-3', site=sites[2]),
|
||||
)
|
||||
for location in locations:
|
||||
location.save()
|
||||
|
||||
devices = (
|
||||
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0]),
|
||||
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1]),
|
||||
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2]),
|
||||
)
|
||||
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.objects.bulk_create(module_bays)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Module Bay 1', 'Module Bay 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_label(self):
|
||||
params = {'label': ['A', 'B']}
|
||||
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)
|
||||
|
||||
def test_region(self):
|
||||
regions = Region.objects.all()[:2]
|
||||
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'region': [regions[0].slug, regions[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_site_group(self):
|
||||
site_groups = SiteGroup.objects.all()[:2]
|
||||
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_site(self):
|
||||
sites = Site.objects.all()[:2]
|
||||
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_location(self):
|
||||
locations = Location.objects.all()[:2]
|
||||
params = {'location_id': [locations[0].pk, locations[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'location': [locations[0].slug, locations[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_device(self):
|
||||
devices = Device.objects.all()[:2]
|
||||
params = {'device_id': [devices[0].pk, devices[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'device': [devices[0].name, devices[1].name]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class DeviceBayTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = DeviceBay.objects.all()
|
||||
filterset = DeviceBayFilterSet
|
||||
|
@ -308,6 +308,11 @@ class DeviceTestCase(TestCase):
|
||||
rear_port_position=2
|
||||
).save()
|
||||
|
||||
ModuleBayTemplate(
|
||||
device_type=self.device_type,
|
||||
name='Module Bay 1'
|
||||
).save()
|
||||
|
||||
DeviceBayTemplate(
|
||||
device_type=self.device_type,
|
||||
name='Device Bay 1'
|
||||
@ -371,6 +376,11 @@ class DeviceTestCase(TestCase):
|
||||
rear_port_position=2
|
||||
)
|
||||
|
||||
ModuleBay.objects.get(
|
||||
device=d,
|
||||
name='Module Bay 1'
|
||||
)
|
||||
|
||||
DeviceBay.objects.get(
|
||||
device=d,
|
||||
name='Device Bay 1'
|
||||
|
@ -554,6 +554,19 @@ class DeviceTypeTestCase(
|
||||
url = reverse('dcim:devicetype_frontports', kwargs={'pk': devicetype.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_devicetype_modulebays(self):
|
||||
devicetype = DeviceType.objects.first()
|
||||
module_bays = (
|
||||
ModuleBayTemplate(device_type=devicetype, name='Module Bay 1'),
|
||||
ModuleBayTemplate(device_type=devicetype, name='Module Bay 2'),
|
||||
ModuleBayTemplate(device_type=devicetype, name='Module Bay 3'),
|
||||
)
|
||||
ModuleBayTemplate.objects.bulk_create(module_bays)
|
||||
|
||||
url = reverse('dcim:devicetype_modulebays', kwargs={'pk': devicetype.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_devicetype_devicebays(self):
|
||||
devicetype = DeviceType.objects.first()
|
||||
@ -638,6 +651,10 @@ front-ports:
|
||||
- name: Front Port 3
|
||||
type: 8p8c
|
||||
rear_port: Rear Port 3
|
||||
module-bays:
|
||||
- name: Module Bay 1
|
||||
- name: Module Bay 2
|
||||
- name: Module Bay 3
|
||||
device-bays:
|
||||
- name: Device Bay 1
|
||||
- name: Device Bay 2
|
||||
@ -658,6 +675,7 @@ device-bays:
|
||||
'dcim.add_interfacetemplate',
|
||||
'dcim.add_frontporttemplate',
|
||||
'dcim.add_rearporttemplate',
|
||||
'dcim.add_modulebaytemplate',
|
||||
'dcim.add_devicebaytemplate',
|
||||
)
|
||||
|
||||
@ -710,6 +728,10 @@ device-bays:
|
||||
self.assertEqual(fp1.rear_port, rp1)
|
||||
self.assertEqual(fp1.rear_port_position, 1)
|
||||
|
||||
self.assertEqual(dt.modulebaytemplates.count(), 3)
|
||||
db1 = ModuleBayTemplate.objects.first()
|
||||
self.assertEqual(db1.name, 'Module Bay 1')
|
||||
|
||||
self.assertEqual(dt.devicebaytemplates.count(), 3)
|
||||
db1 = DeviceBayTemplate.objects.first()
|
||||
self.assertEqual(db1.name, 'Device Bay 1')
|
||||
@ -1011,6 +1033,39 @@ class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase
|
||||
}
|
||||
|
||||
|
||||
class ModuleBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||
model = ModuleBayTemplate
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||
devicetypes = (
|
||||
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
|
||||
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
|
||||
)
|
||||
DeviceType.objects.bulk_create(devicetypes)
|
||||
|
||||
ModuleBayTemplate.objects.bulk_create((
|
||||
ModuleBayTemplate(device_type=devicetypes[0], name='Module Bay Template 1'),
|
||||
ModuleBayTemplate(device_type=devicetypes[0], name='Module Bay Template 2'),
|
||||
ModuleBayTemplate(device_type=devicetypes[0], name='Module Bay Template 3'),
|
||||
))
|
||||
|
||||
cls.form_data = {
|
||||
'device_type': devicetypes[1].pk,
|
||||
'name': 'Module Bay Template X',
|
||||
}
|
||||
|
||||
cls.bulk_create_data = {
|
||||
'device_type': devicetypes[1].pk,
|
||||
'name_pattern': 'Module Bay Template [4-6]',
|
||||
}
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'description': 'Foo bar',
|
||||
}
|
||||
|
||||
|
||||
class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||
model = DeviceBayTemplate
|
||||
|
||||
@ -1307,6 +1362,19 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
url = reverse('dcim:device_frontports', kwargs={'pk': device.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_device_modulebays(self):
|
||||
device = Device.objects.first()
|
||||
device_bays = (
|
||||
ModuleBay(device=device, name='Module Bay 1'),
|
||||
ModuleBay(device=device, name='Module Bay 2'),
|
||||
ModuleBay(device=device, name='Module Bay 3'),
|
||||
)
|
||||
ModuleBay.objects.bulk_create(device_bays)
|
||||
|
||||
url = reverse('dcim:device_modulebays', kwargs={'pk': device.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_device_devicebays(self):
|
||||
device = Device.objects.first()
|
||||
@ -1807,6 +1875,47 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
self.assertHttpStatus(response, 200)
|
||||
|
||||
|
||||
class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
model = ModuleBay
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
device = create_test_device('Device 1')
|
||||
|
||||
ModuleBay.objects.bulk_create([
|
||||
ModuleBay(device=device, name='Module Bay 1'),
|
||||
ModuleBay(device=device, name='Module Bay 2'),
|
||||
ModuleBay(device=device, name='Module Bay 3'),
|
||||
])
|
||||
|
||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
|
||||
cls.form_data = {
|
||||
'device': device.pk,
|
||||
'name': 'Module Bay X',
|
||||
'description': 'A device bay',
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
|
||||
cls.bulk_create_data = {
|
||||
'device': device.pk,
|
||||
'name_pattern': 'Module Bay [4-6]',
|
||||
'description': 'A module bay',
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"device,name",
|
||||
"Device 1,Module Bay 4",
|
||||
"Device 1,Module Bay 5",
|
||||
"Device 1,Module Bay 6",
|
||||
)
|
||||
|
||||
|
||||
class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
model = DeviceBay
|
||||
|
||||
|
@ -113,6 +113,7 @@ urlpatterns = [
|
||||
path('device-types/<int:pk>/interfaces/', views.DeviceTypeInterfacesView.as_view(), name='devicetype_interfaces'),
|
||||
path('device-types/<int:pk>/front-ports/', views.DeviceTypeFrontPortsView.as_view(), name='devicetype_frontports'),
|
||||
path('device-types/<int:pk>/rear-ports/', views.DeviceTypeRearPortsView.as_view(), name='devicetype_rearports'),
|
||||
path('device-types/<int:pk>/module-bays/', views.DeviceTypeModuleBaysView.as_view(), name='devicetype_modulebays'),
|
||||
path('device-types/<int:pk>/device-bays/', views.DeviceTypeDeviceBaysView.as_view(), name='devicetype_devicebays'),
|
||||
path('device-types/<int:pk>/edit/', views.DeviceTypeEditView.as_view(), name='devicetype_edit'),
|
||||
path('device-types/<int:pk>/delete/', views.DeviceTypeDeleteView.as_view(), name='devicetype_delete'),
|
||||
@ -183,6 +184,14 @@ urlpatterns = [
|
||||
path('device-bay-templates/<int:pk>/edit/', views.DeviceBayTemplateEditView.as_view(), name='devicebaytemplate_edit'),
|
||||
path('device-bay-templates/<int:pk>/delete/', views.DeviceBayTemplateDeleteView.as_view(), name='devicebaytemplate_delete'),
|
||||
|
||||
# Device bay templates
|
||||
path('module-bay-templates/add/', views.ModuleBayTemplateCreateView.as_view(), name='modulebaytemplate_add'),
|
||||
path('module-bay-templates/edit/', views.ModuleBayTemplateBulkEditView.as_view(), name='modulebaytemplate_bulk_edit'),
|
||||
path('module-bay-templates/rename/', views.ModuleBayTemplateBulkRenameView.as_view(), name='modulebaytemplate_bulk_rename'),
|
||||
path('module-bay-templates/delete/', views.ModuleBayTemplateBulkDeleteView.as_view(), name='modulebaytemplate_bulk_delete'),
|
||||
path('module-bay-templates/<int:pk>/edit/', views.ModuleBayTemplateEditView.as_view(), name='modulebaytemplate_edit'),
|
||||
path('module-bay-templates/<int:pk>/delete/', views.ModuleBayTemplateDeleteView.as_view(), name='modulebaytemplate_delete'),
|
||||
|
||||
# Device roles
|
||||
path('device-roles/', views.DeviceRoleListView.as_view(), name='devicerole_list'),
|
||||
path('device-roles/add/', views.DeviceRoleEditView.as_view(), name='devicerole_add'),
|
||||
@ -222,6 +231,7 @@ urlpatterns = [
|
||||
path('devices/<int:pk>/interfaces/', views.DeviceInterfacesView.as_view(), name='device_interfaces'),
|
||||
path('devices/<int:pk>/front-ports/', views.DeviceFrontPortsView.as_view(), name='device_frontports'),
|
||||
path('devices/<int:pk>/rear-ports/', views.DeviceRearPortsView.as_view(), name='device_rearports'),
|
||||
path('devices/<int:pk>/module-bays/', views.DeviceModuleBaysView.as_view(), name='device_modulebays'),
|
||||
path('devices/<int:pk>/device-bays/', views.DeviceDeviceBaysView.as_view(), name='device_devicebays'),
|
||||
path('devices/<int:pk>/inventory/', views.DeviceInventoryView.as_view(), name='device_inventory'),
|
||||
path('devices/<int:pk>/config-context/', views.DeviceConfigContextView.as_view(), name='device_configcontext'),
|
||||
@ -343,6 +353,19 @@ urlpatterns = [
|
||||
path('rear-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='rearport_connect', kwargs={'termination_a_type': RearPort}),
|
||||
path('devices/rear-ports/add/', views.DeviceBulkAddRearPortView.as_view(), name='device_bulk_add_rearport'),
|
||||
|
||||
# Module bays
|
||||
path('module-bays/', views.ModuleBayListView.as_view(), name='modulebay_list'),
|
||||
path('module-bays/add/', views.ModuleBayCreateView.as_view(), name='modulebay_add'),
|
||||
path('module-bays/import/', views.ModuleBayBulkImportView.as_view(), name='modulebay_import'),
|
||||
path('module-bays/edit/', views.ModuleBayBulkEditView.as_view(), name='modulebay_bulk_edit'),
|
||||
path('module-bays/rename/', views.ModuleBayBulkRenameView.as_view(), name='modulebay_bulk_rename'),
|
||||
path('module-bays/delete/', views.ModuleBayBulkDeleteView.as_view(), name='modulebay_bulk_delete'),
|
||||
path('module-bays/<int:pk>/', views.ModuleBayView.as_view(), name='modulebay'),
|
||||
path('module-bays/<int:pk>/edit/', views.ModuleBayEditView.as_view(), name='modulebay_edit'),
|
||||
path('module-bays/<int:pk>/delete/', views.ModuleBayDeleteView.as_view(), name='modulebay_delete'),
|
||||
path('module-bays/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='modulebay_changelog', kwargs={'model': ModuleBay}),
|
||||
path('devices/module-bays/add/', views.DeviceBulkAddModuleBayView.as_view(), name='device_bulk_add_modulebay'),
|
||||
|
||||
# Device bays
|
||||
path('device-bays/', views.DeviceBayListView.as_view(), name='devicebay_list'),
|
||||
path('device-bays/add/', views.DeviceBayCreateView.as_view(), name='devicebay_add'),
|
||||
|
@ -30,9 +30,9 @@ from .constants import NONCONNECTABLE_IFACE_TYPES
|
||||
from .models import (
|
||||
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||
InventoryItem, Manufacturer, PathEndpoint, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel,
|
||||
PowerPort, PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
||||
SiteGroup, VirtualChassis,
|
||||
InventoryItem, Manufacturer, ModuleBay, ModuleBayTemplate, PathEndpoint, Platform, PowerFeed, PowerOutlet,
|
||||
PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort,
|
||||
RearPortTemplate, Region, Site, SiteGroup, VirtualChassis,
|
||||
)
|
||||
|
||||
|
||||
@ -836,6 +836,12 @@ class DeviceTypeRearPortsView(DeviceTypeComponentsView):
|
||||
filterset = filtersets.RearPortTemplateFilterSet
|
||||
|
||||
|
||||
class DeviceTypeModuleBaysView(DeviceTypeComponentsView):
|
||||
child_model = ModuleBayTemplate
|
||||
table = tables.ModuleBayTemplateTable
|
||||
filterset = filtersets.ModuleBayTemplateFilterSet
|
||||
|
||||
|
||||
class DeviceTypeDeviceBaysView(DeviceTypeComponentsView):
|
||||
child_model = DeviceBayTemplate
|
||||
table = tables.DeviceBayTemplateTable
|
||||
@ -861,6 +867,7 @@ class DeviceTypeImportView(generic.ObjectImportView):
|
||||
'dcim.add_interfacetemplate',
|
||||
'dcim.add_frontporttemplate',
|
||||
'dcim.add_rearporttemplate',
|
||||
'dcim.add_modulebaytemplate',
|
||||
'dcim.add_devicebaytemplate',
|
||||
]
|
||||
queryset = DeviceType.objects.all()
|
||||
@ -873,6 +880,7 @@ class DeviceTypeImportView(generic.ObjectImportView):
|
||||
('interfaces', forms.InterfaceTemplateImportForm),
|
||||
('rear-ports', forms.RearPortTemplateImportForm),
|
||||
('front-ports', forms.FrontPortTemplateImportForm),
|
||||
('module-bays', forms.ModuleBayTemplateImportForm),
|
||||
('device-bays', forms.DeviceBayTemplateImportForm),
|
||||
))
|
||||
|
||||
@ -1132,6 +1140,40 @@ class RearPortTemplateBulkDeleteView(generic.BulkDeleteView):
|
||||
table = tables.RearPortTemplateTable
|
||||
|
||||
|
||||
#
|
||||
# Module bay templates
|
||||
#
|
||||
|
||||
class ModuleBayTemplateCreateView(generic.ComponentCreateView):
|
||||
queryset = ModuleBayTemplate.objects.all()
|
||||
form = forms.ModuleBayTemplateCreateForm
|
||||
model_form = forms.ModuleBayTemplateForm
|
||||
|
||||
|
||||
class ModuleBayTemplateEditView(generic.ObjectEditView):
|
||||
queryset = ModuleBayTemplate.objects.all()
|
||||
model_form = forms.ModuleBayTemplateForm
|
||||
|
||||
|
||||
class ModuleBayTemplateDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ModuleBayTemplate.objects.all()
|
||||
|
||||
|
||||
class ModuleBayTemplateBulkEditView(generic.BulkEditView):
|
||||
queryset = ModuleBayTemplate.objects.all()
|
||||
table = tables.ModuleBayTemplateTable
|
||||
form = forms.ModuleBayTemplateBulkEditForm
|
||||
|
||||
|
||||
class ModuleBayTemplateBulkRenameView(generic.BulkRenameView):
|
||||
queryset = ModuleBayTemplate.objects.all()
|
||||
|
||||
|
||||
class ModuleBayTemplateBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = ModuleBayTemplate.objects.all()
|
||||
table = tables.ModuleBayTemplateTable
|
||||
|
||||
|
||||
#
|
||||
# Device bay templates
|
||||
#
|
||||
@ -1388,6 +1430,13 @@ class DeviceRearPortsView(DeviceComponentsView):
|
||||
template_name = 'dcim/device/rearports.html'
|
||||
|
||||
|
||||
class DeviceModuleBaysView(DeviceComponentsView):
|
||||
child_model = ModuleBay
|
||||
table = tables.DeviceModuleBayTable
|
||||
filterset = filtersets.ModuleBayFilterSet
|
||||
template_name = 'dcim/device/modulebays.html'
|
||||
|
||||
|
||||
class DeviceDeviceBaysView(DeviceComponentsView):
|
||||
child_model = DeviceBay
|
||||
table = tables.DeviceDeviceBayTable
|
||||
@ -1978,6 +2027,61 @@ class RearPortBulkDeleteView(generic.BulkDeleteView):
|
||||
table = tables.RearPortTable
|
||||
|
||||
|
||||
#
|
||||
# Module bays
|
||||
#
|
||||
|
||||
class ModuleBayListView(generic.ObjectListView):
|
||||
queryset = ModuleBay.objects.all()
|
||||
filterset = filtersets.ModuleBayFilterSet
|
||||
filterset_form = forms.ModuleBayFilterForm
|
||||
table = tables.ModuleBayTable
|
||||
action_buttons = ('import', 'export')
|
||||
|
||||
|
||||
class ModuleBayView(generic.ObjectView):
|
||||
queryset = ModuleBay.objects.all()
|
||||
|
||||
|
||||
class ModuleBayCreateView(generic.ComponentCreateView):
|
||||
queryset = ModuleBay.objects.all()
|
||||
form = forms.ModuleBayCreateForm
|
||||
model_form = forms.ModuleBayForm
|
||||
|
||||
|
||||
class ModuleBayEditView(generic.ObjectEditView):
|
||||
queryset = ModuleBay.objects.all()
|
||||
model_form = forms.ModuleBayForm
|
||||
template_name = 'dcim/device_component_edit.html'
|
||||
|
||||
|
||||
class ModuleBayDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ModuleBay.objects.all()
|
||||
|
||||
|
||||
class ModuleBayBulkImportView(generic.BulkImportView):
|
||||
queryset = ModuleBay.objects.all()
|
||||
model_form = forms.ModuleBayCSVForm
|
||||
table = tables.ModuleBayTable
|
||||
|
||||
|
||||
class ModuleBayBulkEditView(generic.BulkEditView):
|
||||
queryset = ModuleBay.objects.all()
|
||||
filterset = filtersets.ModuleBayFilterSet
|
||||
table = tables.ModuleBayTable
|
||||
form = forms.ModuleBayBulkEditForm
|
||||
|
||||
|
||||
class ModuleBayBulkRenameView(generic.BulkRenameView):
|
||||
queryset = ModuleBay.objects.all()
|
||||
|
||||
|
||||
class ModuleBayBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = ModuleBay.objects.all()
|
||||
filterset = filtersets.ModuleBayFilterSet
|
||||
table = tables.ModuleBayTable
|
||||
|
||||
|
||||
#
|
||||
# Device bays
|
||||
#
|
||||
@ -2234,6 +2338,17 @@ class DeviceBulkAddRearPortView(generic.BulkComponentCreateView):
|
||||
default_return_url = 'dcim:device_list'
|
||||
|
||||
|
||||
class DeviceBulkAddModuleBayView(generic.BulkComponentCreateView):
|
||||
parent_model = Device
|
||||
parent_field = 'device'
|
||||
form = forms.ModuleBayBulkCreateForm
|
||||
queryset = ModuleBay.objects.all()
|
||||
model_form = forms.ModuleBayForm
|
||||
filterset = filtersets.DeviceFilterSet
|
||||
table = tables.DeviceTable
|
||||
default_return_url = 'dcim:device_list'
|
||||
|
||||
|
||||
class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
|
||||
parent_model = Device
|
||||
parent_field = 'device'
|
||||
|
@ -161,6 +161,7 @@ DEVICES_MENU = Menu(
|
||||
get_model_item('dcim', 'consoleserverport', 'Console Server Ports', actions=['import']),
|
||||
get_model_item('dcim', 'powerport', 'Power Ports', actions=['import']),
|
||||
get_model_item('dcim', 'poweroutlet', 'Power Outlets', actions=['import']),
|
||||
get_model_item('dcim', 'modulebay', 'Module Bays', actions=['import']),
|
||||
get_model_item('dcim', 'devicebay', 'Device Bays', actions=['import']),
|
||||
get_model_item('dcim', 'inventoryitem', 'Inventory Items', actions=['import']),
|
||||
),
|
||||
|
@ -69,6 +69,13 @@
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_devicebay %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'dcim:modulebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}">
|
||||
Module Bays
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_devicebay %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'dcim:devicebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}">
|
||||
@ -151,6 +158,14 @@
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with modulebay_count=object.modulebays.count %}
|
||||
{% if modulebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'module-bays' %} active{% endif %}" href="{% url 'dcim:device_modulebays' pk=object.pk %}">Module Bays {% badge modulebay_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with devicebay_count=object.devicebays.count %}
|
||||
{% if devicebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
|
43
netbox/templates/dcim/device/modulebays.html
Normal file
43
netbox/templates/dcim/device/modulebays.html
Normal file
@ -0,0 +1,43 @@
|
||||
{% extends 'dcim/device/base.html' %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load helpers %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceModuleBayTable_config" %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body" id="object_list">
|
||||
{% include 'htmx/table.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="noprint bulk-buttons">
|
||||
<div class="bulk-button-group">
|
||||
{% if perms.dcim.change_modulebay %}
|
||||
<button type="submit" name="_rename" formaction="{% url 'dcim:modulebay_bulk_rename' %}?return_url={{ object.get_absolute_url }}%23tab_modulebays" class="btn btn-outline-warning btn-sm">
|
||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
||||
</button>
|
||||
<button type="submit" name="_edit" formaction="{% url 'dcim:modulebay_bulk_edit' %}?device={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_modulebays" class="btn btn-warning btn-sm">
|
||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_modulebay %}
|
||||
<button type="submit" formaction="{% url 'dcim:modulebay_bulk_delete' %}?return_url={{ object.get_absolute_url }}%23tab_modulebays" class="btn btn-outline-danger btn-sm">
|
||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if perms.dcim.add_modulebay %}
|
||||
<div class="bulk-button-group">
|
||||
<a href="{% url 'dcim:modulebay_add' %}?device={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_modulebays" class="btn btn-primary btn-sm">
|
||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Module Bays
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
@ -56,6 +56,13 @@
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_modulebay %}
|
||||
<li>
|
||||
<button type="submit" formaction="{% url 'dcim:device_bulk_add_modulebay' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||
Module Bays
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_inventoryitem %}
|
||||
<li>
|
||||
<button type="submit" formaction="{% url 'dcim:device_bulk_add_inventoryitem' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||
|
@ -38,6 +38,9 @@
|
||||
{% if perms.dcim.add_rearporttemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:rearporttemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_rearports">Rear Ports</a></li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_modulebaytemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:modulebaytemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_modulebays">Module Bays</a></li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_devicebaytemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:devicebaytemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_devicebays">Device Bays</a></li>
|
||||
{% endif %}
|
||||
@ -109,6 +112,14 @@
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with modulebay_count=object.modulebaytemplates.count %}
|
||||
{% if modulebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'module-bay-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_modulebays' pk=object.pk %}">Module Bays {% badge modulebay_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with devicebay_count=object.devicebaytemplates.count %}
|
||||
{% if devicebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
|
69
netbox/templates/dcim/modulebay.html
Normal file
69
netbox/templates/dcim/modulebay.html
Normal file
@ -0,0 +1,69 @@
|
||||
{% extends 'dcim/device_component.html' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Module Bay</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">Device</th>
|
||||
<td>
|
||||
<a href="{{ object.device.get_absolute_url }}">{{ object.device }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Name</th>
|
||||
<td>{{ object.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Label</th>
|
||||
<td>{{ object.label|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Description</th>
|
||||
<td>{{ object.description|placeholder }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Installed Module</h5>
|
||||
<div class="card-body">
|
||||
{% if object.module %}
|
||||
{% with module=object.module %}
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">Module</th>
|
||||
<td>
|
||||
<a href="{{ module.get_absolute_url }}">{{ module }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Module Type</th>
|
||||
<td>{{ module.module_type }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<div class="text-muted">None</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user