mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-15 11:42:52 -06:00
Merge branch 'develop' into develop-2.3
This commit is contained in:
commit
2fc1519bc6
@ -123,7 +123,7 @@ $ curl -X PATCH -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc
|
||||
Send an authenticated `DELETE` request to the site detail endpoint.
|
||||
|
||||
```
|
||||
$ curl -v X DELETE -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/sites/16/
|
||||
$ curl -v -X DELETE -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/sites/16/
|
||||
* Connected to localhost (127.0.0.1) port 8000 (#0)
|
||||
> DELETE /api/dcim/sites/16/ HTTP/1.1
|
||||
> User-Agent: curl/7.35.0
|
||||
|
@ -143,6 +143,11 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
|
||||
def count_circuits(self):
|
||||
return Circuit.objects.filter(terminations__site=self).count()
|
||||
|
||||
@property
|
||||
def count_vms(self):
|
||||
from virtualization.models import VirtualMachine
|
||||
return VirtualMachine.objects.filter(cluster__site=self).count()
|
||||
|
||||
|
||||
#
|
||||
# Racks
|
||||
@ -1090,16 +1095,11 @@ class ConsolePort(models.Model):
|
||||
class ConsoleServerPortManager(models.Manager):
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Include the trailing numeric portion of each port name to allow for proper ordering.
|
||||
For example:
|
||||
Port 1, Port 2, Port 3 ... Port 9, Port 10, Port 11 ...
|
||||
Instead of:
|
||||
Port 1, Port 10, Port 11 ... Port 19, Port 2, Port 20 ...
|
||||
"""
|
||||
# Pad any trailing digits to effect natural sorting
|
||||
return super(ConsoleServerPortManager, self).get_queryset().extra(select={
|
||||
'name_as_integer': "CAST(substring(dcim_consoleserverport.name FROM '[0-9]+$') AS INTEGER)",
|
||||
}).order_by('device', 'name_as_integer')
|
||||
'name_padded': "CONCAT(REGEXP_REPLACE(dcim_consoleserverport.name, '\d+$', ''), "
|
||||
"LPAD(SUBSTRING(dcim_consoleserverport.name FROM '\d+$'), 8, '0'))",
|
||||
}).order_by('device', 'name_padded')
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
@ -1172,9 +1172,10 @@ class PowerPort(models.Model):
|
||||
class PowerOutletManager(models.Manager):
|
||||
|
||||
def get_queryset(self):
|
||||
# Pad any trailing digits to effect natural sorting
|
||||
return super(PowerOutletManager, self).get_queryset().extra(select={
|
||||
'name_padded': "CONCAT(SUBSTRING(dcim_poweroutlet.name FROM '^[^0-9]+'), "
|
||||
"LPAD(SUBSTRING(dcim_poweroutlet.name FROM '[0-9\/]+$'), 8, '0'))",
|
||||
'name_padded': "CONCAT(REGEXP_REPLACE(dcim_poweroutlet.name, '\d+$', ''), "
|
||||
"LPAD(SUBSTRING(dcim_poweroutlet.name FROM '\d+$'), 8, '0'))",
|
||||
}).order_by('device', 'name_padded')
|
||||
|
||||
|
||||
|
@ -153,11 +153,12 @@ class SiteDetailTable(SiteTable):
|
||||
prefix_count = tables.Column(accessor=Accessor('count_prefixes'), orderable=False, verbose_name='Prefixes')
|
||||
vlan_count = tables.Column(accessor=Accessor('count_vlans'), orderable=False, verbose_name='VLANs')
|
||||
circuit_count = tables.Column(accessor=Accessor('count_circuits'), orderable=False, verbose_name='Circuits')
|
||||
vm_count = tables.Column(accessor=Accessor('count_vms'), orderable=False, verbose_name='VMs')
|
||||
|
||||
class Meta(SiteTable.Meta):
|
||||
fields = (
|
||||
'pk', 'name', 'facility', 'region', 'tenant', 'asn', 'rack_count', 'device_count', 'prefix_count',
|
||||
'vlan_count', 'circuit_count',
|
||||
'vlan_count', 'circuit_count', 'vm_count',
|
||||
)
|
||||
|
||||
|
||||
|
@ -25,6 +25,7 @@ from utilities.views import (
|
||||
BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ComponentDeleteView,
|
||||
ComponentEditView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
)
|
||||
from virtualization.models import VirtualMachine
|
||||
from . import filters, forms, tables
|
||||
from .constants import CONNECTION_STATUS_CONNECTED
|
||||
from .models import (
|
||||
@ -134,6 +135,7 @@ class SiteView(View):
|
||||
'prefix_count': Prefix.objects.filter(site=site).count(),
|
||||
'vlan_count': VLAN.objects.filter(site=site).count(),
|
||||
'circuit_count': Circuit.objects.filter(terminations__site=site).count(),
|
||||
'vm_count': VirtualMachine.objects.filter(cluster__site=site).count(),
|
||||
}
|
||||
rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
|
||||
topology_maps = TopologyMap.objects.filter(site=site)
|
||||
@ -808,15 +810,11 @@ class DeviceView(View):
|
||||
console_ports = natsorted(
|
||||
ConsolePort.objects.filter(device=device).select_related('cs_port__device'), key=attrgetter('name')
|
||||
)
|
||||
cs_ports = natsorted(
|
||||
ConsoleServerPort.objects.filter(device=device).select_related('connected_console'), key=attrgetter('name')
|
||||
)
|
||||
cs_ports = ConsoleServerPort.objects.filter(device=device).select_related('connected_console')
|
||||
power_ports = natsorted(
|
||||
PowerPort.objects.filter(device=device).select_related('power_outlet__device'), key=attrgetter('name')
|
||||
)
|
||||
power_outlets = natsorted(
|
||||
PowerOutlet.objects.filter(device=device).select_related('connected_port'), key=attrgetter('name')
|
||||
)
|
||||
power_outlets = PowerOutlet.objects.filter(device=device).select_related('connected_port')
|
||||
interfaces = Interface.objects.order_naturally(
|
||||
device.device_type.interface_ordering
|
||||
).filter(
|
||||
|
@ -5,10 +5,7 @@ from django.db import models
|
||||
from netaddr import IPNetwork
|
||||
|
||||
from .formfields import IPFormField
|
||||
from .lookups import (
|
||||
EndsWith, IEndsWith, IRegex, IStartsWith, NetContained, NetContainedOrEqual, NetContains, NetContainsOrEquals,
|
||||
NetHost, NetHostContained, NetMaskLength, Regex, StartsWith,
|
||||
)
|
||||
from . import lookups
|
||||
|
||||
|
||||
def prefix_validator(prefix):
|
||||
@ -57,17 +54,18 @@ class IPNetworkField(BaseIPField):
|
||||
return 'cidr'
|
||||
|
||||
|
||||
IPNetworkField.register_lookup(EndsWith)
|
||||
IPNetworkField.register_lookup(IEndsWith)
|
||||
IPNetworkField.register_lookup(StartsWith)
|
||||
IPNetworkField.register_lookup(IStartsWith)
|
||||
IPNetworkField.register_lookup(Regex)
|
||||
IPNetworkField.register_lookup(IRegex)
|
||||
IPNetworkField.register_lookup(NetContained)
|
||||
IPNetworkField.register_lookup(NetContainedOrEqual)
|
||||
IPNetworkField.register_lookup(NetContains)
|
||||
IPNetworkField.register_lookup(NetContainsOrEquals)
|
||||
IPNetworkField.register_lookup(NetMaskLength)
|
||||
IPNetworkField.register_lookup(lookups.IExact)
|
||||
IPNetworkField.register_lookup(lookups.EndsWith)
|
||||
IPNetworkField.register_lookup(lookups.IEndsWith)
|
||||
IPNetworkField.register_lookup(lookups.StartsWith)
|
||||
IPNetworkField.register_lookup(lookups.IStartsWith)
|
||||
IPNetworkField.register_lookup(lookups.Regex)
|
||||
IPNetworkField.register_lookup(lookups.IRegex)
|
||||
IPNetworkField.register_lookup(lookups.NetContained)
|
||||
IPNetworkField.register_lookup(lookups.NetContainedOrEqual)
|
||||
IPNetworkField.register_lookup(lookups.NetContains)
|
||||
IPNetworkField.register_lookup(lookups.NetContainsOrEquals)
|
||||
IPNetworkField.register_lookup(lookups.NetMaskLength)
|
||||
|
||||
|
||||
class IPAddressField(BaseIPField):
|
||||
@ -80,16 +78,17 @@ class IPAddressField(BaseIPField):
|
||||
return 'inet'
|
||||
|
||||
|
||||
IPAddressField.register_lookup(EndsWith)
|
||||
IPAddressField.register_lookup(IEndsWith)
|
||||
IPAddressField.register_lookup(StartsWith)
|
||||
IPAddressField.register_lookup(IStartsWith)
|
||||
IPAddressField.register_lookup(Regex)
|
||||
IPAddressField.register_lookup(IRegex)
|
||||
IPAddressField.register_lookup(NetContained)
|
||||
IPAddressField.register_lookup(NetContainedOrEqual)
|
||||
IPAddressField.register_lookup(NetContains)
|
||||
IPAddressField.register_lookup(NetContainsOrEquals)
|
||||
IPAddressField.register_lookup(NetHost)
|
||||
IPAddressField.register_lookup(NetHostContained)
|
||||
IPAddressField.register_lookup(NetMaskLength)
|
||||
IPAddressField.register_lookup(lookups.IExact)
|
||||
IPAddressField.register_lookup(lookups.EndsWith)
|
||||
IPAddressField.register_lookup(lookups.IEndsWith)
|
||||
IPAddressField.register_lookup(lookups.StartsWith)
|
||||
IPAddressField.register_lookup(lookups.IStartsWith)
|
||||
IPAddressField.register_lookup(lookups.Regex)
|
||||
IPAddressField.register_lookup(lookups.IRegex)
|
||||
IPAddressField.register_lookup(lookups.NetContained)
|
||||
IPAddressField.register_lookup(lookups.NetContainedOrEqual)
|
||||
IPAddressField.register_lookup(lookups.NetContains)
|
||||
IPAddressField.register_lookup(lookups.NetContainsOrEquals)
|
||||
IPAddressField.register_lookup(lookups.NetHost)
|
||||
IPAddressField.register_lookup(lookups.NetHostContained)
|
||||
IPAddressField.register_lookup(lookups.NetMaskLength)
|
||||
|
@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import django_filters
|
||||
from django.db.models import Q
|
||||
from netaddr import IPNetwork
|
||||
import netaddr
|
||||
from netaddr.core import AddrFormatError
|
||||
|
||||
from dcim.models import Site, Device, Interface
|
||||
@ -79,7 +79,7 @@ class AggregateFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
return queryset
|
||||
qs_filter = Q(description__icontains=value)
|
||||
try:
|
||||
prefix = str(IPNetwork(value.strip()).cidr)
|
||||
prefix = str(netaddr.IPNetwork(value.strip()).cidr)
|
||||
qs_filter |= Q(prefix__net_contains_or_equals=prefix)
|
||||
except (AddrFormatError, ValueError):
|
||||
pass
|
||||
@ -107,6 +107,10 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
method='search_within_include',
|
||||
label='Within and including prefix',
|
||||
)
|
||||
contains = django_filters.CharFilter(
|
||||
method='search_contains',
|
||||
label='Prefixes which contain this prefix or IP',
|
||||
)
|
||||
mask_length = django_filters.NumberFilter(
|
||||
method='filter_mask_length',
|
||||
label='Mask length',
|
||||
@ -173,7 +177,7 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
return queryset
|
||||
qs_filter = Q(description__icontains=value)
|
||||
try:
|
||||
prefix = str(IPNetwork(value.strip()).cidr)
|
||||
prefix = str(netaddr.IPNetwork(value.strip()).cidr)
|
||||
qs_filter |= Q(prefix__net_contains_or_equals=prefix)
|
||||
except (AddrFormatError, ValueError):
|
||||
pass
|
||||
@ -184,7 +188,7 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
if not value:
|
||||
return queryset
|
||||
try:
|
||||
query = str(IPNetwork(value).cidr)
|
||||
query = str(netaddr.IPNetwork(value).cidr)
|
||||
return queryset.filter(prefix__net_contained=query)
|
||||
except (AddrFormatError, ValueError):
|
||||
return queryset.none()
|
||||
@ -194,11 +198,25 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
if not value:
|
||||
return queryset
|
||||
try:
|
||||
query = str(IPNetwork(value).cidr)
|
||||
query = str(netaddr.IPNetwork(value).cidr)
|
||||
return queryset.filter(prefix__net_contained_or_equal=query)
|
||||
except (AddrFormatError, ValueError):
|
||||
return queryset.none()
|
||||
|
||||
def search_contains(self, queryset, name, value):
|
||||
value = value.strip()
|
||||
if not value:
|
||||
return queryset
|
||||
try:
|
||||
# Searching by prefix
|
||||
if '/' in value:
|
||||
return queryset.filter(prefix__net_contains_or_equals=str(netaddr.IPNetwork(value).cidr))
|
||||
# Searching by IP address
|
||||
else:
|
||||
return queryset.filter(prefix__net_contains=str(netaddr.IPAddress(value)))
|
||||
except (AddrFormatError, ValueError):
|
||||
return queryset.none()
|
||||
|
||||
def filter_mask_length(self, queryset, name, value):
|
||||
if not value:
|
||||
return queryset
|
||||
@ -291,7 +309,7 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
if not value:
|
||||
return queryset
|
||||
try:
|
||||
query = str(IPNetwork(value.strip()).cidr)
|
||||
query = str(netaddr.IPNetwork(value.strip()).cidr)
|
||||
return queryset.filter(address__net_host_contained=query)
|
||||
except (AddrFormatError, ValueError):
|
||||
return queryset.none()
|
||||
|
@ -13,12 +13,21 @@ class NetFieldDecoratorMixin(object):
|
||||
return lhs_string, lhs_params
|
||||
|
||||
|
||||
class IExact(NetFieldDecoratorMixin, lookups.IExact):
|
||||
|
||||
def get_rhs_op(self, connection, rhs):
|
||||
return '= LOWER(%s)' % rhs
|
||||
|
||||
|
||||
class EndsWith(NetFieldDecoratorMixin, lookups.EndsWith):
|
||||
lookup_name = 'endswith'
|
||||
pass
|
||||
|
||||
|
||||
class IEndsWith(NetFieldDecoratorMixin, lookups.IEndsWith):
|
||||
lookup_name = 'iendswith'
|
||||
pass
|
||||
|
||||
def get_rhs_op(self, connection, rhs):
|
||||
return 'LIKE LOWER(%s)' % rhs
|
||||
|
||||
|
||||
class StartsWith(NetFieldDecoratorMixin, lookups.StartsWith):
|
||||
@ -26,15 +35,18 @@ class StartsWith(NetFieldDecoratorMixin, lookups.StartsWith):
|
||||
|
||||
|
||||
class IStartsWith(NetFieldDecoratorMixin, lookups.IStartsWith):
|
||||
lookup_name = 'istartswith'
|
||||
pass
|
||||
|
||||
def get_rhs_op(self, connection, rhs):
|
||||
return 'LIKE LOWER(%s)' % rhs
|
||||
|
||||
|
||||
class Regex(NetFieldDecoratorMixin, lookups.Regex):
|
||||
lookup_name = 'regex'
|
||||
pass
|
||||
|
||||
|
||||
class IRegex(NetFieldDecoratorMixin, lookups.IRegex):
|
||||
lookup_name = 'iregex'
|
||||
pass
|
||||
|
||||
|
||||
class NetContainsOrEquals(Lookup):
|
||||
|
@ -454,9 +454,6 @@ class PrefixView(View):
|
||||
except Aggregate.DoesNotExist:
|
||||
aggregate = None
|
||||
|
||||
# Count child IP addresses
|
||||
ipaddress_count = prefix.get_child_ips().count()
|
||||
|
||||
# Parent prefixes table
|
||||
parent_prefixes = Prefix.objects.filter(
|
||||
Q(vrf=prefix.vrf) | Q(vrf__isnull=True)
|
||||
@ -507,7 +504,6 @@ class PrefixView(View):
|
||||
return render(request, 'ipam/prefix.html', {
|
||||
'prefix': prefix,
|
||||
'aggregate': aggregate,
|
||||
'ipaddress_count': ipaddress_count,
|
||||
'parent_prefix_table': parent_prefix_table,
|
||||
'child_prefix_table': child_prefix_table,
|
||||
'duplicate_prefix_table': duplicate_prefix_table,
|
||||
|
@ -38,7 +38,7 @@ OBJ_TYPE_CHOICES = (
|
||||
|
||||
class SearchForm(BootstrapMixin, forms.Form):
|
||||
q = forms.CharField(
|
||||
label='Search', widget=forms.TextInput(attrs={'style': 'width: 350px'})
|
||||
label='Search'
|
||||
)
|
||||
obj_type = forms.ChoiceField(
|
||||
choices=OBJ_TYPE_CHOICES, required=False, label='Type'
|
||||
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import render
|
||||
from django.views.generic import View
|
||||
from rest_framework.response import Response
|
||||
@ -58,7 +59,7 @@ SEARCH_TYPES = OrderedDict((
|
||||
'url': 'dcim:rack_list',
|
||||
}),
|
||||
('devicetype', {
|
||||
'queryset': DeviceType.objects.select_related('manufacturer'),
|
||||
'queryset': DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')),
|
||||
'filter': DeviceTypeFilter,
|
||||
'table': DeviceTypeTable,
|
||||
'url': 'dcim:devicetype_list',
|
||||
|
@ -303,6 +303,7 @@ class Secret(CreatedUpdatedModel):
|
||||
|LL|MySecret|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
|
||||
+--+--------+-------------------------------------------+
|
||||
"""
|
||||
s = s.encode('utf8')
|
||||
if len(s) > 65535:
|
||||
raise ValueError("Maximum plaintext size is 65535 bytes.")
|
||||
# Minimum ciphertext size is 64 bytes to conceal the length of short secrets.
|
||||
@ -315,7 +316,7 @@ class Secret(CreatedUpdatedModel):
|
||||
return (
|
||||
chr(len(s) >> 8).encode() +
|
||||
chr(len(s) % 256).encode() +
|
||||
s.encode() +
|
||||
s +
|
||||
os.urandom(pad_length)
|
||||
)
|
||||
|
||||
@ -324,11 +325,11 @@ class Secret(CreatedUpdatedModel):
|
||||
Consume the first two bytes of s as a plaintext length indicator and return only that many bytes as the
|
||||
plaintext.
|
||||
"""
|
||||
if isinstance(s[0], int):
|
||||
plaintext_length = (s[0] << 8) + s[1]
|
||||
elif isinstance(s[0], str):
|
||||
if isinstance(s[0], str):
|
||||
plaintext_length = (ord(s[0]) << 8) + ord(s[1])
|
||||
return s[2:plaintext_length + 2].decode()
|
||||
else:
|
||||
plaintext_length = (s[0] << 8) + s[1]
|
||||
return s[2:plaintext_length + 2].decode('utf8')
|
||||
|
||||
def encrypt(self, secret_key):
|
||||
"""
|
||||
|
@ -166,7 +166,7 @@ def secret_edit(request, pk):
|
||||
# Create and encrypt the new Secret
|
||||
if master_key is not None:
|
||||
secret = form.save(commit=False)
|
||||
secret.plaintext = str(form.cleaned_data['plaintext'])
|
||||
secret.plaintext = form.cleaned_data['plaintext']
|
||||
secret.encrypt(master_key)
|
||||
secret.save()
|
||||
messages.success(request, "Modified secret {}.".format(secret))
|
||||
|
@ -211,6 +211,10 @@
|
||||
<h2><a href="{% url 'circuits:circuit_list' %}?site={{ site.slug }}" class="btn {% if stats.circuit_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.circuit_count }}</a></h2>
|
||||
<p>Circuits</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-center">
|
||||
<h2><a href="{% url 'virtualization:virtualmachine_list' %}?site={{ site.slug }}" class="btn {% if stats.vm_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.vm_count }}</a></h2>
|
||||
<p>Virtual Machines</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
|
@ -23,7 +23,7 @@
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if perms.ipam.add_ipaddress %}
|
||||
<a href="{% url 'ipam:ipaddress_add' %}?address={{ prefix.get_first_available_ip }}{% if prefix.vrf %}&vrf={{ prefix.vrf.pk }}{% endif %}{% if prefix.tenant %}&tenant={{ prefix.tenant.pk }}{% endif %}" class="btn btn-success">
|
||||
<a href="{% url 'ipam:ipaddress_add' %}?address={{ prefix.get_first_available_ip }}&vrf={{ prefix.vrf.pk }}&tenant_group={{ prefix.tenant.group.pk }}&tenant={{ prefix.tenant.pk }}" class="btn btn-success">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add an IP Address
|
||||
</a>
|
||||
@ -45,5 +45,5 @@
|
||||
{% include 'inc/created_updated.html' with obj=prefix %}
|
||||
<ul class="nav nav-tabs" style="margin-bottom: 20px">
|
||||
<li role="presentation"{% if active_tab == 'prefix' %} class="active"{% endif %}><a href="{% url 'ipam:prefix' pk=prefix.pk %}">Prefix</a></li>
|
||||
<li role="presentation"{% if active_tab == 'ip-addresses' %} class="active"{% endif %}><a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">IP Addresses</a></li>
|
||||
<li role="presentation"{% if active_tab == 'ip-addresses' %} class="active"{% endif %}><a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">IP Addresses <span class="badge">{{ prefix.get_child_ips.count }}</span></a></li>
|
||||
</ul>
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load helpers %}
|
||||
|
||||
{% block title %}{{ prefix }}{% endblock %}
|
||||
|
||||
@ -100,16 +101,6 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is a pool</td>
|
||||
<td>
|
||||
{% if prefix.is_pool %}
|
||||
<i class="glyphicon glyphicon-ok text-success" title="Yes"></i>
|
||||
{% else %}
|
||||
<i class="glyphicon glyphicon-remove text-danger" title="No"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>
|
||||
@ -120,9 +111,19 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is a pool</td>
|
||||
<td>
|
||||
{% if prefix.is_pool %}
|
||||
<i class="glyphicon glyphicon-ok text-success" title="Yes"></i>
|
||||
{% else %}
|
||||
<i class="glyphicon glyphicon-remove text-danger" title="No"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Utilization</td>
|
||||
<td><a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">{{ ipaddress_count }} IP addresses</a> ({{ prefix.get_utilization }}%)</td>
|
||||
<td>{% utilization_graph prefix.get_utilization %}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="row" style="padding-bottom: 20px">
|
||||
<div class="col-md-12 text-center">
|
||||
<form action="{% url 'search' %}" method="get" class="form-inline">
|
||||
{{ search_form.q }}
|
||||
<input type="text" name="q" value="{{ request.GET.q }}" placeholder="Search" id="id_q" class="form-control" style="width: 350px" />
|
||||
{{ search_form.obj_type }}
|
||||
<button type="submit" class="btn btn-primary">Search</button>
|
||||
</form>
|
||||
|
@ -55,10 +55,16 @@ class LoginView(View):
|
||||
class LogoutView(View):
|
||||
|
||||
def get(self, request):
|
||||
|
||||
# Log out the user
|
||||
auth_logout(request)
|
||||
messages.info(request, "You have logged out.")
|
||||
|
||||
return HttpResponseRedirect(reverse('home'))
|
||||
# Delete session key cookie (if set) upon logout
|
||||
response = HttpResponseRedirect(reverse('home'))
|
||||
response.delete_cookie('session_key')
|
||||
|
||||
return response
|
||||
|
||||
|
||||
#
|
||||
|
@ -88,6 +88,17 @@ class VirtualMachineFilter(CustomFieldFilterSet):
|
||||
queryset=Cluster.objects.all(),
|
||||
label='Cluster (ID)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='cluster__site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
name='cluster__site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
)
|
||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=DeviceRole.objects.all(),
|
||||
label='Role (ID)',
|
||||
|
@ -344,6 +344,11 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
|
||||
label='Cluster'
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.annotate(filter_count=Count('clusters__virtual_machines')),
|
||||
to_field_name='slug',
|
||||
null_option=(0, 'None')
|
||||
)
|
||||
role = FilterChoiceField(
|
||||
queryset=DeviceRole.objects.filter(vm_role=True).annotate(filter_count=Count('virtual_machines')),
|
||||
to_field_name='slug',
|
||||
|
Loading…
Reference in New Issue
Block a user