|
|
@@ -10,24 +10,24 @@ from mptt.forms import TreeNodeChoiceField
|
|
|
from taggit.forms import TagField
|
|
|
from timezone_field import TimeZoneFormField
|
|
|
|
|
|
+from circuits.models import Circuit, Provider
|
|
|
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
|
|
from ipam.models import IPAddress, VLAN, VLANGroup
|
|
|
from tenancy.forms import TenancyForm
|
|
|
from tenancy.models import Tenant
|
|
|
from utilities.forms import (
|
|
|
APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
|
|
|
- BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ColorSelect, CommentField,
|
|
|
- ComponentForm, ConfirmationForm, ContentTypeSelect, CSVChoiceField, ExpandableNameField,
|
|
|
- FilterChoiceField, FlexibleModelChoiceField, JSONField, SelectWithPK, SmallTextarea, SlugField,
|
|
|
- StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES
|
|
|
+ BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ColorSelect, CommentField, ComponentForm,
|
|
|
+ ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, JSONField,
|
|
|
+ SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES
|
|
|
)
|
|
|
from virtualization.models import Cluster, ClusterGroup
|
|
|
from .constants import *
|
|
|
from .models import (
|
|
|
Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
|
|
|
Device, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer,
|
|
|
- InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
|
|
- RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis
|
|
|
+ InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate,
|
|
|
+ Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
|
|
|
)
|
|
|
|
|
|
DEVICE_BY_PK_RE = r'{\d+\}'
|
|
|
@@ -963,7 +963,7 @@ class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
|
|
class Meta:
|
|
|
model = PowerPortTemplate
|
|
|
fields = [
|
|
|
- 'device_type', 'name',
|
|
|
+ 'device_type', 'name', 'maximum_draw', 'allocated_draw',
|
|
|
]
|
|
|
widgets = {
|
|
|
'device_type': forms.HiddenInput(),
|
|
|
@@ -977,16 +977,29 @@ class PowerPortTemplateCreateForm(ComponentForm):
|
|
|
|
|
|
|
|
|
class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
|
|
|
+ power_port = forms.ModelChoiceField(
|
|
|
+ queryset=PowerPortTemplate.objects.all(),
|
|
|
+ required=False
|
|
|
+ )
|
|
|
|
|
|
class Meta:
|
|
|
model = PowerOutletTemplate
|
|
|
fields = [
|
|
|
- 'device_type', 'name',
|
|
|
+ 'device_type', 'name', 'power_port', 'feed_leg',
|
|
|
]
|
|
|
widgets = {
|
|
|
'device_type': forms.HiddenInput(),
|
|
|
}
|
|
|
|
|
|
+ def __init__(self, *args, **kwargs):
|
|
|
+
|
|
|
+ super().__init__(*args, **kwargs)
|
|
|
+
|
|
|
+ # Limit power_port choices to current DeviceType
|
|
|
+ self.fields['power_port'].queryset = PowerPortTemplate.objects.filter(
|
|
|
+ device_type=self.parent
|
|
|
+ )
|
|
|
+
|
|
|
|
|
|
class PowerOutletTemplateCreateForm(ComponentForm):
|
|
|
name_pattern = ExpandableNameField(
|
|
|
@@ -1947,7 +1960,7 @@ class PowerPortForm(BootstrapMixin, forms.ModelForm):
|
|
|
class Meta:
|
|
|
model = PowerPort
|
|
|
fields = [
|
|
|
- 'device', 'name', 'description', 'tags',
|
|
|
+ 'device', 'name', 'maximum_draw', 'allocated_draw', 'description', 'tags',
|
|
|
]
|
|
|
widgets = {
|
|
|
'device': forms.HiddenInput(),
|
|
|
@@ -1972,6 +1985,10 @@ class PowerPortCreateForm(ComponentForm):
|
|
|
#
|
|
|
|
|
|
class PowerOutletForm(BootstrapMixin, forms.ModelForm):
|
|
|
+ power_port = forms.ModelChoiceField(
|
|
|
+ queryset=PowerPort.objects.all(),
|
|
|
+ required=False
|
|
|
+ )
|
|
|
tags = TagField(
|
|
|
required=False
|
|
|
)
|
|
|
@@ -1979,12 +1996,20 @@ class PowerOutletForm(BootstrapMixin, forms.ModelForm):
|
|
|
class Meta:
|
|
|
model = PowerOutlet
|
|
|
fields = [
|
|
|
- 'device', 'name', 'description', 'tags',
|
|
|
+ 'device', 'name', 'power_port', 'feed_leg', 'description', 'tags',
|
|
|
]
|
|
|
widgets = {
|
|
|
'device': forms.HiddenInput(),
|
|
|
}
|
|
|
|
|
|
+ def __init__(self, *args, **kwargs):
|
|
|
+ super().__init__(*args, **kwargs)
|
|
|
+
|
|
|
+ # Limit power_port choices to the local device
|
|
|
+ self.fields['power_port'].queryset = PowerPort.objects.filter(
|
|
|
+ device=self.instance.device
|
|
|
+ )
|
|
|
+
|
|
|
|
|
|
class PowerOutletCreateForm(ComponentForm):
|
|
|
name_pattern = ExpandableNameField(
|
|
|
@@ -2004,6 +2029,10 @@ class PowerOutletBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
|
|
queryset=PowerOutlet.objects.all(),
|
|
|
widget=forms.MultipleHiddenInput()
|
|
|
)
|
|
|
+ feed_leg = forms.ChoiceField(
|
|
|
+ choices=POWERFEED_LEG_CHOICES,
|
|
|
+ required=False,
|
|
|
+ )
|
|
|
description = forms.CharField(
|
|
|
max_length=100,
|
|
|
required=False
|
|
|
@@ -2520,7 +2549,10 @@ class RearPortBulkDisconnectForm(ConfirmationForm):
|
|
|
# Cables
|
|
|
#
|
|
|
|
|
|
-class CableCreateForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
|
|
+class ConnectCableToDeviceForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
|
|
+ """
|
|
|
+ Base form for connecting a Cable to a Device component
|
|
|
+ """
|
|
|
termination_b_site = forms.ModelChoiceField(
|
|
|
queryset=Site.objects.all(),
|
|
|
label='Site',
|
|
|
@@ -2566,39 +2598,196 @@ class CableCreateForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
|
|
}
|
|
|
)
|
|
|
)
|
|
|
- termination_b_type = forms.ModelChoiceField(
|
|
|
- queryset=ContentType.objects.all(),
|
|
|
- label='Type',
|
|
|
- widget=ContentTypeSelect()
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ model = Cable
|
|
|
+ fields = [
|
|
|
+ 'termination_b_site', 'termination_b_rack', 'termination_b_device', 'termination_b_id', 'type', 'status',
|
|
|
+ 'label', 'color', 'length', 'length_unit',
|
|
|
+ ]
|
|
|
+
|
|
|
+
|
|
|
+class ConnectCableToConsolePortForm(ConnectCableToDeviceForm):
|
|
|
+ termination_b_id = forms.IntegerField(
|
|
|
+ label='Name',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/console-ports/',
|
|
|
+ disabled_indicator='cable',
|
|
|
+ )
|
|
|
)
|
|
|
+
|
|
|
+
|
|
|
+class ConnectCableToConsoleServerPortForm(ConnectCableToDeviceForm):
|
|
|
termination_b_id = forms.IntegerField(
|
|
|
label='Name',
|
|
|
widget=APISelect(
|
|
|
- api_url='/api/dcim/{{termination_b_type}}s/',
|
|
|
+ api_url='/api/dcim/console-server-ports/',
|
|
|
disabled_indicator='cable',
|
|
|
- conditional_query_params={
|
|
|
- 'termination_b_type__interface': 'type=physical',
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+class ConnectCableToPowerPortForm(ConnectCableToDeviceForm):
|
|
|
+ termination_b_id = forms.IntegerField(
|
|
|
+ label='Name',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/power-ports/',
|
|
|
+ disabled_indicator='cable',
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+class ConnectCableToPowerOutletForm(ConnectCableToDeviceForm):
|
|
|
+ termination_b_id = forms.IntegerField(
|
|
|
+ label='Name',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/power-outlets/',
|
|
|
+ disabled_indicator='cable',
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+class ConnectCableToInterfaceForm(ConnectCableToDeviceForm):
|
|
|
+ termination_b_id = forms.IntegerField(
|
|
|
+ label='Name',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/interfaces/',
|
|
|
+ disabled_indicator='cable',
|
|
|
+ additional_query_params={
|
|
|
+ 'type': 'physical',
|
|
|
}
|
|
|
)
|
|
|
)
|
|
|
|
|
|
+
|
|
|
+class ConnectCableToFrontPortForm(ConnectCableToDeviceForm):
|
|
|
+ termination_b_id = forms.IntegerField(
|
|
|
+ label='Name',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/front-ports/',
|
|
|
+ disabled_indicator='cable',
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
|
|
|
+ termination_b_id = forms.IntegerField(
|
|
|
+ label='Name',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/rear-ports/',
|
|
|
+ disabled_indicator='cable',
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
|
|
+ termination_b_provider = forms.ModelChoiceField(
|
|
|
+ queryset=Provider.objects.all(),
|
|
|
+ label='Provider',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/circuits/providers/',
|
|
|
+ filter_for={
|
|
|
+ 'termination_b_circuit': 'provider_id',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ )
|
|
|
+ termination_b_site = forms.ModelChoiceField(
|
|
|
+ queryset=Site.objects.all(),
|
|
|
+ label='Site',
|
|
|
+ required=False,
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/sites/',
|
|
|
+ filter_for={
|
|
|
+ 'termination_b_circuit': 'site_id',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ )
|
|
|
+ termination_b_circuit = ChainedModelChoiceField(
|
|
|
+ queryset=Circuit.objects.all(),
|
|
|
+ chains=(
|
|
|
+ ('provider', 'termination_b_provider'),
|
|
|
+ ),
|
|
|
+ label='Circuit',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/circuits/circuits/',
|
|
|
+ display_field='cid',
|
|
|
+ filter_for={
|
|
|
+ 'termination_b_id': 'circuit_id',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ )
|
|
|
+ termination_b_id = forms.IntegerField(
|
|
|
+ label='Side',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/circuits/circuit-terminations/',
|
|
|
+ disabled_indicator='cable',
|
|
|
+ display_field='term_side'
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
class Meta:
|
|
|
model = Cable
|
|
|
fields = [
|
|
|
- 'termination_b_site', 'termination_b_rack', 'termination_b_device', 'termination_b_type',
|
|
|
- 'termination_b_id', 'type', 'status', 'label', 'color', 'length', 'length_unit',
|
|
|
+ 'termination_b_provider', 'termination_b_site', 'termination_b_circuit', 'termination_b_id', 'type',
|
|
|
+ 'status', 'label', 'color', 'length', 'length_unit',
|
|
|
]
|
|
|
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
- super().__init__(*args, **kwargs)
|
|
|
|
|
|
- # Define available types for endpoint B based on the type of endpoint A
|
|
|
- termination_a_type = self.instance.termination_a._meta.model_name
|
|
|
- self.fields['termination_b_type'].queryset = ContentType.objects.filter(
|
|
|
- model__in=COMPATIBLE_TERMINATION_TYPES.get(termination_a_type)
|
|
|
- ).exclude(
|
|
|
- model='circuittermination'
|
|
|
+class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
|
|
+ termination_b_site = forms.ModelChoiceField(
|
|
|
+ queryset=Site.objects.all(),
|
|
|
+ label='Site',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/sites/',
|
|
|
+ display_field='cid',
|
|
|
+ filter_for={
|
|
|
+ 'termination_b_rackgroup': 'site_id',
|
|
|
+ 'termination_b_powerpanel': 'site_id',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ )
|
|
|
+ termination_b_rackgroup = ChainedModelChoiceField(
|
|
|
+ queryset=RackGroup.objects.all(),
|
|
|
+ label='Rack Group',
|
|
|
+ chains=(
|
|
|
+ ('site', 'termination_b_site'),
|
|
|
+ ),
|
|
|
+ required=False,
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/rack-groups/',
|
|
|
+ display_field='cid',
|
|
|
+ filter_for={
|
|
|
+ 'termination_b_powerpanel': 'rackgroup_id',
|
|
|
+ }
|
|
|
)
|
|
|
+ )
|
|
|
+ termination_b_powerpanel = ChainedModelChoiceField(
|
|
|
+ queryset=PowerPanel.objects.all(),
|
|
|
+ chains=(
|
|
|
+ ('site', 'termination_b_site'),
|
|
|
+ ('rack_group', 'termination_b_rackgroup'),
|
|
|
+ ),
|
|
|
+ label='Power Panel',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/power-panels/',
|
|
|
+ filter_for={
|
|
|
+ 'termination_b_id': 'power_panel_id',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ )
|
|
|
+ termination_b_id = forms.IntegerField(
|
|
|
+ label='Name',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/power-feeds/',
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ model = Cable
|
|
|
+ fields = [
|
|
|
+ 'termination_b_rackgroup', 'termination_b_powerpanel', 'termination_b_id', 'type', 'status', 'label',
|
|
|
+ 'color', 'length', 'length_unit',
|
|
|
+ ]
|
|
|
|
|
|
|
|
|
class CableForm(BootstrapMixin, forms.ModelForm):
|
|
|
@@ -3155,3 +3344,346 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|
|
to_field_name='slug',
|
|
|
null_label='-- None --',
|
|
|
)
|
|
|
+
|
|
|
+
|
|
|
+#
|
|
|
+# Power panels
|
|
|
+#
|
|
|
+
|
|
|
+class PowerPanelForm(BootstrapMixin, forms.ModelForm):
|
|
|
+ rack_group = ChainedModelChoiceField(
|
|
|
+ queryset=RackGroup.objects.all(),
|
|
|
+ chains=(
|
|
|
+ ('site', 'site'),
|
|
|
+ ),
|
|
|
+ required=False,
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/rack-groups/',
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ model = PowerPanel
|
|
|
+ fields = [
|
|
|
+ 'site', 'rack_group', 'name',
|
|
|
+ ]
|
|
|
+ widgets = {
|
|
|
+ 'site': APISelect(
|
|
|
+ api_url="/api/dcim/sites/",
|
|
|
+ filter_for={
|
|
|
+ 'rack_group': 'site_id',
|
|
|
+ }
|
|
|
+ ),
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+class PowerPanelCSVForm(forms.ModelForm):
|
|
|
+ site = forms.ModelChoiceField(
|
|
|
+ queryset=Site.objects.all(),
|
|
|
+ to_field_name='name',
|
|
|
+ help_text='Name of parent site',
|
|
|
+ error_messages={
|
|
|
+ 'invalid_choice': 'Site not found.',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ rack_group_name = forms.CharField(
|
|
|
+ required=False,
|
|
|
+ help_text="Rack group name (optional)"
|
|
|
+ )
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ model = PowerPanel
|
|
|
+ fields = PowerPanel.csv_headers
|
|
|
+
|
|
|
+ def clean(self):
|
|
|
+
|
|
|
+ super().clean()
|
|
|
+
|
|
|
+ site = self.cleaned_data.get('site')
|
|
|
+ rack_group_name = self.cleaned_data.get('rack_group_name')
|
|
|
+
|
|
|
+ # Validate rack group
|
|
|
+ if rack_group_name:
|
|
|
+ try:
|
|
|
+ self.instance.rack_group = RackGroup.objects.get(site=site, name=rack_group_name)
|
|
|
+ except RackGroup.DoesNotExist:
|
|
|
+ raise forms.ValidationError(
|
|
|
+ "Rack group {} not found in site {}".format(rack_group_name, site)
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|
|
+ model = PowerPanel
|
|
|
+ q = forms.CharField(
|
|
|
+ required=False,
|
|
|
+ label='Search'
|
|
|
+ )
|
|
|
+ site = FilterChoiceField(
|
|
|
+ queryset=Site.objects.all(),
|
|
|
+ to_field_name='slug',
|
|
|
+ widget=APISelectMultiple(
|
|
|
+ api_url="/api/dcim/sites/",
|
|
|
+ value_field="slug",
|
|
|
+ filter_for={
|
|
|
+ 'rack_id': 'site',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ )
|
|
|
+ rack_group_id = FilterChoiceField(
|
|
|
+ queryset=RackGroup.objects.all(),
|
|
|
+ label='Rack group (ID)',
|
|
|
+ null_label='-- None --',
|
|
|
+ widget=APISelectMultiple(
|
|
|
+ api_url="/api/dcim/rack-groups/",
|
|
|
+ null_option=True,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+#
|
|
|
+# Power feeds
|
|
|
+#
|
|
|
+
|
|
|
+class PowerFeedForm(BootstrapMixin, CustomFieldForm):
|
|
|
+ site = ChainedModelChoiceField(
|
|
|
+ queryset=Site.objects.all(),
|
|
|
+ required=False,
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/sites/',
|
|
|
+ filter_for={
|
|
|
+ 'power_panel': 'site_id',
|
|
|
+ 'rack': 'site_id',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ )
|
|
|
+ comments = CommentField()
|
|
|
+ tags = TagField(
|
|
|
+ required=False
|
|
|
+ )
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ model = PowerFeed
|
|
|
+ fields = [
|
|
|
+ 'site', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage',
|
|
|
+ 'power_factor', 'comments', 'tags',
|
|
|
+ ]
|
|
|
+ widgets = {
|
|
|
+ 'power_panel': APISelect(
|
|
|
+ api_url="/api/dcim/power-panels/"
|
|
|
+ ),
|
|
|
+ 'rack': APISelect(
|
|
|
+ api_url="/api/dcim/racks/"
|
|
|
+ ),
|
|
|
+ 'status': StaticSelect2(),
|
|
|
+ 'type': StaticSelect2(),
|
|
|
+ 'supply': StaticSelect2(),
|
|
|
+ 'phase': StaticSelect2(),
|
|
|
+ }
|
|
|
+
|
|
|
+ def __init__(self, *args, **kwargs):
|
|
|
+
|
|
|
+ super().__init__(*args, **kwargs)
|
|
|
+
|
|
|
+ # Initialize site field
|
|
|
+ if self.instance and self.instance.power_panel:
|
|
|
+ self.initial['site'] = self.instance.power_panel.site
|
|
|
+
|
|
|
+
|
|
|
+class PowerFeedCSVForm(forms.ModelForm):
|
|
|
+ site = forms.ModelChoiceField(
|
|
|
+ queryset=Site.objects.all(),
|
|
|
+ to_field_name='name',
|
|
|
+ help_text='Name of parent site',
|
|
|
+ error_messages={
|
|
|
+ 'invalid_choice': 'Site not found.',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ panel_name = forms.ModelChoiceField(
|
|
|
+ queryset=PowerPanel.objects.all(),
|
|
|
+ to_field_name='name',
|
|
|
+ help_text='Name of upstream power panel',
|
|
|
+ error_messages={
|
|
|
+ 'invalid_choice': 'Power panel not found.',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ rack_group = forms.CharField(
|
|
|
+ required=False,
|
|
|
+ help_text="Rack group name (optional)"
|
|
|
+ )
|
|
|
+ rack_name = forms.CharField(
|
|
|
+ required=False,
|
|
|
+ help_text="Rack name (optional)"
|
|
|
+ )
|
|
|
+ status = CSVChoiceField(
|
|
|
+ choices=POWERFEED_STATUS_CHOICES,
|
|
|
+ required=False,
|
|
|
+ help_text='Operational status'
|
|
|
+ )
|
|
|
+ type = CSVChoiceField(
|
|
|
+ choices=POWERFEED_TYPE_CHOICES,
|
|
|
+ required=False,
|
|
|
+ help_text='Primary or redundant'
|
|
|
+ )
|
|
|
+ supply = CSVChoiceField(
|
|
|
+ choices=POWERFEED_SUPPLY_CHOICES,
|
|
|
+ required=False,
|
|
|
+ help_text='AC/DC'
|
|
|
+ )
|
|
|
+ phase = CSVChoiceField(
|
|
|
+ choices=POWERFEED_PHASE_CHOICES,
|
|
|
+ required=False,
|
|
|
+ help_text='Single or three-phase'
|
|
|
+ )
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ model = PowerFeed
|
|
|
+ fields = PowerFeed.csv_headers
|
|
|
+
|
|
|
+ def clean(self):
|
|
|
+
|
|
|
+ super().clean()
|
|
|
+
|
|
|
+ site = self.cleaned_data.get('site')
|
|
|
+ panel_name = self.cleaned_data.get('panel_name')
|
|
|
+ rack_group = self.cleaned_data.get('rack_group')
|
|
|
+ rack_name = self.cleaned_data.get('rack_name')
|
|
|
+
|
|
|
+ # Validate power panel
|
|
|
+ if panel_name:
|
|
|
+ try:
|
|
|
+ self.instance.power_panel = PowerPanel.objects.get(site=site, name=panel_name)
|
|
|
+ except Rack.DoesNotExist:
|
|
|
+ raise forms.ValidationError(
|
|
|
+ "Power panel {} not found in site {}".format(panel_name, site)
|
|
|
+ )
|
|
|
+
|
|
|
+ # Validate rack
|
|
|
+ if rack_name:
|
|
|
+ try:
|
|
|
+ self.instance.rack = Rack.objects.get(site=site, rack_group=rack_group, name=rack_name)
|
|
|
+ except Rack.DoesNotExist:
|
|
|
+ raise forms.ValidationError(
|
|
|
+ "Rack {} not found in site {}, group {}".format(rack_name, site, rack_group)
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
|
|
+ pk = forms.ModelMultipleChoiceField(
|
|
|
+ queryset=PowerFeed.objects.all(),
|
|
|
+ widget=forms.MultipleHiddenInput
|
|
|
+ )
|
|
|
+ powerpanel = forms.ModelChoiceField(
|
|
|
+ queryset=PowerPanel.objects.all(),
|
|
|
+ required=False,
|
|
|
+ widget=APISelect(
|
|
|
+ api_url="/api/dcim/sites",
|
|
|
+ filter_for={
|
|
|
+ 'rackgroup': 'site_id',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ )
|
|
|
+ rack = forms.ModelChoiceField(
|
|
|
+ queryset=Rack.objects.all(),
|
|
|
+ required=False,
|
|
|
+ widget=APISelect(
|
|
|
+ api_url="/api/dcim/racks",
|
|
|
+ )
|
|
|
+ )
|
|
|
+ status = forms.ChoiceField(
|
|
|
+ choices=add_blank_choice(POWERFEED_STATUS_CHOICES),
|
|
|
+ required=False,
|
|
|
+ initial='',
|
|
|
+ widget=StaticSelect2()
|
|
|
+ )
|
|
|
+ type = forms.ChoiceField(
|
|
|
+ choices=add_blank_choice(POWERFEED_TYPE_CHOICES),
|
|
|
+ required=False,
|
|
|
+ initial='',
|
|
|
+ widget=StaticSelect2()
|
|
|
+ )
|
|
|
+ supply = forms.ChoiceField(
|
|
|
+ choices=add_blank_choice(POWERFEED_SUPPLY_CHOICES),
|
|
|
+ required=False,
|
|
|
+ initial='',
|
|
|
+ widget=StaticSelect2()
|
|
|
+ )
|
|
|
+ phase = forms.ChoiceField(
|
|
|
+ choices=add_blank_choice(POWERFEED_PHASE_CHOICES),
|
|
|
+ required=False,
|
|
|
+ initial='',
|
|
|
+ widget=StaticSelect2()
|
|
|
+ )
|
|
|
+ voltage = forms.IntegerField(
|
|
|
+ required=False
|
|
|
+ )
|
|
|
+ amperage = forms.IntegerField(
|
|
|
+ required=False
|
|
|
+ )
|
|
|
+ power_factor = forms.IntegerField(
|
|
|
+ required=False
|
|
|
+ )
|
|
|
+ comments = forms.CharField(
|
|
|
+ required=False
|
|
|
+ )
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ nullable_fields = [
|
|
|
+ 'rackgroup', 'comments',
|
|
|
+ ]
|
|
|
+
|
|
|
+
|
|
|
+class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|
|
+ model = PowerFeed
|
|
|
+ q = forms.CharField(
|
|
|
+ required=False,
|
|
|
+ label='Search'
|
|
|
+ )
|
|
|
+ site = FilterChoiceField(
|
|
|
+ queryset=Site.objects.all(),
|
|
|
+ to_field_name='slug',
|
|
|
+ widget=APISelectMultiple(
|
|
|
+ api_url="/api/dcim/sites/",
|
|
|
+ value_field="slug",
|
|
|
+ filter_for={
|
|
|
+ 'rack_id': 'site',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ )
|
|
|
+ rack_id = FilterChoiceField(
|
|
|
+ queryset=Rack.objects.all(),
|
|
|
+ label='Rack',
|
|
|
+ null_label='-- None --',
|
|
|
+ widget=APISelectMultiple(
|
|
|
+ api_url="/api/dcim/racks/",
|
|
|
+ null_option=True,
|
|
|
+ )
|
|
|
+ )
|
|
|
+ status = forms.MultipleChoiceField(
|
|
|
+ choices=POWERFEED_STATUS_CHOICES,
|
|
|
+ required=False,
|
|
|
+ widget=StaticSelect2Multiple()
|
|
|
+ )
|
|
|
+ type = forms.ChoiceField(
|
|
|
+ choices=add_blank_choice(POWERFEED_TYPE_CHOICES),
|
|
|
+ required=False,
|
|
|
+ widget=StaticSelect2()
|
|
|
+ )
|
|
|
+ supply = forms.ChoiceField(
|
|
|
+ choices=add_blank_choice(POWERFEED_SUPPLY_CHOICES),
|
|
|
+ required=False,
|
|
|
+ widget=StaticSelect2()
|
|
|
+ )
|
|
|
+ phase = forms.ChoiceField(
|
|
|
+ choices=add_blank_choice(POWERFEED_PHASE_CHOICES),
|
|
|
+ required=False,
|
|
|
+ widget=StaticSelect2()
|
|
|
+ )
|
|
|
+ voltage = forms.IntegerField(
|
|
|
+ required=False
|
|
|
+ )
|
|
|
+ amperage = forms.IntegerField(
|
|
|
+ required=False
|
|
|
+ )
|
|
|
+ power_factor = forms.IntegerField(
|
|
|
+ required=False
|
|
|
+ )
|