From 7ed023a8fbb81a71a1334d91df1364c57f5f16af Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 15 Jan 2020 12:23:34 +0000 Subject: [PATCH 01/67] Fixes #3840: Only show valid interface VLAN choices --- docs/release-notes/version-2.6.md | 3 ++- netbox/dcim/forms.py | 42 ++++++++++++++++++++++++++----- netbox/project-static/js/forms.js | 17 +++++++------ netbox/utilities/forms.py | 9 +++++-- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 792e8990a..b31e769a3 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -3,6 +3,7 @@ ## Enhancements * [#3525](https://github.com/netbox-community/netbox/issues/3525) - Enable IP address filtering with multiple address terms +* [#3840](https://github.com/netbox-community/netbox/issues/3840) - Only show the valid list of interface VLAN choices ## Bug Fixes @@ -42,7 +43,7 @@ * [#3872](https://github.com/netbox-community/netbox/issues/3872) - Paginate related IPs on the IP address view * [#3876](https://github.com/netbox-community/netbox/issues/3876) - Fix minimum/maximum value rendering for site ASN field * [#3882](https://github.com/netbox-community/netbox/issues/3882) - Fix filtering of devices by rack group -* [#3898](https://github.com/netbox-community/netbox/issues/3898) - Fix references to deleted cables without a label +* [#3898](https://github.com/netbox-community/netbox/issues/3898) - Fix references to deleted cables without a label * [#3905](https://github.com/netbox-community/netbox/issues/3905) - Fix divide-by-zero on power feeds with low power values --- diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index cd356cc09..4b5dd33cf 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2238,7 +2238,10 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) tagged_vlans = forms.ModelMultipleChoiceField( @@ -2247,7 +2250,10 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) @@ -2289,6 +2295,10 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): device__in=[self.instance.device, self.instance.device.get_vc_master()], type=IFACE_TYPE_LAG ) + # Add current site to VLANs query params + self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', device.site.pk) + self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', device.site.pk) + class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): name_pattern = ExpandableNameField( @@ -2340,7 +2350,10 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) tagged_vlans = forms.ModelMultipleChoiceField( @@ -2349,7 +2362,10 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) @@ -2366,6 +2382,10 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): self.fields['lag'].queryset = Interface.objects.filter( device__in=[self.parent, self.parent.get_vc_master()], type=IFACE_TYPE_LAG ) + + # Add current site to VLANs query params + self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', self.parent.site.pk) + self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', self.parent.site.pk) else: self.fields['lag'].queryset = Interface.objects.none() @@ -2420,7 +2440,10 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) tagged_vlans = forms.ModelMultipleChoiceField( @@ -2429,7 +2452,10 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) @@ -2448,6 +2474,10 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo device__in=[device, device.get_vc_master()], type=IFACE_TYPE_LAG ) + + # Add current site to VLANs query params + self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', device.site.pk) + self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', device.site.pk) else: self.fields['lag'].choices = [] diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js index b7dbb1cfa..1fbd211a7 100644 --- a/netbox/project-static/js/forms.js +++ b/netbox/project-static/js/forms.js @@ -187,15 +187,18 @@ $(document).ready(function() { $.each(element.attributes, function(index, attr){ if (attr.name.includes("data-additional-query-param-")){ var param_name = attr.name.split("data-additional-query-param-")[1]; - if (param_name in parameters) { - if (Array.isArray(parameters[param_name])) { - parameters[param_name].push(attr.value) + + $.each($.parseJSON(attr.value), function(index, value) { + if (param_name in parameters) { + if (Array.isArray(parameters[param_name])) { + parameters[param_name].push(value) + } else { + parameters[param_name] = [parameters[param_name], value] + } } else { - parameters[param_name] = [parameters[param_name], attr.value] + parameters[param_name] = value; } - } else { - parameters[param_name] = attr.value; - } + }) } }); diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 39422c265..ba16774bb 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -346,12 +346,17 @@ class APISelect(SelectWithDisabled): def add_additional_query_param(self, name, value): """ - Add details for an additional query param in the form of a data-* attribute. + Add details for an additional query param in the form of a data-* JSON-encoded list attribute. :param name: The name of the query param :param value: The value of the query param """ - self.attrs['data-additional-query-param-{}'.format(name)] = value + key = 'data-additional-query-param-{}'.format(name) + + values = json.loads(self.attrs.get(key, '[]')) + values.append(value) + + self.attrs[key] = json.dumps(values) def add_conditional_query_param(self, condition, value): """ From f7e28de1d35817053c507b01b17d70f0dce7005a Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 15 Jan 2020 12:38:09 +0000 Subject: [PATCH 02/67] Semicolons for completeness --- netbox/project-static/js/forms.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js index 1fbd211a7..60bc32849 100644 --- a/netbox/project-static/js/forms.js +++ b/netbox/project-static/js/forms.js @@ -191,14 +191,14 @@ $(document).ready(function() { $.each($.parseJSON(attr.value), function(index, value) { if (param_name in parameters) { if (Array.isArray(parameters[param_name])) { - parameters[param_name].push(value) + parameters[param_name].push(value); } else { - parameters[param_name] = [parameters[param_name], value] + parameters[param_name] = [parameters[param_name], value]; } } else { parameters[param_name] = value; } - }) + }); } }); From a5425a740b842ec15c4dbe44ee452025e5159bf2 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 16 Jan 2020 15:10:25 +0000 Subject: [PATCH 03/67] Added #3840 changelog --- docs/release-notes/version-2.7.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index ac9d81e2c..5bf9fc314 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -237,6 +237,7 @@ PATCH) to maintain backward compatibility. This behavior will be discontinued be * [#3706](https://github.com/netbox-community/netbox/issues/3706) - Increase `available_power` maximum value on PowerFeed * [#3731](https://github.com/netbox-community/netbox/issues/3731) - Change Graph.type to a ContentType foreign key field * [#3801](https://github.com/netbox-community/netbox/issues/3801) - Use YAML for export of device types +* [#3840](https://github.com/netbox-community/netbox/issues/3840) - Only show the valid list of interface VLAN choices ## Bug Fixes From 9ae8c7e0a67a488cbd738ad1c80b1e156902287d Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 16 Jan 2020 21:51:37 +0000 Subject: [PATCH 04/67] Moved into v2.7.1 --- docs/release-notes/version-2.7.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 5bf9fc314..45223a056 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -1,3 +1,11 @@ +# v2.7.1 (FUTURE) + +## Enhancements + +* [#3840](https://github.com/netbox-community/netbox/issues/3840) - Only show the valid list of interface VLAN choices + +--- + # v2.7.0 (FUTURE) **Note:** NetBox v2.7 is the last major release that will support Python 3.5. Beginning with NetBox v2.8, Python 3.6 or @@ -237,7 +245,6 @@ PATCH) to maintain backward compatibility. This behavior will be discontinued be * [#3706](https://github.com/netbox-community/netbox/issues/3706) - Increase `available_power` maximum value on PowerFeed * [#3731](https://github.com/netbox-community/netbox/issues/3731) - Change Graph.type to a ContentType foreign key field * [#3801](https://github.com/netbox-community/netbox/issues/3801) - Use YAML for export of device types -* [#3840](https://github.com/netbox-community/netbox/issues/3840) - Only show the valid list of interface VLAN choices ## Bug Fixes From 375925488621a9ddd5be7a82acc7705deb20a81a Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 30 Jan 2020 18:10:39 +0000 Subject: [PATCH 05/67] Corrected linter warning --- netbox/dcim/models/device_components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 68bab8037..e37569f79 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -676,7 +676,7 @@ class Interface(CableTermination, ComponentModel): self.untagged_vlan = None # Only "tagged" interfaces may have tagged VLANs assigned. ("tagged all" implies all VLANs are assigned.) - if self.pk and self.mode is not InterfaceModeChoices.MODE_TAGGED: + if self.pk and self.mode != InterfaceModeChoices.MODE_TAGGED: self.tagged_vlans.clear() return super().save(*args, **kwargs) From 779fde55bb8cd834c9eb8378d8af3634f9927a30 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 30 Jan 2020 18:26:30 +0000 Subject: [PATCH 06/67] Virtualization interfaces VLAN filtering --- netbox/virtualization/forms.py | 143 +++++++++------------------------ 1 file changed, 38 insertions(+), 105 deletions(-) diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index ae516fcb3..018e14e85 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -648,7 +648,10 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm): widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) tagged_vlans = forms.ModelMultipleChoiceField( @@ -657,7 +660,10 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm): widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) tags = TagField( @@ -685,51 +691,12 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site - vlan_choices = [] - global_vlans = VLAN.objects.filter(site=None, group=None) - vlan_choices.append( - ('Global', [(vlan.pk, vlan) for vlan in global_vlans]) - ) - for group in VLANGroup.objects.filter(site=None): - global_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append( - (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans]) - ) - + # Add current site to VLANs query params site = getattr(self.instance.parent, 'site', None) if site is not None: - - # Add non-grouped site VLANs - site_vlans = VLAN.objects.filter(site=site, group=None) - vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans])) - - # Add grouped site VLANs - for group in VLANGroup.objects.filter(site=site): - site_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append(( - '{} / {}'.format(group.site.name, group.name), - [(vlan.pk, vlan) for vlan in site_group_vlans] - )) - - self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices - self.fields['tagged_vlans'].choices = vlan_choices - - def clean(self): - super().clean() - - # Validate VLAN assignments - tagged_vlans = self.cleaned_data['tagged_vlans'] - - # Untagged interfaces cannot be assigned tagged VLANs - if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans: - raise forms.ValidationError({ - 'mode': "An access interface cannot have tagged VLANs assigned." - }) - - # Remove all tagged VLAN assignments from "tagged all" interfaces - elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL: - self.cleaned_data['tagged_vlans'] = [] + # Add current site to VLANs query params + self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', site.pk) + self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk) class InterfaceCreateForm(ComponentForm): @@ -769,7 +736,10 @@ class InterfaceCreateForm(ComponentForm): widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) tagged_vlans = forms.ModelMultipleChoiceField( @@ -778,7 +748,10 @@ class InterfaceCreateForm(ComponentForm): widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) tags = TagField( @@ -793,35 +766,12 @@ class InterfaceCreateForm(ComponentForm): super().__init__(*args, **kwargs) - # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site - vlan_choices = [] - global_vlans = VLAN.objects.filter(site=None, group=None) - vlan_choices.append( - ('Global', [(vlan.pk, vlan) for vlan in global_vlans]) - ) - for group in VLANGroup.objects.filter(site=None): - global_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append( - (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans]) - ) - + # Add current site to VLANs query params site = getattr(self.parent.cluster, 'site', None) if site is not None: - - # Add non-grouped site VLANs - site_vlans = VLAN.objects.filter(site=site, group=None) - vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans])) - - # Add grouped site VLANs - for group in VLANGroup.objects.filter(site=site): - site_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append(( - '{} / {}'.format(group.site.name, group.name), - [(vlan.pk, vlan) for vlan in site_group_vlans] - )) - - self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices - self.fields['tagged_vlans'].choices = vlan_choices + # Add current site to VLANs query params + self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', site.pk) + self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk) class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): @@ -854,7 +804,10 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) tagged_vlans = forms.ModelMultipleChoiceField( @@ -863,7 +816,10 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', - full=True + full=True, + additional_query_params={ + 'site_id': 'null', + }, ) ) @@ -875,35 +831,12 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site - vlan_choices = [] - global_vlans = VLAN.objects.filter(site=None, group=None) - vlan_choices.append( - ('Global', [(vlan.pk, vlan) for vlan in global_vlans]) - ) - for group in VLANGroup.objects.filter(site=None): - global_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append( - (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans]) - ) - if self.parent_obj.cluster is not None: - site = getattr(self.parent_obj.cluster, 'site', None) - if site is not None: - - # Add non-grouped site VLANs - site_vlans = VLAN.objects.filter(site=site, group=None) - vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans])) - - # Add grouped site VLANs - for group in VLANGroup.objects.filter(site=site): - site_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append(( - '{} / {}'.format(group.site.name, group.name), - [(vlan.pk, vlan) for vlan in site_group_vlans] - )) - - self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices - self.fields['tagged_vlans'].choices = vlan_choices + # Add current site to VLANs query params + site = getattr(self.parent_obj.cluster, 'site', None) + if site is not None: + # Add current site to VLANs query params + self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', site.pk) + self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk) # From 1d9b66cc4c287375df18950219a3d31e1bdf2254 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 30 Jan 2020 18:29:08 +0000 Subject: [PATCH 07/67] Removed changelog to avoid merge conflicts --- docs/release-notes/version-2.7.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 8caf5c17b..5c489a96c 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -4,7 +4,6 @@ * [#3310](https://github.com/netbox-community/netbox/issues/3310) - Pre-select site/rack for B side when creating a new cable * [#3509](https://github.com/netbox-community/netbox/issues/3509) - Add IP address variables for custom scripts -* [#3840](https://github.com/netbox-community/netbox/issues/3840) - Only show the valid list of interface VLAN choices * [#4005](https://github.com/netbox-community/netbox/issues/4005) - Include timezone context in webhook timestamps ## Bug Fixes From dccd0dcc9cf73b98b3e2a1034a11789d3956b944 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sat, 8 Feb 2020 16:18:58 +0000 Subject: [PATCH 08/67] Cleaned duplicate code --- netbox/dcim/forms.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index ed42e9914..52047151b 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2871,18 +2871,16 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # Limit LAG choices to interfaces belonging to this device (or VC master) if self.is_bound: device = Device.objects.get(pk=self.data['device']) - self.fields['lag'].queryset = Interface.objects.filter( - device__in=[device, device.get_vc_master()], - type=InterfaceTypeChoices.TYPE_LAG - ) else: - self.fields['lag'].queryset = Interface.objects.filter( - device__in=[self.instance.device, self.instance.device.get_vc_master()], - type=InterfaceTypeChoices.TYPE_LAG - ) + device = self.instance.device + + # Limit LAG choices to interfaces belonging to this device (or VC master) + self.fields['lag'].queryset = Interface.objects.filter( + device__in=[device, device.get_vc_master()], + type=InterfaceTypeChoices.TYPE_LAG + ) # Add current site to VLANs query params self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', device.site.pk) From 7acda4fa3761472e972684124d3692c3cc6306ed Mon Sep 17 00:00:00 2001 From: Matt Olenik Date: Fri, 13 Dec 2019 11:52:59 -0800 Subject: [PATCH 09/67] Fix race condition in available-prefix/ip APIs Implement advisory lock to prevent duplicate records being inserted when making simultaneous calls. Fixes #2519 --- base_requirements.txt | 4 ++++ netbox/ipam/api/views.py | 10 ++++++++++ netbox/utilities/constants.py | 11 +++++++++++ requirements.txt | 1 + 4 files changed, 26 insertions(+) diff --git a/base_requirements.txt b/base_requirements.txt index 8b42c835d..ed42b6c08 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -22,6 +22,10 @@ django-filter # https://github.com/django-mptt/django-mptt django-mptt +# Context managers for PostgreSQL advisory locks +# https://github.com/Xof/django-pglocks +django-pglocks + # Prometheus metrics library for Django # https://github.com/korfuri/django-prometheus django-prometheus diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 08e21367c..262ca7908 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -1,6 +1,7 @@ from django.conf import settings from django.db.models import Count from django.shortcuts import get_object_or_404 +from django_pglocks import advisory_lock from rest_framework import status from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied @@ -10,6 +11,7 @@ from extras.api.views import CustomFieldModelViewSet from ipam import filters from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from utilities.api import FieldChoicesViewSet, ModelViewSet +from utilities.constants import ADVISORY_LOCK_KEYS from utilities.utils import get_subquery from . import serializers @@ -86,9 +88,13 @@ class PrefixViewSet(CustomFieldModelViewSet): filterset_class = filters.PrefixFilterSet @action(detail=True, url_path='available-prefixes', methods=['get', 'post']) + @advisory_lock(ADVISORY_LOCK_KEYS['available-prefixes']) def available_prefixes(self, request, pk=None): """ A convenience method for returning available child prefixes within a parent. + + The advisory lock decorator uses a PostgreSQL advisory lock to prevent this API from being + invoked in parallel, which results in a race condition where multiple insertions can occur. """ prefix = get_object_or_404(Prefix, pk=pk) available_prefixes = prefix.get_available_prefixes() @@ -180,11 +186,15 @@ class PrefixViewSet(CustomFieldModelViewSet): return Response(serializer.data) @action(detail=True, url_path='available-ips', methods=['get', 'post']) + @advisory_lock(ADVISORY_LOCK_KEYS['available-ips']) def available_ips(self, request, pk=None): """ A convenience method for returning available IP addresses within a prefix. By default, the number of IPs returned will be equivalent to PAGINATE_COUNT. An arbitrary limit (up to MAX_PAGE_SIZE, if set) may be passed, however results will not be paginated. + + The advisory lock decorator uses a PostgreSQL advisory lock to prevent this API from being + invoked in parallel, which results in a race condition where multiple insertions can occur. """ prefix = get_object_or_404(Prefix, pk=pk) diff --git a/netbox/utilities/constants.py b/netbox/utilities/constants.py index ad6e8fd90..2cec2b532 100644 --- a/netbox/utilities/constants.py +++ b/netbox/utilities/constants.py @@ -27,3 +27,14 @@ COLOR_CHOICES = ( ('111111', 'Black'), ('ffffff', 'White'), ) + +# Keys for PostgreSQL advisory locks. These are arbitrary bigints used by +# the advisory_lock contextmanager. When a lock is acquired, +# one of these keys will be used to identify said lock. +# +# When adding a new key, pick something arbitrary and unique so +# that it is easily searchable in query logs. +ADVISORY_LOCK_KEYS = { + 'available-prefixes': 100100, + 'available-ips': 100200, +} diff --git a/requirements.txt b/requirements.txt index 9c5f624f9..b0b1b971d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ django-cors-headers==3.2.1 django-debug-toolbar==2.1 django-filter==2.2.0 django-mptt==0.9.1 +django-pglocks==1.0.4 django-prometheus==1.1.0 django-rq==2.2.0 django-tables2==2.2.1 From 843b52145c8010d6b671a811090e21391241689c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 13:13:27 -0500 Subject: [PATCH 10/67] Introduce a common template for object list views --- netbox/templates/utilities/obj_list.html | 28 ++++++++++++++++++++++++ netbox/utilities/templatetags/helpers.py | 19 ++++++++++++++-- netbox/utilities/views.py | 10 +++++---- 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 netbox/templates/utilities/obj_list.html diff --git a/netbox/templates/utilities/obj_list.html b/netbox/templates/utilities/obj_list.html new file mode 100644 index 000000000..c06afaed8 --- /dev/null +++ b/netbox/templates/utilities/obj_list.html @@ -0,0 +1,28 @@ +{% extends '_base.html' %} +{% load buttons %} +{% load helpers %} + +{% block content %} +
+ {% if permissions.add %} + {% add_button content_type.model_class|url_name:"add" %} + {% import_button content_type.model_class|url_name:"import" %} + {% endif %} + {% export_button content_type %} +
+

