14147 Prevent logging to Change Log when no changes are made

This commit is contained in:
Arthur 2023-12-08 11:32:59 -08:00
parent d428dd172c
commit f6f1fa799e
14 changed files with 50 additions and 14 deletions

View File

@ -80,6 +80,17 @@ changes in the database indefinitely.
--- ---
## CHANGELOG_SKIP_EMPTY_CHANGES
Default: False
Enables skipping the creation of logged changes on updates if there were no modifications to the object.
!!! note
As a side-effect of turning this on, the `last_updated` field will not be included in the change log record.
---
## DATA_UPLOAD_MAX_MEMORY_SIZE ## DATA_UPLOAD_MAX_MEMORY_SIZE
Default: `2621440` (2.5 MB) Default: `2621440` (2.5 MB)

View File

@ -239,7 +239,8 @@ class CircuitTermination(
raise ValidationError("A circuit termination cannot attach to both a site and a provider network.") raise ValidationError("A circuit termination cannot attach to both a site and a provider network.")
def to_objectchange(self, action): def to_objectchange(self, action):
objectchange = super().to_objectchange(action) if (objectchange := super().to_objectchange(action)) is None:
return None
objectchange.related_object = self.circuit objectchange.related_object = self.circuit
return objectchange return objectchange

View File

@ -386,7 +386,8 @@ class CableTermination(ChangeLoggedModel):
cache_related_objects.alters_data = True cache_related_objects.alters_data = True
def to_objectchange(self, action): def to_objectchange(self, action):
objectchange = super().to_objectchange(action) if (objectchange := super().to_objectchange(action)) is None:
return None
objectchange.related_object = self.termination objectchange.related_object = self.termination
return objectchange return objectchange

View File

@ -91,7 +91,8 @@ class ComponentTemplateModel(ChangeLoggedModel, TrackingModelMixin):
self._original_device_type = self.__dict__.get('device_type_id') self._original_device_type = self.__dict__.get('device_type_id')
def to_objectchange(self, action): def to_objectchange(self, action):
objectchange = super().to_objectchange(action) if (objectchange := super().to_objectchange(action)) is None:
return None
objectchange.related_object = self.device_type objectchange.related_object = self.device_type
return objectchange return objectchange
@ -138,7 +139,8 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
) )
def to_objectchange(self, action): def to_objectchange(self, action):
objectchange = super().to_objectchange(action) if (objectchange := super().to_objectchange(action)) is None:
return None
if self.device_type is not None: if self.device_type is not None:
objectchange.related_object = self.device_type objectchange.related_object = self.device_type
elif self.module_type is not None: elif self.module_type is not None:

View File

@ -93,7 +93,8 @@ class ComponentModel(NetBoxModel):
return self.name return self.name
def to_objectchange(self, action): def to_objectchange(self, action):
objectchange = super().to_objectchange(action) if (objectchange := super().to_objectchange(action)) is None:
return None
objectchange.related_object = self.device objectchange.related_object = self.device
return objectchange return objectchange

View File

@ -688,7 +688,8 @@ class ImageAttachment(ChangeLoggedModel):
return None return None
def to_objectchange(self, action): def to_objectchange(self, action):
objectchange = super().to_objectchange(action) if (objectchange := super().to_objectchange(action)) is None:
return None
objectchange.related_object = self.parent objectchange.related_object = self.parent
return objectchange return objectchange

View File

@ -80,9 +80,10 @@ def handle_changed_object(sender, instance, **kwargs):
) )
else: else:
objectchange = instance.to_objectchange(action) objectchange = instance.to_objectchange(action)
objectchange.user = request.user if objectchange:
objectchange.request_id = request.id objectchange.user = request.user
objectchange.save() objectchange.request_id = request.id
objectchange.save()
# If this is an M2M change, update the previously queued webhook (from post_save) # If this is an M2M change, update the previously queued webhook (from post_save)
queue = events_queue.get() queue = events_queue.get()

View File

@ -902,7 +902,8 @@ class IPAddress(PrimaryModel):
return attrs return attrs
def to_objectchange(self, action): def to_objectchange(self, action):
objectchange = super().to_objectchange(action) if (objectchange := super().to_objectchange(action)) is None:
return None
objectchange.related_object = self.assigned_object objectchange.related_object = self.assigned_object
return objectchange return objectchange

View File

