Browse Source

Merge branch 'develop' into develop-2.1

Conflicts:
	netbox/ipam/models.py
	netbox/netbox/settings.py
	netbox/templates/dcim/inc/interface.html
Jeremy Stretch 8 years ago
parent
commit
5b43a108bc

+ 1 - 1
docs/data-model/extras.md

@@ -119,7 +119,7 @@ Each line of the **device patterns** field represents a hierarchical layer withi
 ```
 ```
 core-switch-[abcd]
 core-switch-[abcd]
 dist-switch\d
 dist-switch\d
-access-switch\d+,oob-switch\d+
+access-switch\d+;oob-switch\d+
 ```
 ```
 
 
 Note that you can combine multiple regexes onto one line using semicolons. The order in which regexes are listed on a line is significant: devices matching the first regex will be rendered first, and subsequent groups will be rendered to the right of those.
 Note that you can combine multiple regexes onto one line using semicolons. The order in which regexes are listed on a line is significant: devices matching the first regex will be rendered first, and subsequent groups will be rendered to the right of those.

+ 4 - 0
netbox/extras/api/customfields.py

@@ -49,6 +49,10 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
 
 
             # Validate selected choice
             # Validate selected choice
             if cf.type == CF_TYPE_SELECT:
             if cf.type == CF_TYPE_SELECT:
+                try:
+                    value = int(value)
+                except ValueError:
+                    raise ValidationError("{}: Choice selections must be passed as integers.".format(field_name))
                 valid_choices = [c.pk for c in cf.choices.all()]
                 valid_choices = [c.pk for c in cf.choices.all()]
                 if value not in valid_choices:
                 if value not in valid_choices:
                     raise ValidationError("Invalid choice for field {}: {}".format(field_name, value))
                     raise ValidationError("Invalid choice for field {}: {}".format(field_name, value))

+ 1 - 1
netbox/extras/models.py

@@ -332,7 +332,7 @@ def image_upload(instance, filename):
     path = 'image-attachments/'
     path = 'image-attachments/'
 
 
     # Rename the file to the provided name, if any. Attempt to preserve the file extension.
     # Rename the file to the provided name, if any. Attempt to preserve the file extension.
-    extension = filename.rsplit('.')[-1]
+    extension = filename.rsplit('.')[-1].lower()
     if instance.name and extension in ['bmp', 'gif', 'jpeg', 'jpg', 'png']:
     if instance.name and extension in ['bmp', 'gif', 'jpeg', 'jpg', 'png']:
         filename = '.'.join([instance.name, extension])
         filename = '.'.join([instance.name, extension])
     elif instance.name:
     elif instance.name:

+ 1 - 1
netbox/extras/views.py

@@ -25,7 +25,7 @@ class ImageAttachmentEditView(PermissionRequiredMixin, ObjectEditView):
 
 
 
 
 class ImageAttachmentDeleteView(PermissionRequiredMixin, ObjectDeleteView):
 class ImageAttachmentDeleteView(PermissionRequiredMixin, ObjectDeleteView):
-    permission_required = 'dcim.delete_imageattachment'
+    permission_required = 'extras.delete_imageattachment'
     model = ImageAttachment
     model = ImageAttachment
 
 
     def get_return_url(self, request, imageattachment):
     def get_return_url(self, request, imageattachment):

+ 12 - 5
netbox/ipam/forms.py

@@ -646,16 +646,23 @@ class IPAddressCSVForm(forms.ModelForm):
 
 
         # Set interface
         # Set interface
         if self.cleaned_data['device'] and self.cleaned_data['interface_name']:
         if self.cleaned_data['device'] and self.cleaned_data['interface_name']:
-            self.instance.interface = Interface.objects.get(device=self.cleaned_data['device'],
+            self.instance.interface = Interface.objects.get(
-                                                            name=self.cleaned_data['interface_name'])
+                device=self.cleaned_data['device'],
+                name=self.cleaned_data['interface_name']
+            )
+
+        ipaddress = super(IPAddressCSVForm, self).save(*args, **kwargs)
+
         # Set as primary for device
         # Set as primary for device
         if self.cleaned_data['is_primary']:
         if self.cleaned_data['is_primary']:
