From 7ed023a8fbb81a71a1334d91df1364c57f5f16af 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 f7e28de1d35817053c507b01b17d70f0dce7005a 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 a5425a740b842ec15c4dbe44ee452025e5159bf2 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 9ae8c7e0a67a488cbd738ad1c80b1e156902287d 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 375925488621a9ddd5be7a82acc7705deb20a81a 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 779fde55bb8cd834c9eb8378d8af3634f9927a30 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 1d9b66cc4c287375df18950219a3d31e1bdf2254 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 dccd0dcc9cf73b98b3e2a1034a11789d3956b944 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 7acda4fa3761472e972684124d3692c3cc6306ed 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 843b52145c8010d6b671a811090e21391241689c 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 5624a878eb4f8205f71a375acc777aac55242498 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 b586f81e57672226cf082eae15fdf98eb331459a 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 137629b631d9de196ac1f122b64e9c8e8e4b209f 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 1d15f7432224585d5315fe68b314a925c09134fd 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 75a2ade6feba1b1de317585f18699695be30e0fd 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 490bad12d18cdbcc847a08fb8d227339e64af82f 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 ebda44388834039b6325b94be7d63d90df0d839b 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 54bd9fa48afd433188f41951abc4e8003e9da0c6 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 a7dc6a26e82103635f88fcfe9fac097d808bfa92 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 5cd25b2721ec34f674767acb40807b36a6aefaed 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 ea21fddda6f8a2d4196b6496758d173c4b09d2ee 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 2b1d786fe21e1d1e1b4b2cb945a5144c76102e8e 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 98a59b91169bad27ee43ad8f086ace012cd5a882 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 2c27cc64dfb6f4df732ffb668b16774389066d18 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 c054240a69d8775ebc6941cd116cc41aa56d39ea 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 93cc95076c556ab401bd5d5df8030271fad7ce7f 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 574b00351897a917621227cd36c215d509443372 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 ae73c5f051b8f0f06b1b4a8aeaec3d33a8799838 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 175ce21da9cf83c25cc9127134fe0f43390f7baa 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 e870c4853ab6e8174af2204ff9577f7009ae348c 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 9a6a1e99666fb2f18cd6c521edadc7e946705a19 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 0c6cfd6255a00f7c01db3cee1492ea28c7f738a3 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 af046ecb0e9afb1548dcf5a5972ddbe786afd6a4 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 3612057fc1f7a4e39a9e76f76f8f5c0e5f244569 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