{% block title %}{{ content_type.model_class|model_name_plural|bettertitle }}{% endblock %}

+
+ {% if filter_form %} +
+ {% include 'utilities/obj_table.html' with bulk_edit_url=content_type.model_class|url_name:"bulk_edit" bulk_delete_url=content_type.model_class|url_name:"bulk_delete" %} +
+
+ {% include 'inc/search_panel.html' %} +
+ {% else %} +
+ {% include 'utilities/obj_table.html' with bulk_edit_url=content_type.model_class|url_name:"bulk_edit" bulk_delete_url=content_type.model_class|url_name:"bulk_delete" %} +
+ {% endif %} +
+{% endblock %} diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index ae679c91a..dfeddd31a 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -1,9 +1,10 @@ import datetime import json import re -import yaml +import yaml from django import template +from django.urls import NoReverseMatch, reverse from django.utils.html import strip_tags from django.utils.safestring import mark_safe from markdown import markdown @@ -11,7 +12,6 @@ from markdown import markdown from utilities.choices import unpack_grouped_choices from utilities.utils import foreground_color - register = template.Library() @@ -101,6 +101,21 @@ def model_name_plural(obj): return obj._meta.verbose_name_plural +@register.filter() +def url_name(model, action): + """ + Return the URL name for the given model and action, or None if invalid. + """ + url_name = '{}:{}_{}'.format(model._meta.app_label, model._meta.model_name, action) + try: + # Validate and return the URL name. We don't return the actual URL yet because many of the templates + # are written to pass a name to {% url %}. + reverse(url_name) + return url_name + except NoReverseMatch: + return None + + @register.filter() def contains(value, arg): """ diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 7c38aceee..7618031ed 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -71,7 +71,7 @@ class ObjectListView(View): filterset = None filterset_form = None table = None - template_name = None + template_name = 'utilities/obj_list.html' def queryset_to_yaml(self): """ @@ -156,9 +156,11 @@ class ObjectListView(View): # Provide a hook to tweak the queryset based on the request immediately prior to rendering the object list self.queryset = self.alter_queryset(request) - # Compile user model permissions for access from within the template - perm_base_name = '{}.{{}}_{}'.format(model._meta.app_label, model._meta.model_name) - permissions = {p: request.user.has_perm(perm_base_name.format(p)) for p in ['add', 'change', 'delete']} + # Compile a dictionary indicating which permissions are available to the current user for this model + permissions = {} + for action in ('add', 'change', 'delete', 'view'): + perm_name = '{}.{}_{}'.format(model._meta.app_label, action, model._meta.model_name) + permissions[action] = request.user.has_perm(perm_name) # Construct the table based on the user's permissions table = self.table(self.queryset) From 5624a878eb4f8205f71a375acc777aac55242498 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 13:31:04 -0500 Subject: [PATCH 11/67] Migrate circuits views to use common object list template --- netbox/circuits/views.py | 3 --- netbox/templates/circuits/circuit_list.html | 21 ------------------- .../templates/circuits/circuittype_list.html | 18 ---------------- netbox/templates/circuits/provider_list.html | 21 ------------------- 4 files changed, 63 deletions(-) delete mode 100644 netbox/templates/circuits/circuit_list.html delete mode 100644 netbox/templates/circuits/circuittype_list.html delete mode 100644 netbox/templates/circuits/provider_list.html diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index 15cf901c1..ba873f23f 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -29,7 +29,6 @@ class ProviderListView(PermissionRequiredMixin, ObjectListView): filterset = filters.ProviderFilterSet filterset_form = forms.ProviderFilterForm table = tables.ProviderDetailTable - template_name = 'circuits/provider_list.html' class ProviderView(PermissionRequiredMixin, View): @@ -107,7 +106,6 @@ class CircuitTypeListView(PermissionRequiredMixin, ObjectListView): permission_required = 'circuits.view_circuittype' queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')) table = tables.CircuitTypeTable - template_name = 'circuits/circuittype_list.html' class CircuitTypeCreateView(PermissionRequiredMixin, ObjectEditView): @@ -151,7 +149,6 @@ class CircuitListView(PermissionRequiredMixin, ObjectListView): filterset = filters.CircuitFilterSet filterset_form = forms.CircuitFilterForm table = tables.CircuitTable - template_name = 'circuits/circuit_list.html' class CircuitView(PermissionRequiredMixin, View): diff --git a/netbox/templates/circuits/circuit_list.html b/netbox/templates/circuits/circuit_list.html deleted file mode 100644 index 169aab072..000000000 --- a/netbox/templates/circuits/circuit_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.circuits.add_circuit %} - {% add_button 'circuits:circuit_add' %} - {% import_button 'circuits:circuit_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Circuits{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='circuits:circuit_bulk_edit' bulk_delete_url='circuits:circuit_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/circuits/circuittype_list.html b/netbox/templates/circuits/circuittype_list.html deleted file mode 100644 index 654d4ab09..000000000 --- a/netbox/templates/circuits/circuittype_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.circuits.add_circuittype %} - {% add_button 'circuits:circuittype_add' %} - {% import_button 'circuits:circuittype_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Circuit Types{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='circuits:circuittype_bulk_delete' %} -
-
-{% endblock %} diff --git a/netbox/templates/circuits/provider_list.html b/netbox/templates/circuits/provider_list.html deleted file mode 100644 index 4126f75ec..000000000 --- a/netbox/templates/circuits/provider_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.circuits.add_provider %} - {% add_button 'circuits:provider_add' %} - {% import_button 'circuits:provider_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Providers{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='circuits:provider_bulk_edit' bulk_delete_url='circuits:provider_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} From b586f81e57672226cf082eae15fdf98eb331459a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 14:07:15 -0500 Subject: [PATCH 12/67] Migrate DCIM views to use common object list template --- netbox/dcim/views.py | 36 +++++++------------ netbox/templates/dcim/cable_list.html | 20 ----------- netbox/templates/dcim/consoleport_list.html | 17 --------- .../dcim/consoleserverport_list.html | 17 --------- netbox/templates/dcim/devicebay_list.html | 17 --------- netbox/templates/dcim/devicerole_list.html | 18 ---------- netbox/templates/dcim/devicetype_list.html | 21 ----------- netbox/templates/dcim/frontport_list.html | 17 --------- netbox/templates/dcim/interface_list.html | 17 --------- netbox/templates/dcim/inventoryitem_list.html | 21 ----------- netbox/templates/dcim/manufacturer_list.html | 18 ---------- netbox/templates/dcim/platform_list.html | 18 ---------- netbox/templates/dcim/powerfeed_list.html | 21 ----------- netbox/templates/dcim/poweroutlet_list.html | 17 --------- netbox/templates/dcim/powerpanel_list.html | 21 ----------- netbox/templates/dcim/powerport_list.html | 17 --------- netbox/templates/dcim/rack_list.html | 21 ----------- netbox/templates/dcim/rackgroup_list.html | 21 ----------- .../templates/dcim/rackreservation_list.html | 14 -------- netbox/templates/dcim/rackrole_list.html | 18 ---------- netbox/templates/dcim/rearport_list.html | 17 --------- netbox/templates/dcim/region_list.html | 21 ----------- netbox/templates/dcim/site_list.html | 21 ----------- .../templates/dcim/virtualchassis_list.html | 18 ---------- netbox/templates/utilities/obj_list.html | 8 +++-- netbox/utilities/views.py | 2 ++ 26 files changed, 21 insertions(+), 453 deletions(-) delete mode 100644 netbox/templates/dcim/cable_list.html delete mode 100644 netbox/templates/dcim/consoleport_list.html delete mode 100644 netbox/templates/dcim/consoleserverport_list.html delete mode 100644 netbox/templates/dcim/devicebay_list.html delete mode 100644 netbox/templates/dcim/devicerole_list.html delete mode 100644 netbox/templates/dcim/devicetype_list.html delete mode 100644 netbox/templates/dcim/frontport_list.html delete mode 100644 netbox/templates/dcim/interface_list.html delete mode 100644 netbox/templates/dcim/inventoryitem_list.html delete mode 100644 netbox/templates/dcim/manufacturer_list.html delete mode 100644 netbox/templates/dcim/platform_list.html delete mode 100644 netbox/templates/dcim/powerfeed_list.html delete mode 100644 netbox/templates/dcim/poweroutlet_list.html delete mode 100644 netbox/templates/dcim/powerpanel_list.html delete mode 100644 netbox/templates/dcim/powerport_list.html delete mode 100644 netbox/templates/dcim/rack_list.html delete mode 100644 netbox/templates/dcim/rackgroup_list.html delete mode 100644 netbox/templates/dcim/rackreservation_list.html delete mode 100644 netbox/templates/dcim/rackrole_list.html delete mode 100644 netbox/templates/dcim/rearport_list.html delete mode 100644 netbox/templates/dcim/region_list.html delete mode 100644 netbox/templates/dcim/site_list.html delete mode 100644 netbox/templates/dcim/virtualchassis_list.html diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index ae59890a3..89f051b2a 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -152,7 +152,6 @@ class RegionListView(PermissionRequiredMixin, ObjectListView): filterset = filters.RegionFilterSet filterset_form = forms.RegionFilterForm table = tables.RegionTable - template_name = 'dcim/region_list.html' class RegionCreateView(PermissionRequiredMixin, ObjectEditView): @@ -191,7 +190,6 @@ class SiteListView(PermissionRequiredMixin, ObjectListView): filterset = filters.SiteFilterSet filterset_form = forms.SiteFilterForm table = tables.SiteTable - template_name = 'dcim/site_list.html' class SiteView(PermissionRequiredMixin, View): @@ -271,7 +269,6 @@ class RackGroupListView(PermissionRequiredMixin, ObjectListView): filterset = filters.RackGroupFilterSet filterset_form = forms.RackGroupFilterForm table = tables.RackGroupTable - template_name = 'dcim/rackgroup_list.html' class RackGroupCreateView(PermissionRequiredMixin, ObjectEditView): @@ -308,7 +305,6 @@ class RackRoleListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_rackrole' queryset = RackRole.objects.annotate(rack_count=Count('racks')) table = tables.RackRoleTable - template_name = 'dcim/rackrole_list.html' class RackRoleCreateView(PermissionRequiredMixin, ObjectEditView): @@ -350,7 +346,6 @@ class RackListView(PermissionRequiredMixin, ObjectListView): filterset = filters.RackFilterSet filterset_form = forms.RackFilterForm table = tables.RackDetailTable - template_name = 'dcim/rack_list.html' class RackElevationListView(PermissionRequiredMixin, View): @@ -474,7 +469,7 @@ class RackReservationListView(PermissionRequiredMixin, ObjectListView): filterset = filters.RackReservationFilterSet filterset_form = forms.RackReservationFilterForm table = tables.RackReservationTable - template_name = 'dcim/rackreservation_list.html' + action_buttons = () class RackReservationCreateView(PermissionRequiredMixin, ObjectEditView): @@ -533,7 +528,6 @@ class ManufacturerListView(PermissionRequiredMixin, ObjectListView): platform_count=Count('platforms', distinct=True), ) table = tables.ManufacturerTable - template_name = 'dcim/manufacturer_list.html' class ManufacturerCreateView(PermissionRequiredMixin, ObjectEditView): @@ -571,7 +565,6 @@ class DeviceTypeListView(PermissionRequiredMixin, ObjectListView): filterset = filters.DeviceTypeFilterSet filterset_form = forms.DeviceTypeFilterForm table = tables.DeviceTypeTable - template_name = 'dcim/devicetype_list.html' class DeviceTypeView(PermissionRequiredMixin, View): @@ -995,7 +988,6 @@ class DeviceRoleListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_devicerole' queryset = DeviceRole.objects.all() table = tables.DeviceRoleTable - template_name = 'dcim/devicerole_list.html' class DeviceRoleCreateView(PermissionRequiredMixin, ObjectEditView): @@ -1031,7 +1023,6 @@ class PlatformListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_platform' queryset = Platform.objects.all() table = tables.PlatformTable - template_name = 'dcim/platform_list.html' class PlatformCreateView(PermissionRequiredMixin, ObjectEditView): @@ -1071,6 +1062,7 @@ class DeviceListView(PermissionRequiredMixin, ObjectListView): filterset = filters.DeviceFilterSet filterset_form = forms.DeviceFilterForm table = tables.DeviceDetailTable + # TODO: Remove custom template template_name = 'dcim/device_list.html' @@ -1292,7 +1284,7 @@ class ConsolePortListView(PermissionRequiredMixin, ObjectListView): filterset = filters.ConsolePortFilterSet filterset_form = forms.ConsolePortFilterForm table = tables.ConsolePortDetailTable - template_name = 'dcim/consoleport_list.html' + action_buttons = ('import', 'export') class ConsolePortCreateView(PermissionRequiredMixin, ComponentCreateView): @@ -1345,7 +1337,7 @@ class ConsoleServerPortListView(PermissionRequiredMixin, ObjectListView): filterset = filters.ConsoleServerPortFilterSet filterset_form = forms.ConsoleServerPortFilterForm table = tables.ConsoleServerPortDetailTable - template_name = 'dcim/consoleserverport_list.html' + action_buttons = ('import', 'export') class ConsoleServerPortCreateView(PermissionRequiredMixin, ComponentCreateView): @@ -1410,7 +1402,7 @@ class PowerPortListView(PermissionRequiredMixin, ObjectListView): filterset = filters.PowerPortFilterSet filterset_form = forms.PowerPortFilterForm table = tables.PowerPortDetailTable - template_name = 'dcim/powerport_list.html' + action_buttons = ('import', 'export') class PowerPortCreateView(PermissionRequiredMixin, ComponentCreateView): @@ -1463,7 +1455,7 @@ class PowerOutletListView(PermissionRequiredMixin, ObjectListView): filterset = filters.PowerOutletFilterSet filterset_form = forms.PowerOutletFilterForm table = tables.PowerOutletDetailTable - template_name = 'dcim/poweroutlet_list.html' + action_buttons = ('import', 'export') class PowerOutletCreateView(PermissionRequiredMixin, ComponentCreateView): @@ -1528,7 +1520,7 @@ class InterfaceListView(PermissionRequiredMixin, ObjectListView): filterset = filters.InterfaceFilterSet filterset_form = forms.InterfaceFilterForm table = tables.InterfaceDetailTable - template_name = 'dcim/interface_list.html' + action_buttons = ('import', 'export') class InterfaceView(PermissionRequiredMixin, View): @@ -1630,7 +1622,7 @@ class FrontPortListView(PermissionRequiredMixin, ObjectListView): filterset = filters.FrontPortFilterSet filterset_form = forms.FrontPortFilterForm table = tables.FrontPortDetailTable - template_name = 'dcim/frontport_list.html' + action_buttons = ('import', 'export') class FrontPortCreateView(PermissionRequiredMixin, ComponentCreateView): @@ -1695,7 +1687,7 @@ class RearPortListView(PermissionRequiredMixin, ObjectListView): filterset = filters.RearPortFilterSet filterset_form = forms.RearPortFilterForm table = tables.RearPortDetailTable - template_name = 'dcim/rearport_list.html' + action_buttons = ('import', 'export') class RearPortCreateView(PermissionRequiredMixin, ComponentCreateView): @@ -1762,7 +1754,7 @@ class DeviceBayListView(PermissionRequiredMixin, ObjectListView): filterset = filters.DeviceBayFilterSet filterset_form = forms.DeviceBayFilterForm table = tables.DeviceBayDetailTable - template_name = 'dcim/devicebay_list.html' + action_buttons = ('import', 'export') class DeviceBayCreateView(PermissionRequiredMixin, ComponentCreateView): @@ -1961,7 +1953,7 @@ class CableListView(PermissionRequiredMixin, ObjectListView): filterset = filters.CableFilterSet filterset_form = forms.CableFilterForm table = tables.CableTable - template_name = 'dcim/cable_list.html' + action_buttons = ('import', 'export') class CableView(PermissionRequiredMixin, View): @@ -2233,7 +2225,7 @@ class InventoryItemListView(PermissionRequiredMixin, ObjectListView): filterset = filters.InventoryItemFilterSet filterset_form = forms.InventoryItemFilterForm table = tables.InventoryItemTable - template_name = 'dcim/inventoryitem_list.html' + action_buttons = ('import', 'export') class InventoryItemEditView(PermissionRequiredMixin, ObjectEditView): @@ -2289,7 +2281,7 @@ class VirtualChassisListView(PermissionRequiredMixin, ObjectListView): table = tables.VirtualChassisTable filterset = filters.VirtualChassisFilterSet filterset_form = forms.VirtualChassisFilterForm - template_name = 'dcim/virtualchassis_list.html' + action_buttons = ('export') class VirtualChassisCreateView(PermissionRequiredMixin, View): @@ -2533,7 +2525,6 @@ class PowerPanelListView(PermissionRequiredMixin, ObjectListView): filterset = filters.PowerPanelFilterSet filterset_form = forms.PowerPanelFilterForm table = tables.PowerPanelTable - template_name = 'dcim/powerpanel_list.html' class PowerPanelView(PermissionRequiredMixin, View): @@ -2602,7 +2593,6 @@ class PowerFeedListView(PermissionRequiredMixin, ObjectListView): filterset = filters.PowerFeedFilterSet filterset_form = forms.PowerFeedFilterForm table = tables.PowerFeedTable - template_name = 'dcim/powerfeed_list.html' class PowerFeedView(PermissionRequiredMixin, View): diff --git a/netbox/templates/dcim/cable_list.html b/netbox/templates/dcim/cable_list.html deleted file mode 100644 index 0dd8095a5..000000000 --- a/netbox/templates/dcim/cable_list.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_cable %} - {% import_button 'dcim:cable_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Cables{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:cable_bulk_edit' bulk_delete_url='dcim:cable_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/consoleport_list.html b/netbox/templates/dcim/consoleport_list.html deleted file mode 100644 index 0ed840820..000000000 --- a/netbox/templates/dcim/consoleport_list.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% export_button content_type %} -
-