+            device = self.cleaned_data['device']
             if self.instance.address.version == 4:
             if self.instance.address.version == 4:
-                self.instance.primary_ip4_for = self.cleaned_data['device']
+                device.primary_ip4 = ipaddress
             elif self.instance.address.version == 6:
             elif self.instance.address.version == 6:
-                self.instance.primary_ip6_for = self.cleaned_data['device']
+                device.primary_ip6 = ipaddress
+            device.save()
 
 
-        return super(IPAddressCSVForm, self).save(*args, **kwargs)
+        return ipaddress
 
 
 
 
 class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
 class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):

+ 17 - 13
netbox/ipam/models.py

@@ -158,13 +158,9 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel):
         """
         """
         Determine the prefix utilization of the aggregate and return it as a percentage.
         Determine the prefix utilization of the aggregate and return it as a percentage.
         """
         """
-        child_prefixes = Prefix.objects.filter(prefix__net_contained_or_equal=str(self.prefix))
+        queryset = Prefix.objects.filter(prefix__net_contained_or_equal=str(self.prefix))
-        # Remove overlapping prefixes from list of children
+        child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
-        networks = netaddr.cidr_merge([c.prefix for c in child_prefixes])
+        return int(float(child_prefixes.size) / self.prefix.size * 100)
-        children_size = float(0)
-        for p in networks:
-            children_size += p.size
-        return int(children_size / self.prefix.size * 100)
 
 
 
 
 @python_2_unicode_compatible
 @python_2_unicode_compatible
@@ -345,13 +341,21 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
 
 
     def get_utilization(self):
     def get_utilization(self):
         """
         """
-        Determine the utilization of the prefix and return it as a percentage.
+        Determine the utilization of the prefix and return it as a percentage. For Prefixes with a status of
+        "container", calculate utilization based on child prefixes. For all others, count child IP addresses.
         """
         """
-        child_count = self.get_child_ips().count()
+        if self.status == PREFIX_STATUS_CONTAINER:
-        prefix_size = self.prefix.size
+            queryset = Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
-        if self.family == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
+            child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
-            prefix_size -= 2
+            return int(float(child_prefixes.size) / self.prefix.size * 100)
-        return int(float(child_count) / prefix_size * 100)
+        else:
+            child_count = IPAddress.objects.filter(
+                address__net_contained_or_equal=str(self.prefix), vrf=self.vrf
+            ).count()
+            prefix_size = self.prefix.size
+            if self.family == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
+                prefix_size -= 2
+            return int(float(child_count) / prefix_size * 100)
 
 
     @property
     @property
     def new_subnet(self):
     def new_subnet(self):

+ 1 - 1
netbox/ipam/tables.py

@@ -241,7 +241,7 @@ class PrefixTable(BaseTable):
     prefix = tables.TemplateColumn(PREFIX_LINK, attrs={'th': {'style': 'padding-left: 17px'}})
     prefix = tables.TemplateColumn(PREFIX_LINK, attrs={'th': {'style': 'padding-left: 17px'}})
     status = tables.TemplateColumn(STATUS_LABEL)
     status = tables.TemplateColumn(STATUS_LABEL)
     vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
     vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
-    get_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='IP Usage')
+    get_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
     tenant = tables.TemplateColumn(TENANT_LINK)
     tenant = tables.TemplateColumn(TENANT_LINK)
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     vlan = tables.LinkColumn('ipam:vlan', args=[Accessor('vlan.pk')], verbose_name='VLAN')
     vlan = tables.LinkColumn('ipam:vlan', args=[Accessor('vlan.pk')], verbose_name='VLAN')

+ 0 - 13
netbox/ipam/views.py

@@ -665,19 +665,6 @@ class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView):
     table = tables.IPAddressTable
     table = tables.IPAddressTable
     default_return_url = 'ipam:ipaddress_list'
     default_return_url = 'ipam:ipaddress_list'
 
 
