Add associatiton from power outlet to power port/phase

This commit is contained in:
Jeremy Stretch 2019-04-10 14:16:16 -04:00
parent 0ddd71fc36
commit 3d5f85c0ca
8 changed files with 137 additions and 10 deletions

View File

@ -215,10 +215,16 @@ class PowerPortTemplateSerializer(ValidatedModelSerializer):
class PowerOutletTemplateSerializer(ValidatedModelSerializer): class PowerOutletTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer() device_type = NestedDeviceTypeSerializer()
power_port = PowerPortTemplateSerializer()
feed_leg = ChoiceField(
choices=POWERFEED_LEG_CHOICES,
required=False,
allow_null=True
)
class Meta: class Meta:
model = PowerOutletTemplate model = PowerOutletTemplate
fields = ['id', 'device_type', 'name'] fields = ['id', 'device_type', 'name', 'power_port', 'feed_leg']
class InterfaceTemplateSerializer(ValidatedModelSerializer): class InterfaceTemplateSerializer(ValidatedModelSerializer):
@ -372,14 +378,24 @@ class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
class PowerOutletSerializer(TaggitSerializer, ConnectedEndpointSerializer): class PowerOutletSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
cable = NestedCableSerializer(read_only=True) power_port = NestedPowerPortSerializer()
tags = TagListSerializerField(required=False) feed_leg = ChoiceField(
choices=POWERFEED_LEG_CHOICES,
required=False,
allow_null=True
)
cable = NestedCableSerializer(
read_only=True
)
tags = TagListSerializerField(
required=False
)
class Meta: class Meta:
model = PowerOutlet model = PowerOutlet
fields = [ fields = [
'id', 'device', 'name', 'description', 'connected_endpoint_type', 'connected_endpoint', 'connection_status', 'id', 'device', 'name', 'power_port', 'feed_leg', 'description', 'connected_endpoint_type',
'cable', 'tags', 'connected_endpoint', 'connection_status', 'cable', 'tags',
] ]

View File

@ -44,6 +44,8 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
(FrontPortTemplate, ['type']), (FrontPortTemplate, ['type']),
(Interface, ['form_factor', 'mode']), (Interface, ['form_factor', 'mode']),
(InterfaceTemplate, ['form_factor']), (InterfaceTemplate, ['form_factor']),
(PowerOutlet, ['feed_leg']),
(PowerOutletTemplate, ['feed_leg']),
(PowerPort, ['connection_status']), (PowerPort, ['connection_status']),
(Rack, ['outer_unit', 'status', 'type', 'width']), (Rack, ['outer_unit', 'status', 'type', 'width']),
(RearPort, ['type']), (RearPort, ['type']),

View File

@ -475,3 +475,11 @@ POWERFEED_STATUS_CHOICES = (
(POWERFEED_STATUS_PLANNED, 'Planned'), (POWERFEED_STATUS_PLANNED, 'Planned'),
(POWERFEED_STATUS_FAILED, 'Failed'), (POWERFEED_STATUS_FAILED, 'Failed'),
) )
POWERFEED_LEG_A = 1
POWERFEED_LEG_B = 2
POWERFEED_LEG_C = 3
POWERFEED_LEG_CHOICES = (
(POWERFEED_LEG_A, 'A'),
(POWERFEED_LEG_B, 'B'),
(POWERFEED_LEG_C, 'C'),
)

View File

