Răsfoiți Sursa

Further work on power feed modeling

Jeremy Stretch 7 ani în urmă
părinte
comite
681e20133a

+ 1 - 1
netbox/dcim/api/views.py

@@ -496,7 +496,7 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
     queryset = PowerPort.objects.select_related(
     queryset = PowerPort.objects.select_related(
         'device', 'connected_endpoint__device'
         'device', 'connected_endpoint__device'
     ).filter(
     ).filter(
-        connected_endpoint__isnull=False
+        _connected_poweroutlet__isnull=False
     )
     )
     serializer_class = serializers.PowerPortSerializer
     serializer_class = serializers.PowerPortSerializer
     filterset_class = filters.PowerConnectionFilter
     filterset_class = filters.PowerConnectionFilter

+ 1 - 1
netbox/dcim/constants.py

@@ -420,7 +420,7 @@ CABLE_TERMINATION_TYPE_CHOICES = {
 COMPATIBLE_TERMINATION_TYPES = {
 COMPATIBLE_TERMINATION_TYPES = {
     'consoleport': ['consoleserverport', 'frontport', 'rearport'],
     'consoleport': ['consoleserverport', 'frontport', 'rearport'],
     'consoleserverport': ['consoleport', 'frontport', 'rearport'],
     'consoleserverport': ['consoleport', 'frontport', 'rearport'],
-    'powerport': ['poweroutlet'],
+    'powerport': ['poweroutlet', 'powerfeed'],
     'poweroutlet': ['powerport'],
     'poweroutlet': ['powerport'],
     'interface': ['interface', 'circuittermination', 'frontport', 'rearport'],
     'interface': ['interface', 'circuittermination', 'frontport', 'rearport'],
     'frontport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
     'frontport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],

+ 100 - 1
netbox/dcim/forms.py

@@ -10,6 +10,7 @@ from mptt.forms import TreeNodeChoiceField
 from taggit.forms import TagField
 from taggit.forms import TagField
 from timezone_field import TimeZoneFormField
 from timezone_field import TimeZoneFormField
 
 
+from circuits.models import Circuit, CircuitTermination, Provider
 from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
 from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
 from ipam.models import IPAddress, VLAN, VLANGroup
 from ipam.models import IPAddress, VLAN, VLANGroup
 from tenancy.forms import TenancyForm
 from tenancy.forms import TenancyForm
@@ -2521,7 +2522,7 @@ class RearPortBulkDisconnectForm(ConfirmationForm):
 # Cables
 # Cables
 #
 #
 
 
-class CableCreateForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
+class ConnectCableToDeviceForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
     termination_b_site = forms.ModelChoiceField(
     termination_b_site = forms.ModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         label='Site',
         label='Site',
@@ -2602,6 +2603,104 @@ class CableCreateForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
         )
         )
 
 
 
 
+class ConnectCableToCircuitForm(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_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='Termination',
+        widget=APISelect(
+            api_url='/api/circuits/circuit-terminations/',
+            disabled_indicator='cable'
+        )
+    )
+
+    class Meta:
+        model = Cable
+        fields = [
+            'termination_b_provider', 'termination_b_circuit', 'termination_b_id', 'type', 'status', 'label', 'color',
+            'length', 'length_unit',
+        ]
+
+
+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_powerfeed': 'powerpanel_id',
+            }
+        )
+    )
+    termination_b_id = forms.IntegerField(
+        label='Power Feed',
+        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):
 class CableForm(BootstrapMixin, forms.ModelForm):
 
 
     class Meta:
     class Meta:

+ 24 - 3
netbox/dcim/migrations/0072_powerfeeds.py

@@ -1,4 +1,4 @@
-# Generated by Django 2.1.7 on 2019-03-12 14:08
+# 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
@@ -21,14 +21,15 @@ class Migration(migrations.Migration):
                 ('created', models.DateField(auto_now_add=True, null=True)),
                 ('created', models.DateField(auto_now_add=True, null=True)),
                 ('last_updated', models.DateTimeField(auto_now=True, null=True)),
                 ('last_updated', models.DateTimeField(auto_now=True, null=True)),
                 ('name', models.CharField(max_length=50)),
                 ('name', models.CharField(max_length=50)),
-                ('type', models.PositiveSmallIntegerField(default=1)),
                 ('status', models.PositiveSmallIntegerField(default=1)),
                 ('status', models.PositiveSmallIntegerField(default=1)),
