mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-24 18:58:15 +01:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64a34ced72 | ||
|
|
d0dc505220 | ||
|
|
2f32e11f53 | ||
|
|
280f55a875 | ||
|
|
6f37e97c67 | ||
|
|
e54c74d972 | ||
|
|
af9fa85cc1 | ||
|
|
dc77400ab1 | ||
|
|
5f66893038 | ||
|
|
e05d379101 | ||
|
|
41ea433e7c | ||
|
|
bfd7881b7b | ||
|
|
b253c8cc95 | ||
|
|
0fc9ed852e | ||
|
|
175c1f2720 | ||
|
|
a355783377 | ||
|
|
dafdbc9ddb | ||
|
|
14f5204548 | ||
|
|
5233463f0b | ||
|
|
1d4a416100 | ||
|
|
25ee796d5b | ||
|
|
e08107063a | ||
|
|
cd5a86bfcf | ||
|
|
97b67d0f93 | ||
|
|
3f82be7192 | ||
|
|
adfcb5f7b6 | ||
|
|
5aba1d9aec | ||
|
|
afdf5750b5 | ||
|
|
ea869d4ffc | ||
|
|
9d89eed873 | ||
|
|
c00eea7991 |
@@ -119,7 +119,7 @@ Each line of the **device patterns** field represents a hierarchical layer withi
|
||||
```
|
||||
core-switch-[abcd]
|
||||
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.
|
||||
|
||||
@@ -252,6 +252,11 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
|
||||
super(CircuitTerminationForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# Mark connected interfaces as disabled
|
||||
self.fields['interface'].choices = [
|
||||
(iface.id, {'label': iface.name, 'disabled': iface.is_connected}) for iface in self.fields['interface'].queryset
|
||||
]
|
||||
self.fields['interface'].choices = []
|
||||
for iface in self.fields['interface'].queryset:
|
||||
self.fields['interface'].choices.append(
|
||||
(iface.id, {
|
||||
'label': iface.name,
|
||||
'disabled': iface.is_connected and iface.pk != self.initial.get('interface'),
|
||||
})
|
||||
)
|
||||
|
||||
@@ -402,7 +402,9 @@ class DeviceTypeBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||
u_height = forms.IntegerField(min_value=1, required=False)
|
||||
is_full_depth = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is full depth')
|
||||
interface_ordering = forms.ChoiceField(choices=add_blank_choice(IFACE_ORDERING_CHOICES), required=False)
|
||||
is_console_server = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is full depth')
|
||||
is_console_server = forms.NullBooleanField(
|
||||
required=False, widget=BulkEditNullBooleanSelect, label='Is a console server'
|
||||
)
|
||||
is_pdu = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is a PDU')
|
||||
is_network_device = forms.NullBooleanField(
|
||||
required=False, widget=BulkEditNullBooleanSelect, label='Is a network device'
|
||||
|
||||
@@ -49,6 +49,10 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
|
||||
|
||||
# Validate selected choice
|
||||
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()]
|
||||
if value not in valid_choices:
|
||||
raise ValidationError("Invalid choice for field {}: {}".format(field_name, value))
|
||||
|
||||
@@ -387,7 +387,7 @@ def image_upload(instance, filename):
|
||||
path = 'image-attachments/'
|
||||
|
||||
# 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']:
|
||||
filename = '.'.join([instance.name, extension])
|
||||
elif instance.name:
|
||||
|
||||
@@ -25,7 +25,7 @@ class ImageAttachmentEditView(PermissionRequiredMixin, ObjectEditView):
|
||||
|
||||
|
||||
class ImageAttachmentDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
permission_required = 'dcim.delete_imageattachment'
|
||||
permission_required = 'extras.delete_imageattachment'
|
||||
model = ImageAttachment
|
||||
|
||||
def get_return_url(self, request, imageattachment):
|
||||
|
||||
@@ -264,7 +264,7 @@ class PrefixCSVForm(forms.ModelForm):
|
||||
required=False
|
||||
)
|
||||
status = CSVChoiceField(
|
||||
choices=IPADDRESS_STATUS_CHOICES,
|
||||
choices=PREFIX_STATUS_CHOICES,
|
||||
help_text='Operational status'
|
||||
)
|
||||
role = forms.ModelChoiceField(
|
||||
@@ -641,16 +641,23 @@ class IPAddressCSVForm(forms.ModelForm):
|
||||
|
||||
# Set interface
|
||||
if self.cleaned_data['device'] and self.cleaned_data['interface_name']:
|
||||
self.instance.interface = Interface.objects.get(device=self.cleaned_data['device'],
|
||||
name=self.cleaned_data['interface_name'])
|
||||
self.instance.interface = Interface.objects.get(
|
||||
device=self.cleaned_data['device'],
|
||||
name=self.cleaned_data['interface_name']
|
||||
)
|
||||
|
||||
ipaddress = super(IPAddressCSVForm, self).save(*args, **kwargs)
|
||||
|
||||
# Set as primary for device
|
||||
if self.cleaned_data['is_primary']:
|
||||
device = self.cleaned_data['device']
|
||||
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:
|
||||
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):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from netaddr import IPNetwork, cidr_merge
|
||||
from netaddr import IPNetwork, IPSet
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
@@ -206,13 +206,9 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel):
|
||||
"""
|
||||
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))
|
||||
# Remove overlapping prefixes from list of children
|
||||
networks = cidr_merge([c.prefix for c in child_prefixes])
|
||||
children_size = float(0)
|
||||
for p in networks:
|
||||
children_size += p.size
|
||||
return int(children_size / self.prefix.size * 100)
|
||||
queryset = Prefix.objects.filter(prefix__net_contained_or_equal=str(self.prefix))
|
||||
child_prefixes = IPSet([p.prefix for p in queryset])
|
||||
return int(float(child_prefixes.size) / self.prefix.size * 100)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
@@ -370,13 +366,21 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
||||
|
||||
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 = 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)
|
||||
if self.status == PREFIX_STATUS_CONTAINER:
|
||||
queryset = Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
|
||||
child_prefixes = IPSet([p.prefix for p in queryset])
|
||||
return int(float(child_prefixes.size) / self.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
|
||||
def new_subnet(self):
|
||||
|
||||
@@ -241,7 +241,7 @@ class PrefixTable(BaseTable):
|
||||
prefix = tables.TemplateColumn(PREFIX_LINK, attrs={'th': {'style': 'padding-left: 17px'}})
|
||||
status = tables.TemplateColumn(STATUS_LABEL)
|
||||
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)
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
||||
vlan = tables.LinkColumn('ipam:vlan', args=[Accessor('vlan.pk')], verbose_name='VLAN')
|
||||
|
||||
@@ -665,19 +665,6 @@ class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
table = tables.IPAddressTable
|
||||
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):
|
||||
permission_required = 'ipam.change_ipaddress'
|
||||
|
||||
@@ -13,7 +13,7 @@ except ImportError:
|
||||
)
|
||||
|
||||
|
||||
VERSION = '2.0.7'
|
||||
VERSION = '2.0.10'
|
||||
|
||||
# Import required configuration parameters
|
||||
ALLOWED_HOSTS = DATABASE = SECRET_KEY = None
|
||||
|
||||
@@ -42,13 +42,15 @@ class UserKeyAdmin(admin.ModelAdmin):
|
||||
if 'activate' in request.POST:
|
||||
form = ActivateUserKeyForm(request.POST)
|
||||
if form.is_valid():
|
||||
try:
|
||||
master_key = my_userkey.get_master_key(form.cleaned_data['secret_key'])
|
||||
master_key = my_userkey.get_master_key(form.cleaned_data['secret_key'])
|
||||
if master_key is not None:
|
||||
for uk in form.cleaned_data['_selected_action']:
|
||||
uk.activate(master_key)
|
||||
return redirect('admin:secrets_userkey_changelist')
|
||||
except ValueError:
|
||||
messages.error(request, "Invalid private key provided. Unable to retrieve master key.")
|
||||
else:
|
||||
messages.error(
|
||||
request, "Invalid private key provided. Unable to retrieve master key.", extra_tags='error'
|
||||
)
|
||||
else:
|
||||
form = ActivateUserKeyForm(initial={'_selected_action': request.POST.getlist(admin.ACTION_CHECKBOX_NAME)})
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class SecretRoleForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = SecretRole
|
||||
fields = ['name', 'slug']
|
||||
fields = ['name', 'slug', 'users', 'groups']
|
||||
|
||||
|
||||
#
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
None
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_service %}
|
||||
{% if perms.ipam.add_service %}
|
||||
<div class="panel-footer text-right">
|
||||
<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
|
||||
@@ -591,7 +591,7 @@ function toggleConnection(elem, api_url) {
|
||||
success: function() {
|
||||
elem.parents('tr').removeClass('success').addClass('info');
|
||||
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')
|
||||
}
|
||||
});
|
||||
@@ -610,7 +610,7 @@ function toggleConnection(elem, api_url) {
|
||||
success: function() {
|
||||
elem.parents('tr').removeClass('info').addClass('success');
|
||||
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')
|
||||
}
|
||||
});
|
||||
|
||||
@@ -24,24 +24,24 @@
|
||||
{% if perms.dcim.change_consoleport %}
|
||||
{% if cp.cs_port %}
|
||||
{% if cp.connection_status %}
|
||||
<a href="#" class="btn btn-warning btn-xs consoleport-toggle connected" data="{{ cp.pk }}">
|
||||
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true" title="Mark planned"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="#" class="btn btn-success btn-xs consoleport-toggle" data="{{ cp.pk }}">
|
||||
<i class="fa fa-plug" aria-hidden="true" title="Mark connected"></i>
|
||||
<a href="#" class="btn btn-success btn-xs consoleport-toggle" title="Mark installed" data="{{ cp.pk }}">
|
||||
<i class="fa fa-plug" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:consoleport_disconnect' pk=cp.pk %}" class="btn btn-danger btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-full" aria-hidden="true" title="Delete connection"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:consoleport_connect' pk=cp.pk %}" class="btn btn-success btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-small" aria-hidden="true" title="Connect"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:consoleport_edit' pk=cp.pk %}" class="btn btn-info btn-xs">
|
||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit port"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_consoleport %}
|
||||
@@ -50,8 +50,8 @@
|
||||
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:consoleport_delete' pk=cp.pk %}" class="btn btn-danger btn-xs">
|
||||
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete port"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -24,24 +24,24 @@
|
||||
{% if perms.dcim.change_consoleserverport %}
|
||||
{% if csp.connected_console %}
|
||||
{% if csp.connected_console.connection_status %}
|
||||
<a href="#" class="btn btn-warning btn-xs consoleport-toggle connected" data="{{ csp.connected_console.pk }}">
|
||||
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true" title="Mark planned"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="#" class="btn btn-success btn-xs consoleport-toggle" data="{{ csp.connected_console.pk }}">
|
||||
<i class="fa fa-plug" aria-hidden="true" title="Mark connected"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:consoleserverport_disconnect' pk=csp.pk %}" class="btn btn-danger btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-full" aria-hidden="true" title="Delete connection"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:consoleserverport_connect' pk=csp.pk %}" class="btn btn-success btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-small" aria-hidden="true" title="Connect"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:consoleserverport_edit' pk=csp.pk %}" class="btn btn-info btn-xs">
|
||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit port"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_consoleserverport %}
|
||||
@@ -50,8 +50,8 @@
|
||||
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:consoleserverport_delete' pk=csp.pk %}" class="btn btn-danger btn-xs">
|
||||
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete port"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<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 == '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>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<tr class="interface{% if iface.connection and not iface.connection.connection_status %} info{% endif %}">
|
||||
<tr class="interface{% if 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 %}
|
||||
<td class="pk">
|
||||
<input name="pk" type="checkbox" value="{{ iface.pk }}" />
|
||||
@@ -72,7 +72,7 @@
|
||||
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% 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>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -24,24 +24,24 @@
|
||||
{% if perms.dcim.change_poweroutlet %}
|
||||
{% if po.connected_port %}
|
||||
{% if po.connected_port.connection_status %}
|
||||
<a href="#" class="btn btn-warning btn-xs powerport-toggle connected" data="{{ po.connected_port.pk }}">
|
||||
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true" title="Mark planned"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="#" class="btn btn-success btn-xs consoleport-toggle" data="{{ po.connected_port.pk }}">
|
||||
<i class="fa fa-plug" aria-hidden="true" title="Mark connected"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:poweroutlet_disconnect' pk=po.pk %}" class="btn btn-danger btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-full" aria-hidden="true" title="Delete connection"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:poweroutlet_connect' pk=po.pk %}" class="btn btn-success btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-small" aria-hidden="true" title="Connect"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:poweroutlet_edit' pk=po.pk %}" class="btn btn-info btn-xs">
|
||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit outlet"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_poweroutlet %}
|
||||
@@ -50,8 +50,8 @@
|
||||
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:poweroutlet_delete' pk=po.pk %}" class="btn btn-danger btn-xs">
|
||||
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete outlet"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -24,24 +24,24 @@
|
||||
{% if perms.dcim.change_powerport %}
|
||||
{% if pp.power_outlet %}
|
||||
{% if pp.connection_status %}
|
||||
<a href="#" class="btn btn-warning btn-xs powerport-toggle connected" data="{{ pp.pk }}">
|
||||
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true" title="Mark planned"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="#" class="btn btn-success btn-xs powerport-toggle" data="{{ pp.pk }}">
|
||||
<i class="fa fa-plug" aria-hidden="true" title="Mark connected"></i>
|
||||
<a href="#" class="btn btn-success btn-xs powerport-toggle" title="Mark installed" data="{{ pp.pk }}">
|
||||
<i class="fa fa-plug" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:powerport_disconnect' pk=pp.pk %}" class="btn btn-danger btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-full" aria-hidden="true" title="Delete connection"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:powerport_connect' pk=pp.pk %}" class="btn btn-success btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-small" aria-hidden="true" title="Connect"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:powerport_edit' pk=pp.pk %}" class="btn btn-info btn-xs">
|
||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit port"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_powerport %}
|
||||
@@ -50,8 +50,8 @@
|
||||
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:powerport_delete' pk=pp.pk %}" class="btn btn-danger btn-xs">
|
||||
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete port"></i>
|
||||
<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"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
{% endif %}
|
||||
</h4>
|
||||
{% 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>
|
||||
<hr />
|
||||
{% if userkey.session_key %}
|
||||
|
||||
@@ -472,9 +472,6 @@ class ChainedFieldsMixin(forms.BaseForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ChainedFieldsMixin, self).__init__(*args, **kwargs)
|
||||
|
||||
# if self.is_bound:
|
||||
# assert False, self.data
|
||||
|
||||
for field_name, field in self.fields.items():
|
||||
|
||||
if isinstance(field, ChainedModelChoiceField):
|
||||
@@ -492,6 +489,12 @@ class ChainedFieldsMixin(forms.BaseForm):
|
||||
|
||||
if 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:
|
||||
field.queryset = field.queryset.none()
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ class ObjectListView(View):
|
||||
# Construct the table based on the user's permissions
|
||||
table = self.table(self.queryset)
|
||||
if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']):
|
||||
table.base_columns['pk'].visible = True
|
||||
table.columns.show('pk')
|
||||
|
||||
# Apply the request context
|
||||
paginate = {
|
||||
@@ -234,7 +234,7 @@ class ObjectDeleteView(GetReturnURLMixin, View):
|
||||
"""
|
||||
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
|
||||
default_return_url: Name of the URL to which the user is redirected after deleting the object
|
||||
"""
|
||||
|
||||
@@ -6,7 +6,7 @@ django-debug-toolbar>=1.7
|
||||
django-filter>=1.0.2
|
||||
django-mptt==0.8.7
|
||||
django-rest-swagger>=2.1.0
|
||||
django-tables2>=1.6.0
|
||||
django-tables2>=1.7.0
|
||||
djangorestframework>=3.6.2
|
||||
graphviz>=0.6
|
||||
Markdown>=2.6.7
|
||||
|
||||
Reference in New Issue
Block a user