mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Merge pull request #8105 from netbox-community/7844-modules
Closes #7844: Add support for device modules
This commit is contained in:
commit
d69a314bbf
@ -37,4 +37,5 @@ Once component templates have been created, every new device that you create as
|
||||
{!models/dcim/interfacetemplate.md!}
|
||||
{!models/dcim/frontporttemplate.md!}
|
||||
{!models/dcim/rearporttemplate.md!}
|
||||
{!models/dcim/modulebaytemplate.md!}
|
||||
{!models/dcim/devicebaytemplate.md!}
|
||||
|
@ -17,6 +17,7 @@ Device components represent discrete objects within a device which are used to t
|
||||
{!models/dcim/interface.md!}
|
||||
{!models/dcim/frontport.md!}
|
||||
{!models/dcim/rearport.md!}
|
||||
{!models/dcim/modulebay.md!}
|
||||
{!models/dcim/devicebay.md!}
|
||||
{!models/dcim/inventoryitem.md!}
|
||||
|
||||
|
4
docs/core-functionality/modules.md
Normal file
4
docs/core-functionality/modules.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Modules
|
||||
|
||||
{!models/dcim/moduletype.md!}
|
||||
{!models/dcim/module.md!}
|
@ -5,4 +5,4 @@ Device bays represent a space or slot within a parent device in which a child de
|
||||
Child devices are first-class Devices in their own right: That is, they are fully independent managed entities which don't share any control plane with the parent. Just like normal devices, child devices have their own platform (OS), role, tags, and components. LAG interfaces may not group interfaces belonging to different child devices.
|
||||
|
||||
!!! note
|
||||
Device bays are **not** suitable for modeling line cards (such as those commonly found in chassis-based routers and switches), as these components depend on the control plane of the parent device to operate. Instead, line cards and similarly non-autonomous hardware should be modeled as inventory items within a device, with any associated interfaces or other components assigned directly to the device.
|
||||
Device bays are **not** suitable for modeling line cards (such as those commonly found in chassis-based routers and switches), as these components depend on the control plane of the parent device to operate. Instead, these should be modeled as modules installed within module bays.
|
||||
|
@ -1,3 +1,3 @@
|
||||
## Device Bay Templates
|
||||
|
||||
A template for a device bay that will be created on all instantiations of the parent device type.
|
||||
A template for a device bay that will be created on all instantiations of the parent device type. Device bays hold child devices, such as blade servers.
|
||||
|
5
docs/models/dcim/module.md
Normal file
5
docs/models/dcim/module.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Modules
|
||||
|
||||
A module is a field-replaceable hardware component installed within a device which houses its own child components. The most common example is a chassis-based router or switch.
|
||||
|
||||
Similar to devices, modules are instantiated from module types, and any components associated with the module type are automatically instantiated on the new model. Each module must be installed within a module bay on a device, and each module bay may have only one module installed in it. A module may optionally be assigned a serial number and asset tag.
|
3
docs/models/dcim/modulebay.md
Normal file
3
docs/models/dcim/modulebay.md
Normal file
@ -0,0 +1,3 @@
|
||||
## Module Bays
|
||||
|
||||
Module bays represent a space or slot within a device in which a field-replaceable module 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.
|
3
docs/models/dcim/modulebaytemplate.md
Normal file
3
docs/models/dcim/modulebaytemplate.md
Normal file
@ -0,0 +1,3 @@
|
||||
## Module Bay Templates
|
||||
|
||||
A template for a module bay that will be created on all instantiations of the parent device type. Module bays hold installed modules that do not have an independent management plane, such as line cards.
|
23
docs/models/dcim/moduletype.md
Normal file
23
docs/models/dcim/moduletype.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Module Types
|
||||
|
||||
A module type represent a specific make and model of hardware component which is installable within a device and has its own child components. For example, consider a chassis-based switch or router with a number of field-replaceable line cards. Each line card has its own model number and includes a certain set of components such as interfaces. Each module type may have a manufacturer, model number, and part number assigned to it.
|
||||
|
||||
Similar to device types, each module type can have any of the following component templates associated with it:
|
||||
|
||||
* Interfaces
|
||||
* Console ports
|
||||
* Console server ports
|
||||
* Power ports
|
||||
* Power Outlets
|
||||
* Front pass-through ports
|
||||
* Rear pass-through ports
|
||||
|
||||
Note that device bays and module bays may _not_ be added to modules.
|
||||
|
||||
## Automatic Component Renaming
|
||||
|
||||
When adding component templates to a module type, the string `{module}` can be used to reference the `position` field of the module bay into which an instance of the module type is being installed.
|
||||
|
||||
For example, you can create a module type with interface templates named `Gi{module}/0/[1-48]`. When a new module of this type is "installed" to a module bay with a position of "3", NetBox will automatically name these interfaces `Gi3/0/[1-48]`.
|
||||
|
||||
Automatic renaming is supported for all modular component types (those listed above).
|
@ -59,6 +59,7 @@ nav:
|
||||
- Sites and Racks: 'core-functionality/sites-and-racks.md'
|
||||
- Devices and Cabling: 'core-functionality/devices.md'
|
||||
- Device Types: 'core-functionality/device-types.md'
|
||||
- Modules: 'core-functionality/modules.md'
|
||||
- Virtualization: 'core-functionality/virtualization.md'
|
||||
- Service Mapping: 'core-functionality/services.md'
|
||||
- Circuits: 'core-functionality/circuits.md'
|
||||
|
@ -4,6 +4,7 @@ from dcim import models
|
||||
from netbox.api.serializers import BaseModelSerializer, WritableNestedSerializer
|
||||
|
||||
__all__ = [
|
||||
'ComponentNestedModuleSerializer',
|
||||
'NestedCableSerializer',
|
||||
'NestedConsolePortSerializer',
|
||||
'NestedConsolePortTemplateSerializer',
|
||||
@ -20,6 +21,10 @@ __all__ = [
|
||||
'NestedInterfaceTemplateSerializer',
|
||||
'NestedInventoryItemSerializer',
|
||||
'NestedManufacturerSerializer',
|
||||
'NestedModuleBaySerializer',
|
||||
'NestedModuleBayTemplateSerializer',
|
||||
'NestedModuleSerializer',
|
||||
'NestedModuleTypeSerializer',
|
||||
'NestedPlatformSerializer',
|
||||
'NestedPowerFeedSerializer',
|
||||
'NestedPowerOutletSerializer',
|
||||
@ -117,7 +122,7 @@ class NestedRackReservationSerializer(WritableNestedSerializer):
|
||||
|
||||
|
||||
#
|
||||
# Device types
|
||||
# Device/module types
|
||||
#
|
||||
|
||||
class NestedManufacturerSerializer(WritableNestedSerializer):
|
||||
@ -139,6 +144,20 @@ class NestedDeviceTypeSerializer(WritableNestedSerializer):
|
||||
fields = ['id', 'url', 'display', 'manufacturer', 'model', 'slug', 'device_count']
|
||||
|
||||
|
||||
class NestedModuleTypeSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:moduletype-detail')
|
||||
manufacturer = NestedManufacturerSerializer(read_only=True)
|
||||
# module_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.ModuleType
|
||||
fields = ['id', 'url', 'display', 'manufacturer', 'model']
|
||||
|
||||
|
||||
#
|
||||
# Component templates
|
||||
#
|
||||
|
||||
class NestedConsolePortTemplateSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleporttemplate-detail')
|
||||
|
||||
@ -195,6 +214,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')
|
||||
|
||||
@ -235,6 +262,37 @@ class NestedDeviceSerializer(WritableNestedSerializer):
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class ModuleNestedModuleBaySerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebay-detail')
|
||||
|
||||
class Meta:
|
||||
model = models.ModuleBay
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class ComponentNestedModuleSerializer(WritableNestedSerializer):
|
||||
"""
|
||||
Used by device component serializers.
|
||||
"""
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
|
||||
module_bay = ModuleNestedModuleBaySerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Module
|
||||
fields = ['id', 'url', 'display', 'device', 'module_bay']
|
||||
|
||||
|
||||
class NestedModuleSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
module_bay = ModuleNestedModuleBaySerializer(read_only=True)
|
||||
module_type = NestedModuleTypeSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Module
|
||||
fields = ['id', 'url', 'display', 'device', 'module_bay', 'module_type']
|
||||
|
||||
|
||||
class NestedConsoleServerPortSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
@ -298,6 +356,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.ModuleBay
|
||||
fields = ['id', 'url', 'display', 'module', 'name']
|
||||
|
||||
|
||||
class NestedDeviceBaySerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
|
@ -261,7 +261,7 @@ class RackElevationDetailFilterSerializer(serializers.Serializer):
|
||||
|
||||
|
||||
#
|
||||
# Device types
|
||||
# Device/module types
|
||||
#
|
||||
|
||||
class ManufacturerSerializer(PrimaryModelSerializer):
|
||||
@ -294,6 +294,23 @@ class DeviceTypeSerializer(PrimaryModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class ModuleTypeSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:moduletype-detail')
|
||||
manufacturer = NestedManufacturerSerializer()
|
||||
# module_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ModuleType
|
||||
fields = [
|
||||
'id', 'url', 'display', 'manufacturer', 'model', 'part_number', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Component templates
|
||||
#
|
||||
|
||||
class ConsolePortTemplateSerializer(ValidatedModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleporttemplate-detail')
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
@ -409,6 +426,18 @@ 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', 'position', 'description', 'created',
|
||||
'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class DeviceBayTemplateSerializer(ValidatedModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebaytemplate-detail')
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
@ -491,6 +520,20 @@ class DeviceSerializer(PrimaryModelSerializer):
|
||||
return data
|
||||
|
||||
|
||||
class ModuleSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
module_bay = NestedModuleBaySerializer()
|
||||
module_type = NestedModuleTypeSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Module
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class DeviceWithConfigContextSerializer(DeviceSerializer):
|
||||
config_context = serializers.SerializerMethodField()
|
||||
|
||||
@ -518,6 +561,10 @@ class DeviceNAPALMSerializer(serializers.Serializer):
|
||||
class ConsoleServerPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
module = ComponentNestedModuleSerializer(
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
type = ChoiceField(
|
||||
choices=ConsolePortTypeChoices,
|
||||
allow_blank=True,
|
||||
@ -533,8 +580,8 @@ class ConsoleServerPortSerializer(PrimaryModelSerializer, LinkTerminationSeriali
|
||||
class Meta:
|
||||
model = ConsoleServerPort
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected',
|
||||
'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description',
|
||||
'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
@ -542,6 +589,10 @@ class ConsoleServerPortSerializer(PrimaryModelSerializer, LinkTerminationSeriali
|
||||
class ConsolePortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
module = ComponentNestedModuleSerializer(
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
type = ChoiceField(
|
||||
choices=ConsolePortTypeChoices,
|
||||
allow_blank=True,
|
||||
@ -557,8 +608,8 @@ class ConsolePortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, C
|
||||
class Meta:
|
||||
model = ConsolePort
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected',
|
||||
'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description',
|
||||
'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
@ -566,6 +617,10 @@ class ConsolePortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, C
|
||||
class PowerOutletSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
module = ComponentNestedModuleSerializer(
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
type = ChoiceField(
|
||||
choices=PowerOutletTypeChoices,
|
||||
allow_blank=True,
|
||||
@ -587,15 +642,20 @@ class PowerOutletSerializer(PrimaryModelSerializer, LinkTerminationSerializer, C
|
||||
class Meta:
|
||||
model = PowerOutlet
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg',
|
||||
'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint',
|
||||
'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
|
||||
class PowerPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
module = ComponentNestedModuleSerializer(
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
type = ChoiceField(
|
||||
choices=PowerPortTypeChoices,
|
||||
allow_blank=True,
|
||||
@ -606,15 +666,20 @@ class PowerPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
|
||||
class Meta:
|
||||
model = PowerPort
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||
'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw',
|
||||
'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint',
|
||||
'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
|
||||
class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
module = ComponentNestedModuleSerializer(
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
type = ChoiceField(choices=InterfaceTypeChoices)
|
||||
parent = NestedInterfaceSerializer(required=False, allow_null=True)
|
||||
bridge = NestedInterfaceSerializer(required=False, allow_null=True)
|
||||
@ -643,12 +708,12 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
|
||||
class Meta:
|
||||
model = Interface
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mtu',
|
||||
'mac_address', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
|
||||
'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable', 'wireless_link',
|
||||
'link_peer', 'link_peer_type', 'wireless_lans', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses',
|
||||
'count_fhrp_groups', '_occupied',
|
||||
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag',
|
||||
'mtu', 'mac_address', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel',
|
||||
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'mark_connected',
|
||||
'cable', 'wireless_link', 'link_peer', 'link_peer_type', 'wireless_lans', 'connected_endpoint',
|
||||
'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
|
||||
]
|
||||
|
||||
def validate(self, data):
|
||||
@ -668,13 +733,17 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
|
||||
class RearPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
module = ComponentNestedModuleSerializer(
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
type = ChoiceField(choices=PortTypeChoices)
|
||||
cable = NestedCableSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RearPort
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'color', 'positions', 'description',
|
||||
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'description',
|
||||
'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', '_occupied',
|
||||
]
|
||||
@ -694,6 +763,10 @@ class FrontPortRearPortSerializer(WritableNestedSerializer):
|
||||
class FrontPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
module = ComponentNestedModuleSerializer(
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
type = ChoiceField(choices=PortTypeChoices)
|
||||
rear_port = FrontPortRearPortSerializer()
|
||||
cable = NestedCableSerializer(read_only=True)
|
||||
@ -701,9 +774,22 @@ class FrontPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer):
|
||||
class Meta:
|
||||
model = FrontPort
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
|
||||
'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', '_occupied',
|
||||
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port',
|
||||
'rear_port_position', 'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
|
||||
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', 'position', 'description', 'tags', 'custom_fields',
|
||||
'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
|
@ -16,9 +16,10 @@ router.register('rack-roles', views.RackRoleViewSet)
|
||||
router.register('racks', views.RackViewSet)
|
||||
router.register('rack-reservations', views.RackReservationViewSet)
|
||||
|
||||
# Device types
|
||||
# Device/module types
|
||||
router.register('manufacturers', views.ManufacturerViewSet)
|
||||
router.register('device-types', views.DeviceTypeViewSet)
|
||||
router.register('module-types', views.ModuleTypeViewSet)
|
||||
|
||||
# Device type components
|
||||
router.register('console-port-templates', views.ConsolePortTemplateViewSet)
|
||||
@ -28,12 +29,14 @@ 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
|
||||
# Device/modules
|
||||
router.register('device-roles', views.DeviceRoleViewSet)
|
||||
router.register('platforms', views.PlatformViewSet)
|
||||
router.register('devices', views.DeviceViewSet)
|
||||
router.register('modules', views.ModuleViewSet)
|
||||
|
||||
# Device components
|
||||
router.register('console-ports', views.ConsolePortViewSet)
|
||||
@ -43,6 +46,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)
|
||||
|
||||
|
@ -271,7 +271,7 @@ class ManufacturerViewSet(CustomFieldModelViewSet):
|
||||
|
||||
|
||||
#
|
||||
# Device types
|
||||
# Device/module types
|
||||
#
|
||||
|
||||
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||
@ -283,6 +283,15 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||
brief_prefetch_fields = ['manufacturer']
|
||||
|
||||
|
||||
class ModuleTypeViewSet(CustomFieldModelViewSet):
|
||||
queryset = ModuleType.objects.prefetch_related('manufacturer', 'tags').annotate(
|
||||
# module_count=count_related(Module, 'module_type')
|
||||
)
|
||||
serializer_class = serializers.ModuleTypeSerializer
|
||||
filterset_class = filtersets.ModuleTypeFilterSet
|
||||
brief_prefetch_fields = ['manufacturer']
|
||||
|
||||
|
||||
#
|
||||
# Device type components
|
||||
#
|
||||
@ -329,6 +338,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
|
||||
@ -362,7 +377,7 @@ class PlatformViewSet(CustomFieldModelViewSet):
|
||||
|
||||
|
||||
#
|
||||
# Devices
|
||||
# Devices/modules
|
||||
#
|
||||
|
||||
class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet):
|
||||
@ -511,12 +526,22 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet):
|
||||
return Response(response)
|
||||
|
||||
|
||||
class ModuleViewSet(CustomFieldModelViewSet):
|
||||
queryset = Module.objects.prefetch_related(
|
||||
'device', 'module_bay', 'module_type__manufacturer', 'tags',
|
||||
)
|
||||
serializer_class = serializers.ModuleSerializer
|
||||
filterset_class = filtersets.ModuleFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Device components
|
||||
#
|
||||
|
||||
class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
|
||||
queryset = ConsolePort.objects.prefetch_related('device', '_path__destination', 'cable', '_link_peer', 'tags')
|
||||
queryset = ConsolePort.objects.prefetch_related(
|
||||
'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
|
||||
)
|
||||
serializer_class = serializers.ConsolePortSerializer
|
||||
filterset_class = filtersets.ConsolePortFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
@ -524,7 +549,7 @@ class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
|
||||
|
||||
class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
|
||||
queryset = ConsoleServerPort.objects.prefetch_related(
|
||||
'device', '_path__destination', 'cable', '_link_peer', 'tags'
|
||||
'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
|
||||
)
|
||||
serializer_class = serializers.ConsoleServerPortSerializer
|
||||
filterset_class = filtersets.ConsoleServerPortFilterSet
|
||||
@ -532,14 +557,18 @@ class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
|
||||
|
||||
|
||||
class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
|
||||
queryset = PowerPort.objects.prefetch_related('device', '_path__destination', 'cable', '_link_peer', 'tags')
|
||||
queryset = PowerPort.objects.prefetch_related(
|
||||
'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
|
||||
)
|
||||
serializer_class = serializers.PowerPortSerializer
|
||||
filterset_class = filtersets.PowerPortFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
|
||||
queryset = PowerOutlet.objects.prefetch_related('device', '_path__destination', 'cable', '_link_peer', 'tags')
|
||||
queryset = PowerOutlet.objects.prefetch_related(
|
||||
'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
|
||||
)
|
||||
serializer_class = serializers.PowerOutletSerializer
|
||||
filterset_class = filtersets.PowerOutletFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
@ -547,8 +576,8 @@ class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
|
||||
|
||||
class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
|
||||
queryset = Interface.objects.prefetch_related(
|
||||
'device', 'parent', 'bridge', 'lag', '_path__destination', 'cable', '_link_peer', 'wireless_lans',
|
||||
'untagged_vlan', 'tagged_vlans', 'ip_addresses', 'fhrp_group_assignments', 'tags'
|
||||
'device', 'module__module_bay', 'parent', 'bridge', 'lag', '_path__destination', 'cable', '_link_peer',
|
||||
'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'ip_addresses', 'fhrp_group_assignments', 'tags'
|
||||
)
|
||||
serializer_class = serializers.InterfaceSerializer
|
||||
filterset_class = filtersets.InterfaceFilterSet
|
||||
@ -556,28 +585,39 @@ class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
|
||||
|
||||
|
||||
class FrontPortViewSet(PassThroughPortMixin, ModelViewSet):
|
||||
queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
|
||||
queryset = FrontPort.objects.prefetch_related(
|
||||
'device__device_type__manufacturer', 'module__module_bay', 'rear_port', 'cable', 'tags'
|
||||
)
|
||||
serializer_class = serializers.FrontPortSerializer
|
||||
filterset_class = filtersets.FrontPortFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class RearPortViewSet(PassThroughPortMixin, ModelViewSet):
|
||||
queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
|
||||
queryset = RearPort.objects.prefetch_related(
|
||||
'device__device_type__manufacturer', 'module__module_bay', 'cable', 'tags'
|
||||
)
|
||||
serializer_class = serializers.RearPortSerializer
|
||||
filterset_class = filtersets.RearPortFilterSet
|
||||
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,10 @@ __all__ = (
|
||||
'InventoryItemFilterSet',
|
||||
'LocationFilterSet',
|
||||
'ManufacturerFilterSet',
|
||||
'ModuleBayFilterSet',
|
||||
'ModuleBayTemplateFilterSet',
|
||||
'ModuleFilterSet',
|
||||
'ModuleTypeFilterSet',
|
||||
'PathEndpointFilterSet',
|
||||
'PlatformFilterSet',
|
||||
'PowerConnectionFilterSet',
|
||||
@ -447,6 +451,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,10 +498,90 @@ 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)
|
||||
|
||||
|
||||
class ModuleTypeFilterSet(PrimaryModelFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
label='Manufacturer (ID)',
|
||||
)
|
||||
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='manufacturer__slug',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Manufacturer (slug)',
|
||||
)
|
||||
console_ports = django_filters.BooleanFilter(
|
||||
method='_console_ports',
|
||||
label='Has console ports',
|
||||
)
|
||||
console_server_ports = django_filters.BooleanFilter(
|
||||
method='_console_server_ports',
|
||||
label='Has console server ports',
|
||||
)
|
||||
power_ports = django_filters.BooleanFilter(
|
||||
method='_power_ports',
|
||||
label='Has power ports',
|
||||
)
|
||||
power_outlets = django_filters.BooleanFilter(
|
||||
method='_power_outlets',
|
||||
label='Has power outlets',
|
||||
)
|
||||
interfaces = django_filters.BooleanFilter(
|
||||
method='_interfaces',
|
||||
label='Has interfaces',
|
||||
)
|
||||
pass_through_ports = django_filters.BooleanFilter(
|
||||
method='_pass_through_ports',
|
||||
label='Has pass-through ports',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = ModuleType
|
||||
fields = ['id', 'model', 'part_number']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(manufacturer__name__icontains=value) |
|
||||
Q(model__icontains=value) |
|
||||
Q(part_number__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
)
|
||||
|
||||
def _console_ports(self, queryset, name, value):
|
||||
return queryset.exclude(consoleporttemplates__isnull=value)
|
||||
|
||||
def _console_server_ports(self, queryset, name, value):
|
||||
return queryset.exclude(consoleserverporttemplates__isnull=value)
|
||||
|
||||
def _power_ports(self, queryset, name, value):
|
||||
return queryset.exclude(powerporttemplates__isnull=value)
|
||||
|
||||
def _power_outlets(self, queryset, name, value):
|
||||
return queryset.exclude(poweroutlettemplates__isnull=value)
|
||||
|
||||
def _interfaces(self, queryset, name, value):
|
||||
return queryset.exclude(interfacetemplates__isnull=value)
|
||||
|
||||
def _pass_through_ports(self, queryset, name, value):
|
||||
return queryset.exclude(
|
||||
frontporttemplates__isnull=value,
|
||||
rearporttemplates__isnull=value
|
||||
)
|
||||
|
||||
|
||||
class DeviceTypeComponentFilterSet(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
@ -511,28 +599,36 @@ class DeviceTypeComponentFilterSet(django_filters.FilterSet):
|
||||
return queryset.filter(name__icontains=value)
|
||||
|
||||
|
||||
class ConsolePortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||
class ModularDeviceTypeComponentFilterSet(DeviceTypeComponentFilterSet):
|
||||
moduletype_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=ModuleType.objects.all(),
|
||||
field_name='module_type_id',
|
||||
label='Module type (ID)',
|
||||
)
|
||||
|
||||
|
||||
class ConsolePortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = ConsolePortTemplate
|
||||
fields = ['id', 'name', 'type']
|
||||
|
||||
|
||||
class ConsoleServerPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||
class ConsoleServerPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = ConsoleServerPortTemplate
|
||||
fields = ['id', 'name', 'type']
|
||||
|
||||
|
||||
class PowerPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||
class PowerPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = PowerPortTemplate
|
||||
fields = ['id', 'name', 'type', 'maximum_draw', 'allocated_draw']
|
||||
|
||||
|
||||
class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||
class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||
feed_leg = django_filters.MultipleChoiceFilter(
|
||||
choices=PowerOutletFeedLegChoices,
|
||||
null_value=None
|
||||
@ -543,7 +639,7 @@ class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeCompone
|
||||
fields = ['id', 'name', 'type', 'feed_leg']
|
||||
|
||||
|
||||
class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||
class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=InterfaceTypeChoices,
|
||||
null_value=None
|
||||
@ -554,7 +650,7 @@ class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponent
|
||||
fields = ['id', 'name', 'type', 'mgmt_only']
|
||||
|
||||
|
||||
class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||
class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=PortTypeChoices,
|
||||
null_value=None
|
||||
@ -565,7 +661,7 @@ class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponent
|
||||
fields = ['id', 'name', 'type', 'color']
|
||||
|
||||
|
||||
class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||
class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=PortTypeChoices,
|
||||
null_value=None
|
||||
@ -576,6 +672,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 +863,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,10 +918,49 @@ 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)
|
||||
|
||||
|
||||
class ModuleFilterSet(PrimaryModelFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='module_type__manufacturer',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
label='Manufacturer (ID)',
|
||||
)
|
||||
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='module_type__manufacturer__slug',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Manufacturer (slug)',
|
||||
)
|
||||
device_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Device.objects.all(),
|
||||
label='Device (ID)',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = Module
|
||||
fields = ['id', 'serial', 'asset_tag']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(serial__icontains=value.strip()) |
|
||||
Q(asset_tag__icontains=value.strip()) |
|
||||
Q(comments__icontains=value)
|
||||
).distinct()
|
||||
|
||||
|
||||
class DeviceComponentFilterSet(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
@ -1104,6 +1250,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,10 @@ __all__ = (
|
||||
'InventoryItemBulkEditForm',
|
||||
'LocationBulkEditForm',
|
||||
'ManufacturerBulkEditForm',
|
||||
'ModuleBulkEditForm',
|
||||
'ModuleBayBulkEditForm',
|
||||
'ModuleBayTemplateBulkEditForm',
|
||||
'ModuleTypeBulkEditForm',
|
||||
'PlatformBulkEditForm',
|
||||
'PowerFeedBulkEditForm',
|
||||
'PowerOutletBulkEditForm',
|
||||
@ -326,6 +329,9 @@ class DeviceTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False
|
||||
)
|
||||
part_number = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
u_height = forms.IntegerField(
|
||||
min_value=1,
|
||||
required=False
|
||||
@ -342,7 +348,24 @@ class DeviceTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['airflow']
|
||||
nullable_fields = ['part_number', 'airflow']
|
||||
|
||||
|
||||
class ModuleTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ModuleType.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False
|
||||
)
|
||||
part_number = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['part_number']
|
||||
|
||||
|
||||
class DeviceRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
@ -451,6 +474,32 @@ class DeviceBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
]
|
||||
|
||||
|
||||
class ModuleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Module.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False
|
||||
)
|
||||
module_type = DynamicModelChoiceField(
|
||||
queryset=ModuleType.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'manufacturer_id': '$manufacturer'
|
||||
}
|
||||
)
|
||||
serial = forms.CharField(
|
||||
max_length=50,
|
||||
required=False,
|
||||
label='Serial Number'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['serial']
|
||||
|
||||
|
||||
class CableBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Cable.objects.all(),
|
||||
@ -823,6 +872,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', 'position', 'description')
|
||||
|
||||
|
||||
class DeviceBayTemplateBulkEditForm(BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=DeviceBayTemplate.objects.all(),
|
||||
@ -1076,6 +1142,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', 'position', 'description']
|
||||
|
||||
|
||||
class DeviceBayBulkEditForm(
|
||||
form_from_model(DeviceBay, ['label', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
|
@ -26,6 +26,8 @@ __all__ = (
|
||||
'InventoryItemCSVForm',
|
||||
'LocationCSVForm',
|
||||
'ManufacturerCSVForm',
|
||||
'ModuleCSVForm',
|
||||
'ModuleBayCSVForm',
|
||||
'PlatformCSVForm',
|
||||
'PowerFeedCSVForm',
|
||||
'PowerOutletCSVForm',
|
||||
@ -399,6 +401,35 @@ class DeviceCSVForm(BaseDeviceCSVForm):
|
||||
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
||||
|
||||
|
||||
class ModuleCSVForm(CustomFieldModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
)
|
||||
module_bay = CSVModelChoiceField(
|
||||
queryset=ModuleBay.objects.all(),
|
||||
to_field_name='name'
|
||||
)
|
||||
module_type = CSVModelChoiceField(
|
||||
queryset=ModuleType.objects.all(),
|
||||
to_field_name='model'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Module
|
||||
fields = (
|
||||
'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'comments',
|
||||
)
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
super().__init__(data, *args, **kwargs)
|
||||
|
||||
if data:
|
||||
# Limit module_bay queryset by assigned device
|
||||
params = {f"device__{self.fields['device'].to_field_name}": data.get('device')}
|
||||
self.fields['module_bay'].queryset = self.fields['module_bay'].queryset.filter(**params)
|
||||
|
||||
|
||||
class ChildDeviceCSVForm(BaseDeviceCSVForm):
|
||||
parent = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
@ -678,6 +709,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', 'position', 'description')
|
||||
|
||||
|
||||
class DeviceBayCSVForm(CustomFieldModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
|
@ -29,6 +29,10 @@ __all__ = (
|
||||
'InventoryItemFilterForm',
|
||||
'LocationFilterForm',
|
||||
'ManufacturerFilterForm',
|
||||
'ModuleFilterForm',
|
||||
'ModuleFilterForm',
|
||||
'ModuleBayFilterForm',
|
||||
'ModuleTypeFilterForm',
|
||||
'PlatformFilterForm',
|
||||
'PowerConnectionFilterForm',
|
||||
'PowerFeedFilterForm',
|
||||
@ -336,7 +340,7 @@ class DeviceTypeFilterForm(CustomFieldModelFilterForm):
|
||||
model = DeviceType
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['manufacturer_id', 'subdevice_role', 'airflow'],
|
||||
['manufacturer_id', 'part_number', 'subdevice_role', 'airflow'],
|
||||
['console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports'],
|
||||
]
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
@ -345,6 +349,9 @@ class DeviceTypeFilterForm(CustomFieldModelFilterForm):
|
||||
label=_('Manufacturer'),
|
||||
fetch_trigger='open'
|
||||
)
|
||||
part_number = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
subdevice_role = forms.MultipleChoiceField(
|
||||
choices=add_blank_choice(SubdeviceRoleChoices),
|
||||
required=False,
|
||||
@ -400,6 +407,67 @@ class DeviceTypeFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ModuleTypeFilterForm(CustomFieldModelFilterForm):
|
||||
model = ModuleType
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['manufacturer_id', 'part_number'],
|
||||
['console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports'],
|
||||
]
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False,
|
||||
label=_('Manufacturer'),
|
||||
fetch_trigger='open'
|
||||
)
|
||||
part_number = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
console_ports = forms.NullBooleanField(
|
||||
required=False,
|
||||
label='Has console ports',
|
||||
widget=StaticSelect(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
console_server_ports = forms.NullBooleanField(
|
||||
required=False,
|
||||
label='Has console server ports',
|
||||
widget=StaticSelect(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
power_ports = forms.NullBooleanField(
|
||||
required=False,
|
||||
label='Has power ports',
|
||||
widget=StaticSelect(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
power_outlets = forms.NullBooleanField(
|
||||
required=False,
|
||||
label='Has power outlets',
|
||||
widget=StaticSelect(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
interfaces = forms.NullBooleanField(
|
||||
required=False,
|
||||
label='Has interfaces',
|
||||
widget=StaticSelect(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
pass_through_ports = forms.NullBooleanField(
|
||||
required=False,
|
||||
label='Has pass-through ports',
|
||||
widget=StaticSelect(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class DeviceRoleFilterForm(CustomFieldModelFilterForm):
|
||||
model = DeviceRole
|
||||
tag = TagFilterField(model)
|
||||
@ -579,6 +647,37 @@ class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFi
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
model = Module
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['manufacturer_id', 'module_type_id'],
|
||||
['serial', 'asset_tag'],
|
||||
]
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False,
|
||||
label=_('Manufacturer'),
|
||||
fetch_trigger='open'
|
||||
)
|
||||
module_type_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ModuleType.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'manufacturer_id': '$manufacturer_id'
|
||||
},
|
||||
label=_('Type'),
|
||||
fetch_trigger='open'
|
||||
)
|
||||
serial = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
asset_tag = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VirtualChassisFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
model = VirtualChassis
|
||||
field_groups = [
|
||||
@ -970,6 +1069,19 @@ class RearPortFilterForm(DeviceComponentFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||
model = ModuleBay
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label', 'position'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
tag = TagFilterField(model)
|
||||
position = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
|
||||
|
||||
class DeviceBayFilterForm(DeviceComponentFilterForm):
|
||||
model = DeviceBay
|
||||
field_groups = [
|
||||
|
@ -39,6 +39,10 @@ __all__ = (
|
||||
'InventoryItemForm',
|
||||
'LocationForm',
|
||||
'ManufacturerForm',
|
||||
'ModuleForm',
|
||||
'ModuleBayForm',
|
||||
'ModuleBayTemplateForm',
|
||||
'ModuleTypeForm',
|
||||
'PlatformForm',
|
||||
'PopulateDeviceBayForm',
|
||||
'PowerFeedForm',
|
||||
@ -412,6 +416,23 @@ class DeviceTypeForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ModuleTypeForm(CustomFieldModelForm):
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
queryset=Manufacturer.objects.all()
|
||||
)
|
||||
comments = CommentField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ModuleType
|
||||
fields = [
|
||||
'manufacturer', 'model', 'part_number', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class DeviceRoleForm(CustomFieldModelForm):
|
||||
slug = SlugField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
@ -631,6 +652,46 @@ class DeviceForm(TenancyForm, CustomFieldModelForm):
|
||||
self.fields['position'].widget.choices = [(position, f'U{position}')]
|
||||
|
||||
|
||||
class ModuleForm(CustomFieldModelForm):
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'modulebays': '$module_bay'
|
||||
}
|
||||
)
|
||||
module_bay = DynamicModelChoiceField(
|
||||
queryset=ModuleBay.objects.all(),
|
||||
query_params={
|
||||
'device_id': '$device'
|
||||
}
|
||||
)
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'device_types': '$device_type'
|
||||
}
|
||||
)
|
||||
module_type = DynamicModelChoiceField(
|
||||
queryset=ModuleType.objects.all(),
|
||||
query_params={
|
||||
'manufacturer_id': '$manufacturer'
|
||||
}
|
||||
)
|
||||
comments = CommentField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Module
|
||||
fields = [
|
||||
'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'tags', 'comments',
|
||||
]
|
||||
|
||||
|
||||
class CableForm(TenancyForm, CustomFieldModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -890,10 +951,11 @@ class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = ConsolePortTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
||||
]
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
'module_type': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
@ -901,10 +963,11 @@ class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = ConsoleServerPortTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
||||
]
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
'module_type': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
@ -912,10 +975,11 @@ class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = PowerPortTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||
]
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
'module_type': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
@ -923,19 +987,21 @@ class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = PowerOutletTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
]
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
'module_type': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Limit power_port choices to current DeviceType
|
||||
if hasattr(self.instance, 'device_type'):
|
||||
# Limit power_port choices to current DeviceType/ModuleType
|
||||
if self.instance.pk:
|
||||
self.fields['power_port'].queryset = PowerPortTemplate.objects.filter(
|
||||
device_type=self.instance.device_type
|
||||
device_type=self.instance.device_type,
|
||||
module_type=self.instance.module_type
|
||||
)
|
||||
|
||||
|
||||
@ -943,10 +1009,11 @@ class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = InterfaceTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'mgmt_only', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description',
|
||||
]
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
'module_type': forms.HiddenInput(),
|
||||
'type': StaticSelect(),
|
||||
}
|
||||
|
||||
@ -955,20 +1022,23 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = FrontPortTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
|
||||
'description',
|
||||
]
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
'module_type': forms.HiddenInput(),
|
||||
'rear_port': StaticSelect(),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Limit rear_port choices to current DeviceType
|
||||
if hasattr(self.instance, 'device_type'):
|
||||
# Limit rear_port choices to current DeviceType/ModuleType
|
||||
if self.instance.pk:
|
||||
self.fields['rear_port'].queryset = RearPortTemplate.objects.filter(
|
||||
device_type=self.instance.device_type
|
||||
device_type=self.instance.device_type,
|
||||
module_type=self.instance.module_type
|
||||
)
|
||||
|
||||
|
||||
@ -976,14 +1046,26 @@ class RearPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = RearPortTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'color', 'positions', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description',
|
||||
]
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
'module_type': forms.HiddenInput(),
|
||||
'type': StaticSelect(),
|
||||
}
|
||||
|
||||
|
||||
class ModuleBayTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = ModuleBayTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'position', 'description',
|
||||
]
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = DeviceBayTemplate
|
||||
@ -1222,6 +1304,22 @@ class RearPortForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ModuleBayForm(CustomFieldModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ModuleBay
|
||||
fields = [
|
||||
'device', 'name', 'label', 'position', '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',
|
||||
@ -150,11 +152,13 @@ class ComponentTemplateCreateForm(ComponentForm):
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'device_types': 'device_type'
|
||||
'device_types': 'device_type',
|
||||
'module_types': 'module_type',
|
||||
}
|
||||
)
|
||||
device_type = DynamicModelChoiceField(
|
||||
queryset=DeviceType.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'manufacturer_id': '$manufacturer'
|
||||
}
|
||||
@ -164,23 +168,37 @@ class ComponentTemplateCreateForm(ComponentForm):
|
||||
)
|
||||
|
||||
|
||||
class ConsolePortTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
class ModularComponentTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
module_type = DynamicModelChoiceField(
|
||||
queryset=ModuleType.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'manufacturer_id': '$manufacturer'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ConsolePortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
||||
type = forms.ChoiceField(
|
||||
choices=add_blank_choice(ConsolePortTypeChoices),
|
||||
widget=StaticSelect()
|
||||
)
|
||||
field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'description')
|
||||
field_order = (
|
||||
'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'description',
|
||||
)
|
||||
|
||||
|
||||
class ConsoleServerPortTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
class ConsoleServerPortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
||||
type = forms.ChoiceField(
|
||||
choices=add_blank_choice(ConsolePortTypeChoices),
|
||||
widget=StaticSelect()
|
||||
)
|
||||
field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'description')
|
||||
field_order = (
|
||||
'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'description',
|
||||
)
|
||||
|
||||
|
||||
class PowerPortTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
class PowerPortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
||||
type = forms.ChoiceField(
|
||||
choices=add_blank_choice(PowerPortTypeChoices),
|
||||
required=False
|
||||
@ -196,19 +214,23 @@ class PowerPortTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
help_text="Allocated power draw (watts)"
|
||||
)
|
||||
field_order = (
|
||||
'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'maximum_draw', 'allocated_draw',
|
||||
'description',
|
||||
'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'maximum_draw',
|
||||
'allocated_draw', 'description',
|
||||
)
|
||||
|
||||
|
||||
class PowerOutletTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
class PowerOutletTemplateCreateForm(ModularComponentTemplateCreateForm):
|
||||
type = forms.ChoiceField(
|
||||
choices=add_blank_choice(PowerOutletTypeChoices),
|
||||
required=False
|
||||
)
|
||||
power_port = forms.ModelChoiceField(
|
||||
power_port = DynamicModelChoiceField(
|
||||
queryset=PowerPortTemplate.objects.all(),
|
||||
required=False
|
||||
required=False,
|
||||
query_params={
|
||||
'devicetype_id': '$device_type',
|
||||
'moduletype_id': '$module_type',
|
||||
}
|
||||
)
|
||||
feed_leg = forms.ChoiceField(
|
||||
choices=add_blank_choice(PowerOutletFeedLegChoices),
|
||||
@ -216,23 +238,12 @@ class PowerOutletTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
widget=StaticSelect()
|
||||
)
|
||||
field_order = (
|
||||
'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'power_port', 'feed_leg',
|
||||
'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'power_port', 'feed_leg',
|
||||
'description',
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Limit power_port choices to current DeviceType
|
||||
device_type = DeviceType.objects.get(
|
||||
pk=self.initial.get('device_type') or self.data.get('device_type')
|
||||
)
|
||||
self.fields['power_port'].queryset = PowerPortTemplate.objects.filter(
|
||||
device_type=device_type
|
||||
)
|
||||
|
||||
|
||||
class InterfaceTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
class InterfaceTemplateCreateForm(ModularComponentTemplateCreateForm):
|
||||
type = forms.ChoiceField(
|
||||
choices=InterfaceTypeChoices,
|
||||
widget=StaticSelect()
|
||||
@ -241,10 +252,13 @@ class InterfaceTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
required=False,
|
||||
label='Management only'
|
||||
)
|
||||
field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'mgmt_only', 'description')
|
||||
field_order = (
|
||||
'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'mgmt_only',
|
||||
'description',
|
||||
)
|
||||
|
||||
|
||||
class FrontPortTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
||||
type = forms.ChoiceField(
|
||||
choices=PortTypeChoices,
|
||||
widget=StaticSelect()
|
||||
@ -258,7 +272,8 @@ class FrontPortTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
help_text='Select one rear port assignment for each front port being created.',
|
||||
)
|
||||
field_order = (
|
||||
'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'color', 'rear_port_set', 'description',
|
||||
'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'color', 'rear_port_set',
|
||||
'description',
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -308,7 +323,7 @@ class FrontPortTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
}
|
||||
|
||||
|
||||
class RearPortTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
class RearPortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
||||
type = forms.ChoiceField(
|
||||
choices=PortTypeChoices,
|
||||
widget=StaticSelect(),
|
||||
@ -323,10 +338,16 @@ class RearPortTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
help_text='The number of front ports which may be mapped to each rear port'
|
||||
)
|
||||
field_order = (
|
||||
'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'color', 'positions', 'description',
|
||||
'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'color', 'positions',
|
||||
'description',
|
||||
)
|
||||
|
||||
|
||||
class ModuleBayTemplateCreateForm(ComponentTemplateCreateForm):
|
||||
# TODO: Support patterned position assignment
|
||||
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 +640,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,8 @@ __all__ = (
|
||||
'DeviceTypeImportForm',
|
||||
'FrontPortTemplateImportForm',
|
||||
'InterfaceTemplateImportForm',
|
||||
'ModuleBayTemplateImportForm',
|
||||
'ModuleTypeImportForm',
|
||||
'PowerOutletTemplateImportForm',
|
||||
'PowerPortTemplateImportForm',
|
||||
'RearPortTemplateImportForm',
|
||||
@ -31,29 +33,38 @@ class DeviceTypeImportForm(BootstrapMixin, forms.ModelForm):
|
||||
]
|
||||
|
||||
|
||||
class ModuleTypeImportForm(BootstrapMixin, forms.ModelForm):
|
||||
manufacturer = forms.ModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
to_field_name='name'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ModuleType
|
||||
fields = ['manufacturer', 'model', 'part_number', 'comments']
|
||||
|
||||
|
||||
#
|
||||
# Component template import forms
|
||||
#
|
||||
|
||||
class ComponentTemplateImportForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
def __init__(self, device_type, data=None, *args, **kwargs):
|
||||
|
||||
# Must pass the parent DeviceType on form initialization
|
||||
data.update({
|
||||
'device_type': device_type.pk,
|
||||
})
|
||||
|
||||
super().__init__(data, *args, **kwargs)
|
||||
|
||||
def clean_device_type(self):
|
||||
|
||||
data = self.cleaned_data['device_type']
|
||||
|
||||
# Limit fields referencing other components to the parent DeviceType
|
||||
for field_name, field in self.fields.items():
|
||||
if isinstance(field, forms.ModelChoiceField) and field_name != 'device_type':
|
||||
field.queryset = field.queryset.filter(device_type=data)
|
||||
if data := self.cleaned_data['device_type']:
|
||||
for field_name, field in self.fields.items():
|
||||
if isinstance(field, forms.ModelChoiceField) and field_name not in ['device_type', 'module_type']:
|
||||
field.queryset = field.queryset.filter(device_type=data)
|
||||
|
||||
return data
|
||||
|
||||
def clean_module_type(self):
|
||||
# Limit fields referencing other components to the parent ModuleType
|
||||
if data := self.cleaned_data['module_type']:
|
||||
for field_name, field in self.fields.items():
|
||||
if isinstance(field, forms.ModelChoiceField) and field_name not in ['device_type', 'module_type']:
|
||||
field.queryset = field.queryset.filter(module_type=data)
|
||||
|
||||
return data
|
||||
|
||||
@ -63,7 +74,7 @@ class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
|
||||
class Meta:
|
||||
model = ConsolePortTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
||||
]
|
||||
|
||||
|
||||
@ -72,7 +83,7 @@ class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm):
|
||||
class Meta:
|
||||
model = ConsoleServerPortTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
||||
]
|
||||
|
||||
|
||||
@ -81,7 +92,7 @@ class PowerPortTemplateImportForm(ComponentTemplateImportForm):
|
||||
class Meta:
|
||||
model = PowerPortTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||
]
|
||||
|
||||
|
||||
@ -95,7 +106,7 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
|
||||
class Meta:
|
||||
model = PowerOutletTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
]
|
||||
|
||||
|
||||
@ -107,7 +118,7 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
|
||||
class Meta:
|
||||
model = InterfaceTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'type', 'mgmt_only', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description',
|
||||
]
|
||||
|
||||
|
||||
@ -123,7 +134,7 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm):
|
||||
class Meta:
|
||||
model = FrontPortTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'type', 'rear_port', 'rear_port_position', 'label', 'description',
|
||||
'device_type', 'module_type', 'name', 'type', 'rear_port', 'rear_port_position', 'label', 'description',
|
||||
]
|
||||
|
||||
|
||||
@ -135,7 +146,16 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm):
|
||||
class Meta:
|
||||
model = RearPortTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'type', 'positions', 'label', 'description',
|
||||
'device_type', 'module_type', 'name', 'type', 'positions', 'label', 'description',
|
||||
]
|
||||
|
||||
|
||||
class ModuleBayTemplateImportForm(ComponentTemplateImportForm):
|
||||
|
||||
class Meta:
|
||||
model = ModuleBayTemplate
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'position', 'description',
|
||||
]
|
||||
|
||||
|
||||
|
@ -56,6 +56,18 @@ class DCIMQuery(graphene.ObjectType):
|
||||
manufacturer = ObjectField(ManufacturerType)
|
||||
manufacturer_list = ObjectListField(ManufacturerType)
|
||||
|
||||
module = ObjectField(ModuleType)
|
||||
module_list = ObjectListField(ModuleType)
|
||||
|
||||
module_bay = ObjectField(ModuleBayType)
|
||||
module_bay_list = ObjectListField(ModuleBayType)
|
||||
|
||||
module_bay_template = ObjectField(ModuleBayTemplateType)
|
||||
module_bay_template_list = ObjectListField(ModuleBayTemplateType)
|
||||
|
||||
module_type = ObjectField(ModuleTypeType)
|
||||
module_type_list = ObjectListField(ModuleTypeType)
|
||||
|
||||
platform = ObjectField(PlatformType)
|
||||
platform_list = ObjectListField(PlatformType)
|
||||
|
||||
|
@ -27,6 +27,10 @@ __all__ = (
|
||||
'InventoryItemType',
|
||||
'LocationType',
|
||||
'ManufacturerType',
|
||||
'ModuleType',
|
||||
'ModuleBayType',
|
||||
'ModuleBayTemplateType',
|
||||
'ModuleTypeType',
|
||||
'PlatformType',
|
||||
'PowerFeedType',
|
||||
'PowerOutletType',
|
||||
@ -254,6 +258,38 @@ class ManufacturerType(OrganizationalObjectType):
|
||||
filterset_class = filtersets.ManufacturerFilterSet
|
||||
|
||||
|
||||
class ModuleType(ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Module
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ModuleFilterSet
|
||||
|
||||
|
||||
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 ModuleTypeType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ModuleType
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ModuleTypeFilterSet
|
||||
|
||||
|
||||
class PlatformType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
|
254
netbox/dcim/migrations/0145_modules.py
Normal file
254
netbox/dcim/migrations/0145_modules.py
Normal file
@ -0,0 +1,254 @@
|
||||
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.AlterModelOptions(
|
||||
name='consoleporttemplate',
|
||||
options={'ordering': ('device_type', 'module_type', '_name')},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='consoleserverporttemplate',
|
||||
options={'ordering': ('device_type', 'module_type', '_name')},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='frontporttemplate',
|
||||
options={'ordering': ('device_type', 'module_type', '_name')},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='interfacetemplate',
|
||||
options={'ordering': ('device_type', 'module_type', '_name')},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='poweroutlettemplate',
|
||||
options={'ordering': ('device_type', 'module_type', '_name')},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='powerporttemplate',
|
||||
options={'ordering': ('device_type', 'module_type', '_name')},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='rearporttemplate',
|
||||
options={'ordering': ('device_type', 'module_type', '_name')},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='consoleporttemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='consoleporttemplates', to='dcim.devicetype'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='consoleserverporttemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverporttemplates', to='dcim.devicetype'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='frontporttemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='frontporttemplates', to='dcim.devicetype'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfacetemplates', to='dcim.devicetype'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='poweroutlettemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlettemplates', to='dcim.devicetype'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powerporttemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='powerporttemplates', to='dcim.devicetype'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rearporttemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='rearporttemplates', to='dcim.devicetype'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ModuleType',
|
||||
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)),
|
||||
('model', models.CharField(max_length=100)),
|
||||
('part_number', models.CharField(blank=True, max_length=50)),
|
||||
('comments', models.TextField(blank=True)),
|
||||
('manufacturer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='module_types', to='dcim.manufacturer')),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('manufacturer', 'model'),
|
||||
'unique_together': {('manufacturer', 'model')},
|
||||
},
|
||||
),
|
||||
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)),
|
||||
('position', models.CharField(blank=True, max_length=30)),
|
||||
('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')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Module',
|
||||
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)),
|
||||
('local_context_data', models.JSONField(blank=True, null=True)),
|
||||
('serial', models.CharField(blank=True, max_length=50)),
|
||||
('asset_tag', models.CharField(blank=True, max_length=50, null=True, unique=True)),
|
||||
('comments', models.TextField(blank=True)),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modules', to='dcim.device')),
|
||||
('module_bay', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='installed_module', to='dcim.modulebay')),
|
||||
('module_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='instances', to='dcim.moduletype')),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('module_bay',),
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleport',
|
||||
name='module',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='consoleports', to='dcim.module'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleporttemplate',
|
||||
name='module_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='consoleporttemplates', to='dcim.moduletype'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverport',
|
||||
name='module',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverports', to='dcim.module'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverporttemplate',
|
||||
name='module_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverporttemplates', to='dcim.moduletype'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='frontport',
|
||||
name='module',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='frontports', to='dcim.module'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='frontporttemplate',
|
||||
name='module_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='frontporttemplates', to='dcim.moduletype'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='module',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='dcim.module'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interfacetemplate',
|
||||
name='module_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfacetemplates', to='dcim.moduletype'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlet',
|
||||
name='module',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlets', to='dcim.module'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlettemplate',
|
||||
name='module_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlettemplates', to='dcim.moduletype'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerport',
|
||||
name='module',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='powerports', to='dcim.module'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerporttemplate',
|
||||
name='module_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='powerporttemplates', to='dcim.moduletype'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rearport',
|
||||
name='module',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='rearports', to='dcim.module'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rearporttemplate',
|
||||
name='module_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='rearporttemplates', to='dcim.moduletype'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='consoleporttemplate',
|
||||
unique_together={('device_type', 'name'), ('module_type', 'name')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='consoleserverporttemplate',
|
||||
unique_together={('device_type', 'name'), ('module_type', 'name')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='frontporttemplate',
|
||||
unique_together={('device_type', 'name'), ('rear_port', 'rear_port_position'), ('module_type', 'name')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='interfacetemplate',
|
||||
unique_together={('device_type', 'name'), ('module_type', 'name')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='poweroutlettemplate',
|
||||
unique_together={('device_type', 'name'), ('module_type', 'name')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='powerporttemplate',
|
||||
unique_together={('device_type', 'name'), ('module_type', 'name')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='rearporttemplate',
|
||||
unique_together={('device_type', 'name'), ('module_type', 'name')},
|
||||
),
|
||||
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)),
|
||||
('position', models.CharField(blank=True, max_length=30)),
|
||||
('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')},
|
||||
},
|
||||
),
|
||||
]
|
@ -27,6 +27,10 @@ __all__ = (
|
||||
'InventoryItem',
|
||||
'Location',
|
||||
'Manufacturer',
|
||||
'Module',
|
||||
'ModuleBay',
|
||||
'ModuleBayTemplate',
|
||||
'ModuleType',
|
||||
'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',
|
||||
@ -63,7 +64,7 @@ class ComponentTemplateModel(ChangeLoggedModel):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def to_objectchange(self, action):
|
||||
def to_objectchange(self, action, related_object=None):
|
||||
# Annotate the parent DeviceType
|
||||
try:
|
||||
device_type = self.device_type
|
||||
@ -73,8 +74,63 @@ class ComponentTemplateModel(ChangeLoggedModel):
|
||||
return super().to_objectchange(action, related_object=device_type)
|
||||
|
||||
|
||||
class ModularComponentTemplateModel(ComponentTemplateModel):
|
||||
"""
|
||||
A ComponentTemplateModel which supports optional assignment to a ModuleType.
|
||||
"""
|
||||
device_type = models.ForeignKey(
|
||||
to='dcim.DeviceType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='%(class)ss',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
module_type = models.ForeignKey(
|
||||
to='dcim.ModuleType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='%(class)ss',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def to_objectchange(self, action, related_object=None):
|
||||
# Annotate the parent DeviceType or ModuleType
|
||||
try:
|
||||
if getattr(self, 'device_type'):
|
||||
return super().to_objectchange(action, related_object=self.device_type)
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
try:
|
||||
if getattr(self, 'module_type'):
|
||||
return super().to_objectchange(action, related_object=self.module_type)
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
return super().to_objectchange(action)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
# A component template must belong to a DeviceType *or* to a ModuleType
|
||||
if self.device_type and self.module_type:
|
||||
raise ValidationError(
|
||||
"A component template cannot be associated with both a device type and a module type."
|
||||
)
|
||||
if not self.device_type and not self.module_type:
|
||||
raise ValidationError(
|
||||
"A component template must be associated with either a device type or a module type."
|
||||
)
|
||||
|
||||
def resolve_name(self, module):
|
||||
if module:
|
||||
return self.name.replace('{module}', module.module_bay.position)
|
||||
return self.name
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class ConsolePortTemplate(ComponentTemplateModel):
|
||||
class ConsolePortTemplate(ModularComponentTemplateModel):
|
||||
"""
|
||||
A template for a ConsolePort to be created for a new Device.
|
||||
"""
|
||||
@ -85,20 +141,23 @@ class ConsolePortTemplate(ComponentTemplateModel):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', '_name')
|
||||
unique_together = ('device_type', 'name')
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
('device_type', 'name'),
|
||||
('module_type', 'name'),
|
||||
)
|
||||
|
||||
def instantiate(self, device):
|
||||
def instantiate(self, **kwargs):
|
||||
return ConsolePort(
|
||||
device=device,
|
||||
name=self.name,
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type
|
||||
type=self.type,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class ConsoleServerPortTemplate(ComponentTemplateModel):
|
||||
class ConsoleServerPortTemplate(ModularComponentTemplateModel):
|
||||
"""
|
||||
A template for a ConsoleServerPort to be created for a new Device.
|
||||
"""
|
||||
@ -109,20 +168,23 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', '_name')
|
||||
unique_together = ('device_type', 'name')
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
('device_type', 'name'),
|
||||
('module_type', 'name'),
|
||||
)
|
||||
|
||||
def instantiate(self, device):
|
||||
def instantiate(self, **kwargs):
|
||||
return ConsoleServerPort(
|
||||
device=device,
|
||||
name=self.name,
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type
|
||||
type=self.type,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class PowerPortTemplate(ComponentTemplateModel):
|
||||
class PowerPortTemplate(ModularComponentTemplateModel):
|
||||
"""
|
||||
A template for a PowerPort to be created for a new Device.
|
||||
"""
|
||||
@ -145,17 +207,20 @@ class PowerPortTemplate(ComponentTemplateModel):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', '_name')
|
||||
unique_together = ('device_type', 'name')
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
('device_type', 'name'),
|
||||
('module_type', 'name'),
|
||||
)
|
||||
|
||||
def instantiate(self, device):
|
||||
def instantiate(self, **kwargs):
|
||||
return PowerPort(
|
||||
device=device,
|
||||
name=self.name,
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
maximum_draw=self.maximum_draw,
|
||||
allocated_draw=self.allocated_draw
|
||||
allocated_draw=self.allocated_draw,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
@ -169,7 +234,7 @@ class PowerPortTemplate(ComponentTemplateModel):
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class PowerOutletTemplate(ComponentTemplateModel):
|
||||
class PowerOutletTemplate(ModularComponentTemplateModel):
|
||||
"""
|
||||
A template for a PowerOutlet to be created for a new Device.
|
||||
"""
|
||||
@ -193,35 +258,43 @@ class PowerOutletTemplate(ComponentTemplateModel):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', '_name')
|
||||
unique_together = ('device_type', 'name')
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
('device_type', 'name'),
|
||||
('module_type', 'name'),
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
# Validate power port assignment
|
||||
if self.power_port and self.power_port.device_type != self.device_type:
|
||||
raise ValidationError(
|
||||
"Parent power port ({}) must belong to the same device type".format(self.power_port)
|
||||
)
|
||||
|
||||
def instantiate(self, device):
|
||||
if self.power_port:
|
||||
power_port = PowerPort.objects.get(device=device, name=self.power_port.name)
|
||||
if self.device_type and self.power_port.device_type != self.device_type:
|
||||
raise ValidationError(
|
||||
f"Parent power port ({self.power_port}) must belong to the same device type"
|
||||
)
|
||||
if self.module_type and self.power_port.module_type != self.module_type:
|
||||
raise ValidationError(
|
||||
f"Parent power port ({self.power_port}) must belong to the same module type"
|
||||
)
|
||||
|
||||
def instantiate(self, **kwargs):
|
||||
if self.power_port:
|
||||
power_port = PowerPort.objects.get(name=self.power_port.name, **kwargs)
|
||||
else:
|
||||
power_port = None
|
||||
return PowerOutlet(
|
||||
device=device,
|
||||
name=self.name,
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
power_port=power_port,
|
||||
feed_leg=self.feed_leg
|
||||
feed_leg=self.feed_leg,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class InterfaceTemplate(ComponentTemplateModel):
|
||||
class InterfaceTemplate(ModularComponentTemplateModel):
|
||||
"""
|
||||
A template for a physical data interface on a new Device.
|
||||
"""
|
||||
@ -242,21 +315,24 @@ class InterfaceTemplate(ComponentTemplateModel):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', '_name')
|
||||
unique_together = ('device_type', 'name')
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
('device_type', 'name'),
|
||||
('module_type', 'name'),
|
||||
)
|
||||
|
||||
def instantiate(self, device):
|
||||
def instantiate(self, **kwargs):
|
||||
return Interface(
|
||||
device=device,
|
||||
name=self.name,
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
mgmt_only=self.mgmt_only
|
||||
mgmt_only=self.mgmt_only,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class FrontPortTemplate(ComponentTemplateModel):
|
||||
class FrontPortTemplate(ModularComponentTemplateModel):
|
||||
"""
|
||||
Template for a pass-through port on the front of a new Device.
|
||||
"""
|
||||
@ -281,9 +357,10 @@ class FrontPortTemplate(ComponentTemplateModel):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', '_name')
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
('device_type', 'name'),
|
||||
('module_type', 'name'),
|
||||
('rear_port', 'rear_port_position'),
|
||||
)
|
||||
|
||||
@ -309,24 +386,24 @@ class FrontPortTemplate(ComponentTemplateModel):
|
||||
except RearPortTemplate.DoesNotExist:
|
||||
pass
|
||||
|
||||
def instantiate(self, device):
|
||||
def instantiate(self, **kwargs):
|
||||
if self.rear_port:
|
||||
rear_port = RearPort.objects.get(device=device, name=self.rear_port.name)
|
||||
rear_port = RearPort.objects.get(name=self.rear_port.name, **kwargs)
|
||||
else:
|
||||
rear_port = None
|
||||
return FrontPort(
|
||||
device=device,
|
||||
name=self.name,
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
color=self.color,
|
||||
rear_port=rear_port,
|
||||
rear_port_position=self.rear_port_position
|
||||
rear_port_position=self.rear_port_position,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class RearPortTemplate(ComponentTemplateModel):
|
||||
class RearPortTemplate(ModularComponentTemplateModel):
|
||||
"""
|
||||
Template for a pass-through port on the rear of a new Device.
|
||||
"""
|
||||
@ -345,18 +422,45 @@ class RearPortTemplate(ComponentTemplateModel):
|
||||
]
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
('device_type', 'name'),
|
||||
('module_type', 'name'),
|
||||
)
|
||||
|
||||
def instantiate(self, **kwargs):
|
||||
return RearPort(
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
color=self.color,
|
||||
positions=self.positions,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class ModuleBayTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a ModuleBay to be created for a new parent Device.
|
||||
"""
|
||||
position = models.CharField(
|
||||
max_length=30,
|
||||
blank=True,
|
||||
help_text='Identifier to reference when renaming installed components'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', '_name')
|
||||
unique_together = ('device_type', 'name')
|
||||
|
||||
def instantiate(self, device):
|
||||
return RearPort(
|
||||
return ModuleBay(
|
||||
device=device,
|
||||
name=self.name,
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
color=self.color,
|
||||
positions=self.positions
|
||||
position=self.position
|
||||
)
|
||||
|
||||
|
||||
|
@ -30,6 +30,7 @@ __all__ = (
|
||||
'FrontPort',
|
||||
'Interface',
|
||||
'InventoryItem',
|
||||
'ModuleBay',
|
||||
'PathEndpoint',
|
||||
'PowerOutlet',
|
||||
'PowerPort',
|
||||
@ -86,6 +87,19 @@ class ComponentModel(PrimaryModel):
|
||||
return self.device
|
||||
|
||||
|
||||
class ModularComponentModel(ComponentModel):
|
||||
module = models.ForeignKey(
|
||||
to='dcim.Module',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='%(class)ss',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class LinkTermination(models.Model):
|
||||
"""
|
||||
An abstract model inherited by all models to which a Cable, WirelessLink, or other such link can terminate. Examples
|
||||
@ -229,11 +243,11 @@ class PathEndpoint(models.Model):
|
||||
|
||||
|
||||
#
|
||||
# Console ports
|
||||
# Console components
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class ConsolePort(ComponentModel, LinkTermination, PathEndpoint):
|
||||
class ConsolePort(ModularComponentModel, LinkTermination, PathEndpoint):
|
||||
"""
|
||||
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
|
||||
"""
|
||||
@ -260,12 +274,8 @@ 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):
|
||||
class ConsoleServerPort(ModularComponentModel, LinkTermination, PathEndpoint):
|
||||
"""
|
||||
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
|
||||
"""
|
||||
@ -293,11 +303,11 @@ class ConsoleServerPort(ComponentModel, LinkTermination, PathEndpoint):
|
||||
|
||||
|
||||
#
|
||||
# Power ports
|
||||
# Power components
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class PowerPort(ComponentModel, LinkTermination, PathEndpoint):
|
||||
class PowerPort(ModularComponentModel, LinkTermination, PathEndpoint):
|
||||
"""
|
||||
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
|
||||
"""
|
||||
@ -389,12 +399,8 @@ class PowerPort(ComponentModel, LinkTermination, PathEndpoint):
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Power outlets
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class PowerOutlet(ComponentModel, LinkTermination, PathEndpoint):
|
||||
class PowerOutlet(ModularComponentModel, LinkTermination, PathEndpoint):
|
||||
"""
|
||||
A physical power outlet (output) within a Device which provides power to a PowerPort.
|
||||
"""
|
||||
@ -509,7 +515,7 @@ class BaseInterface(models.Model):
|
||||
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class Interface(ComponentModel, BaseInterface, LinkTermination, PathEndpoint):
|
||||
class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpoint):
|
||||
"""
|
||||
A network interface within a Device. A physical Interface can connect to exactly one other Interface.
|
||||
"""
|
||||
@ -772,7 +778,7 @@ class Interface(ComponentModel, BaseInterface, LinkTermination, PathEndpoint):
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class FrontPort(ComponentModel, LinkTermination):
|
||||
class FrontPort(ModularComponentModel, LinkTermination):
|
||||
"""
|
||||
A pass-through port on the front of a Device.
|
||||
"""
|
||||
@ -826,7 +832,7 @@ class FrontPort(ComponentModel, LinkTermination):
|
||||
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class RearPort(ComponentModel, LinkTermination):
|
||||
class RearPort(ModularComponentModel, LinkTermination):
|
||||
"""
|
||||
A pass-through port on the rear of a Device.
|
||||
"""
|
||||
@ -866,9 +872,30 @@ 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
|
||||
"""
|
||||
position = models.CharField(
|
||||
max_length=30,
|
||||
blank=True,
|
||||
help_text='Identifier to reference when renaming installed components'
|
||||
)
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -26,6 +26,8 @@ __all__ = (
|
||||
'DeviceRole',
|
||||
'DeviceType',
|
||||
'Manufacturer',
|
||||
'Module',
|
||||
'ModuleType',
|
||||
'Platform',
|
||||
'VirtualChassis',
|
||||
)
|
||||
@ -253,6 +255,15 @@ class DeviceType(PrimaryModel):
|
||||
}
|
||||
for c in self.rearporttemplates.all()
|
||||
]
|
||||
if self.modulebaytemplates.exists():
|
||||
data['module-bays'] = [
|
||||
{
|
||||
'name': c.name,
|
||||
'label': c.label,
|
||||
'description': c.description,
|
||||
}
|
||||
for c in self.modulebaytemplates.all()
|
||||
]
|
||||
if self.devicebaytemplates.exists():
|
||||
data['device-bays'] = [
|
||||
{
|
||||
@ -342,6 +353,136 @@ class DeviceType(PrimaryModel):
|
||||
return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD
|
||||
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class ModuleType(PrimaryModel):
|
||||
"""
|
||||
A ModuleType represents a hardware element that can be installed within a device and which houses additional
|
||||
components; for example, a line card within a chassis-based switch such as the Cisco Catalyst 6500. Like a
|
||||
DeviceType, each ModuleType can have console, power, interface, and pass-through port templates assigned to it. It
|
||||
cannot, however house device bays or module bays.
|
||||
"""
|
||||
manufacturer = models.ForeignKey(
|
||||
to='dcim.Manufacturer',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='module_types'
|
||||
)
|
||||
model = models.CharField(
|
||||
max_length=100
|
||||
)
|
||||
part_number = models.CharField(
|
||||
max_length=50,
|
||||
blank=True,
|
||||
help_text='Discrete part number (optional)'
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
clone_fields = ('manufacturer',)
|
||||
|
||||
class Meta:
|
||||
ordering = ('manufacturer', 'model')
|
||||
unique_together = (
|
||||
('manufacturer', 'model'),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.model
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:moduletype', args=[self.pk])
|
||||
|
||||
def to_yaml(self):
|
||||
data = OrderedDict((
|
||||
('manufacturer', self.manufacturer.name),
|
||||
('model', self.model),
|
||||
('part_number', self.part_number),
|
||||
('comments', self.comments),
|
||||
))
|
||||
|
||||
# Component templates
|
||||
if self.consoleporttemplates.exists():
|
||||
data['console-ports'] = [
|
||||
{
|
||||
'name': c.name,
|
||||
'type': c.type,
|
||||
'label': c.label,
|
||||
'description': c.description,
|
||||
}
|
||||
for c in self.consoleporttemplates.all()
|
||||
]
|
||||
if self.consoleserverporttemplates.exists():
|
||||
data['console-server-ports'] = [
|
||||
{
|
||||
'name': c.name,
|
||||
'type': c.type,
|
||||
'label': c.label,
|
||||
'description': c.description,
|
||||
}
|
||||
for c in self.consoleserverporttemplates.all()
|
||||
]
|
||||
if self.powerporttemplates.exists():
|
||||
data['power-ports'] = [
|
||||
{
|
||||
'name': c.name,
|
||||
'type': c.type,
|
||||
'maximum_draw': c.maximum_draw,
|
||||
'allocated_draw': c.allocated_draw,
|
||||
'label': c.label,
|
||||
'description': c.description,
|
||||
}
|
||||
for c in self.powerporttemplates.all()
|
||||
]
|
||||
if self.poweroutlettemplates.exists():
|
||||
data['power-outlets'] = [
|
||||
{
|
||||
'name': c.name,
|
||||
'type': c.type,
|
||||
'power_port': c.power_port.name if c.power_port else None,
|
||||
'feed_leg': c.feed_leg,
|
||||
'label': c.label,
|
||||
'description': c.description,
|
||||
}
|
||||
for c in self.poweroutlettemplates.all()
|
||||
]
|
||||
if self.interfacetemplates.exists():
|
||||
data['interfaces'] = [
|
||||
{
|
||||
'name': c.name,
|
||||
'type': c.type,
|
||||
'mgmt_only': c.mgmt_only,
|
||||
'label': c.label,
|
||||
'description': c.description,
|
||||
}
|
||||
for c in self.interfacetemplates.all()
|
||||
]
|
||||
if self.frontporttemplates.exists():
|
||||
data['front-ports'] = [
|
||||
{
|
||||
'name': c.name,
|
||||
'type': c.type,
|
||||
'rear_port': c.rear_port.name,
|
||||
'rear_port_position': c.rear_port_position,
|
||||
'label': c.label,
|
||||
'description': c.description,
|
||||
}
|
||||
for c in self.frontporttemplates.all()
|
||||
]
|
||||
if self.rearporttemplates.exists():
|
||||
data['rear-ports'] = [
|
||||
{
|
||||
'name': c.name,
|
||||
'type': c.type,
|
||||
'positions': c.positions,
|
||||
'label': c.label,
|
||||
'description': c.description,
|
||||
}
|
||||
for c in self.rearporttemplates.all()
|
||||
]
|
||||
|
||||
return yaml.dump(dict(data), sort_keys=False)
|
||||
|
||||
|
||||
#
|
||||
# Devices
|
||||
#
|
||||
@ -766,28 +907,31 @@ class Device(PrimaryModel, ConfigContextModel):
|
||||
# If this is a new Device, instantiate all of the related components per the DeviceType definition
|
||||
if is_new:
|
||||
ConsolePort.objects.bulk_create(
|
||||
[x.instantiate(self) for x in self.device_type.consoleporttemplates.all()]
|
||||
[x.instantiate(device=self) for x in self.device_type.consoleporttemplates.all()]
|
||||
)
|
||||
ConsoleServerPort.objects.bulk_create(
|
||||
[x.instantiate(self) for x in self.device_type.consoleserverporttemplates.all()]
|
||||
[x.instantiate(device=self) for x in self.device_type.consoleserverporttemplates.all()]
|
||||
)
|
||||
PowerPort.objects.bulk_create(
|
||||
[x.instantiate(self) for x in self.device_type.powerporttemplates.all()]
|
||||
[x.instantiate(device=self) for x in self.device_type.powerporttemplates.all()]
|
||||
)
|
||||
PowerOutlet.objects.bulk_create(
|
||||
[x.instantiate(self) for x in self.device_type.poweroutlettemplates.all()]
|
||||
[x.instantiate(device=self) for x in self.device_type.poweroutlettemplates.all()]
|
||||
)
|
||||
Interface.objects.bulk_create(
|
||||
[x.instantiate(self) for x in self.device_type.interfacetemplates.all()]
|
||||
[x.instantiate(device=self) for x in self.device_type.interfacetemplates.all()]
|
||||
)
|
||||
RearPort.objects.bulk_create(
|
||||
[x.instantiate(self) for x in self.device_type.rearporttemplates.all()]
|
||||
[x.instantiate(device=self) for x in self.device_type.rearporttemplates.all()]
|
||||
)
|
||||
FrontPort.objects.bulk_create(
|
||||
[x.instantiate(self) for x in self.device_type.frontporttemplates.all()]
|
||||
[x.instantiate(device=self) for x in self.device_type.frontporttemplates.all()]
|
||||
)
|
||||
ModuleBay.objects.bulk_create(
|
||||
[x.instantiate(device=self) for x in self.device_type.modulebaytemplates.all()]
|
||||
)
|
||||
DeviceBay.objects.bulk_create(
|
||||
[x.instantiate(self) for x in self.device_type.devicebaytemplates.all()]
|
||||
[x.instantiate(device=self) for x in self.device_type.devicebaytemplates.all()]
|
||||
)
|
||||
|
||||
# Update Site and Rack assignment for any child Devices
|
||||
@ -865,6 +1009,85 @@ class Device(PrimaryModel, ConfigContextModel):
|
||||
return DeviceStatusChoices.colors.get(self.status, 'secondary')
|
||||
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class Module(PrimaryModel, ConfigContextModel):
|
||||
"""
|
||||
A Module represents a field-installable component within a Device which may itself hold multiple device components
|
||||
(for example, a line card within a chassis switch). Modules are instantiated from ModuleTypes.
|
||||
"""
|
||||
device = models.ForeignKey(
|
||||
to='dcim.Device',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='modules'
|
||||
)
|
||||
module_bay = models.OneToOneField(
|
||||
to='dcim.ModuleBay',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='installed_module'
|
||||
)
|
||||
module_type = models.ForeignKey(
|
||||
to='dcim.ModuleType',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='instances'
|
||||
)
|
||||
serial = models.CharField(
|
||||
max_length=50,
|
||||
blank=True,
|
||||
verbose_name='Serial number'
|
||||
)
|
||||
asset_tag = models.CharField(
|
||||
max_length=50,
|
||||
blank=True,
|
||||
null=True,
|
||||
unique=True,
|
||||
verbose_name='Asset tag',
|
||||
help_text='A unique tag used to identify this device'
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
clone_fields = ('device', 'module_type')
|
||||
|
||||
class Meta:
|
||||
ordering = ('module_bay',)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.module_type)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:module', args=[self.pk])
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
is_new = not bool(self.pk)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# If this is a new Module, instantiate all its related components per the ModuleType definition
|
||||
if is_new:
|
||||
ConsolePort.objects.bulk_create(
|
||||
[x.instantiate(device=self.device, module=self) for x in self.module_type.consoleporttemplates.all()]
|
||||
)
|
||||
ConsoleServerPort.objects.bulk_create(
|
||||
[x.instantiate(device=self.device, module=self) for x in self.module_type.consoleserverporttemplates.all()]
|
||||
)
|
||||
PowerPort.objects.bulk_create(
|
||||
[x.instantiate(device=self.device, module=self) for x in self.module_type.powerporttemplates.all()]
|
||||
)
|
||||
PowerOutlet.objects.bulk_create(
|
||||
[x.instantiate(device=self.device, module=self) for x in self.module_type.poweroutlettemplates.all()]
|
||||
)
|
||||
Interface.objects.bulk_create(
|
||||
[x.instantiate(device=self.device, module=self) for x in self.module_type.interfacetemplates.all()]
|
||||
)
|
||||
RearPort.objects.bulk_create(
|
||||
[x.instantiate(device=self.device, module=self) for x in self.module_type.rearporttemplates.all()]
|
||||
)
|
||||
FrontPort.objects.bulk_create(
|
||||
[x.instantiate(device=self.device, module=self) for x in self.module_type.frontporttemplates.all()]
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Virtual chassis
|
||||
#
|
||||
|
@ -6,6 +6,7 @@ from dcim.models import ConsolePort, Interface, PowerPort
|
||||
from .cables import *
|
||||
from .devices import *
|
||||
from .devicetypes import *
|
||||
from .modules import *
|
||||
from .power import *
|
||||
from .racks import *
|
||||
from .sites import *
|
||||
|
@ -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',
|
||||
@ -255,6 +257,19 @@ class DeviceComponentTable(BaseTable):
|
||||
order_by = ('device', 'name')
|
||||
|
||||
|
||||
class ModularDeviceComponentTable(DeviceComponentTable):
|
||||
module_bay = tables.Column(
|
||||
accessor=Accessor('module__module_bay'),
|
||||
linkify={
|
||||
'viewname': 'dcim:device_modulebays',
|
||||
'args': [Accessor('device_id')],
|
||||
}
|
||||
)
|
||||
module = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
|
||||
|
||||
class CableTerminationTable(BaseTable):
|
||||
cable = tables.Column(
|
||||
linkify=True
|
||||
@ -282,7 +297,7 @@ class PathEndpointTable(CableTerminationTable):
|
||||
)
|
||||
|
||||
|
||||
class ConsolePortTable(DeviceComponentTable, PathEndpointTable):
|
||||
class ConsolePortTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
device = tables.Column(
|
||||
linkify={
|
||||
'viewname': 'dcim:device_consoleports',
|
||||
@ -296,8 +311,8 @@ class ConsolePortTable(DeviceComponentTable, PathEndpointTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ConsolePort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color',
|
||||
'link_peer', 'connection', 'tags',
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'speed', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description')
|
||||
|
||||
@ -317,8 +332,8 @@ class DeviceConsolePortTable(ConsolePortTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ConsolePort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color',
|
||||
'link_peer', 'connection', 'tags', 'actions'
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected',
|
||||
'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions'
|
||||
)
|
||||
default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions')
|
||||
row_attrs = {
|
||||
@ -326,7 +341,7 @@ class DeviceConsolePortTable(ConsolePortTable):
|
||||
}
|
||||
|
||||
|
||||
class ConsoleServerPortTable(DeviceComponentTable, PathEndpointTable):
|
||||
class ConsoleServerPortTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
device = tables.Column(
|
||||
linkify={
|
||||
'viewname': 'dcim:device_consoleserverports',
|
||||
@ -340,8 +355,8 @@ class ConsoleServerPortTable(DeviceComponentTable, PathEndpointTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ConsoleServerPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable',
|
||||
'cable_color', 'link_peer', 'connection', 'tags',
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'speed', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description')
|
||||
|
||||
@ -362,8 +377,8 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ConsoleServerPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color',
|
||||
'link_peer', 'connection', 'tags', 'actions',
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected',
|
||||
'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions')
|
||||
row_attrs = {
|
||||
@ -371,7 +386,7 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
|
||||
}
|
||||
|
||||
|
||||
class PowerPortTable(DeviceComponentTable, PathEndpointTable):
|
||||
class PowerPortTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
device = tables.Column(
|
||||
linkify={
|
||||
'viewname': 'dcim:device_powerports',
|
||||
@ -385,8 +400,8 @@ class PowerPortTable(DeviceComponentTable, PathEndpointTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = PowerPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'label', 'type', 'description', 'mark_connected', 'maximum_draw',
|
||||
'allocated_draw', 'cable', 'cable_color', 'link_peer', 'connection', 'tags',
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'mark_connected',
|
||||
'maximum_draw', 'allocated_draw', 'cable', 'cable_color', 'link_peer', 'connection', 'tags',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description')
|
||||
|
||||
@ -407,8 +422,8 @@ class DevicePowerPortTable(PowerPortTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = PowerPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'mark_connected',
|
||||
'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'maximum_draw', 'allocated_draw',
|
||||
'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'connection',
|
||||
@ -419,7 +434,7 @@ class DevicePowerPortTable(PowerPortTable):
|
||||
}
|
||||
|
||||
|
||||
class PowerOutletTable(DeviceComponentTable, PathEndpointTable):
|
||||
class PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
device = tables.Column(
|
||||
linkify={
|
||||
'viewname': 'dcim:device_poweroutlets',
|
||||
@ -436,8 +451,8 @@ class PowerOutletTable(DeviceComponentTable, PathEndpointTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = PowerOutlet
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'label', 'type', 'description', 'power_port', 'feed_leg', 'mark_connected',
|
||||
'cable', 'cable_color', 'link_peer', 'connection', 'tags',
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'power_port',
|
||||
'feed_leg', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'power_port', 'feed_leg', 'description')
|
||||
|
||||
@ -457,8 +472,8 @@ class DevicePowerOutletTable(PowerOutletTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = PowerOutlet
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'mark_connected', 'cable',
|
||||
'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'connection', 'actions',
|
||||
@ -489,7 +504,7 @@ class BaseInterfaceTable(BaseTable):
|
||||
)
|
||||
|
||||
|
||||
class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable):
|
||||
class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpointTable):
|
||||
device = tables.Column(
|
||||
linkify={
|
||||
'viewname': 'dcim:device_interfaces',
|
||||
@ -512,10 +527,10 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = Interface
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn',
|
||||
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection',
|
||||
'tags', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans',
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'enabled', 'type', 'mgmt_only', 'mtu',
|
||||
'mode', 'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
|
||||
'tx_power', 'description', 'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans',
|
||||
'link_peer', 'connection', 'tags', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'type', 'description')
|
||||
|
||||
@ -547,10 +562,11 @@ class DeviceInterfaceTable(InterfaceTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = Interface
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'enabled', 'type', 'parent', 'bridge', 'lag', 'mgmt_only', 'mtu', 'mode',
|
||||
'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
|
||||
'description', 'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'link_peer',
|
||||
'connection', 'tags', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'actions',
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'enabled', 'type', 'parent', 'bridge', 'lag',
|
||||
'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_frequency',
|
||||
'rf_channel_width', 'tx_power', 'description', 'mark_connected', 'cable', 'cable_color', 'wireless_link',
|
||||
'wireless_lans', 'link_peer', 'connection', 'tags', 'ip_addresses', 'fhrp_groups', 'untagged_vlan',
|
||||
'tagged_vlans', 'actions',
|
||||
)
|
||||
order_by = ('name',)
|
||||
default_columns = (
|
||||
@ -564,7 +580,7 @@ class DeviceInterfaceTable(InterfaceTable):
|
||||
}
|
||||
|
||||
|
||||
class FrontPortTable(DeviceComponentTable, CableTerminationTable):
|
||||
class FrontPortTable(ModularDeviceComponentTable, CableTerminationTable):
|
||||
device = tables.Column(
|
||||
linkify={
|
||||
'viewname': 'dcim:device_frontports',
|
||||
@ -585,8 +601,8 @@ class FrontPortTable(DeviceComponentTable, CableTerminationTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = FrontPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags',
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'rear_port',
|
||||
'rear_port_position', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'device', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description',
|
||||
@ -609,8 +625,8 @@ class DeviceFrontPortTable(FrontPortTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = FrontPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'mark_connected', 'cable',
|
||||
'cable_color', 'link_peer', 'tags', 'actions',
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'rear_port', 'rear_port_position',
|
||||
'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'actions',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'link_peer',
|
||||
@ -621,7 +637,7 @@ class DeviceFrontPortTable(FrontPortTable):
|
||||
}
|
||||
|
||||
|
||||
class RearPortTable(DeviceComponentTable, CableTerminationTable):
|
||||
class RearPortTable(ModularDeviceComponentTable, CableTerminationTable):
|
||||
device = tables.Column(
|
||||
linkify={
|
||||
'viewname': 'dcim:device_rearports',
|
||||
@ -636,8 +652,8 @@ class RearPortTable(DeviceComponentTable, CableTerminationTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = RearPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'label', 'type', 'color', 'positions', 'description', 'mark_connected', 'cable',
|
||||
'cable_color', 'link_peer', 'tags',
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'color', 'description')
|
||||
|
||||
@ -658,8 +674,8 @@ class DeviceRearPortTable(RearPortTable):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = RearPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'type', 'positions', 'description', 'mark_connected', 'cable', 'cable_color',
|
||||
'link_peer', 'tags', 'actions',
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'positions', 'description', 'mark_connected',
|
||||
'cable', 'cable_color', 'link_peer', 'tags', 'actions',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'link_peer', 'actions',
|
||||
@ -716,6 +732,40 @@ class DeviceDeviceBayTable(DeviceBayTable):
|
||||
)
|
||||
|
||||
|
||||
class ModuleBayTable(DeviceComponentTable):
|
||||
device = tables.Column(
|
||||
linkify={
|
||||
'viewname': 'dcim:device_modulebays',
|
||||
'args': [Accessor('device_id')],
|
||||
}
|
||||
)
|
||||
installed_module = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name='Installed module'
|
||||
)
|
||||
tags = TagColumn(
|
||||
url_name='dcim:modulebay_list'
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ModuleBay
|
||||
fields = ('pk', 'id', 'name', 'device', 'label', 'position', 'installed_module', 'description', 'tags')
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'installed_module', 'description')
|
||||
|
||||
|
||||
class DeviceModuleBayTable(ModuleBayTable):
|
||||
actions = ButtonsColumn(
|
||||
model=DeviceBay,
|
||||
buttons=('edit', 'delete'),
|
||||
prepend_template=MODULEBAY_BUTTONS
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ModuleBay
|
||||
fields = ('pk', 'id', 'name', 'label', 'description', 'installed_module', 'tags', 'actions')
|
||||
default_columns = ('pk', 'name', 'label', 'description', 'installed_module', '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', 'position', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
|
||||
class DeviceBayTemplateTable(ComponentTemplateTable):
|
||||
actions = ButtonsColumn(
|
||||
model=DeviceBayTemplate,
|
||||
|
61
netbox/dcim/tables/modules.py
Normal file
61
netbox/dcim/tables/modules.py
Normal file
@ -0,0 +1,61 @@
|
||||
import django_tables2 as tables
|
||||
|
||||
from dcim.models import Module, ModuleType
|
||||
from utilities.tables import BaseTable, LinkedCountColumn, MarkdownColumn, TagColumn, ToggleColumn
|
||||
|
||||
__all__ = (
|
||||
'ModuleTable',
|
||||
'ModuleTypeTable',
|
||||
)
|
||||
|
||||
|
||||
class ModuleTypeTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
model = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name='Module Type'
|
||||
)
|
||||
instance_count = LinkedCountColumn(
|
||||
viewname='dcim:module_list',
|
||||
url_params={'module_type_id': 'pk'},
|
||||
verbose_name='Instances'
|
||||
)
|
||||
comments = MarkdownColumn()
|
||||
tags = TagColumn(
|
||||
url_name='dcim:moduletype_list'
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = ModuleType
|
||||
fields = (
|
||||
'pk', 'id', 'model', 'manufacturer', 'part_number', 'comments', 'tags',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'model', 'manufacturer', 'part_number',
|
||||
)
|
||||
|
||||
|
||||
class ModuleTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
device = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
module_bay = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
module_type = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
comments = MarkdownColumn()
|
||||
tags = TagColumn(
|
||||
url_name='dcim:module_list'
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Module
|
||||
fields = (
|
||||
'pk', 'id', 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'comments', 'tags',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'id', 'device', 'module_bay', 'module_type', 'serial', 'asset_tag',
|
||||
)
|
@ -321,3 +321,17 @@ DEVICEBAY_BUTTONS = """
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
MODULEBAY_BUTTONS = """
|
||||
{% if perms.dcim.add_module %}
|
||||
{% if record.installed_module %}
|
||||
<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-minus-thick" aria-hidden="true" title="Remove module"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:module_add' %}?device={{ record.device.pk }}&module_bay={{ record.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-success btn-sm">
|
||||
<i class="mdi mdi-plus-thick" aria-hidden="true" title="Install module"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
"""
|
||||
|
@ -7,7 +7,7 @@ from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import *
|
||||
from ipam.models import ASN, RIR, VLAN
|
||||
from utilities.testing import APITestCase, APIViewTestCases
|
||||
from utilities.testing import APITestCase, APIViewTestCases, create_test_device
|
||||
from virtualization.models import Cluster, ClusterType
|
||||
from wireless.models import WirelessLAN
|
||||
|
||||
@ -470,6 +470,45 @@ class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
|
||||
]
|
||||
|
||||
|
||||
class ModuleTypeTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ModuleType
|
||||
brief_fields = ['display', 'id', 'manufacturer', 'model', 'url']
|
||||
bulk_update_data = {
|
||||
'part_number': 'ABC123',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
manufacturers = (
|
||||
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
||||
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
|
||||
)
|
||||
Manufacturer.objects.bulk_create(manufacturers)
|
||||
|
||||
module_types = (
|
||||
ModuleType(manufacturer=manufacturers[0], model='Module Type 1'),
|
||||
ModuleType(manufacturer=manufacturers[0], model='Module Type 2'),
|
||||
ModuleType(manufacturer=manufacturers[0], model='Module Type 3'),
|
||||
)
|
||||
ModuleType.objects.bulk_create(module_types)
|
||||
|
||||
cls.create_data = [
|
||||
{
|
||||
'manufacturer': manufacturers[1].pk,
|
||||
'model': 'Module Type 4',
|
||||
},
|
||||
{
|
||||
'manufacturer': manufacturers[1].pk,
|
||||
'model': 'Module Type 5',
|
||||
},
|
||||
{
|
||||
'manufacturer': manufacturers[1].pk,
|
||||
'model': 'Module Type 6',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class ConsolePortTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ConsolePortTemplate
|
||||
brief_fields = ['display', 'id', 'name', 'url']
|
||||
@ -778,6 +817,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']
|
||||
@ -1026,6 +1105,67 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
|
||||
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class ModuleTest(APIViewTestCases.APIViewTestCase):
|
||||
model = Module
|
||||
brief_fields = ['device', 'display', 'id', 'module_bay', 'module_type', 'url']
|
||||
bulk_update_data = {
|
||||
'serial': '1234ABCD',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
manufacturer = Manufacturer.objects.create(name='Generic', slug='generic')
|
||||
device = create_test_device('Test Device 1')
|
||||
|
||||
module_types = (
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 1'),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 2'),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 3'),
|
||||
)
|
||||
ModuleType.objects.bulk_create(module_types)
|
||||
|
||||
module_bays = (
|
||||
ModuleBay(device=device, name='Module Bay 1'),
|
||||
ModuleBay(device=device, name='Module Bay 2'),
|
||||
ModuleBay(device=device, name='Module Bay 3'),
|
||||
ModuleBay(device=device, name='Module Bay 4'),
|
||||
ModuleBay(device=device, name='Module Bay 5'),
|
||||
ModuleBay(device=device, name='Module Bay 6'),
|
||||
)
|
||||
ModuleBay.objects.bulk_create(module_bays)
|
||||
|
||||
modules = (
|
||||
Module(device=device, module_bay=module_bays[0], module_type=module_types[0]),
|
||||
Module(device=device, module_bay=module_bays[1], module_type=module_types[1]),
|
||||
Module(device=device, module_bay=module_bays[2], module_type=module_types[2]),
|
||||
)
|
||||
Module.objects.bulk_create(modules)
|
||||
|
||||
cls.create_data = [
|
||||
{
|
||||
'device': device.pk,
|
||||
'module_bay': module_bays[3].pk,
|
||||
'module_type': module_types[0].pk,
|
||||
'serial': 'ABC123',
|
||||
'asset_tag': 'Foo1',
|
||||
},
|
||||
{
|
||||
'device': device.pk,
|
||||
'module_bay': module_bays[4].pk,
|
||||
'module_type': module_types[1].pk,
|
||||
'serial': 'DEF456',
|
||||
'asset_tag': 'Foo2',
|
||||
},
|
||||
{
|
||||
'device': device.pk,
|
||||
'module_bay': module_bays[5].pk,
|
||||
'module_type': module_types[2].pk,
|
||||
'serial': 'GHI789',
|
||||
'asset_tag': 'Foo3',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class ConsolePortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
||||
model = ConsolePort
|
||||
brief_fields = ['_occupied', 'cable', 'device', 'display', 'id', 'name', 'url']
|
||||
@ -1369,6 +1509,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']
|
||||
|
@ -7,7 +7,7 @@ from dcim.models import *
|
||||
from ipam.models import ASN, IPAddress, RIR
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.choices import ColorChoices
|
||||
from utilities.testing import ChangeLoggedFilterSetTests
|
||||
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device
|
||||
from virtualization.models import Cluster, ClusterType
|
||||
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
|
||||
|
||||
@ -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,116 @@ 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 ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = ModuleType.objects.all()
|
||||
filterset = ModuleTypeFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
manufacturers = (
|
||||
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
||||
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
|
||||
Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
|
||||
)
|
||||
Manufacturer.objects.bulk_create(manufacturers)
|
||||
|
||||
module_types = (
|
||||
ModuleType(manufacturer=manufacturers[0], model='Model 1', part_number='Part Number 1'),
|
||||
ModuleType(manufacturer=manufacturers[1], model='Model 2', part_number='Part Number 2'),
|
||||
ModuleType(manufacturer=manufacturers[2], model='Model 3', part_number='Part Number 3'),
|
||||
)
|
||||
ModuleType.objects.bulk_create(module_types)
|
||||
|
||||
# Add component templates for filtering
|
||||
ConsolePortTemplate.objects.bulk_create((
|
||||
ConsolePortTemplate(module_type=module_types[0], name='Console Port 1'),
|
||||
ConsolePortTemplate(module_type=module_types[1], name='Console Port 2'),
|
||||
))
|
||||
ConsoleServerPortTemplate.objects.bulk_create((
|
||||
ConsoleServerPortTemplate(module_type=module_types[0], name='Console Server Port 1'),
|
||||
ConsoleServerPortTemplate(module_type=module_types[1], name='Console Server Port 2'),
|
||||
))
|
||||
PowerPortTemplate.objects.bulk_create((
|
||||
PowerPortTemplate(module_type=module_types[0], name='Power Port 1'),
|
||||
PowerPortTemplate(module_type=module_types[1], name='Power Port 2'),
|
||||
))
|
||||
PowerOutletTemplate.objects.bulk_create((
|
||||
PowerOutletTemplate(module_type=module_types[0], name='Power Outlet 1'),
|
||||
PowerOutletTemplate(module_type=module_types[1], name='Power Outlet 2'),
|
||||
))
|
||||
InterfaceTemplate.objects.bulk_create((
|
||||
InterfaceTemplate(module_type=module_types[0], name='Interface 1'),
|
||||
InterfaceTemplate(module_type=module_types[1], name='Interface 2'),
|
||||
))
|
||||
rear_ports = (
|
||||
RearPortTemplate(module_type=module_types[0], name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C),
|
||||
RearPortTemplate(module_type=module_types[1], name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C),
|
||||
)
|
||||
RearPortTemplate.objects.bulk_create(rear_ports)
|
||||
FrontPortTemplate.objects.bulk_create((
|
||||
FrontPortTemplate(module_type=module_types[0], name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0]),
|
||||
FrontPortTemplate(module_type=module_types[1], name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[1]),
|
||||
))
|
||||
|
||||
def test_model(self):
|
||||
params = {'model': ['Model 1', 'Model 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_part_number(self):
|
||||
params = {'part_number': ['Part Number 1', 'Part Number 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_manufacturer(self):
|
||||
manufacturers = Manufacturer.objects.all()[:2]
|
||||
params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_console_ports(self):
|
||||
params = {'console_ports': 'true'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'console_ports': 'false'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_console_server_ports(self):
|
||||
params = {'console_server_ports': 'true'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'console_server_ports': 'false'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_power_ports(self):
|
||||
params = {'power_ports': 'true'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'power_ports': 'false'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_power_outlets(self):
|
||||
params = {'power_outlets': 'true'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'power_outlets': 'false'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_interfaces(self):
|
||||
params = {'interfaces': 'true'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'interfaces': 'false'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_pass_through_ports(self):
|
||||
params = {'pass_through_ports': 'true'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'pass_through_ports': 'false'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
|
||||
class ConsolePortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = ConsolePortTemplate.objects.all()
|
||||
@ -1036,6 +1150,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 +1426,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 +1615,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)
|
||||
@ -1492,6 +1648,79 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = Module.objects.all()
|
||||
filterset = ModuleFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
manufacturers = (
|
||||
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
||||
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
|
||||
Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
|
||||
)
|
||||
Manufacturer.objects.bulk_create(manufacturers)
|
||||
|
||||
devices = (
|
||||
create_test_device('Test Device 1'),
|
||||
create_test_device('Test Device 2'),
|
||||
create_test_device('Test Device 3'),
|
||||
)
|
||||
|
||||
module_types = (
|
||||
ModuleType(manufacturer=manufacturers[0], model='Module Type 1'),
|
||||
ModuleType(manufacturer=manufacturers[1], model='Module Type 2'),
|
||||
ModuleType(manufacturer=manufacturers[2], model='Module Type 3'),
|
||||
)
|
||||
ModuleType.objects.bulk_create(module_types)
|
||||
|
||||
module_bays = (
|
||||
ModuleBay(device=devices[0], name='Module Bay 1'),
|
||||
ModuleBay(device=devices[0], name='Module Bay 2'),
|
||||
ModuleBay(device=devices[0], name='Module Bay 3'),
|
||||
ModuleBay(device=devices[1], name='Module Bay 1'),
|
||||
ModuleBay(device=devices[1], name='Module Bay 2'),
|
||||
ModuleBay(device=devices[1], name='Module Bay 3'),
|
||||
ModuleBay(device=devices[2], name='Module Bay 1'),
|
||||
ModuleBay(device=devices[2], name='Module Bay 2'),
|
||||
ModuleBay(device=devices[2], name='Module Bay 3'),
|
||||
)
|
||||
ModuleBay.objects.bulk_create(module_bays)
|
||||
|
||||
modules = (
|
||||
Module(device=devices[0], module_bay=module_bays[0], module_type=module_types[0], serial='A', asset_tag='A'),
|
||||
Module(device=devices[0], module_bay=module_bays[1], module_type=module_types[1], serial='B', asset_tag='B'),
|
||||
Module(device=devices[0], module_bay=module_bays[2], module_type=module_types[2], serial='C', asset_tag='C'),
|
||||
Module(device=devices[1], module_bay=module_bays[3], module_type=module_types[0], serial='D', asset_tag='D'),
|
||||
Module(device=devices[1], module_bay=module_bays[4], module_type=module_types[1], serial='E', asset_tag='E'),
|
||||
Module(device=devices[1], module_bay=module_bays[5], module_type=module_types[2], serial='F', asset_tag='F'),
|
||||
Module(device=devices[2], module_bay=module_bays[6], module_type=module_types[0], serial='G', asset_tag='G'),
|
||||
Module(device=devices[2], module_bay=module_bays[7], module_type=module_types[1], serial='H', asset_tag='H'),
|
||||
Module(device=devices[2], module_bay=module_bays[8], module_type=module_types[2], serial='I', asset_tag='I'),
|
||||
)
|
||||
Module.objects.bulk_create(modules)
|
||||
|
||||
def test_manufacturer(self):
|
||||
manufacturers = Manufacturer.objects.all()[:2]
|
||||
params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
|
||||
def test_device(self):
|
||||
device_types = Device.objects.all()[:2]
|
||||
params = {'device_id': [device_types[0].pk, device_types[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
|
||||
def test_serial(self):
|
||||
params = {'asset_tag': ['A', 'B']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_asset_tag(self):
|
||||
params = {'asset_tag': ['A', 'B']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = ConsolePort.objects.all()
|
||||
filterset = ConsolePortFilterSet
|
||||
@ -2508,6 +2737,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()
|
||||
@ -578,7 +591,7 @@ model: TEST-1000
|
||||
slug: test-1000
|
||||
u_height: 2
|
||||
subdevice_role: parent
|
||||
comments: test comment
|
||||
comments: Test comment
|
||||
console-ports:
|
||||
- name: Console Port 1
|
||||
type: de-9
|
||||
@ -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',
|
||||
)
|
||||
|
||||
@ -668,49 +686,53 @@ device-bays:
|
||||
response = self.client.post(reverse('dcim:devicetype_import'), data=form_data, follow=True)
|
||||
self.assertHttpStatus(response, 200)
|
||||
|
||||
dt = DeviceType.objects.get(model='TEST-1000')
|
||||
self.assertEqual(dt.comments, 'test comment')
|
||||
device_type = DeviceType.objects.get(model='TEST-1000')
|
||||
self.assertEqual(device_type.comments, 'Test comment')
|
||||
|
||||
# Verify all of the components were created
|
||||
self.assertEqual(dt.consoleporttemplates.count(), 3)
|
||||
self.assertEqual(device_type.consoleporttemplates.count(), 3)
|
||||
cp1 = ConsolePortTemplate.objects.first()
|
||||
self.assertEqual(cp1.name, 'Console Port 1')
|
||||
self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
|
||||
|
||||
self.assertEqual(dt.consoleserverporttemplates.count(), 3)
|
||||
self.assertEqual(device_type.consoleserverporttemplates.count(), 3)
|
||||
csp1 = ConsoleServerPortTemplate.objects.first()
|
||||
self.assertEqual(csp1.name, 'Console Server Port 1')
|
||||
self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
|
||||
|
||||
self.assertEqual(dt.powerporttemplates.count(), 3)
|
||||
self.assertEqual(device_type.powerporttemplates.count(), 3)
|
||||
pp1 = PowerPortTemplate.objects.first()
|
||||
self.assertEqual(pp1.name, 'Power Port 1')
|
||||
self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
|
||||
|
||||
self.assertEqual(dt.poweroutlettemplates.count(), 3)
|
||||
self.assertEqual(device_type.poweroutlettemplates.count(), 3)
|
||||
po1 = PowerOutletTemplate.objects.first()
|
||||
self.assertEqual(po1.name, 'Power Outlet 1')
|
||||
self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
|
||||
self.assertEqual(po1.power_port, pp1)
|
||||
self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
|
||||
|
||||
self.assertEqual(dt.interfacetemplates.count(), 3)
|
||||
self.assertEqual(device_type.interfacetemplates.count(), 3)
|
||||
iface1 = InterfaceTemplate.objects.first()
|
||||
self.assertEqual(iface1.name, 'Interface 1')
|
||||
self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||
self.assertTrue(iface1.mgmt_only)
|
||||
|
||||
self.assertEqual(dt.rearporttemplates.count(), 3)
|
||||
self.assertEqual(device_type.rearporttemplates.count(), 3)
|
||||
rp1 = RearPortTemplate.objects.first()
|
||||
self.assertEqual(rp1.name, 'Rear Port 1')
|
||||
|
||||
self.assertEqual(dt.frontporttemplates.count(), 3)
|
||||
self.assertEqual(device_type.frontporttemplates.count(), 3)
|
||||
fp1 = FrontPortTemplate.objects.first()
|
||||
self.assertEqual(fp1.name, 'Front Port 1')
|
||||
self.assertEqual(fp1.rear_port, rp1)
|
||||
self.assertEqual(fp1.rear_port_position, 1)
|
||||
|
||||
self.assertEqual(dt.devicebaytemplates.count(), 3)
|
||||
self.assertEqual(device_type.modulebaytemplates.count(), 3)
|
||||
db1 = ModuleBayTemplate.objects.first()
|
||||
self.assertEqual(db1.name, 'Module Bay 1')
|
||||
|
||||
self.assertEqual(device_type.devicebaytemplates.count(), 3)
|
||||
db1 = DeviceBayTemplate.objects.first()
|
||||
self.assertEqual(db1.name, 'Device Bay 1')
|
||||
|
||||
@ -719,7 +741,7 @@ device-bays:
|
||||
self.add_permissions('dcim.view_devicetype')
|
||||
|
||||
# Test default YAML export
|
||||
response = self.client.get('{}?export'.format(url))
|
||||
response = self.client.get(f'{url}?export')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = list(yaml.load_all(response.content, Loader=yaml.SafeLoader))
|
||||
self.assertEqual(len(data), 3)
|
||||
@ -732,6 +754,300 @@ device-bays:
|
||||
self.assertEqual(response.get('Content-Type'), 'text/csv; charset=utf-8')
|
||||
|
||||
|
||||
# TODO: Change base class to PrimaryObjectViewTestCase
|
||||
# Blocked by absence of bulk import view for ModuleTypes
|
||||
class ModuleTypeTestCase(
|
||||
ViewTestCases.GetObjectViewTestCase,
|
||||
ViewTestCases.GetObjectChangelogViewTestCase,
|
||||
ViewTestCases.CreateObjectViewTestCase,
|
||||
ViewTestCases.EditObjectViewTestCase,
|
||||
ViewTestCases.DeleteObjectViewTestCase,
|
||||
ViewTestCases.ListObjectsViewTestCase,
|
||||
ViewTestCases.BulkEditObjectsViewTestCase,
|
||||
ViewTestCases.BulkDeleteObjectsViewTestCase
|
||||
):
|
||||
model = ModuleType
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
manufacturers = (
|
||||
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
||||
Manufacturer(name='Manufacturer 2', slug='manufacturer-2')
|
||||
)
|
||||
Manufacturer.objects.bulk_create(manufacturers)
|
||||
|
||||
ModuleType.objects.bulk_create([
|
||||
ModuleType(model='Module Type 1', manufacturer=manufacturers[0]),
|
||||
ModuleType(model='Module Type 2', manufacturer=manufacturers[0]),
|
||||
ModuleType(model='Module Type 3', manufacturer=manufacturers[0]),
|
||||
])
|
||||
|
||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
|
||||
cls.form_data = {
|
||||
'manufacturer': manufacturers[1].pk,
|
||||
'model': 'Device Type X',
|
||||
'part_number': '123ABC',
|
||||
'comments': 'Some comments',
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'manufacturer': manufacturers[1].pk,
|
||||
'part_number': '456DEF',
|
||||
}
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_moduletype_consoleports(self):
|
||||
moduletype = ModuleType.objects.first()
|
||||
console_ports = (
|
||||
ConsolePortTemplate(module_type=moduletype, name='Console Port 1'),
|
||||
ConsolePortTemplate(module_type=moduletype, name='Console Port 2'),
|
||||
ConsolePortTemplate(module_type=moduletype, name='Console Port 3'),
|
||||
)
|
||||
ConsolePortTemplate.objects.bulk_create(console_ports)
|
||||
|
||||
url = reverse('dcim:moduletype_consoleports', kwargs={'pk': moduletype.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_moduletype_consoleserverports(self):
|
||||
moduletype = ModuleType.objects.first()
|
||||
console_server_ports = (
|
||||
ConsoleServerPortTemplate(module_type=moduletype, name='Console Server Port 1'),
|
||||
ConsoleServerPortTemplate(module_type=moduletype, name='Console Server Port 2'),
|
||||
ConsoleServerPortTemplate(module_type=moduletype, name='Console Server Port 3'),
|
||||
)
|
||||
ConsoleServerPortTemplate.objects.bulk_create(console_server_ports)
|
||||
|
||||
url = reverse('dcim:moduletype_consoleserverports', kwargs={'pk': moduletype.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_moduletype_powerports(self):
|
||||
moduletype = ModuleType.objects.first()
|
||||
power_ports = (
|
||||
PowerPortTemplate(module_type=moduletype, name='Power Port 1'),
|
||||
PowerPortTemplate(module_type=moduletype, name='Power Port 2'),
|
||||
PowerPortTemplate(module_type=moduletype, name='Power Port 3'),
|
||||
)
|
||||
PowerPortTemplate.objects.bulk_create(power_ports)
|
||||
|
||||
url = reverse('dcim:moduletype_powerports', kwargs={'pk': moduletype.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_moduletype_poweroutlets(self):
|
||||
moduletype = ModuleType.objects.first()
|
||||
power_outlets = (
|
||||
PowerOutletTemplate(module_type=moduletype, name='Power Outlet 1'),
|
||||
PowerOutletTemplate(module_type=moduletype, name='Power Outlet 2'),
|
||||
PowerOutletTemplate(module_type=moduletype, name='Power Outlet 3'),
|
||||
)
|
||||
PowerOutletTemplate.objects.bulk_create(power_outlets)
|
||||
|
||||
url = reverse('dcim:moduletype_poweroutlets', kwargs={'pk': moduletype.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_moduletype_interfaces(self):
|
||||
moduletype = ModuleType.objects.first()
|
||||
interfaces = (
|
||||
InterfaceTemplate(module_type=moduletype, name='Interface 1'),
|
||||
InterfaceTemplate(module_type=moduletype, name='Interface 2'),
|
||||
InterfaceTemplate(module_type=moduletype, name='Interface 3'),
|
||||
)
|
||||
InterfaceTemplate.objects.bulk_create(interfaces)
|
||||
|
||||
url = reverse('dcim:moduletype_interfaces', kwargs={'pk': moduletype.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_moduletype_rearports(self):
|
||||
moduletype = ModuleType.objects.first()
|
||||
rear_ports = (
|
||||
RearPortTemplate(module_type=moduletype, name='Rear Port 1'),
|
||||
RearPortTemplate(module_type=moduletype, name='Rear Port 2'),
|
||||
RearPortTemplate(module_type=moduletype, name='Rear Port 3'),
|
||||
)
|
||||
RearPortTemplate.objects.bulk_create(rear_ports)
|
||||
|
||||
url = reverse('dcim:moduletype_rearports', kwargs={'pk': moduletype.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_moduletype_frontports(self):
|
||||
moduletype = ModuleType.objects.first()
|
||||
rear_ports = (
|
||||
RearPortTemplate(module_type=moduletype, name='Rear Port 1'),
|
||||
RearPortTemplate(module_type=moduletype, name='Rear Port 2'),
|
||||
RearPortTemplate(module_type=moduletype, name='Rear Port 3'),
|
||||
)
|
||||
RearPortTemplate.objects.bulk_create(rear_ports)
|
||||
front_ports = (
|
||||
FrontPortTemplate(module_type=moduletype, name='Front Port 1', rear_port=rear_ports[0], rear_port_position=1),
|
||||
FrontPortTemplate(module_type=moduletype, name='Front Port 2', rear_port=rear_ports[1], rear_port_position=1),
|
||||
FrontPortTemplate(module_type=moduletype, name='Front Port 3', rear_port=rear_ports[2], rear_port_position=1),
|
||||
)
|
||||
FrontPortTemplate.objects.bulk_create(front_ports)
|
||||
|
||||
url = reverse('dcim:moduletype_frontports', kwargs={'pk': moduletype.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_import_objects(self):
|
||||
"""
|
||||
Custom import test for YAML-based imports (versus CSV)
|
||||
"""
|
||||
IMPORT_DATA = """
|
||||
manufacturer: Generic
|
||||
model: TEST-1000
|
||||
comments: Test comment
|
||||
console-ports:
|
||||
- name: Console Port 1
|
||||
type: de-9
|
||||
- name: Console Port 2
|
||||
type: de-9
|
||||
- name: Console Port 3
|
||||
type: de-9
|
||||
console-server-ports:
|
||||
- name: Console Server Port 1
|
||||
type: rj-45
|
||||
- name: Console Server Port 2
|
||||
type: rj-45
|
||||
- name: Console Server Port 3
|
||||
type: rj-45
|
||||
power-ports:
|
||||
- name: Power Port 1
|
||||
type: iec-60320-c14
|
||||
- name: Power Port 2
|
||||
type: iec-60320-c14
|
||||
- name: Power Port 3
|
||||
type: iec-60320-c14
|
||||
power-outlets:
|
||||
- name: Power Outlet 1
|
||||
type: iec-60320-c13
|
||||
power_port: Power Port 1
|
||||
feed_leg: A
|
||||
- name: Power Outlet 2
|
||||
type: iec-60320-c13
|
||||
power_port: Power Port 1
|
||||
feed_leg: A
|
||||
- name: Power Outlet 3
|
||||
type: iec-60320-c13
|
||||
power_port: Power Port 1
|
||||
feed_leg: A
|
||||
interfaces:
|
||||
- name: Interface 1
|
||||
type: 1000base-t
|
||||
mgmt_only: true
|
||||
- name: Interface 2
|
||||
type: 1000base-t
|
||||
- name: Interface 3
|
||||
type: 1000base-t
|
||||
rear-ports:
|
||||
- name: Rear Port 1
|
||||
type: 8p8c
|
||||
- name: Rear Port 2
|
||||
type: 8p8c
|
||||
- name: Rear Port 3
|
||||
type: 8p8c
|
||||
front-ports:
|
||||
- name: Front Port 1
|
||||
type: 8p8c
|
||||
rear_port: Rear Port 1
|
||||
- name: Front Port 2
|
||||
type: 8p8c
|
||||
rear_port: Rear Port 2
|
||||
- name: Front Port 3
|
||||
type: 8p8c
|
||||
rear_port: Rear Port 3
|
||||
"""
|
||||
|
||||
# Create the manufacturer
|
||||
Manufacturer(name='Generic', slug='generic').save()
|
||||
|
||||
# Add all required permissions to the test user
|
||||
self.add_permissions(
|
||||
'dcim.view_moduletype',
|
||||
'dcim.add_moduletype',
|
||||
'dcim.add_consoleporttemplate',
|
||||
'dcim.add_consoleserverporttemplate',
|
||||
'dcim.add_powerporttemplate',
|
||||
'dcim.add_poweroutlettemplate',
|
||||
'dcim.add_interfacetemplate',
|
||||
'dcim.add_frontporttemplate',
|
||||
'dcim.add_rearporttemplate',
|
||||
)
|
||||
|
||||
form_data = {
|
||||
'data': IMPORT_DATA,
|
||||
'format': 'yaml'
|
||||
}
|
||||
response = self.client.post(reverse('dcim:moduletype_import'), data=form_data, follow=True)
|
||||
self.assertHttpStatus(response, 200)
|
||||
|
||||
module_type = ModuleType.objects.get(model='TEST-1000')
|
||||
self.assertEqual(module_type.comments, 'Test comment')
|
||||
|
||||
# Verify all the components were created
|
||||
self.assertEqual(module_type.consoleporttemplates.count(), 3)
|
||||
cp1 = ConsolePortTemplate.objects.first()
|
||||
self.assertEqual(cp1.name, 'Console Port 1')
|
||||
self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
|
||||
|
||||
self.assertEqual(module_type.consoleserverporttemplates.count(), 3)
|
||||
csp1 = ConsoleServerPortTemplate.objects.first()
|
||||
self.assertEqual(csp1.name, 'Console Server Port 1')
|
||||
self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
|
||||
|
||||
self.assertEqual(module_type.powerporttemplates.count(), 3)
|
||||
pp1 = PowerPortTemplate.objects.first()
|
||||
self.assertEqual(pp1.name, 'Power Port 1')
|
||||
self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
|
||||
|
||||
self.assertEqual(module_type.poweroutlettemplates.count(), 3)
|
||||
po1 = PowerOutletTemplate.objects.first()
|
||||
self.assertEqual(po1.name, 'Power Outlet 1')
|
||||
self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
|
||||
self.assertEqual(po1.power_port, pp1)
|
||||
self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
|
||||
|
||||
self.assertEqual(module_type.interfacetemplates.count(), 3)
|
||||
iface1 = InterfaceTemplate.objects.first()
|
||||
self.assertEqual(iface1.name, 'Interface 1')
|
||||
self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||
self.assertTrue(iface1.mgmt_only)
|
||||
|
||||
self.assertEqual(module_type.rearporttemplates.count(), 3)
|
||||
rp1 = RearPortTemplate.objects.first()
|
||||
self.assertEqual(rp1.name, 'Rear Port 1')
|
||||
|
||||
self.assertEqual(module_type.frontporttemplates.count(), 3)
|
||||
fp1 = FrontPortTemplate.objects.first()
|
||||
self.assertEqual(fp1.name, 'Front Port 1')
|
||||
self.assertEqual(fp1.rear_port, rp1)
|
||||
self.assertEqual(fp1.rear_port_position, 1)
|
||||
|
||||
def test_export_objects(self):
|
||||
url = reverse('dcim:moduletype_list')
|
||||
self.add_permissions('dcim.view_moduletype')
|
||||
|
||||
# Test default YAML export
|
||||
response = self.client.get(f'{url}?export')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = list(yaml.load_all(response.content, Loader=yaml.SafeLoader))
|
||||
self.assertEqual(len(data), 3)
|
||||
self.assertEqual(data[0]['manufacturer'], 'Manufacturer 1')
|
||||
self.assertEqual(data[0]['model'], 'Module Type 1')
|
||||
|
||||
# Test table-based export
|
||||
response = self.client.get(f'{url}?export=table')
|
||||
self.assertHttpStatus(response, 200)
|
||||
self.assertEqual(response.get('Content-Type'), 'text/csv; charset=utf-8')
|
||||
|
||||
|
||||
#
|
||||
# DeviceType components
|
||||
#
|
||||
@ -1011,6 +1327,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 +1656,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()
|
||||
@ -1335,6 +1697,75 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
|
||||
class ModuleTestCase(
|
||||
# Module does not support bulk renaming (no name field) or
|
||||
# bulk creation (need to specify module bays)
|
||||
ViewTestCases.GetObjectViewTestCase,
|
||||
ViewTestCases.GetObjectChangelogViewTestCase,
|
||||
ViewTestCases.EditObjectViewTestCase,
|
||||
ViewTestCases.DeleteObjectViewTestCase,
|
||||
ViewTestCases.ListObjectsViewTestCase,
|
||||
ViewTestCases.BulkImportObjectsViewTestCase,
|
||||
ViewTestCases.BulkEditObjectsViewTestCase,
|
||||
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
||||
):
|
||||
model = Module
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
manufacturer = Manufacturer.objects.create(name='Generic', slug='generic')
|
||||
devices = (
|
||||
create_test_device('Device 1'),
|
||||
create_test_device('Device 2'),
|
||||
)
|
||||
|
||||
module_types = (
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 1'),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 2'),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 3'),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 4'),
|
||||
)
|
||||
ModuleType.objects.bulk_create(module_types)
|
||||
|
||||
module_bays = (
|
||||
ModuleBay(device=devices[0], name='Module Bay 1'),
|
||||
ModuleBay(device=devices[0], name='Module Bay 2'),
|
||||
ModuleBay(device=devices[0], name='Module Bay 3'),
|
||||
ModuleBay(device=devices[1], name='Module Bay 1'),
|
||||
ModuleBay(device=devices[1], name='Module Bay 2'),
|
||||
ModuleBay(device=devices[1], name='Module Bay 3'),
|
||||
)
|
||||
ModuleBay.objects.bulk_create(module_bays)
|
||||
|
||||
modules = (
|
||||
Module(device=devices[0], module_bay=module_bays[0], module_type=module_types[0]),
|
||||
Module(device=devices[0], module_bay=module_bays[1], module_type=module_types[1]),
|
||||
Module(device=devices[0], module_bay=module_bays[2], module_type=module_types[2]),
|
||||
)
|
||||
Module.objects.bulk_create(modules)
|
||||
|
||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
|
||||
cls.form_data = {
|
||||
'device': devices[1].pk,
|
||||
'module_bay': module_bays[3].pk,
|
||||
'module_type': module_types[0].pk,
|
||||
'serial': 'A',
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'module_type': module_types[3].pk,
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"device,module_bay,module_type,serial,asset_tag",
|
||||
"Device 2,Module Bay 1,Module Type 1,A,A",
|
||||
"Device 2,Module Bay 2,Module Type 2,B,B",
|
||||
"Device 2,Module Bay 3,Module Type 3,C,C",
|
||||
)
|
||||
|
||||
|
||||
class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
model = ConsolePort
|
||||
|
||||
@ -1807,6 +2238,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,12 +113,32 @@ 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'),
|
||||
path('device-types/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='devicetype_changelog', kwargs={'model': DeviceType}),
|
||||
path('device-types/<int:pk>/journal/', ObjectJournalView.as_view(), name='devicetype_journal', kwargs={'model': DeviceType}),
|
||||
|
||||
# Module types
|
||||
path('module-types/', views.ModuleTypeListView.as_view(), name='moduletype_list'),
|
||||
path('module-types/add/', views.ModuleTypeEditView.as_view(), name='moduletype_add'),
|
||||
path('module-types/import/', views.ModuleTypeImportView.as_view(), name='moduletype_import'),
|
||||
path('module-types/edit/', views.ModuleTypeBulkEditView.as_view(), name='moduletype_bulk_edit'),
|
||||
path('module-types/delete/', views.ModuleTypeBulkDeleteView.as_view(), name='moduletype_bulk_delete'),
|
||||
path('module-types/<int:pk>/', views.ModuleTypeView.as_view(), name='moduletype'),
|
||||
path('module-types/<int:pk>/console-ports/', views.ModuleTypeConsolePortsView.as_view(), name='moduletype_consoleports'),
|
||||
path('module-types/<int:pk>/console-server-ports/', views.ModuleTypeConsoleServerPortsView.as_view(), name='moduletype_consoleserverports'),
|
||||
path('module-types/<int:pk>/power-ports/', views.ModuleTypePowerPortsView.as_view(), name='moduletype_powerports'),
|
||||
path('module-types/<int:pk>/power-outlets/', views.ModuleTypePowerOutletsView.as_view(), name='moduletype_poweroutlets'),
|
||||
path('module-types/<int:pk>/interfaces/', views.ModuleTypeInterfacesView.as_view(), name='moduletype_interfaces'),
|
||||
path('module-types/<int:pk>/front-ports/', views.ModuleTypeFrontPortsView.as_view(), name='moduletype_frontports'),
|
||||
path('module-types/<int:pk>/rear-ports/', views.ModuleTypeRearPortsView.as_view(), name='moduletype_rearports'),
|
||||
path('module-types/<int:pk>/edit/', views.ModuleTypeEditView.as_view(), name='moduletype_edit'),
|
||||
path('module-types/<int:pk>/delete/', views.ModuleTypeDeleteView.as_view(), name='moduletype_delete'),
|
||||
path('module-types/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='moduletype_changelog', kwargs={'model': ModuleType}),
|
||||
path('module-types/<int:pk>/journal/', ObjectJournalView.as_view(), name='moduletype_journal', kwargs={'model': ModuleType}),
|
||||
|
||||
# Console port templates
|
||||
path('console-port-templates/add/', views.ConsolePortTemplateCreateView.as_view(), name='consoleporttemplate_add'),
|
||||
path('console-port-templates/edit/', views.ConsolePortTemplateBulkEditView.as_view(), name='consoleporttemplate_bulk_edit'),
|
||||
@ -183,6 +203,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,15 +250,28 @@ 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'),
|
||||
path('devices/<int:pk>/changelog/', views.DeviceChangeLogView.as_view(), name='device_changelog', kwargs={'model': Device}),
|
||||
path('devices/<int:pk>/journal/', views.DeviceJournalView.as_view(), name='device_journal', kwargs={'model': Device}),
|
||||
path('devices/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='device_changelog', kwargs={'model': Device}),
|
||||
path('devices/<int:pk>/journal/', ObjectJournalView.as_view(), name='device_journal', kwargs={'model': Device}),
|
||||
path('devices/<int:pk>/status/', views.DeviceStatusView.as_view(), name='device_status'),
|
||||
path('devices/<int:pk>/lldp-neighbors/', views.DeviceLLDPNeighborsView.as_view(), name='device_lldp_neighbors'),
|
||||
path('devices/<int:pk>/config/', views.DeviceConfigView.as_view(), name='device_config'),
|
||||
|
||||
# Modules
|
||||
path('modules/', views.ModuleListView.as_view(), name='module_list'),
|
||||
path('modules/add/', views.ModuleEditView.as_view(), name='module_add'),
|
||||
path('modules/import/', views.ModuleBulkImportView.as_view(), name='module_import'),
|
||||
path('modules/edit/', views.ModuleBulkEditView.as_view(), name='module_bulk_edit'),
|
||||
path('modules/delete/', views.ModuleBulkDeleteView.as_view(), name='module_bulk_delete'),
|
||||
path('modules/<int:pk>/', views.ModuleView.as_view(), name='module'),
|
||||
path('modules/<int:pk>/edit/', views.ModuleEditView.as_view(), name='module_edit'),
|
||||
path('modules/<int:pk>/delete/', views.ModuleDeleteView.as_view(), name='module_delete'),
|
||||
path('modules/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='module_changelog', kwargs={'model': Module}),
|
||||
path('modules/<int:pk>/journal/', ObjectJournalView.as_view(), name='module_journal', kwargs={'model': Module}),
|
||||
|
||||
# Console ports
|
||||
path('console-ports/', views.ConsolePortListView.as_view(), name='consoleport_list'),
|
||||
path('console-ports/add/', views.ConsolePortCreateView.as_view(), name='consoleport_add'),
|
||||
@ -343,6 +384,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'),
|
||||
|
@ -13,7 +13,7 @@ from django.utils.safestring import mark_safe
|
||||
from django.views.generic import View
|
||||
|
||||
from circuits.models import Circuit
|
||||
from extras.views import ObjectChangeLogView, ObjectConfigContextView, ObjectJournalView
|
||||
from extras.views import ObjectConfigContextView
|
||||
from ipam.models import ASN, IPAddress, Prefix, Service, VLAN
|
||||
from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable
|
||||
from netbox.views import generic
|
||||
@ -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, Module, ModuleBay, ModuleBayTemplate, ModuleType, PathEndpoint, Platform, PowerFeed,
|
||||
PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate, Rack, Location, RackReservation,
|
||||
RackRole, RearPort, RearPortTemplate, Region, Site, SiteGroup, VirtualChassis,
|
||||
)
|
||||
|
||||
|
||||
@ -56,6 +56,14 @@ class DeviceTypeComponentsView(DeviceComponentsView):
|
||||
return self.child_model.objects.restrict(request.user, 'view').filter(device_type=parent)
|
||||
|
||||
|
||||
class ModuleTypeComponentsView(DeviceComponentsView):
|
||||
queryset = ModuleType.objects.all()
|
||||
template_name = 'dcim/moduletype/component_templates.html'
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return self.child_model.objects.restrict(request.user, 'view').filter(module_type=parent)
|
||||
|
||||
|
||||
class BulkDisconnectView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
||||
"""
|
||||
An extendable view for disconnection console/power/interface components in bulk.
|
||||
@ -836,6 +844,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 +875,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,9 +888,14 @@ class DeviceTypeImportView(generic.ObjectImportView):
|
||||
('interfaces', forms.InterfaceTemplateImportForm),
|
||||
('rear-ports', forms.RearPortTemplateImportForm),
|
||||
('front-ports', forms.FrontPortTemplateImportForm),
|
||||
('module-bays', forms.ModuleBayTemplateImportForm),
|
||||
('device-bays', forms.DeviceBayTemplateImportForm),
|
||||
))
|
||||
|
||||
def prep_related_object_data(self, parent, data):
|
||||
data.update({'device_type': parent})
|
||||
return data
|
||||
|
||||
|
||||
class DeviceTypeBulkEditView(generic.BulkEditView):
|
||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||
@ -894,6 +914,127 @@ class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
|
||||
table = tables.DeviceTypeTable
|
||||
|
||||
|
||||
#
|
||||
# Module types
|
||||
#
|
||||
|
||||
class ModuleTypeListView(generic.ObjectListView):
|
||||
queryset = ModuleType.objects.prefetch_related('manufacturer').annotate(
|
||||
# instance_count=count_related(Module, 'module_type')
|
||||
)
|
||||
filterset = filtersets.ModuleTypeFilterSet
|
||||
filterset_form = forms.ModuleTypeFilterForm
|
||||
table = tables.ModuleTypeTable
|
||||
|
||||
|
||||
class ModuleTypeView(generic.ObjectView):
|
||||
queryset = ModuleType.objects.prefetch_related('manufacturer')
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
# instance_count = Module.objects.restrict(request.user).filter(device_type=instance).count()
|
||||
|
||||
return {
|
||||
# 'instance_count': instance_count,
|
||||
'active_tab': 'moduletype',
|
||||
}
|
||||
|
||||
|
||||
class ModuleTypeConsolePortsView(ModuleTypeComponentsView):
|
||||
child_model = ConsolePortTemplate
|
||||
table = tables.ConsolePortTemplateTable
|
||||
filterset = filtersets.ConsolePortTemplateFilterSet
|
||||
|
||||
|
||||
class ModuleTypeConsoleServerPortsView(ModuleTypeComponentsView):
|
||||
child_model = ConsoleServerPortTemplate
|
||||
table = tables.ConsoleServerPortTemplateTable
|
||||
filterset = filtersets.ConsoleServerPortTemplateFilterSet
|
||||
|
||||
|
||||
class ModuleTypePowerPortsView(ModuleTypeComponentsView):
|
||||
child_model = PowerPortTemplate
|
||||
table = tables.PowerPortTemplateTable
|
||||
filterset = filtersets.PowerPortTemplateFilterSet
|
||||
|
||||
|
||||
class ModuleTypePowerOutletsView(ModuleTypeComponentsView):
|
||||
child_model = PowerOutletTemplate
|
||||
table = tables.PowerOutletTemplateTable
|
||||
filterset = filtersets.PowerOutletTemplateFilterSet
|
||||
|
||||
|
||||
class ModuleTypeInterfacesView(ModuleTypeComponentsView):
|
||||
child_model = InterfaceTemplate
|
||||
table = tables.InterfaceTemplateTable
|
||||
filterset = filtersets.InterfaceTemplateFilterSet
|
||||
|
||||
|
||||
class ModuleTypeFrontPortsView(ModuleTypeComponentsView):
|
||||
child_model = FrontPortTemplate
|
||||
table = tables.FrontPortTemplateTable
|
||||
filterset = filtersets.FrontPortTemplateFilterSet
|
||||
|
||||
|
||||
class ModuleTypeRearPortsView(ModuleTypeComponentsView):
|
||||
child_model = RearPortTemplate
|
||||
table = tables.RearPortTemplateTable
|
||||
filterset = filtersets.RearPortTemplateFilterSet
|
||||
|
||||
|
||||
class ModuleTypeEditView(generic.ObjectEditView):
|
||||
queryset = ModuleType.objects.all()
|
||||
model_form = forms.ModuleTypeForm
|
||||
|
||||
|
||||
class ModuleTypeDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ModuleType.objects.all()
|
||||
|
||||
|
||||
class ModuleTypeImportView(generic.ObjectImportView):
|
||||
additional_permissions = [
|
||||
'dcim.add_moduletype',
|
||||
'dcim.add_consoleporttemplate',
|
||||
'dcim.add_consoleserverporttemplate',
|
||||
'dcim.add_powerporttemplate',
|
||||
'dcim.add_poweroutlettemplate',
|
||||
'dcim.add_interfacetemplate',
|
||||
'dcim.add_frontporttemplate',
|
||||
'dcim.add_rearporttemplate',
|
||||
]
|
||||
queryset = ModuleType.objects.all()
|
||||
model_form = forms.ModuleTypeImportForm
|
||||
related_object_forms = OrderedDict((
|
||||
('console-ports', forms.ConsolePortTemplateImportForm),
|
||||
('console-server-ports', forms.ConsoleServerPortTemplateImportForm),
|
||||
('power-ports', forms.PowerPortTemplateImportForm),
|
||||
('power-outlets', forms.PowerOutletTemplateImportForm),
|
||||
('interfaces', forms.InterfaceTemplateImportForm),
|
||||
('rear-ports', forms.RearPortTemplateImportForm),
|
||||
('front-ports', forms.FrontPortTemplateImportForm),
|
||||
))
|
||||
|
||||
def prep_related_object_data(self, parent, data):
|
||||
data.update({'module_type': parent})
|
||||
return data
|
||||
|
||||
|
||||
class ModuleTypeBulkEditView(generic.BulkEditView):
|
||||
queryset = ModuleType.objects.prefetch_related('manufacturer').annotate(
|
||||
# instance_count=count_related(Module, 'module_type')
|
||||
)
|
||||
filterset = filtersets.ModuleTypeFilterSet
|
||||
table = tables.ModuleTypeTable
|
||||
form = forms.ModuleTypeBulkEditForm
|
||||
|
||||
|
||||
class ModuleTypeBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = ModuleType.objects.prefetch_related('manufacturer').annotate(
|
||||
# instance_count=count_related(Module, 'module_type')
|
||||
)
|
||||
filterset = filtersets.ModuleTypeFilterSet
|
||||
table = tables.ModuleTypeTable
|
||||
|
||||
|
||||
#
|
||||
# Console port templates
|
||||
#
|
||||
@ -1132,6 +1273,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 +1563,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
|
||||
@ -1447,14 +1629,6 @@ class DeviceConfigContextView(ObjectConfigContextView):
|
||||
base_template = 'dcim/device/base.html'
|
||||
|
||||
|
||||
class DeviceChangeLogView(ObjectChangeLogView):
|
||||
base_template = 'dcim/device/base.html'
|
||||
|
||||
|
||||
class DeviceJournalView(ObjectJournalView):
|
||||
base_template = 'dcim/device/base.html'
|
||||
|
||||
|
||||
class DeviceEditView(generic.ObjectEditView):
|
||||
queryset = Device.objects.all()
|
||||
model_form = forms.DeviceForm
|
||||
@ -1503,6 +1677,49 @@ class DeviceBulkDeleteView(generic.BulkDeleteView):
|
||||
table = tables.DeviceTable
|
||||
|
||||
|
||||
#
|
||||
# Devices
|
||||
#
|
||||
|
||||
class ModuleListView(generic.ObjectListView):
|
||||
queryset = Module.objects.prefetch_related('device', 'module_type__manufacturer')
|
||||
filterset = filtersets.ModuleFilterSet
|
||||
filterset_form = forms.ModuleFilterForm
|
||||
table = tables.ModuleTable
|
||||
|
||||
|
||||
class ModuleView(generic.ObjectView):
|
||||
queryset = Module.objects.all()
|
||||
|
||||
|
||||
class ModuleEditView(generic.ObjectEditView):
|
||||
queryset = Module.objects.all()
|
||||
model_form = forms.ModuleForm
|
||||
|
||||
|
||||
class ModuleDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Module.objects.all()
|
||||
|
||||
|
||||
class ModuleBulkImportView(generic.BulkImportView):
|
||||
queryset = Module.objects.all()
|
||||
model_form = forms.ModuleCSVForm
|
||||
table = tables.ModuleTable
|
||||
|
||||
|
||||
class ModuleBulkEditView(generic.BulkEditView):
|
||||
queryset = Module.objects.prefetch_related('device', 'module_type__manufacturer')
|
||||
filterset = filtersets.ModuleFilterSet
|
||||
table = tables.ModuleTable
|
||||
form = forms.ModuleBulkEditForm
|
||||
|
||||
|
||||
class ModuleBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = Module.objects.prefetch_related('device', 'module_type__manufacturer')
|
||||
filterset = filtersets.ModuleFilterSet
|
||||
table = tables.ModuleTable
|
||||
|
||||
|
||||
#
|
||||
# Console ports
|
||||
#
|
||||
@ -1978,6 +2195,61 @@ class RearPortBulkDeleteView(generic.BulkDeleteView):
|
||||
table = tables.RearPortTable
|
||||
|
||||
|
||||
#
|
||||
# Module bays
|
||||
#
|
||||
|
||||
class ModuleBayListView(generic.ObjectListView):
|
||||
queryset = ModuleBay.objects.select_related('installed_module__module_type')
|
||||
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 +2506,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'
|
||||
|
@ -139,6 +139,7 @@ DEVICES_MENU = Menu(
|
||||
label='Devices',
|
||||
items=(
|
||||
get_model_item('dcim', 'device', 'Devices'),
|
||||
get_model_item('dcim', 'module', 'Modules'),
|
||||
get_model_item('dcim', 'devicerole', 'Device Roles'),
|
||||
get_model_item('dcim', 'platform', 'Platforms'),
|
||||
get_model_item('dcim', 'virtualchassis', 'Virtual Chassis'),
|
||||
@ -148,6 +149,7 @@ DEVICES_MENU = Menu(
|
||||
label='Device Types',
|
||||
items=(
|
||||
get_model_item('dcim', 'devicetype', 'Device Types'),
|
||||
get_model_item('dcim', 'moduletype', 'Module Types'),
|
||||
get_model_item('dcim', 'manufacturer', 'Manufacturers'),
|
||||
),
|
||||
),
|
||||
@ -161,6 +163,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']),
|
||||
),
|
||||
|
@ -319,6 +319,13 @@ class ObjectImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
||||
def get_required_permission(self):
|
||||
return get_permission_for_model(self.queryset.model, 'add')
|
||||
|
||||
def prep_related_object_data(self, parent, data):
|
||||
"""
|
||||
Hook to modify the data for related objects before it's passed to the related object form (for example, to
|
||||
assign a parent object).
|
||||
"""
|
||||
return data
|
||||
|
||||
def _create_object(self, model_form):
|
||||
|
||||
# Save the primary object
|
||||
@ -333,8 +340,8 @@ class ObjectImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
||||
|
||||
related_obj_pks = []
|
||||
for i, rel_obj_data in enumerate(model_form.data.get(field_name, list())):
|
||||
|
||||
f = related_object_form(obj, rel_obj_data)
|
||||
rel_obj_data = self.prep_related_object_data(obj, rel_obj_data)
|
||||
f = related_object_form(rel_obj_data)
|
||||
|
||||
for subfield_name, field in f.fields.items():
|
||||
if subfield_name not in rel_obj_data and hasattr(field, 'initial'):
|
||||
|
@ -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 %}">
|
||||
@ -95,6 +102,22 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% with devicebay_count=object.devicebays.count %}
|
||||
{% if devicebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'device-bays' %} active{% endif %}" href="{% url 'dcim:device_devicebays' pk=object.pk %}">Device Bays {% badge devicebay_count %}</a>
|
||||
</li>
|
||||
{% 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 interface_count=object.interfaces_count %}
|
||||
{% if interface_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
@ -151,14 +174,6 @@
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with devicebay_count=object.devicebays.count %}
|
||||
{% if devicebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'device-bays' %} active{% endif %}" href="{% url 'dcim:device_devicebays' pk=object.pk %}">Device Bays {% badge devicebay_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with inventoryitem_count=object.inventoryitems.count %}
|
||||
{% if inventoryitem_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 %}
|
||||
@ -53,6 +56,22 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% with devicebay_count=object.devicebaytemplates.count %}
|
||||
{% if devicebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'device-bay-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_devicebays' pk=object.pk %}">Device Bays {% badge devicebay_count %}</a>
|
||||
</li>
|
||||
{% 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 interface_count=object.interfacetemplates.count %}
|
||||
{% if interface_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
@ -108,12 +127,4 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with devicebay_count=object.devicebaytemplates.count %}
|
||||
{% if devicebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'device-bay-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_devicebays' pk=object.pk %}">Device Bays {% badge devicebay_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
154
netbox/templates/dcim/module.html
Normal file
154
netbox/templates/dcim/module.html
Normal file
@ -0,0 +1,154 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
{% load tz %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'dcim:module_list' %}?module_type_id={{ object.module_type.pk }}">{{ object.module_type }}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Module</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">Device Type</th>
|
||||
<td>
|
||||
<a href="{{ object.device.device_type.get_absolute_url }}">{{ object.device.device_type }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Module Type</th>
|
||||
<td>
|
||||
<a href="{{ object.module_type.get_absolute_url }}">{{ object.module_type }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Serial Number</th>
|
||||
<td class="font-monospace">{{ object.serial|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Asset Tag</th>
|
||||
<td class="font-monospace">{{ object.asset_tag|placeholder }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Components</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">Interfaces</th>
|
||||
<td>
|
||||
{% with component_count=object.interfaces.count %}
|
||||
{% if component_count %}
|
||||
<a href="{% url 'dcim:interface_list' %}?module={{ object.pk }}">{{ component_count }}</a>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Console Ports</th>
|
||||
<td>
|
||||
{% with component_count=object.consoleports.count %}
|
||||
{% if component_count %}
|
||||
<a href="{% url 'dcim:consoleport_list' %}?module={{ object.pk }}">{{ component_count }}</a>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Console Server Ports</th>
|
||||
<td>
|
||||
{% with component_count=object.consoleserverports.count %}
|
||||
{% if component_count %}
|
||||
<a href="{% url 'dcim:consoleserverport_list' %}?module={{ object.pk }}">{{ component_count }}</a>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Power Ports</th>
|
||||
<td>
|
||||
{% with component_count=object.powerports.count %}
|
||||
{% if component_count %}
|
||||
<a href="{% url 'dcim:powerport_list' %}?module={{ object.pk }}">{{ component_count }}</a>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Power Outlets</th>
|
||||
<td>
|
||||
{% with component_count=object.poweroutlets.count %}
|
||||
{% if component_count %}
|
||||
<a href="{% url 'dcim:poweroutlet_list' %}?module={{ object.pk }}">{{ component_count }}</a>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Front Ports</th>
|
||||
<td>
|
||||
{% with component_count=object.frontports.count %}
|
||||
{% if component_count %}
|
||||
<a href="{% url 'dcim:frontport_list' %}?module={{ object.pk }}">{{ component_count }}</a>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Rear Ports</th>
|
||||
<td>
|
||||
{% with component_count=object.rearports.count %}
|
||||
{% if component_count %}
|
||||
<a href="{% url 'dcim:rearport_list' %}?module={{ object.pk }}">{{ component_count }}</a>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</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 %}
|
75
netbox/templates/dcim/modulebay.html
Normal file
75
netbox/templates/dcim/modulebay.html
Normal file
@ -0,0 +1,75 @@
|
||||
{% 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="{% url 'dcim:device_modulebays' pk=object.device.pk %}">{{ 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">Position</th>
|
||||
<td>{{ object.position|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Description</th>
|
||||
<td>{{ object.description|placeholder }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
<div class="card">
|
||||
<h5 class="card-header">Installed Module</h5>
|
||||
<div class="card-body">
|
||||
{% if object.installed_module %}
|
||||
{% with module=object.installed_module %}
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">Manufacturer</th>
|
||||
<td>
|
||||
<a href="{{ module.module_type.manufacturer.get_absolute_url }}">{{ module.module_type.manufacturer }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Module Type</th>
|
||||
<td>
|
||||
<a href="{{ module.get_absolute_url }}">{{ module.module_type }}</a>
|
||||
</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 %}
|
48
netbox/templates/dcim/moduletype.html
Normal file
48
netbox/templates/dcim/moduletype.html
Normal file
@ -0,0 +1,48 @@
|
||||
{% extends 'dcim/moduletype/base.html' %}
|
||||
{% load buttons %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Module Type</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<td>Manufacturer</td>
|
||||
<td><a href="{{ object.manufacturer.get_absolute_url }}">{{ object.manufacturer }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Model Name</td>
|
||||
<td>{{ object.model }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Part Number</td>
|
||||
<td>{{ object.part_number|placeholder }}</td>
|
||||
</tr>
|
||||
{% comment %}
|
||||
<tr>
|
||||
<td>Instances</td>
|
||||
<td><a href="{% url 'dcim:module_list' %}?module_type_id={{ object.pk }}">{{ instance_count }}</a></td>
|
||||
</tr>
|
||||
{% endcomment %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
108
netbox/templates/dcim/moduletype/base.html
Normal file
108
netbox/templates/dcim/moduletype/base.html
Normal file
@ -0,0 +1,108 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load buttons %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
|
||||
{% block title %}{{ object.manufacturer }} {{ object.model }}{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
<li class="breadcrumb-item"><a href="{% url 'dcim:moduletype_list' %}?manufacturer_id={{ object.manufacturer.pk }}">{{ object.manufacturer }}</a></li>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_controls %}
|
||||
{% if perms.dcim.change_devicetype %}
|
||||
<div class="dropdown">
|
||||
<button type="button" class="btn btn-primary btn-sm dropdown-toggle"data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Components
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% if perms.dcim.add_consoleporttemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:consoleporttemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_consoleports">Console Ports</a></li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_consoleserverporttemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:consoleserverporttemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_consoleserverports">Console Server Ports</a></li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_powerporttemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:powerporttemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_powerports">Power Ports</a></li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_poweroutlettemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:poweroutlettemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_poweroutlets">Power Outlets</a></li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_interfacetemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:interfacetemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_interfaces">Interfaces</a></li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_frontporttemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:frontporttemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_frontports">Front Ports</a></li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_rearporttemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:rearporttemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_rearports">Rear Ports</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block tab_items %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a href="{% url 'dcim:moduletype' pk=object.pk %}" class="nav-link{% if active_tab == 'moduletype' %} active{% endif %}">
|
||||
Module Type
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% with interface_count=object.interfacetemplates.count %}
|
||||
{% if interface_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'interface-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with frontport_count=object.frontporttemplates.count %}
|
||||
{% if frontport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'front-port-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_frontports' pk=object.pk %}">Front Ports {% badge frontport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with rearport_count=object.rearporttemplates.count %}
|
||||
{% if rearport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'rear-port-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_rearports' pk=object.pk %}">Rear Ports {% badge rearport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with consoleport_count=object.consoleporttemplates.count %}
|
||||
{% if consoleport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'console-port-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_consoleports' pk=object.pk %}">Console Ports {% badge consoleport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with consoleserverport_count=object.consoleserverporttemplates.count %}
|
||||
{% if consoleserverport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'console-server-port-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_consoleserverports' pk=object.pk %}">Console Server Ports {% badge consoleserverport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with powerport_count=object.powerporttemplates.count %}
|
||||
{% if powerport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'power-port-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_powerports' pk=object.pk %}">Power Ports {% badge powerport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with poweroutlet_count=object.poweroutlettemplates.count %}
|
||||
{% if poweroutlet_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'power-outlet-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_poweroutlets' pk=object.pk %}">Power Outlets {% badge poweroutlet_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
44
netbox/templates/dcim/moduletype/component_templates.html
Normal file
44
netbox/templates/dcim/moduletype/component_templates.html
Normal file
@ -0,0 +1,44 @@
|
||||
{% extends 'dcim/moduletype/base.html' %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load helpers %}
|
||||
|
||||
{% block content %}
|
||||
{% if perms.dcim.change_moduletype %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="card">
|
||||
<h5 class="card-header">{{ title }}</h5>
|
||||
<div class="card-body" id="object_list">
|
||||
{% include 'htmx/table.html' %}
|
||||
</div>
|
||||
<div class="card-footer noprint">
|
||||
{% if table.rows %}
|
||||
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_rename" %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-warning">
|
||||
<span class="mdi mdi-pencil-outline" aria-hidden="true"></span> Rename
|
||||
</button>
|
||||
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_edit" %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-warning">
|
||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||
</button>
|
||||
<button type="submit" name="_delete" formaction="{% url table.Meta.model|viewname:"bulk_delete" %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-danger">
|
||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
||||
</button>
|
||||
{% endif %}
|
||||
<div class="float-end">
|
||||
<a href="{% url table.Meta.model|viewname:"add" %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_{{ tab }}" class="btn btn-primary btn-sm">
|
||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i>
|
||||
Add {{ title }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="card">
|
||||
<h5 class="card-header">{{ title }}</h5>
|
||||
<div class="card-body" id="object_list">
|
||||
{% include 'htmx/table.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
Loading…
Reference in New Issue
Block a user