+                ('type', models.PositiveSmallIntegerField(default=1)),
                 ('supply', models.PositiveSmallIntegerField(default=1)),
                 ('supply', models.PositiveSmallIntegerField(default=1)),
+                ('phase', models.PositiveSmallIntegerField(default=1)),
                 ('voltage', models.PositiveSmallIntegerField(default=120, validators=[django.core.validators.MinValueValidator(1)])),
                 ('voltage', models.PositiveSmallIntegerField(default=120, validators=[django.core.validators.MinValueValidator(1)])),
                 ('amperage', models.PositiveSmallIntegerField(default=20, validators=[django.core.validators.MinValueValidator(1)])),
                 ('amperage', models.PositiveSmallIntegerField(default=20, validators=[django.core.validators.MinValueValidator(1)])),
-                ('phase', models.PositiveSmallIntegerField(default=1)),
                 ('max_utilization', models.PositiveSmallIntegerField(default=80, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])),
                 ('max_utilization', models.PositiveSmallIntegerField(default=80, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])),
                 ('comments', models.TextField(blank=True)),
                 ('comments', models.TextField(blank=True)),
+                ('cable', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable')),
             ],
             ],
             options={
             options={
                 'ordering': ['power_panel', 'name'],
                 'ordering': ['power_panel', 'name'],
@@ -63,6 +64,26 @@ class Migration(migrations.Migration):
             name='tags',
             name='tags',
             field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
             field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
         ),
         ),
+        migrations.AddField(
+            model_name='powerfeed',
+            name='connected_endpoint',
+            field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.PowerPort'),
+        ),
+        migrations.AddField(
+            model_name='powerfeed',
+            name='connection_status',
+            field=models.NullBooleanField(),
+        ),
+        migrations.RenameField(
+            model_name='powerport',
+            old_name='connected_endpoint',
+            new_name='_connected_poweroutlet',
+        ),
+        migrations.AddField(
+            model_name='powerport',
+            name='_connected_powerfeed',
+            field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.PowerFeed'),
+        ),
         migrations.AlterUniqueTogether(
         migrations.AlterUniqueTogether(
             name='powerpanel',
             name='powerpanel',
             unique_together={('site', 'name')},
             unique_together={('site', 'name')},

+ 50 - 2
netbox/dcim/models.py

@@ -1828,13 +1828,20 @@ class PowerPort(CableTermination, ComponentModel):
     name = models.CharField(
     name = models.CharField(
         max_length=50
         max_length=50
     )
     )
-    connected_endpoint = models.OneToOneField(
+    _connected_poweroutlet = models.OneToOneField(
         to='dcim.PowerOutlet',
         to='dcim.PowerOutlet',
         on_delete=models.SET_NULL,
         on_delete=models.SET_NULL,
         related_name='connected_endpoint',
         related_name='connected_endpoint',
         blank=True,
         blank=True,
         null=True
         null=True
     )
     )
+    _connected_powerfeed = models.OneToOneField(
+        to='dcim.PowerFeed',
+        on_delete=models.SET_NULL,
+        related_name='+',
+        blank=True,
+        null=True
+    )
     connection_status = models.NullBooleanField(
     connection_status = models.NullBooleanField(
         choices=CONNECTION_STATUS_CHOICES,
         choices=CONNECTION_STATUS_CHOICES,
         blank=True
         blank=True
@@ -1862,6 +1869,28 @@ class PowerPort(CableTermination, ComponentModel):
             self.description,
             self.description,
         )
         )
 
 
+    @property
+    def connected_endpoint(self):
+        if self._connected_poweroutlet:
+            return self._connected_poweroutlet
+        return self._connected_powerfeed
+
+    @connected_endpoint.setter
+    def connected_endpoint(self, value):
+        if value is None:
+            self._connected_poweroutlet = None
+            self._connected_powerfeed = None
+        elif isinstance(value, PowerOutlet):
+            self._connected_poweroutlet = value
+            self._connected_powerfeed = None
+        elif isinstance(value, PowerFeed):
+            self._connected_poweroutlet = None
+            self._connected_powerfeed = value
+        else:
+            raise ValueError(
+                "Connected endpoint must be a PowerOutlet or PowerFeed, not {}.".format(type(value))
+            )
+
 
 
 #
 #
 # Power outlets
 # Power outlets
@@ -2646,6 +2675,14 @@ class Cable(ChangeLoggedModel):
     def get_status_class(self):
     def get_status_class(self):
         return 'success' if self.status else 'info'
         return 'success' if self.status else 'info'
 
 
+    def get_compatible_types(self):
+        """
+        Return all termination types compatible with termination A.
+        """
+        if self.termination_a is None:
+            return
+        return COMPATIBLE_TERMINATION_TYPES[self.termination_a._meta.model_name]
+
     def get_path_endpoints(self):
     def get_path_endpoints(self):
         """
         """
         Traverse both ends of a cable path and return its connected endpoints. Note that one or both endpoints may be
         Traverse both ends of a cable path and return its connected endpoints. Note that one or both endpoints may be
@@ -2712,7 +2749,7 @@ class PowerPanel(ChangeLoggedModel):
         )
         )
 
 
 
 