{% block title %}Console Ports{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:consoleport_bulk_edit' bulk_delete_url='dcim:consoleport_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/consoleserverport_list.html b/netbox/templates/dcim/consoleserverport_list.html deleted file mode 100644 index 47a8676e3..000000000 --- a/netbox/templates/dcim/consoleserverport_list.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% export_button content_type %} -
-

{% block title %}Console Server Ports{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:consoleserverport_bulk_edit' bulk_delete_url='dcim:consoleserverport_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/devicebay_list.html b/netbox/templates/dcim/devicebay_list.html deleted file mode 100644 index 74f64858a..000000000 --- a/netbox/templates/dcim/devicebay_list.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% export_button content_type %} -
-

{% block title %}Device Bays{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='dcim:devicebay_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/devicerole_list.html b/netbox/templates/dcim/devicerole_list.html deleted file mode 100644 index 9f560dab4..000000000 --- a/netbox/templates/dcim/devicerole_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_devicerole %} - {% add_button 'dcim:devicerole_add' %} - {% import_button 'dcim:devicerole_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Device Roles{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='dcim:devicerole_bulk_delete' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/devicetype_list.html b/netbox/templates/dcim/devicetype_list.html deleted file mode 100644 index 75f587f5d..000000000 --- a/netbox/templates/dcim/devicetype_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_devicetype %} - {% add_button 'dcim:devicetype_add' %} - {% import_button 'dcim:devicetype_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Device Types{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:devicetype_bulk_edit' bulk_delete_url='dcim:devicetype_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/frontport_list.html b/netbox/templates/dcim/frontport_list.html deleted file mode 100644 index a3334b876..000000000 --- a/netbox/templates/dcim/frontport_list.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% export_button content_type %} -
-

{% block title %}Front Ports{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:frontport_bulk_edit' bulk_delete_url='dcim:frontport_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/interface_list.html b/netbox/templates/dcim/interface_list.html deleted file mode 100644 index 9dd8f7858..000000000 --- a/netbox/templates/dcim/interface_list.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% export_button content_type %} -
-

{% block title %}Interfaces{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:interface_bulk_edit' bulk_delete_url='dcim:interface_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/inventoryitem_list.html b/netbox/templates/dcim/inventoryitem_list.html deleted file mode 100644 index 57e7d2d03..000000000 --- a/netbox/templates/dcim/inventoryitem_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} -{% load helpers %} - -{% block content %} -
- {% if perms.dcim.add_devicetype %} - {% import_button 'dcim:inventoryitem_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Inventory Items{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:inventoryitem_bulk_edit' bulk_delete_url='dcim:inventoryitem_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/manufacturer_list.html b/netbox/templates/dcim/manufacturer_list.html deleted file mode 100644 index 9b612dfa5..000000000 --- a/netbox/templates/dcim/manufacturer_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_manufacturer %} - {% add_button 'dcim:manufacturer_add' %} - {% import_button 'dcim:manufacturer_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Manufacturers{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='dcim:manufacturer_bulk_delete' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/platform_list.html b/netbox/templates/dcim/platform_list.html deleted file mode 100644 index d82ef9a44..000000000 --- a/netbox/templates/dcim/platform_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_platform %} - {% add_button 'dcim:platform_add' %} - {% import_button 'dcim:platform_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Platforms{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='dcim:platform_bulk_delete' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/powerfeed_list.html b/netbox/templates/dcim/powerfeed_list.html deleted file mode 100644 index e384cb2c2..000000000 --- a/netbox/templates/dcim/powerfeed_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_powerfeed %} - {% add_button 'dcim:powerfeed_add' %} - {% import_button 'dcim:powerfeed_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Power Feeds{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:powerfeed_bulk_edit' bulk_delete_url='dcim:powerfeed_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/poweroutlet_list.html b/netbox/templates/dcim/poweroutlet_list.html deleted file mode 100644 index 2e842d699..000000000 --- a/netbox/templates/dcim/poweroutlet_list.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% export_button content_type %} -
-

{% block title %}Power Outlets{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:poweroutlet_bulk_edit' bulk_delete_url='dcim:poweroutlet_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/powerpanel_list.html b/netbox/templates/dcim/powerpanel_list.html deleted file mode 100644 index a0d49b30b..000000000 --- a/netbox/templates/dcim/powerpanel_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_powerpanel %} - {% add_button 'dcim:powerpanel_add' %} - {% import_button 'dcim:powerpanel_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Power Panels{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='dcim:powerpanel_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/powerport_list.html b/netbox/templates/dcim/powerport_list.html deleted file mode 100644 index b5830edca..000000000 --- a/netbox/templates/dcim/powerport_list.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% export_button content_type %} -
-

{% block title %}Power Ports{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:powerport_bulk_edit' bulk_delete_url='dcim:powerport_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/rack_list.html b/netbox/templates/dcim/rack_list.html deleted file mode 100644 index 2724e4427..000000000 --- a/netbox/templates/dcim/rack_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_rack %} - {% add_button 'dcim:rack_add' %} - {% import_button 'dcim:rack_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Racks{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rack_bulk_edit' bulk_delete_url='dcim:rack_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/rackgroup_list.html b/netbox/templates/dcim/rackgroup_list.html deleted file mode 100644 index 52723ef92..000000000 --- a/netbox/templates/dcim/rackgroup_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_rackgroup %} - {% add_button 'dcim:rackgroup_add' %} - {% import_button 'dcim:rackgroup_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Rack Groups{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='dcim:rackgroup_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/rackreservation_list.html b/netbox/templates/dcim/rackreservation_list.html deleted file mode 100644 index b5424bbe6..000000000 --- a/netbox/templates/dcim/rackreservation_list.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends '_base.html' %} -{% load helpers %} - -{% block content %} -

{% block title %}Rack Reservations{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rackreservation_bulk_edit' bulk_delete_url='dcim:rackreservation_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/rackrole_list.html b/netbox/templates/dcim/rackrole_list.html deleted file mode 100644 index 267ef3c7f..000000000 --- a/netbox/templates/dcim/rackrole_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_rackrole %} - {% add_button 'dcim:rackrole_add' %} - {% import_button 'dcim:rackrole_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Rack Roles{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='dcim:rackrole_bulk_delete' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/rearport_list.html b/netbox/templates/dcim/rearport_list.html deleted file mode 100644 index cc603d620..000000000 --- a/netbox/templates/dcim/rearport_list.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% export_button content_type %} -
-

{% block title %}Rear Ports{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rearport_bulk_edit' bulk_delete_url='dcim:rearport_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/region_list.html b/netbox/templates/dcim/region_list.html deleted file mode 100644 index ec1adfc06..000000000 --- a/netbox/templates/dcim/region_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_region %} - {% add_button 'dcim:region_add' %} - {% import_button 'dcim:region_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Regions{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='dcim:region_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/site_list.html b/netbox/templates/dcim/site_list.html deleted file mode 100644 index ef9e0e411..000000000 --- a/netbox/templates/dcim/site_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.dcim.add_site %} - {% add_button 'dcim:site_add' %} - {% import_button 'dcim:site_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Sites{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:site_bulk_edit' bulk_delete_url='dcim:site_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/virtualchassis_list.html b/netbox/templates/dcim/virtualchassis_list.html deleted file mode 100644 index 55cfc1691..000000000 --- a/netbox/templates/dcim/virtualchassis_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} -{% load helpers %} - -{% block content %} -
- {% export_button content_type %} -
-

{% block title %}Virtual Chassis{% endblock %}

-
-
- {% include 'utilities/obj_table.html' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/utilities/obj_list.html b/netbox/templates/utilities/obj_list.html index c06afaed8..dba9e2f36 100644 --- a/netbox/templates/utilities/obj_list.html +++ b/netbox/templates/utilities/obj_list.html @@ -4,11 +4,15 @@ {% block content %}
- {% if permissions.add %} + {% if permissions.add and 'add' in action_buttons %} {% add_button content_type.model_class|url_name:"add" %} + {% endif %} + {% if permissions.add and 'import' in action_buttons %} {% import_button content_type.model_class|url_name:"import" %} {% endif %} - {% export_button content_type %} + {% if 'export' in action_buttons %} + {% export_button content_type %} + {% endif %}

{% block title %}{{ content_type.model_class|model_name_plural|bettertitle }}{% endblock %}

diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 7618031ed..c93842d4b 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -72,6 +72,7 @@ class ObjectListView(View): filterset_form = None table = None template_name = 'utilities/obj_list.html' + action_buttons = ('add', 'import', 'export') def queryset_to_yaml(self): """ @@ -178,6 +179,7 @@ class ObjectListView(View): 'content_type': content_type, 'table': table, 'permissions': permissions, + 'action_buttons': self.action_buttons, 'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None, } context.update(self.extra_context()) From 137629b631d9de196ac1f122b64e9c8e8e4b209f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 14:17:13 -0500 Subject: [PATCH 13/67] Migrate IPAM views to use common object list template --- netbox/ipam/views.py | 11 ++++------- netbox/templates/ipam/ipaddress_list.html | 21 --------------------- netbox/templates/ipam/role_list.html | 18 ------------------ netbox/templates/ipam/service_list.html | 17 ----------------- netbox/templates/ipam/vlan_list.html | 21 --------------------- netbox/templates/ipam/vlangroup_list.html | 21 --------------------- netbox/templates/ipam/vrf_list.html | 21 --------------------- 7 files changed, 4 insertions(+), 126 deletions(-) delete mode 100644 netbox/templates/ipam/ipaddress_list.html delete mode 100644 netbox/templates/ipam/role_list.html delete mode 100644 netbox/templates/ipam/service_list.html delete mode 100644 netbox/templates/ipam/vlan_list.html delete mode 100644 netbox/templates/ipam/vlangroup_list.html delete mode 100644 netbox/templates/ipam/vrf_list.html diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index c8c7d40ca..8484d7a32 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -118,7 +118,6 @@ class VRFListView(PermissionRequiredMixin, ObjectListView): filterset = filters.VRFFilterSet filterset_form = forms.VRFFilterForm table = tables.VRFTable - template_name = 'ipam/vrf_list.html' class VRFView(PermissionRequiredMixin, View): @@ -187,6 +186,7 @@ class RIRListView(PermissionRequiredMixin, ObjectListView): filterset = filters.RIRFilterSet filterset_form = forms.RIRFilterForm table = tables.RIRDetailTable + # TODO: Remove custom template template_name = 'ipam/rir_list.html' def alter_queryset(self, request): @@ -293,10 +293,10 @@ class AggregateListView(PermissionRequiredMixin, ObjectListView): queryset = Aggregate.objects.prefetch_related('rir').annotate( child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ()) ) - filterset = filters.AggregateFilterSet filterset_form = forms.AggregateFilterForm table = tables.AggregateDetailTable + # TODO: Remove custom template template_name = 'ipam/aggregate_list.html' def extra_context(self): @@ -411,7 +411,6 @@ class RoleListView(PermissionRequiredMixin, ObjectListView): permission_required = 'ipam.view_role' queryset = Role.objects.all() table = tables.RoleTable - template_name = 'ipam/role_list.html' class RoleCreateView(PermissionRequiredMixin, ObjectEditView): @@ -449,6 +448,7 @@ class PrefixListView(PermissionRequiredMixin, ObjectListView): filterset = filters.PrefixFilterSet filterset_form = forms.PrefixFilterForm table = tables.PrefixDetailTable + # TODO: Remove custom template template_name = 'ipam/prefix_list.html' def alter_queryset(self, request): @@ -644,7 +644,6 @@ class IPAddressListView(PermissionRequiredMixin, ObjectListView): filterset = filters.IPAddressFilterSet filterset_form = forms.IPAddressFilterForm table = tables.IPAddressDetailTable - template_name = 'ipam/ipaddress_list.html' class IPAddressView(PermissionRequiredMixin, View): @@ -817,7 +816,6 @@ class VLANGroupListView(PermissionRequiredMixin, ObjectListView): filterset = filters.VLANGroupFilterSet filterset_form = forms.VLANGroupFilterForm table = tables.VLANGroupTable - template_name = 'ipam/vlangroup_list.html' class VLANGroupCreateView(PermissionRequiredMixin, ObjectEditView): @@ -893,7 +891,6 @@ class VLANListView(PermissionRequiredMixin, ObjectListView): filterset = filters.VLANFilterSet filterset_form = forms.VLANFilterForm table = tables.VLANDetailTable - template_name = 'ipam/vlan_list.html' class VLANView(PermissionRequiredMixin, View): @@ -989,7 +986,7 @@ class ServiceListView(PermissionRequiredMixin, ObjectListView): filterset = filters.ServiceFilterSet filterset_form = forms.ServiceFilterForm table = tables.ServiceTable - template_name = 'ipam/service_list.html' + action_buttons = ('export') class ServiceView(PermissionRequiredMixin, View): diff --git a/netbox/templates/ipam/ipaddress_list.html b/netbox/templates/ipam/ipaddress_list.html deleted file mode 100644 index b7920a434..000000000 --- a/netbox/templates/ipam/ipaddress_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.ipam.add_ipaddress %} - {% add_button 'ipam:ipaddress_add' %} - {% import_button 'ipam:ipaddress_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}IP Addresses{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/role_list.html b/netbox/templates/ipam/role_list.html deleted file mode 100644 index 958fa8e06..000000000 --- a/netbox/templates/ipam/role_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.ipam.add_role %} - {% add_button 'ipam:role_add' %} - {% import_button 'ipam:role_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Prefix/VLAN Roles{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='ipam:role_bulk_delete' %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/service_list.html b/netbox/templates/ipam/service_list.html deleted file mode 100644 index 4aac520d9..000000000 --- a/netbox/templates/ipam/service_list.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% export_button content_type %} -
-

{% block title %}Services{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='ipam:service_bulk_edit' bulk_delete_url='ipam:service_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/vlan_list.html b/netbox/templates/ipam/vlan_list.html deleted file mode 100644 index 24d538f88..000000000 --- a/netbox/templates/ipam/vlan_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.ipam.add_vlan %} - {% add_button 'ipam:vlan_add' %} - {% import_button 'ipam:vlan_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}VLANs{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='ipam:vlan_bulk_edit' bulk_delete_url='ipam:vlan_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/vlangroup_list.html b/netbox/templates/ipam/vlangroup_list.html deleted file mode 100644 index 16ddd9669..000000000 --- a/netbox/templates/ipam/vlangroup_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.ipam.add_vlangroup %} - {% add_button 'ipam:vlangroup_add' %} - {% import_button 'ipam:vlangroup_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}VLAN Groups{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='ipam:vlangroup_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/vrf_list.html b/netbox/templates/ipam/vrf_list.html deleted file mode 100644 index 975c73a37..000000000 --- a/netbox/templates/ipam/vrf_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.ipam.add_vrf %} - {% add_button 'ipam:vrf_add' %} - {% import_button 'ipam:vrf_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}VRFs{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='ipam:vrf_bulk_edit' bulk_delete_url='ipam:vrf_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} From 1d15f7432224585d5315fe68b314a925c09134fd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 14:19:14 -0500 Subject: [PATCH 14/67] Migrate secrets views to use common object list template --- netbox/secrets/views.py | 3 +-- netbox/templates/secrets/secret_list.html | 20 ------------------- netbox/templates/secrets/secretrole_list.html | 18 ----------------- 3 files changed, 1 insertion(+), 40 deletions(-) delete mode 100644 netbox/templates/secrets/secret_list.html delete mode 100644 netbox/templates/secrets/secretrole_list.html diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index 288edaa6f..d92e4b64d 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -35,7 +35,6 @@ class SecretRoleListView(PermissionRequiredMixin, ObjectListView): permission_required = 'secrets.view_secretrole' queryset = SecretRole.objects.annotate(secret_count=Count('secrets')) table = tables.SecretRoleTable - template_name = 'secrets/secretrole_list.html' class SecretRoleCreateView(PermissionRequiredMixin, ObjectEditView): @@ -73,7 +72,7 @@ class SecretListView(PermissionRequiredMixin, ObjectListView): filterset = filters.SecretFilterSet filterset_form = forms.SecretFilterForm table = tables.SecretTable - template_name = 'secrets/secret_list.html' + action_buttons = ('import', 'export') class SecretView(PermissionRequiredMixin, View): diff --git a/netbox/templates/secrets/secret_list.html b/netbox/templates/secrets/secret_list.html deleted file mode 100644 index ee631b439..000000000 --- a/netbox/templates/secrets/secret_list.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.secrets.add_secret %} - {% import_button 'secrets:secret_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Secrets{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='secrets:secret_bulk_edit' bulk_delete_url='secrets:secret_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/secrets/secretrole_list.html b/netbox/templates/secrets/secretrole_list.html deleted file mode 100644 index 0e4caadae..000000000 --- a/netbox/templates/secrets/secretrole_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.secrets.add_secretrole %} - {% add_button 'secrets:secretrole_add' %} - {% import_button 'secrets:secretrole_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Secret Roles{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='secrets:secretrole_bulk_delete' %} -
-
-{% endblock %} From 75a2ade6feba1b1de317585f18699695be30e0fd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 14:21:14 -0500 Subject: [PATCH 15/67] Migrate tenancy views to use common object list template --- netbox/templates/tenancy/tenant_list.html | 21 ------------------- .../templates/tenancy/tenantgroup_list.html | 18 ---------------- netbox/tenancy/views.py | 2 -- 3 files changed, 41 deletions(-) delete mode 100644 netbox/templates/tenancy/tenant_list.html delete mode 100644 netbox/templates/tenancy/tenantgroup_list.html diff --git a/netbox/templates/tenancy/tenant_list.html b/netbox/templates/tenancy/tenant_list.html deleted file mode 100644 index a77636a5b..000000000 --- a/netbox/templates/tenancy/tenant_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.tenancy.add_tenant %} - {% add_button 'tenancy:tenant_add' %} - {% import_button 'tenancy:tenant_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Tenants{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='tenancy:tenant_bulk_edit' bulk_delete_url='tenancy:tenant_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/tenancy/tenantgroup_list.html b/netbox/templates/tenancy/tenantgroup_list.html deleted file mode 100644 index af0dc1aad..000000000 --- a/netbox/templates/tenancy/tenantgroup_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.tenancy.add_tenantgroup %} - {% add_button 'tenancy:tenantgroup_add' %} - {% import_button 'tenancy:tenantgroup_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Tenant Groups{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='tenancy:tenantgroup_bulk_delete' %} -
-
-{% endblock %} diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index a53458694..0319a20b0 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -22,7 +22,6 @@ class TenantGroupListView(PermissionRequiredMixin, ObjectListView): permission_required = 'tenancy.view_tenantgroup' queryset = TenantGroup.objects.annotate(tenant_count=Count('tenants')) table = tables.TenantGroupTable - template_name = 'tenancy/tenantgroup_list.html' class TenantGroupCreateView(PermissionRequiredMixin, ObjectEditView): @@ -60,7 +59,6 @@ class TenantListView(PermissionRequiredMixin, ObjectListView): filterset = filters.TenantFilterSet filterset_form = forms.TenantFilterForm table = tables.TenantTable - template_name = 'tenancy/tenant_list.html' class TenantView(PermissionRequiredMixin, View): From 490bad12d18cdbcc847a08fb8d227339e64af82f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 14:24:22 -0500 Subject: [PATCH 16/67] Migrate virtualization views to use common object list template --- .../virtualization/cluster_list.html | 21 ------------------- .../virtualization/clustergroup_list.html | 18 ---------------- .../virtualization/clustertype_list.html | 18 ---------------- netbox/virtualization/views.py | 4 +--- 4 files changed, 1 insertion(+), 60 deletions(-) delete mode 100644 netbox/templates/virtualization/cluster_list.html delete mode 100644 netbox/templates/virtualization/clustergroup_list.html delete mode 100644 netbox/templates/virtualization/clustertype_list.html diff --git a/netbox/templates/virtualization/cluster_list.html b/netbox/templates/virtualization/cluster_list.html deleted file mode 100644 index 6f5f058ad..000000000 --- a/netbox/templates/virtualization/cluster_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.virtualization.add_cluster %} - {% add_button 'virtualization:cluster_add' %} - {% import_button 'virtualization:cluster_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Clusters{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='virtualization:cluster_bulk_edit' bulk_delete_url='virtualization:cluster_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/virtualization/clustergroup_list.html b/netbox/templates/virtualization/clustergroup_list.html deleted file mode 100644 index f922b5efa..000000000 --- a/netbox/templates/virtualization/clustergroup_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.virtualization.add_clustergroup %} - {% add_button 'virtualization:clustergroup_add' %} - {% import_button 'virtualization:clustergroup_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Cluster Groups{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='virtualization:clustergroup_bulk_delete' %} -
-
-{% endblock %} diff --git a/netbox/templates/virtualization/clustertype_list.html b/netbox/templates/virtualization/clustertype_list.html deleted file mode 100644 index 48ff077c7..000000000 --- a/netbox/templates/virtualization/clustertype_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.virtualization.add_clustertype %} - {% add_button 'virtualization:clustertype_add' %} - {% import_button 'virtualization:clustertype_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Cluster Types{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='virtualization:clustertype_bulk_delete' %} -
-
-{% endblock %} diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index b961d65e5..e6292ba7d 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -26,7 +26,6 @@ class ClusterTypeListView(PermissionRequiredMixin, ObjectListView): permission_required = 'virtualization.view_clustertype' queryset = ClusterType.objects.annotate(cluster_count=Count('clusters')) table = tables.ClusterTypeTable - template_name = 'virtualization/clustertype_list.html' class ClusterTypeCreateView(PermissionRequiredMixin, ObjectEditView): @@ -62,7 +61,6 @@ class ClusterGroupListView(PermissionRequiredMixin, ObjectListView): permission_required = 'virtualization.view_clustergroup' queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters')) table = tables.ClusterGroupTable - template_name = 'virtualization/clustergroup_list.html' class ClusterGroupCreateView(PermissionRequiredMixin, ObjectEditView): @@ -100,7 +98,6 @@ class ClusterListView(PermissionRequiredMixin, ObjectListView): table = tables.ClusterTable filterset = filters.ClusterFilterSet filterset_form = forms.ClusterFilterForm - template_name = 'virtualization/cluster_list.html' class ClusterView(PermissionRequiredMixin, View): @@ -257,6 +254,7 @@ class VirtualMachineListView(PermissionRequiredMixin, ObjectListView): filterset = filters.VirtualMachineFilterSet filterset_form = forms.VirtualMachineFilterForm table = tables.VirtualMachineDetailTable + # TODO: Remove custom template template_name = 'virtualization/virtualmachine_list.html' From ebda44388834039b6325b94be7d63d90df0d839b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 16:39:38 -0500 Subject: [PATCH 17/67] Migrate extras views to use common object list template --- netbox/dcim/views.py | 2 +- netbox/extras/views.py | 5 +++-- netbox/ipam/views.py | 2 +- .../templates/extras/configcontext_list.html | 19 ------------------- netbox/templates/extras/tag_list.html | 14 -------------- 5 files changed, 5 insertions(+), 37 deletions(-) delete mode 100644 netbox/templates/extras/configcontext_list.html delete mode 100644 netbox/templates/extras/tag_list.html diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 89f051b2a..4a7e9eca5 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2281,7 +2281,7 @@ class VirtualChassisListView(PermissionRequiredMixin, ObjectListView): table = tables.VirtualChassisTable filterset = filters.VirtualChassisFilterSet filterset_form = forms.VirtualChassisFilterForm - action_buttons = ('export') + action_buttons = ('export',) class VirtualChassisCreateView(PermissionRequiredMixin, View): diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 73d29393f..0998646cf 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -34,7 +34,7 @@ class TagListView(PermissionRequiredMixin, ObjectListView): filterset = filters.TagFilterSet filterset_form = forms.TagFilterForm table = TagTable - template_name = 'extras/tag_list.html' + action_buttons = () class TagView(PermissionRequiredMixin, View): @@ -111,7 +111,7 @@ class ConfigContextListView(PermissionRequiredMixin, ObjectListView): filterset = filters.ConfigContextFilterSet filterset_form = forms.ConfigContextFilterForm table = ConfigContextTable - template_name = 'extras/configcontext_list.html' + action_buttons = ('add',) class ConfigContextView(PermissionRequiredMixin, View): @@ -190,6 +190,7 @@ class ObjectChangeListView(PermissionRequiredMixin, ObjectListView): filterset = filters.ObjectChangeFilterSet filterset_form = forms.ObjectChangeFilterForm table = ObjectChangeTable + # TODO: Remove custom template template_name = 'extras/objectchange_list.html' diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 8484d7a32..36d757b6a 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -986,7 +986,7 @@ class ServiceListView(PermissionRequiredMixin, ObjectListView): filterset = filters.ServiceFilterSet filterset_form = forms.ServiceFilterForm table = tables.ServiceTable - action_buttons = ('export') + action_buttons = ('export',) class ServiceView(PermissionRequiredMixin, View): diff --git a/netbox/templates/extras/configcontext_list.html b/netbox/templates/extras/configcontext_list.html deleted file mode 100644 index f21be2836..000000000 --- a/netbox/templates/extras/configcontext_list.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -
- {% if perms.extras.add_configcontext %} - {% add_button 'extras:configcontext_add' %} - {% endif %} -
-

{% block title %}Config Contexts{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='extras:configcontext_bulk_edit' bulk_delete_url='extras:configcontext_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} diff --git a/netbox/templates/extras/tag_list.html b/netbox/templates/extras/tag_list.html deleted file mode 100644 index c87b6c2e5..000000000 --- a/netbox/templates/extras/tag_list.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends '_base.html' %} -{% load buttons %} - -{% block content %} -

{% block title %}Tags{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='extras:tag_bulk_edit' bulk_delete_url='extras:tag_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
-{% endblock %} From 54bd9fa48afd433188f41951abc4e8003e9da0c6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 17:11:39 -0500 Subject: [PATCH 18/67] Convert change log view to extend standard template --- netbox/extras/views.py | 2 +- .../templates/extras/objectchange_list.html | 23 +++++-------------- netbox/templates/inc/nav_menu.html | 2 +- netbox/templates/utilities/obj_list.html | 19 +++++++-------- 4 files changed, 16 insertions(+), 30 deletions(-) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 0998646cf..3912c602f 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -190,8 +190,8 @@ class ObjectChangeListView(PermissionRequiredMixin, ObjectListView): filterset = filters.ObjectChangeFilterSet filterset_form = forms.ObjectChangeFilterForm table = ObjectChangeTable - # TODO: Remove custom template template_name = 'extras/objectchange_list.html' + action_buttons = ('export',) class ObjectChangeView(PermissionRequiredMixin, View): diff --git a/netbox/templates/extras/objectchange_list.html b/netbox/templates/extras/objectchange_list.html index e9be6ba69..3672f4f04 100644 --- a/netbox/templates/extras/objectchange_list.html +++ b/netbox/templates/extras/objectchange_list.html @@ -1,20 +1,9 @@ -{% extends '_base.html' %} -{% load buttons %} +{% extends 'utilities/obj_list.html' %} -{% block content %} -
- {% export_button content_type %} -
-

{% block title %}Changelog{% endblock %}

-
-
- {% include 'utilities/obj_table.html' %} -
- Changelog retention: {% if settings.CHANGELOG_RETENTION %}{{ settings.CHANGELOG_RETENTION }} days{% else %}Indefinite{% endif %} -
+{% block title %}Change Log{% endblock %} + +{% block sidebar %} +
+ Change log retention: {% if settings.CHANGELOG_RETENTION %}{{ settings.CHANGELOG_RETENTION }} days{% else %}Indefinite{% endif %}
-
- {% include 'inc/search_panel.html' %} -
-
{% endblock %} diff --git a/netbox/templates/inc/nav_menu.html b/netbox/templates/inc/nav_menu.html index 55ac3e5c2..900d783f6 100644 --- a/netbox/templates/inc/nav_menu.html +++ b/netbox/templates/inc/nav_menu.html @@ -473,7 +473,7 @@

{% block title %}{{ content_type.model_class|model_name_plural|bettertitle }}{% endblock %}

- {% if filter_form %} -
- {% include 'utilities/obj_table.html' with bulk_edit_url=content_type.model_class|url_name:"bulk_edit" bulk_delete_url=content_type.model_class|url_name:"bulk_delete" %} -
-
+
+ {% include 'utilities/obj_table.html' with bulk_edit_url=content_type.model_class|url_name:"bulk_edit" bulk_delete_url=content_type.model_class|url_name:"bulk_delete" %} +
+
+ {% if filter_form %} {% include 'inc/search_panel.html' %} -
- {% else %} -
- {% include 'utilities/obj_table.html' with bulk_edit_url=content_type.model_class|url_name:"bulk_edit" bulk_delete_url=content_type.model_class|url_name:"bulk_delete" %} -
- {% endif %} + {% endif %} + {% block sidebar %}{% endblock %} +
{% endblock %} From a7dc6a26e82103635f88fcfe9fac097d808bfa92 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 17:22:17 -0500 Subject: [PATCH 19/67] Convert IPAM list views to extend standard template --- netbox/templates/ipam/aggregate_list.html | 37 ++++++----------------- netbox/templates/ipam/prefix_list.html | 21 ++----------- netbox/templates/ipam/rir_list.html | 33 ++++++-------------- netbox/templates/utilities/obj_list.html | 1 + 4 files changed, 23 insertions(+), 69 deletions(-) diff --git a/netbox/templates/ipam/aggregate_list.html b/netbox/templates/ipam/aggregate_list.html index 27363a56d..85a2bd36d 100644 --- a/netbox/templates/ipam/aggregate_list.html +++ b/netbox/templates/ipam/aggregate_list.html @@ -1,31 +1,14 @@ -{% extends '_base.html' %} -{% load buttons %} +{% extends 'utilities/obj_list.html' %} {% load humanize %} -{% block content %} -
- {% if perms.ipam.add_aggregate %} - {% add_button 'ipam:aggregate_add' %} - {% import_button 'ipam:aggregate_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Aggregates{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='ipam:aggregate_bulk_edit' bulk_delete_url='ipam:aggregate_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
- Statistics -
-
    -
  • Total IPv4 IPs {{ ipv4_total|intcomma }}
  • -
  • Total IPv6 /64s {{ ipv6_total|intcomma }}
  • -
+{% block sidebar %} +
+
+ Statistics
-
-
+
    +
  • Total IPv4 IPs {{ ipv4_total|intcomma }}
  • +
  • Total IPv6 /64s {{ ipv6_total|intcomma }}
  • +
+
{% endblock %} diff --git a/netbox/templates/ipam/prefix_list.html b/netbox/templates/ipam/prefix_list.html index f0754d37b..00f0b7fe9 100644 --- a/netbox/templates/ipam/prefix_list.html +++ b/netbox/templates/ipam/prefix_list.html @@ -1,26 +1,9 @@ -{% extends '_base.html' %} -{% load buttons %} +{% extends 'utilities/obj_list.html' %} {% load helpers %} -{% block content %} -
+{% block buttons %} - {% if perms.ipam.add_prefix %} - {% add_button 'ipam:prefix_add' %} - {% import_button 'ipam:prefix_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}Prefixes{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
{% endblock %} diff --git a/netbox/templates/ipam/rir_list.html b/netbox/templates/ipam/rir_list.html index 846eb3cb2..02f01fc7c 100644 --- a/netbox/templates/ipam/rir_list.html +++ b/netbox/templates/ipam/rir_list.html @@ -1,9 +1,6 @@ -{% extends '_base.html' %} -{% load buttons %} -{% load humanize %} +{% extends 'utilities/obj_list.html' %} -{% block content %} -
+{% block buttons %} {% if request.GET.family == '6' %} @@ -15,22 +12,12 @@ IPv6 Stats {% endif %} - {% if perms.ipam.add_rir %} - {% add_button 'ipam:rir_add' %} - {% import_button 'ipam:rir_import' %} - {% endif %} - {% export_button content_type %} -
-

{% block title %}RIRs{% endblock %}

-
-
- {% include 'utilities/obj_table.html' with bulk_delete_url='ipam:rir_bulk_delete' %} - {% if request.GET.family == '6' %} -
Note: Numbers shown indicate /64 prefixes.
- {% endif %} -
-
- {% include 'inc/search_panel.html' %} -
-
+{% endblock %} + +{% block sidebar %} + {% if request.GET.family == '6' %} +
+ Numbers shown indicate /64 prefixes. +
+ {% endif %} {% endblock %} diff --git a/netbox/templates/utilities/obj_list.html b/netbox/templates/utilities/obj_list.html index 05dd6a6ac..5ffed3c2b 100644 --- a/netbox/templates/utilities/obj_list.html +++ b/netbox/templates/utilities/obj_list.html @@ -4,6 +4,7 @@ {% block content %}
+ {% block buttons %}{% endblock %} {% if permissions.add and 'add' in action_buttons %} {% add_button content_type.model_class|url_name:"add" %} {% endif %} From 2b1d786fe21e1d1e1b4b2cb945a5144c76102e8e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 21:51:03 -0500 Subject: [PATCH 20/67] Post-release version bump --- netbox/netbox/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 81422b714..249ee9e53 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured # Environment setup # -VERSION = '2.7.6' +VERSION = '2.7.7-dev' # Hostname HOSTNAME = platform.node() From 98a59b91169bad27ee43ad8f086ace012cd5a882 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 09:13:05 -0500 Subject: [PATCH 21/67] Fixes #4168: Role is not required when creating a virtual machine --- docs/release-notes/version-2.7.md | 8 ++++++++ netbox/virtualization/forms.py | 1 + 2 files changed, 9 insertions(+) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 70981658f..c235b8115 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -1,3 +1,11 @@ +# v2.7.7 (FUTURE) + +## Bug Fixes + +* [#4168](https://github.com/netbox-community/netbox/issues/4168) - Role is not required when creating a virtual machine + +--- + # v2.7.6 (2020-02-13) ## Bug Fixes diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 12393d400..f35a94c99 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -351,6 +351,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): ) role = DynamicModelChoiceField( queryset=DeviceRole.objects.all(), + required=False, widget=APISelect( api_url="/api/dcim/device-roles/", additional_query_params={ From 2c27cc64dfb6f4df732ffb668b16774389066d18 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 09:35:43 -0500 Subject: [PATCH 22/67] Changelog for #2519 --- docs/release-notes/version-2.7.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index c235b8115..ac2f149e1 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -2,6 +2,7 @@ ## Bug Fixes +* [#2519](https://github.com/netbox-community/netbox/issues/2519) - Avoid race condition when provisioning "next available" IPs/prefixes via the API * [#4168](https://github.com/netbox-community/netbox/issues/4168) - Role is not required when creating a virtual machine --- From c054240a69d8775ebc6941cd116cc41aa56d39ea Mon Sep 17 00:00:00 2001 From: Dan Starner Date: Fri, 14 Feb 2020 09:39:01 -0500 Subject: [PATCH 23/67] ammend redis conn check to acccount for sentinel --- netbox/extras/apps.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/netbox/extras/apps.py b/netbox/extras/apps.py index f8c5a98e6..257e7801a 100644 --- a/netbox/extras/apps.py +++ b/netbox/extras/apps.py @@ -13,13 +13,23 @@ class ExtrasConfig(AppConfig): # Check that we can connect to the configured Redis database. try: - rs = redis.Redis( - host=settings.WEBHOOKS_REDIS_HOST, - port=settings.WEBHOOKS_REDIS_PORT, - db=settings.WEBHOOKS_REDIS_DATABASE, - password=settings.WEBHOOKS_REDIS_PASSWORD or None, - ssl=settings.WEBHOOKS_REDIS_SSL, - ) + if settings.WEBHOOKS_REDIS_USING_SENTINEL: + sentinel = redis.sentinel.Sentinel( + settings.WEBHOOKS_REDIS_SENTINELS, + socket_timeout=settings.WEBHOOKS_REDIS_DEFAULT_TIMEOUT + ) + rs = sentinel.master_for( + settings.WEBHOOKS_REDIS_SENTINEL_SERVICE, + socket_timeout=settings.WEBHOOKS_REDIS_DEFAULT_TIMEOUT + ) + else: + rs = redis.Redis( + host=settings.WEBHOOKS_REDIS_HOST, + port=settings.WEBHOOKS_REDIS_PORT, + db=settings.WEBHOOKS_REDIS_DATABASE, + password=settings.WEBHOOKS_REDIS_PASSWORD or None, + ssl=settings.WEBHOOKS_REDIS_SSL, + ) rs.ping() except redis.exceptions.ConnectionError: raise ImproperlyConfigured( From 93cc95076c556ab401bd5d5df8030271fad7ce7f Mon Sep 17 00:00:00 2001 From: Dan Starner Date: Fri, 14 Feb 2020 10:29:09 -0500 Subject: [PATCH 24/67] fix extraneous formatting of notice boxes in required settings doc --- docs/configuration/required-settings.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/configuration/required-settings.md b/docs/configuration/required-settings.md index 81790eae0..e86b2810a 100644 --- a/docs/configuration/required-settings.md +++ b/docs/configuration/required-settings.md @@ -80,11 +80,11 @@ REDIS = { } ``` -!!! note: +!!! note If you are upgrading from a version prior to v2.7, please note that the Redis connection configuration settings have changed. Manual modification to bring the `REDIS` section inline with the above specification is necessary -!!! warning: +!!! note It is highly recommended to keep the webhook and cache databases separate. Using the same database number on the same Redis instance for both may result in webhook processing data being lost during cache flushing events. @@ -124,7 +124,7 @@ REDIS = { } ``` -!!! note: +!!! note It is possible to have only one or the other Redis configurations to use Sentinel functionality. It is possible for example to have the webhook use sentinel via `HOST`/`PORT` and for caching to use Sentinel via `SENTINELS`/`SENTINEL_SERVICE`. From 574b00351897a917621227cd36c215d509443372 Mon Sep 17 00:00:00 2001 From: Dan Starner Date: Fri, 14 Feb 2020 11:16:59 -0500 Subject: [PATCH 25/67] remove redis conn check from extras AppConfig --- netbox/extras/apps.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/netbox/extras/apps.py b/netbox/extras/apps.py index 257e7801a..5dad07b48 100644 --- a/netbox/extras/apps.py +++ b/netbox/extras/apps.py @@ -8,31 +8,4 @@ class ExtrasConfig(AppConfig): name = "extras" def ready(self): - import extras.signals - - # Check that we can connect to the configured Redis database. - try: - if settings.WEBHOOKS_REDIS_USING_SENTINEL: - sentinel = redis.sentinel.Sentinel( - settings.WEBHOOKS_REDIS_SENTINELS, - socket_timeout=settings.WEBHOOKS_REDIS_DEFAULT_TIMEOUT - ) - rs = sentinel.master_for( - settings.WEBHOOKS_REDIS_SENTINEL_SERVICE, - socket_timeout=settings.WEBHOOKS_REDIS_DEFAULT_TIMEOUT - ) - else: - rs = redis.Redis( - host=settings.WEBHOOKS_REDIS_HOST, - port=settings.WEBHOOKS_REDIS_PORT, - db=settings.WEBHOOKS_REDIS_DATABASE, - password=settings.WEBHOOKS_REDIS_PASSWORD or None, - ssl=settings.WEBHOOKS_REDIS_SSL, - ) - rs.ping() - except redis.exceptions.ConnectionError: - raise ImproperlyConfigured( - "Unable to connect to the Redis database. Check that the Redis configuration has been defined in " - "configuration.py." - ) From ae73c5f051b8f0f06b1b4a8aeaec3d33a8799838 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 14 Feb 2020 16:43:42 +0000 Subject: [PATCH 26/67] Added back clean --- netbox/virtualization/forms.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 6771ee76b..2aa57608a 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -704,6 +704,22 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm): self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', site.pk) self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk) + def clean(self): + super().clean() + + # Validate VLAN assignments + tagged_vlans = self.cleaned_data['tagged_vlans'] + + # Untagged interfaces cannot be assigned tagged VLANs + if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans: + raise forms.ValidationError({ + 'mode': "An access interface cannot have tagged VLANs assigned." + }) + + # Remove all tagged VLAN assignments from "tagged all" interfaces + elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL: + self.cleaned_data['tagged_vlans'] = [] + class InterfaceCreateForm(BootstrapMixin, forms.Form): virtual_machine = forms.ModelChoiceField( From 175ce21da9cf83c25cc9127134fe0f43390f7baa Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 12:04:35 -0500 Subject: [PATCH 27/67] Changelog for #3840 --- docs/release-notes/version-2.7.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index ac2f149e1..5e58e0c58 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -1,5 +1,9 @@ # v2.7.7 (FUTURE) +## Enhancements + +* [#3840](https://github.com/netbox-community/netbox/issues/3840) - Enhance search function when selecting VLANs for interface assignment + ## Bug Fixes * [#2519](https://github.com/netbox-community/netbox/issues/2519) - Avoid race condition when provisioning "next available" IPs/prefixes via the API From e870c4853ab6e8174af2204ff9577f7009ae348c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 12:04:56 -0500 Subject: [PATCH 28/67] Clean up unused imports --- netbox/extras/apps.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/netbox/extras/apps.py b/netbox/extras/apps.py index 5dad07b48..3201c3bb2 100644 --- a/netbox/extras/apps.py +++ b/netbox/extras/apps.py @@ -1,7 +1,4 @@ from django.apps import AppConfig -from django.conf import settings -from django.core.exceptions import ImproperlyConfigured -import redis class ExtrasConfig(AppConfig): From 9a6a1e99666fb2f18cd6c521edadc7e946705a19 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 13:09:01 -0500 Subject: [PATCH 29/67] Closes #4170: Improve color contrast in rack elevation drawings --- docs/release-notes/version-2.7.md | 1 + netbox/dcim/models/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 5e58e0c58..d3b889513 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -3,6 +3,7 @@ ## Enhancements * [#3840](https://github.com/netbox-community/netbox/issues/3840) - Enhance search function when selecting VLANs for interface assignment +* [#4170](https://github.com/netbox-community/netbox/issues/4170) - Improve color contrast in rack elevation drawings ## Bug Fixes diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index f291fc825..29afef1f1 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -382,8 +382,8 @@ class RackElevationHelperMixin: # add gradients RackElevationHelperMixin._add_gradient(drawing, 'reserved', '#c7c7ff') - RackElevationHelperMixin._add_gradient(drawing, 'occupied', '#f0f0f0') - RackElevationHelperMixin._add_gradient(drawing, 'blocked', '#ffc7c7') + RackElevationHelperMixin._add_gradient(drawing, 'occupied', '#d7d7d7') + RackElevationHelperMixin._add_gradient(drawing, 'blocked', '#ffc0c0') return drawing From 0c6cfd6255a00f7c01db3cee1492ea28c7f738a3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 13:21:32 -0500 Subject: [PATCH 30/67] Convert device and VM list views to use obj_list.html --- netbox/templates/dcim/device_list.html | 39 +++++++------- netbox/templates/dcim/inc/device_table.html | 24 --------- netbox/templates/utilities/obj_list.html | 51 ++++++++++++++++++- .../inc/virtualmachine_table.html | 14 ----- .../virtualization/virtualmachine_list.html | 29 ++++------- 5 files changed, 82 insertions(+), 75 deletions(-) delete mode 100644 netbox/templates/dcim/inc/device_table.html delete mode 100644 netbox/templates/virtualization/inc/virtualmachine_table.html diff --git a/netbox/templates/dcim/device_list.html b/netbox/templates/dcim/device_list.html index 8b991689f..b12e4b5a8 100644 --- a/netbox/templates/dcim/device_list.html +++ b/netbox/templates/dcim/device_list.html @@ -1,21 +1,24 @@ -{% extends '_base.html' %} -{% load buttons %} +{% extends 'utilities/obj_list.html' %} -{% block content %} -
- {% if perms.dcim.add_device %} - {% add_button 'dcim:device_add' %} - {% import_button 'dcim:device_import' %} +{% block bulk_buttons %} + {% if perms.dcim.change_device %} +
+ + +
+ {% endif %} + {% if perms.dcim.add_virtualchassis %} + {% endif %} - {% export_button content_type %} -
-

{% block title %}Devices{% endblock %}

-
-
- {% include 'dcim/inc/device_table.html' with bulk_edit_url='dcim:device_bulk_edit' bulk_delete_url='dcim:device_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
{% endblock %} diff --git a/netbox/templates/dcim/inc/device_table.html b/netbox/templates/dcim/inc/device_table.html deleted file mode 100644 index 68570fdf3..000000000 --- a/netbox/templates/dcim/inc/device_table.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends 'utilities/obj_table.html' %} - -{% block extra_actions %} - {% if perms.dcim.change_device %} -
- - -
- {% endif %} - {% if perms.dcim.add_virtualchassis %} - - {% endif %} -{% endblock %} diff --git a/netbox/templates/utilities/obj_list.html b/netbox/templates/utilities/obj_list.html index 5ffed3c2b..020a37660 100644 --- a/netbox/templates/utilities/obj_list.html +++ b/netbox/templates/utilities/obj_list.html @@ -18,7 +18,56 @@

{% block title %}{{ content_type.model_class|model_name_plural|bettertitle }}{% endblock %}

- {% include 'utilities/obj_table.html' with bulk_edit_url=content_type.model_class|url_name:"bulk_edit" bulk_delete_url=content_type.model_class|url_name:"bulk_delete" %} + {% with bulk_edit_url=content_type.model_class|url_name:"bulk_edit" bulk_delete_url=content_type.model_class|url_name:"bulk_delete" %} + {% if permissions.change or permissions.delete %} +
+ {% csrf_token %} + + {% if table.paginator.num_pages > 1 %} + + {% endif %} + {% include table_template|default:'responsive_table.html' %} +
+ {% block bulk_buttons %}{% endblock %} + {% if bulk_edit_url and permissions.change %} + + {% endif %} + {% if bulk_delete_url and permissions.delete %} + + {% endif %} +
+
+ {% else %} + {% include table_template|default:'responsive_table.html' %} + {% endif %} + {% endwith %} + {% include 'inc/paginator.html' with paginator=table.paginator page=table.page %} +
{% if filter_form %} diff --git a/netbox/templates/virtualization/inc/virtualmachine_table.html b/netbox/templates/virtualization/inc/virtualmachine_table.html deleted file mode 100644 index ce249593e..000000000 --- a/netbox/templates/virtualization/inc/virtualmachine_table.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'utilities/obj_table.html' %} - -{% block extra_actions %} - {% if perms.virtualization.change_virtualmachine %} -
- - -
- {% endif %} -{% endblock %} \ No newline at end of file diff --git a/netbox/templates/virtualization/virtualmachine_list.html b/netbox/templates/virtualization/virtualmachine_list.html index 821f956a2..74839b250 100644 --- a/netbox/templates/virtualization/virtualmachine_list.html +++ b/netbox/templates/virtualization/virtualmachine_list.html @@ -1,21 +1,14 @@ -{% extends '_base.html' %} -{% load buttons %} +{% extends 'utilities/obj_list.html' %} -{% block content %} -
- {% if perms.virtualization.add_virtualmachine %} - {% add_button 'virtualization:virtualmachine_add' %} - {% import_button 'virtualization:virtualmachine_import' %} +{% block bulk_buttons %} + {% if perms.virtualization.change_virtualmachine %} +
+ + +
{% endif %} - {% export_button content_type %} -
-

{% block title %}Virtual Machines{% endblock %}

-
-
- {% include 'virtualization/inc/virtualmachine_table.html' with bulk_edit_url='virtualization:virtualmachine_bulk_edit' bulk_delete_url='virtualization:virtualmachine_bulk_delete' %} -
-
- {% include 'inc/search_panel.html' %} -
-
{% endblock %} From af046ecb0e9afb1548dcf5a5972ddbe786afd6a4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 13:30:53 -0500 Subject: [PATCH 31/67] Clean up TODO notes --- netbox/dcim/views.py | 1 - netbox/ipam/views.py | 3 --- netbox/virtualization/views.py | 1 - 3 files changed, 5 deletions(-) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 4a7e9eca5..0bb6658a2 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1062,7 +1062,6 @@ class DeviceListView(PermissionRequiredMixin, ObjectListView): filterset = filters.DeviceFilterSet filterset_form = forms.DeviceFilterForm table = tables.DeviceDetailTable - # TODO: Remove custom template template_name = 'dcim/device_list.html' diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 36d757b6a..053098f0b 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -186,7 +186,6 @@ class RIRListView(PermissionRequiredMixin, ObjectListView): filterset = filters.RIRFilterSet filterset_form = forms.RIRFilterForm table = tables.RIRDetailTable - # TODO: Remove custom template template_name = 'ipam/rir_list.html' def alter_queryset(self, request): @@ -296,7 +295,6 @@ class AggregateListView(PermissionRequiredMixin, ObjectListView): filterset = filters.AggregateFilterSet filterset_form = forms.AggregateFilterForm table = tables.AggregateDetailTable - # TODO: Remove custom template template_name = 'ipam/aggregate_list.html' def extra_context(self): @@ -448,7 +446,6 @@ class PrefixListView(PermissionRequiredMixin, ObjectListView): filterset = filters.PrefixFilterSet filterset_form = forms.PrefixFilterForm table = tables.PrefixDetailTable - # TODO: Remove custom template template_name = 'ipam/prefix_list.html' def alter_queryset(self, request): diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index e6292ba7d..291392eb4 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -254,7 +254,6 @@ class VirtualMachineListView(PermissionRequiredMixin, ObjectListView): filterset = filters.VirtualMachineFilterSet filterset_form = forms.VirtualMachineFilterForm table = tables.VirtualMachineDetailTable - # TODO: Remove custom template template_name = 'virtualization/virtualmachine_list.html' From 3612057fc1f7a4e39a9e76f76f8f5c0e5f244569 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 14:27:47 -0500 Subject: [PATCH 32/67] Fixes #4175: Fix potential exception when bulk editing objects from a filtered list --- docs/release-notes/version-2.7.md | 1 + netbox/templates/utilities/obj_list.html | 4 ++-- netbox/utilities/views.py | 6 +----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index d3b889513..e224196ad 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -9,6 +9,7 @@ * [#2519](https://github.com/netbox-community/netbox/issues/2519) - Avoid race condition when provisioning "next available" IPs/prefixes via the API * [#4168](https://github.com/netbox-community/netbox/issues/4168) - Role is not required when creating a virtual machine +* [#4175](https://github.com/netbox-community/netbox/issues/4175) - Fix potential exception when bulk editing objects from a filtered list --- diff --git a/netbox/templates/utilities/obj_list.html b/netbox/templates/utilities/obj_list.html index 020a37660..fe70edd3b 100644 --- a/netbox/templates/utilities/obj_list.html +++ b/netbox/templates/utilities/obj_list.html @@ -51,12 +51,12 @@
{% block bulk_buttons %}{% endblock %} {% if bulk_edit_url and permissions.change %} - {% endif %} {% if bulk_delete_url and permissions.delete %} - {% endif %} diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index c93842d4b..d0257324e 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -634,7 +634,7 @@ class BulkEditView(GetReturnURLMixin, View): post_data['pk'] = [obj.pk for obj in self.filterset(request.GET, model.objects.only('pk')).qs] if '_apply' in request.POST: - form = self.form(model, request.POST, initial=request.GET) + form = self.form(model, request.POST) if form.is_valid(): custom_fields = form.custom_fields if hasattr(form, 'custom_fields') else [] @@ -718,10 +718,6 @@ class BulkEditView(GetReturnURLMixin, View): else: # Pass the PK list as initial data to avoid binding the form initial_data = querydict_to_dict(post_data) - - # Append any normal initial data (passed as GET parameters) - initial_data.update(request.GET) - form = self.form(model, initial=initial_data) # Retrieve objects being edited From a5f4b197b37447988663a8019c9457f68512ddc8 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sat, 15 Feb 2020 22:39:08 +0000 Subject: [PATCH 33/67] Fixes #2511: Compare object change to the previous change --- netbox/extras/views.py | 24 +++++++++++++++++++ netbox/templates/extras/objectchange.html | 29 +++++++++++++++++++++++ netbox/utilities/utils.py | 16 +++++++++++++ 3 files changed, 69 insertions(+) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 3912c602f..b625cf7b1 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -12,6 +12,7 @@ from django_tables2 import RequestConfig from utilities.forms import ConfirmationForm from utilities.paginator import EnhancedPaginator +from utilities.utils import shallow_compare_dict from utilities.views import BulkDeleteView, BulkEditView, ObjectDeleteView, ObjectEditView, ObjectListView from . import filters, forms from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult, Tag, TaggedItem @@ -207,8 +208,31 @@ class ObjectChangeView(PermissionRequiredMixin, View): orderable=False ) + objectchanges = ObjectChange.objects.filter( + changed_object_type=objectchange.changed_object_type, + changed_object_id=objectchange.changed_object_id, + ) + + next_change = objectchanges.filter(time__gt=objectchange.time).order_by('time').first() + prev_change = objectchanges.filter(time__lt=objectchange.time).order_by('-time').first() + + if prev_change: + diff_added = shallow_compare_dict( + prev_change.object_data, + objectchange.object_data, + exclude=['last_updated'], + ) + diff_removed = {x: prev_change.object_data.get(x) for x in diff_added} + else: + # No previous change; this is the initial change that added the object + diff_added = diff_removed = objectchange.object_data + return render(request, 'extras/objectchange.html', { 'objectchange': objectchange, + 'diff_added': diff_added, + 'diff_removed': diff_removed, + 'next_change': next_change, + 'prev_change': prev_change, 'related_changes_table': related_changes_table, 'related_changes_count': related_changes.count() }) diff --git a/netbox/templates/extras/objectchange.html b/netbox/templates/extras/objectchange.html index ee29281f9..35be29bbd 100644 --- a/netbox/templates/extras/objectchange.html +++ b/netbox/templates/extras/objectchange.html @@ -83,6 +83,35 @@
+
+
+ Difference + +
+
+ {% if diff_added == diff_removed %} + + {% if objectchange.action == 'create' %} + Object created + {% elif objectchange.action == 'delete' %} + Object deleted + {% else %} + No changes + {% endif %} + + {% else %} +
{{ diff_removed|render_json }}
+
{{ diff_added|render_json }}
+ {% endif %} +
+
diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 979f95af9..6969a60e9 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -222,3 +222,19 @@ def querydict_to_dict(querydict): key: querydict.get(key) if len(value) == 1 and key != 'pk' else querydict.getlist(key) for key, value in querydict.lists() } + + +def shallow_compare_dict(source_dict, destination_dict, exclude=None): + """ + Return a new dictionary of the different keys. The values of `destination_dict` are returned. Only the equality of + the first layer of keys/values is checked. `exclude` is a list or tuple of keys to be ignored. + """ + difference = {} + + for key in destination_dict: + if source_dict.get(key) != destination_dict[key]: + if isinstance(exclude, (list, tuple)) and key in exclude: + continue + difference[key] = destination_dict[key] + + return difference From 2b9e23df1a435d79b8acb92886917de4ba21bb87 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sat, 15 Feb 2020 23:55:03 +0000 Subject: [PATCH 34/67] Changelog for #2511 --- docs/release-notes/version-2.7.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index e224196ad..39f3a0ac2 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -2,6 +2,7 @@ ## Enhancements +* [#2511](https://github.com/netbox-community/netbox/issues/2511) - Compare object change to the previous change * [#3840](https://github.com/netbox-community/netbox/issues/3840) - Enhance search function when selecting VLANs for interface assignment * [#4170](https://github.com/netbox-community/netbox/issues/4170) - Improve color contrast in rack elevation drawings From 71d5f2ad2bb3ad48b06f5ab4e29c0e204723c2d3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 18 Feb 2020 10:50:14 -0500 Subject: [PATCH 35/67] Fixes #4183: Fix representation of NaturalOrderingField values in change log --- docs/release-notes/version-2.7.md | 1 + netbox/utilities/fields.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index e224196ad..f59aa72e4 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -10,6 +10,7 @@ * [#2519](https://github.com/netbox-community/netbox/issues/2519) - Avoid race condition when provisioning "next available" IPs/prefixes via the API * [#4168](https://github.com/netbox-community/netbox/issues/4168) - Role is not required when creating a virtual machine * [#4175](https://github.com/netbox-community/netbox/issues/4175) - Fix potential exception when bulk editing objects from a filtered list +* [#4183](https://github.com/netbox-community/netbox/issues/4183) - Fix representation of NaturalOrderingField values in change log --- diff --git a/netbox/utilities/fields.py b/netbox/utilities/fields.py index 6181a7ca1..4eb19f539 100644 --- a/netbox/utilities/fields.py +++ b/netbox/utilities/fields.py @@ -56,8 +56,11 @@ class NaturalOrderingField(models.CharField): """ Generate a naturalized value from the target field """ - value = getattr(model_instance, self.target_field) - return self.naturalize_function(value, max_length=self.max_length) + original_value = getattr(model_instance, self.target_field) + naturalized_value = self.naturalize_function(original_value, max_length=self.max_length) + setattr(model_instance, self.attname, naturalized_value) + + return naturalized_value def deconstruct(self): kwargs = super().deconstruct()[3] # Pass kwargs from CharField From 735560c0773b25195b4e69af2e8941bf95314721 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 18 Feb 2020 11:08:16 -0500 Subject: [PATCH 36/67] Fixes #4179: Site is required when creating a rack group or power panel --- docs/release-notes/version-2.7.md | 1 + netbox/dcim/forms.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index f59aa72e4..3d4306ac7 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -10,6 +10,7 @@ * [#2519](https://github.com/netbox-community/netbox/issues/2519) - Avoid race condition when provisioning "next available" IPs/prefixes via the API * [#4168](https://github.com/netbox-community/netbox/issues/4168) - Role is not required when creating a virtual machine * [#4175](https://github.com/netbox-community/netbox/issues/4175) - Fix potential exception when bulk editing objects from a filtered list +* [#4179](https://github.com/netbox-community/netbox/issues/4179) - Site is required when creating a rack group or power panel * [#4183](https://github.com/netbox-community/netbox/issues/4183) - Fix representation of NaturalOrderingField values in change log --- diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 4c8a0821f..c4b5d4503 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -385,7 +385,6 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): class RackGroupForm(BootstrapMixin, forms.ModelForm): site = DynamicModelChoiceField( queryset=Site.objects.all(), - required=False, widget=APISelect( api_url="/api/dcim/sites/" ) @@ -4522,7 +4521,6 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm): class PowerPanelForm(BootstrapMixin, forms.ModelForm): site = DynamicModelChoiceField( queryset=Site.objects.all(), - required=False, widget=APISelect( api_url="/api/dcim/sites/", filter_for={ From e5a4f94d352cb6113be5ebf7a2eb12bb548fef34 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 18 Feb 2020 11:14:37 -0500 Subject: [PATCH 37/67] Fixes #4194: Role field should not be required when searching/filtering secrets --- docs/release-notes/version-2.7.md | 1 + netbox/secrets/forms.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 3d4306ac7..c6985f9f2 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -12,6 +12,7 @@ * [#4175](https://github.com/netbox-community/netbox/issues/4175) - Fix potential exception when bulk editing objects from a filtered list * [#4179](https://github.com/netbox-community/netbox/issues/4179) - Site is required when creating a rack group or power panel * [#4183](https://github.com/netbox-community/netbox/issues/4183) - Fix representation of NaturalOrderingField values in change log +* [#4194](https://github.com/netbox-community/netbox/issues/4194) - Role field should not be required when searching/filtering secrets --- diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index 79064e0dd..88e5325ec 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -185,7 +185,7 @@ class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm): role = DynamicModelMultipleChoiceField( queryset=SecretRole.objects.all(), to_field_name='slug', - required=True, + required=False, widget=APISelectMultiple( api_url="/api/secrets/secret-roles/", value_field="slug", From 164c947f345afaad01436a2abca29780411b70bf Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 18 Feb 2020 11:43:47 -0500 Subject: [PATCH 38/67] Add helpful links to "new issue" page --- .github/ISSUE_TEMPLATE/config.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..ab7d7cdc4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +# Reference: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser +blank_issues_enabled: false +contact_links: + - name: 📖 Contributing Policy + url: https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md + about: Please read through our contributing policy before opening an issue or pull request + - name: 💬 Discussion Group + url: https://groups.google.com/forum/#!forum/netbox-discuss + about: Join our discussion group for assistance with installation issues and other problems From 55a6b19afbba1eaf8be36436fb27ed7acafc2394 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 18 Feb 2020 16:21:50 -0500 Subject: [PATCH 39/67] Fixes #4196: Fix exception when viewing LLDP neighbors page --- docs/release-notes/version-2.7.md | 1 + netbox/dcim/views.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index c6985f9f2..458328369 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -13,6 +13,7 @@ * [#4179](https://github.com/netbox-community/netbox/issues/4179) - Site is required when creating a rack group or power panel * [#4183](https://github.com/netbox-community/netbox/issues/4183) - Fix representation of NaturalOrderingField values in change log * [#4194](https://github.com/netbox-community/netbox/issues/4194) - Role field should not be required when searching/filtering secrets +* [#4196](https://github.com/netbox-community/netbox/issues/4196) - Fix exception when viewing LLDP neighbors page --- diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 0bb6658a2..91b32bc70 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -31,6 +31,7 @@ from utilities.views import ( from virtualization.models import VirtualMachine from . import filters, forms, tables from .choices import DeviceFaceChoices +from .constants import NONCONNECTABLE_IFACE_TYPES from .models import ( Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, @@ -1181,7 +1182,7 @@ class DeviceLLDPNeighborsView(PermissionRequiredMixin, View): def get(self, request, pk): device = get_object_or_404(Device, pk=pk) - interfaces = device.vc_interfaces.connectable().prefetch_related( + interfaces = device.vc_interfaces.exclude(type__in=NONCONNECTABLE_IFACE_TYPES).prefetch_related( '_connected_interface__device' ) From a92fb2be2f891936e65ecfd82af264a16f7e3bb3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 18 Feb 2020 16:22:17 -0500 Subject: [PATCH 40/67] Remove obsolete InterfaceManager --- netbox/dcim/managers.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 netbox/dcim/managers.py diff --git a/netbox/dcim/managers.py b/netbox/dcim/managers.py deleted file mode 100644 index 502719646..000000000 --- a/netbox/dcim/managers.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.db.models import Manager, QuerySet - -from .constants import NONCONNECTABLE_IFACE_TYPES - - -class InterfaceQuerySet(QuerySet): - - def connectable(self): - """ - Return only physical interfaces which are capable of being connected to other interfaces (i.e. not virtual or - wireless). - """ - return self.exclude(type__in=NONCONNECTABLE_IFACE_TYPES) - - -class InterfaceManager(Manager): - - def get_queryset(self): - return InterfaceQuerySet(self.model, using=self._db) From a4fd41ad3d0ccaa444ba3ade4da7120d8fc54063 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 18 Feb 2020 16:56:50 -0500 Subject: [PATCH 41/67] Fixes #3967: Fix missing migration for interface templates of type "other" --- docs/release-notes/version-2.7.md | 1 + .../0097_interfacetemplate_type_other.py | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 netbox/dcim/migrations/0097_interfacetemplate_type_other.py diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 458328369..02b3aa9bf 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -8,6 +8,7 @@ ## Bug Fixes * [#2519](https://github.com/netbox-community/netbox/issues/2519) - Avoid race condition when provisioning "next available" IPs/prefixes via the API +* [#3967](https://github.com/netbox-community/netbox/issues/3967) - Fix missing migration for interface templates of type "other" * [#4168](https://github.com/netbox-community/netbox/issues/4168) - Role is not required when creating a virtual machine * [#4175](https://github.com/netbox-community/netbox/issues/4175) - Fix potential exception when bulk editing objects from a filtered list * [#4179](https://github.com/netbox-community/netbox/issues/4179) - Site is required when creating a rack group or power panel diff --git a/netbox/dcim/migrations/0097_interfacetemplate_type_other.py b/netbox/dcim/migrations/0097_interfacetemplate_type_other.py new file mode 100644 index 000000000..d71b5c655 --- /dev/null +++ b/netbox/dcim/migrations/0097_interfacetemplate_type_other.py @@ -0,0 +1,20 @@ +from django.db import migrations + + +def interfacetemplate_type_to_slug(apps, schema_editor): + InterfaceTemplate = apps.get_model('dcim', 'InterfaceTemplate') + InterfaceTemplate.objects.filter(type=32767).update(type='other') + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0096_interface_ordering'), + ] + + operations = [ + # Missed type "other" in the initial migration (see #3967) + migrations.RunPython( + code=interfacetemplate_type_to_slug + ), + ] From 17ff39c68cbfd603c4e90d78094fd5ec8ad2176b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 19 Feb 2020 10:31:10 -0500 Subject: [PATCH 42/67] Closes #4199: Update example report to use ChoiceSet --- docs/additional-features/reports.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/additional-features/reports.md b/docs/additional-features/reports.md index fc1e89221..6deddc140 100644 --- a/docs/additional-features/reports.md +++ b/docs/additional-features/reports.md @@ -32,7 +32,8 @@ class DeviceIPsReport(Report): Within each report class, we'll create a number of test methods to execute our report's logic. In DeviceConnectionsReport, for instance, we want to ensure that every live device has a console connection, an out-of-band management connection, and two power connections. ``` -from dcim.constants import CONNECTION_STATUS_PLANNED, DEVICE_STATUS_ACTIVE +from dcim.choices import DeviceStatusChoices +from dcim.constants import CONNECTION_STATUS_PLANNED from dcim.models import ConsolePort, Device, PowerPort from extras.reports import Report @@ -43,7 +44,8 @@ class DeviceConnectionsReport(Report): def test_console_connection(self): # Check that every console port for every active device has a connection defined. - for console_port in ConsolePort.objects.prefetch_related('device').filter(device__status=DEVICE_STATUS_ACTIVE): + active = DeviceStatusChoices.STATUS_ACTIVE + for console_port in ConsolePort.objects.prefetch_related('device').filter(device__status=active): if console_port.connected_endpoint is None: self.log_failure( console_port.device, @@ -60,7 +62,7 @@ class DeviceConnectionsReport(Report): def test_power_connections(self): # Check that every active device has at least two connected power supplies. - for device in Device.objects.filter(status=DEVICE_STATUS_ACTIVE): + for device in Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE): connected_ports = 0 for power_port in PowerPort.objects.filter(device=device): if power_port.connected_endpoint is not None: From 922be0dc5c2569f9b88008a0a1a4e8e644b279c4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 19 Feb 2020 11:29:42 -0500 Subject: [PATCH 43/67] Fixes #4202: Prevent reassignment to master device when bulk editing VC member interfaces --- docs/release-notes/version-2.7.md | 1 + netbox/dcim/forms.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 02b3aa9bf..c9bfd2cae 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -15,6 +15,7 @@ * [#4183](https://github.com/netbox-community/netbox/issues/4183) - Fix representation of NaturalOrderingField values in change log * [#4194](https://github.com/netbox-community/netbox/issues/4194) - Role field should not be required when searching/filtering secrets * [#4196](https://github.com/netbox-community/netbox/issues/4196) - Fix exception when viewing LLDP neighbors page +* [#4202](https://github.com/netbox-community/netbox/issues/4202) - Prevent reassignment to master device when bulk editing VC member interfaces --- diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index c4b5d4503..9ec2c443e 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2763,6 +2763,7 @@ class PowerOutletBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): device = forms.ModelChoiceField( queryset=Device.objects.all(), required=False, + disabled=True, widget=forms.HiddenInput() ) type = forms.ChoiceField( @@ -3060,6 +3061,7 @@ class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): device = forms.ModelChoiceField( queryset=Device.objects.all(), required=False, + disabled=True, widget=forms.HiddenInput() ) type = forms.ChoiceField( From bc843250eda1e3fe89c044cfdbd7ba5eb51f479e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 19 Feb 2020 12:45:52 -0500 Subject: [PATCH 44/67] Standardize on two-word form of "change log" --- netbox/circuits/tables.py | 2 +- netbox/dcim/tables.py | 16 ++++++++-------- netbox/extras/tables.py | 2 +- netbox/ipam/tables.py | 6 +++--- netbox/secrets/tables.py | 2 +- netbox/templates/circuits/circuit.html | 2 +- netbox/templates/circuits/provider.html | 2 +- netbox/templates/dcim/cable.html | 2 +- netbox/templates/dcim/device.html | 2 +- netbox/templates/dcim/devicetype.html | 2 +- netbox/templates/dcim/interface.html | 2 +- netbox/templates/dcim/powerfeed.html | 2 +- netbox/templates/dcim/powerpanel.html | 2 +- netbox/templates/dcim/rack.html | 2 +- netbox/templates/dcim/site.html | 2 +- netbox/templates/extras/object_changelog.html | 4 ++-- netbox/templates/extras/objectchange.html | 2 +- netbox/templates/extras/tag.html | 2 +- netbox/templates/home.html | 2 +- netbox/templates/ipam/aggregate.html | 2 +- netbox/templates/ipam/inc/service.html | 2 +- netbox/templates/ipam/ipaddress.html | 2 +- netbox/templates/ipam/prefix.html | 2 +- netbox/templates/ipam/vlan.html | 2 +- netbox/templates/ipam/vrf.html | 2 +- netbox/templates/secrets/secret.html | 2 +- netbox/templates/tenancy/tenant.html | 2 +- netbox/templates/virtualization/cluster.html | 2 +- .../templates/virtualization/virtualmachine.html | 2 +- netbox/tenancy/tables.py | 2 +- netbox/virtualization/tables.py | 4 ++-- 31 files changed, 42 insertions(+), 42 deletions(-) diff --git a/netbox/circuits/tables.py b/netbox/circuits/tables.py index dbd9e6ba1..a425b3ace 100644 --- a/netbox/circuits/tables.py +++ b/netbox/circuits/tables.py @@ -6,7 +6,7 @@ from utilities.tables import BaseTable, ToggleColumn from .models import Circuit, CircuitType, Provider CIRCUITTYPE_ACTIONS = """ - + {% if perms.circuit.change_circuittype %} diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 1f67b93f1..1cc438f38 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -41,7 +41,7 @@ DEVICE_LINK = """ """ REGION_ACTIONS = """ - + {% if perms.dcim.change_region %} @@ -50,7 +50,7 @@ REGION_ACTIONS = """ """ RACKGROUP_ACTIONS = """ - + @@ -64,7 +64,7 @@ RACKGROUP_ACTIONS = """ """ RACKROLE_ACTIONS = """ - + {% if perms.dcim.change_rackrole %} @@ -86,7 +86,7 @@ RACK_DEVICE_COUNT = """ """ RACKRESERVATION_ACTIONS = """ - + {% if perms.dcim.change_rackreservation %} @@ -95,7 +95,7 @@ RACKRESERVATION_ACTIONS = """ """ MANUFACTURER_ACTIONS = """ - + {% if perms.dcim.change_manufacturer %} @@ -104,7 +104,7 @@ MANUFACTURER_ACTIONS = """ """ DEVICEROLE_ACTIONS = """ - + {% if perms.dcim.change_devicerole %} @@ -129,7 +129,7 @@ PLATFORM_VM_COUNT = """ """ PLATFORM_ACTIONS = """ - + {% if perms.dcim.change_platform %} @@ -166,7 +166,7 @@ UTILIZATION_GRAPH = """ """ VIRTUALCHASSIS_ACTIONS = """ - + {% if perms.dcim.change_virtualchassis %} diff --git a/netbox/extras/tables.py b/netbox/extras/tables.py index dca84dfdc..08c5ed471 100644 --- a/netbox/extras/tables.py +++ b/netbox/extras/tables.py @@ -5,7 +5,7 @@ from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn from .models import ConfigContext, ObjectChange, Tag, TaggedItem TAG_ACTIONS = """ - + {% if perms.taggit.change_tag %} diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index ad119a907..8f059c652 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -26,7 +26,7 @@ RIR_UTILIZATION = """ """ RIR_ACTIONS = """ - + {% if perms.ipam.change_rir %} @@ -48,7 +48,7 @@ ROLE_VLAN_COUNT = """ """ ROLE_ACTIONS = """ - + {% if perms.ipam.change_role %} @@ -145,7 +145,7 @@ VLAN_ROLE_LINK = """ """ VLANGROUP_ACTIONS = """ - + {% with next_vid=record.get_next_available_vid %} diff --git a/netbox/secrets/tables.py b/netbox/secrets/tables.py index cc1760b93..1e8a4e669 100644 --- a/netbox/secrets/tables.py +++ b/netbox/secrets/tables.py @@ -4,7 +4,7 @@ from utilities.tables import BaseTable, ToggleColumn from .models import SecretRole, Secret SECRETROLE_ACTIONS = """ - + {% if perms.secrets.change_secretrole %} diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html index 712ee5861..07d3c360d 100644 --- a/netbox/templates/circuits/circuit.html +++ b/netbox/templates/circuits/circuit.html @@ -49,7 +49,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/circuits/provider.html b/netbox/templates/circuits/provider.html index 5bf05b0e2..e99f8b2c4 100644 --- a/netbox/templates/circuits/provider.html +++ b/netbox/templates/circuits/provider.html @@ -54,7 +54,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/dcim/cable.html b/netbox/templates/dcim/cable.html index a190a7001..a78879b23 100644 --- a/netbox/templates/dcim/cable.html +++ b/netbox/templates/dcim/cable.html @@ -31,7 +31,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 5ede19d78..8c457121f 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -119,7 +119,7 @@ {% endif %} {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index 68384f12b..99f76ab35 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -54,7 +54,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index 6027afb58..4fc7d21ac 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -34,7 +34,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/dcim/powerfeed.html b/netbox/templates/dcim/powerfeed.html index 3ce1d36f2..1f6c6ab8e 100644 --- a/netbox/templates/dcim/powerfeed.html +++ b/netbox/templates/dcim/powerfeed.html @@ -52,7 +52,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/dcim/powerpanel.html b/netbox/templates/dcim/powerpanel.html index b0a6961f6..6d47e08b1 100644 --- a/netbox/templates/dcim/powerpanel.html +++ b/netbox/templates/dcim/powerpanel.html @@ -48,7 +48,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 51faeef50..067f4fbdb 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -53,7 +53,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index decfc9261..dfbf65aeb 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -60,7 +60,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/extras/object_changelog.html b/netbox/templates/extras/object_changelog.html index 5d649a692..970b54d4d 100644 --- a/netbox/templates/extras/object_changelog.html +++ b/netbox/templates/extras/object_changelog.html @@ -1,12 +1,12 @@ {% extends base_template %} -{% block title %}{% if obj %}{{ obj }}{% else %}{{ block.super }}{% endif %} - Changelog{% endblock %} +{% block title %}{% if obj %}{{ obj }}{% else %}{{ block.super }}{% endif %} - Change Log{% endblock %} {% block content %} {% if obj %}

{{ obj }}

{% endif %} {% include 'panel_table.html' %} {% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
- Changelog retention: {% if settings.CHANGELOG_RETENTION %}{{ settings.CHANGELOG_RETENTION }} days{% else %}Indefinite{% endif %} + Change log retention: {% if settings.CHANGELOG_RETENTION %}{{ settings.CHANGELOG_RETENTION }} days{% else %}Indefinite{% endif %}
{% endblock %} diff --git a/netbox/templates/extras/objectchange.html b/netbox/templates/extras/objectchange.html index 35be29bbd..16efa6421 100644 --- a/netbox/templates/extras/objectchange.html +++ b/netbox/templates/extras/objectchange.html @@ -7,7 +7,7 @@
- Changelog + Change Log
{% if changelog and perms.extras.view_objectchange %}
diff --git a/netbox/templates/ipam/aggregate.html b/netbox/templates/ipam/aggregate.html index 66281aace..590ed9208 100644 --- a/netbox/templates/ipam/aggregate.html +++ b/netbox/templates/ipam/aggregate.html @@ -48,7 +48,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/ipam/inc/service.html b/netbox/templates/ipam/inc/service.html index 6a3a23bde..9611be175 100644 --- a/netbox/templates/ipam/inc/service.html +++ b/netbox/templates/ipam/inc/service.html @@ -14,7 +14,7 @@ {{ service.description }} - + {% if perms.ipam.change_service %} diff --git a/netbox/templates/ipam/ipaddress.html b/netbox/templates/ipam/ipaddress.html index 50bd90610..08a311492 100644 --- a/netbox/templates/ipam/ipaddress.html +++ b/netbox/templates/ipam/ipaddress.html @@ -49,7 +49,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/ipam/prefix.html b/netbox/templates/ipam/prefix.html index 324bd927d..7bfa7ba1f 100644 --- a/netbox/templates/ipam/prefix.html +++ b/netbox/templates/ipam/prefix.html @@ -69,7 +69,7 @@ {% endif %} {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/ipam/vlan.html b/netbox/templates/ipam/vlan.html index 50b964992..246f3c866 100644 --- a/netbox/templates/ipam/vlan.html +++ b/netbox/templates/ipam/vlan.html @@ -55,7 +55,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/ipam/vrf.html b/netbox/templates/ipam/vrf.html index 242cfde92..7bb2dea25 100644 --- a/netbox/templates/ipam/vrf.html +++ b/netbox/templates/ipam/vrf.html @@ -46,7 +46,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/secrets/secret.html b/netbox/templates/secrets/secret.html index 438a5a943..6045897c9 100644 --- a/netbox/templates/secrets/secret.html +++ b/netbox/templates/secrets/secret.html @@ -34,7 +34,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html index 71c063502..9a1ed1be8 100644 --- a/netbox/templates/tenancy/tenant.html +++ b/netbox/templates/tenancy/tenant.html @@ -49,7 +49,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/virtualization/cluster.html b/netbox/templates/virtualization/cluster.html index 6b90a660d..92540fd70 100644 --- a/netbox/templates/virtualization/cluster.html +++ b/netbox/templates/virtualization/cluster.html @@ -49,7 +49,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 10c1f36d4..8f3bb61d4 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -54,7 +54,7 @@ {% endif %} {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/tenancy/tables.py b/netbox/tenancy/tables.py index 884bdc3df..af4fb34c0 100644 --- a/netbox/tenancy/tables.py +++ b/netbox/tenancy/tables.py @@ -4,7 +4,7 @@ from utilities.tables import BaseTable, ToggleColumn from .models import Tenant, TenantGroup TENANTGROUP_ACTIONS = """ - + {% if perms.tenancy.change_tenantgroup %} diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py index ba4554ff5..fdb997dab 100644 --- a/netbox/virtualization/tables.py +++ b/netbox/virtualization/tables.py @@ -7,7 +7,7 @@ from utilities.tables import BaseTable, ToggleColumn from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine CLUSTERTYPE_ACTIONS = """ - + {% if perms.virtualization.change_clustertype %} @@ -16,7 +16,7 @@ CLUSTERTYPE_ACTIONS = """ """ CLUSTERGROUP_ACTIONS = """ - + {% if perms.virtualization.change_clustergroup %} From 11f6faefc1a9488068a1674f9748d5f53a1c4280 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 19 Feb 2020 13:28:07 -0500 Subject: [PATCH 45/67] Fixes #4204: Fix assignment of mask length when bulk editing prefixes --- docs/release-notes/version-2.7.md | 1 + netbox/utilities/views.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 3a4e466e3..14fe69622 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -17,6 +17,7 @@ * [#4194](https://github.com/netbox-community/netbox/issues/4194) - Role field should not be required when searching/filtering secrets * [#4196](https://github.com/netbox-community/netbox/issues/4196) - Fix exception when viewing LLDP neighbors page * [#4202](https://github.com/netbox-community/netbox/issues/4202) - Prevent reassignment to master device when bulk editing VC member interfaces +* [#4204](https://github.com/netbox-community/netbox/issues/4204) - Fix assignment of mask length when bulk editing prefixes --- diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index d0257324e..19a6d655e 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -656,9 +656,8 @@ class BulkEditView(GetReturnURLMixin, View): try: model_field = model._meta.get_field(name) except FieldDoesNotExist: - # The form field is used to modify a field rather than set its value directly, - # so we skip it. - continue + # This form field is used to modify a field rather than set its value directly + model_field = None # Handle nullification if name in form.nullable_fields and name in nullified_fields: From 8d79c799070b46c286cd916eac7c2b849b89f789 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 19 Feb 2020 13:53:11 -0500 Subject: [PATCH 46/67] Closes #3810: Preserve slug value when editing existing objects --- docs/release-notes/version-2.7.md | 1 + netbox/project-static/js/forms.js | 14 ++++++++++---- netbox/utilities/forms.py | 10 +++++++++- netbox/utilities/templates/widgets/sluginput.html | 8 ++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 netbox/utilities/templates/widgets/sluginput.html diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 14fe69622..ef05bda8c 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -3,6 +3,7 @@ ## Enhancements * [#2511](https://github.com/netbox-community/netbox/issues/2511) - Compare object change to the previous change +* [#3810](https://github.com/netbox-community/netbox/issues/3810) - Preserve slug value when editing existing objects * [#3840](https://github.com/netbox-community/netbox/issues/3840) - Enhance search function when selecting VLANs for interface assignment * [#4170](https://github.com/netbox-community/netbox/issues/4170) - Improve color contrast in rack elevation drawings diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js index 802d1b4e9..7f71d28c0 100644 --- a/netbox/project-static/js/forms.js +++ b/netbox/project-static/js/forms.js @@ -42,17 +42,23 @@ $(document).ready(function() { return s.substring(0, num_chars); // Trim to first num_chars chars } var slug_field = $('#id_slug'); - slug_field.change(function() { - $(this).attr('_changed', true); - }); if (slug_field) { var slug_source = $('#id_' + slug_field.attr('slug-source')); var slug_length = slug_field.attr('maxlength'); + if (slug_field[0].value) { + slug_field.attr('_changed', true); + } + slug_field.change(function() { + $(this).attr('_changed', true); + }); slug_source.on('keyup change', function() { if (slug_field && !slug_field.attr('_changed')) { slug_field.val(slugify($(this).val(), (slug_length ? slug_length : 50))); } - }) + }); + $('button.reslugify').click(function() { + slug_field.val(slugify(slug_source.val(), (slug_length ? slug_length : 50))); + }); } // Bulk edit nullification diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index c629d70a2..a7ee63eaa 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -132,6 +132,13 @@ class SmallTextarea(forms.Textarea): pass +class SlugWidget(forms.TextInput): + """ + Subclass TextInput and add a slug regeneration button next to the form field. + """ + template_name = 'widgets/sluginput.html' + + class ColorSelect(forms.Select): """ Extends the built-in Select widget to colorize each
+
+
Rack Images
+
+ {% render_field form.front_image %} + {% render_field form.rear_image %} +
+
{% if form.custom_fields %}
Custom Fields
diff --git a/netbox/templates/dcim/inc/rack_elevation.html b/netbox/templates/dcim/inc/rack_elevation.html index b0fcf4908..feced6a22 100644 --- a/netbox/templates/dcim/inc/rack_elevation.html +++ b/netbox/templates/dcim/inc/rack_elevation.html @@ -1,7 +1,4 @@ {% load helpers %} -
- - - +
diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 067f4fbdb..a43b00f54 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -47,6 +47,11 @@
{% custom_links rack %}
+
+ +
+ {% include 'inc/custom_fields_panel.html' with obj=powerfeed %} + {% include 'extras/inc/tags_panel.html' with tags=powerfeed.tags.all url='dcim:powerfeed_list' %}
Comments From 0fc3d693e9cd0f7ab2c0f5731c01e5e192495cbf Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 20 Feb 2020 13:09:43 -0500 Subject: [PATCH 57/67] Add ability to toggle the inclusion of device images when rendering a rack elevation SVG --- netbox/dcim/api/serializers.py | 7 +++++++ netbox/dcim/api/views.py | 8 +++++++- netbox/dcim/elevations.py | 19 +++++++++++-------- netbox/dcim/models/__init__.py | 9 +++++---- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 234a9fb1c..3859ecfeb 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -186,6 +186,9 @@ class RackElevationDetailFilterSerializer(serializers.Serializer): unit_height = serializers.IntegerField( default=RACK_ELEVATION_UNIT_HEIGHT_DEFAULT ) + legend_width = serializers.IntegerField( + default=RACK_ELEVATION_LEGEND_WIDTH_DEFAULT + ) exclude = serializers.IntegerField( required=False, default=None @@ -194,6 +197,10 @@ class RackElevationDetailFilterSerializer(serializers.Serializer): required=False, default=True ) + include_images = serializers.BooleanField( + required=False, + default=True + ) # diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 8bb127f67..f8297fe46 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -220,7 +220,13 @@ class RackViewSet(CustomFieldModelViewSet): if data['render'] == 'svg': # Render and return the elevation as an SVG drawing with the correct content type - drawing = rack.get_elevation_svg(data['face'], data['unit_width'], data['unit_height']) + drawing = rack.get_elevation_svg( + face=data['face'], + unit_width=data['unit_width'], + unit_height=data['unit_height'], + legend_width=data['legend_width'], + include_images=data['include_images'] + ) return HttpResponse(drawing.tostring(), content_type='image/svg+xml') else: diff --git a/netbox/dcim/elevations.py b/netbox/dcim/elevations.py index e8b476b60..9c58134fd 100644 --- a/netbox/dcim/elevations.py +++ b/netbox/dcim/elevations.py @@ -11,9 +11,13 @@ from .choices import DeviceFaceChoices class RackElevationSVG: """ Use this class to render a rack elevation as an SVG image. + + :param rack: A NetBox Rack instance + :param include_images: If true, the SVG document will embed front/rear device face images, where available """ - def __init__(self, rack): + def __init__(self, rack, include_images=True): self.rack = rack + self.include_images = include_images @staticmethod def _add_gradient(drawing, id_, color): @@ -46,8 +50,7 @@ class RackElevationSVG: return drawing - @staticmethod - def _draw_device_front(drawing, device, start, end, text): + def _draw_device_front(self, drawing, device, start, end, text): name = str(device) if device.devicebay_count: name += ' ({}/{})'.format(device.get_children().count(), device.devicebay_count) @@ -69,14 +72,13 @@ class RackElevationSVG: link.add(drawing.text(str(name), insert=text, fill=hex_color)) # Embed front device type image if one exists - if device.device_type.front_image: + if self.include_images and device.device_type.front_image: url = device.device_type.front_image.url image = drawing.image(href=url, insert=start, size=end, class_='device-image') image.stretch() link.add(image) - @staticmethod - def _draw_device_rear(drawing, device, start, end, text): + def _draw_device_rear(self, drawing, device, start, end, text): rect = drawing.rect(start, end, class_="slot blocked") rect.set_desc('{} — {} ({}U) {} {}'.format( device.device_role, device.device_type.display_name, @@ -86,7 +88,7 @@ class RackElevationSVG: drawing.add(drawing.text(str(device), insert=text)) # Embed rear device type image if one exists - if device.device_type.front_image: + if self.include_images and device.device_type.front_image: url = device.device_type.rear_image.url image = drawing.image(href=url, insert=start, size=end, class_='device-image') image.stretch() @@ -128,11 +130,12 @@ class RackElevationSVG: return elevation - def render(self, reserved_units, face, unit_width, unit_height, legend_width): + def render(self, face, unit_width, unit_height, legend_width): """ Return an SVG document representing a rack elevation. """ drawing = self._setup_drawing(unit_width + legend_width, unit_height * self.rack.u_height) + reserved_units = self.rack.get_reserved_units() unit_cursor = 0 for ru in range(0, self.rack.u_height): diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index 25f2217b6..5848a6201 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -666,7 +666,8 @@ class Rack(ChangeLoggedModel, CustomFieldModel): face=DeviceFaceChoices.FACE_FRONT, unit_width=RACK_ELEVATION_UNIT_WIDTH_DEFAULT, unit_height=RACK_ELEVATION_UNIT_HEIGHT_DEFAULT, - legend_width=RACK_ELEVATION_LEGEND_WIDTH_DEFAULT + legend_width=RACK_ELEVATION_LEGEND_WIDTH_DEFAULT, + include_images=True ): """ Return an SVG of the rack elevation @@ -676,11 +677,11 @@ class Rack(ChangeLoggedModel, CustomFieldModel): :param unit_height: Height of each rack unit for the rendered drawing. Note this is not the total height of the elevation :param legend_width: Width of the unit legend, in pixels + :param include_images: Embed front/rear device images where available """ - elevation = RackElevationSVG(self) - reserved_units = self.get_reserved_units() + elevation = RackElevationSVG(self, include_images=include_images) - return elevation.render(reserved_units, face, unit_width, unit_height, legend_width) + return elevation.render(face, unit_width, unit_height, legend_width) def get_0u_devices(self): return self.devices.filter(position=0) From 581b29d185632210ed66db95c18bc7b7983e8993 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 20 Feb 2020 13:20:58 -0500 Subject: [PATCH 58/67] Enable toggling of device images on elevations list --- netbox/project-static/js/rack_elevations.js | 16 ++++++++++ netbox/templates/dcim/rack.html | 29 +++---------------- .../templates/dcim/rack_elevation_list.html | 10 +++---- 3 files changed, 25 insertions(+), 30 deletions(-) create mode 100644 netbox/project-static/js/rack_elevations.js diff --git a/netbox/project-static/js/rack_elevations.js b/netbox/project-static/js/rack_elevations.js new file mode 100644 index 000000000..138065e3c --- /dev/null +++ b/netbox/project-static/js/rack_elevations.js @@ -0,0 +1,16 @@ +// Toggle the display of device images within an SVG rack elevation +$('button.toggle-images').click(function() { + var selected = $(this).attr('selected'); + var rack_front = $("#rack_front"); + var rack_rear = $("#rack_rear"); + if (selected) { + $('.device-image', rack_front.contents()).addClass('hidden'); + $('.device-image', rack_rear.contents()).addClass('hidden'); + } else { + $('.device-image', rack_front.contents()).removeClass('hidden'); + $('.device-image', rack_rear.contents()).removeClass('hidden'); + } + $(this).attr('selected', !selected); + $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked'); + return false; +}); diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index a43b00f54..b43a4bfdf 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -2,6 +2,7 @@ {% load buttons %} {% load custom_links %} {% load helpers %} +{% load static %} {% block header %}
@@ -45,12 +46,10 @@

{% block title %}Rack {{ rack }}{% endblock %}

{% include 'inc/created_updated.html' with obj=rack %}
- {% custom_links rack %} -
-
- + {% custom_links rack %}
{% include 'inc/custom_fields_panel.html' with obj=powerfeed %} {% include 'extras/inc/tags_panel.html' with tags=powerfeed.tags.all url='dcim:powerfeed_list' %} -
-
- Comments -
-
- {% if powerfeed.comments %} - {{ powerfeed.comments|gfm }} - {% else %} - None - {% endif %} -
-
@@ -164,6 +152,18 @@
+
+
+ Comments +
+
+ {% if powerfeed.comments %} + {{ powerfeed.comments|gfm }} + {% else %} + None + {% endif %} +
+
{% endblock %} From ff3622e59770c04ec9dd689c1c9c7be6ba95f55e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 20 Feb 2020 14:17:18 -0500 Subject: [PATCH 63/67] Changelog for #4213 --- docs/release-notes/version-2.7.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 600b0ea3e..e1c6375de 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -28,6 +28,7 @@ python3 manage.py renaturalize dcim.Interface * [#4202](https://github.com/netbox-community/netbox/issues/4202) - Prevent reassignment to master device when bulk editing VC member interfaces * [#4204](https://github.com/netbox-community/netbox/issues/4204) - Fix assignment of mask length when bulk editing prefixes * [#4211](https://github.com/netbox-community/netbox/issues/4211) - Include trailing text when naturalizing interface names +* [#4213](https://github.com/netbox-community/netbox/issues/4213) - Restore display of tags and custom fields on power feed view --- From 684c3099e45700a1468ed7a3324a8b87309fba89 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 20 Feb 2020 14:24:22 -0500 Subject: [PATCH 64/67] Closes #4209: Enable filtering interfaces list view by enabled --- docs/release-notes/version-2.7.md | 1 + netbox/dcim/forms.py | 6 ++++++ netbox/dcim/tables.py | 5 +++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index e1c6375de..dfb25d75f 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -14,6 +14,7 @@ python3 manage.py renaturalize dcim.Interface * [#3810](https://github.com/netbox-community/netbox/issues/3810) - Preserve slug value when editing existing objects * [#3840](https://github.com/netbox-community/netbox/issues/3840) - Enhance search function when selecting VLANs for interface assignment * [#4170](https://github.com/netbox-community/netbox/issues/4170) - Improve color contrast in rack elevation drawings +* [#4209](https://github.com/netbox-community/netbox/issues/4209) - Enable filtering interfaces list view by enabled ## Bug Fixes diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 37aecbd53..0e5fb382a 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2821,6 +2821,12 @@ class PowerOutletBulkDisconnectForm(ConfirmationForm): class InterfaceFilterForm(DeviceComponentFilterForm): model = Interface + enabled = forms.NullBooleanField( + required=False, + widget=StaticSelect2( + choices=BOOLEAN_WITH_BLANK_CHOICES + ) + ) tag = TagFilterField(model) diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 1cc438f38..bc91dd70c 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -795,11 +795,12 @@ class InterfaceTable(BaseTable): class InterfaceDetailTable(DeviceComponentDetailTable): parent = tables.LinkColumn(order_by=('device', 'virtual_machine')) name = tables.LinkColumn() + enabled = BooleanColumn() class Meta(InterfaceTable.Meta): order_by = ('parent', 'name') - fields = ('pk', 'parent', 'name', 'type', 'description', 'cable') - sequence = ('pk', 'parent', 'name', 'type', 'description', 'cable') + fields = ('pk', 'parent', 'name', 'enabled', 'type', 'description', 'cable') + sequence = ('pk', 'parent', 'name', 'enabled', 'type', 'description', 'cable') class FrontPortTable(BaseTable): From 58864718556a1239e5961af1a824d4cea9eae8b1 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 20 Feb 2020 14:27:26 -0500 Subject: [PATCH 65/67] Changelog for #4206 --- docs/release-notes/version-2.7.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index dfb25d75f..aaa93b17b 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -14,6 +14,7 @@ python3 manage.py renaturalize dcim.Interface * [#3810](https://github.com/netbox-community/netbox/issues/3810) - Preserve slug value when editing existing objects * [#3840](https://github.com/netbox-community/netbox/issues/3840) - Enhance search function when selecting VLANs for interface assignment * [#4170](https://github.com/netbox-community/netbox/issues/4170) - Improve color contrast in rack elevation drawings +* [#4206](https://github.com/netbox-community/netbox/issues/4206) - Add RJ-11 console port type * [#4209](https://github.com/netbox-community/netbox/issues/4209) - Enable filtering interfaces list view by enabled ## Bug Fixes From ead34635f63bf69658a4ce0771f16288dd938d33 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 20 Feb 2020 14:37:08 -0500 Subject: [PATCH 66/67] #1529: Add front/rear image fields to DeviceType serializer --- netbox/dcim/api/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 3859ecfeb..34d9a7890 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -227,7 +227,8 @@ class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer): model = DeviceType fields = [ 'id', 'manufacturer', 'model', 'slug', 'display_name', 'part_number', 'u_height', 'is_full_depth', - 'subdevice_role', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', + 'subdevice_role', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields', 'created', + 'last_updated', 'device_count', ] From 1ab93b6a57a089277a909cc66c8783651c8951df Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 20 Feb 2020 14:48:23 -0500 Subject: [PATCH 67/67] Release v2.7.7 --- docs/release-notes/version-2.7.md | 2 +- netbox/netbox/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index aaa93b17b..95cdb70dd 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -1,4 +1,4 @@ -# v2.7.7 (FUTURE) +# v2.7.7 (2020-02-20) **Note:** This release fixes a bug affecting the natural ordering of interfaces. If any interfaces appear unordered in NetBox, run the following management command to recalculate their naturalized values after upgrading: diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 249ee9e53..6c33e5b54 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured # Environment setup # -VERSION = '2.7.7-dev' +VERSION = '2.7.7' # Hostname HOSTNAME = platform.node()