From 4afebd3565145fffd6bee41b3679e99be88441d8 Mon Sep 17 00:00:00 2001 From: Anders Harrisson Date: Tue, 6 Feb 2024 12:32:03 +0100 Subject: [PATCH 01/16] Fix custom script documentation example script The example script still uses the old "role" field when creating a Device object. Fixes #15052 --- docs/customization/custom-scripts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/customization/custom-scripts.md b/docs/customization/custom-scripts.md index e2bc53cfc..c68bc21f1 100644 --- a/docs/customization/custom-scripts.md +++ b/docs/customization/custom-scripts.md @@ -390,7 +390,7 @@ class NewBranchScript(Script): name=f'{site.slug}-switch{i}', site=site, status=DeviceStatusChoices.STATUS_PLANNED, - role=switch_role + device_role=switch_role ) switch.full_clean() switch.save() From 64b2ebdc794f80af8f021eb2a0d425f1a1f32bfd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 8 Feb 2024 08:47:16 -0500 Subject: [PATCH 02/16] Fixes #15084: Fix "add export template" link --- docs/release-notes/version-3.7.md | 4 ++++ netbox/utilities/templates/buttons/export.html | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.7.md b/docs/release-notes/version-3.7.md index 103b0664c..531678f2d 100644 --- a/docs/release-notes/version-3.7.md +++ b/docs/release-notes/version-3.7.md @@ -2,6 +2,10 @@ ## v3.7.3 (FUTURE) +### Bug Fixes + +* [#15084](https://github.com/netbox-community/netbox/issues/15084) - Fix "add export template" link under "export" button on object list views + --- ## v3.7.2 (2024-02-05) diff --git a/netbox/utilities/templates/buttons/export.html b/netbox/utilities/templates/buttons/export.html index 879fc02c5..baa1253eb 100644 --- a/netbox/utilities/templates/buttons/export.html +++ b/netbox/utilities/templates/buttons/export.html @@ -25,7 +25,7 @@
  • - {% trans "Add export template" %}... + {% trans "Add export template" %}...
  • {% endif %} From 040dbcc8751f51aaf650ba07ec7861c20b7aeb35 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 8 Feb 2024 09:10:24 -0500 Subject: [PATCH 03/16] Fixes #15070: Fix inclusion of config_template field on REST API serializer for virtual machines --- docs/release-notes/version-3.7.md | 1 + netbox/virtualization/api/serializers.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.7.md b/docs/release-notes/version-3.7.md index 531678f2d..e3c2f8e48 100644 --- a/docs/release-notes/version-3.7.md +++ b/docs/release-notes/version-3.7.md @@ -4,6 +4,7 @@ ### Bug Fixes +* [#15070](https://github.com/netbox-community/netbox/issues/15070) - Fix inclusion of `config_template` field on REST API serializer for virtual machines * [#15084](https://github.com/netbox-community/netbox/issues/15084) - Fix "add export template" link under "export" button on object list views --- diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index 7ed36388b..1dcb413ec 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -103,8 +103,8 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer): fields = [ 'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'comments', - 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated', - 'interface_count', 'virtual_disk_count', + 'config_template', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', + 'last_updated', 'interface_count', 'virtual_disk_count', ] @extend_schema_field(serializers.JSONField(allow_null=True)) From ae7d6ffd925ecdd9f921a594f2e1858b4da3c8e5 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Mon, 12 Feb 2024 17:31:59 +0900 Subject: [PATCH 04/16] Update remote-authentication.md Seperator -> Separator --- docs/configuration/remote-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/remote-authentication.md b/docs/configuration/remote-authentication.md index fb789bd98..e7fe56a09 100644 --- a/docs/configuration/remote-authentication.md +++ b/docs/configuration/remote-authentication.md @@ -67,7 +67,7 @@ When remote user authentication is in use, this is the name of the HTTP header w Default: `|` (Pipe) -The Seperator upon which `REMOTE_AUTH_GROUP_HEADER` gets split into individual Groups. This needs to be coordinated with your authentication Proxy. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` ) +The Separator upon which `REMOTE_AUTH_GROUP_HEADER` gets split into individual Groups. This needs to be coordinated with your authentication Proxy. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` ) --- From c7ae2db8e31960ee1b38d11c15108b3e07ffbbf9 Mon Sep 17 00:00:00 2001 From: teapot Date: Sun, 11 Feb 2024 14:50:24 +0900 Subject: [PATCH 05/16] Fixes #15111: Correct typo in error message --- netbox/dcim/models/devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 4b9689a22..f9e8ba213 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -875,7 +875,7 @@ class Device( if self.position and self.device_type.u_height == 0: raise ValidationError({ 'position': _( - "A U0 device type ({device_type}) cannot be assigned to a rack position." + "A 0U device type ({device_type}) cannot be assigned to a rack position." ).format(device_type=self.device_type) }) From 1f800a975fe2549903a41a3c5da64f437e1bc8f8 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Feb 2024 09:34:59 -0500 Subject: [PATCH 06/16] Fixes #15115: Fix unhandled exception with invalid permission constraints --- netbox/users/forms/model_forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/users/forms/model_forms.py b/netbox/users/forms/model_forms.py index 99320fa25..aa5811cd1 100644 --- a/netbox/users/forms/model_forms.py +++ b/netbox/users/forms/model_forms.py @@ -385,7 +385,7 @@ class ObjectPermissionForm(BootstrapMixin, forms.ModelForm): CONSTRAINT_TOKEN_USER: 0, # Replace token with a null user ID } model.objects.filter(qs_filter_from_constraints(constraints, tokens)).exists() - except FieldError as e: + except (FieldError, ValueError) as e: raise forms.ValidationError({ 'constraints': _('Invalid filter for {model}: {error}').format(model=model, error=e) }) From df910928f2f3cc429ffe50f9f8a40cc1664cbdc7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Feb 2024 09:26:45 -0500 Subject: [PATCH 07/16] Fixes #15126: group field should be optional when creating VPN tunnel via REST API --- netbox/vpn/api/serializers.py | 5 ++++- netbox/vpn/tests/test_api.py | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/netbox/vpn/api/serializers.py b/netbox/vpn/api/serializers.py index dedcbfbf5..5f6fcd5f7 100644 --- a/netbox/vpn/api/serializers.py +++ b/netbox/vpn/api/serializers.py @@ -46,7 +46,10 @@ class TunnelSerializer(NetBoxModelSerializer): status = ChoiceField( choices=TunnelStatusChoices ) - group = NestedTunnelGroupSerializer() + group = NestedTunnelGroupSerializer( + required=False, + allow_null=True + ) encapsulation = ChoiceField( choices=TunnelEncapsulationChoices ) diff --git a/netbox/vpn/tests/test_api.py b/netbox/vpn/tests/test_api.py index eb0520c8b..64c175fe5 100644 --- a/netbox/vpn/tests/test_api.py +++ b/netbox/vpn/tests/test_api.py @@ -105,7 +105,6 @@ class TunnelTest(APIViewTestCases.APIViewTestCase): { 'name': 'Tunnel 6', 'status': TunnelStatusChoices.STATUS_DISABLED, - 'group': tunnel_groups[1].pk, 'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE, }, ] From c37dfdc15028170863cb37379ea164ecf9129303 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Feb 2024 09:42:10 -0500 Subject: [PATCH 08/16] Fixes #15091: Fix initial active tab when editing an L2VPN termination --- netbox/templates/vpn/l2vpntermination_edit.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/templates/vpn/l2vpntermination_edit.html b/netbox/templates/vpn/l2vpntermination_edit.html index 0df2c883e..cbce78dbc 100644 --- a/netbox/templates/vpn/l2vpntermination_edit.html +++ b/netbox/templates/vpn/l2vpntermination_edit.html @@ -13,7 +13,7 @@
    -
    +
    {% render_field form.vlan %}
    From 12d830bcf2a552adcf423fc13f857a5f2f473aee Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Feb 2024 11:29:53 -0500 Subject: [PATCH 09/16] Fixes #15133: Fix FHRP group representation on assignments endpoint under brief mode (#15134) * Fixes #15133: Fix FHRP group representation on assignments endpoint under brief mode * Update API test --- netbox/ipam/api/nested_serializers.py | 3 ++- netbox/ipam/tests/test_api.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/netbox/ipam/api/nested_serializers.py b/netbox/ipam/api/nested_serializers.py index 17d8d74a7..c012eca6d 100644 --- a/netbox/ipam/api/nested_serializers.py +++ b/netbox/ipam/api/nested_serializers.py @@ -116,10 +116,11 @@ class NestedFHRPGroupSerializer(WritableNestedSerializer): class NestedFHRPGroupAssignmentSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:fhrpgroupassignment-detail') + group = NestedFHRPGroupSerializer() class Meta: model = models.FHRPGroupAssignment - fields = ['id', 'url', 'display', 'interface_type', 'interface_id', 'group_id', 'priority'] + fields = ['id', 'url', 'display', 'group', 'interface_type', 'interface_id', 'priority'] # diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index cb633e162..447415a69 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -760,7 +760,7 @@ class FHRPGroupTest(APIViewTestCases.APIViewTestCase): class FHRPGroupAssignmentTest(APIViewTestCases.APIViewTestCase): model = FHRPGroupAssignment - brief_fields = ['display', 'group_id', 'id', 'interface_id', 'interface_type', 'priority', 'url'] + brief_fields = ['display', 'group', 'id', 'interface_id', 'interface_type', 'priority', 'url'] bulk_update_data = { 'priority': 100, } From 01fa2710eba125dbc49e5378198fc10f178e58bd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Feb 2024 10:20:26 -0500 Subject: [PATCH 10/16] Fixes #15067: Fix uncaught exception when attempting invalid device bay import --- netbox/dcim/forms/bulk_import.py | 2 +- netbox/dcim/models/device_components.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index f30ff91fa..732bb87ae 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -996,7 +996,7 @@ class DeviceBayImportForm(NetBoxModelImportForm): device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD ).exclude(pk=device.pk) else: - self.fields['installed_device'].queryset = Interface.objects.none() + self.fields['installed_device'].queryset = Device.objects.none() class InventoryItemImportForm(NetBoxModelImportForm): diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 88dddb312..5b2564b32 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -1133,13 +1133,13 @@ class DeviceBay(ComponentModel, TrackingModelMixin): super().clean() # Validate that the parent Device can have DeviceBays - if not self.device.device_type.is_parent_device: + if hasattr(self, 'device') and not self.device.device_type.is_parent_device: raise ValidationError(_("This type of device ({device_type}) does not support device bays.").format( device_type=self.device.device_type )) # Cannot install a device into itself, obviously - if self.device == self.installed_device: + if self.installed_device and getattr(self, 'device', None) == self.installed_device: raise ValidationError(_("Cannot install a device into itself.")) # Check that the installed device is not already installed elsewhere From 2d70b502868d1fd5f4e01b12621c19966b92168a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Feb 2024 10:36:23 -0500 Subject: [PATCH 11/16] Fixes #15059: Correct IP address count link in VM interfaces table --- netbox/dcim/tables/template_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index 3f8b63688..de27d67ad 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -37,7 +37,7 @@ DEVICEBAY_STATUS = """ INTERFACE_IPADDRESSES = """
    {% if value.count >= 3 %} - {{ value.count }} + {{ value.count }} {% else %} {% for ip in value.all %} {% if ip.status != 'active' %} From e84e2a7969c1dcfc592ecce83aeedd948779f2de Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Feb 2024 16:39:41 -0500 Subject: [PATCH 12/16] Changelog for #15059, #15067, #15091, #15115, #15126, #15133 --- docs/release-notes/version-3.7.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes/version-3.7.md b/docs/release-notes/version-3.7.md index e3c2f8e48..72c15f00c 100644 --- a/docs/release-notes/version-3.7.md +++ b/docs/release-notes/version-3.7.md @@ -4,8 +4,14 @@ ### Bug Fixes +* [#15059](https://github.com/netbox-community/netbox/issues/15059) - Correct IP address count link in VM interfaces table +* [#15067](https://github.com/netbox-community/netbox/issues/15067) - Fix uncaught exception when attempting invalid device bay import * [#15070](https://github.com/netbox-community/netbox/issues/15070) - Fix inclusion of `config_template` field on REST API serializer for virtual machines * [#15084](https://github.com/netbox-community/netbox/issues/15084) - Fix "add export template" link under "export" button on object list views +* [#15091](https://github.com/netbox-community/netbox/issues/15091) - Fix designation of the active tab for assigned object when modifying an L2VPN termination +* [#15115](https://github.com/netbox-community/netbox/issues/15115) - Fix unhandled exception with invalid permission constraints +* [#15126](https://github.com/netbox-community/netbox/issues/15126) - `group` field should be optional when creating VPN tunnel via REST API +* [#15133](https://github.com/netbox-community/netbox/issues/15133) - Fix FHRP group representation on assignments REST API endpoint using brief mode --- From 7cc215437f0b4160b0edd6d6ba2283b6ff019449 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Feb 2024 09:16:42 -0500 Subject: [PATCH 13/16] Fixes #15127: Add missing group column on tunnels table --- netbox/vpn/tables/tunnels.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/netbox/vpn/tables/tunnels.py b/netbox/vpn/tables/tunnels.py index c10985733..bc591c1e6 100644 --- a/netbox/vpn/tables/tunnels.py +++ b/netbox/vpn/tables/tunnels.py @@ -40,6 +40,10 @@ class TunnelTable(TenancyColumnsMixin, NetBoxTable): verbose_name=_('Name'), linkify=True ) + group = tables.Column( + verbose_name=_('Group'), + linkify=True + ) status = columns.ChoiceFieldColumn( verbose_name=_('Status') ) @@ -63,10 +67,10 @@ class TunnelTable(TenancyColumnsMixin, NetBoxTable): class Meta(NetBoxTable.Meta): model = Tunnel fields = ( - 'pk', 'id', 'name', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'tenant_group', 'tunnel_id', - 'termination_count', 'description', 'comments', 'tags', 'created', 'last_updated', + 'pk', 'id', 'name', 'group', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'tenant_group', + 'tunnel_id', 'termination_count', 'description', 'comments', 'tags', 'created', 'last_updated', ) - default_columns = ('pk', 'name', 'status', 'encapsulation', 'tenant', 'terminations_count') + default_columns = ('pk', 'name', 'group', 'status', 'encapsulation', 'tenant', 'terminations_count') class TunnelTerminationTable(TenancyColumnsMixin, NetBoxTable): From 2e74952ac6cc68348284dee8b9517fe0a36179a2 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 16 Feb 2024 01:20:54 +0530 Subject: [PATCH 14/16] added missing import #15058 --- netbox/core/api/schema.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/core/api/schema.py b/netbox/core/api/schema.py index b7e537c23..8eecfa8b9 100644 --- a/netbox/core/api/schema.py +++ b/netbox/core/api/schema.py @@ -8,6 +8,7 @@ from drf_spectacular.plumbing import ( build_basic_type, build_choice_field, build_media_type_object, build_object_type, get_doc, ) from drf_spectacular.types import OpenApiTypes +from rest_framework import serializers from rest_framework.relations import ManyRelatedField from netbox.api.fields import ChoiceField, SerializedPKRelatedField From de5c5aeb2a963a7ab962074dbef698e1ac24441d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 16 Feb 2024 09:00:22 -0500 Subject: [PATCH 15/16] Fixes #14952: Update existing AutoSyncRecord when changing the data file of an auto-synced object --- netbox/netbox/models/features.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index a13b84bed..6eb2b36e1 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -489,10 +489,10 @@ class SyncedDataMixin(models.Model): # Create/delete AutoSyncRecord as needed content_type = ContentType.objects.get_for_model(self) if self.auto_sync_enabled: - AutoSyncRecord.objects.get_or_create( - datafile=self.data_file, + AutoSyncRecord.objects.update_or_create( object_type=content_type, - object_id=self.pk + object_id=self.pk, + defaults={'datafile': self.data_file} ) else: AutoSyncRecord.objects.filter( From bd7d4a3f345ae2d61f6418d82532477fb8a5b903 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 15 Feb 2024 16:26:28 -0500 Subject: [PATCH 16/16] Fixes #14079: Explicitly remove M2M assignments to objects being deleted to ensure change logging --- netbox/extras/signals.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/netbox/extras/signals.py b/netbox/extras/signals.py index 4c15e839a..d3c6da060 100644 --- a/netbox/extras/signals.py +++ b/netbox/extras/signals.py @@ -3,6 +3,7 @@ import logging from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError +from django.db.models.fields.reverse_related import ManyToManyRel from django.db.models.signals import m2m_changed, post_save, pre_delete from django.dispatch import receiver, Signal from django.utils.translation import gettext_lazy as _ @@ -15,6 +16,7 @@ from extras.models import EventRule from extras.validators import CustomValidator from netbox.config import get_config from netbox.context import current_request, events_queue +from netbox.models.features import ChangeLoggingMixin from netbox.signals import post_clean from utilities.exceptions import AbortRequest from .choices import ObjectChangeActionChoices @@ -68,7 +70,7 @@ def handle_changed_object(sender, instance, **kwargs): else: return - # Create/update an ObejctChange record for this change + # Create/update an ObjectChange record for this change objectchange = instance.to_objectchange(action) # If this is a many-to-many field change, check for a previous ObjectChange instance recorded # for this object by this request and update it @@ -122,6 +124,25 @@ def handle_deleted_object(sender, instance, **kwargs): objectchange.request_id = request.id objectchange.save() + # Django does not automatically send an m2m_changed signal for the reverse direction of a + # many-to-many relationship (see https://code.djangoproject.com/ticket/17688), so we need to + # trigger one manually. We do this by checking for any reverse M2M relationships on the + # instance being deleted, and explicitly call .remove() on the remote M2M field to delete + # the association. This triggers an m2m_changed signal with the `post_remove` action type + # for the forward direction of the relationship, ensuring that the change is recorded. + for relation in instance._meta.related_objects: + if type(relation) is not ManyToManyRel: + continue + related_model = relation.related_model + related_field_name = relation.remote_field.name + if not issubclass(related_model, ChangeLoggingMixin): + # We only care about triggering the m2m_changed signal for models which support + # change logging + continue + for obj in related_model.objects.filter(**{related_field_name: instance.pk}): + obj.snapshot() # Ensure the change record includes the "before" state + getattr(obj, related_field_name).remove(instance) + # Enqueue webhooks queue = events_queue.get() enqueue_object(queue, instance, request.user, request.id, ObjectChangeActionChoices.ACTION_DELETE)