-    def save_obj(self, obj):
-        obj.save()
-
-        # Update primary IP for device if needed. The Device must be updated directly in the database; otherwise we risk
-        # overwriting a previous IP assignment from the same import (see #861).
-        try:
-            if obj.family == 4 and obj.primary_ip4_for:
-                Device.objects.filter(pk=obj.primary_ip4_for.pk).update(primary_ip4=obj)
-            elif obj.family == 6 and obj.primary_ip6_for:
-                Device.objects.filter(pk=obj.primary_ip6_for.pk).update(primary_ip6=obj)
-        except Device.DoesNotExist:
-            pass
-
 
 
 class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
 class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
     permission_required = 'ipam.change_ipaddress'
     permission_required = 'ipam.change_ipaddress'

+ 3 - 3
netbox/templates/dcim/device.html

@@ -204,7 +204,7 @@
                     None
                     None
                 </div>
                 </div>
             {% endif %}
             {% endif %}
-            {% if perms.dcim.add_service %}
+            {% if perms.ipam.add_service %}
                 <div class="panel-footer text-right">
                 <div class="panel-footer text-right">
                     <a href="{% url 'dcim:service_assign' device=device.pk %}" class="btn btn-xs btn-primary">
                     <a href="{% url 'dcim:service_assign' device=device.pk %}" class="btn btn-xs btn-primary">
                         <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Assign service
                         <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Assign service
@@ -572,7 +572,7 @@ function toggleConnection(elem, api_url) {
             success: function() {
             success: function() {
                 elem.parents('tr').removeClass('success').addClass('info');
                 elem.parents('tr').removeClass('success').addClass('info');
                 elem.removeClass('connected btn-warning').addClass('btn-success');
                 elem.removeClass('connected btn-warning').addClass('btn-success');
-                elem.attr('title', 'Mark connected');
+                elem.attr('title', 'Mark installed');
                 elem.children('i').removeClass('glyphicon glyphicon-ban-circle').addClass('fa fa-plug')
                 elem.children('i').removeClass('glyphicon glyphicon-ban-circle').addClass('fa fa-plug')
             }
             }
         });
         });
@@ -591,7 +591,7 @@ function toggleConnection(elem, api_url) {
             success: function() {
             success: function() {
                 elem.parents('tr').removeClass('info').addClass('success');
                 elem.parents('tr').removeClass('info').addClass('success');
                 elem.removeClass('btn-success').addClass('connected btn-warning');
                 elem.removeClass('btn-success').addClass('connected btn-warning');
-                elem.attr('title', 'Mark disconnected');
+                elem.attr('title', 'Mark planned');
                 elem.children('i').removeClass('fa fa-plug').addClass('glyphicon glyphicon-ban-circle')
                 elem.children('i').removeClass('fa fa-plug').addClass('glyphicon glyphicon-ban-circle')
             }
             }
         });
         });

+ 12 - 12
netbox/templates/dcim/inc/consoleport.html

@@ -18,24 +18,24 @@
         {% if perms.dcim.change_consoleport %}
         {% if perms.dcim.change_consoleport %}
             {% if cp.cs_port %}
             {% if cp.cs_port %}
                 {% if cp.connection_status %}
                 {% if cp.connection_status %}
-                    <a href="#" class="btn btn-warning btn-xs consoleport-toggle connected" data="{{ cp.pk }}">
+                    <a href="#" class="btn btn-warning btn-xs consoleport-toggle connected" title="Mark planned" data="{{ cp.pk }}">
-                        <i class="glyphicon glyphicon-ban-circle" aria-hidden="true" title="Mark planned"></i>
+                        <i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
                     </a>
                     </a>
                 {% else %}
                 {% else %}
-                    <a href="#" class="btn btn-success btn-xs consoleport-toggle" data="{{ cp.pk }}">
+                    <a href="#" class="btn btn-success btn-xs consoleport-toggle" title="Mark installed" data="{{ cp.pk }}">
-                        <i class="fa fa-plug" aria-hidden="true" title="Mark connected"></i>
+                        <i class="fa fa-plug" aria-hidden="true"></i>
                     </a>
                     </a>
                 {% endif %}
                 {% endif %}
-                <a href="{% url 'dcim:consoleport_disconnect' pk=cp.pk %}" class="btn btn-danger btn-xs">
+                <a href="{% url 'dcim:consoleport_disconnect' pk=cp.pk %}" title="Delete connection" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-resize-full" aria-hidden="true" title="Delete connection"></i>
+                    <i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
                 </a>
                 </a>
             {% else %}
             {% else %}