-class PowerFeed(ChangeLoggedModel, CustomFieldModel):
+class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
     """
     """
     An electrical circuit delivered from a PowerPanel.
     An electrical circuit delivered from a PowerPanel.
     """
     """
@@ -2727,6 +2764,17 @@ class PowerFeed(ChangeLoggedModel, CustomFieldModel):
         blank=True,
         blank=True,
         null=True
         null=True
     )
     )
+    connected_endpoint = models.OneToOneField(
+        to='dcim.PowerPort',
+        on_delete=models.SET_NULL,
+        related_name='+',
+        blank=True,
+        null=True
+    )
+    connection_status = models.NullBooleanField(
+        choices=CONNECTION_STATUS_CHOICES,
+        blank=True
+    )
     name = models.CharField(
     name = models.CharField(
         max_length=50
         max_length=50
     )
     )

+ 71 - 13
netbox/dcim/views.py

@@ -3,6 +3,7 @@ import re
 from django.conf import settings
 from django.conf import settings
 from django.contrib import messages
 from django.contrib import messages
 from django.contrib.auth.mixins import PermissionRequiredMixin
 from django.contrib.auth.mixins import PermissionRequiredMixin
+from django.contrib.contenttypes.models import ContentType
 from django.core.paginator import EmptyPage, PageNotAnInteger
 from django.core.paginator import EmptyPage, PageNotAnInteger
 from django.db import transaction
 from django.db import transaction
 from django.db.models import Count, F
 from django.db.models import Count, F
@@ -10,10 +11,11 @@ from django.forms import modelformset_factory
 from django.shortcuts import get_object_or_404, redirect, render
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.html import escape
 from django.utils.html import escape
+from django.utils.http import is_safe_url
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 from django.views.generic import View
 from django.views.generic import View
 
 
-from circuits.models import Circuit
+from circuits.models import Circuit, CircuitTermination
 from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
 from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
 from extras.views import ObjectConfigContextView
 from extras.views import ObjectConfigContextView
 from ipam.models import Prefix, VLAN
 from ipam.models import Prefix, VLAN
@@ -913,7 +915,7 @@ class DeviceView(View):
         consoleserverports = device.consoleserverports.select_related('connected_endpoint__device', 'cable')
         consoleserverports = device.consoleserverports.select_related('connected_endpoint__device', 'cable')
 
 
         # Power ports
         # Power ports
-        power_ports = device.powerports.select_related('connected_endpoint__device', 'cable')
+        power_ports = device.powerports.select_related('_connected_poweroutlet__device', 'cable')
 
 
         # Power outlets
         # Power outlets
         poweroutlets = device.poweroutlets.select_related('connected_endpoint__device', 'cable')
         poweroutlets = device.poweroutlets.select_related('connected_endpoint__device', 'cable')
@@ -1673,20 +1675,76 @@ class CableTraceView(View):
         })
         })
 
 
 
 
-class CableCreateView(PermissionRequiredMixin, ObjectEditView):
+class CableCreateView(PermissionRequiredMixin, GetReturnURLMixin, View):
     permission_required = 'dcim.add_cable'
     permission_required = 'dcim.add_cable'
-    model = Cable
-    model_form = forms.CableCreateForm
     template_name = 'dcim/cable_connect.html'
     template_name = 'dcim/cable_connect.html'
 
 
