tracking.py 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. from django.db.models.query_utils import DeferredAttribute
  2. from netbox.registry import registry
  3. class Tracker:
  4. """
  5. An ephemeral instance employed to record which tracked fields on an instance have been modified.
  6. """
  7. def __init__(self):
  8. self._changed_fields = {}
  9. def __contains__(self, item):
  10. return item in self._changed_fields
  11. def set(self, name, value):
  12. """
  13. Mark an attribute as having been changed and record its original value.
  14. """
  15. self._changed_fields[name] = value
  16. def get(self, name):
  17. """
  18. Return the original value of a changed field. Raises KeyError if name is not found.
  19. """
  20. return self._changed_fields[name]
  21. def clear(self, *names):
  22. """
  23. Clear any fields that were recorded as having been changed.
  24. """
  25. for name in names:
  26. self._changed_fields.pop(name, None)
  27. else:
  28. self._changed_fields = {}
  29. class TrackingModelMixin:
  30. def __init__(self, *args, **kwargs):
  31. super().__init__(*args, **kwargs)
  32. # Mark the instance as initialized, to enable our custom __setattr__()
  33. self._initialized = True
  34. @property
  35. def tracker(self):
  36. """
  37. Return the Tracker instance for this instance, first creating it if necessary.
  38. """
  39. if not hasattr(self._state, "_tracker"):
  40. self._state._tracker = Tracker()
  41. return self._state._tracker
  42. def save(self, *args, **kwargs):
  43. super().save(*args, **kwargs)
  44. # Clear any tracked fields now that changes have been saved
  45. update_fields = kwargs.get('update_fields', [])
  46. self.tracker.clear(*update_fields)
  47. def __setattr__(self, name, value):
  48. if hasattr(self, "_initialized"):
  49. # Record any changes to a tracked field
  50. if name in registry['counter_fields'][self.__class__]:
  51. if name not in self.tracker:
  52. # The attribute has been created or changed
  53. if name in self.__dict__:
  54. old_value = getattr(self, name)
  55. if value != old_value:
  56. self.tracker.set(name, old_value)
  57. else:
  58. self.tracker.set(name, DeferredAttribute)
  59. elif value == self.tracker.get(name):
  60. # A previously changed attribute has been restored
  61. self.tracker.clear(name)
  62. super().__setattr__(name, value)