@ -977,16 +977,29 @@ class PowerPortTemplateCreateForm(ComponentForm):
class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm): class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
power_port = forms.ModelChoiceField(
queryset=PowerPortTemplate.objects.all(),
required=False
)
class Meta: class Meta:
model = PowerOutletTemplate model = PowerOutletTemplate
fields = [ fields = [
'device_type', 'name', 'device_type', 'name', 'power_port', 'feed_leg',
] ]
widgets = { widgets = {
'device_type': forms.HiddenInput(), '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): class PowerOutletTemplateCreateForm(ComponentForm):
name_pattern = ExpandableNameField( name_pattern = ExpandableNameField(
@ -1972,6 +1985,10 @@ class PowerPortCreateForm(ComponentForm):
# #
class PowerOutletForm(BootstrapMixin, forms.ModelForm): class PowerOutletForm(BootstrapMixin, forms.ModelForm):
power_port = forms.ModelChoiceField(
queryset=PowerPort.objects.all(),
required=False
)
tags = TagField( tags = TagField(
required=False required=False
) )
@ -1979,12 +1996,20 @@ class PowerOutletForm(BootstrapMixin, forms.ModelForm):
class Meta: class Meta:
model = PowerOutlet model = PowerOutlet
fields = [ fields = [
'device', 'name', 'description', 'tags', 'device', 'name', 'power_port', 'feed_leg', 'description', 'tags',
] ]
widgets = { widgets = {
'device': forms.HiddenInput(), '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): class PowerOutletCreateForm(ComponentForm):
name_pattern = ExpandableNameField( name_pattern = ExpandableNameField(
@ -2004,6 +2029,10 @@ class PowerOutletBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
queryset=PowerOutlet.objects.all(), queryset=PowerOutlet.objects.all(),
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
feed_leg = forms.ChoiceField(
choices=POWERFEED_LEG_CHOICES,
required=False,
)
description = forms.CharField( description = forms.CharField(
max_length=100, max_length=100,
required=False required=False

View File

@ -1,5 +1,3 @@
# Generated by Django 2.1.7 on 2019-03-21 20:59
import django.core.validators import django.core.validators
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -112,4 +110,24 @@ class Migration(migrations.Migration):
name='powerfeed', name='powerfeed',
unique_together={('power_panel', 'name')}, unique_together={('power_panel', 'name')},
), ),
migrations.AddField(
model_name='poweroutlet',
name='feed_leg',
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='poweroutlet',
name='power_port',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='poweroutlets', to='dcim.PowerPort'),
),
migrations.AddField(
model_name='poweroutlettemplate',
name='feed_leg',
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='poweroutlettemplate',
name='power_port',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='poweroutlet_templates', to='dcim.PowerPortTemplate'),
),
] ]

View File

@ -1088,6 +1088,19 @@ class PowerOutletTemplate(ComponentTemplateModel):
name = models.CharField( name = models.CharField(
max_length=50 max_length=50
) )
power_port = models.ForeignKey(
to='dcim.PowerPortTemplate',
on_delete=models.SET_NULL,
blank=True,
null=True,
related_name='poweroutlet_templates'
)
feed_leg = models.PositiveSmallIntegerField(
choices=POWERFEED_LEG_CHOICES,
blank=True,
null=True,
help_text="Phase (for three-phase feeds)"
)
objects = DeviceComponentManager() objects = DeviceComponentManager()
@ -1098,6 +1111,14 @@ class PowerOutletTemplate(ComponentTemplateModel):
def __str__(self): def __str__(self):
return self.name return self.name
def clean(self):
# Validate power port assignment
if self.power_port and self.power_port.device_type != self.device_type:
raise ValidationError(
"Parent power port ({}) must belong to the same device type".format(self.power_port)
)
class InterfaceTemplate(ComponentTemplateModel): class InterfaceTemplate(ComponentTemplateModel):
""" """
@ -1934,6 +1955,19 @@ class PowerOutlet(CableTermination, ComponentModel):
name = models.CharField( name = models.CharField(
max_length=50 max_length=50
) )
power_port = models.ForeignKey(
to='dcim.PowerPort',
on_delete=models.SET_NULL,
blank=True,
null=True,
related_name='poweroutlets'
)
feed_leg = models.PositiveSmallIntegerField(
choices=POWERFEED_LEG_CHOICES,
blank=True,
null=True,
help_text="Phase (for three-phase feeds)"
)
connection_status = models.NullBooleanField( connection_status = models.NullBooleanField(
choices=CONNECTION_STATUS_CHOICES, choices=CONNECTION_STATUS_CHOICES,
blank=True blank=True
@ -1942,7 +1976,7 @@ class PowerOutlet(CableTermination, ComponentModel):
objects = DeviceComponentManager() objects = DeviceComponentManager()
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'description'] csv_headers = ['device', 'name', 'power_port', 'feed_leg', 'description']
class Meta: class Meta:
unique_together = ['device', 'name'] unique_together = ['device', 'name']
@ -1957,9 +1991,19 @@ class PowerOutlet(CableTermination, ComponentModel):
return ( return (
self.device.identifier, self.device.identifier,
self.name, self.name,
self.power_port.name if self.power_port else None,
self.get_feed_leg_display(),
self.description, self.description,
) )
def clean(self):
# Validate power port assignment
if self.power_port and self.power_port.device != self.device:
raise ValidationError(
"Parent power port ({}) must belong to the same device".format(self.power_port)
)
# #
# Interfaces # Interfaces

View File

@ -627,6 +627,7 @@
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th> <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %} {% endif %}
<th>Name</th> <th>Name</th>
<th>Input/Leg</th>
<th>Description</th> <th>Description</th>
<th>Cable</th> <th>Cable</th>
<th colspan="3">Connection</th> <th colspan="3">Connection</th>

View File

@ -14,6 +14,15 @@
<i class="fa fa-fw fa-bolt"></i> {{ po }} <i class="fa fa-fw fa-bolt"></i> {{ po }}
</td> </td>
{# Power port #}
<td>
{% if po.power_port %}
{{ po.power_port }}{% if po.feed_leg %} / {{ po.get_feed_leg_display }}{% endif %}
{% else %}
<span class="text-warning">None</span>
{% endif %}
</td>
{# Description #} {# Description #}
<td> <td>
{{ po.description|placeholder }} {{ po.description|placeholder }}