-    def alter_obj(self, obj, request, url_args, url_kwargs):
+    def _get_form_class(self):
+        if self.termination_b_type == 'circuit':
+            return forms.ConnectCableToCircuitForm
+        if self.termination_b_type == 'powerfeed':
+            return forms.ConnectCableToPowerFeedForm
+        return forms.ConnectCableToDeviceForm
+
+    def dispatch(self, request, *args, **kwargs):
 
 
         # Retrieve endpoint A based on the given type and PK
         # Retrieve endpoint A based on the given type and PK
-        termination_a_type = url_kwargs.get('termination_a_type')
-        termination_a_id = url_kwargs.get('termination_a_id')
-        obj.termination_a = termination_a_type.objects.get(pk=termination_a_id)
+        termination_a_type = kwargs.get('termination_a_type')
+        termination_a_id = kwargs.get('termination_a_id')
+        self.obj = Cable(
+            termination_a=termination_a_type.objects.get(pk=termination_a_id)
+        )
 
 
-        return obj
+        self.termination_b_type = request.GET.get('type')
+        if self.termination_b_type == 'circuit':
+            self.obj.termination_b_type = ContentType.objects.get_for_model(CircuitTermination)
+        elif self.termination_b_type == 'powerfeed':
+            self.obj.termination_b_type = ContentType.objects.get_for_model(PowerFeed)
+
+        return super().dispatch(request, *args, **kwargs)
+
+    def get(self, request, *args, **kwargs):
+
+        # Parse initial data manually to avoid setting field values as lists
+        initial_data = {k: request.GET[k] for k in request.GET}
+
+        form = self._get_form_class()(instance=self.obj, initial=initial_data)
+
+        return render(request, self.template_name, {
+            'obj': self.obj,
+            'obj_type': Cable._meta.verbose_name,
+            'form': form,
+            'return_url': self.get_return_url(request, self.obj),
+        })
+
+    def post(self, request, *args, **kwargs):
+
+        form = self._get_form_class()(request.POST, request.FILES, instance=self.obj)
+
+        if form.is_valid():
+            obj = form.save()
+
+            msg = 'Created cable <a href="{}">{}</a>'.format(
+                obj.get_absolute_url(),
+                escape(obj)
+            )
+            messages.success(request, mark_safe(msg))
+
+            if '_addanother' in request.POST:
+                return redirect(request.get_full_path())
+
+            return_url = form.cleaned_data.get('return_url')
+            if return_url is not None and is_safe_url(url=return_url, allowed_hosts=request.get_host()):
+                return redirect(return_url)
+            else:
+                return redirect(self.get_return_url(request, obj))
+
+        return render(request, self.template_name, {
+            'obj': self.obj,
+            'obj_type': Cable._meta.verbose_name,
+            'form': form,
+            'return_url': self.get_return_url(request, self.obj),
+        })
 
 
 
 
 class CableEditView(PermissionRequiredMixin, ObjectEditView):
 class CableEditView(PermissionRequiredMixin, ObjectEditView):
@@ -1763,11 +1821,11 @@ class ConsoleConnectionsListView(ObjectListView):
 
 
 class PowerConnectionsListView(ObjectListView):
 class PowerConnectionsListView(ObjectListView):
     queryset = PowerPort.objects.select_related(
     queryset = PowerPort.objects.select_related(
-        'device', 'connected_endpoint__device'
+        'device', '_connected_poweroutlet__device'
     ).filter(
     ).filter(
-        connected_endpoint__isnull=False
+        _connected_poweroutlet__isnull=False
     ).order_by(
     ).order_by(
-        'cable', 'connected_endpoint__device__name', 'connected_endpoint__name'
+        'cable', '_connected_poweroutlet__device__name', '_connected_poweroutlet__name'
     )
     )
     filter = filters.PowerConnectionFilter
     filter = filters.PowerConnectionFilter
     filter_form = forms.PowerConnectionFilterForm
     filter_form = forms.PowerConnectionFilterForm

+ 1 - 1
netbox/extras/models.py

@@ -541,7 +541,7 @@ class TopologyMap(models.Model):
         from dcim.models import PowerPort
         from dcim.models import PowerPort
 
 
         # Add all power connections to the graph
         # Add all power connections to the graph
-        for pp in PowerPort.objects.filter(device__in=devices, connected_endpoint__device__in=devices):
+        for pp in PowerPort.objects.filter(device__in=devices, _connected_poweroutlet__device__in=devices):
             style = 'solid' if pp.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed'
             style = 'solid' if pp.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed'
             self.graph.edge(pp.connected_endpoint.device.name, pp.device.name, style=style)
             self.graph.edge(pp.connected_endpoint.device.name, pp.device.name, style=style)
 
 