-                <a href="{% url 'dcim:consoleport_connect' pk=cp.pk %}" class="btn btn-success btn-xs">
+                <a href="{% url 'dcim:consoleport_connect' pk=cp.pk %}" title="Connect" class="btn btn-success btn-xs">
-                    <i class="glyphicon glyphicon-resize-small" aria-hidden="true" title="Connect"></i>
+                    <i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
                 </a>
                 </a>
             {% endif %}
             {% endif %}
-            <a href="{% url 'dcim:consoleport_edit' pk=cp.pk %}" class="btn btn-info btn-xs">
+            <a href="{% url 'dcim:consoleport_edit' pk=cp.pk %}" title="Edit port" class="btn btn-info btn-xs">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit port"></i>
+                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
             </a>
             </a>
         {% endif %}
         {% endif %}
         {% if perms.dcim.delete_consoleport %}
         {% if perms.dcim.delete_consoleport %}
@@ -44,8 +44,8 @@
                     <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                     <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                 </button>
                 </button>
             {% else %}
             {% else %}
-                <a href="{% url 'dcim:consoleport_delete' pk=cp.pk %}" class="btn btn-danger btn-xs">
+                <a href="{% url 'dcim:consoleport_delete' pk=cp.pk %}" title="Delete port" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete port"></i>
+                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                 </a>
                 </a>
             {% endif %}
             {% endif %}
         {% endif %}
         {% endif %}

+ 12 - 12
netbox/templates/dcim/inc/consoleserverport.html

@@ -24,24 +24,24 @@
         {% if perms.dcim.change_consoleserverport %}
         {% if perms.dcim.change_consoleserverport %}
             {% if csp.connected_console %}
             {% if csp.connected_console %}
                 {% if csp.connected_console.connection_status %}
                 {% if csp.connected_console.connection_status %}
-                    <a href="#" class="btn btn-warning btn-xs consoleport-toggle connected" data="{{ csp.connected_console.pk }}">
+                    <a href="#" class="btn btn-warning btn-xs consoleport-toggle connected" title="Mark planned" data="{{ csp.connected_console.pk }}">
-                        <i class="glyphicon glyphicon-ban-circle" aria-hidden="true" title="Mark planned"></i>
+                        <i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
                     </a>
                     </a>
                 {% else %}
                 {% else %}
-                    <a href="#" class="btn btn-success btn-xs consoleport-toggle" data="{{ csp.connected_console.pk }}">
+                    <a href="#" class="btn btn-success btn-xs consoleport-toggle" title="Mark installed" data="{{ csp.connected_console.pk }}">
-                        <i class="fa fa-plug" aria-hidden="true" title="Mark connected"></i>
+                        <i class="fa fa-plug" aria-hidden="true"></i>
                     </a>
                     </a>
                 {% endif %}
                 {% endif %}
-                <a href="{% url 'dcim:consoleserverport_disconnect' pk=csp.pk %}" class="btn btn-danger btn-xs">
+                <a href="{% url 'dcim:consoleserverport_disconnect' pk=csp.pk %}" title="Delete connection" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-resize-full" aria-hidden="true" title="Delete connection"></i>
+                    <i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
                 </a>
                 </a>
             {% else %}
             {% else %}
-                <a href="{% url 'dcim:consoleserverport_connect' pk=csp.pk %}" class="btn btn-success btn-xs">
+                <a href="{% url 'dcim:consoleserverport_connect' pk=csp.pk %}" title="Connect" class="btn btn-success btn-xs">
-                    <i class="glyphicon glyphicon-resize-small" aria-hidden="true" title="Connect"></i>
+                    <i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
                 </a>
                 </a>
             {% endif %}
             {% endif %}
-            <a href="{% url 'dcim:consoleserverport_edit' pk=csp.pk %}" class="btn btn-info btn-xs">
+            <a href="{% url 'dcim:consoleserverport_edit' pk=csp.pk %}" title="Edit port" class="btn btn-info btn-xs">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit port"></i>
+                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
             </a>
             </a>
         {% endif %}
         {% endif %}
         {% if perms.dcim.delete_consoleserverport %}
         {% if perms.dcim.delete_consoleserverport %}
