1from django.contrib.auth.models import User 2from django.contrib.contenttypes.fields import GenericForeignKey 3from django.contrib.contenttypes.models import ContentType 4from django.db import models 5from django.urls import reverse 6 7from extras.choices import * 8from netbox.models import BigIDModel 9from utilities.querysets import RestrictedQuerySet 10 11 12class ObjectChange(BigIDModel): 13 """ 14 Record a change to an object and the user account associated with that change. A change record may optionally 15 indicate an object related to the one being changed. For example, a change to an interface may also indicate the 16 parent device. This will ensure changes made to component models appear in the parent model's changelog. 17 """ 18 time = models.DateTimeField( 19 auto_now_add=True, 20 editable=False, 21 db_index=True 22 ) 23 user = models.ForeignKey( 24 to=User, 25 on_delete=models.SET_NULL, 26 related_name='changes', 27 blank=True, 28 null=True 29 ) 30 user_name = models.CharField( 31 max_length=150, 32 editable=False 33 ) 34 request_id = models.UUIDField( 35 editable=False 36 ) 37 action = models.CharField( 38 max_length=50, 39 choices=ObjectChangeActionChoices 40 ) 41 changed_object_type = models.ForeignKey( 42 to=ContentType, 43 on_delete=models.PROTECT, 44 related_name='+' 45 ) 46 changed_object_id = models.PositiveIntegerField() 47 changed_object = GenericForeignKey( 48 ct_field='changed_object_type', 49 fk_field='changed_object_id' 50 ) 51 related_object_type = models.ForeignKey( 52 to=ContentType, 53 on_delete=models.PROTECT, 54 related_name='+', 55 blank=True, 56 null=True 57 ) 58 related_object_id = models.PositiveIntegerField( 59 blank=True, 60 null=True 61 ) 62 related_object = GenericForeignKey( 63 ct_field='related_object_type', 64 fk_field='related_object_id' 65 ) 66 object_repr = models.CharField( 67 max_length=200, 68 editable=False 69 ) 70 prechange_data = models.JSONField( 71 editable=False, 72 blank=True, 73 null=True 74 ) 75 postchange_data = models.JSONField( 76 editable=False, 77 blank=True, 78 null=True 79 ) 80 81 objects = RestrictedQuerySet.as_manager() 82 83 class Meta: 84 ordering = ['-time'] 85 86 def __str__(self): 87 return '{} {} {} by {}'.format( 88 self.changed_object_type, 89 self.object_repr, 90 self.get_action_display().lower(), 91 self.user_name 92 ) 93 94 def save(self, *args, **kwargs): 95 96 # Record the user's name and the object's representation as static strings 97 if not self.user_name: 98 self.user_name = self.user.username 99 if not self.object_repr: 100 self.object_repr = str(self.changed_object) 101 102 return super().save(*args, **kwargs) 103 104 def get_absolute_url(self): 105 return reverse('extras:objectchange', args=[self.pk]) 106 107 def get_action_class(self): 108 return ObjectChangeActionChoices.CSS_CLASSES.get(self.action) 109