Просмотр исходного кода

Add associatiton from power outlet to power port/phase

Jeremy Stretch 6 лет назад
Родитель
Сommit
3d5f85c0ca

+ 21 - 5
netbox/dcim/api/serializers.py

@@ -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)
-    tags = TagListSerializerField(required=False)
+    power_port = NestedPowerPortSerializer()
+    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',
-            'cable', 'tags',
+            'id', 'device', 'name', 'power_port', 'feed_leg', 'description', 'connected_endpoint_type',
+            'connected_endpoint', 'connection_status', 'cable', 'tags',
         ]
         ]
 
 
 
 

+ 2 - 0
netbox/dcim/api/views.py

@@ -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']),

+ 8 - 0
netbox/dcim/constants.py

@@ -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'),
+)

+ 31 - 2
netbox/dcim/forms.py

@@ -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

+ 20 - 2
netbox/dcim/migrations/0072_powerfeeds.py

@@ -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'),
+        ),
     ]
     ]

+ 45 - 1
netbox/dcim/models.py

@@ -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

+ 1 - 0
netbox/templates/dcim/device.html

@@ -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>

+ 9 - 0
netbox/templates/dcim/inc/poweroutlet.html

@@ -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 }}