staging.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import logging
  2. from django.contrib.auth import get_user_model
  3. from django.contrib.contenttypes.fields import GenericForeignKey
  4. from django.contrib.contenttypes.models import ContentType
  5. from django.db import models, transaction
  6. from extras.choices import ChangeActionChoices
  7. from netbox.models import ChangeLoggedModel
  8. from utilities.utils import deserialize_object
  9. __all__ = (
  10. 'Branch',
  11. 'StagedChange',
  12. )
  13. logger = logging.getLogger('netbox.staging')
  14. class Branch(ChangeLoggedModel):
  15. """
  16. A collection of related StagedChanges.
  17. """
  18. name = models.CharField(
  19. max_length=100,
  20. unique=True
  21. )
  22. description = models.CharField(
  23. max_length=200,
  24. blank=True
  25. )
  26. user = models.ForeignKey(
  27. to=get_user_model(),
  28. on_delete=models.SET_NULL,
  29. blank=True,
  30. null=True
  31. )
  32. class Meta:
  33. ordering = ('name',)
  34. def __str__(self):
  35. return f'{self.name} ({self.pk})'
  36. def merge(self):
  37. logger.info(f'Merging changes in branch {self}')
  38. with transaction.atomic():
  39. for change in self.staged_changes.all():
  40. change.apply()
  41. self.staged_changes.all().delete()
  42. class StagedChange(ChangeLoggedModel):
  43. """
  44. The prepared creation, modification, or deletion of an object to be applied to the active database at a
  45. future point.
  46. """
  47. branch = models.ForeignKey(
  48. to=Branch,
  49. on_delete=models.CASCADE,
  50. related_name='staged_changes'
  51. )
  52. action = models.CharField(
  53. max_length=20,
  54. choices=ChangeActionChoices
  55. )
  56. object_type = models.ForeignKey(
  57. to=ContentType,
  58. on_delete=models.CASCADE,
  59. related_name='+'
  60. )
  61. object_id = models.PositiveBigIntegerField(
  62. blank=True,
  63. null=True
  64. )
  65. object = GenericForeignKey(
  66. ct_field='object_type',
  67. fk_field='object_id'
  68. )
  69. data = models.JSONField(
  70. blank=True,
  71. null=True
  72. )
  73. class Meta:
  74. ordering = ('pk',)
  75. def __str__(self):
  76. action = self.get_action_display()
  77. app_label, model_name = self.object_type.natural_key()
  78. return f"{action} {app_label}.{model_name} ({self.object_id})"
  79. @property
  80. def model(self):
  81. return self.object_type.model_class()
  82. def apply(self):
  83. """
  84. Apply the staged create/update/delete action to the database.
  85. """
  86. if self.action == ChangeActionChoices.ACTION_CREATE:
  87. instance = deserialize_object(self.model, self.data, pk=self.object_id)
  88. logger.info(f'Creating {self.model._meta.verbose_name} {instance}')
  89. instance.save()
  90. if self.action == ChangeActionChoices.ACTION_UPDATE:
  91. instance = deserialize_object(self.model, self.data, pk=self.object_id)
  92. logger.info(f'Updating {self.model._meta.verbose_name} {instance}')
  93. instance.save()
  94. if self.action == ChangeActionChoices.ACTION_DELETE:
  95. instance = self.model.objects.get(pk=self.object_id)
  96. logger.info(f'Deleting {self.model._meta.verbose_name} {instance}')
  97. instance.delete()
  98. apply.alters_data = True
  99. def get_action_color(self):
  100. return ChangeActionChoices.colors.get(self.action)