diff --git a/netbox/netbox/api/serializers/__init__.py b/netbox/netbox/api/serializers/__init__.py index 0ec3ab5f3..d7ad19565 100644 --- a/netbox/netbox/api/serializers/__init__.py +++ b/netbox/netbox/api/serializers/__init__.py @@ -10,7 +10,12 @@ from .nested import * # Base model serializers # -class NetBoxModelSerializer(TaggableModelSerializer, CustomFieldModelSerializer, ValidatedModelSerializer): +class NetBoxModelSerializer( + ChangeLogMessageSerializer, + TaggableModelSerializer, + CustomFieldModelSerializer, + ValidatedModelSerializer +): """ Adds support for custom fields and tags. """ @@ -24,5 +29,5 @@ class NestedGroupModelSerializer(NetBoxModelSerializer): _depth = serializers.IntegerField(source='level', read_only=True) -class BulkOperationSerializer(serializers.Serializer): +class BulkOperationSerializer(ChangeLogMessageSerializer): id = serializers.IntegerField() diff --git a/netbox/netbox/api/serializers/features.py b/netbox/netbox/api/serializers/features.py index 3bd5c8a2d..985fcc963 100644 --- a/netbox/netbox/api/serializers/features.py +++ b/netbox/netbox/api/serializers/features.py @@ -5,6 +5,7 @@ from extras.api.customfields import CustomFieldsDataField, CustomFieldDefaultVal from .nested import NestedTagSerializer __all__ = ( + 'ChangeLogMessageSerializer', 'CustomFieldModelSerializer', 'TaggableModelSerializer', ) @@ -54,3 +55,22 @@ class TaggableModelSerializer(serializers.Serializer): instance.tags.clear() return instance + + +class ChangeLogMessageSerializer(serializers.Serializer): + changelog_message = serializers.CharField(write_only=True) + + def to_internal_value(self, data): + ret = super().to_internal_value(data) + + # Workaround to bypass requirement to include changelog_message in Meta.fields on every serializer + if 'changelog_message' in data and 'changelog_message' not in ret: + # TODO: Validation + ret['changelog_message'] = data['changelog_message'] + + return ret + + def save(self, **kwargs): + if self.instance is not None: + self.instance._changelog_message = self.validated_data.get('changelog_message') + return super().save(**kwargs) diff --git a/netbox/netbox/api/viewsets/__init__.py b/netbox/netbox/api/viewsets/__init__.py index 2039f735b..6241be4cd 100644 --- a/netbox/netbox/api/viewsets/__init__.py +++ b/netbox/netbox/api/viewsets/__init__.py @@ -7,9 +7,11 @@ from django.db.models import ProtectedError, RestrictedError from django_pglocks import advisory_lock from netbox.constants import ADVISORY_LOCK_KEYS from rest_framework import mixins as drf_mixins +from rest_framework import status from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet +from netbox.api.serializers.features import ChangeLogMessageSerializer from utilities.api import get_annotations_for_serializer, get_prefetches_for_serializer from utilities.exceptions import AbortRequest from utilities.query import reapply_model_ordering @@ -199,9 +201,16 @@ class NetBoxModelViewSet( # Deletes def destroy(self, request, *args, **kwargs): - # Hotwire get_object() to ensure we save a pre-change snapshot - self.get_object = self.get_object_with_snapshot - return super().destroy(request, *args, **kwargs) + instance = self.get_object_with_snapshot() + + # Attach changelog message (if any) + serializer = ChangeLogMessageSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + instance._changelog_message = serializer.validated_data.get('changelog_message') + + self.perform_destroy(instance) + + return Response(status=status.HTTP_204_NO_CONTENT) def perform_destroy(self, instance): model = self.queryset.model diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 023259cc8..893131336 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -63,11 +63,14 @@ class ChangeLoggingMixin(DeleteMixin, models.Model): null=True ) - _changelog_message = None - class Meta: abstract = True + def __init__(self, *args, **kwargs): + changelog_message = kwargs.pop('changelog_message', None) + super().__init__(*args, **kwargs) + self._changelog_message = changelog_message + def serialize_object(self, exclude=None): """ Return a JSON representation of the instance. Models can override this method to replace or extend the default