Skip to content

Commit

Permalink
Merge pull request #1757 from digitalocean/develop
Browse files Browse the repository at this point in the history
Release v2.2.7
  • Loading branch information
jeremystretch authored Dec 7, 2017
2 parents 50a451e + 5666079 commit e98f0c3
Show file tree
Hide file tree
Showing 20 changed files with 138 additions and 84 deletions.
2 changes: 1 addition & 1 deletion docs/api/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 12 additions & 11 deletions netbox/dcim/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ def count_devices(self):
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
Expand Down Expand Up @@ -1089,16 +1094,11 @@ def to_csv(self):
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
Expand Down Expand Up @@ -1171,9 +1171,10 @@ def to_csv(self):
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')


Expand Down
3 changes: 2 additions & 1 deletion netbox/dcim/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
)


Expand Down
10 changes: 4 additions & 6 deletions netbox/dcim/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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 (
Expand Down Expand Up @@ -134,6 +135,7 @@ def get(self, request, slug):
'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)
Expand Down Expand Up @@ -808,15 +810,11 @@ def get(self, request, pk):
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(
Expand Down
55 changes: 27 additions & 28 deletions netbox/ipam/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
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):
Expand Down Expand Up @@ -57,17 +54,18 @@ def db_type(self, connection):
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):
Expand All @@ -80,16 +78,17 @@ def db_type(self, connection):
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)
30 changes: 24 additions & 6 deletions netbox/ipam/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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
Expand Down Expand Up @@ -79,7 +79,7 @@ def search(self, queryset, name, value):
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
Expand Down Expand Up @@ -112,6 +112,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',
Expand Down Expand Up @@ -178,7 +182,7 @@ def search(self, queryset, name, value):
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
Expand All @@ -189,7 +193,7 @@ def search_within(self, queryset, name, value):
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()
Expand All @@ -199,11 +203,25 @@ def search_within_include(self, queryset, name, value):
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
Expand Down Expand Up @@ -296,7 +314,7 @@ def search_by_parent(self, queryset, name, value):
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()
Expand Down
22 changes: 17 additions & 5 deletions netbox/ipam/lookups.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,40 @@ def process_lhs(self, qn, connection, lhs=None):
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):
lookup_name = '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):
Expand Down
4 changes: 0 additions & 4 deletions netbox/ipam/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,9 +454,6 @@ def get(self, request, pk):
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)
Expand Down Expand Up @@ -507,7 +504,6 @@ def get(self, request, pk):
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,
Expand Down
2 changes: 1 addition & 1 deletion netbox/netbox/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

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'
Expand Down
2 changes: 1 addition & 1 deletion netbox/netbox/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
)


VERSION = '2.2.6'
VERSION = '2.2.7'

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Expand Down
3 changes: 2 additions & 1 deletion netbox/netbox/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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
Expand Down Expand Up @@ -58,7 +59,7 @@
'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',
Expand Down
11 changes: 6 additions & 5 deletions netbox/secrets/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ def _pad(self, s):
|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.
Expand All @@ -315,7 +316,7 @@ def _pad(self, s):
return (
chr(len(s) >> 8).encode() +
chr(len(s) % 256).encode() +
s.encode() +
s +
os.urandom(pad_length)
)

Expand All @@ -324,11 +325,11 @@ def _unpad(self, s):
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):
"""
Expand Down
2 changes: 1 addition & 1 deletion netbox/secrets/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Loading

0 comments on commit e98f0c3

Please sign in to comment.