1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00
2024-03-04 08:33:44 -05:00

142 lines
4.2 KiB
Python

from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from core.models import ObjectType
from extras.choices import *
from ..querysets import ObjectChangeQuerySet
__all__ = (
'ObjectChange',
)
class ObjectChange(models.Model):
"""
Record a change to an object and the user account associated with that change. A change record may optionally
indicate an object related to the one being changed. For example, a change to an interface may also indicate the
parent device. This will ensure changes made to component models appear in the parent model's changelog.
"""
time = models.DateTimeField(
verbose_name=_('time'),
auto_now_add=True,
editable=False,
db_index=True
)
user = models.ForeignKey(
to=settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
related_name='changes',
blank=True,
null=True
)
user_name = models.CharField(
verbose_name=_('user name'),
max_length=150,
editable=False
)
request_id = models.UUIDField(
verbose_name=_('request ID'),
editable=False,
db_index=True
)
action = models.CharField(
verbose_name=_('action'),
max_length=50,
choices=ObjectChangeActionChoices
)
changed_object_type = models.ForeignKey(
to='contenttypes.ContentType',
on_delete=models.PROTECT,
related_name='+'
)
changed_object_id = models.PositiveBigIntegerField()
changed_object = GenericForeignKey(
ct_field='changed_object_type',
fk_field='changed_object_id'
)
related_object_type = models.ForeignKey(
to='contenttypes.ContentType',
on_delete=models.PROTECT,
related_name='+',
blank=True,
null=True
)
related_object_id = models.PositiveBigIntegerField(
blank=True,
null=True
)
related_object = GenericForeignKey(
ct_field='related_object_type',
fk_field='related_object_id'
)
object_repr = models.CharField(
max_length=200,
editable=False
)
prechange_data = models.JSONField(
verbose_name=_('pre-change data'),
editable=False,
blank=True,
null=True
)
postchange_data = models.JSONField(
verbose_name=_('post-change data'),
editable=False,
blank=True,
null=True
)
objects = ObjectChangeQuerySet.as_manager()
class Meta:
ordering = ['-time']
indexes = (
models.Index(fields=('changed_object_type', 'changed_object_id')),
models.Index(fields=('related_object_type', 'related_object_id')),
)
verbose_name = _('object change')
verbose_name_plural = _('object changes')
def __str__(self):
return '{} {} {} by {}'.format(
self.changed_object_type,
self.object_repr,
self.get_action_display().lower(),
self.user_name
)
def clean(self):
super().clean()
# Validate the assigned object type
if self.changed_object_type not in ObjectType.objects.with_feature('change_logging'):
raise ValidationError(
_("Change logging is not supported for this object type ({type}).").format(
type=self.changed_object_type
)
)
def save(self, *args, **kwargs):
# Record the user's name and the object's representation as static strings
if not self.user_name:
self.user_name = self.user.username
if not self.object_repr:
self.object_repr = str(self.changed_object)
return super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('extras:objectchange', args=[self.pk])
def get_action_color(self):
return ObjectChangeActionChoices.colors.get(self.action)
@property
def has_changes(self):
return self.prechange_data != self.postchange_data