From 9d846d7b8729d70e86a106a15f751abc3136a856 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 15 Jan 2020 12:23:34 +0000 Subject: [PATCH 01/34] 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 201416ba526dad9d0fa003bd12d63727f439107b Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 15 Jan 2020 12:38:09 +0000 Subject: [PATCH 02/34] 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 c8997868cee94c7ca8319e10725851578b30f375 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 16 Jan 2020 15:10:25 +0000 Subject: [PATCH 03/34] 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 c31c8b1a2566b62b64ba0fad05d6341534aed365 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 16 Jan 2020 21:51:37 +0000 Subject: [PATCH 04/34] 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 ff822743cc1d4de33fe94a5cbb8700c97dfc63b3 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 30 Jan 2020 18:10:39 +0000 Subject: [PATCH 05/34] 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 ae95b159bc033787886bc5b65ccf65bb1ebe840c Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 30 Jan 2020 18:26:30 +0000 Subject: [PATCH 06/34] 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 ace8fac2c1232e5b93917d7c0c7afb24519c30be Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 30 Jan 2020 18:29:08 +0000 Subject: [PATCH 07/34] 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 26ddd96e303b95e1c7f5224f652007a9bdebc941 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sat, 8 Feb 2020 16:18:58 +0000 Subject: [PATCH 08/34] 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 2e83ce76eddc7f5d7cfb38430c70efc352b0b10e Mon Sep 17 00:00:00 2001 From: Matt Olenik Date: Fri, 13 Dec 2019 11:52:59 -0800 Subject: [PATCH 09/34] 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 c5f74cce805e7fb4a73745a41ddf1b8a5388e8f2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 13:13:27 -0500 Subject: [PATCH 10/34] 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 4ef15e4dc89b90ef6c5ac6d5a401c6683177b29a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 13:31:04 -0500 Subject: [PATCH 11/34] 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 fff657cd5a2649bbfa7d84a7e119ce444009cdfc Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 14:07:15 -0500 Subject: [PATCH 12/34] 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 8fd809ac5e9cfb36263293a624fb8f0a7656dd33 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 14:17:13 -0500 Subject: [PATCH 13/34] 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 a054aff3c431e44a4d2f395fd7cb06a4adb97b28 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 14:19:14 -0500 Subject: [PATCH 14/34] 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 88c917231de849646544d1ea76c801f893f681c3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 14:21:14 -0500 Subject: [PATCH 15/34] 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 6884404957913501dd16062e9181be9d54edb84c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 14:24:22 -0500 Subject: [PATCH 16/34] 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 ff952fb2210f92f8ff643c60ccca2266d830903b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 16:39:38 -0500 Subject: [PATCH 17/34] 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 8df9bb6fb47863a4f6376d0e5a29cd4b9ac84023 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 17:11:39 -0500 Subject: [PATCH 18/34] 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 8212c8f6fc596e5133e960cd8317cde85f8695d3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 17:22:17 -0500 Subject: [PATCH 19/34] 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 909323663eca94493a2eb64e8e08bcae58290be8 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 21:41:00 -0500 Subject: [PATCH 20/34] Fixes #4166: Fix schema migrations to enforce maximum character length for naturalized fields --- docs/release-notes/version-2.7.md | 8 ++++++++ .../dcim/migrations/0093_device_component_ordering.py | 2 +- .../0094_device_component_template_ordering.py | 2 +- netbox/dcim/migrations/0095_primary_model_ordering.py | 2 +- netbox/dcim/migrations/0096_interface_ordering.py | 2 +- netbox/extras/management/commands/renaturalize.py | 2 +- netbox/utilities/ordering.py | 8 ++++---- netbox/utilities/tests/test_ordering.py | 10 ++++++++-- 8 files changed, 25 insertions(+), 11 deletions(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 04b3972ca..75269b6ad 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -1,3 +1,11 @@ +# v2.7.6 (FUTURE) + +## Bug Fixes + +* [#4166](https://github.com/netbox-community/netbox/issues/4166) - Fix schema migrations to enforce maximum character length for naturalized fields + +--- + # v2.7.5 (2020-02-13) **Note:** This release includes several database schema migrations that calculate and store copies of names for certain objects to improve natural ordering performance (see [#3799](https://github.com/netbox-community/netbox/issues/3799)). These migrations may take a few minutes to run if you have a very large number of objects defined in NetBox. diff --git a/netbox/dcim/migrations/0093_device_component_ordering.py b/netbox/dcim/migrations/0093_device_component_ordering.py index 017241c8b..4e3c941a1 100644 --- a/netbox/dcim/migrations/0093_device_component_ordering.py +++ b/netbox/dcim/migrations/0093_device_component_ordering.py @@ -6,7 +6,7 @@ import utilities.ordering def _update_model_names(model): # Update each unique field value in bulk for name in model.objects.values_list('name', flat=True).order_by('name').distinct(): - model.objects.filter(name=name).update(_name=utilities.ordering.naturalize(name)) + model.objects.filter(name=name).update(_name=utilities.ordering.naturalize(name, max_length=100)) def naturalize_consoleports(apps, schema_editor): diff --git a/netbox/dcim/migrations/0094_device_component_template_ordering.py b/netbox/dcim/migrations/0094_device_component_template_ordering.py index fc39f76b2..24fe98e94 100644 --- a/netbox/dcim/migrations/0094_device_component_template_ordering.py +++ b/netbox/dcim/migrations/0094_device_component_template_ordering.py @@ -6,7 +6,7 @@ import utilities.ordering def _update_model_names(model): # Update each unique field value in bulk for name in model.objects.values_list('name', flat=True).order_by('name').distinct(): - model.objects.filter(name=name).update(_name=utilities.ordering.naturalize(name)) + model.objects.filter(name=name).update(_name=utilities.ordering.naturalize(name, max_length=100)) def naturalize_consoleporttemplates(apps, schema_editor): diff --git a/netbox/dcim/migrations/0095_primary_model_ordering.py b/netbox/dcim/migrations/0095_primary_model_ordering.py index 9cef0a581..3bc780161 100644 --- a/netbox/dcim/migrations/0095_primary_model_ordering.py +++ b/netbox/dcim/migrations/0095_primary_model_ordering.py @@ -6,7 +6,7 @@ import utilities.ordering def _update_model_names(model): # Update each unique field value in bulk for name in model.objects.values_list('name', flat=True).order_by('name').distinct(): - model.objects.filter(name=name).update(_name=utilities.ordering.naturalize(name)) + model.objects.filter(name=name).update(_name=utilities.ordering.naturalize(name, max_length=100)) def naturalize_sites(apps, schema_editor): diff --git a/netbox/dcim/migrations/0096_interface_ordering.py b/netbox/dcim/migrations/0096_interface_ordering.py index 284066462..f1622f504 100644 --- a/netbox/dcim/migrations/0096_interface_ordering.py +++ b/netbox/dcim/migrations/0096_interface_ordering.py @@ -6,7 +6,7 @@ import utilities.ordering def _update_model_names(model): # Update each unique field value in bulk for name in model.objects.values_list('name', flat=True).order_by('name').distinct(): - model.objects.filter(name=name).update(_name=utilities.ordering.naturalize_interface(name)) + model.objects.filter(name=name).update(_name=utilities.ordering.naturalize_interface(name, max_length=100)) def naturalize_interfacetemplates(apps, schema_editor): diff --git a/netbox/extras/management/commands/renaturalize.py b/netbox/extras/management/commands/renaturalize.py index 70f57c1ba..cfd037910 100644 --- a/netbox/extras/management/commands/renaturalize.py +++ b/netbox/extras/management/commands/renaturalize.py @@ -86,7 +86,7 @@ class Command(BaseCommand): # Find all unique values for the field queryset = model.objects.values_list(target_field, flat=True).order_by(target_field).distinct() for value in queryset: - naturalized_value = naturalize(value) + naturalized_value = naturalize(value, max_length=field.max_length) if options['verbosity'] >= 2: self.stdout.write(" {} -> {}".format(value, naturalized_value), ending='') diff --git a/netbox/utilities/ordering.py b/netbox/utilities/ordering.py index a560e776e..39e21733a 100644 --- a/netbox/utilities/ordering.py +++ b/netbox/utilities/ordering.py @@ -10,7 +10,7 @@ INTERFACE_NAME_REGEX = r'(^(?P[^\d\.:]+)?)' \ r'(.(?P\d+)$)?' -def naturalize(value, max_length=None, integer_places=8): +def naturalize(value, max_length, integer_places=8): """ Take an alphanumeric string and prepend all integers to `integer_places` places to ensure the strings are ordered naturally. For example: @@ -39,10 +39,10 @@ def naturalize(value, max_length=None, integer_places=8): output.append(segment) ret = ''.join(output) - return ret[:max_length] if max_length else ret + return ret[:max_length] -def naturalize_interface(value, max_length=None): +def naturalize_interface(value, max_length): """ Similar in nature to naturalize(), but takes into account a particular naming format adapted from the old InterfaceManager. @@ -77,4 +77,4 @@ def naturalize_interface(value, max_length=None): output.append('000000') ret = ''.join(output) - return ret[:max_length] if max_length else ret + return ret[:max_length] diff --git a/netbox/utilities/tests/test_ordering.py b/netbox/utilities/tests/test_ordering.py index a875c688c..958224c94 100644 --- a/netbox/utilities/tests/test_ordering.py +++ b/netbox/utilities/tests/test_ordering.py @@ -21,7 +21,10 @@ class NaturalizationTestCase(TestCase): ) for origin, naturalized in data: - self.assertEqual(naturalize(origin), naturalized) + self.assertEqual(naturalize(origin, max_length=50), naturalized) + + def test_naturalize_max_length(self): + self.assertEqual(naturalize('abc123def456', max_length=10), 'abc0000012') def test_naturalize_interface(self): @@ -40,4 +43,7 @@ class NaturalizationTestCase(TestCase): ) for origin, naturalized in data: - self.assertEqual(naturalize_interface(origin), naturalized) + self.assertEqual(naturalize_interface(origin, max_length=50), naturalized) + + def test_naturalize_interface_max_length(self): + self.assertEqual(naturalize_interface('Gi1/2/3', max_length=20), '0001000299999999Gi00') From 0863145c7fdc2d619a28019fa73d848be4d44681 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 21:46:03 -0500 Subject: [PATCH 21/34] Release v2.7.6 --- 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 75269b6ad..70981658f 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -1,4 +1,4 @@ -# v2.7.6 (FUTURE) +# v2.7.6 (2020-02-13) ## Bug Fixes diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index f66828f69..81422b714 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-dev' +VERSION = '2.7.6' # Hostname HOSTNAME = platform.node() From 598d23fc03b7e50530bc8cfae499050bba18039c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 13 Feb 2020 21:51:03 -0500 Subject: [PATCH 22/34] 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 1d72436bfc6d38809a52f7677cdf28de1d936cb9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 09:13:05 -0500 Subject: [PATCH 23/34] 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 a4705fa73a464a88ac8931a44f55e2378f434c97 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 09:35:43 -0500 Subject: [PATCH 24/34] 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 03a7f6bbda63e1fa638804376686f87b39f15d4a Mon Sep 17 00:00:00 2001 From: Dan Starner Date: Fri, 14 Feb 2020 09:39:01 -0500 Subject: [PATCH 25/34] 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 e431ef09e5b49f15fab28ec5332b88051a3ca035 Mon Sep 17 00:00:00 2001 From: Dan Starner Date: Fri, 14 Feb 2020 10:29:09 -0500 Subject: [PATCH 26/34] 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 ec0f45e20dffde593f157b789a922aca1c9fe64a Mon Sep 17 00:00:00 2001 From: Dan Starner Date: Fri, 14 Feb 2020 11:16:59 -0500 Subject: [PATCH 27/34] 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 7aba8e3ec48a03b867ae6a500a77588c6edc6a16 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 14 Feb 2020 16:43:42 +0000 Subject: [PATCH 28/34] 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 e4df02887b2f65b4a36b00396799e3afa342b224 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 12:04:35 -0500 Subject: [PATCH 29/34] 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 7ce1289bb2ad5e1f2d723a6e7eeb5ac315d85df7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 12:04:56 -0500 Subject: [PATCH 30/34] 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 ce89fa74b9797be6d7d5ed235065ed85f8bf90d6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 13:09:01 -0500 Subject: [PATCH 31/34] 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 815a46bfbec47d45994bc33bb80e54ef24b74025 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 13:21:32 -0500 Subject: [PATCH 32/34] 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 440f754fec01d10d8dfbb3c707a85245ea7053ea Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 13:30:53 -0500 Subject: [PATCH 33/34] 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 1a8eea5aa943f4f63b76ecadcf9ee7c4ab60e6e2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 14 Feb 2020 14:27:47 -0500 Subject: [PATCH 34/34] 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