mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
Merge branch 'develop' into feature
This commit is contained in:
commit
1eb0e5d307
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.3.2
|
placeholder: v3.3.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.3.2
|
placeholder: v3.3.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
@ -1,24 +1,35 @@
|
|||||||
# NetBox v3.3
|
# NetBox v3.3
|
||||||
|
|
||||||
## v3.3.3 (FUTURE)
|
## v3.3.4 (FUTURE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v3.3.3 (2022-09-15)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
* [#8580](https://github.com/netbox-community/netbox/issues/8580) - Add `occupied` filter for cabled objects to filter by cable or `mark_connected`
|
* [#8580](https://github.com/netbox-community/netbox/issues/8580) - Add `occupied` filter for cabled objects to filter by cable or `mark_connected`
|
||||||
* [#9577](https://github.com/netbox-community/netbox/issues/9577) - Add `has_front_image` and `has_rear_image` filters for device types
|
* [#9577](https://github.com/netbox-community/netbox/issues/9577) - Add `has_front_image` and `has_rear_image` filters for device types
|
||||||
* [#10268](https://github.com/netbox-community/netbox/issues/10268) - Omit trailing ".0" in device positions within UI
|
* [#10268](https://github.com/netbox-community/netbox/issues/10268) - Omit trailing ".0" in device positions within UI
|
||||||
|
* [#10359](https://github.com/netbox-community/netbox/issues/10359) - Add region and site group columns to the devices table
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* [#9231](https://github.com/netbox-community/netbox/issues/9231) - Fix `empty` lookup expression for string filters
|
* [#9231](https://github.com/netbox-community/netbox/issues/9231) - Fix `empty` lookup expression for string filters
|
||||||
|
* [#10247](https://github.com/netbox-community/netbox/issues/10247) - Allow changing the pre-populated device/VM when creating new components
|
||||||
* [#10250](https://github.com/netbox-community/netbox/issues/10250) - Fix exception when CableTermination validation fails during bulk import of cables
|
* [#10250](https://github.com/netbox-community/netbox/issues/10250) - Fix exception when CableTermination validation fails during bulk import of cables
|
||||||
|
* [#10258](https://github.com/netbox-community/netbox/issues/10258) - Enable the use of reports & scripts packaged in submodules
|
||||||
* [#10259](https://github.com/netbox-community/netbox/issues/10259) - Fix `NoReverseMatch` exception when listing available prefixes with "flat" column displayed
|
* [#10259](https://github.com/netbox-community/netbox/issues/10259) - Fix `NoReverseMatch` exception when listing available prefixes with "flat" column displayed
|
||||||
* [#10270](https://github.com/netbox-community/netbox/issues/10270) - Fix custom field validation when creating new services
|
* [#10270](https://github.com/netbox-community/netbox/issues/10270) - Fix custom field validation when creating new services
|
||||||
* [#10278](https://github.com/netbox-community/netbox/issues/10278) - Fix "create & add another" for image attachments
|
* [#10278](https://github.com/netbox-community/netbox/issues/10278) - Fix "create & add another" for image attachments
|
||||||
* [#10294](https://github.com/netbox-community/netbox/issues/10294) - Fix spurious changelog diff for interface WWN field
|
* [#10294](https://github.com/netbox-community/netbox/issues/10294) - Fix spurious changelog diff for interface WWN field
|
||||||
* [#10304](https://github.com/netbox-community/netbox/issues/10304) - Enable cloning for custom fields & custom links
|
* [#10304](https://github.com/netbox-community/netbox/issues/10304) - Enable cloning for custom fields & custom links
|
||||||
|
* [#10305](https://github.com/netbox-community/netbox/issues/10305) - Fix Virtual Chassis master field cannot be null according to the API
|
||||||
* [#10307](https://github.com/netbox-community/netbox/issues/10307) - Correct value for "Passive 48V (4-pair)" PoE type selection
|
* [#10307](https://github.com/netbox-community/netbox/issues/10307) - Correct value for "Passive 48V (4-pair)" PoE type selection
|
||||||
* [#10333](https://github.com/netbox-community/netbox/issues/10333) - Show available values for `ui_visibility` field of CustomField for CSV import
|
* [#10333](https://github.com/netbox-community/netbox/issues/10333) - Show available values for `ui_visibility` field of CustomField for CSV import
|
||||||
|
* [#10337](https://github.com/netbox-community/netbox/issues/10337) - Display SSO links when local authentication fails
|
||||||
|
* [#10353](https://github.com/netbox-community/netbox/issues/10353) - Table action buttons should reserve return URL parameters
|
||||||
|
* [#10362](https://github.com/netbox-community/netbox/issues/10362) - Correct display of custom fields when editing an L2VPN termination
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -1076,7 +1076,7 @@ class CablePathSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class VirtualChassisSerializer(NetBoxModelSerializer):
|
class VirtualChassisSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
||||||
master = NestedDeviceSerializer(required=False)
|
master = NestedDeviceSerializer(required=False, allow_null=True, default=None)
|
||||||
member_count = serializers.IntegerField(read_only=True)
|
member_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -3,7 +3,7 @@ from django import forms
|
|||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from extras.forms import CustomFieldsMixin
|
from extras.forms import CustomFieldsMixin
|
||||||
from extras.models import Tag
|
from extras.models import Tag
|
||||||
from utilities.forms import DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model
|
from utilities.forms import BootstrapMixin, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model
|
||||||
from .object_create import ComponentCreateForm
|
from .object_create import ComponentCreateForm
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -24,7 +24,7 @@ __all__ = (
|
|||||||
# Device components
|
# Device components
|
||||||
#
|
#
|
||||||
|
|
||||||
class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm):
|
class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldsMixin, ComponentCreateForm):
|
||||||
pk = forms.ModelMultipleChoiceField(
|
pk = forms.ModelMultipleChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
@ -37,6 +37,7 @@ class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm):
|
|||||||
queryset=Tag.objects.all(),
|
queryset=Tag.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
replication_fields = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortBulkCreateForm(
|
class ConsolePortBulkCreateForm(
|
||||||
@ -44,7 +45,7 @@ class ConsolePortBulkCreateForm(
|
|||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
field_order = ('name_pattern', 'label_pattern', 'type', 'mark_connected', 'description', 'tags')
|
field_order = ('name', 'label', 'type', 'mark_connected', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortBulkCreateForm(
|
class ConsoleServerPortBulkCreateForm(
|
||||||
@ -52,7 +53,7 @@ class ConsoleServerPortBulkCreateForm(
|
|||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
field_order = ('name_pattern', 'label_pattern', 'type', 'speed', 'description', 'tags')
|
field_order = ('name', 'label', 'type', 'speed', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class PowerPortBulkCreateForm(
|
class PowerPortBulkCreateForm(
|
||||||
@ -60,7 +61,7 @@ class PowerPortBulkCreateForm(
|
|||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
field_order = ('name_pattern', 'label_pattern', 'type', 'maximum_draw', 'allocated_draw', 'description', 'tags')
|
field_order = ('name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletBulkCreateForm(
|
class PowerOutletBulkCreateForm(
|
||||||
@ -68,7 +69,7 @@ class PowerOutletBulkCreateForm(
|
|||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
field_order = ('name_pattern', 'label_pattern', 'type', 'feed_leg', 'description', 'tags')
|
field_order = ('name', 'label', 'type', 'feed_leg', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class InterfaceBulkCreateForm(
|
class InterfaceBulkCreateForm(
|
||||||
@ -79,7 +80,7 @@ class InterfaceBulkCreateForm(
|
|||||||
):
|
):
|
||||||
model = Interface
|
model = Interface
|
||||||
field_order = (
|
field_order = (
|
||||||
'name_pattern', 'label_pattern', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode',
|
'name', 'label', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode',
|
||||||
'poe_type', 'mark_connected', 'description', 'tags',
|
'poe_type', 'mark_connected', 'description', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -96,13 +97,13 @@ class RearPortBulkCreateForm(
|
|||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = RearPort
|
model = RearPort
|
||||||
field_order = ('name_pattern', 'label_pattern', 'type', 'positions', 'mark_connected', 'description', 'tags')
|
field_order = ('name', 'label', 'type', 'positions', 'mark_connected', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
|
class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
field_order = ('name_pattern', 'label_pattern', 'position_pattern', 'description', 'tags')
|
field_order = ('name', 'label', 'position_pattern', 'description', 'tags')
|
||||||
|
replication_fields = ('name', 'label', 'position')
|
||||||
position_pattern = ExpandableNameField(
|
position_pattern = ExpandableNameField(
|
||||||
label='Position',
|
label='Position',
|
||||||
required=False,
|
required=False,
|
||||||
@ -112,7 +113,7 @@ class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
|
|||||||
|
|
||||||
class DeviceBayBulkCreateForm(DeviceBulkAddComponentForm):
|
class DeviceBayBulkCreateForm(DeviceBulkAddComponentForm):
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
field_order = ('name_pattern', 'label_pattern', 'description', 'tags')
|
field_order = ('name', 'label', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemBulkCreateForm(
|
class InventoryItemBulkCreateForm(
|
||||||
@ -121,6 +122,6 @@ class InventoryItemBulkCreateForm(
|
|||||||
):
|
):
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
field_order = (
|
field_order = (
|
||||||
'name_pattern', 'label_pattern', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
|
'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
|
||||||
'description', 'tags',
|
'description', 'tags',
|
||||||
)
|
)
|
||||||
|
@ -986,47 +986,74 @@ class VCMemberSelectForm(BootstrapMixin, forms.Form):
|
|||||||
# Device component templates
|
# Device component templates
|
||||||
#
|
#
|
||||||
|
|
||||||
|
class ComponentTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||||
|
device_type = DynamicModelChoiceField(
|
||||||
|
queryset=DeviceType.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Disable reassignment of DeviceType when editing an existing instance
|
||||||
|
if self.instance.pk:
|
||||||
|
self.fields['device_type'].disabled = True
|
||||||
|
|
||||||
|
|
||||||
|
class ModularComponentTemplateForm(ComponentTemplateForm):
|
||||||
|
module_type = DynamicModelChoiceField(
|
||||||
|
queryset=ModuleType.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolePortTemplateForm(ModularComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect,
|
'type': StaticSelect,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect,
|
'type': StaticSelect,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
class PowerPortTemplateForm(ModularComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
|
class PowerOutletTemplateForm(ModularComponentTemplateForm):
|
||||||
power_port = DynamicModelChoiceField(
|
power_port = DynamicModelChoiceField(
|
||||||
queryset=PowerPortTemplate.objects.all(),
|
queryset=PowerPortTemplate.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1035,35 +1062,40 @@ class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'feed_leg': StaticSelect(),
|
'feed_leg': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
|
class InterfaceTemplateForm(ModularComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description')),
|
||||||
|
('PoE', ('poe_mode', 'poe_type'))
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'poe_mode', 'poe_type',
|
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'poe_mode', 'poe_type',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'poe_mode': StaticSelect(),
|
'poe_mode': StaticSelect(),
|
||||||
'poe_type': StaticSelect(),
|
'poe_type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
class FrontPortTemplateForm(ModularComponentTemplateForm):
|
||||||
rear_port = DynamicModelChoiceField(
|
rear_port = DynamicModelChoiceField(
|
||||||
queryset=RearPortTemplate.objects.all(),
|
queryset=RearPortTemplate.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1073,6 +1105,13 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
|
||||||
|
'description',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FrontPortTemplate
|
model = FrontPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
@ -1080,48 +1119,50 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
'description',
|
'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
class RearPortTemplateForm(ModularComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RearPortTemplate
|
model = RearPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description',
|
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayTemplateForm(BootstrapMixin, forms.ModelForm):
|
class ModuleBayTemplateForm(ComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'name', 'label', 'position', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleBayTemplate
|
model = ModuleBayTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'label', 'position', 'description',
|
'device_type', 'name', 'label', 'position', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm):
|
class DeviceBayTemplateForm(ComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'name', 'label', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBayTemplate
|
model = DeviceBayTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'label', 'description',
|
'device_type', 'name', 'label', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
|
class InventoryItemTemplateForm(ComponentTemplateForm):
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
queryset=InventoryItemTemplate.objects.all(),
|
queryset=InventoryItemTemplate.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1148,22 +1189,39 @@ class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
widget=forms.HiddenInput
|
widget=forms.HiddenInput
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||||
|
'component_type', 'component_id',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItemTemplate
|
model = InventoryItemTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||||
'component_type', 'component_id',
|
'component_type', 'component_id',
|
||||||
]
|
]
|
||||||
widgets = {
|
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Device components
|
# Device components
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConsolePortForm(NetBoxModelForm):
|
class DeviceComponentForm(NetBoxModelForm):
|
||||||
|
device = DynamicModelChoiceField(
|
||||||
|
queryset=Device.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Disable reassignment of Device when editing an existing instance
|
||||||
|
if self.instance.pk:
|
||||||
|
self.fields['device'].disabled = True
|
||||||
|
|
||||||
|
|
||||||
|
class ModularDeviceComponentForm(DeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
module = DynamicModelChoiceField(
|
||||||
queryset=Module.objects.all(),
|
queryset=Module.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1172,25 +1230,31 @@ class ConsolePortForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolePortForm(ModularDeviceComponentForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'speed': StaticSelect(),
|
'speed': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortForm(NetBoxModelForm):
|
class ConsoleServerPortForm(ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
|
||||||
queryset=Module.objects.all(),
|
fieldsets = (
|
||||||
required=False,
|
(None, (
|
||||||
query_params={
|
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||||
'device_id': '$device',
|
)),
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1199,42 +1263,32 @@ class ConsoleServerPortForm(NetBoxModelForm):
|
|||||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'speed': StaticSelect(),
|
'speed': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerPortForm(NetBoxModelForm):
|
class PowerPortForm(ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
|
||||||
queryset=Module.objects.all(),
|
fieldsets = (
|
||||||
required=False,
|
(None, (
|
||||||
query_params={
|
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
|
||||||
'device_id': '$device',
|
'description', 'tags',
|
||||||
}
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
|
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
|
||||||
'description',
|
'description', 'tags',
|
||||||
'tags',
|
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletForm(NetBoxModelForm):
|
class PowerOutletForm(ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
|
||||||
queryset=Module.objects.all(),
|
|
||||||
required=False,
|
|
||||||
query_params={
|
|
||||||
'device_id': '$device',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
power_port = DynamicModelChoiceField(
|
power_port = DynamicModelChoiceField(
|
||||||
queryset=PowerPort.objects.all(),
|
queryset=PowerPort.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1243,6 +1297,13 @@ class PowerOutletForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
|
||||||
|
'tags',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = [
|
fields = [
|
||||||
@ -1250,20 +1311,12 @@ class PowerOutletForm(NetBoxModelForm):
|
|||||||
'tags',
|
'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'feed_leg': StaticSelect(),
|
'feed_leg': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
|
||||||
queryset=Module.objects.all(),
|
|
||||||
required=False,
|
|
||||||
query_params={
|
|
||||||
'device_id': '$device',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1338,7 +1391,7 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Interface', ('device', 'module', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')),
|
('Interface', ('device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')),
|
||||||
('Addressing', ('vrf', 'mac_address', 'wwn')),
|
('Addressing', ('vrf', 'mac_address', 'wwn')),
|
||||||
('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
||||||
('Related Interfaces', ('parent', 'bridge', 'lag')),
|
('Related Interfaces', ('parent', 'bridge', 'lag')),
|
||||||
@ -1358,7 +1411,6 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
|||||||
'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
|
'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'speed': SelectSpeedWidget(),
|
'speed': SelectSpeedWidget(),
|
||||||
'poe_mode': StaticSelect(),
|
'poe_mode': StaticSelect(),
|
||||||
@ -1388,14 +1440,7 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
|||||||
self.fields['bridge'].widget.add_query_param('device_id', device.virtual_chassis.master.pk)
|
self.fields['bridge'].widget.add_query_param('device_id', device.virtual_chassis.master.pk)
|
||||||
|
|
||||||
|
|
||||||
class FrontPortForm(NetBoxModelForm):
|
class FrontPortForm(ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
|
||||||
queryset=Module.objects.all(),
|
|
||||||
required=False,
|
|
||||||
query_params={
|
|
||||||
'device_id': '$device',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
rear_port = DynamicModelChoiceField(
|
rear_port = DynamicModelChoiceField(
|
||||||
queryset=RearPort.objects.all(),
|
queryset=RearPort.objects.all(),
|
||||||
query_params={
|
query_params={
|
||||||
@ -1403,6 +1448,13 @@ class FrontPortForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
|
||||||
|
'description', 'tags',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
fields = [
|
fields = [
|
||||||
@ -1410,18 +1462,15 @@ class FrontPortForm(NetBoxModelForm):
|
|||||||
'description', 'tags',
|
'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class RearPortForm(NetBoxModelForm):
|
class RearPortForm(ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
fieldsets = (
|
||||||
queryset=Module.objects.all(),
|
(None, (
|
||||||
required=False,
|
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
|
||||||
query_params={
|
)),
|
||||||
'device_id': '$device',
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1430,33 +1479,32 @@ class RearPortForm(NetBoxModelForm):
|
|||||||
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
|
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayForm(NetBoxModelForm):
|
class ModuleBayForm(DeviceComponentForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device', 'name', 'label', 'position', 'description', 'tags',)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'name', 'label', 'position', 'description', 'tags',
|
'device', 'name', 'label', 'position', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayForm(NetBoxModelForm):
|
class DeviceBayForm(DeviceComponentForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device', 'name', 'label', 'description', 'tags',)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'name', 'label', 'description', 'tags',
|
'device', 'name', 'label', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
|
class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
|
||||||
@ -1479,10 +1527,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
|
|||||||
).exclude(pk=device_bay.device.pk)
|
).exclude(pk=device_bay.device.pk)
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemForm(NetBoxModelForm):
|
class InventoryItemForm(DeviceComponentForm):
|
||||||
device = DynamicModelChoiceField(
|
|
||||||
queryset=Device.objects.all()
|
|
||||||
)
|
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
queryset=InventoryItem.objects.all(),
|
queryset=InventoryItem.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -2,46 +2,56 @@ from django import forms
|
|||||||
|
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from utilities.forms import (
|
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
|
||||||
BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField,
|
from . import models as model_forms
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ComponentTemplateCreateForm',
|
'ComponentCreateForm',
|
||||||
'DeviceComponentCreateForm',
|
'ConsolePortCreateForm',
|
||||||
|
'ConsolePortTemplateCreateForm',
|
||||||
|
'ConsoleServerPortCreateForm',
|
||||||
|
'ConsoleServerPortTemplateCreateForm',
|
||||||
|
'DeviceBayCreateForm',
|
||||||
|
'DeviceBayTemplateCreateForm',
|
||||||
'FrontPortCreateForm',
|
'FrontPortCreateForm',
|
||||||
'FrontPortTemplateCreateForm',
|
'FrontPortTemplateCreateForm',
|
||||||
|
'InterfaceCreateForm',
|
||||||
|
'InterfaceTemplateCreateForm',
|
||||||
'InventoryItemCreateForm',
|
'InventoryItemCreateForm',
|
||||||
'ModularComponentTemplateCreateForm',
|
'InventoryItemTemplateCreateForm',
|
||||||
'ModuleBayCreateForm',
|
'ModuleBayCreateForm',
|
||||||
'ModuleBayTemplateCreateForm',
|
'ModuleBayTemplateCreateForm',
|
||||||
|
'PowerOutletCreateForm',
|
||||||
|
'PowerOutletTemplateCreateForm',
|
||||||
|
'PowerPortCreateForm',
|
||||||
|
'PowerPortTemplateCreateForm',
|
||||||
|
'RearPortCreateForm',
|
||||||
|
'RearPortTemplateCreateForm',
|
||||||
'VirtualChassisCreateForm',
|
'VirtualChassisCreateForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ComponentCreateForm(BootstrapMixin, forms.Form):
|
class ComponentCreateForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
Subclass this form when facilitating the creation of one or more device component or component templates based on
|
Subclass this form when facilitating the creation of one or more component or component template objects based on
|
||||||
a name pattern.
|
a name pattern.
|
||||||
"""
|
"""
|
||||||
name_pattern = ExpandableNameField(
|
name = ExpandableNameField()
|
||||||
label='Name'
|
label = ExpandableNameField(
|
||||||
)
|
|
||||||
label_pattern = ExpandableNameField(
|
|
||||||
label='Label',
|
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
|
help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Identify the fields which support replication (i.e. ExpandableNameFields). This is referenced by
|
||||||
|
# ComponentCreateView when creating objects.
|
||||||
|
replication_fields = ('name', 'label')
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# Validate that all patterned fields generate an equal number of values
|
# Validate that all replication fields generate an equal number of values
|
||||||
patterned_fields = [
|
pattern_count = len(self.cleaned_data[self.replication_fields[0]])
|
||||||
field_name for field_name in self.fields if field_name.endswith('_pattern')
|
for field_name in self.replication_fields:
|
||||||
]
|
|
||||||
pattern_count = len(self.cleaned_data['name_pattern'])
|
|
||||||
for field_name in patterned_fields:
|
|
||||||
value_count = len(self.cleaned_data[field_name])
|
value_count = len(self.cleaned_data[field_name])
|
||||||
if self.cleaned_data[field_name] and value_count != pattern_count:
|
if self.cleaned_data[field_name] and value_count != pattern_count:
|
||||||
raise forms.ValidationError({
|
raise forms.ValidationError({
|
||||||
@ -50,56 +60,55 @@ class ComponentCreateForm(BootstrapMixin, forms.Form):
|
|||||||
}, code='label_pattern_mismatch')
|
}, code='label_pattern_mismatch')
|
||||||
|
|
||||||
|
|
||||||
class ComponentTemplateCreateForm(ComponentCreateForm):
|
#
|
||||||
"""
|
# Device component templates
|
||||||
Creation form for component templates that can be assigned only to a DeviceType.
|
#
|
||||||
"""
|
|
||||||
device_type = DynamicModelChoiceField(
|
class ConsolePortTemplateCreateForm(ComponentCreateForm, model_forms.ConsolePortTemplateForm):
|
||||||
queryset=DeviceType.objects.all(),
|
|
||||||
)
|
class Meta(model_forms.ConsolePortTemplateForm.Meta):
|
||||||
field_order = ('device_type', 'name_pattern', 'label_pattern')
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
class ModularComponentTemplateCreateForm(ComponentCreateForm):
|
class ConsoleServerPortTemplateCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortTemplateForm):
|
||||||
"""
|
|
||||||
Creation form for component templates that can be assigned to either a DeviceType *or* a ModuleType.
|
class Meta(model_forms.ConsoleServerPortTemplateForm.Meta):
|
||||||
"""
|
exclude = ('name', 'label')
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name',
|
|
||||||
help_text="""
|
|
||||||
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
|
||||||
are not supported. Example: <code>[ge,xe]-0/0/[0-9]</code>. {module} is accepted as a substitution for
|
|
||||||
the module bay position.
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
device_type = DynamicModelChoiceField(
|
|
||||||
queryset=DeviceType.objects.all(),
|
|
||||||
required=False
|
|
||||||
)
|
|
||||||
module_type = DynamicModelChoiceField(
|
|
||||||
queryset=ModuleType.objects.all(),
|
|
||||||
required=False
|
|
||||||
)
|
|
||||||
field_order = ('device_type', 'module_type', 'name_pattern', 'label_pattern')
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceComponentCreateForm(ComponentCreateForm):
|
class PowerPortTemplateCreateForm(ComponentCreateForm, model_forms.PowerPortTemplateForm):
|
||||||
device = DynamicModelChoiceField(
|
|
||||||
queryset=Device.objects.all()
|
class Meta(model_forms.PowerPortTemplateForm.Meta):
|
||||||
)
|
exclude = ('name', 'label')
|
||||||
field_order = ('device', 'name_pattern', 'label_pattern')
|
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
class PowerOutletTemplateCreateForm(ComponentCreateForm, model_forms.PowerOutletTemplateForm):
|
||||||
rear_port_set = forms.MultipleChoiceField(
|
|
||||||
|
class Meta(model_forms.PowerOutletTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceTemplateCreateForm(ComponentCreateForm, model_forms.InterfaceTemplateForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.InterfaceTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemplateForm):
|
||||||
|
rear_port = forms.MultipleChoiceField(
|
||||||
choices=[],
|
choices=[],
|
||||||
label='Rear ports',
|
label='Rear ports',
|
||||||
help_text='Select one rear port assignment for each front port being created.',
|
help_text='Select one rear port assignment for each front port being created.',
|
||||||
)
|
)
|
||||||
field_order = (
|
|
||||||
'device_type', 'name_pattern', 'label_pattern', 'rear_port_set',
|
# Override fieldsets from FrontPortTemplateForm to omit rear_port_position
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'description')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta(model_forms.FrontPortTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label', 'rear_port', 'rear_port_position')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@ -130,12 +139,12 @@ class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
|||||||
choices.append(
|
choices.append(
|
||||||
('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
|
('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
|
||||||
)
|
)
|
||||||
self.fields['rear_port_set'].choices = choices
|
self.fields['rear_port'].choices = choices
|
||||||
|
|
||||||
def get_iterative_data(self, iteration):
|
def get_iterative_data(self, iteration):
|
||||||
|
|
||||||
# Assign rear port and position from selected set
|
# Assign rear port and position from selected set
|
||||||
rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':')
|
rear_port, position = self.cleaned_data['rear_port'][iteration].split(':')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'rear_port': int(rear_port),
|
'rear_port': int(rear_port),
|
||||||
@ -143,16 +152,94 @@ class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class FrontPortCreateForm(DeviceComponentCreateForm):
|
class RearPortTemplateCreateForm(ComponentCreateForm, model_forms.RearPortTemplateForm):
|
||||||
rear_port_set = forms.MultipleChoiceField(
|
|
||||||
|
class Meta(model_forms.RearPortTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayTemplateCreateForm(ComponentCreateForm, model_forms.DeviceBayTemplateForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.DeviceBayTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleBayTemplateCreateForm(ComponentCreateForm, model_forms.ModuleBayTemplateForm):
|
||||||
|
position = ExpandableNameField(
|
||||||
|
label='Position',
|
||||||
|
required=False,
|
||||||
|
help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
|
||||||
|
)
|
||||||
|
replication_fields = ('name', 'label', 'position')
|
||||||
|
|
||||||
|
class Meta(model_forms.ModuleBayTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label', 'position')
|
||||||
|
|
||||||
|
|
||||||
|
class InventoryItemTemplateCreateForm(ComponentCreateForm, model_forms.InventoryItemTemplateForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.InventoryItemTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Device components
|
||||||
|
#
|
||||||
|
|
||||||
|
class ConsolePortCreateForm(ComponentCreateForm, model_forms.ConsolePortForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.ConsolePortForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleServerPortCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.ConsoleServerPortForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class PowerPortCreateForm(ComponentCreateForm, model_forms.PowerPortForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.PowerPortForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class PowerOutletCreateForm(ComponentCreateForm, model_forms.PowerOutletForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.PowerOutletForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceCreateForm(ComponentCreateForm, model_forms.InterfaceForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.InterfaceForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if 'module' in self.fields:
|
||||||
|
self.fields['name'].help_text += ' The string <code>{module}</code> will be replaced with the position ' \
|
||||||
|
'of the assigned module, if any'
|
||||||
|
|
||||||
|
|
||||||
|
class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
|
||||||
|
rear_port = forms.MultipleChoiceField(
|
||||||
choices=[],
|
choices=[],
|
||||||
label='Rear ports',
|
label='Rear ports',
|
||||||
help_text='Select one rear port assignment for each front port being created.',
|
help_text='Select one rear port assignment for each front port being created.',
|
||||||
)
|
)
|
||||||
field_order = (
|
|
||||||
'device', 'name_pattern', 'label_pattern', 'rear_port_set',
|
# Override fieldsets from FrontPortForm to omit rear_port_position
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'mark_connected', 'description', 'tags',
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta(model_forms.FrontPortForm.Meta):
|
||||||
|
exclude = ('name', 'label', 'rear_port', 'rear_port_position')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@ -176,12 +263,12 @@ class FrontPortCreateForm(DeviceComponentCreateForm):
|
|||||||
choices.append(
|
choices.append(
|
||||||
('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
|
('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
|
||||||
)
|
)
|
||||||
self.fields['rear_port_set'].choices = choices
|
self.fields['rear_port'].choices = choices
|
||||||
|
|
||||||
def get_iterative_data(self, iteration):
|
def get_iterative_data(self, iteration):
|
||||||
|
|
||||||
# Assign rear port and position from selected set
|
# Assign rear port and position from selected set
|
||||||
rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':')
|
rear_port, position = self.cleaned_data['rear_port'][iteration].split(':')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'rear_port': int(rear_port),
|
'rear_port': int(rear_port),
|
||||||
@ -189,28 +276,39 @@ class FrontPortCreateForm(DeviceComponentCreateForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayTemplateCreateForm(ComponentTemplateCreateForm):
|
class RearPortCreateForm(ComponentCreateForm, model_forms.RearPortForm):
|
||||||
position_pattern = ExpandableNameField(
|
|
||||||
|
class Meta(model_forms.RearPortForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayCreateForm(ComponentCreateForm, model_forms.DeviceBayForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.DeviceBayForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleBayCreateForm(ComponentCreateForm, model_forms.ModuleBayForm):
|
||||||
|
position = ExpandableNameField(
|
||||||
label='Position',
|
label='Position',
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
|
help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
|
||||||
)
|
)
|
||||||
field_order = ('device_type', 'name_pattern', 'label_pattern', 'position_pattern')
|
replication_fields = ('name', 'label', 'position')
|
||||||
|
|
||||||
|
class Meta(model_forms.ModuleBayForm.Meta):
|
||||||
|
exclude = ('name', 'label', 'position')
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayCreateForm(DeviceComponentCreateForm):
|
class InventoryItemCreateForm(ComponentCreateForm, model_forms.InventoryItemForm):
|
||||||
position_pattern = ExpandableNameField(
|
|
||||||
label='Position',
|
class Meta(model_forms.InventoryItemForm.Meta):
|
||||||
required=False,
|
exclude = ('name', 'label')
|
||||||
help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
|
|
||||||
)
|
|
||||||
field_order = ('device', 'name_pattern', 'label_pattern', 'position_pattern')
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemCreateForm(ComponentCreateForm):
|
#
|
||||||
# Device is assigned by the model form
|
# Virtual chassis
|
||||||
field_order = ('name_pattern', 'label_pattern')
|
#
|
||||||
|
|
||||||
|
|
||||||
class VirtualChassisCreateForm(NetBoxModelForm):
|
class VirtualChassisCreateForm(NetBoxModelForm):
|
||||||
region = DynamicModelChoiceField(
|
region = DynamicModelChoiceField(
|
||||||
|
@ -908,18 +908,20 @@ class FrontPort(ModularComponentModel, CabledObjectModel):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# Validate rear port assignment
|
if hasattr(self, 'rear_port'):
|
||||||
if self.rear_port.device != self.device:
|
|
||||||
raise ValidationError({
|
|
||||||
"rear_port": f"Rear port ({self.rear_port}) must belong to the same device"
|
|
||||||
})
|
|
||||||
|
|
||||||
# Validate rear port position assignment
|
# Validate rear port assignment
|
||||||
if self.rear_port_position > self.rear_port.positions:
|
if self.rear_port.device != self.device:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"rear_port_position": f"Invalid rear port position ({self.rear_port_position}): Rear port "
|
"rear_port": f"Rear port ({self.rear_port}) must belong to the same device"
|
||||||
f"{self.rear_port.name} has only {self.rear_port.positions} positions"
|
})
|
||||||
})
|
|
||||||
|
# Validate rear port position assignment
|
||||||
|
if self.rear_port_position > self.rear_port.positions:
|
||||||
|
raise ValidationError({
|
||||||
|
"rear_port_position": f"Invalid rear port position ({self.rear_port_position}): Rear port "
|
||||||
|
f"{self.rear_port.name} has only {self.rear_port.positions} positions"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class RearPort(ModularComponentModel, CabledObjectModel):
|
class RearPort(ModularComponentModel, CabledObjectModel):
|
||||||
|
@ -143,6 +143,15 @@ class DeviceTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
template_code=DEVICE_LINK
|
template_code=DEVICE_LINK
|
||||||
)
|
)
|
||||||
status = columns.ChoiceFieldColumn()
|
status = columns.ChoiceFieldColumn()
|
||||||
|
region = tables.Column(
|
||||||
|
accessor=Accessor('site__region'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
site_group = tables.Column(
|
||||||
|
accessor=Accessor('site__group'),
|
||||||
|
linkify=True,
|
||||||
|
verbose_name='Site Group'
|
||||||
|
)
|
||||||
site = tables.Column(
|
site = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
@ -203,9 +212,9 @@ class DeviceTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
model = Device
|
model = Device
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type',
|
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type',
|
||||||
'platform', 'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'primary_ip', 'airflow',
|
'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'position', 'face',
|
||||||
'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments',
|
'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position',
|
||||||
'contacts', 'tags', 'created', 'last_updated',
|
'vc_priority', 'comments', 'contacts', 'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type',
|
'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type',
|
||||||
|
@ -239,7 +239,7 @@ INTERFACE_BUTTONS = """
|
|||||||
<li><a class="dropdown-item" href="{% url 'dcim:inventoryitem_add' %}?device={{ record.device_id }}&component_type={{ record|content_type_id }}&component_id={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Inventory Item</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:inventoryitem_add' %}?device={{ record.device_id }}&component_type={{ record|content_type_id }}&component_id={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Inventory Item</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_interface %}
|
{% if perms.dcim.add_interface %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:interface_add' %}?device={{ record.device_id }}&parent={{ record.pk }}&name_pattern={{ record.name }}.&type=virtual&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Child Interface</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:interface_add' %}?device={{ record.device_id }}&parent={{ record.pk }}&name={{ record.name }}.&type=virtual&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Child Interface</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.ipam.add_l2vpntermination %}
|
{% if perms.ipam.add_l2vpntermination %}
|
||||||
<li><a class="dropdown-item" href="{% url 'ipam:l2vpntermination_add' %}?device={{ object.pk }}&interface={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">L2VPN Termination</a></li>
|
<li><a class="dropdown-item" href="{% url 'ipam:l2vpntermination_add' %}?device={{ object.pk }}&interface={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">L2VPN Termination</a></li>
|
||||||
|
@ -2057,6 +2057,7 @@ class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_update_data = {
|
cls.bulk_update_data = {
|
||||||
'domain': 'newdomain',
|
'domain': 'newdomain',
|
||||||
|
'master': None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from dcim.choices import DeviceFaceChoices, DeviceStatusChoices
|
from dcim.choices import DeviceFaceChoices, DeviceStatusChoices, InterfaceTypeChoices
|
||||||
from dcim.forms import *
|
from dcim.forms import *
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from utilities.testing import create_test_device
|
from utilities.testing import create_test_device
|
||||||
@ -129,10 +129,11 @@ class LabelTestCase(TestCase):
|
|||||||
"""
|
"""
|
||||||
interface_data = {
|
interface_data = {
|
||||||
'device': self.device.pk,
|
'device': self.device.pk,
|
||||||
'name_pattern': 'eth[0-9]',
|
'name': 'eth[0-9]',
|
||||||
'label_pattern': 'Interface[0-9]',
|
'label': 'Interface[0-9]',
|
||||||
|
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
||||||
}
|
}
|
||||||
form = DeviceComponentCreateForm(interface_data)
|
form = InterfaceCreateForm(interface_data)
|
||||||
|
|
||||||
self.assertTrue(form.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
|
|
||||||
@ -142,10 +143,11 @@ class LabelTestCase(TestCase):
|
|||||||
"""
|
"""
|
||||||
bad_interface_data = {
|
bad_interface_data = {
|
||||||
'device': self.device.pk,
|
'device': self.device.pk,
|
||||||
'name_pattern': 'eth[0-9]',
|
'name': 'eth[0-9]',
|
||||||
'label_pattern': 'Interface[0-1]',
|
'label': 'Interface[0-1]',
|
||||||
|
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
||||||
}
|
}
|
||||||
form = DeviceComponentCreateForm(bad_interface_data)
|
form = InterfaceCreateForm(bad_interface_data)
|
||||||
|
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
self.assertIn('label_pattern', form.errors)
|
self.assertIn('label', form.errors)
|
||||||
|
@ -1082,31 +1082,28 @@ front-ports:
|
|||||||
|
|
||||||
class ConsolePortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class ConsolePortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
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)
|
|
||||||
|
|
||||||
ConsolePortTemplate.objects.bulk_create((
|
ConsolePortTemplate.objects.bulk_create((
|
||||||
ConsolePortTemplate(device_type=devicetypes[0], name='Console Port Template 1'),
|
ConsolePortTemplate(device_type=devicetype, name='Console Port Template 1'),
|
||||||
ConsolePortTemplate(device_type=devicetypes[0], name='Console Port Template 2'),
|
ConsolePortTemplate(device_type=devicetype, name='Console Port Template 2'),
|
||||||
ConsolePortTemplate(device_type=devicetypes[0], name='Console Port Template 3'),
|
ConsolePortTemplate(device_type=devicetype, name='Console Port Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Console Port Template X',
|
'name': 'Console Port Template X',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Console Port Template [4-6]',
|
'name': 'Console Port Template [4-6]',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1117,31 +1114,28 @@ class ConsolePortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestC
|
|||||||
|
|
||||||
class ConsoleServerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class ConsoleServerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
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)
|
|
||||||
|
|
||||||
ConsoleServerPortTemplate.objects.bulk_create((
|
ConsoleServerPortTemplate.objects.bulk_create((
|
||||||
ConsoleServerPortTemplate(device_type=devicetypes[0], name='Console Server Port Template 1'),
|
ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 1'),
|
||||||
ConsoleServerPortTemplate(device_type=devicetypes[0], name='Console Server Port Template 2'),
|
ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 2'),
|
||||||
ConsoleServerPortTemplate(device_type=devicetypes[0], name='Console Server Port Template 3'),
|
ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Console Server Port Template X',
|
'name': 'Console Server Port Template X',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Console Server Port Template [4-6]',
|
'name': 'Console Server Port Template [4-6]',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1152,24 +1146,21 @@ class ConsoleServerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateVie
|
|||||||
|
|
||||||
class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
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)
|
|
||||||
|
|
||||||
PowerPortTemplate.objects.bulk_create((
|
PowerPortTemplate.objects.bulk_create((
|
||||||
PowerPortTemplate(device_type=devicetypes[0], name='Power Port Template 1'),
|
PowerPortTemplate(device_type=devicetype, name='Power Port Template 1'),
|
||||||
PowerPortTemplate(device_type=devicetypes[0], name='Power Port Template 2'),
|
PowerPortTemplate(device_type=devicetype, name='Power Port Template 2'),
|
||||||
PowerPortTemplate(device_type=devicetypes[0], name='Power Port Template 3'),
|
PowerPortTemplate(device_type=devicetype, name='Power Port Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Power Port Template X',
|
'name': 'Power Port Template X',
|
||||||
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
||||||
'maximum_draw': 100,
|
'maximum_draw': 100,
|
||||||
@ -1177,8 +1168,8 @@ class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Power Port Template [4-6]',
|
'name': 'Power Port Template [4-6]',
|
||||||
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
||||||
'maximum_draw': 100,
|
'maximum_draw': 100,
|
||||||
'allocated_draw': 50,
|
'allocated_draw': 50,
|
||||||
@ -1193,6 +1184,7 @@ class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1220,7 +1212,7 @@ class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestC
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetype.pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Power Outlet Template [4-6]',
|
'name': 'Power Outlet Template [4-6]',
|
||||||
'type': PowerOutletTypeChoices.TYPE_IEC_C13,
|
'type': PowerOutletTypeChoices.TYPE_IEC_C13,
|
||||||
'power_port': powerports[0].pk,
|
'power_port': powerports[0].pk,
|
||||||
'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
|
'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
|
||||||
@ -1234,34 +1226,31 @@ class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestC
|
|||||||
|
|
||||||
class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
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)
|
|
||||||
|
|
||||||
InterfaceTemplate.objects.bulk_create((
|
InterfaceTemplate.objects.bulk_create((
|
||||||
InterfaceTemplate(device_type=devicetypes[0], name='Interface Template 1'),
|
InterfaceTemplate(device_type=devicetype, name='Interface Template 1'),
|
||||||
InterfaceTemplate(device_type=devicetypes[0], name='Interface Template 2'),
|
InterfaceTemplate(device_type=devicetype, name='Interface Template 2'),
|
||||||
InterfaceTemplate(device_type=devicetypes[0], name='Interface Template 3'),
|
InterfaceTemplate(device_type=devicetype, name='Interface Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Interface Template X',
|
'name': 'Interface Template X',
|
||||||
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
||||||
'mgmt_only': True,
|
'mgmt_only': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Interface Template [4-6]',
|
'name': 'Interface Template [4-6]',
|
||||||
# Test that a label can be applied to each generated interface templates
|
# Test that a label can be applied to each generated interface templates
|
||||||
'label_pattern': 'Interface Template Label [3-5]',
|
'label': 'Interface Template Label [3-5]',
|
||||||
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
||||||
'mgmt_only': True,
|
'mgmt_only': True,
|
||||||
}
|
}
|
||||||
@ -1274,6 +1263,7 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = FrontPortTemplate
|
model = FrontPortTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label', 'rear_port')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1306,11 +1296,9 @@ class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetype.pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Front Port [4-6]',
|
'name': 'Front Port [4-6]',
|
||||||
'type': PortTypeChoices.TYPE_8P8C,
|
'type': PortTypeChoices.TYPE_8P8C,
|
||||||
'rear_port_set': [
|
'rear_port': [f'{rp.pk}:1' for rp in rearports[3:6]],
|
||||||
'{}:1'.format(rp.pk) for rp in rearports[3:6]
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
@ -1320,32 +1308,29 @@ class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = RearPortTemplate
|
model = RearPortTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
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)
|
|
||||||
|
|
||||||
RearPortTemplate.objects.bulk_create((
|
RearPortTemplate.objects.bulk_create((
|
||||||
RearPortTemplate(device_type=devicetypes[0], name='Rear Port Template 1'),
|
RearPortTemplate(device_type=devicetype, name='Rear Port Template 1'),
|
||||||
RearPortTemplate(device_type=devicetypes[0], name='Rear Port Template 2'),
|
RearPortTemplate(device_type=devicetype, name='Rear Port Template 2'),
|
||||||
RearPortTemplate(device_type=devicetypes[0], name='Rear Port Template 3'),
|
RearPortTemplate(device_type=devicetype, name='Rear Port Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Rear Port Template X',
|
'name': 'Rear Port Template X',
|
||||||
'type': PortTypeChoices.TYPE_8P8C,
|
'type': PortTypeChoices.TYPE_8P8C,
|
||||||
'positions': 2,
|
'positions': 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Rear Port Template [4-6]',
|
'name': 'Rear Port Template [4-6]',
|
||||||
'type': PortTypeChoices.TYPE_8P8C,
|
'type': PortTypeChoices.TYPE_8P8C,
|
||||||
'positions': 2,
|
'positions': 2,
|
||||||
}
|
}
|
||||||
@ -1357,30 +1342,27 @@ class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase
|
|||||||
|
|
||||||
class ModuleBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class ModuleBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = ModuleBayTemplate
|
model = ModuleBayTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
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.objects.bulk_create((
|
||||||
ModuleBayTemplate(device_type=devicetypes[0], name='Module Bay Template 1'),
|
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 1'),
|
||||||
ModuleBayTemplate(device_type=devicetypes[0], name='Module Bay Template 2'),
|
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 2'),
|
||||||
ModuleBayTemplate(device_type=devicetypes[0], name='Module Bay Template 3'),
|
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Module Bay Template X',
|
'name': 'Module Bay Template X',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Module Bay Template [4-6]',
|
'name': 'Module Bay Template [4-6]',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
@ -1390,30 +1372,27 @@ class ModuleBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = DeviceBayTemplate
|
model = DeviceBayTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1', subdevice_role=SubdeviceRoleChoices.ROLE_PARENT)
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1', subdevice_role=SubdeviceRoleChoices.ROLE_PARENT),
|
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2', subdevice_role=SubdeviceRoleChoices.ROLE_PARENT),
|
|
||||||
)
|
|
||||||
DeviceType.objects.bulk_create(devicetypes)
|
|
||||||
|
|
||||||
DeviceBayTemplate.objects.bulk_create((
|
DeviceBayTemplate.objects.bulk_create((
|
||||||
DeviceBayTemplate(device_type=devicetypes[0], name='Device Bay Template 1'),
|
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 1'),
|
||||||
DeviceBayTemplate(device_type=devicetypes[0], name='Device Bay Template 2'),
|
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 2'),
|
||||||
DeviceBayTemplate(device_type=devicetypes[0], name='Device Bay Template 3'),
|
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Device Bay Template X',
|
'name': 'Device Bay Template X',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Device Bay Template [4-6]',
|
'name': 'Device Bay Template [4-6]',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
@ -1423,6 +1402,7 @@ class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
class InventoryItemTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class InventoryItemTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = InventoryItemTemplate
|
model = InventoryItemTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1431,30 +1411,25 @@ class InventoryItemTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTes
|
|||||||
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
|
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
|
||||||
)
|
)
|
||||||
Manufacturer.objects.bulk_create(manufacturers)
|
Manufacturer.objects.bulk_create(manufacturers)
|
||||||
|
devicetype = DeviceType.objects.create(manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1')
|
||||||
devicetypes = (
|
|
||||||
DeviceType(manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1'),
|
|
||||||
DeviceType(manufacturer=manufacturers[0], model='Device Type 2', slug='device-type-2'),
|
|
||||||
)
|
|
||||||
DeviceType.objects.bulk_create(devicetypes)
|
|
||||||
|
|
||||||
inventory_item_templates = (
|
inventory_item_templates = (
|
||||||
InventoryItemTemplate(device_type=devicetypes[0], name='Inventory Item Template 1', manufacturer=manufacturers[0]),
|
InventoryItemTemplate(device_type=devicetype, name='Inventory Item Template 1', manufacturer=manufacturers[0]),
|
||||||
InventoryItemTemplate(device_type=devicetypes[0], name='Inventory Item Template 2', manufacturer=manufacturers[0]),
|
InventoryItemTemplate(device_type=devicetype, name='Inventory Item Template 2', manufacturer=manufacturers[0]),
|
||||||
InventoryItemTemplate(device_type=devicetypes[0], name='Inventory Item Template 3', manufacturer=manufacturers[0]),
|
InventoryItemTemplate(device_type=devicetype, name='Inventory Item Template 3', manufacturer=manufacturers[0]),
|
||||||
)
|
)
|
||||||
for item in inventory_item_templates:
|
for item in inventory_item_templates:
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Inventory Item Template X',
|
'name': 'Inventory Item Template X',
|
||||||
'manufacturer': manufacturers[1].pk,
|
'manufacturer': manufacturers[1].pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Inventory Item Template [4-6]',
|
'name': 'Inventory Item Template [4-6]',
|
||||||
'manufacturer': manufacturers[1].pk,
|
'manufacturer': manufacturers[1].pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1912,6 +1887,7 @@ class ModuleTestCase(
|
|||||||
|
|
||||||
class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1935,9 +1911,9 @@ class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Console Port [4-6]',
|
'name': 'Console Port [4-6]',
|
||||||
# Test that a label can be applied to each generated console ports
|
# Test that a label can be applied to each generated console ports
|
||||||
'label_pattern': 'Serial[3-5]',
|
'label': 'Serial[3-5]',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
'description': 'A console port',
|
'description': 'A console port',
|
||||||
'tags': sorted([t.pk for t in tags]),
|
'tags': sorted([t.pk for t in tags]),
|
||||||
@ -1970,6 +1946,7 @@ class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1993,7 +1970,7 @@ class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Console Server Port [4-6]',
|
'name': 'Console Server Port [4-6]',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
'description': 'A console server port',
|
'description': 'A console server port',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
@ -2026,6 +2003,7 @@ class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2051,7 +2029,7 @@ class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Power Port [4-6]]',
|
'name': 'Power Port [4-6]]',
|
||||||
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
||||||
'maximum_draw': 100,
|
'maximum_draw': 100,
|
||||||
'allocated_draw': 50,
|
'allocated_draw': 50,
|
||||||
@ -2088,6 +2066,7 @@ class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2119,7 +2098,7 @@ class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Power Outlet [4-6]',
|
'name': 'Power Outlet [4-6]',
|
||||||
'type': PowerOutletTypeChoices.TYPE_IEC_C13,
|
'type': PowerOutletTypeChoices.TYPE_IEC_C13,
|
||||||
'power_port': powerports[1].pk,
|
'power_port': powerports[1].pk,
|
||||||
'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
|
'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
|
||||||
@ -2153,6 +2132,7 @@ class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = Interface
|
model = Interface
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2217,7 +2197,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Interface [4-6]',
|
'name': 'Interface [4-6]',
|
||||||
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
'bridge': interfaces[4].pk,
|
'bridge': interfaces[4].pk,
|
||||||
@ -2277,6 +2257,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
|
validation_excluded_fields = ('name', 'label', 'rear_port')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2312,11 +2293,9 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Front Port [4-6]',
|
'name': 'Front Port [4-6]',
|
||||||
'type': PortTypeChoices.TYPE_8P8C,
|
'type': PortTypeChoices.TYPE_8P8C,
|
||||||
'rear_port_set': [
|
'rear_port': [f'{rp.pk}:1' for rp in rearports[3:6]],
|
||||||
'{}:1'.format(rp.pk) for rp in rearports[3:6]
|
|
||||||
],
|
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
@ -2348,6 +2327,7 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = RearPort
|
model = RearPort
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2372,7 +2352,7 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Rear Port [4-6]',
|
'name': 'Rear Port [4-6]',
|
||||||
'type': PortTypeChoices.TYPE_8P8C,
|
'type': PortTypeChoices.TYPE_8P8C,
|
||||||
'positions': 3,
|
'positions': 3,
|
||||||
'description': 'A rear port',
|
'description': 'A rear port',
|
||||||
@ -2406,6 +2386,7 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2428,7 +2409,7 @@ class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Module Bay [4-6]',
|
'name': 'Module Bay [4-6]',
|
||||||
'description': 'A module bay',
|
'description': 'A module bay',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
@ -2447,6 +2428,7 @@ class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2472,7 +2454,7 @@ class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Device Bay [4-6]',
|
'name': 'Device Bay [4-6]',
|
||||||
'description': 'A device bay',
|
'description': 'A device bay',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
@ -2491,6 +2473,7 @@ class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2525,7 +2508,7 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Inventory Item [4-6]',
|
'name': 'Inventory Item [4-6]',
|
||||||
'role': roles[1].pk,
|
'role': roles[1].pk,
|
||||||
'manufacturer': manufacturer.pk,
|
'manufacturer': manufacturer.pk,
|
||||||
'parent': None,
|
'parent': None,
|
||||||
|
@ -1120,9 +1120,8 @@ class ModuleTypeBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class ConsolePortTemplateCreateView(generic.ComponentCreateView):
|
class ConsolePortTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = ConsolePortTemplate.objects.all()
|
queryset = ConsolePortTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.ConsolePortTemplateCreateForm
|
||||||
model_form = forms.ConsolePortTemplateForm
|
model_form = forms.ConsolePortTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTemplateEditView(generic.ObjectEditView):
|
class ConsolePortTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1155,9 +1154,8 @@ class ConsolePortTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView):
|
class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = ConsoleServerPortTemplate.objects.all()
|
queryset = ConsoleServerPortTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.ConsoleServerPortTemplateCreateForm
|
||||||
model_form = forms.ConsoleServerPortTemplateForm
|
model_form = forms.ConsoleServerPortTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateEditView(generic.ObjectEditView):
|
class ConsoleServerPortTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1190,9 +1188,8 @@ class ConsoleServerPortTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class PowerPortTemplateCreateView(generic.ComponentCreateView):
|
class PowerPortTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = PowerPortTemplate.objects.all()
|
queryset = PowerPortTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.PowerPortTemplateCreateForm
|
||||||
model_form = forms.PowerPortTemplateForm
|
model_form = forms.PowerPortTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateEditView(generic.ObjectEditView):
|
class PowerPortTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1225,9 +1222,8 @@ class PowerPortTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class PowerOutletTemplateCreateView(generic.ComponentCreateView):
|
class PowerOutletTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = PowerOutletTemplate.objects.all()
|
queryset = PowerOutletTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.PowerOutletTemplateCreateForm
|
||||||
model_form = forms.PowerOutletTemplateForm
|
model_form = forms.PowerOutletTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateEditView(generic.ObjectEditView):
|
class PowerOutletTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1260,9 +1256,8 @@ class PowerOutletTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class InterfaceTemplateCreateView(generic.ComponentCreateView):
|
class InterfaceTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = InterfaceTemplate.objects.all()
|
queryset = InterfaceTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.InterfaceTemplateCreateForm
|
||||||
model_form = forms.InterfaceTemplateForm
|
model_form = forms.InterfaceTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateEditView(generic.ObjectEditView):
|
class InterfaceTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1297,15 +1292,6 @@ class FrontPortTemplateCreateView(generic.ComponentCreateView):
|
|||||||
queryset = FrontPortTemplate.objects.all()
|
queryset = FrontPortTemplate.objects.all()
|
||||||
form = forms.FrontPortTemplateCreateForm
|
form = forms.FrontPortTemplateCreateForm
|
||||||
model_form = forms.FrontPortTemplateForm
|
model_form = forms.FrontPortTemplateForm
|
||||||
template_name = 'dcim/frontporttemplate_create.html'
|
|
||||||
|
|
||||||
def initialize_forms(self, request):
|
|
||||||
form, model_form = super().initialize_forms(request)
|
|
||||||
|
|
||||||
model_form.fields.pop('rear_port')
|
|
||||||
model_form.fields.pop('rear_port_position')
|
|
||||||
|
|
||||||
return form, model_form
|
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateEditView(generic.ObjectEditView):
|
class FrontPortTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1338,9 +1324,8 @@ class FrontPortTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class RearPortTemplateCreateView(generic.ComponentCreateView):
|
class RearPortTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = RearPortTemplate.objects.all()
|
queryset = RearPortTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.RearPortTemplateCreateForm
|
||||||
model_form = forms.RearPortTemplateForm
|
model_form = forms.RearPortTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateEditView(generic.ObjectEditView):
|
class RearPortTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1375,8 +1360,6 @@ class ModuleBayTemplateCreateView(generic.ComponentCreateView):
|
|||||||
queryset = ModuleBayTemplate.objects.all()
|
queryset = ModuleBayTemplate.objects.all()
|
||||||
form = forms.ModuleBayTemplateCreateForm
|
form = forms.ModuleBayTemplateCreateForm
|
||||||
model_form = forms.ModuleBayTemplateForm
|
model_form = forms.ModuleBayTemplateForm
|
||||||
template_name = 'dcim/modulebaytemplate_create.html'
|
|
||||||
patterned_fields = ('name', 'label', 'position')
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayTemplateEditView(generic.ObjectEditView):
|
class ModuleBayTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1409,9 +1392,8 @@ class ModuleBayTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class DeviceBayTemplateCreateView(generic.ComponentCreateView):
|
class DeviceBayTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = DeviceBayTemplate.objects.all()
|
queryset = DeviceBayTemplate.objects.all()
|
||||||
form = forms.ComponentTemplateCreateForm
|
form = forms.DeviceBayTemplateCreateForm
|
||||||
model_form = forms.DeviceBayTemplateForm
|
model_form = forms.DeviceBayTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateEditView(generic.ObjectEditView):
|
class DeviceBayTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1444,9 +1426,8 @@ class DeviceBayTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class InventoryItemTemplateCreateView(generic.ComponentCreateView):
|
class InventoryItemTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = InventoryItemTemplate.objects.all()
|
queryset = InventoryItemTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.InventoryItemTemplateCreateForm
|
||||||
model_form = forms.InventoryItemTemplateForm
|
model_form = forms.InventoryItemTemplateForm
|
||||||
template_name = 'dcim/inventoryitemtemplate_create.html'
|
|
||||||
|
|
||||||
def alter_object(self, instance, request):
|
def alter_object(self, instance, request):
|
||||||
# Set component (if any)
|
# Set component (if any)
|
||||||
@ -1874,14 +1855,13 @@ class ConsolePortView(generic.ObjectView):
|
|||||||
|
|
||||||
class ConsolePortCreateView(generic.ComponentCreateView):
|
class ConsolePortCreateView(generic.ComponentCreateView):
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.ConsolePortCreateForm
|
||||||
model_form = forms.ConsolePortForm
|
model_form = forms.ConsolePortForm
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortEditView(generic.ObjectEditView):
|
class ConsolePortEditView(generic.ObjectEditView):
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
form = forms.ConsolePortForm
|
form = forms.ConsolePortForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortDeleteView(generic.ObjectDeleteView):
|
class ConsolePortDeleteView(generic.ObjectDeleteView):
|
||||||
@ -1933,14 +1913,13 @@ class ConsoleServerPortView(generic.ObjectView):
|
|||||||
|
|
||||||
class ConsoleServerPortCreateView(generic.ComponentCreateView):
|
class ConsoleServerPortCreateView(generic.ComponentCreateView):
|
||||||
queryset = ConsoleServerPort.objects.all()
|
queryset = ConsoleServerPort.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.ConsoleServerPortCreateForm
|
||||||
model_form = forms.ConsoleServerPortForm
|
model_form = forms.ConsoleServerPortForm
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortEditView(generic.ObjectEditView):
|
class ConsoleServerPortEditView(generic.ObjectEditView):
|
||||||
queryset = ConsoleServerPort.objects.all()
|
queryset = ConsoleServerPort.objects.all()
|
||||||
form = forms.ConsoleServerPortForm
|
form = forms.ConsoleServerPortForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortDeleteView(generic.ObjectDeleteView):
|
class ConsoleServerPortDeleteView(generic.ObjectDeleteView):
|
||||||
@ -1992,14 +1971,13 @@ class PowerPortView(generic.ObjectView):
|
|||||||
|
|
||||||
class PowerPortCreateView(generic.ComponentCreateView):
|
class PowerPortCreateView(generic.ComponentCreateView):
|
||||||
queryset = PowerPort.objects.all()
|
queryset = PowerPort.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.PowerPortCreateForm
|
||||||
model_form = forms.PowerPortForm
|
model_form = forms.PowerPortForm
|
||||||
|
|
||||||
|
|
||||||
class PowerPortEditView(generic.ObjectEditView):
|
class PowerPortEditView(generic.ObjectEditView):
|
||||||
queryset = PowerPort.objects.all()
|
queryset = PowerPort.objects.all()
|
||||||
form = forms.PowerPortForm
|
form = forms.PowerPortForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class PowerPortDeleteView(generic.ObjectDeleteView):
|
class PowerPortDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2051,14 +2029,13 @@ class PowerOutletView(generic.ObjectView):
|
|||||||
|
|
||||||
class PowerOutletCreateView(generic.ComponentCreateView):
|
class PowerOutletCreateView(generic.ComponentCreateView):
|
||||||
queryset = PowerOutlet.objects.all()
|
queryset = PowerOutlet.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.PowerOutletCreateForm
|
||||||
model_form = forms.PowerOutletForm
|
model_form = forms.PowerOutletForm
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletEditView(generic.ObjectEditView):
|
class PowerOutletEditView(generic.ObjectEditView):
|
||||||
queryset = PowerOutlet.objects.all()
|
queryset = PowerOutlet.objects.all()
|
||||||
form = forms.PowerOutletForm
|
form = forms.PowerOutletForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletDeleteView(generic.ObjectDeleteView):
|
class PowerOutletDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2154,42 +2131,13 @@ class InterfaceView(generic.ObjectView):
|
|||||||
|
|
||||||
class InterfaceCreateView(generic.ComponentCreateView):
|
class InterfaceCreateView(generic.ComponentCreateView):
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.InterfaceCreateForm
|
||||||
model_form = forms.InterfaceForm
|
model_form = forms.InterfaceForm
|
||||||
# template_name = 'dcim/interface_create.html'
|
|
||||||
|
|
||||||
# TODO: Figure out what to do with this
|
|
||||||
# def post(self, request):
|
|
||||||
# """
|
|
||||||
# Override inherited post() method to handle request to assign newly created
|
|
||||||
# interface objects (first object) to an IP Address object.
|
|
||||||
# """
|
|
||||||
# form = self.form(request.POST, initial=request.GET)
|
|
||||||
# new_objs = self.validate_form(request, form)
|
|
||||||
#
|
|
||||||
# if form.is_valid() and not form.errors:
|
|
||||||
# if '_addanother' in request.POST:
|
|
||||||
# return redirect(request.get_full_path())
|
|
||||||
# elif new_objs is not None and '_assignip' in request.POST and len(new_objs) >= 1 and \
|
|
||||||
# request.user.has_perm('ipam.add_ipaddress'):
|
|
||||||
# first_obj = new_objs[0].pk
|
|
||||||
# return redirect(
|
|
||||||
# f'/ipam/ip-addresses/add/?interface={first_obj}&return_url={self.get_return_url(request)}'
|
|
||||||
# )
|
|
||||||
# else:
|
|
||||||
# return redirect(self.get_return_url(request))
|
|
||||||
#
|
|
||||||
# return render(request, self.template_name, {
|
|
||||||
# 'obj_type': self.queryset.model._meta.verbose_name,
|
|
||||||
# 'form': form,
|
|
||||||
# 'return_url': self.get_return_url(request),
|
|
||||||
# })
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceEditView(generic.ObjectEditView):
|
class InterfaceEditView(generic.ObjectEditView):
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
form = forms.InterfaceForm
|
form = forms.InterfaceForm
|
||||||
template_name = 'dcim/interface_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceDeleteView(generic.ObjectDeleteView):
|
class InterfaceDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2244,19 +2192,10 @@ class FrontPortCreateView(generic.ComponentCreateView):
|
|||||||
form = forms.FrontPortCreateForm
|
form = forms.FrontPortCreateForm
|
||||||
model_form = forms.FrontPortForm
|
model_form = forms.FrontPortForm
|
||||||
|
|
||||||
def initialize_forms(self, request):
|
|
||||||
form, model_form = super().initialize_forms(request)
|
|
||||||
|
|
||||||
model_form.fields.pop('rear_port')
|
|
||||||
model_form.fields.pop('rear_port_position')
|
|
||||||
|
|
||||||
return form, model_form
|
|
||||||
|
|
||||||
|
|
||||||
class FrontPortEditView(generic.ObjectEditView):
|
class FrontPortEditView(generic.ObjectEditView):
|
||||||
queryset = FrontPort.objects.all()
|
queryset = FrontPort.objects.all()
|
||||||
form = forms.FrontPortForm
|
form = forms.FrontPortForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class FrontPortDeleteView(generic.ObjectDeleteView):
|
class FrontPortDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2308,14 +2247,13 @@ class RearPortView(generic.ObjectView):
|
|||||||
|
|
||||||
class RearPortCreateView(generic.ComponentCreateView):
|
class RearPortCreateView(generic.ComponentCreateView):
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.RearPortCreateForm
|
||||||
model_form = forms.RearPortForm
|
model_form = forms.RearPortForm
|
||||||
|
|
||||||
|
|
||||||
class RearPortEditView(generic.ObjectEditView):
|
class RearPortEditView(generic.ObjectEditView):
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
form = forms.RearPortForm
|
form = forms.RearPortForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class RearPortDeleteView(generic.ObjectDeleteView):
|
class RearPortDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2369,13 +2307,11 @@ class ModuleBayCreateView(generic.ComponentCreateView):
|
|||||||
queryset = ModuleBay.objects.all()
|
queryset = ModuleBay.objects.all()
|
||||||
form = forms.ModuleBayCreateForm
|
form = forms.ModuleBayCreateForm
|
||||||
model_form = forms.ModuleBayForm
|
model_form = forms.ModuleBayForm
|
||||||
patterned_fields = ('name', 'label', 'position')
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayEditView(generic.ObjectEditView):
|
class ModuleBayEditView(generic.ObjectEditView):
|
||||||
queryset = ModuleBay.objects.all()
|
queryset = ModuleBay.objects.all()
|
||||||
form = forms.ModuleBayForm
|
form = forms.ModuleBayForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayDeleteView(generic.ObjectDeleteView):
|
class ModuleBayDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2423,14 +2359,13 @@ class DeviceBayView(generic.ObjectView):
|
|||||||
|
|
||||||
class DeviceBayCreateView(generic.ComponentCreateView):
|
class DeviceBayCreateView(generic.ComponentCreateView):
|
||||||
queryset = DeviceBay.objects.all()
|
queryset = DeviceBay.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.DeviceBayCreateForm
|
||||||
model_form = forms.DeviceBayForm
|
model_form = forms.DeviceBayForm
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayEditView(generic.ObjectEditView):
|
class DeviceBayEditView(generic.ObjectEditView):
|
||||||
queryset = DeviceBay.objects.all()
|
queryset = DeviceBay.objects.all()
|
||||||
form = forms.DeviceBayForm
|
form = forms.DeviceBayForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayDeleteView(generic.ObjectDeleteView):
|
class DeviceBayDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2552,7 +2487,6 @@ class InventoryItemCreateView(generic.ComponentCreateView):
|
|||||||
queryset = InventoryItem.objects.all()
|
queryset = InventoryItem.objects.all()
|
||||||
form = forms.InventoryItemCreateForm
|
form = forms.InventoryItemCreateForm
|
||||||
model_form = forms.InventoryItemForm
|
model_form = forms.InventoryItemForm
|
||||||
template_name = 'dcim/inventoryitem_create.html'
|
|
||||||
|
|
||||||
def alter_object(self, instance, request):
|
def alter_object(self, instance, request):
|
||||||
# Set component (if any)
|
# Set component (if any)
|
||||||
@ -2736,7 +2670,6 @@ class DeviceBulkAddModuleBayView(generic.BulkComponentCreateView):
|
|||||||
filterset = filtersets.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
patterned_fields = ('name', 'label', 'position')
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
|
class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
|
||||||
|
@ -159,7 +159,7 @@ class ReportViewSet(ViewSet):
|
|||||||
# Read the PK as "<module>.<report>"
|
# Read the PK as "<module>.<report>"
|
||||||
if '.' not in pk:
|
if '.' not in pk:
|
||||||
raise Http404
|
raise Http404
|
||||||
module_name, report_name = pk.split('.', 1)
|
module_name, report_name = pk.split('.', maxsplit=1)
|
||||||
|
|
||||||
# Raise a 404 on an invalid Report module/name
|
# Raise a 404 on an invalid Report module/name
|
||||||
report = get_report(module_name, report_name)
|
report = get_report(module_name, report_name)
|
||||||
@ -183,8 +183,8 @@ class ReportViewSet(ViewSet):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Iterate through all available Reports.
|
# Iterate through all available Reports.
|
||||||
for module_name, reports in get_reports():
|
for module_name, reports in get_reports().items():
|
||||||
for report in reports:
|
for report in reports.values():
|
||||||
|
|
||||||
# Attach the relevant JobResult (if any) to each Report.
|
# Attach the relevant JobResult (if any) to each Report.
|
||||||
report.result = results.get(report.full_name, None)
|
report.result = results.get(report.full_name, None)
|
||||||
@ -257,7 +257,7 @@ class ScriptViewSet(ViewSet):
|
|||||||
lookup_value_regex = '[^/]+' # Allow dots
|
lookup_value_regex = '[^/]+' # Allow dots
|
||||||
|
|
||||||
def _get_script(self, pk):
|
def _get_script(self, pk):
|
||||||
module_name, script_name = pk.split('.')
|
module_name, script_name = pk.split('.', maxsplit=1)
|
||||||
script = get_script(module_name, script_name)
|
script = get_script(module_name, script_name)
|
||||||
if script is None:
|
if script is None:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
@ -21,8 +21,8 @@ class Command(BaseCommand):
|
|||||||
reports = get_reports()
|
reports = get_reports()
|
||||||
|
|
||||||
# Run reports
|
# Run reports
|
||||||
for module_name, report_list in reports:
|
for module_name, report_list in reports.items():
|
||||||
for report in report_list:
|
for report in report_list.values():
|
||||||
if module_name in options['reports'] or report.full_name in options['reports']:
|
if module_name in options['reports'] or report.full_name in options['reports']:
|
||||||
|
|
||||||
# Run the report and create a new JobResult
|
# Run the report and create a new JobResult
|
||||||
|
@ -26,20 +26,18 @@ def get_report(module_name, report_name):
|
|||||||
"""
|
"""
|
||||||
Return a specific report from within a module.
|
Return a specific report from within a module.
|
||||||
"""
|
"""
|
||||||
file_path = '{}/{}.py'.format(settings.REPORTS_ROOT, module_name)
|
reports = get_reports()
|
||||||
|
module = reports.get(module_name)
|
||||||
|
|
||||||
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
if module is None:
|
||||||
module = importlib.util.module_from_spec(spec)
|
|
||||||
try:
|
|
||||||
spec.loader.exec_module(module)
|
|
||||||
except FileNotFoundError:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
report = getattr(module, report_name, None)
|
report = module.get(report_name)
|
||||||
|
|
||||||
if report is None:
|
if report is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return report()
|
return report
|
||||||
|
|
||||||
|
|
||||||
def get_reports():
|
def get_reports():
|
||||||
@ -52,7 +50,7 @@ def get_reports():
|
|||||||
...
|
...
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
module_list = []
|
module_list = {}
|
||||||
|
|
||||||
# Iterate through all modules within the reports path. These are the user-created files in which reports are
|
# Iterate through all modules within the reports path. These are the user-created files in which reports are
|
||||||
# defined.
|
# defined.
|
||||||
@ -61,7 +59,16 @@ def get_reports():
|
|||||||
report_order = getattr(module, "report_order", ())
|
report_order = getattr(module, "report_order", ())
|
||||||
ordered_reports = [cls() for cls in report_order if is_report(cls)]
|
ordered_reports = [cls() for cls in report_order if is_report(cls)]
|
||||||
unordered_reports = [cls() for _, cls in inspect.getmembers(module, is_report) if cls not in report_order]
|
unordered_reports = [cls() for _, cls in inspect.getmembers(module, is_report) if cls not in report_order]
|
||||||
module_list.append((module_name, [*ordered_reports, *unordered_reports]))
|
|
||||||
|
module_reports = {}
|
||||||
|
|
||||||
|
for cls in [*ordered_reports, *unordered_reports]:
|
||||||
|
# For reports in submodules use the full import path w/o the root module as the name
|
||||||
|
report_name = cls.full_name.split(".", maxsplit=1)[1]
|
||||||
|
module_reports[report_name] = cls
|
||||||
|
|
||||||
|
if module_reports:
|
||||||
|
module_list[module_name] = module_reports
|
||||||
|
|
||||||
return module_list
|
return module_list
|
||||||
|
|
||||||
|
@ -299,6 +299,10 @@ class BaseScript:
|
|||||||
def module(cls):
|
def module(cls):
|
||||||
return cls.__module__
|
return cls.__module__
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def root_module(cls):
|
||||||
|
return cls.__module__.split(".")[0]
|
||||||
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def job_timeout(self):
|
def job_timeout(self):
|
||||||
return getattr(self.Meta, 'job_timeout', None)
|
return getattr(self.Meta, 'job_timeout', None)
|
||||||
@ -514,7 +518,9 @@ def get_scripts(use_names=False):
|
|||||||
ordered_scripts = [cls for cls in script_order if is_script(cls)]
|
ordered_scripts = [cls for cls in script_order if is_script(cls)]
|
||||||
unordered_scripts = [cls for _, cls in inspect.getmembers(module, is_script) if cls not in script_order]
|
unordered_scripts = [cls for _, cls in inspect.getmembers(module, is_script) if cls not in script_order]
|
||||||
for cls in [*ordered_scripts, *unordered_scripts]:
|
for cls in [*ordered_scripts, *unordered_scripts]:
|
||||||
module_scripts[cls.__name__] = cls
|
# For scripts in submodules use the full import path w/o the root module as the name
|
||||||
|
script_name = cls.full_name.split(".", maxsplit=1)[1]
|
||||||
|
module_scripts[script_name] = cls
|
||||||
if module_scripts:
|
if module_scripts:
|
||||||
scripts[module_name] = module_scripts
|
scripts[module_name] = module_scripts
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from django.urls import path
|
from django.urls import path, re_path
|
||||||
|
|
||||||
from extras import models, views
|
from extras import models, views
|
||||||
from netbox.views.generic import ObjectChangeLogView
|
from netbox.views.generic import ObjectChangeLogView
|
||||||
@ -100,12 +100,12 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Reports
|
# Reports
|
||||||
path('reports/', views.ReportListView.as_view(), name='report_list'),
|
path('reports/', views.ReportListView.as_view(), name='report_list'),
|
||||||
path('reports/<str:module>.<str:name>/', views.ReportView.as_view(), name='report'),
|
|
||||||
path('reports/results/<int:job_result_pk>/', views.ReportResultView.as_view(), name='report_result'),
|
path('reports/results/<int:job_result_pk>/', views.ReportResultView.as_view(), name='report_result'),
|
||||||
|
re_path(r'^reports/(?P<module>.([^.]+)).(?P<name>.(.+))/', views.ReportView.as_view(), name='report'),
|
||||||
|
|
||||||
# Scripts
|
# Scripts
|
||||||
path('scripts/', views.ScriptListView.as_view(), name='script_list'),
|
path('scripts/', views.ScriptListView.as_view(), name='script_list'),
|
||||||
path('scripts/<str:module>.<str:name>/', views.ScriptView.as_view(), name='script'),
|
|
||||||
path('scripts/results/<int:job_result_pk>/', views.ScriptResultView.as_view(), name='script_result'),
|
path('scripts/results/<int:job_result_pk>/', views.ScriptResultView.as_view(), name='script_result'),
|
||||||
|
re_path(r'^scripts/(?P<module>.([^.]+)).(?P<name>.(.+))/', views.ScriptView.as_view(), name='script'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -534,9 +534,10 @@ class ReportListView(ContentTypePermissionRequiredMixin, View):
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
for module, report_list in reports:
|
|
||||||
|
for module, report_list in reports.items():
|
||||||
module_reports = []
|
module_reports = []
|
||||||
for report in report_list:
|
for report in report_list.values():
|
||||||
report.result = results.get(report.full_name, None)
|
report.result = results.get(report.full_name, None)
|
||||||
module_reports.append(report)
|
module_reports.append(report)
|
||||||
ret.append((module, module_reports))
|
ret.append((module, module_reports))
|
||||||
@ -613,7 +614,7 @@ class ReportResultView(ContentTypePermissionRequiredMixin, View):
|
|||||||
result = get_object_or_404(JobResult.objects.all(), pk=job_result_pk, obj_type=report_content_type)
|
result = get_object_or_404(JobResult.objects.all(), pk=job_result_pk, obj_type=report_content_type)
|
||||||
|
|
||||||
# Retrieve the Report and attach the JobResult to it
|
# Retrieve the Report and attach the JobResult to it
|
||||||
module, report_name = result.name.split('.')
|
module, report_name = result.name.split('.', maxsplit=1)
|
||||||
report = get_report(module, report_name)
|
report = get_report(module, report_name)
|
||||||
report.result = result
|
report.result = result
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ django.utils.encoding.force_text = force_str
|
|||||||
# Environment setup
|
# Environment setup
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION = '3.3.3-dev'
|
VERSION = '3.3.4-dev'
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
HOSTNAME = platform.node()
|
HOSTNAME = platform.node()
|
||||||
|
@ -7,6 +7,7 @@ from django.contrib.auth.models import AnonymousUser
|
|||||||
from django.db.models import DateField, DateTimeField
|
from django.db.models import DateField, DateTimeField
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.encoding import escape_uri_path
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.formats import date_format
|
from django.utils.formats import date_format
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
@ -210,7 +211,7 @@ class ActionsColumn(tables.Column):
|
|||||||
|
|
||||||
model = table.Meta.model
|
model = table.Meta.model
|
||||||
request = getattr(table, 'context', {}).get('request')
|
request = getattr(table, 'context', {}).get('request')
|
||||||
url_appendix = f'?return_url={request.path}' if request else ''
|
url_appendix = f'?return_url={escape_uri_path(request.get_full_path())}' if request else ''
|
||||||
html = ''
|
html = ''
|
||||||
|
|
||||||
# Compile actions menu
|
# Compile actions menu
|
||||||
|
@ -774,7 +774,6 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
model_form = None
|
model_form = None
|
||||||
filterset = None
|
filterset = None
|
||||||
table = None
|
table = None
|
||||||
patterned_fields = ('name', 'label')
|
|
||||||
|
|
||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return f'dcim.add_{self.queryset.model._meta.model_name}'
|
return f'dcim.add_{self.queryset.model._meta.model_name}'
|
||||||
@ -804,23 +803,25 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
|
|
||||||
new_components = []
|
new_components = []
|
||||||
data = deepcopy(form.cleaned_data)
|
data = deepcopy(form.cleaned_data)
|
||||||
|
replication_data = {
|
||||||
|
field: data.pop(field) for field in form.replication_fields
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
|
||||||
for obj in data['pk']:
|
for obj in data['pk']:
|
||||||
|
|
||||||
pattern_count = len(data[f'{self.patterned_fields[0]}_pattern'])
|
pattern_count = len(replication_data[form.replication_fields[0]])
|
||||||
for i in range(pattern_count):
|
for i in range(pattern_count):
|
||||||
component_data = {
|
component_data = {
|
||||||
self.parent_field: obj.pk
|
self.parent_field: obj.pk
|
||||||
}
|
}
|
||||||
|
|
||||||
for field_name in self.patterned_fields:
|
|
||||||
if data.get(f'{field_name}_pattern'):
|
|
||||||
component_data[field_name] = data[f'{field_name}_pattern'][i]
|
|
||||||
|
|
||||||
component_data.update(data)
|
component_data.update(data)
|
||||||
|
for field, values in replication_data.items():
|
||||||
|
if values:
|
||||||
|
component_data[field] = values[i]
|
||||||
|
|
||||||
component_form = self.model_form(component_data)
|
component_form = self.model_form(component_data)
|
||||||
if component_form.is_valid():
|
if component_form.is_valid():
|
||||||
instance = component_form.save()
|
instance = component_form.save()
|
||||||
@ -829,7 +830,7 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
else:
|
else:
|
||||||
for field, errors in component_form.errors.as_data().items():
|
for field, errors in component_form.errors.as_data().items():
|
||||||
for e in errors:
|
for e in errors:
|
||||||
form.add_error(field, '{} {}: {}'.format(obj, name, ', '.join(e)))
|
form.add_error(field, '{}: {}'.format(obj, ', '.join(e)))
|
||||||
|
|
||||||
# Enforce object-level permissions
|
# Enforce object-level permissions
|
||||||
if self.queryset.filter(pk__in=[obj.pk for obj in new_components]).count() != len(new_components):
|
if self.queryset.filter(pk__in=[obj.pk for obj in new_components]).count() != len(new_components):
|
||||||
|
@ -538,10 +538,9 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
|||||||
"""
|
"""
|
||||||
Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine.
|
Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine.
|
||||||
"""
|
"""
|
||||||
template_name = 'dcim/component_create.html'
|
template_name = 'generic/object_edit.html'
|
||||||
form = None
|
form = None
|
||||||
model_form = None
|
model_form = None
|
||||||
patterned_fields = ('name', 'label')
|
|
||||||
|
|
||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return get_permission_for_model(self.queryset.model, 'add')
|
return get_permission_for_model(self.queryset.model, 'add')
|
||||||
@ -549,44 +548,38 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
|||||||
def alter_object(self, instance, request):
|
def alter_object(self, instance, request):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def initialize_forms(self, request):
|
def initialize_form(self, request):
|
||||||
data = request.POST if request.method == 'POST' else None
|
data = request.POST if request.method == 'POST' else None
|
||||||
initial_data = normalize_querydict(request.GET)
|
initial_data = normalize_querydict(request.GET)
|
||||||
|
|
||||||
form = self.form(data=data, initial=request.GET)
|
form = self.form(data=data, initial=initial_data)
|
||||||
model_form = self.model_form(data=data, initial=initial_data)
|
|
||||||
|
|
||||||
# These fields will be set from the pattern values
|
return form
|
||||||
for field_name in self.patterned_fields:
|
|
||||||
model_form.fields[field_name].widget = HiddenInput()
|
|
||||||
|
|
||||||
return form, model_form
|
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
form, model_form = self.initialize_forms(request)
|
form = self.initialize_form(request)
|
||||||
instance = self.alter_object(self.queryset.model(), request)
|
instance = self.alter_object(self.queryset.model(), request)
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'object': instance,
|
'object': instance,
|
||||||
'replication_form': form,
|
'form': form,
|
||||||
'form': model_form,
|
|
||||||
'return_url': self.get_return_url(request),
|
'return_url': self.get_return_url(request),
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
logger = logging.getLogger('netbox.views.ComponentCreateView')
|
logger = logging.getLogger('netbox.views.ComponentCreateView')
|
||||||
form, model_form = self.initialize_forms(request)
|
form = self.initialize_form(request)
|
||||||
instance = self.alter_object(self.queryset.model(), request)
|
instance = self.alter_object(self.queryset.model(), request)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
new_components = []
|
new_components = []
|
||||||
data = deepcopy(request.POST)
|
data = deepcopy(request.POST)
|
||||||
pattern_count = len(form.cleaned_data[f'{self.patterned_fields[0]}_pattern'])
|
pattern_count = len(form.cleaned_data[self.form.replication_fields[0]])
|
||||||
|
|
||||||
for i in range(pattern_count):
|
for i in range(pattern_count):
|
||||||
for field_name in self.patterned_fields:
|
for field_name in self.form.replication_fields:
|
||||||
if form.cleaned_data.get(f'{field_name}_pattern'):
|
if form.cleaned_data.get(field_name):
|
||||||
data[field_name] = form.cleaned_data[f'{field_name}_pattern'][i]
|
data[field_name] = form.cleaned_data[field_name][i]
|
||||||
|
|
||||||
if hasattr(form, 'get_iterative_data'):
|
if hasattr(form, 'get_iterative_data'):
|
||||||
data.update(form.get_iterative_data(i))
|
data.update(form.get_iterative_data(i))
|
||||||
@ -626,7 +619,6 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
|||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'object': instance,
|
'object': instance,
|
||||||
'replication_form': form,
|
'form': form,
|
||||||
'form': model_form,
|
|
||||||
'return_url': self.get_return_url(request),
|
'return_url': self.get_return_url(request),
|
||||||
})
|
})
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
{% if form.module_type %}
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="offset-sm-3">
|
|
||||||
<ul class="nav nav-pills" role="tablist">
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="devicetype_tab" data-bs-toggle="tab" aria-controls="devicetype" data-bs-target="#devicetype" class="nav-link {% if not form.initial.module_type %}active{% endif %}">
|
|
||||||
Device Type
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="moduletype_tab" data-bs-toggle="tab" aria-controls="moduletype" data-bs-target="#moduletype" class="nav-link {% if form.initial.module_type %}active{% endif %}">
|
|
||||||
Module Type
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-content p-0 border-0">
|
|
||||||
<div class="tab-pane {% if not form.initial.module_type %}active{% endif %}" id="devicetype" role="tabpanel">
|
|
||||||
{% render_field replication_form.device_type %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.module_type %}active{% endif %}" id="moduletype" role="tabpanel">
|
|
||||||
{% render_field replication_form.module_type %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
{% render_field replication_form.device_type %}
|
|
||||||
{% endif %}
|
|
||||||
{% block replication_fields %}
|
|
||||||
{% render_field replication_form.name_pattern %}
|
|
||||||
{% render_field replication_form.label_pattern %}
|
|
||||||
{% endblock replication_fields %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock form %}
|
|
@ -1,16 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<div class="field-group mb-5">
|
|
||||||
{% if form.instance.device %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col-sm-3 col-form-label text-lg-end">Device</label>
|
|
||||||
<div class="col">
|
|
||||||
<input class="form-control" value="{{ form.instance.device }}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% render_form form %}
|
|
||||||
</div>
|
|
||||||
{% endblock form %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'dcim/component_template_create.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block replication_fields %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% render_field replication_form.rear_port_set %}
|
|
||||||
{% endblock replication_fields %}
|
|
@ -1,17 +0,0 @@
|
|||||||
{% extends 'dcim/component_create.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block replication_fields %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if object.component %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col-sm-3 col-form-label text-lg-end">
|
|
||||||
{{ object.component|meta:"verbose_name"|bettertitle }}
|
|
||||||
</label>
|
|
||||||
<div class="col">
|
|
||||||
<input class="form-control" value="{{ object.component }}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock replication_fields %}
|
|
@ -1,17 +0,0 @@
|
|||||||
{% extends 'dcim/component_template_create.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block replication_fields %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if object.component %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col-sm-3 col-form-label text-lg-end">
|
|
||||||
{{ object.component|meta:"verbose_name"|bettertitle }}
|
|
||||||
</label>
|
|
||||||
<div class="col">
|
|
||||||
<input class="form-control" value="{{ object.component }}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock replication_fields %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'dcim/component_template_create.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block replication_fields %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% render_field replication_form.position_pattern %}
|
|
||||||
{% endblock replication_fields %}
|
|
@ -34,7 +34,7 @@
|
|||||||
{% for class_name, script in module_scripts.items %}
|
{% for class_name, script in module_scripts.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'extras:script' module=script.module name=class_name %}" name="script.{{ class_name }}">{{ script.name }}</a>
|
<a href="{% url 'extras:script' module=script.root_module name=class_name %}" name="script.{{ class_name }}">{{ script.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% include 'extras/inc/job_label.html' with result=script.result %}
|
{% include 'extras/inc/job_label.html' with result=script.result %}
|
||||||
|
@ -59,9 +59,11 @@ Context:
|
|||||||
{# Render grouped fields according to Form #}
|
{# Render grouped fields according to Form #}
|
||||||
{% for group, fields in form.fieldsets %}
|
{% for group, fields in form.fieldsets %}
|
||||||
<div class="field-group mb-5">
|
<div class="field-group mb-5">
|
||||||
<div class="row mb-2">
|
{% if group %}
|
||||||
<h5 class="offset-sm-3">{{ group }}</h5>
|
<div class="row mb-2">
|
||||||
</div>
|
<h5 class="offset-sm-3">{{ group }}</h5>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% for name in fields %}
|
{% for name in fields %}
|
||||||
{% with field=form|getfield:name %}
|
{% with field=form|getfield:name %}
|
||||||
{% if not field.field.widget.is_hidden %}
|
{% if not field.field.widget.is_hidden %}
|
||||||
|
@ -46,4 +46,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if form.custom_fields %}
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
|
</div>
|
||||||
|
{% render_custom_fields form %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -13,6 +13,16 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Login form errors #}
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<h4 class="alert-heading">Errors</h4>
|
||||||
|
<p>
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# Login form #}
|
{# Login form #}
|
||||||
<div class="form-login">
|
<div class="form-login">
|
||||||
<form action="{% url 'login' %}" method="post">
|
<form action="{% url 'login' %}" method="post">
|
||||||
@ -48,16 +58,6 @@
|
|||||||
</h5>
|
</h5>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Login form errors #}
|
|
||||||
{% if form.non_field_errors %}
|
|
||||||
<div class="alert alert-danger" role="alert">
|
|
||||||
<h4 class="alert-heading">Errors</h4>
|
|
||||||
<p>
|
|
||||||
{{ form.non_field_errors }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{# Page footer #}
|
{# Page footer #}
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
{# Render hidden fields #}
|
|
||||||
{% for field in form.hidden_fields %}
|
|
||||||
{{ field }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">Interface</h5>
|
|
||||||
</div>
|
|
||||||
{% if form.instance.virtual_machine %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col-sm-3 col-form-label text-lg-end required" for="id_device">Virtual Machine</label>
|
|
||||||
<div class="col">
|
|
||||||
<input class="form-control" value="{{ form.instance.virtual_machine }}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% render_field form.name %}
|
|
||||||
{% render_field form.description %}
|
|
||||||
{% render_field form.tags %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">Addressing</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.vrf %}
|
|
||||||
{% render_field form.mac_address %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">Operation</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.mtu %}
|
|
||||||
{% render_field form.enabled %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">Related Interfaces</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.parent %}
|
|
||||||
{% render_field form.bridge %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">802.1Q Switching</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.mode %}
|
|
||||||
{% render_field form.vlan_group %}
|
|
||||||
{% render_field form.untagged_vlan %}
|
|
||||||
{% render_field form.tagged_vlans %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
|
||||||
</div>
|
|
||||||
{% render_custom_fields form %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -47,20 +47,14 @@ class LoginView(View):
|
|||||||
'url': f'{url}?{urlencode(params)}',
|
'url': f'{url}?{urlencode(params)}',
|
||||||
}
|
}
|
||||||
|
|
||||||
def get(self, request):
|
def get_auth_backends(self, request):
|
||||||
form = LoginForm(request)
|
|
||||||
|
|
||||||
if request.user.is_authenticated:
|
|
||||||
logger = logging.getLogger('netbox.auth.login')
|
|
||||||
return self.redirect_to_next(request, logger)
|
|
||||||
|
|
||||||
auth_backends = []
|
auth_backends = []
|
||||||
saml_idps = get_saml_idps()
|
saml_idps = get_saml_idps()
|
||||||
|
|
||||||
for name in load_backends(settings.AUTHENTICATION_BACKENDS).keys():
|
for name in load_backends(settings.AUTHENTICATION_BACKENDS).keys():
|
||||||
url = reverse('social:begin', args=[name, ])
|
url = reverse('social:begin', args=[name])
|
||||||
params = {}
|
params = {}
|
||||||
next = request.GET.get('next')
|
if next := request.GET.get('next'):
|
||||||
if next:
|
|
||||||
params['next'] = next
|
params['next'] = next
|
||||||
if name.lower() == 'saml' and saml_idps:
|
if name.lower() == 'saml' and saml_idps:
|
||||||
for idp in saml_idps:
|
for idp in saml_idps:
|
||||||
@ -71,9 +65,18 @@ class LoginView(View):
|
|||||||
else:
|
else:
|
||||||
auth_backends.append(self.gen_auth_data(name, url, params))
|
auth_backends.append(self.gen_auth_data(name, url, params))
|
||||||
|
|
||||||
|
return auth_backends
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
form = LoginForm(request)
|
||||||
|
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
logger = logging.getLogger('netbox.auth.login')
|
||||||
|
return self.redirect_to_next(request, logger)
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'form': form,
|
'form': form,
|
||||||
'auth_backends': auth_backends,
|
'auth_backends': self.get_auth_backends(request),
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
@ -107,7 +110,7 @@ class LoginView(View):
|
|||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'form': form,
|
'form': form,
|
||||||
'auth_backends': load_backends(settings.AUTHENTICATION_BACKENDS),
|
'auth_backends': self.get_auth_backends(request),
|
||||||
})
|
})
|
||||||
|
|
||||||
def redirect_to_next(self, request, logger):
|
def redirect_to_next(self, request, logger):
|
||||||
|
@ -22,7 +22,7 @@ class ExpandableNameField(forms.CharField):
|
|||||||
if not self.help_text:
|
if not self.help_text:
|
||||||
self.help_text = """
|
self.help_text = """
|
||||||
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
||||||
are not supported. Example: <code>[ge,xe]-0/0/[0-9]</code>
|
are not supported (example: <code>[ge,xe]-0/0/[0-9]</code>).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
|
@ -466,6 +466,7 @@ class ViewTestCases:
|
|||||||
"""
|
"""
|
||||||
bulk_create_count = 3
|
bulk_create_count = 3
|
||||||
bulk_create_data = {}
|
bulk_create_data = {}
|
||||||
|
validation_excluded_fields = []
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
||||||
def test_create_multiple_objects_without_permission(self):
|
def test_create_multiple_objects_without_permission(self):
|
||||||
@ -500,7 +501,7 @@ class ViewTestCases:
|
|||||||
self.assertHttpStatus(response, 302)
|
self.assertHttpStatus(response, 302)
|
||||||
self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
|
self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
|
||||||
for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
|
for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
|
||||||
self.assertInstanceEqual(instance, self.bulk_create_data)
|
self.assertInstanceEqual(instance, self.bulk_create_data, exclude=self.validation_excluded_fields)
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
||||||
def test_create_multiple_objects_with_constrained_permission(self):
|
def test_create_multiple_objects_with_constrained_permission(self):
|
||||||
@ -532,7 +533,7 @@ class ViewTestCases:
|
|||||||
self.assertHttpStatus(response, 302)
|
self.assertHttpStatus(response, 302)
|
||||||
self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
|
self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
|
||||||
for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
|
for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
|
||||||
self.assertInstanceEqual(instance, self.bulk_create_data)
|
self.assertInstanceEqual(instance, self.bulk_create_data, exclude=self.validation_excluded_fields)
|
||||||
|
|
||||||
class BulkImportObjectsViewTestCase(ModelViewTestCase):
|
class BulkImportObjectsViewTestCase(ModelViewTestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -13,7 +13,7 @@ class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
|
|||||||
queryset=VirtualMachine.objects.all(),
|
queryset=VirtualMachine.objects.all(),
|
||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
name = ExpandableNameField(
|
||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,4 +27,4 @@ class VMInterfaceBulkCreateForm(
|
|||||||
form_from_model(VMInterface, ['enabled', 'mtu', 'description', 'tags']),
|
form_from_model(VMInterface, ['enabled', 'mtu', 'description', 'tags']),
|
||||||
VirtualMachineBulkAddComponentForm
|
VirtualMachineBulkAddComponentForm
|
||||||
):
|
):
|
||||||
pass
|
replication_fields = ('name',)
|
||||||
|
@ -5,7 +5,6 @@ from django.core.exceptions import ValidationError
|
|||||||
from dcim.forms.common import InterfaceCommonForm
|
from dcim.forms.common import InterfaceCommonForm
|
||||||
from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
|
from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
|
||||||
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
|
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
|
||||||
from extras.models import Tag
|
|
||||||
from ipam.models import IPAddress, VLAN, VLANGroup, VRF
|
from ipam.models import IPAddress, VLAN, VLANGroup, VRF
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
@ -278,6 +277,9 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
||||||
|
virtual_machine = DynamicModelChoiceField(
|
||||||
|
queryset=VirtualMachine.objects.all()
|
||||||
|
)
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
queryset=VMInterface.objects.all(),
|
queryset=VMInterface.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -338,7 +340,6 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
|||||||
'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
|
'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'virtual_machine': forms.HiddenInput(),
|
|
||||||
'mode': StaticSelect()
|
'mode': StaticSelect()
|
||||||
}
|
}
|
||||||
labels = {
|
labels = {
|
||||||
@ -347,3 +348,10 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
|||||||
help_texts = {
|
help_texts = {
|
||||||
'mode': INTERFACE_MODE_HELP_TEXT,
|
'mode': INTERFACE_MODE_HELP_TEXT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Disable reassignment of VirtualMachine when editing an existing instance
|
||||||
|
if self.instance.pk:
|
||||||
|
self.fields['virtual_machine'].disabled = True
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
from django import forms
|
from utilities.forms import ExpandableNameField
|
||||||
|
from .models import VMInterfaceForm
|
||||||
from utilities.forms import BootstrapMixin, DynamicModelChoiceField, ExpandableNameField
|
|
||||||
from .models import VirtualMachine
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'VMInterfaceCreateForm',
|
'VMInterfaceCreateForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class VMInterfaceCreateForm(BootstrapMixin, forms.Form):
|
class VMInterfaceCreateForm(VMInterfaceForm):
|
||||||
virtual_machine = DynamicModelChoiceField(
|
name = ExpandableNameField()
|
||||||
queryset=VirtualMachine.objects.all()
|
replication_fields = ('name',)
|
||||||
)
|
|
||||||
name_pattern = ExpandableNameField(
|
class Meta(VMInterfaceForm.Meta):
|
||||||
label='Name'
|
exclude = ('name',)
|
||||||
)
|
|
||||||
|
@ -251,6 +251,7 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
|
|
||||||
class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = VMInterface
|
model = VMInterface
|
||||||
|
validation_excluded_fields = ('name',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -290,10 +291,10 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'virtual_machine': virtualmachines[1].pk,
|
'virtual_machine': virtualmachines[0].pk,
|
||||||
'name': 'Interface X',
|
'name': 'Interface X',
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
'bridge': interfaces[3].pk,
|
'bridge': interfaces[1].pk,
|
||||||
'mac_address': EUI('01-02-03-04-05-06'),
|
'mac_address': EUI('01-02-03-04-05-06'),
|
||||||
'mtu': 65000,
|
'mtu': 65000,
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
@ -306,7 +307,7 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'virtual_machine': virtualmachines[1].pk,
|
'virtual_machine': virtualmachines[1].pk,
|
||||||
'name_pattern': 'Interface [4-6]',
|
'name': 'Interface [4-6]',
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
'bridge': interfaces[3].pk,
|
'bridge': interfaces[3].pk,
|
||||||
'mac_address': EUI('01-02-03-04-05-06'),
|
'mac_address': EUI('01-02-03-04-05-06'),
|
||||||
|
@ -451,13 +451,11 @@ class VMInterfaceCreateView(generic.ComponentCreateView):
|
|||||||
queryset = VMInterface.objects.all()
|
queryset = VMInterface.objects.all()
|
||||||
form = forms.VMInterfaceCreateForm
|
form = forms.VMInterfaceCreateForm
|
||||||
model_form = forms.VMInterfaceForm
|
model_form = forms.VMInterfaceForm
|
||||||
patterned_fields = ('name',)
|
|
||||||
|
|
||||||
|
|
||||||
class VMInterfaceEditView(generic.ObjectEditView):
|
class VMInterfaceEditView(generic.ObjectEditView):
|
||||||
queryset = VMInterface.objects.all()
|
queryset = VMInterface.objects.all()
|
||||||
form = forms.VMInterfaceForm
|
form = forms.VMInterfaceForm
|
||||||
template_name = 'virtualization/vminterface_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class VMInterfaceDeleteView(generic.ObjectDeleteView):
|
class VMInterfaceDeleteView(generic.ObjectDeleteView):
|
||||||
|
@ -19,13 +19,13 @@ graphene-django==2.15.0
|
|||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.2
|
||||||
Markdown==3.4.1
|
Markdown==3.4.1
|
||||||
mkdocs-material==8.4.2
|
mkdocs-material==8.5.1
|
||||||
mkdocstrings[python-legacy]==0.19.0
|
mkdocstrings[python-legacy]==0.19.0
|
||||||
netaddr==0.8.0
|
netaddr==0.8.0
|
||||||
Pillow==9.2.0
|
Pillow==9.2.0
|
||||||
psycopg2-binary==2.9.3
|
psycopg2-binary==2.9.3
|
||||||
PyYAML==6.0
|
PyYAML==6.0
|
||||||
sentry-sdk==1.9.7
|
sentry-sdk==1.9.8
|
||||||
social-auth-app-django==5.0.0
|
social-auth-app-django==5.0.0
|
||||||
social-auth-core==4.3.0
|
social-auth-core==4.3.0
|
||||||
svgwrite==1.4.3
|
svgwrite==1.4.3
|
||||||
|
Loading…
Reference in New Issue
Block a user