Merge branch 'develop' into develop-2.1

Conflicts:
	netbox/ipam/models.py
	netbox/netbox/settings.py
	netbox/templates/dcim/inc/interface.html
This commit is contained in:
Jeremy Stretch 2017-07-06 13:27:13 -04:00
commit 5b43a108bc
18 changed files with 104 additions and 93 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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')
} }
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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