@@ -50,8 +50,8 @@
                     <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                     <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                 </button>
                 </button>
             {% else %}
             {% else %}
-                <a href="{% url 'dcim:consoleserverport_delete' pk=csp.pk %}" class="btn btn-danger btn-xs">
+                <a href="{% url 'dcim:consoleserverport_delete' pk=csp.pk %}" title="Delete port" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete port"></i>
+                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                 </a>
                 </a>
             {% endif %}
             {% endif %}
         {% endif %}
         {% endif %}

+ 1 - 1
netbox/templates/dcim/inc/device_header.html

@@ -45,7 +45,7 @@
 <ul class="nav nav-tabs" style="margin-bottom: 20px">
 <ul class="nav nav-tabs" style="margin-bottom: 20px">
     <li role="presentation"{% if active_tab == 'info' %} class="active"{% endif %}><a href="{% url 'dcim:device' pk=device.pk %}">Info</a></li>
     <li role="presentation"{% if active_tab == 'info' %} class="active"{% endif %}><a href="{% url 'dcim:device' pk=device.pk %}">Info</a></li>
     <li role="presentation"{% if active_tab == 'inventory' %} class="active"{% endif %}><a href="{% url 'dcim:device_inventory' pk=device.pk %}">Inventory</a></li>
     <li role="presentation"{% if active_tab == 'inventory' %} class="active"{% endif %}><a href="{% url 'dcim:device_inventory' pk=device.pk %}">Inventory</a></li>
-    {% if device.status %}
+    {% if device.status == 1 and device.platform.rpc_client and device.primary_ip %}
         <li role="presentation"{% if active_tab == 'lldp-neighbors' %} class="active"{% endif %}><a href="{% url 'dcim:device_lldp_neighbors' pk=device.pk %}">LLDP Neighbors</a></li>
         <li role="presentation"{% if active_tab == 'lldp-neighbors' %} class="active"{% endif %}><a href="{% url 'dcim:device_lldp_neighbors' pk=device.pk %}">LLDP Neighbors</a></li>
     {% endif %}
     {% endif %}
 </ul>
 </ul>

+ 2 - 2
netbox/templates/dcim/inc/interface.html

