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