@ -15,6 +15,7 @@ from core.choices import JobStatusChoices
from core.models import ContentType from core.models import ContentType
from extras.choices import * from extras.choices import *
from extras.utils import is_taggable, register_features from extras.utils import is_taggable, register_features
from netbox.config import get_config
from netbox.registry import registry from netbox.registry import registry
from netbox.signals import post_clean from netbox.signals import post_clean
from utilities.json import CustomFieldJSONEncoder from utilities.json import CustomFieldJSONEncoder
@ -84,6 +85,15 @@ class ChangeLoggingMixin(models.Model):
by ChangeLoggingMiddleware. by ChangeLoggingMiddleware.
""" """
from extras.models import ObjectChange from extras.models import ObjectChange
postchange_data = None
if action in (ObjectChangeActionChoices.ACTION_CREATE, ObjectChangeActionChoices.ACTION_UPDATE):
postchange_data = self.serialize_object()
if get_config().CHANGELOG_SKIP_EMPTY_CHANGES and action == ObjectChangeActionChoices.ACTION_UPDATE and hasattr(self, '_prechange_snapshot'):
if postchange_data == self._prechange_snapshot:
return None
objectchange = ObjectChange( objectchange = ObjectChange(
changed_object=self, changed_object=self,
object_repr=str(self)[:200], object_repr=str(self)[:200],
@ -92,7 +102,7 @@ class ChangeLoggingMixin(models.Model):
if hasattr(self, '_prechange_snapshot'): if hasattr(self, '_prechange_snapshot'):
objectchange.prechange_data = self._prechange_snapshot objectchange.prechange_data = self._prechange_snapshot
if action in (ObjectChangeActionChoices.ACTION_CREATE, ObjectChangeActionChoices.ACTION_UPDATE): if action in (ObjectChangeActionChoices.ACTION_CREATE, ObjectChangeActionChoices.ACTION_UPDATE):
objectchange.postchange_data = self.serialize_object() objectchange.postchange_data = postchange_data
return objectchange return objectchange

View File

@ -177,6 +177,7 @@ STORAGE_CONFIG = getattr(configuration, 'STORAGE_CONFIG', {})
TIME_FORMAT = getattr(configuration, 'TIME_FORMAT', 'g:i a') TIME_FORMAT = getattr(configuration, 'TIME_FORMAT', 'g:i a')
TIME_ZONE = getattr(configuration, 'TIME_ZONE', 'UTC') TIME_ZONE = getattr(configuration, 'TIME_ZONE', 'UTC')
ENABLE_LOCALIZATION = getattr(configuration, 'ENABLE_LOCALIZATION', False) ENABLE_LOCALIZATION = getattr(configuration, 'ENABLE_LOCALIZATION', False)
CHANGELOG_SKIP_EMPTY_CHANGES = getattr(configuration, 'CHANGELOG_SKIP_EMPTY_CHANGES', False)
# Check for hard-coded dynamic config parameters # Check for hard-coded dynamic config parameters
for param in PARAMS: for param in PARAMS:

View File

@ -171,6 +171,7 @@ class ContactAssignment(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, Chan
) )
def to_objectchange(self, action): def to_objectchange(self, action):
objectchange = super().to_objectchange(action) if (objectchange := super().to_objectchange(action)) is None:
return None
objectchange.related_object = self.object objectchange.related_object = self.object
return objectchange return objectchange

View File

@ -159,6 +159,9 @@ def serialize_object(obj, resolve_tags=True, extra=None):
for field in ['level', 'lft', 'rght', 'tree_id']: for field in ['level', 'lft', 'rght', 'tree_id']:
data.pop(field) data.pop(field)
if get_config().CHANGELOG_SKIP_EMPTY_CHANGES and 'last_updated' in data:
data.pop('last_updated')
# Include custom_field_data as "custom_fields" # Include custom_field_data as "custom_fields"
if hasattr(obj, 'custom_field_data'): if hasattr(obj, 'custom_field_data'):
data['custom_fields'] = data.pop('custom_field_data') data['custom_fields'] = data.pop('custom_field_data')

View File

@ -298,7 +298,8 @@ class ComponentModel(NetBoxModel):
return self.name return self.name
def to_objectchange(self, action): def to_objectchange(self, action):
objectchange = super().to_objectchange(action) if (objectchange := super().to_objectchange(action)) is None:
return None
objectchange.related_object = self.virtual_machine objectchange.related_object = self.virtual_machine
return objectchange return objectchange

View File

@ -178,6 +178,7 @@ class TunnelTermination(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ChangeLo
}) })
def to_objectchange(self, action): def to_objectchange(self, action):
objectchange = super().to_objectchange(action) if (objectchange := super().to_objectchange(action)) is None:
return None
objectchange.related_object = self.tunnel objectchange.related_object = self.tunnel
return objectchange return objectchange