diff --git a/netbox/extras/migrations/0084_staging.py b/netbox/extras/migrations/0084_staging.py index 4f300d49a..25c3f164f 100644 --- a/netbox/extras/migrations/0084_staging.py +++ b/netbox/extras/migrations/0084_staging.py @@ -1,5 +1,3 @@ -# Generated by Django 4.1.2 on 2022-11-08 16:25 - from django.conf import settings from django.db import migrations, models import django.db.models.deletion @@ -29,7 +27,7 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='Change', + name='StagedChange', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), ('created', models.DateTimeField(auto_now_add=True, null=True)), @@ -37,7 +35,7 @@ class Migration(migrations.Migration): ('action', models.CharField(max_length=20)), ('object_id', models.PositiveBigIntegerField(blank=True, null=True)), ('data', models.JSONField(blank=True, null=True)), - ('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='changes', to='extras.branch')), + ('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staged_changes', to='extras.branch')), ('object_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype')), ], options={ diff --git a/netbox/extras/models/__init__.py b/netbox/extras/models/__init__.py index e992822f4..9b5c660c4 100644 --- a/netbox/extras/models/__init__.py +++ b/netbox/extras/models/__init__.py @@ -7,9 +7,8 @@ from .staging import * from .tags import Tag, TaggedItem __all__ = ( - 'CachedValue', - 'Change', 'Branch', + 'CachedValue', 'ConfigContext', 'ConfigContextModel', 'ConfigRevision', @@ -23,6 +22,7 @@ __all__ = ( 'Report', 'SavedFilter', 'Script', + 'StagedChange', 'Tag', 'TaggedItem', 'Webhook', diff --git a/netbox/extras/models/staging.py b/netbox/extras/models/staging.py index e68f2ca23..b46d6a7bc 100644 --- a/netbox/extras/models/staging.py +++ b/netbox/extras/models/staging.py @@ -11,13 +11,16 @@ from utilities.utils import deserialize_object __all__ = ( 'Branch', - 'Change', + 'StagedChange', ) logger = logging.getLogger('netbox.staging') class Branch(ChangeLoggedModel): + """ + A collection of related StagedChanges. + """ name = models.CharField( max_length=100, unique=True @@ -42,16 +45,20 @@ class Branch(ChangeLoggedModel): def merge(self): logger.info(f'Merging changes in branch {self}') with transaction.atomic(): - for change in self.changes.all(): + for change in self.staged_changes.all(): change.apply() - self.changes.all().delete() + self.staged_changes.all().delete() -class Change(ChangeLoggedModel): +class StagedChange(ChangeLoggedModel): + """ + The prepared creation, modification, or deletion of an object to be applied to the active database at a + future point. + """ branch = models.ForeignKey( to=Branch, on_delete=models.CASCADE, - related_name='changes' + related_name='staged_changes' ) action = models.CharField( max_length=20, @@ -79,7 +86,9 @@ class Change(ChangeLoggedModel): ordering = ('pk',) def __str__(self): - return f"{self.get_action_display()} {self.model}" + action = self.get_action_display() + app_label, model_name = self.object_type.natural_key() + return f"{action} {app_label}.{model_name} ({self.object_id})" @property def model(self): diff --git a/netbox/netbox/staging.py b/netbox/netbox/staging.py index c18bd86ce..ec38dcadc 100644 --- a/netbox/netbox/staging.py +++ b/netbox/netbox/staging.py @@ -5,7 +5,7 @@ from django.db import transaction from django.db.models.signals import m2m_changed, pre_delete, post_save from extras.choices import ChangeActionChoices -from extras.models import Change +from extras.models import StagedChange from utilities.utils import serialize_object logger = logging.getLogger('netbox.staging') @@ -36,10 +36,10 @@ class checkout: transaction.set_autocommit(False) # Apply any existing Changes assigned to this Branch - changes = self.branch.changes.all() - if change_count := changes.count(): + staged_changes = self.branch.staged_changes.all() + if change_count := staged_changes.count(): logger.debug(f"Applying {change_count} pre-staged changes...") - for change in changes: + for change in staged_changes: change.apply() else: logger.debug("No pre-staged changes found") @@ -91,7 +91,7 @@ class checkout: object_type, pk = key action, data = change - changes.append(Change( + changes.append(StagedChange( branch=self.branch, action=action, object_type=object_type, @@ -100,7 +100,7 @@ class checkout: )) # Save all Change instances to the database - Change.objects.bulk_create(changes) + StagedChange.objects.bulk_create(changes) # # Signal handlers diff --git a/netbox/netbox/tests/test_staging.py b/netbox/netbox/tests/test_staging.py index 8ef76e649..ed3a69f10 100644 --- a/netbox/netbox/tests/test_staging.py +++ b/netbox/netbox/tests/test_staging.py @@ -2,7 +2,7 @@ from django.test import TransactionTestCase from circuits.models import Provider, Circuit, CircuitType from extras.choices import ChangeActionChoices -from extras.models import Branch, Change, Tag +from extras.models import Branch, StagedChange, Tag from ipam.models import ASN, RIR from netbox.staging import checkout from utilities.testing import create_tags @@ -62,7 +62,7 @@ class StagingTestCase(TransactionTestCase): # Verify that changes have been rolled back after exiting the context self.assertEqual(Provider.objects.count(), 3) self.assertEqual(Circuit.objects.count(), 9) - self.assertEqual(Change.objects.count(), 5) + self.assertEqual(StagedChange.objects.count(), 5) # Verify that changes are replayed upon entering the context with checkout(branch): @@ -81,7 +81,7 @@ class StagingTestCase(TransactionTestCase): self.assertListEqual(list(provider.asns.all()), list(asns)) circuit = Circuit.objects.get(cid='Circuit D1') self.assertListEqual(list(circuit.tags.all()), list(tags)) - self.assertEqual(Change.objects.count(), 0) + self.assertEqual(StagedChange.objects.count(), 0) def test_object_modification(self): branch = Branch.objects.create(name='Branch 1') @@ -115,7 +115,7 @@ class StagingTestCase(TransactionTestCase): circuit = Circuit.objects.get(pk=circuit.pk) self.assertEqual(circuit.cid, 'Circuit A1') self.assertListEqual(list(circuit.tags.all()), []) - self.assertEqual(Change.objects.count(), 5) + self.assertEqual(StagedChange.objects.count(), 5) # Verify that changes are replayed upon entering the context with checkout(branch): @@ -138,7 +138,7 @@ class StagingTestCase(TransactionTestCase): circuit = Circuit.objects.get(pk=circuit.pk) self.assertEqual(circuit.cid, 'Circuit X') self.assertListEqual(list(circuit.tags.all()), list(tags)) - self.assertEqual(Change.objects.count(), 0) + self.assertEqual(StagedChange.objects.count(), 0) def test_object_deletion(self): branch = Branch.objects.create(name='Branch 1') @@ -155,7 +155,7 @@ class StagingTestCase(TransactionTestCase): # Verify that changes have been rolled back after exiting the context self.assertEqual(Provider.objects.count(), 3) self.assertEqual(Circuit.objects.count(), 9) - self.assertEqual(Change.objects.count(), 4) + self.assertEqual(StagedChange.objects.count(), 4) # Verify that changes are replayed upon entering the context with checkout(branch): @@ -166,7 +166,7 @@ class StagingTestCase(TransactionTestCase): branch.merge() self.assertEqual(Provider.objects.count(), 2) self.assertEqual(Circuit.objects.count(), 6) - self.assertEqual(Change.objects.count(), 0) + self.assertEqual(StagedChange.objects.count(), 0) def test_exit_enter_context(self): branch = Branch.objects.create(name='Branch 1') @@ -178,8 +178,8 @@ class StagingTestCase(TransactionTestCase): provider.save() # Check that a create Change was recorded - self.assertEqual(Change.objects.count(), 1) - change = Change.objects.first() + self.assertEqual(StagedChange.objects.count(), 1) + change = StagedChange.objects.first() self.assertEqual(change.action, ChangeActionChoices.ACTION_CREATE) self.assertEqual(change.data['name'], provider.name) @@ -191,8 +191,8 @@ class StagingTestCase(TransactionTestCase): provider.save() # Check that a second Change object has been created for the object - self.assertEqual(Change.objects.count(), 2) - change = Change.objects.last() + self.assertEqual(StagedChange.objects.count(), 2) + change = StagedChange.objects.last() self.assertEqual(change.action, ChangeActionChoices.ACTION_UPDATE) self.assertEqual(change.data['name'], provider.name) self.assertEqual(change.data['comments'], provider.comments) @@ -204,7 +204,7 @@ class StagingTestCase(TransactionTestCase): provider.delete() # Check that a third Change has recorded the object's deletion - self.assertEqual(Change.objects.count(), 3) - change = Change.objects.last() + self.assertEqual(StagedChange.objects.count(), 3) + change = StagedChange.objects.last() self.assertEqual(change.action, ChangeActionChoices.ACTION_DELETE) self.assertIsNone(change.data)