From 29a5fb041ff6b1b82d23f110b5008f84b3f954d2 Mon Sep 17 00:00:00 2001 From: Craig Pund Date: Mon, 13 Jun 2022 17:04:25 -0400 Subject: [PATCH 01/34] add fields for virtual chassis to device_edit form --- netbox/templates/dcim/device_edit.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/netbox/templates/dcim/device_edit.html b/netbox/templates/dcim/device_edit.html index 7cbb224c9..38125e83c 100644 --- a/netbox/templates/dcim/device_edit.html +++ b/netbox/templates/dcim/device_edit.html @@ -86,6 +86,15 @@ {% render_field form.tenant %} +
+
+
Virtual Chassis
+
+ {% render_field form.virtual_chassis %} + {% render_field form.vc_position %} + {% render_field form.vc_priority %} +
+ {% if form.custom_fields %}
From 6876c9878e1e0965b61a341fd63083fc514c4540 Mon Sep 17 00:00:00 2001 From: Craig Pund Date: Mon, 13 Jun 2022 17:06:08 -0400 Subject: [PATCH 02/34] added field definitions for device form --- netbox/dcim/forms/models.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index 179893219..2c905cc5c 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -521,13 +521,26 @@ class DeviceForm(TenancyForm, NetBoxModelForm): required=False, label='' ) + virtual_chassis = DynamicModelChoiceField( + queryset=VirtualChassis.objects.all(), + required=False + ) + vc_position = forms.IntegerField( + required=False, + help_text="The position in the virtual chassis this device is identified by" + ) + vc_priority = forms.IntegerField( + required=False, + help_text="The priority of the device in the virtual chassis" + ) class Meta: model = Device fields = [ 'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'rack', 'location', 'position', 'face', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', - 'cluster_group', 'cluster', 'tenant_group', 'tenant', 'comments', 'tags', 'local_context_data' + 'cluster_group', 'cluster', 'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', + 'comments', 'tags', 'local_context_data' ] help_texts = { 'device_role': "The function this device serves", From 575e2c443bf19e427c1f229f97eaa8a16d141e61 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 20 Jun 2022 11:38:49 -0400 Subject: [PATCH 03/34] PRVB --- docs/release-notes/version-3.2.md | 6 +++++- netbox/netbox/settings.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index bb5182702..059fc8924 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -1,5 +1,9 @@ # NetBox v3.2 +## v3.2.6 (FUTURE) + +--- + ## v3.2.5 (2022-06-20) ### Enhancements @@ -25,7 +29,7 @@ * [#9484](https://github.com/netbox-community/netbox/issues/9484) - Include services listening on "all IPs" under IP address view * [#9486](https://github.com/netbox-community/netbox/issues/9486) - Fix redirect URL when adding device components from the module view * [#9495](https://github.com/netbox-community/netbox/issues/9495) - Correct link to contacts in contact groups table column -* [#9503](https://github.com/netbox-community/netbox/issues/9503) - Hyperlinks in ack elevation SVGs must always use absolute URLs +* [#9503](https://github.com/netbox-community/netbox/issues/9503) - Hyperlinks in rack elevation SVGs must always use absolute URLs * [#9512](https://github.com/netbox-community/netbox/issues/9512) - Fix duplicate site results when searching by ASN * [#9524](https://github.com/netbox-community/netbox/issues/9524) - Correct order of VLAN fields under VM interface creation form * [#9537](https://github.com/netbox-community/netbox/issues/9537) - Ensure consistent use of placeholder tag throughout UI diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index f86f3a8f2..b2e1eca6c 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -29,7 +29,7 @@ django.utils.encoding.force_text = force_str # Environment setup # -VERSION = '3.2.5' +VERSION = '3.2.6-dev' # Hostname HOSTNAME = platform.node() From 52178f78d1b22478aeee567dddeab511c8cc2e3a Mon Sep 17 00:00:00 2001 From: Hunter Johnston Date: Tue, 21 Jun 2022 12:58:41 -0400 Subject: [PATCH 04/34] Closes #7702: Add Powerfeed Defaults to User Configurations --- netbox/dcim/urls.py | 2 +- netbox/dcim/views.py | 12 ++++++++++++ netbox/extras/admin.py | 3 +++ netbox/netbox/config/parameters.py | 25 +++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index c5cd0fa65..82ea3fec0 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -489,7 +489,7 @@ urlpatterns = [ # Power feeds path('power-feeds/', views.PowerFeedListView.as_view(), name='powerfeed_list'), - path('power-feeds/add/', views.PowerFeedEditView.as_view(), name='powerfeed_add'), + path('power-feeds/add/', views.PowerFeedCreateView.as_view(), name='powerfeed_add'), path('power-feeds/import/', views.PowerFeedBulkImportView.as_view(), name='powerfeed_import'), path('power-feeds/edit/', views.PowerFeedBulkEditView.as_view(), name='powerfeed_bulk_edit'), path('power-feeds/disconnect/', views.PowerFeedBulkDisconnectView.as_view(), name='powerfeed_bulk_disconnect'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 35a1056b2..0716f595e 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -16,6 +16,7 @@ from circuits.models import Circuit from extras.views import ObjectConfigContextView from ipam.models import ASN, IPAddress, Prefix, Service, VLAN, VLANGroup from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable +from netbox.config import ConfigItem from netbox.views import generic from utilities.forms import ConfirmationForm from utilities.paginator import EnhancedPaginator, get_paginate_count @@ -3253,6 +3254,17 @@ class PowerFeedView(generic.ObjectView): queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack') +class PowerFeedCreateView(generic.ObjectEditView): + queryset = PowerFeed.objects.all() + form = forms.PowerFeedForm + + def alter_object(self, obj, request, args, kwargs): + obj.voltage = ConfigItem('POWERFEED_DEFAULT_VOLTAGE') + obj.amperage = ConfigItem('POWERFEED_DEFAULT_AMPERAGE') + obj.max_utilization = ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION') + return obj + + class PowerFeedEditView(generic.ObjectEditView): queryset = PowerFeed.objects.all() form = forms.PowerFeedForm diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index 28902c323..01011b276 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -15,6 +15,9 @@ class ConfigRevisionAdmin(admin.ModelAdmin): ('Rack Elevations', { 'fields': ('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH'), }), + ('Power', { + 'fields': ('POWERFEED_DEFAULT_VOLTAGE', 'POWERFEED_DEFAULT_AMPERAGE', 'POWERFEED_DEFAULT_MAX_UTILIZATION') + }), ('IPAM', { 'fields': ('ENFORCE_GLOBAL_UNIQUE', 'PREFER_IPV4'), }), diff --git a/netbox/netbox/config/parameters.py b/netbox/netbox/config/parameters.py index 68c96b38a..e2295888f 100644 --- a/netbox/netbox/config/parameters.py +++ b/netbox/netbox/config/parameters.py @@ -82,6 +82,31 @@ PARAMS = ( field=forms.IntegerField ), + # Power + ConfigParam( + name='POWERFEED_DEFAULT_VOLTAGE', + label='Powerfeed voltage', + default=120, + description="Default voltage for powerfeeds", + field=forms.IntegerField + ), + + ConfigParam( + name='POWERFEED_DEFAULT_AMPERAGE', + label='Powerfeed amperage', + default=15, + description="Default amperage for powerfeeds", + field=forms.IntegerField + ), + + ConfigParam( + name='POWERFEED_DEFAULT_MAX_UTILIZATION', + label='Powerfeed max utilization', + default=80, + description="Default max utilization for powerfeeds", + field=forms.IntegerField + ), + # Security ConfigParam( name='ALLOWED_URL_SCHEMES', From 6cb8b9110ebf2d6ff5a41485ad7a1c29cb89008c Mon Sep 17 00:00:00 2001 From: Hunter Johnston <64506580+huntabyte@users.noreply.github.com> Date: Thu, 23 Jun 2022 13:28:36 -0400 Subject: [PATCH 05/34] Closes #9396: Query modules by module bay & display installed_modules for module_bay REST API endpoint (#9574) * Closes #9396 - Added ability to query modules by module bay & installed_modules for module bay REST API endpoint * Closes #9396 - Added ability to query modules by module bay & installed_modules for module bay REST API endpoint * Closes #9396 - Added ability to query modules by module bay & installed_modules for module bay REST API endpoint --- netbox/dcim/api/nested_serializers.py | 9 +++++++++ netbox/dcim/api/serializers.py | 4 ++-- netbox/dcim/api/views.py | 2 +- netbox/dcim/filtersets.py | 6 ++++++ netbox/dcim/tests/test_filtersets.py | 5 +++++ 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/api/nested_serializers.py b/netbox/dcim/api/nested_serializers.py index 0ec0e07e0..1be8bb9dc 100644 --- a/netbox/dcim/api/nested_serializers.py +++ b/netbox/dcim/api/nested_serializers.py @@ -5,6 +5,7 @@ from netbox.api.serializers import BaseModelSerializer, WritableNestedSerializer __all__ = [ 'ComponentNestedModuleSerializer', + 'ModuleBayNestedModuleSerializer', 'NestedCableSerializer', 'NestedConsolePortSerializer', 'NestedConsolePortTemplateSerializer', @@ -281,6 +282,14 @@ class ModuleNestedModuleBaySerializer(WritableNestedSerializer): fields = ['id', 'url', 'display', 'name'] +class ModuleBayNestedModuleSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail') + + class Meta: + model = models.Module + fields = ['id', 'url', 'display', 'serial'] + + class ComponentNestedModuleSerializer(WritableNestedSerializer): """ Used by device component serializers. diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 7fcab6ba3..c2cb846a9 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -886,12 +886,12 @@ class FrontPortSerializer(NetBoxModelSerializer, LinkTerminationSerializer): class ModuleBaySerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebay-detail') device = NestedDeviceSerializer() - # installed_module = NestedModuleSerializer(required=False, allow_null=True) + installed_module = ModuleBayNestedModuleSerializer(required=False, allow_null=True) class Meta: model = ModuleBay fields = [ - 'id', 'url', 'display', 'device', 'name', 'label', 'position', 'description', 'tags', 'custom_fields', + 'id', 'url', 'display', 'device', 'name', 'installed_module', 'label', 'position', 'description', 'tags', 'custom_fields', 'created', 'last_updated', ] diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index c4c25f654..3fa652a9b 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -611,7 +611,7 @@ class RearPortViewSet(PassThroughPortMixin, NetBoxModelViewSet): class ModuleBayViewSet(NetBoxModelViewSet): - queryset = ModuleBay.objects.prefetch_related('tags') + queryset = ModuleBay.objects.prefetch_related('tags', 'installed_module') serializer_class = serializers.ModuleBaySerializer filterset_class = filtersets.ModuleBayFilterSet brief_prefetch_fields = ['device'] diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index f052a8be9..4b4201578 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -992,6 +992,12 @@ class ModuleFilterSet(NetBoxModelFilterSet): to_field_name='model', label='Module type (model)', ) + module_bay_id = django_filters.ModelMultipleChoiceFilter( + field_name='module_bay', + queryset=ModuleBay.objects.all(), + to_field_name='id', + label='Module Bay (ID)' + ) device_id = django_filters.ModelMultipleChoiceFilter( queryset=Device.objects.all(), label='Device (ID)', diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 273ee6570..47aa9368c 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -1849,6 +1849,11 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'module_type': [module_types[0].model, module_types[1].model]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) + def test_module_bay(self): + module_bays = ModuleBay.objects.all()[:2] + params = {'module_bay_id': [module_bays[0].pk, module_bays[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_device(self): device_types = Device.objects.all()[:2] params = {'device_id': [device_types[0].pk, device_types[1].pk]} From afec53cea3873dac9ea970bf70431bde0a399a66 Mon Sep 17 00:00:00 2001 From: Hunter Johnston <64506580+huntabyte@users.noreply.github.com> Date: Thu, 23 Jun 2022 14:11:59 -0400 Subject: [PATCH 06/34] Fixes #9575: Add exception handling to services (#9586) --- netbox/ipam/views.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index a01f2d052..6bcdc4c64 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -680,13 +680,16 @@ class IPAddressView(generic.ObjectView): service_filter = Q(ipaddresses=instance) # Find services listening on all IPs on the assigned device/vm - if instance.assigned_object and instance.assigned_object.parent_object: - parent_object = instance.assigned_object.parent_object + try: + if instance.assigned_object and instance.assigned_object.parent_object: + parent_object = instance.assigned_object.parent_object - if isinstance(parent_object, VirtualMachine): - service_filter |= (Q(virtual_machine=parent_object) & Q(ipaddresses=None)) - elif isinstance(parent_object, Device): - service_filter |= (Q(device=parent_object) & Q(ipaddresses=None)) + if isinstance(parent_object, VirtualMachine): + service_filter |= (Q(virtual_machine=parent_object) & Q(ipaddresses=None)) + elif isinstance(parent_object, Device): + service_filter |= (Q(device=parent_object) & Q(ipaddresses=None)) + except AttributeError: + pass services = Service.objects.restrict(request.user, 'view').filter(service_filter) From d55e3c352a3e8c3ac7d2d3639b85a509272f9d0e Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 23 Jun 2022 14:14:02 -0400 Subject: [PATCH 07/34] Changelog for #9396, #9575, #9597 --- docs/release-notes/version-3.2.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 059fc8924..c87200147 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -2,6 +2,15 @@ ## v3.2.6 (FUTURE) +### Enhancements + +* [#9396](https://github.com/netbox-community/netbox/issues/9396) - Allow filtering modules by bay ID + +### Bug Fixes + +* [#9575](https://github.com/netbox-community/netbox/issues/9575) - Fix AttributeError exception for FHRP group with an IP address assigned +* [#9597](https://github.com/netbox-community/netbox/issues/9597) - Include `installed_module` in module bay REST API serializer + --- ## v3.2.5 (2022-06-20) From db807ab4a6f5d0ff9ff84ae39b5f18688bb19a1a Mon Sep 17 00:00:00 2001 From: Hunter Johnston <64506580+huntabyte@users.noreply.github.com> Date: Thu, 23 Jun 2022 14:16:09 -0400 Subject: [PATCH 08/34] Closes #7702: Add power feed defaults to user configurations --- netbox/dcim/models/power.py | 7 ++++--- netbox/dcim/urls.py | 2 +- netbox/dcim/views.py | 12 ------------ 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/netbox/dcim/models/power.py b/netbox/dcim/models/power.py index 08f89e3b0..5316951c6 100644 --- a/netbox/dcim/models/power.py +++ b/netbox/dcim/models/power.py @@ -6,6 +6,7 @@ from django.urls import reverse from dcim.choices import * from dcim.constants import * +from netbox.config import ConfigItem from netbox.models import NetBoxModel from utilities.validators import ExclusionValidator from .device_components import LinkTermination, PathEndpoint @@ -105,16 +106,16 @@ class PowerFeed(NetBoxModel, PathEndpoint, LinkTermination): default=PowerFeedPhaseChoices.PHASE_SINGLE ) voltage = models.SmallIntegerField( - default=POWERFEED_VOLTAGE_DEFAULT, + default=ConfigItem('POWERFEED_DEFAULT_VOLTAGE') validators=[ExclusionValidator([0])] ) amperage = models.PositiveSmallIntegerField( validators=[MinValueValidator(1)], - default=POWERFEED_AMPERAGE_DEFAULT + default=ConfigItem('POWERFEED_DEFAULT_AMPERAGE') ) max_utilization = models.PositiveSmallIntegerField( validators=[MinValueValidator(1), MaxValueValidator(100)], - default=POWERFEED_MAX_UTILIZATION_DEFAULT, + default=ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION') help_text="Maximum permissible draw (percentage)" ) available_power = models.PositiveIntegerField( diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 82ea3fec0..c5cd0fa65 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -489,7 +489,7 @@ urlpatterns = [ # Power feeds path('power-feeds/', views.PowerFeedListView.as_view(), name='powerfeed_list'), - path('power-feeds/add/', views.PowerFeedCreateView.as_view(), name='powerfeed_add'), + path('power-feeds/add/', views.PowerFeedEditView.as_view(), name='powerfeed_add'), path('power-feeds/import/', views.PowerFeedBulkImportView.as_view(), name='powerfeed_import'), path('power-feeds/edit/', views.PowerFeedBulkEditView.as_view(), name='powerfeed_bulk_edit'), path('power-feeds/disconnect/', views.PowerFeedBulkDisconnectView.as_view(), name='powerfeed_bulk_disconnect'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 0716f595e..35a1056b2 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -16,7 +16,6 @@ from circuits.models import Circuit from extras.views import ObjectConfigContextView from ipam.models import ASN, IPAddress, Prefix, Service, VLAN, VLANGroup from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable -from netbox.config import ConfigItem from netbox.views import generic from utilities.forms import ConfirmationForm from utilities.paginator import EnhancedPaginator, get_paginate_count @@ -3254,17 +3253,6 @@ class PowerFeedView(generic.ObjectView): queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack') -class PowerFeedCreateView(generic.ObjectEditView): - queryset = PowerFeed.objects.all() - form = forms.PowerFeedForm - - def alter_object(self, obj, request, args, kwargs): - obj.voltage = ConfigItem('POWERFEED_DEFAULT_VOLTAGE') - obj.amperage = ConfigItem('POWERFEED_DEFAULT_AMPERAGE') - obj.max_utilization = ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION') - return obj - - class PowerFeedEditView(generic.ObjectEditView): queryset = PowerFeed.objects.all() form = forms.PowerFeedForm From c330282919f7903315abc0263c9d3f5d22321fa2 Mon Sep 17 00:00:00 2001 From: Hunter Johnston <64506580+huntabyte@users.noreply.github.com> Date: Thu, 23 Jun 2022 14:56:24 -0400 Subject: [PATCH 09/34] Fix syntax error --- netbox/dcim/models/power.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/models/power.py b/netbox/dcim/models/power.py index 5316951c6..5978d86bd 100644 --- a/netbox/dcim/models/power.py +++ b/netbox/dcim/models/power.py @@ -106,7 +106,7 @@ class PowerFeed(NetBoxModel, PathEndpoint, LinkTermination): default=PowerFeedPhaseChoices.PHASE_SINGLE ) voltage = models.SmallIntegerField( - default=ConfigItem('POWERFEED_DEFAULT_VOLTAGE') + default=ConfigItem('POWERFEED_DEFAULT_VOLTAGE'), validators=[ExclusionValidator([0])] ) amperage = models.PositiveSmallIntegerField( @@ -115,7 +115,7 @@ class PowerFeed(NetBoxModel, PathEndpoint, LinkTermination): ) max_utilization = models.PositiveSmallIntegerField( validators=[MinValueValidator(1), MaxValueValidator(100)], - default=ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION') + default=ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION'), help_text="Maximum permissible draw (percentage)" ) available_power = models.PositiveIntegerField( From b77013c859d91f5bbd4da64eb28094886280d70f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 23 Jun 2022 17:26:20 -0400 Subject: [PATCH 10/34] Docs & cleanup for #7702 --- docs/configuration/dynamic-settings.md | 48 +++++++++++++++++++------- docs/release-notes/version-3.2.md | 1 + netbox/dcim/constants.py | 9 ----- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/docs/configuration/dynamic-settings.md b/docs/configuration/dynamic-settings.md index 2fa046fcf..d376dc5c4 100644 --- a/docs/configuration/dynamic-settings.md +++ b/docs/configuration/dynamic-settings.md @@ -43,18 +43,6 @@ changes in the database indefinitely. --- -## JOBRESULT_RETENTION - -Default: 90 - -The number of days to retain job results (scripts and reports). Set this to `0` to retain -job results in the database indefinitely. - -!!! warning - If enabling indefinite job results retention, it is recommended to periodically delete old entries. Otherwise, the database may eventually exceed capacity. - ---- - ## CUSTOM_VALIDATORS This is a mapping of models to [custom validators](../customization/custom-validation.md) that have been defined locally to enforce custom validation logic. An example is provided below: @@ -110,6 +98,18 @@ Setting this to False will disable the GraphQL API. --- +## JOBRESULT_RETENTION + +Default: 90 + +The number of days to retain job results (scripts and reports). Set this to `0` to retain +job results in the database indefinitely. + +!!! warning + If enabling indefinite job results retention, it is recommended to periodically delete old entries. Otherwise, the database may eventually exceed capacity. + +--- + ## MAINTENANCE_MODE Default: False @@ -185,6 +185,30 @@ The default maximum number of objects to display per page within each list of ob --- +## POWERFEED_DEFAULT_AMPERAGE + +Default: 15 + +The default value for the `amperage` field when creating new power feeds. + +--- + +## POWERFEED_DEFAULT_MAX_UTILIZATION + +Default: 80 + +The default value (percentage) for the `max_utilization` field when creating new power feeds. + +--- + +## POWERFEED_DEFAULT_VOLTAGE + +Default: 120 + +The default value for the `voltage` field when creating new power feeds. + +--- + ## PREFER_IPV4 Default: False diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index c87200147..719ceb7e1 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -4,6 +4,7 @@ ### Enhancements +* [#7702](https://github.com/netbox-community/netbox/issues/7702) - Enable dynamic configuration for default powerfeed attributes * [#9396](https://github.com/netbox-community/netbox/issues/9396) - Allow filtering modules by bay ID ### Bug Fixes diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index 38bf16f0b..155f19c88 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -49,15 +49,6 @@ WIRELESS_IFACE_TYPES = [ NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES -# -# Power feeds -# - -POWERFEED_VOLTAGE_DEFAULT = 120 -POWERFEED_AMPERAGE_DEFAULT = 20 -POWERFEED_MAX_UTILIZATION_DEFAULT = 80 # Percentage - - # # Device components # From 4315c4697cc3ade4901c0a9bb9bef6720027252e Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 23 Jun 2022 17:44:19 -0400 Subject: [PATCH 11/34] Ignore default field values which reference ConfigItems when calculating migrations --- netbox/dcim/migrations/0001_squashed.py | 6 +++--- netbox/utilities/management/commands/__init__.py | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/migrations/0001_squashed.py b/netbox/dcim/migrations/0001_squashed.py index bb99d199f..374d3bf45 100644 --- a/netbox/dcim/migrations/0001_squashed.py +++ b/netbox/dcim/migrations/0001_squashed.py @@ -386,9 +386,9 @@ class Migration(migrations.Migration): ('type', models.CharField(default='primary', max_length=50)), ('supply', models.CharField(default='ac', max_length=50)), ('phase', models.CharField(default='single-phase', max_length=50)), - ('voltage', models.SmallIntegerField(default=120, validators=[utilities.validators.ExclusionValidator([0])])), - ('amperage', models.PositiveSmallIntegerField(default=20, validators=[django.core.validators.MinValueValidator(1)])), - ('max_utilization', models.PositiveSmallIntegerField(default=80, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])), + ('voltage', models.SmallIntegerField(validators=[utilities.validators.ExclusionValidator([0])])), + ('amperage', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(1)])), + ('max_utilization', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])), ('available_power', models.PositiveIntegerField(default=0, editable=False)), ('comments', models.TextField(blank=True)), ], diff --git a/netbox/utilities/management/commands/__init__.py b/netbox/utilities/management/commands/__init__.py index bdd4face6..2c261b0d3 100644 --- a/netbox/utilities/management/commands/__init__.py +++ b/netbox/utilities/management/commands/__init__.py @@ -1,6 +1,8 @@ from django.db import models from timezone_field import TimeZoneField +from netbox.config import ConfigItem + SKIP_FIELDS = ( TimeZoneField, @@ -26,4 +28,9 @@ def custom_deconstruct(field): for attr in EXEMPT_ATTRS: kwargs.pop(attr, None) + # Ignore any field defaults which reference a ConfigItem + kwargs = { + k: v for k, v in kwargs.items() if not isinstance(v, ConfigItem) + } + return name, path, args, kwargs From d8b40056b52d6c12c1e37160f0e86f0730658d89 Mon Sep 17 00:00:00 2001 From: Hunter Johnston Date: Thu, 23 Jun 2022 20:54:26 -0400 Subject: [PATCH 12/34] Fixes #8854: Remote auth default groups added to new remote auth users --- netbox/netbox/authentication.py | 25 +++++++++++++++++++++++++ netbox/netbox/settings.py | 13 +++++++++++++ 2 files changed, 38 insertions(+) diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py index a13e8d192..208378872 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication.py @@ -1,5 +1,7 @@ import logging from collections import defaultdict +import requests +from rich import print from django.conf import settings from django.contrib.auth import get_user_model @@ -348,3 +350,26 @@ class LDAPBackend: ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) return obj + + +# Custom Social Auth Pipeline Handlers +def user_default_groups_handler(backend, user, response, *args, **kwargs): + """ + Custom pipeline handler which adds remote auth users to the default group specified in the + configuration file. + """ + logger = logging.getLogger('netbox.auth.user_default_groups_handler') + if settings.REMOTE_AUTH_DEFAULT_GROUPS: + # Assign default groups to the user + group_list = [] + for name in settings.REMOTE_AUTH_DEFAULT_GROUPS: + try: + group_list.append(Group.objects.get(name=name)) + except Group.DoesNotExist: + logging.error( + f"Could not assign group {name} to remotely-authenticated user {user}: Group not found") + if group_list: + user.groups.add(*group_list) + else: + user.groups.clear() + logger.debug(f"Stripping user {user} from Groups") diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index b2e1eca6c..c0df42a2b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -483,6 +483,19 @@ for param in dir(configuration): SOCIAL_AUTH_JSONFIELD_ENABLED = True +SOCIAL_AUTH_PIPELINE = ( + 'social_core.pipeline.social_auth.social_details', + 'social_core.pipeline.social_auth.social_uid', + 'social_core.pipeline.social_auth.social_user', + 'social_core.pipeline.user.get_username', + 'social_core.pipeline.social_auth.associate_by_email', + 'social_core.pipeline.user.create_user', + 'social_core.pipeline.social_auth.associate_user', + 'netbox.authentication.user_default_groups_handler', + 'social_core.pipeline.social_auth.load_extra_data', + 'social_core.pipeline.user.user_details', +) + # # Django Prometheus From 9b91c2a88665f3621c05ae81f52ab2a8fed60c7f Mon Sep 17 00:00:00 2001 From: Hunter Johnston <64506580+huntabyte@users.noreply.github.com> Date: Thu, 23 Jun 2022 23:29:14 -0400 Subject: [PATCH 13/34] syntax: Removed dev imports --- netbox/netbox/authentication.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py index 208378872..00fb3ee66 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication.py @@ -1,7 +1,5 @@ import logging from collections import defaultdict -import requests -from rich import print from django.conf import settings from django.contrib.auth import get_user_model From 2077378ae174013f43cfca75bf4fc082bbfd3ed1 Mon Sep 17 00:00:00 2001 From: Hunter Johnston Date: Sat, 25 Jun 2022 15:41:31 -0400 Subject: [PATCH 14/34] Closes #9540: Filter IP addresses by assigned Device/VM --- netbox/ipam/forms/filtersets.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index bbd6bb97b..3d67d4d37 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -1,7 +1,8 @@ from django import forms from django.utils.translation import gettext as _ -from dcim.models import Location, Rack, Region, Site, SiteGroup +from dcim.models import Location, Rack, Region, Site, SiteGroup, Device +from virtualization.models import VirtualMachine from ipam.choices import * from ipam.constants import * from ipam.models import * @@ -265,6 +266,7 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): ('Attributes', ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface')), ('VRF', ('vrf_id', 'present_in_vrf_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), + ('Device/VM', ('device_id', 'virtual_machine_id')), ) parent = forms.CharField( required=False, @@ -298,6 +300,16 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): required=False, label=_('Present in VRF') ) + device_id = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + label=_('Assigned Device'), + ) + virtual_machine_id = DynamicModelMultipleChoiceField( + queryset=VirtualMachine.objects.all(), + required=False, + label=_('Assigned VM'), + ) status = MultipleChoiceField( choices=IPAddressStatusChoices, required=False From ccb7e96d8a294c45bf8413c4bba167e5835d8a64 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 28 Jun 2022 16:22:38 -0400 Subject: [PATCH 15/34] Changelog for #8854, #9403, #9540 --- docs/release-notes/version-3.2.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 719ceb7e1..57d965538 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -6,9 +6,12 @@ * [#7702](https://github.com/netbox-community/netbox/issues/7702) - Enable dynamic configuration for default powerfeed attributes * [#9396](https://github.com/netbox-community/netbox/issues/9396) - Allow filtering modules by bay ID +* [#9403](https://github.com/netbox-community/netbox/issues/9403) - Enable modifying virtual chassis properties when creating/editing a device +* [#9540](https://github.com/netbox-community/netbox/issues/9540) - Add filters for assigned device & VM to IP addresses list ### Bug Fixes +* [#8854](https://github.com/netbox-community/netbox/issues/8854) - Fix `REMOTE_AUTH_DEFAULT_GROUPS` for social-auth backends * [#9575](https://github.com/netbox-community/netbox/issues/9575) - Fix AttributeError exception for FHRP group with an IP address assigned * [#9597](https://github.com/netbox-community/netbox/issues/9597) - Include `installed_module` in module bay REST API serializer From 8e200a9cb485df698fe8ac30f13025fcab0f3281 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 28 Jun 2022 16:24:56 -0400 Subject: [PATCH 16/34] #9403: Add labels to device VC fields --- netbox/dcim/forms/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index 2c905cc5c..043af751d 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -527,10 +527,12 @@ class DeviceForm(TenancyForm, NetBoxModelForm): ) vc_position = forms.IntegerField( required=False, + label='Position', help_text="The position in the virtual chassis this device is identified by" ) vc_priority = forms.IntegerField( required=False, + label='Priority', help_text="The priority of the device in the virtual chassis" ) From 29f629156a4ed2136c002480d9cf6238e64b686c Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 1 Jul 2022 11:36:34 -0400 Subject: [PATCH 17/34] Update NOTICE file --- NOTICE | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NOTICE b/NOTICE index e6dc6408a..4e4dd600c 100644 --- a/NOTICE +++ b/NOTICE @@ -1 +1,7 @@ Copyrighted and licensed under Apache License 2.0 by DigitalOcean, LLC. + +This project contains code developed expressly for NetBox, and its reuse in +other projects may introduce issues affecting performance, data integrity, +and security. + +For more information, please see https://github.com/netbox-community/netbox. From 13f854c91f931c50192ce84dc3631d12cf0d5ad0 Mon Sep 17 00:00:00 2001 From: Pieter Lambrecht Date: Wed, 6 Jul 2022 14:10:10 +0200 Subject: [PATCH 18/34] Re-order journal list and form --- netbox/templates/extras/object_journal.html | 34 ++++++++++++--------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/netbox/templates/extras/object_journal.html b/netbox/templates/extras/object_journal.html index 363b067a8..0ed95d9eb 100644 --- a/netbox/templates/extras/object_journal.html +++ b/netbox/templates/extras/object_journal.html @@ -5,25 +5,29 @@ {% render_errors form %} {% block content %} - {% if perms.extras.add_journalentry %} -
-
-
-

New Journal Entry

- {% csrf_token %} - {% render_form form %} -
-
- Cancel - -
-
-
- {% endif %}
{% render_table table 'inc/table.html' %} {% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
+ {% if perms.extras.add_journalentry %} +
+
+

New Journal Entry

+
+
+
+ {% csrf_token %} + {% render_form form %} +
+
+ Cancel + +
+
+
+
+
+ {% endif %} {% endblock %} From 55b3e4eeb3a1bd0e80822a47f6c6a7f46af82c49 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 8 Jul 2022 14:16:42 -0400 Subject: [PATCH 19/34] Fixes #9682: Fix bulk assignment of ASNs to sites --- docs/release-notes/version-3.2.md | 1 + netbox/netbox/views/generic/bulk_views.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 57d965538..3bd3773cf 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -14,6 +14,7 @@ * [#8854](https://github.com/netbox-community/netbox/issues/8854) - Fix `REMOTE_AUTH_DEFAULT_GROUPS` for social-auth backends * [#9575](https://github.com/netbox-community/netbox/issues/9575) - Fix AttributeError exception for FHRP group with an IP address assigned * [#9597](https://github.com/netbox-community/netbox/issues/9597) - Include `installed_module` in module bay REST API serializer +* [#9682](https://github.com/netbox-community/netbox/issues/9682) - Fix bulk assignment of ASNs to sites --- diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 96efc0de7..5bdf5cbc9 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -8,6 +8,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import FieldDoesNotExist, ValidationError from django.db import transaction, IntegrityError from django.db.models import ManyToManyField, ProtectedError +from django.db.models.fields.reverse_related import ManyToManyRel from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect, render @@ -484,7 +485,7 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): setattr(obj, name, None if model_field.null else '') # ManyToManyFields - elif isinstance(model_field, ManyToManyField): + elif isinstance(model_field, (ManyToManyField, ManyToManyRel)): if form.cleaned_data[name]: getattr(obj, name).set(form.cleaned_data[name]) # Normal fields From a40ab9ffb1bfc9404ceb38d607d98baaaa1ab78e Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 8 Jul 2022 14:59:16 -0400 Subject: [PATCH 20/34] Fixes #9657: Fix filtering for custom fields and webhooks in the UI --- docs/release-notes/version-3.2.md | 1 + netbox/extras/filtersets.py | 15 ++++-- netbox/extras/forms/filtersets.py | 14 +++--- netbox/extras/tests/test_filtersets.py | 65 +++++++++++++++++++++++++- 4 files changed, 85 insertions(+), 10 deletions(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 3bd3773cf..9b4dc800d 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -14,6 +14,7 @@ * [#8854](https://github.com/netbox-community/netbox/issues/8854) - Fix `REMOTE_AUTH_DEFAULT_GROUPS` for social-auth backends * [#9575](https://github.com/netbox-community/netbox/issues/9575) - Fix AttributeError exception for FHRP group with an IP address assigned * [#9597](https://github.com/netbox-community/netbox/issues/9597) - Include `installed_module` in module bay REST API serializer +* [#9657](https://github.com/netbox-community/netbox/issues/9657) - Fix filtering for custom fields and webhooks in the UI * [#9682](https://github.com/netbox-community/netbox/issues/9682) - Fix bulk assignment of ASNs to sites --- diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py index 25477fbda..bb8d16c42 100644 --- a/netbox/extras/filtersets.py +++ b/netbox/extras/filtersets.py @@ -32,6 +32,9 @@ class WebhookFilterSet(BaseFilterSet): method='search', label='Search', ) + content_type_id = MultiValueNumberFilter( + field_name='content_types__id' + ) content_types = ContentTypeFilter() http_method = django_filters.MultipleChoiceFilter( choices=WebhookHttpMethodChoices @@ -40,8 +43,8 @@ class WebhookFilterSet(BaseFilterSet): class Meta: model = Webhook fields = [ - 'id', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled', - 'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path', + 'id', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled', 'http_method', + 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path', ] def search(self, queryset, name, value): @@ -58,11 +61,17 @@ class CustomFieldFilterSet(BaseFilterSet): method='search', label='Search', ) + type = django_filters.MultipleChoiceFilter( + choices=CustomFieldTypeChoices + ) + content_type_id = MultiValueNumberFilter( + field_name='content_types__id' + ) content_types = ContentTypeFilter() class Meta: model = CustomField - fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight', 'description'] + fields = ['id', 'name', 'required', 'filter_logic', 'weight', 'description'] def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/extras/forms/filtersets.py b/netbox/extras/forms/filtersets.py index 5d66c8be8..71bcfd4c2 100644 --- a/netbox/extras/forms/filtersets.py +++ b/netbox/extras/forms/filtersets.py @@ -32,12 +32,13 @@ __all__ = ( class CustomFieldFilterForm(FilterForm): fieldsets = ( (None, ('q',)), - ('Attributes', ('type', 'content_types', 'weight', 'required')), + ('Attributes', ('type', 'content_type_id', 'weight', 'required')), ) - content_types = ContentTypeMultipleChoiceField( + content_type_id = ContentTypeMultipleChoiceField( queryset=ContentType.objects.all(), limit_choices_to=FeatureQuery('custom_fields'), - required=False + required=False, + label='Object type' ) type = MultipleChoiceField( choices=CustomFieldTypeChoices, @@ -110,13 +111,14 @@ class ExportTemplateFilterForm(FilterForm): class WebhookFilterForm(FilterForm): fieldsets = ( (None, ('q',)), - ('Attributes', ('content_types', 'http_method', 'enabled')), + ('Attributes', ('content_type_id', 'http_method', 'enabled')), ('Events', ('type_create', 'type_update', 'type_delete')), ) - content_types = ContentTypeMultipleChoiceField( + content_type_id = ContentTypeMultipleChoiceField( queryset=ContentType.objects.all(), limit_choices_to=FeatureQuery('webhooks'), - required=False + required=False, + label='Object type' ) http_method = MultipleChoiceField( choices=WebhookHttpMethodChoices, diff --git a/netbox/extras/tests/test_filtersets.py b/netbox/extras/tests/test_filtersets.py index bdb8de9db..aa9d724a4 100644 --- a/netbox/extras/tests/test_filtersets.py +++ b/netbox/extras/tests/test_filtersets.py @@ -7,7 +7,9 @@ from django.test import TestCase from circuits.models import Provider from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup -from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices +from extras.choices import ( + CustomFieldTypeChoices, CustomFieldFilterLogicChoices, JournalEntryKindChoices, ObjectChangeActionChoices, +) from extras.filtersets import * from extras.models import * from ipam.models import IPAddress @@ -16,6 +18,65 @@ from utilities.testing import BaseFilterSetTests, ChangeLoggedFilterSetTests, cr from virtualization.models import Cluster, ClusterGroup, ClusterType +class CustomFieldTestCase(TestCase, BaseFilterSetTests): + queryset = CustomField.objects.all() + filterset = CustomFieldFilterSet + + @classmethod + def setUpTestData(cls): + content_types = ContentType.objects.filter(model__in=['site', 'rack', 'device']) + + custom_fields = ( + CustomField( + name='Custom Field 1', + type=CustomFieldTypeChoices.TYPE_TEXT, + required=True, + weight=100, + filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE + ), + CustomField( + name='Custom Field 2', + type=CustomFieldTypeChoices.TYPE_INTEGER, + required=False, + weight=200, + filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT + ), + CustomField( + name='Custom Field 3', + type=CustomFieldTypeChoices.TYPE_BOOLEAN, + required=False, + weight=300, + filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED + ), + ) + CustomField.objects.bulk_create(custom_fields) + custom_fields[0].content_types.add(content_types[0]) + custom_fields[1].content_types.add(content_types[1]) + custom_fields[2].content_types.add(content_types[2]) + + def test_name(self): + params = {'name': ['Custom Field 1', 'Custom Field 2']} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_content_types(self): + params = {'content_types': 'dcim.site'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + params = {'content_type_id': [ContentType.objects.get_for_model(Site).pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + def test_required(self): + params = {'required': True} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + def test_weight(self): + params = {'weight': [100, 200]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_filter_logic(self): + params = {'filter_logic': CustomFieldFilterLogicChoices.FILTER_LOOSE} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + class WebhookTestCase(TestCase, BaseFilterSetTests): queryset = Webhook.objects.all() filterset = WebhookFilterSet @@ -62,6 +123,8 @@ class WebhookTestCase(TestCase, BaseFilterSetTests): def test_content_types(self): params = {'content_types': 'dcim.site'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + params = {'content_type_id': [ContentType.objects.get_for_model(Site).pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_type_create(self): params = {'type_create': True} From 3c2a55a521b1a8c8a36340bf37f036f242a514a0 Mon Sep 17 00:00:00 2001 From: Kim Johansson Date: Sun, 10 Jul 2022 11:58:45 +0200 Subject: [PATCH 21/34] Add TenantGroupColumn to display Tenant Group on tables Works the same as the existing TenantColumn, but displats the Tenant Group of the Tenant. Views should prefetch the Tenants Group for this to be efficient in large tables. --- netbox/tenancy/tables/columns.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/netbox/tenancy/tables/columns.py b/netbox/tenancy/tables/columns.py index bb5aba5de..aa66e8bc8 100644 --- a/netbox/tenancy/tables/columns.py +++ b/netbox/tenancy/tables/columns.py @@ -2,6 +2,7 @@ import django_tables2 as tables __all__ = ( 'TenantColumn', + 'TenantGroupColumn', ) @@ -24,3 +25,28 @@ class TenantColumn(tables.TemplateColumn): def value(self, value): return str(value) if value else None + + +class TenantGroupColumn(tables.TemplateColumn): + """ + Include the tenant group description. + """ + template_code = """ + {% if record.tenant and record.tenant.group %} + {{ record.tenant.group }} + {% elif record.vrf.tenant and record.vrf.tenant.group %} + {{ record.vrf.tenant.group }}* + {% else %} + — + {% endif %} + """ + + def __init__(self, *args, **kwargs): + if 'verbose_name' not in kwargs: + kwargs['verbose_name'] = 'Tenant Group' + + super().__init__(template_code=self.template_code, *args, **kwargs) + + def value(self, value): + return str(value) if value else None + From bd60d46b8285e62b05e83a36989cf39a3aaa74b4 Mon Sep 17 00:00:00 2001 From: Kim Johansson Date: Sun, 10 Jul 2022 15:08:55 +0200 Subject: [PATCH 22/34] Table mixin for Tenancy columns A mixin to add the Tenant and Tenant Group columns to a table. --- netbox/tenancy/tables/columns.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/netbox/tenancy/tables/columns.py b/netbox/tenancy/tables/columns.py index aa66e8bc8..2d25590aa 100644 --- a/netbox/tenancy/tables/columns.py +++ b/netbox/tenancy/tables/columns.py @@ -3,6 +3,7 @@ import django_tables2 as tables __all__ = ( 'TenantColumn', 'TenantGroupColumn', + 'TenancyColumnsMixin', ) @@ -50,3 +51,7 @@ class TenantGroupColumn(tables.TemplateColumn): def value(self, value): return str(value) if value else None + +class TenancyColumnsMixin(tables.Table): + tenant_group = TenantGroupColumn() + tenant = TenantColumn() From e6bfde1397314950fd11dca01675abf490353cc0 Mon Sep 17 00:00:00 2001 From: Kim Johansson Date: Sun, 10 Jul 2022 15:13:48 +0200 Subject: [PATCH 23/34] Replace TenantColumn with new TenancyColumnsMixin Replaces all usages of the TenantColumn with the new TenancyColumnsMixin. This enables the user to add a column for Tenant Group on all tables which also has a column for Tenant. --- netbox/circuits/tables/circuits.py | 7 ++-- netbox/dcim/tables/cables.py | 7 ++-- netbox/dcim/tables/devices.py | 12 +++---- netbox/dcim/tables/racks.py | 12 +++---- netbox/dcim/tables/sites.py | 12 +++---- netbox/ipam/tables/ip.py | 32 ++++++++----------- netbox/ipam/tables/vlans.py | 12 +++---- netbox/ipam/tables/vrfs.py | 12 +++---- netbox/virtualization/tables/clusters.py | 8 ++--- .../virtualization/tables/virtualmachines.py | 7 ++-- 10 files changed, 50 insertions(+), 71 deletions(-) diff --git a/netbox/circuits/tables/circuits.py b/netbox/circuits/tables/circuits.py index 40f8918ae..ea4310def 100644 --- a/netbox/circuits/tables/circuits.py +++ b/netbox/circuits/tables/circuits.py @@ -2,7 +2,7 @@ import django_tables2 as tables from circuits.models import * from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenantColumn +from tenancy.tables import TenancyColumnsMixin from .columns import CommitRateColumn __all__ = ( @@ -39,7 +39,7 @@ class CircuitTypeTable(NetBoxTable): default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug') -class CircuitTable(NetBoxTable): +class CircuitTable(TenancyColumnsMixin, NetBoxTable): cid = tables.Column( linkify=True, verbose_name='Circuit ID' @@ -48,7 +48,6 @@ class CircuitTable(NetBoxTable): linkify=True ) status = columns.ChoiceFieldColumn() - tenant = TenantColumn() termination_a = tables.TemplateColumn( template_code=CIRCUITTERMINATION_LINK, verbose_name='Side A' @@ -69,7 +68,7 @@ class CircuitTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = Circuit fields = ( - 'pk', 'id', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'install_date', + 'pk', 'id', 'cid', 'provider', 'type', 'status', 'tenant', 'tenant_group', 'termination_a', 'termination_z', 'install_date', 'commit_rate', 'description', 'comments', 'contacts', 'tags', 'created', 'last_updated', ) default_columns = ( diff --git a/netbox/dcim/tables/cables.py b/netbox/dcim/tables/cables.py index 4b062ad48..4fd1b3266 100644 --- a/netbox/dcim/tables/cables.py +++ b/netbox/dcim/tables/cables.py @@ -3,7 +3,7 @@ from django_tables2.utils import Accessor from dcim.models import Cable from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenantColumn +from tenancy.tables import TenancyColumnsMixin from .template_code import CABLE_LENGTH, CABLE_TERMINATION_PARENT __all__ = ( @@ -15,7 +15,7 @@ __all__ = ( # Cables # -class CableTable(NetBoxTable): +class CableTable(TenancyColumnsMixin, NetBoxTable): termination_a_parent = tables.TemplateColumn( template_code=CABLE_TERMINATION_PARENT, accessor=Accessor('termination_a'), @@ -53,7 +53,6 @@ class CableTable(NetBoxTable): verbose_name='Termination B' ) status = columns.ChoiceFieldColumn() - tenant = TenantColumn() length = columns.TemplateColumn( template_code=CABLE_LENGTH, order_by=('_abs_length', 'length_unit') @@ -67,7 +66,7 @@ class CableTable(NetBoxTable): model = Cable fields = ( 'pk', 'id', 'label', 'termination_a_parent', 'rack_a', 'termination_a', 'termination_b_parent', 'rack_b', 'termination_b', - 'status', 'type', 'tenant', 'color', 'length', 'tags', 'created', 'last_updated', + 'status', 'type', 'tenant', 'tenant_group', 'color', 'length', 'tags', 'created', 'last_updated', ) default_columns = ( 'pk', 'id', 'label', 'termination_a_parent', 'termination_a', 'termination_b_parent', 'termination_b', diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 0f015b7f3..710921beb 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -6,7 +6,7 @@ from dcim.models import ( InventoryItemRole, ModuleBay, Platform, PowerOutlet, PowerPort, RearPort, VirtualChassis, ) from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenantColumn +from tenancy.tables import TenancyColumnsMixin from .template_code import * __all__ = ( @@ -137,13 +137,12 @@ class PlatformTable(NetBoxTable): # Devices # -class DeviceTable(NetBoxTable): +class DeviceTable(TenancyColumnsMixin, NetBoxTable): name = tables.TemplateColumn( order_by=('_name',), template_code=DEVICE_LINK ) status = columns.ChoiceFieldColumn() - tenant = TenantColumn() site = tables.Column( linkify=True ) @@ -200,7 +199,7 @@ class DeviceTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = Device fields = ( - 'pk', 'id', 'name', 'status', 'tenant', 'device_role', 'manufacturer', 'device_type', 'platform', 'serial', + 'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'primary_ip', 'airflow', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'contacts', 'tags', 'created', 'last_updated', @@ -211,12 +210,11 @@ class DeviceTable(NetBoxTable): ) -class DeviceImportTable(NetBoxTable): +class DeviceImportTable(TenancyColumnsMixin, NetBoxTable): name = tables.TemplateColumn( template_code=DEVICE_LINK ) status = columns.ChoiceFieldColumn() - tenant = TenantColumn() site = tables.Column( linkify=True ) @@ -232,7 +230,7 @@ class DeviceImportTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = Device - fields = ('id', 'name', 'status', 'tenant', 'site', 'rack', 'position', 'device_role', 'device_type') + fields = ('id', 'name', 'status', 'tenant', 'tenant_group', 'site', 'rack', 'position', 'device_role', 'device_type') empty_text = False diff --git a/netbox/dcim/tables/racks.py b/netbox/dcim/tables/racks.py index e6368cb74..5412e2297 100644 --- a/netbox/dcim/tables/racks.py +++ b/netbox/dcim/tables/racks.py @@ -3,7 +3,7 @@ from django_tables2.utils import Accessor from dcim.models import Rack, RackReservation, RackRole from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenantColumn +from tenancy.tables import TenancyColumnsMixin __all__ = ( 'RackTable', @@ -37,7 +37,7 @@ class RackRoleTable(NetBoxTable): # Racks # -class RackTable(NetBoxTable): +class RackTable(TenancyColumnsMixin, NetBoxTable): name = tables.Column( order_by=('_name',), linkify=True @@ -48,7 +48,6 @@ class RackTable(NetBoxTable): site = tables.Column( linkify=True ) - tenant = TenantColumn() status = columns.ChoiceFieldColumn() role = columns.ColoredLabelColumn() u_height = tables.TemplateColumn( @@ -87,7 +86,7 @@ class RackTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = Rack fields = ( - 'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', + 'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role', 'serial', 'asset_tag', 'type', 'width', 'outer_width', 'outer_depth', 'u_height', 'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'contacts', 'tags', 'created', 'last_updated', ) @@ -101,7 +100,7 @@ class RackTable(NetBoxTable): # Rack reservations # -class RackReservationTable(NetBoxTable): +class RackReservationTable(TenancyColumnsMixin, NetBoxTable): reservation = tables.Column( accessor='pk', linkify=True @@ -110,7 +109,6 @@ class RackReservationTable(NetBoxTable): accessor=Accessor('rack__site'), linkify=True ) - tenant = TenantColumn() rack = tables.Column( linkify=True ) @@ -125,7 +123,7 @@ class RackReservationTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = RackReservation fields = ( - 'pk', 'id', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'tags', + 'pk', 'id', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'tenant_group', 'description', 'tags', 'actions', 'created', 'last_updated', ) default_columns = ('pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'description') diff --git a/netbox/dcim/tables/sites.py b/netbox/dcim/tables/sites.py index fa3c73e12..1945199a3 100644 --- a/netbox/dcim/tables/sites.py +++ b/netbox/dcim/tables/sites.py @@ -2,7 +2,7 @@ import django_tables2 as tables from dcim.models import Location, Region, Site, SiteGroup from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenantColumn +from tenancy.tables import TenancyColumnsMixin from .template_code import LOCATION_BUTTONS __all__ = ( @@ -75,7 +75,7 @@ class SiteGroupTable(NetBoxTable): # Sites # -class SiteTable(NetBoxTable): +class SiteTable(TenancyColumnsMixin, NetBoxTable): name = tables.Column( linkify=True ) @@ -96,7 +96,6 @@ class SiteTable(NetBoxTable): url_params={'site_id': 'pk'}, verbose_name='ASN Count' ) - tenant = TenantColumn() comments = columns.MarkdownColumn() contacts = columns.ManyToManyColumn( linkify_item=True @@ -108,7 +107,7 @@ class SiteTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = Site fields = ( - 'pk', 'id', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'asns', 'asn_count', + 'pk', 'id', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'tenant_group', 'asns', 'asn_count', 'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'contacts', 'tags', 'created', 'last_updated', 'actions', ) @@ -119,14 +118,13 @@ class SiteTable(NetBoxTable): # Locations # -class LocationTable(NetBoxTable): +class LocationTable(TenancyColumnsMixin, NetBoxTable): name = columns.MPTTColumn( linkify=True ) site = tables.Column( linkify=True ) - tenant = TenantColumn() rack_count = columns.LinkedCountColumn( viewname='dcim:rack_list', url_params={'location_id': 'pk'}, @@ -150,7 +148,7 @@ class LocationTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = Location fields = ( - 'pk', 'id', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description', 'slug', 'contacts', + 'pk', 'id', 'name', 'site', 'tenant', 'tenant_group', 'rack_count', 'device_count', 'description', 'slug', 'contacts', 'tags', 'actions', 'created', 'last_updated', ) default_columns = ('pk', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description') diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index 558631585..e61d99fd8 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -4,7 +4,7 @@ from django_tables2.utils import Accessor from ipam.models import * from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenantColumn +from tenancy.tables import TenancyColumnsMixin __all__ = ( 'AggregateTable', @@ -99,7 +99,7 @@ class RIRTable(NetBoxTable): # ASNs # -class ASNTable(NetBoxTable): +class ASNTable(TenancyColumnsMixin, NetBoxTable): asn = tables.Column( linkify=True ) @@ -122,7 +122,6 @@ class ASNTable(NetBoxTable): linkify_item=True, verbose_name='Sites' ) - tenant = TenantColumn() tags = columns.TagColumn( url_name='ipam:asn_list' ) @@ -130,7 +129,7 @@ class ASNTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = ASN fields = ( - 'pk', 'asn', 'asn_asdot', 'rir', 'site_count', 'provider_count', 'tenant', 'description', 'sites', 'tags', + 'pk', 'asn', 'asn_asdot', 'rir', 'site_count', 'provider_count', 'tenant', 'tenant_group', 'description', 'sites', 'tags', 'created', 'last_updated', 'actions', ) default_columns = ('pk', 'asn', 'rir', 'site_count', 'provider_count', 'sites', 'description', 'tenant') @@ -140,12 +139,11 @@ class ASNTable(NetBoxTable): # Aggregates # -class AggregateTable(NetBoxTable): +class AggregateTable(TenancyColumnsMixin, NetBoxTable): prefix = tables.Column( linkify=True, verbose_name='Aggregate' ) - tenant = TenantColumn() date_added = tables.DateColumn( format="Y-m-d", verbose_name='Added' @@ -164,7 +162,7 @@ class AggregateTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = Aggregate fields = ( - 'pk', 'id', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description', 'tags', + 'pk', 'id', 'prefix', 'rir', 'tenant', 'tenant_group', 'child_count', 'utilization', 'date_added', 'description', 'tags', 'created', 'last_updated', ) default_columns = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description') @@ -225,7 +223,7 @@ class PrefixUtilizationColumn(columns.UtilizationColumn): """ -class PrefixTable(NetBoxTable): +class PrefixTable(TenancyColumnsMixin, NetBoxTable): prefix = columns.TemplateColumn( template_code=PREFIX_LINK, export_raw=True, @@ -256,7 +254,6 @@ class PrefixTable(NetBoxTable): template_code=VRF_LINK, verbose_name='VRF' ) - tenant = TenantColumn() site = tables.Column( linkify=True ) @@ -289,7 +286,7 @@ class PrefixTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = Prefix fields = ( - 'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', + 'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'tenant_group', 'site', 'vlan_group', 'vlan', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', 'created', 'last_updated', ) default_columns = ( @@ -303,7 +300,7 @@ class PrefixTable(NetBoxTable): # # IP ranges # -class IPRangeTable(NetBoxTable): +class IPRangeTable(TenancyColumnsMixin, NetBoxTable): start_address = tables.Column( linkify=True ) @@ -317,7 +314,6 @@ class IPRangeTable(NetBoxTable): role = tables.Column( linkify=True ) - tenant = TenantColumn() utilization = columns.UtilizationColumn( accessor='utilization', orderable=False @@ -329,7 +325,7 @@ class IPRangeTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = IPRange fields = ( - 'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description', + 'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'description', 'utilization', 'tags', 'created', 'last_updated', ) default_columns = ( @@ -344,7 +340,7 @@ class IPRangeTable(NetBoxTable): # IPAddresses # -class IPAddressTable(NetBoxTable): +class IPAddressTable(TenancyColumnsMixin, NetBoxTable): address = tables.TemplateColumn( template_code=IPADDRESS_LINK, verbose_name='IP Address' @@ -357,7 +353,6 @@ class IPAddressTable(NetBoxTable): default=AVAILABLE_LABEL ) role = columns.ChoiceFieldColumn() - tenant = TenantColumn() assigned_object = tables.Column( linkify=True, orderable=False, @@ -386,7 +381,7 @@ class IPAddressTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = IPAddress fields = ( - 'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name', 'description', + 'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'nat_inside', 'assigned', 'dns_name', 'description', 'tags', 'created', 'last_updated', ) default_columns = ( @@ -414,7 +409,7 @@ class IPAddressAssignTable(NetBoxTable): orderable = False -class AssignedIPAddressesTable(NetBoxTable): +class AssignedIPAddressesTable(TenancyColumnsMixin, NetBoxTable): """ List IP addresses assigned to an object. """ @@ -427,9 +422,8 @@ class AssignedIPAddressesTable(NetBoxTable): verbose_name='VRF' ) status = columns.ChoiceFieldColumn() - tenant = TenantColumn() class Meta(NetBoxTable.Meta): model = IPAddress - fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description') + fields = ('address', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'description') exclude = ('id', ) diff --git a/netbox/ipam/tables/vlans.py b/netbox/ipam/tables/vlans.py index 4551a1c3d..4329177eb 100644 --- a/netbox/ipam/tables/vlans.py +++ b/netbox/ipam/tables/vlans.py @@ -5,7 +5,7 @@ from django_tables2.utils import Accessor from dcim.models import Interface from ipam.models import * from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenantColumn +from tenancy.tables import TenancyColumnsMixin from virtualization.models import VMInterface __all__ = ( @@ -90,7 +90,7 @@ class VLANGroupTable(NetBoxTable): # VLANs # -class VLANTable(NetBoxTable): +class VLANTable(TenancyColumnsMixin, NetBoxTable): vid = tables.TemplateColumn( template_code=VLAN_LINK, verbose_name='VID' @@ -104,7 +104,6 @@ class VLANTable(NetBoxTable): group = tables.Column( linkify=True ) - tenant = TenantColumn() status = columns.ChoiceFieldColumn( default=AVAILABLE_LABEL ) @@ -123,7 +122,7 @@ class VLANTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = VLAN fields = ( - 'pk', 'id', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags', + 'pk', 'id', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'tenant_group', 'status', 'role', 'description', 'tags', 'created', 'last_updated', ) default_columns = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description') @@ -174,7 +173,7 @@ class VLANVirtualMachinesTable(VLANMembersTable): exclude = ('id', ) -class InterfaceVLANTable(NetBoxTable): +class InterfaceVLANTable(TenancyColumnsMixin, NetBoxTable): """ List VLANs assigned to a specific Interface. """ @@ -190,7 +189,6 @@ class InterfaceVLANTable(NetBoxTable): accessor=Accessor('group__name'), verbose_name='Group' ) - tenant = TenantColumn() status = columns.ChoiceFieldColumn() role = tables.Column( linkify=True @@ -198,7 +196,7 @@ class InterfaceVLANTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = VLAN - fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description') + fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'tenant_group', 'status', 'role', 'description') exclude = ('id', ) def __init__(self, interface, *args, **kwargs): diff --git a/netbox/ipam/tables/vrfs.py b/netbox/ipam/tables/vrfs.py index 727f402ff..69807410b 100644 --- a/netbox/ipam/tables/vrfs.py +++ b/netbox/ipam/tables/vrfs.py @@ -2,7 +2,7 @@ import django_tables2 as tables from ipam.models import * from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenantColumn +from tenancy.tables import TenancyColumnsMixin __all__ = ( 'RouteTargetTable', @@ -20,14 +20,13 @@ VRF_TARGETS = """ # VRFs # -class VRFTable(NetBoxTable): +class VRFTable(TenancyColumnsMixin, NetBoxTable): name = tables.Column( linkify=True ) rd = tables.Column( verbose_name='RD' ) - tenant = TenantColumn() enforce_unique = columns.BooleanColumn( verbose_name='Unique' ) @@ -46,7 +45,7 @@ class VRFTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = VRF fields = ( - 'pk', 'id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', + 'pk', 'id', 'name', 'rd', 'tenant', 'tenant_group', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tags', 'created', 'last_updated', ) default_columns = ('pk', 'name', 'rd', 'tenant', 'description') @@ -56,16 +55,15 @@ class VRFTable(NetBoxTable): # Route targets # -class RouteTargetTable(NetBoxTable): +class RouteTargetTable(TenancyColumnsMixin, NetBoxTable): name = tables.Column( linkify=True ) - tenant = TenantColumn() tags = columns.TagColumn( url_name='ipam:vrf_list' ) class Meta(NetBoxTable.Meta): model = RouteTarget - fields = ('pk', 'id', 'name', 'tenant', 'description', 'tags', 'created', 'last_updated',) + fields = ('pk', 'id', 'name', 'tenant', 'tenant_group', 'description', 'tags', 'created', 'last_updated',) default_columns = ('pk', 'name', 'tenant', 'description') diff --git a/netbox/virtualization/tables/clusters.py b/netbox/virtualization/tables/clusters.py index a0c98425a..ccffe23fd 100644 --- a/netbox/virtualization/tables/clusters.py +++ b/netbox/virtualization/tables/clusters.py @@ -1,6 +1,7 @@ import django_tables2 as tables from netbox.tables import NetBoxTable, columns +from tenancy.tables import TenancyColumnsMixin from virtualization.models import Cluster, ClusterGroup, ClusterType __all__ = ( @@ -56,7 +57,7 @@ class ClusterGroupTable(NetBoxTable): default_columns = ('pk', 'name', 'cluster_count', 'description') -class ClusterTable(NetBoxTable): +class ClusterTable(TenancyColumnsMixin, NetBoxTable): name = tables.Column( linkify=True ) @@ -66,9 +67,6 @@ class ClusterTable(NetBoxTable): group = tables.Column( linkify=True ) - tenant = tables.Column( - linkify=True - ) site = tables.Column( linkify=True ) @@ -93,7 +91,7 @@ class ClusterTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = Cluster fields = ( - 'pk', 'id', 'name', 'type', 'group', 'tenant', 'site', 'comments', 'device_count', 'vm_count', 'contacts', + 'pk', 'id', 'name', 'type', 'group', 'tenant', 'tenant_group', 'site', 'comments', 'device_count', 'vm_count', 'contacts', 'tags', 'created', 'last_updated', ) default_columns = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'device_count', 'vm_count') diff --git a/netbox/virtualization/tables/virtualmachines.py b/netbox/virtualization/tables/virtualmachines.py index 89dbdf901..8cff96227 100644 --- a/netbox/virtualization/tables/virtualmachines.py +++ b/netbox/virtualization/tables/virtualmachines.py @@ -2,7 +2,7 @@ import django_tables2 as tables from dcim.tables.devices import BaseInterfaceTable from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenantColumn +from tenancy.tables import TenancyColumnsMixin from virtualization.models import VirtualMachine, VMInterface __all__ = ( @@ -24,7 +24,7 @@ VMINTERFACE_BUTTONS = """ # Virtual machines # -class VirtualMachineTable(NetBoxTable): +class VirtualMachineTable(TenancyColumnsMixin, NetBoxTable): name = tables.Column( order_by=('_name',), linkify=True @@ -34,7 +34,6 @@ class VirtualMachineTable(NetBoxTable): linkify=True ) role = columns.ColoredLabelColumn() - tenant = TenantColumn() comments = columns.MarkdownColumn() primary_ip4 = tables.Column( linkify=True, @@ -56,7 +55,7 @@ class VirtualMachineTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = VirtualMachine fields = ( - 'pk', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', + 'pk', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'tenant_group', 'platform', 'vcpus', 'memory', 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'comments', 'tags', 'created', 'last_updated', ) default_columns = ( From 69a22ffe5e3aefafa540f13c40e88f9c5ca0b7ec Mon Sep 17 00:00:00 2001 From: Kim Johansson Date: Sun, 10 Jul 2022 15:38:21 +0200 Subject: [PATCH 24/34] Prefetch Tenant Group in global search Configure the prefetch to also include the Tenant Group, avoids additional database queries when the Tenant Group column is to be rendered. NOTE: If no personalisation of the global search tables should be done, this commit can be reverted. --- netbox/netbox/constants.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/netbox/netbox/constants.py b/netbox/netbox/constants.py index 8ca0d98c1..cc04e9aa8 100644 --- a/netbox/netbox/constants.py +++ b/netbox/netbox/constants.py @@ -34,7 +34,7 @@ CIRCUIT_TYPES = OrderedDict( }), ('circuit', { 'queryset': Circuit.objects.prefetch_related( - 'type', 'provider', 'tenant', 'terminations__site' + 'type', 'provider', 'tenant', 'tenant__group', 'terminations__site' ), 'filterset': circuits.filtersets.CircuitFilterSet, 'table': circuits.tables.CircuitTable, @@ -53,13 +53,13 @@ CIRCUIT_TYPES = OrderedDict( DCIM_TYPES = OrderedDict( ( ('site', { - 'queryset': Site.objects.prefetch_related('region', 'tenant'), + 'queryset': Site.objects.prefetch_related('region', 'tenant', 'tenant__group'), 'filterset': dcim.filtersets.SiteFilterSet, 'table': dcim.tables.SiteTable, 'url': 'dcim:site_list', }), ('rack', { - 'queryset': Rack.objects.prefetch_related('site', 'location', 'tenant', 'role').annotate( + 'queryset': Rack.objects.prefetch_related('site', 'location', 'tenant', 'tenant__group', 'role').annotate( device_count=count_related(Device, 'rack') ), 'filterset': dcim.filtersets.RackFilterSet, @@ -100,7 +100,7 @@ DCIM_TYPES = OrderedDict( }), ('device', { 'queryset': Device.objects.prefetch_related( - 'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6', + 'device_type__manufacturer', 'device_role', 'tenant', 'tenant__group', 'site', 'rack', 'primary_ip4', 'primary_ip6', ), 'filterset': dcim.filtersets.DeviceFilterSet, 'table': dcim.tables.DeviceTable, @@ -148,7 +148,7 @@ DCIM_TYPES = OrderedDict( IPAM_TYPES = OrderedDict( ( ('vrf', { - 'queryset': VRF.objects.prefetch_related('tenant'), + 'queryset': VRF.objects.prefetch_related('tenant', 'tenant__group'), 'filterset': ipam.filtersets.VRFFilterSet, 'table': ipam.tables.VRFTable, 'url': 'ipam:vrf_list', @@ -160,25 +160,25 @@ IPAM_TYPES = OrderedDict( 'url': 'ipam:aggregate_list', }), ('prefix', { - 'queryset': Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role'), + 'queryset': Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'tenant__group', 'vlan', 'role'), 'filterset': ipam.filtersets.PrefixFilterSet, 'table': ipam.tables.PrefixTable, 'url': 'ipam:prefix_list', }), ('ipaddress', { - 'queryset': IPAddress.objects.prefetch_related('vrf__tenant', 'tenant'), + 'queryset': IPAddress.objects.prefetch_related('vrf__tenant', 'tenant', 'tenant__group'), 'filterset': ipam.filtersets.IPAddressFilterSet, 'table': ipam.tables.IPAddressTable, 'url': 'ipam:ipaddress_list', }), ('vlan', { - 'queryset': VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role'), + 'queryset': VLAN.objects.prefetch_related('site', 'group', 'tenant', 'tenant__group', 'role'), 'filterset': ipam.filtersets.VLANFilterSet, 'table': ipam.tables.VLANTable, 'url': 'ipam:vlan_list', }), ('asn', { - 'queryset': ASN.objects.prefetch_related('rir', 'tenant'), + 'queryset': ASN.objects.prefetch_related('rir', 'tenant', 'tenant__group'), 'filterset': ipam.filtersets.ASNFilterSet, 'table': ipam.tables.ASNTable, 'url': 'ipam:asn_list', @@ -223,7 +223,7 @@ VIRTUALIZATION_TYPES = OrderedDict( }), ('virtualmachine', { 'queryset': VirtualMachine.objects.prefetch_related( - 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', + 'cluster', 'tenant', 'tenant__group', 'platform', 'primary_ip4', 'primary_ip6', ), 'filterset': virtualization.filtersets.VirtualMachineFilterSet, 'table': virtualization.tables.VirtualMachineTable, From c7ece43a1887bdc1bb3daf894c0929b9babd0505 Mon Sep 17 00:00:00 2001 From: Kim Johansson Date: Sun, 10 Jul 2022 17:16:12 +0200 Subject: [PATCH 25/34] Remove Tenant Group from child tables Removes the Tenant Group column from tables which aren't configurable by the user. --- netbox/ipam/tables/ip.py | 7 ++++--- netbox/ipam/tables/vlans.py | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index e61d99fd8..bec05eeff 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -4,7 +4,7 @@ from django_tables2.utils import Accessor from ipam.models import * from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenancyColumnsMixin +from tenancy.tables import TenancyColumnsMixin, TenantColumn __all__ = ( 'AggregateTable', @@ -409,7 +409,7 @@ class IPAddressAssignTable(NetBoxTable): orderable = False -class AssignedIPAddressesTable(TenancyColumnsMixin, NetBoxTable): +class AssignedIPAddressesTable(NetBoxTable): """ List IP addresses assigned to an object. """ @@ -422,8 +422,9 @@ class AssignedIPAddressesTable(TenancyColumnsMixin, NetBoxTable): verbose_name='VRF' ) status = columns.ChoiceFieldColumn() + tenant = TenantColumn() class Meta(NetBoxTable.Meta): model = IPAddress - fields = ('address', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'description') + fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description') exclude = ('id', ) diff --git a/netbox/ipam/tables/vlans.py b/netbox/ipam/tables/vlans.py index 4329177eb..7878de507 100644 --- a/netbox/ipam/tables/vlans.py +++ b/netbox/ipam/tables/vlans.py @@ -5,7 +5,7 @@ from django_tables2.utils import Accessor from dcim.models import Interface from ipam.models import * from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenancyColumnsMixin +from tenancy.tables import TenancyColumnsMixin, TenantColumn from virtualization.models import VMInterface __all__ = ( @@ -173,7 +173,7 @@ class VLANVirtualMachinesTable(VLANMembersTable): exclude = ('id', ) -class InterfaceVLANTable(TenancyColumnsMixin, NetBoxTable): +class InterfaceVLANTable(NetBoxTable): """ List VLANs assigned to a specific Interface. """ @@ -189,6 +189,7 @@ class InterfaceVLANTable(TenancyColumnsMixin, NetBoxTable): accessor=Accessor('group__name'), verbose_name='Group' ) + tenant = TenantColumn() status = columns.ChoiceFieldColumn() role = tables.Column( linkify=True @@ -196,7 +197,7 @@ class InterfaceVLANTable(TenancyColumnsMixin, NetBoxTable): class Meta(NetBoxTable.Meta): model = VLAN - fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'tenant_group', 'status', 'role', 'description') + fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description') exclude = ('id', ) def __init__(self, interface, *args, **kwargs): From 1539769c08e200c8586de43700f8d152e7b3cc0d Mon Sep 17 00:00:00 2001 From: Kim Johansson Date: Sun, 10 Jul 2022 17:17:56 +0200 Subject: [PATCH 26/34] Prefetch Tenant Group on user configurable tables Prefetch the Tenant Group in views which allows its table to be configured by the user. This decreases the amount of database queries that are required to fetch the data. --- netbox/circuits/views.py | 6 +++--- netbox/dcim/views.py | 2 +- netbox/ipam/views.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index f3b1269f9..11f211b27 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -30,7 +30,7 @@ class ProviderView(generic.ObjectView): circuits = Circuit.objects.restrict(request.user, 'view').filter( provider=instance ).prefetch_related( - 'type', 'tenant', 'terminations__site' + 'type', 'tenant', 'tenant__group', 'terminations__site' ) circuits_table = tables.CircuitTable(circuits, user=request.user, exclude=('provider',)) circuits_table.configure(request) @@ -91,7 +91,7 @@ class ProviderNetworkView(generic.ObjectView): Q(termination_a__provider_network=instance.pk) | Q(termination_z__provider_network=instance.pk) ).prefetch_related( - 'type', 'tenant', 'terminations__site' + 'type', 'tenant', 'tenant__group', 'terminations__site' ) circuits_table = tables.CircuitTable(circuits, user=request.user) circuits_table.configure(request) @@ -192,7 +192,7 @@ class CircuitTypeBulkDeleteView(generic.BulkDeleteView): class CircuitListView(generic.ObjectListView): queryset = Circuit.objects.prefetch_related( - 'provider', 'type', 'tenant', 'termination_a', 'termination_z' + 'provider', 'type', 'tenant', 'tenant__group', 'termination_a', 'termination_z' ) filterset = filtersets.CircuitFilterSet filterset_form = forms.CircuitFilterForm diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 35a1056b2..a466246f5 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -561,7 +561,7 @@ class RackRoleBulkDeleteView(generic.BulkDeleteView): class RackListView(generic.ObjectListView): queryset = Rack.objects.prefetch_related( - 'site', 'location', 'tenant', 'role', 'devices__device_type' + 'site', 'location', 'tenant', 'tenant_group', 'role', 'devices__device_type' ).annotate( device_count=count_related(Device, 'rack') ) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 6bcdc4c64..e6b11c427 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -298,7 +298,7 @@ class AggregatePrefixesView(generic.ObjectChildrenView): def get_children(self, request, parent): return Prefix.objects.restrict(request.user, 'view').filter( prefix__net_contained_or_equal=str(parent.prefix) - ).prefetch_related('site', 'role', 'tenant', 'vlan') + ).prefetch_related('site', 'role', 'tenant', 'tenant__group', 'vlan') def prep_table_data(self, request, queryset, parent): # Determine whether to show assigned prefixes, available prefixes, or both @@ -470,7 +470,7 @@ class PrefixPrefixesView(generic.ObjectChildrenView): def get_children(self, request, parent): return parent.get_child_prefixes().restrict(request.user, 'view').prefetch_related( - 'site', 'vrf', 'vlan', 'role', 'tenant', + 'site', 'vrf', 'vlan', 'role', 'tenant', 'tenant__group' ) def prep_table_data(self, request, queryset, parent): @@ -499,7 +499,7 @@ class PrefixIPRangesView(generic.ObjectChildrenView): def get_children(self, request, parent): return parent.get_child_ranges().restrict(request.user, 'view').prefetch_related( - 'vrf', 'role', 'tenant', + 'vrf', 'role', 'tenant', 'tenant__group', ) def get_extra_context(self, request, instance): @@ -587,7 +587,7 @@ class IPRangeIPAddressesView(generic.ObjectChildrenView): def get_children(self, request, parent): return parent.get_child_ips().restrict(request.user, 'view').prefetch_related( - 'vrf', 'role', 'tenant', + 'vrf', 'role', 'tenant', 'tenant__group', ) def get_extra_context(self, request, instance): From 93c30c94b3b25642af2e74c536a16d18c8a3d102 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Sun, 10 Jul 2022 19:16:16 +0200 Subject: [PATCH 27/34] Focus on select field after populating data --- netbox/project-static/dist/netbox.js | Bin 376144 -> 376180 bytes netbox/project-static/dist/netbox.js.map | Bin 345564 -> 345595 bytes .../src/select/api/apiSelect.ts | 1 + 3 files changed, 1 insertion(+) diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index bc0cabef08f2cab679e475fe18741c4e97ee187e..b611079e17d534b7e0aea1ed07b5e33c124129f5 100644 GIT binary patch delta 42 ycmcccOYF-pv4$4L7N!>F7M3lnH-o0T<+2KLXXX`@mguGBCzlp)zZk??p9KJbz!4Mx delta 30 mcmezJOYFigv4$4L7N!>F7M3lnH-n~sk7ZTft`N)`k_7cxt0K_2NW&< delta 47 zcmey}EqbS0w4sHug{g&k3(JLX+uwgc`G#zP*o!HP;dV D&I=I5 diff --git a/netbox/project-static/src/select/api/apiSelect.ts b/netbox/project-static/src/select/api/apiSelect.ts index f5b605d58..798694f0e 100644 --- a/netbox/project-static/src/select/api/apiSelect.ts +++ b/netbox/project-static/src/select/api/apiSelect.ts @@ -411,6 +411,7 @@ export class APISelect { } finally { this.setOptionStyles(); this.enable(); + this.slim.slim.search.input.focus(); this.base.dispatchEvent(this.loadEvent); } } From d3f91ce0a6140f4e2ecfdbaf7287ec4527de265a Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 11 Jul 2022 09:57:12 -0400 Subject: [PATCH 28/34] Changelog for #9632, #9686 --- docs/release-notes/version-3.2.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 9b4dc800d..7c62f85ad 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -8,12 +8,14 @@ * [#9396](https://github.com/netbox-community/netbox/issues/9396) - Allow filtering modules by bay ID * [#9403](https://github.com/netbox-community/netbox/issues/9403) - Enable modifying virtual chassis properties when creating/editing a device * [#9540](https://github.com/netbox-community/netbox/issues/9540) - Add filters for assigned device & VM to IP addresses list +* [#9686](https://github.com/netbox-community/netbox/issues/9686) - Add tenant group column for all object tables with tenant assignments ### Bug Fixes * [#8854](https://github.com/netbox-community/netbox/issues/8854) - Fix `REMOTE_AUTH_DEFAULT_GROUPS` for social-auth backends * [#9575](https://github.com/netbox-community/netbox/issues/9575) - Fix AttributeError exception for FHRP group with an IP address assigned * [#9597](https://github.com/netbox-community/netbox/issues/9597) - Include `installed_module` in module bay REST API serializer +* [#9632](https://github.com/netbox-community/netbox/issues/9632) - Automatically focus on search box when expanding dropdowns * [#9657](https://github.com/netbox-community/netbox/issues/9657) - Fix filtering for custom fields and webhooks in the UI * [#9682](https://github.com/netbox-community/netbox/issues/9682) - Fix bulk assignment of ASNs to sites From e2af716a81b84972ce2f310d1f9f11d2a54f5367 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 11 Jul 2022 10:03:45 -0400 Subject: [PATCH 29/34] #9686: Add default accessor to TenantGroupColumn --- netbox/tenancy/tables/columns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/tenancy/tables/columns.py b/netbox/tenancy/tables/columns.py index 2d25590aa..21622f18b 100644 --- a/netbox/tenancy/tables/columns.py +++ b/netbox/tenancy/tables/columns.py @@ -42,11 +42,11 @@ class TenantGroupColumn(tables.TemplateColumn): {% endif %} """ - def __init__(self, *args, **kwargs): + def __init__(self, accessor=tables.A('tenant__group'), *args, **kwargs): if 'verbose_name' not in kwargs: kwargs['verbose_name'] = 'Tenant Group' - super().__init__(template_code=self.template_code, *args, **kwargs) + super().__init__(template_code=self.template_code, accessor=accessor, *args, **kwargs) def value(self, value): return str(value) if value else None From ed7f42a803c5a2d2af28a817b45d672ef417c728 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 11 Jul 2022 10:28:37 -0400 Subject: [PATCH 30/34] Fixes #9704: Include last_updated field on JournalEntry REST API serializer --- docs/release-notes/version-3.2.md | 1 + netbox/extras/api/serializers.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 7c62f85ad..83c0765c3 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -18,6 +18,7 @@ * [#9632](https://github.com/netbox-community/netbox/issues/9632) - Automatically focus on search box when expanding dropdowns * [#9657](https://github.com/netbox-community/netbox/issues/9657) - Fix filtering for custom fields and webhooks in the UI * [#9682](https://github.com/netbox-community/netbox/issues/9682) - Fix bulk assignment of ASNs to sites +* [#9704](https://github.com/netbox-community/netbox/issues/9704) - Include `last_updated` field on JournalEntry REST API serializer --- diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index e05d4083c..bdb54067a 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -221,7 +221,7 @@ class JournalEntrySerializer(NetBoxModelSerializer): model = JournalEntry fields = [ 'id', 'url', 'display', 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'created', - 'created_by', 'kind', 'comments', 'tags', 'custom_fields', + 'created_by', 'kind', 'comments', 'tags', 'custom_fields', 'last_updated', ] def validate(self, data): From 6415661b61d85a12a0b8002719623d19477574f2 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 11 Jul 2022 10:33:14 -0400 Subject: [PATCH 31/34] Remove extraneous argument to GenericRelation --- netbox/dcim/models/device_components.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 9a0609c12..714ee3ff5 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -95,8 +95,7 @@ class ModularComponentModel(ComponentModel): inventory_items = GenericRelation( to='dcim.InventoryItem', content_type_field='component_type', - object_id_field='component_id', - related_name='%(class)ss', + object_id_field='component_id' ) class Meta: From 7c109ffd8cb6e9c76c440263568fb7194519e491 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 11 Jul 2022 11:02:18 -0400 Subject: [PATCH 32/34] Fixes #9697: Fix device role link under device view --- netbox/templates/dcim/device.html | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index d3d6f03dc..68af0c08f 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -46,13 +46,7 @@ Rack - - {% if object.rack %} - {{ object.rack }} - {% else %} - {{ ''|placeholder }} - {% endif %} - + {{ object.rack|linkify|placeholder }} Position @@ -161,9 +155,7 @@ Role - - {{ object.device_role }} - + {{ object.device_role|linkify }} Platform @@ -173,7 +165,7 @@ Primary IPv4 {% if object.primary_ip4 %} - {{ object.primary_ip4.address.ip }} + {{ object.primary_ip4.address.ip }} {% if object.primary_ip4.nat_inside %} (NAT for {{ object.primary_ip4.nat_inside.address.ip|linkify }}) {% elif object.primary_ip4.nat_outside %} @@ -188,7 +180,7 @@ Primary IPv6 {% if object.primary_ip6 %} - {{ object.primary_ip6.address.ip }} + {{ object.primary_ip6.address.ip }} {% if object.primary_ip6.nat_inside %} (NAT for {{ object.primary_ip6.nat_inside.address.ip|linkify }}) {% elif object.primary_ip6.nat_outside %} From 5a4467a4a8cc92b83606a8bbc661873b5fd294cd Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 11 Jul 2022 11:12:06 -0400 Subject: [PATCH 33/34] Fixes #9687: Don't restrict custom text field lengths when entering via UI form --- docs/models/extras/customfield.md | 2 +- docs/release-notes/version-3.2.md | 1 + netbox/extras/models/customfields.py | 9 ++------- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/models/extras/customfield.md b/docs/models/extras/customfield.md index da73816b6..e0c01688d 100644 --- a/docs/models/extras/customfield.md +++ b/docs/models/extras/customfield.md @@ -10,7 +10,7 @@ Within the database, custom fields are stored as JSON data directly alongside ea Custom fields may be created by navigating to Customization > Custom Fields. NetBox supports six types of custom field: -* Text: Free-form text (up to 255 characters) +* Text: Free-form text (intended for single-line use) * Long text: Free-form of any length; supports Markdown rendering * Integer: A whole number (positive or negative) * Boolean: True or false diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 83c0765c3..86a352215 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -18,6 +18,7 @@ * [#9632](https://github.com/netbox-community/netbox/issues/9632) - Automatically focus on search box when expanding dropdowns * [#9657](https://github.com/netbox-community/netbox/issues/9657) - Fix filtering for custom fields and webhooks in the UI * [#9682](https://github.com/netbox-community/netbox/issues/9682) - Fix bulk assignment of ASNs to sites +* [#9687](https://github.com/netbox-community/netbox/issues/9687) - Don't restrict custom text field lengths when entering via UI form * [#9704](https://github.com/netbox-community/netbox/issues/9704) - Include `last_updated` field on JournalEntry REST API serializer --- diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 49afe1bba..6a8c1dacf 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -365,13 +365,8 @@ class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel): # Text else: - if self.type == CustomFieldTypeChoices.TYPE_LONGTEXT: - max_length = None - widget = forms.Textarea - else: - max_length = 255 - widget = None - field = forms.CharField(max_length=max_length, required=required, initial=initial, widget=widget) + widget = forms.Textarea if self.type == CustomFieldTypeChoices.TYPE_LONGTEXT else None + field = forms.CharField(required=required, initial=initial, widget=widget) if self.validation_regex: field.validators = [ RegexValidator( From 68f24755aa4d8464111dd3eda320eee1cb99d53f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 11 Jul 2022 11:40:10 -0400 Subject: [PATCH 34/34] Release v3.2.6 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- base_requirements.txt | 8 ++++---- docs/release-notes/version-3.2.md | 2 +- netbox/netbox/settings.py | 2 +- requirements.txt | 12 ++++++------ 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 3b87a49e4..78231890b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.2.5 + placeholder: v3.2.6 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 1fc0268ab..71d45092c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.2.5 + placeholder: v3.2.6 validations: required: true - type: dropdown diff --git a/base_requirements.txt b/base_requirements.txt index 98d3f78c2..9dc85231b 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -1,3 +1,7 @@ +# HTML sanitizer +# https://github.com/mozilla/bleach +bleach + # The Python web framework on which NetBox is built # https://github.com/django/django Django @@ -126,7 +130,3 @@ tablib # Timezone data (required by django-timezone-field on Python 3.9+) # https://github.com/python/tzdata tzdata - -# HTML sanitizer -# https://github.com/mozilla/bleach -bleach \ No newline at end of file diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 86a352215..8775da01f 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -1,6 +1,6 @@ # NetBox v3.2 -## v3.2.6 (FUTURE) +## v3.2.6 (2022-07-11) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index c0df42a2b..b9d7e20b9 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -29,7 +29,7 @@ django.utils.encoding.force_text = force_str # Environment setup # -VERSION = '3.2.6-dev' +VERSION = '3.2.6' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index 1fdace4f0..f987ad7ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -bleach==5.0.0 -Django==4.0.5 +bleach==5.0.1 +Django==4.0.6 django-cors-headers==3.13.0 -django-debug-toolbar==3.4.0 +django-debug-toolbar==3.5.0 django-filter==22.1 django-graphiql-debug-toolbar==0.2.0 django-mptt==0.13.4 @@ -19,13 +19,13 @@ gunicorn==20.1.0 Jinja2==3.1.2 Markdown==3.3.7 markdown-include==0.6.0 -mkdocs-material==8.3.6 +mkdocs-material==8.3.9 mkdocstrings[python-legacy]==0.19.0 netaddr==0.8.0 -Pillow==9.1.1 +Pillow==9.2.0 psycopg2-binary==2.9.3 PyYAML==6.0 -sentry-sdk==1.5.12 +sentry-sdk==1.7.0 social-auth-app-django==5.0.0 social-auth-core==4.3.0 svgwrite==1.4.2