@@ -1,4 +1,4 @@
-<tr class="interface{% if not iface.enabled %} danger{% elif iface.connection and not iface.connection.connection_status %} info{% endif %}">
+<tr class="interface{% if not iface.enabled %} danger{% elif iface.connection and iface.connection.connection_status %} success{% elif iface.connection and not iface.connection.connection_status %} info{% endif %}">
     {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
     {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
         <td class="pk">
         <td class="pk">
             <input name="pk" type="checkbox" value="{{ iface.pk }}" />
             <input name="pk" type="checkbox" value="{{ iface.pk }}" />
@@ -76,7 +76,7 @@
                             <i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
                             <i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
                         </a>
                         </a>
                     {% else %}
                     {% else %}
-                        <a href="#" class="btn btn-success btn-xs interface-toggle" data="{{ iface.connection.pk }}" title="Mark connected">
+                        <a href="#" class="btn btn-success btn-xs interface-toggle" data="{{ iface.connection.pk }}" title="Mark installed">
                             <i class="fa fa-plug" aria-hidden="true"></i>
                             <i class="fa fa-plug" aria-hidden="true"></i>
                         </a>
                         </a>
                     {% endif %}
                     {% endif %}

+ 12 - 12
netbox/templates/dcim/inc/poweroutlet.html

@@ -24,24 +24,24 @@
         {% if perms.dcim.change_poweroutlet %}
         {% if perms.dcim.change_poweroutlet %}
             {% if po.connected_port %}
             {% if po.connected_port %}
                 {% if po.connected_port.connection_status %}
                 {% if po.connected_port.connection_status %}
-                    <a href="#" class="btn btn-warning btn-xs powerport-toggle connected" data="{{ po.connected_port.pk }}">
+                    <a href="#" class="btn btn-warning btn-xs powerport-toggle connected" title="Mark planned" data="{{ po.connected_port.pk }}">
-                        <i class="glyphicon glyphicon-ban-circle" aria-hidden="true" title="Mark planned"></i>
+                        <i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
                     </a>
                     </a>
                 {% else %}
                 {% else %}
-                    <a href="#" class="btn btn-success btn-xs consoleport-toggle" data="{{ po.connected_port.pk }}">
+                    <a href="#" class="btn btn-success btn-xs consoleport-toggle" title="Mark installed" data="{{ po.connected_port.pk }}">
-                        <i class="fa fa-plug" aria-hidden="true" title="Mark connected"></i>
+                        <i class="fa fa-plug" aria-hidden="true"></i>
                     </a>
                     </a>
                 {% endif %}
                 {% endif %}
-                <a href="{% url 'dcim:poweroutlet_disconnect' pk=po.pk %}" class="btn btn-danger btn-xs">
+                <a href="{% url 'dcim:poweroutlet_disconnect' pk=po.pk %}" title="Delete connection" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-resize-full" aria-hidden="true" title="Delete connection"></i>
+                    <i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
                 </a>
                 </a>
             {% else %}
             {% else %}
-                <a href="{% url 'dcim:poweroutlet_connect' pk=po.pk %}" class="btn btn-success btn-xs">
+                <a href="{% url 'dcim:poweroutlet_connect' pk=po.pk %}" title="Connect" class="btn btn-success btn-xs">
-                    <i class="glyphicon glyphicon-resize-small" aria-hidden="true" title="Connect"></i>
+                    <i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
                 </a>
                 </a>
             {% endif %}
             {% endif %}
-            <a href="{% url 'dcim:poweroutlet_edit' pk=po.pk %}" class="btn btn-info btn-xs">
+            <a href="{% url 'dcim:poweroutlet_edit' pk=po.pk %}" title="Edit outlet" class="btn btn-info btn-xs">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit outlet"></i>
+                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
             </a>
             </a>
         {% endif %}
         {% endif %}
         {% if perms.dcim.delete_poweroutlet %}
         {% if perms.dcim.delete_poweroutlet %}
@@ -50,8 +50,8 @@
                     <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                     <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                 </button>
                 </button>
             {% else %}
             {% else %}
-                <a href="{% url 'dcim:poweroutlet_delete' pk=po.pk %}" class="btn btn-danger btn-xs">
+                <a href="{% url 'dcim:poweroutlet_delete' pk=po.pk %}" title="Delete outlet" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete outlet"></i>
+                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                 </a>
                 </a>
             {% endif %}
             {% endif %}
         {% endif %}
         {% endif %}

+ 12 - 12
netbox/templates/dcim/inc/powerport.html

@@ -18,24 +18,24 @@
         {% if perms.dcim.change_powerport %}
         {% if perms.dcim.change_powerport %}
             {% if pp.power_outlet %}
             {% if pp.power_outlet %}
                 {% if pp.connection_status %}
                 {% if pp.connection_status %}
-                    <a href="#" class="btn btn-warning btn-xs powerport-toggle connected" data="{{ pp.pk }}">
+                    <a href="#" class="btn btn-warning btn-xs powerport-toggle connected" title="Mark planned" data="{{ pp.pk }}">
-                        <i class="glyphicon glyphicon-ban-circle" aria-hidden="true" title="Mark planned"></i>
+                        <i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
                     </a>
                     </a>
                 {% else %}
                 {% else %}
-                    <a href="#" class="btn btn-success btn-xs powerport-toggle" data="{{ pp.pk }}">
+                    <a href="#" class="btn btn-success btn-xs powerport-toggle" title="Mark installed" data="{{ pp.pk }}">
-                        <i class="fa fa-plug" aria-hidden="true" title="Mark connected"></i>
+                        <i class="fa fa-plug" aria-hidden="true"></i>
                     </a>
                     </a>
                 {% endif %}
                 {% endif %}
-            <a href="{% url 'dcim:powerport_disconnect' pk=pp.pk %}" class="btn btn-danger btn-xs">
+            <a href="{% url 'dcim:powerport_disconnect' pk=pp.pk %}" title="Delete connection" class="btn btn-danger btn-xs">
-                <i class="glyphicon glyphicon-resize-full" aria-hidden="true" title="Delete connection"></i>
+                <i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
             </a>
             </a>
             {% else %}
             {% else %}
-                <a href="{% url 'dcim:powerport_connect' pk=pp.pk %}" class="btn btn-success btn-xs">
+                <a href="{% url 'dcim:powerport_connect' pk=pp.pk %}" title="Connect" class="btn btn-success btn-xs">
-                    <i class="glyphicon glyphicon-resize-small" aria-hidden="true" title="Connect"></i>
+                    <i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
                 </a>
                 </a>
             {% endif %}
             {% endif %}
-            <a href="{% url 'dcim:powerport_edit' pk=pp.pk %}" class="btn btn-info btn-xs">
+            <a href="{% url 'dcim:powerport_edit' pk=pp.pk %}" title="Edit port" class="btn btn-info btn-xs">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit port"></i>
+                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
             </a>
             </a>
         {% endif %}
         {% endif %}
         {% if perms.dcim.delete_powerport %}
         {% if perms.dcim.delete_powerport %}
@@ -44,8 +44,8 @@
                     <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                     <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                 </button>
                 </button>
             {% else %}
             {% else %}
-                <a href="{% url 'dcim:powerport_delete' pk=pp.pk %}" class="btn btn-danger btn-xs">
+                <a href="{% url 'dcim:powerport_delete' pk=pp.pk %}" title="Delete port" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete port"></i>
+                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
                 </a>
                 </a>
             {% endif %}
             {% endif %}
         {% endif %}
         {% endif %}

+ 6 - 0
netbox/templates/users/userkey.html

@@ -19,6 +19,12 @@
             {% endif %}
             {% endif %}
         </h4>
         </h4>
         {% include 'inc/created_updated.html' with obj=userkey %}
         {% include 'inc/created_updated.html' with obj=userkey %}
+        {% if not userkey.is_active %}
+            <div class="alert alert-warning" role="alert">
+                <i class="fa fa-warning"></i>
+                Your user key is inactive. Ask an administrator to enable it for you.
+            </div>
+        {% endif %}
         <pre>{{ userkey.public_key }}</pre>
         <pre>{{ userkey.public_key }}</pre>
         <hr />
         <hr />
         {% if userkey.session_key %}
         {% if userkey.session_key %}

+ 6 - 3
netbox/utilities/forms.py

@@ -472,9 +472,6 @@ class ChainedFieldsMixin(forms.BaseForm):
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         super(ChainedFieldsMixin, self).__init__(*args, **kwargs)
         super(ChainedFieldsMixin, self).__init__(*args, **kwargs)
 
 
-        # if self.is_bound:
-        #     assert False, self.data
-
         for field_name, field in self.fields.items():
         for field_name, field in self.fields.items():
 
 
             if isinstance(field, ChainedModelChoiceField):
             if isinstance(field, ChainedModelChoiceField):
@@ -492,6 +489,12 @@ class ChainedFieldsMixin(forms.BaseForm):
 
 
                 if filters_dict:
                 if filters_dict:
                     field.queryset = field.queryset.filter(**filters_dict)
                     field.queryset = field.queryset.filter(**filters_dict)
+                elif not self.is_bound and getattr(self, 'instance', None) and hasattr(self.instance, field_name):
+                    obj = getattr(self.instance, field_name)
+                    if obj is not None:
+                        field.queryset = field.queryset.filter(pk=obj.pk)
+                    else:
+                        field.queryset = field.queryset.none()
                 elif not self.is_bound:
                 elif not self.is_bound:
                     field.queryset = field.queryset.none()
                     field.queryset = field.queryset.none()
 
 

+ 1 - 1
netbox/utilities/views.py

@@ -234,7 +234,7 @@ class ObjectDeleteView(GetReturnURLMixin, View):
     """
     """
     Delete a single object.
     Delete a single object.
 
 
-    model: The model of the object being edited
+    model: The model of the object being deleted
     template_name: The name of the template
     template_name: The name of the template
     default_return_url: Name of the URL to which the user is redirected after deleting the object
     default_return_url: Name of the URL to which the user is redirected after deleting the object
     """
     """