+ 1 - 1
netbox/netbox/views.py

@@ -166,7 +166,7 @@ class HomeView(View):
             connected_endpoint__isnull=False
             connected_endpoint__isnull=False
         )
         )
         connected_powerports = PowerPort.objects.filter(
         connected_powerports = PowerPort.objects.filter(
-            connected_endpoint__isnull=False
+            _connected_poweroutlet__isnull=False
         )
         )
         connected_interfaces = Interface.objects.filter(
         connected_interfaces = Interface.objects.filter(
             _connected_interface__isnull=False,
             _connected_interface__isnull=False,

+ 28 - 15
netbox/templates/dcim/cable_connect.html

@@ -101,21 +101,34 @@
                         <strong>B Side</strong>
                         <strong>B Side</strong>
                     </div>
                     </div>
                     <div class="panel-body">
                     <div class="panel-body">
-                        <ul class="nav nav-tabs" role="tablist">
-                            <li role="presentation" class="active"><a href="#search" aria-controls="search" role="tab" data-toggle="tab">Search</a></li>
-                            <li role="presentation"><a href="#select" aria-controls="home" role="tab" data-toggle="tab">Select</a></li>
-                        </ul>
-                        <div class="tab-content">
-                            <div class="tab-pane active" id="search">
-                                &nbsp;
-                            </div>
-                            <div class="tab-pane" id="select">
-                                {% render_field form.termination_b_site %}
-                                {% render_field form.termination_b_rack %}
-                            </div>
-                        </div>
-                        {% render_field form.termination_b_device %}
-                        {% render_field form.termination_b_type %}
+                        {# TODO: Clean this up #}
+                        {% if 'termination_b_site' in form.fields %}
+                            {% render_field form.termination_b_site %}
+                        {% endif %}
+                        {% if 'termination_b_rackgroup' in form.fields %}
+                            {% render_field form.termination_b_rackgroup %}
+                        {% endif %}
+                        {% if 'termination_b_rack' in form.fields %}
+                            {% render_field form.termination_b_rack %}
+                        {% endif %}
+                        {% if 'termination_b_device' in form.fields %}
+                            {% render_field form.termination_b_device %}
+                        {% endif %}
+                        {% if 'termination_b_type' in form.fields %}
+                            {% render_field form.termination_b_type %}
+                        {% endif %}
+                        {% if 'termination_b_provider' in form.fields %}
+                            {% render_field form.termination_b_provider %}
+                        {% endif %}
+                        {% if 'termination_b_circuit' in form.fields %}
+                            {% render_field form.termination_b_circuit %}
+                        {% endif %}
+                        {% if 'termination_b_powerpanel' in form.fields %}
+                            {% render_field form.termination_b_powerpanel %}
+                        {% endif %}
+                        {% if 'termination_b_powerfeed' in form.fields %}
+                            {% render_field form.termination_b_powerfeed %}
+                        {% endif %}
                         {% render_field form.termination_b_id %}
                         {% render_field form.termination_b_id %}
                     </div>
                     </div>
                 </div>
                 </div>

+ 5 - 1
netbox/templates/dcim/inc/powerport.html

@@ -20,13 +20,17 @@
     </td>
     </td>
 
 
     {# Connection #}
     {# Connection #}
-    {% if pp.connected_endpoint %}
+    {% if pp.connected_endpoint.device %}
         <td>
         <td>
             <a href="{% url 'dcim:device' pk=pp.connected_endpoint.device.pk %}">{{ pp.connected_endpoint.device }}</a>
             <a href="{% url 'dcim:device' pk=pp.connected_endpoint.device.pk %}">{{ pp.connected_endpoint.device }}</a>
         </td>
         </td>
         <td>
         <td>
             {{ pp.connected_endpoint }}
             {{ pp.connected_endpoint }}
         </td>
         </td>
+    {% elif pp.connected_endpoint %}
+        <td colspan="2">
+            <a href="{{ pp.connected_endpoint.get_absolute_url }}">{{ pp.connected_endpoint }}</a>
+        </td>
     {% else %}
     {% else %}
         <td colspan="2">
         <td colspan="2">
             <span class="text-muted">Not connected</span>
             <span class="text-muted">Not connected</span>