diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 042057c7062..d1e721df0e1 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -265,10 +265,11 @@ class DeviceListView(CustomFieldModelAPIView, generics.ListAPIView): """ List devices (filterable) """ - queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'platform', - 'rack__site', 'parent_bay').prefetch_related('primary_ip4__nat_outside', - 'primary_ip6__nat_outside', - 'custom_field_values__field') + queryset = Device.objects.select_related( + 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay' + ).prefetch_related( + 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'custom_field_values__field' + ) serializer_class = serializers.DeviceSerializer filter_class = filters.DeviceFilter renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer, FlatJSONRenderer] @@ -278,8 +279,9 @@ class DeviceDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView): """ Retrieve a single device """ - queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'platform', - 'rack__site', 'parent_bay').prefetch_related('custom_field_values__field') + queryset = Device.objects.select_related( + 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay' + ).prefetch_related('custom_field_values__field') serializer_class = serializers.DeviceSerializer diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 23cedae98c0..dc2b296ee62 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -934,28 +934,29 @@ def __init__(self, *args, **kwargs): if not self.instance.pk: raise RuntimeError("ConsolePortConnectionForm must be initialized with an existing ConsolePort instance.") - self.initial['site'] = self.instance.device.site - self.fields['rack'].queryset = Rack.objects.filter(site=self.instance.device.site) - self.fields['cs_port'].required = True - self.fields['connection_status'].choices = CONNECTION_STATUS_CHOICES - - # Initialize console server choices - if self.is_bound and self.data.get('rack'): - self.fields['console_server'].queryset = Device.objects.filter(rack=self.data['rack'], - device_type__is_console_server=True) - elif self.initial.get('rack'): - self.fields['console_server'].queryset = Device.objects.filter(rack=self.initial['rack'], - device_type__is_console_server=True) + # Initialize rack choices if site is set + if self.initial.get('site'): + self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site']) else: - self.fields['console_server'].queryset = Device.objects.filter(site=self.instance.device.site, - rack__isnull=True, - device_type__is_console_server=True) + self.fields['rack'].choices = [] - # Initialize CS port choices - if self.is_bound: - self.fields['cs_port'].queryset = ConsoleServerPort.objects.filter(device__pk=self.data['console_server']) - elif self.initial.get('console_server', None): - self.fields['cs_port'].queryset = ConsoleServerPort.objects.filter(device__pk=self.initial['console_server']) + # Initialize console_server choices if rack or site is set + if self.initial.get('rack'): + self.fields['console_server'].queryset = Device.objects.filter( + rack=self.initial['rack'], device_type__is_console_server=True + ) + elif self.initial.get('site'): + self.fields['console_server'].queryset = Device.objects.filter( + site=self.initial['site'], rack__isnull=True, device_type__is_console_server=True + ) + else: + self.fields['console_server'].choices = [] + + # Initialize CS port choices if console_server is set + if self.initial.get('console_server'): + self.fields['cs_port'].queryset = ConsoleServerPort.objects.filter( + device=self.initial['console_server'] + ) else: self.fields['cs_port'].choices = [] @@ -1033,27 +1034,27 @@ class Meta: 'connection_status': 'Status', } - def __init__(self, consoleserverport, *args, **kwargs): + def __init__(self, *args, **kwargs): super(ConsoleServerPortConnectionForm, self).__init__(*args, **kwargs) - self.initial['site'] = consoleserverport.device.site - self.fields['rack'].queryset = Rack.objects.filter(site=consoleserverport.device.site) + # Initialize rack choices if site is set + if self.initial.get('site'): + self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site']) + else: + self.fields['rack'].choices = [] - # Initialize device choices - if self.is_bound and self.data.get('rack'): - self.fields['device'].queryset = Device.objects.filter(rack=self.data['rack']) - elif self.initial.get('rack', None): + # Initialize device choices if rack or site is set + if self.initial.get('rack'): self.fields['device'].queryset = Device.objects.filter(rack=self.initial['rack']) + elif self.initial.get('site'): + self.fields['device'].queryset = Device.objects.filter(site=self.initial['site'], rack__isnull=True) else: - self.fields['device'].queryset = Device.objects.filter(site=consoleserverport.device.site, - rack__isnull=True) + self.fields['device'].choices = [] - # Initialize port choices - if self.is_bound: - self.fields['port'].queryset = ConsolePort.objects.filter(device__pk=self.data['device']) - elif self.initial.get('device', None): - self.fields['port'].queryset = ConsolePort.objects.filter(device_pk=self.initial['device']) + # Initialize port choices if device is set + if self.initial.get('device'): + self.fields['port'].queryset = ConsolePort.objects.filter(device=self.initial['device']) else: self.fields['port'].choices = [] @@ -1201,28 +1202,27 @@ def __init__(self, *args, **kwargs): if not self.instance.pk: raise RuntimeError("PowerPortConnectionForm must be initialized with an existing PowerPort instance.") - self.initial['site'] = self.instance.device.site - self.fields['rack'].queryset = Rack.objects.filter(site=self.instance.device.site) - self.fields['power_outlet'].required = True - self.fields['connection_status'].choices = CONNECTION_STATUS_CHOICES - - # Initialize PDU choices - if self.is_bound and self.data.get('rack'): - self.fields['pdu'].queryset = Device.objects.filter(rack=self.data['rack'], - device_type__is_pdu=True) - elif self.initial.get('rack', None): - self.fields['pdu'].queryset = Device.objects.filter(rack=self.initial['rack'], - device_type__is_pdu=True) + # Initialize rack choices if site is set + if self.initial.get('site'): + self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site']) else: - self.fields['pdu'].queryset = Device.objects.filter(site=self.instance.device.site, - rack__isnull=True, - device_type__is_pdu=True) + self.fields['rack'].choices = [] - # Initialize power outlet choices - if self.is_bound: - self.fields['power_outlet'].queryset = PowerOutlet.objects.filter(device__pk=self.data['pdu']) - elif self.initial.get('pdu', None): - self.fields['power_outlet'].queryset = PowerOutlet.objects.filter(device__pk=self.initial['pdu']) + # Initialize pdu choices if rack or site is set + if self.initial.get('rack'): + self.fields['pdu'].queryset = Device.objects.filter( + rack=self.initial['rack'], device_type__is_pdu=True + ) + elif self.initial.get('site'): + self.fields['pdu'].queryset = Device.objects.filter( + site=self.initial['site'], rack__isnull=True, device_type__is_pdu=True + ) + else: + self.fields['pdu'].choices = [] + + # Initialize power outlet choices if pdu is set + if self.initial.get('pdu'): + self.fields['power_outlet'].queryset = PowerOutlet.objects.filter(device=self.initial['pdu']) else: self.fields['power_outlet'].choices = [] @@ -1300,27 +1300,27 @@ class Meta: 'connection_status': 'Status', } - def __init__(self, poweroutlet, *args, **kwargs): + def __init__(self, *args, **kwargs): super(PowerOutletConnectionForm, self).__init__(*args, **kwargs) - self.initial['site'] = poweroutlet.device.site - self.fields['rack'].queryset = Rack.objects.filter(site=poweroutlet.device.site) + # Initialize rack choices if site is set + if self.initial.get('site'): + self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site']) + else: + self.fields['rack'].choices = [] - # Initialize device choices - if self.is_bound and self.data.get('rack'): - self.fields['device'].queryset = Device.objects.filter(rack=self.data['rack']) - elif self.initial.get('rack', None): + # Initialize device choices if rack or site is set + if self.initial.get('rack'): self.fields['device'].queryset = Device.objects.filter(rack=self.initial['rack']) + elif self.initial.get('site'): + self.fields['device'].queryset = Device.objects.filter(site=self.initial['site'], rack__isnull=True) else: - self.fields['device'].queryset = Device.objects.filter(site=poweroutlet.device.site, - rack__isnull=True) + self.fields['device'].choices = [] - # Initialize port choices - if self.is_bound: - self.fields['port'].queryset = PowerPort.objects.filter(device__pk=self.data['device']) - elif self.initial.get('device', None): - self.fields['port'].queryset = PowerPort.objects.filter(device_pk=self.initial['device']) + # Initialize port choices if device is set + if self.initial.get('device'): + self.fields['port'].queryset = PowerPort.objects.filter(device=self.initial['device']) else: self.fields['port'].choices = [] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index e2c24c8c9ee..03d5b25874d 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -293,7 +293,7 @@ def count_racks(self): @property def count_devices(self): - return Device.objects.filter(rack__site=self).count() + return Device.objects.filter(site=self).count() @property def count_circuits(self): diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 9773be9dca2..42c6a39bf9d 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -12,11 +12,11 @@ REGION_LINK = """ {% if record.get_children %} - + {% else %} {% endif %} - {{ record.name }} + {{ record.name }} """ diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 02d9acd1069..73de16e5336 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -171,7 +171,7 @@ def site(request, slug): site = get_object_or_404(Site.objects.select_related('region', 'tenant__group'), slug=slug) stats = { 'rack_count': Rack.objects.filter(site=site).count(), - 'device_count': Device.objects.filter(rack__site=site).count(), + 'device_count': Device.objects.filter(site=site).count(), '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(), @@ -844,7 +844,9 @@ def consoleport_connect(request, pk): else: form = forms.ConsolePortConnectionForm(instance=consoleport, initial={ - 'rack': consoleport.device.rack, + 'site': request.GET.get('site', consoleport.device.site), + 'rack': request.GET.get('rack', None), + 'console_server': request.GET.get('console_server', None), 'connection_status': CONNECTION_STATUS_CONNECTED, }) @@ -927,7 +929,7 @@ def consoleserverport_connect(request, pk): consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk) if request.method == 'POST': - form = forms.ConsoleServerPortConnectionForm(consoleserverport, request.POST) + form = forms.ConsoleServerPortConnectionForm(request.POST) if form.is_valid(): consoleport = form.cleaned_data['port'] consoleport.cs_port = consoleserverport @@ -942,7 +944,12 @@ def consoleserverport_connect(request, pk): return redirect('dcim:device', pk=consoleserverport.device.pk) else: - form = forms.ConsoleServerPortConnectionForm(consoleserverport, initial={'rack': consoleserverport.device.rack}) + form = forms.ConsoleServerPortConnectionForm(initial={ + 'site': request.GET.get('site', consoleserverport.device.site), + 'rack': request.GET.get('rack', None), + 'device': request.GET.get('device', None), + 'connection_status': CONNECTION_STATUS_CONNECTED, + }) return render(request, 'dcim/consoleserverport_connect.html', { 'consoleserverport': consoleserverport, @@ -1030,7 +1037,9 @@ def powerport_connect(request, pk): else: form = forms.PowerPortConnectionForm(instance=powerport, initial={ - 'rack': powerport.device.rack, + 'site': request.GET.get('site', powerport.device.site), + 'rack': request.GET.get('rack', None), + 'pdu': request.GET.get('pdu', None), 'connection_status': CONNECTION_STATUS_CONNECTED, }) @@ -1113,7 +1122,7 @@ def poweroutlet_connect(request, pk): poweroutlet = get_object_or_404(PowerOutlet, pk=pk) if request.method == 'POST': - form = forms.PowerOutletConnectionForm(poweroutlet, request.POST) + form = forms.PowerOutletConnectionForm(request.POST) if form.is_valid(): powerport = form.cleaned_data['port'] powerport.power_outlet = poweroutlet @@ -1128,7 +1137,12 @@ def poweroutlet_connect(request, pk): return redirect('dcim:device', pk=poweroutlet.device.pk) else: - form = forms.PowerOutletConnectionForm(poweroutlet, initial={'rack': poweroutlet.device.rack}) + form = forms.PowerOutletConnectionForm(initial={ + 'site': request.GET.get('site', poweroutlet.device.site), + 'rack': request.GET.get('rack', None), + 'device': request.GET.get('device', None), + 'connection_status': CONNECTION_STATUS_CONNECTED, + }) return render(request, 'dcim/poweroutlet_connect.html', { 'poweroutlet': poweroutlet, diff --git a/netbox/extras/management/commands/run_inventory.py b/netbox/extras/management/commands/run_inventory.py index dbbf8f3c9a7..ebfee92e053 100644 --- a/netbox/extras/management/commands/run_inventory.py +++ b/netbox/extras/management/commands/run_inventory.py @@ -49,7 +49,7 @@ def create_modules(modules, parent=None): self.stdout.write("Running inventory for these sites: {}".format(', '.join(site_names))) else: raise CommandError("One or more sites specified but none found.") - device_list = device_list.filter(rack__site__in=sites) + device_list = device_list.filter(site__in=sites) # --name: Filter devices by name matching a regex if options['name']: diff --git a/netbox/ipam/fields.py b/netbox/ipam/fields.py index da07f68d9b3..c44385b6d56 100644 --- a/netbox/ipam/fields.py +++ b/netbox/ipam/fields.py @@ -6,7 +6,7 @@ from .formfields import IPFormField from .lookups import ( EndsWith, IEndsWith, IRegex, IStartsWith, NetContained, NetContainedOrEqual, NetContains, NetContainsOrEquals, - NetHost, NetMaskLength, Regex, StartsWith, + NetHost, NetHostContained, NetMaskLength, Regex, StartsWith, ) @@ -66,7 +66,6 @@ def db_type(self, connection): IPNetworkField.register_lookup(NetContainedOrEqual) IPNetworkField.register_lookup(NetContains) IPNetworkField.register_lookup(NetContainsOrEquals) -IPNetworkField.register_lookup(NetHost) IPNetworkField.register_lookup(NetMaskLength) @@ -91,4 +90,5 @@ def db_type(self, connection): IPAddressField.register_lookup(NetContains) IPAddressField.register_lookup(NetContainsOrEquals) IPAddressField.register_lookup(NetHost) +IPAddressField.register_lookup(NetHostContained) IPAddressField.register_lookup(NetMaskLength) diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 42809b95412..10a18d1b764 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -254,7 +254,7 @@ def search_by_parent(self, queryset, name, value): return queryset try: query = str(IPNetwork(value.strip()).cidr) - return queryset.filter(address__net_contained_or_equal=query) + return queryset.filter(address__net_host_contained=query) except AddrFormatError: return queryset.none() diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 681a3c3a0c2..72b73208c5b 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -333,9 +333,11 @@ def __init__(self, *args, **kwargs): self.initial['nat_site'] = self.instance.nat_inside.interface.device.site.pk self.initial['nat_device'] = self.instance.nat_inside.interface.device.pk self.fields['nat_device'].queryset = Device.objects.filter( - rack__site=nat_inside.interface.device.site) + site=nat_inside.interface.device.site + ) self.fields['nat_inside'].queryset = IPAddress.objects.filter( - interface__device=nat_inside.interface.device) + interface__device=nat_inside.interface.device + ) else: self.fields['nat_inside'].queryset = IPAddress.objects.filter(pk=nat_inside.pk) @@ -343,9 +345,9 @@ def __init__(self, *args, **kwargs): # Initialize nat_device choices if nat_site is set if self.is_bound and self.data.get('nat_site'): - self.fields['nat_device'].queryset = Device.objects.filter(rack__site__pk=self.data['nat_site']) + self.fields['nat_device'].queryset = Device.objects.filter(site__pk=self.data['nat_site']) elif self.initial.get('nat_site'): - self.fields['nat_device'].queryset = Device.objects.filter(rack__site=self.initial['nat_site']) + self.fields['nat_device'].queryset = Device.objects.filter(site=self.initial['nat_site']) else: self.fields['nat_device'].choices = [] diff --git a/netbox/ipam/lookups.py b/netbox/ipam/lookups.py index 8346169cead..05c69dfb254 100644 --- a/netbox/ipam/lookups.py +++ b/netbox/ipam/lookups.py @@ -89,6 +89,20 @@ def as_sql(self, qn, connection): return 'HOST(%s) = %s' % (lhs, rhs), params +class NetHostContained(Lookup): + """ + Check for the host portion of an IP address without regard to its mask. This allows us to find e.g. 192.0.2.1/24 + when specifying a parent prefix of 192.0.2.0/26. + """ + lookup_name = 'net_host_contained' + + def as_sql(self, qn, connection): + lhs, lhs_params = self.process_lhs(qn, connection) + rhs, rhs_params = self.process_rhs(qn, connection) + params = lhs_params + rhs_params + return 'CAST(HOST(%s) AS INET) << %s' % (lhs, rhs), params + + class NetMaskLength(Transform): lookup_name = 'net_mask_length' function = 'MASKLEN' diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index a04e4b9ce68..51807d0fafa 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -267,7 +267,7 @@ def annotate_depth(self, limit=None): p.depth = len(stack) - 1 if limit is None: return queryset - return filter(lambda p: p.depth <= limit, queryset) + return list(filter(lambda p: p.depth <= limit, queryset)) @python_2_unicode_compatible diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 71e261dce3b..bcbb80e8be4 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -403,7 +403,7 @@ def prefix(request, pk): aggregate = None # Count child IP addresses - ipaddress_count = IPAddress.objects.filter(vrf=prefix.vrf, address__net_contained_or_equal=str(prefix.prefix))\ + ipaddress_count = IPAddress.objects.filter(vrf=prefix.vrf, address__net_host_contained=str(prefix.prefix))\ .count() # Parent prefixes table @@ -420,13 +420,7 @@ def prefix(request, pk): duplicate_prefix_table.exclude = ('vrf',) # Child prefixes table - if prefix.vrf: - # If the prefix is in a VRF, show child prefixes only within that VRF. - child_prefixes = Prefix.objects.filter(vrf=prefix.vrf) - else: - # If the prefix is in the global table, show child prefixes from all VRFs. - child_prefixes = Prefix.objects.all() - child_prefixes = child_prefixes.filter(prefix__net_contained=str(prefix.prefix))\ + child_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix))\ .select_related('site', 'role').annotate_depth(limit=0) if child_prefixes: child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes) @@ -499,7 +493,7 @@ def prefix_ipaddresses(request, pk): prefix = get_object_or_404(Prefix.objects.all(), pk=pk) # Find all IPAddresses belonging to this Prefix - ipaddresses = IPAddress.objects.filter(vrf=prefix.vrf, address__net_contained_or_equal=str(prefix.prefix))\ + ipaddresses = IPAddress.objects.filter(vrf=prefix.vrf, address__net_host_contained=str(prefix.prefix))\ .select_related('vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for') ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 466fbd56330..51a5fed7f2b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -12,7 +12,7 @@ "the documentation.") -VERSION = '1.9.0-r1' +VERSION = '1.9.1' # Import local configuration for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']: diff --git a/netbox/templates/_base.html b/netbox/templates/_base.html index 90bb3ad62c9..d1090a5fbbc 100644 --- a/netbox/templates/_base.html +++ b/netbox/templates/_base.html @@ -245,7 +245,7 @@ {% if request.user.is_staff %}
  • Admin
  • {% endif %} -
  • Profile
  • +
  • {{ request.user }}
  • Log out
  • {% else %}
  • Log in