From a80cf7612090ecf8c4e468ce09e6823ca3b00dfc Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 13 Sep 2024 16:04:52 -0400 Subject: [PATCH 01/13] Closes #123: Introduce template tags for branch action buttons --- .../templates/netbox_branching/branch.html | 41 +++--------------- .../buttons/branch_archive.html | 10 +++++ .../buttons/branch_merge.html | 10 +++++ .../buttons/branch_revert.html | 10 +++++ .../netbox_branching/buttons/branch_sync.html | 10 +++++ netbox_branching/templatetags/__init__.py | 0 .../templatetags/branch_buttons.py | 42 +++++++++++++++++++ 7 files changed, 87 insertions(+), 36 deletions(-) create mode 100644 netbox_branching/templates/netbox_branching/buttons/branch_archive.html create mode 100644 netbox_branching/templates/netbox_branching/buttons/branch_merge.html create mode 100644 netbox_branching/templates/netbox_branching/buttons/branch_revert.html create mode 100644 netbox_branching/templates/netbox_branching/buttons/branch_sync.html create mode 100644 netbox_branching/templatetags/__init__.py create mode 100644 netbox_branching/templatetags/branch_buttons.py diff --git a/netbox_branching/templates/netbox_branching/branch.html b/netbox_branching/templates/netbox_branching/branch.html index 75edc00..75eff0c 100644 --- a/netbox_branching/templates/netbox_branching/branch.html +++ b/netbox_branching/templates/netbox_branching/branch.html @@ -4,6 +4,7 @@ {% load plugins %} {% load render_table from django_tables2 %} {% load i18n %} +{% load branch_buttons %} {% block extra_controls %} {% if not object.is_active %} @@ -22,44 +23,12 @@ {% endif %} {% if object.ready %} - {% if perms.netbox_branching.sync_branch %} - - {% trans "Sync" %} - - {% else %} - - {% endif %} - {% if perms.netbox_branching.merge_branch %} - - {% trans "Merge" %} - - {% else %} - - {% endif %} + {% branch_sync_button object %} + {% branch_merge_button object %} {% endif %} {% if object.merged %} - {% if perms.netbox_branching.revert_branch %} - - {% trans "Revert" %} - - {% else %} - - {% endif %} - {% if perms.netbox_branching.archive_branch %} - - {% trans "Archive" %} - - {% else %} - - {% endif %} + {% branch_revert_button object %} + {% branch_archive_button object %} {% endif %} {% endblock %} diff --git a/netbox_branching/templates/netbox_branching/buttons/branch_archive.html b/netbox_branching/templates/netbox_branching/buttons/branch_archive.html new file mode 100644 index 0000000..1ecbf34 --- /dev/null +++ b/netbox_branching/templates/netbox_branching/buttons/branch_archive.html @@ -0,0 +1,10 @@ +{% load i18n %} +{% if perms.netbox_branching.archive_branch %} + + {% trans "Archive" %} + +{% else %} + +{% endif %} diff --git a/netbox_branching/templates/netbox_branching/buttons/branch_merge.html b/netbox_branching/templates/netbox_branching/buttons/branch_merge.html new file mode 100644 index 0000000..efbb5b0 --- /dev/null +++ b/netbox_branching/templates/netbox_branching/buttons/branch_merge.html @@ -0,0 +1,10 @@ +{% load i18n %} +{% if perms.netbox_branching.merge_branch %} + + {% trans "Merge" %} + +{% else %} + +{% endif %} diff --git a/netbox_branching/templates/netbox_branching/buttons/branch_revert.html b/netbox_branching/templates/netbox_branching/buttons/branch_revert.html new file mode 100644 index 0000000..f004631 --- /dev/null +++ b/netbox_branching/templates/netbox_branching/buttons/branch_revert.html @@ -0,0 +1,10 @@ +{% load i18n %} +{% if perms.netbox_branching.revert_branch %} + + {% trans "Revert" %} + +{% else %} + +{% endif %} diff --git a/netbox_branching/templates/netbox_branching/buttons/branch_sync.html b/netbox_branching/templates/netbox_branching/buttons/branch_sync.html new file mode 100644 index 0000000..fcf21bb --- /dev/null +++ b/netbox_branching/templates/netbox_branching/buttons/branch_sync.html @@ -0,0 +1,10 @@ +{% load i18n %} +{% if perms.netbox_branching.sync_branch %} + + {% trans "Sync" %} + +{% else %} + +{% endif %} diff --git a/netbox_branching/templatetags/__init__.py b/netbox_branching/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/netbox_branching/templatetags/branch_buttons.py b/netbox_branching/templatetags/branch_buttons.py new file mode 100644 index 0000000..2fa62f2 --- /dev/null +++ b/netbox_branching/templatetags/branch_buttons.py @@ -0,0 +1,42 @@ +from django import template + +__all__ = ( + 'branch_sync_button', + 'branch_merge_button', + 'branch_revert_button', + 'branch_archive_button', +) + +register = template.Library() + + +@register.inclusion_tag('netbox_branching/buttons/branch_sync.html', takes_context=True) +def branch_sync_button(context, branch): + return { + 'branch': branch, + 'perms': context.get('perms'), + } + + +@register.inclusion_tag('netbox_branching/buttons/branch_merge.html', takes_context=True) +def branch_merge_button(context, branch): + return { + 'branch': branch, + 'perms': context.get('perms'), + } + + +@register.inclusion_tag('netbox_branching/buttons/branch_revert.html', takes_context=True) +def branch_revert_button(context, branch): + return { + 'branch': branch, + 'perms': context.get('perms'), + } + + +@register.inclusion_tag('netbox_branching/buttons/branch_archive.html', takes_context=True) +def branch_archive_button(context, branch): + return { + 'branch': branch, + 'perms': context.get('perms'), + } From 548d604b8992da2e54bb8e7de821694a18c094c9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 16 Sep 2024 09:20:27 -0400 Subject: [PATCH 02/13] Fixes #120: max_branches should disregard archived branches --- netbox_branching/models/branches.py | 6 +++--- netbox_branching/tests/test_branches.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/netbox_branching/models/branches.py b/netbox_branching/models/branches.py index 23591d0..a8a90fb 100644 --- a/netbox_branching/models/branches.py +++ b/netbox_branching/models/branches.py @@ -129,12 +129,12 @@ def clean(self): # Enforce the maximum number of total branches if not self.pk and (max_branches := get_plugin_config('netbox_branching', 'max_branches')): - total_branch_count = Branch.objects.count() + total_branch_count = Branch.objects.exclude(status=BranchStatusChoices.ARCHIVED).count() if total_branch_count >= max_branches: raise ValidationError( _( - "The configured maximum number of branches ({max}) cannot be exceeded. One or more existing " - "branches must be deleted before a new branch may be created." + "The configured maximum number of non-archived branches ({max}) cannot be exceeded. One or " + "more existing branches must be deleted before a new branch may be created." ).format(max=max_branches) ) diff --git a/netbox_branching/tests/test_branches.py b/netbox_branching/tests/test_branches.py index 6cbd01d..3bb263b 100644 --- a/netbox_branching/tests/test_branches.py +++ b/netbox_branching/tests/test_branches.py @@ -112,10 +112,16 @@ def test_max_branches(self): Verify that the max_branches config parameter is enforced. """ Branch.objects.bulk_create(( - Branch(name='Branch 1'), - Branch(name='Branch 2'), + Branch(name='Branch 1', status=BranchStatusChoices.ARCHIVED), + Branch(name='Branch 2', status=BranchStatusChoices.READY), )) + # Creating a second non-archived Branch should succeed branch = Branch(name='Branch 3') + branch.full_clean() + branch.save(provision=False) + + # Creating a third non-archived Branch should fail + branch = Branch(name='Branch 4') with self.assertRaises(ValidationError): branch.full_clean() From 0997b6d439465ef5c8a8800809d0cbb000599284 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 16 Sep 2024 10:07:48 -0400 Subject: [PATCH 03/13] Fixes #119: API requests should honor both header & cookie specifying active branch --- netbox_branching/middleware.py | 15 ++++++------- netbox_branching/tests/test_api.py | 35 +++++++++++++++++++++++++----- netbox_branching/utilities.py | 9 ++++++++ 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/netbox_branching/middleware.py b/netbox_branching/middleware.py index 79f9bbc..a79e994 100644 --- a/netbox_branching/middleware.py +++ b/netbox_branching/middleware.py @@ -8,7 +8,7 @@ from .choices import BranchStatusChoices from .constants import COOKIE_NAME, BRANCH_HEADER, QUERY_PARAM from .models import Branch -from .utilities import activate_branch +from .utilities import activate_branch, is_api_request __all__ = ( 'BranchMiddleware', @@ -45,13 +45,12 @@ def get_active_branch(request): """ Return the active Branch (if any). """ - # The active Branch is specified by HTTP header for REST & GraphQL API requests. - if request.path_info.startswith(reverse('api-root')) or request.path_info.startswith(reverse('graphql')): - if schema_id := request.headers.get(BRANCH_HEADER): - branch = Branch.objects.get(schema_id=schema_id) - if not branch.ready: - return HttpResponseBadRequest(f"Branch {branch} is not ready for use (status: {branch.status})") - return branch + # The active Branch may be specified by HTTP header for REST & GraphQL API requests. + if is_api_request(request) and BRANCH_HEADER in request.headers: + branch = Branch.objects.get(schema_id=request.headers.get(BRANCH_HEADER)) + if not branch.ready: + return HttpResponseBadRequest(f"Branch {branch} is not ready for use (status: {branch.status})") + return branch # Branch activated/deactivated by URL query parameter elif QUERY_PARAM in request.GET: diff --git a/netbox_branching/tests/test_api.py b/netbox_branching/tests/test_api.py index 3e63781..341dacd 100644 --- a/netbox_branching/tests/test_api.py +++ b/netbox_branching/tests/test_api.py @@ -9,6 +9,7 @@ from dcim.models import Site from users.models import Token +from netbox_branching.constants import COOKIE_NAME from netbox_branching.models import Branch @@ -58,21 +59,43 @@ def test_without_branch(self): self.assertEqual(len(results), 1) self.assertEqual(results[0]['name'], 'Site 1') - def test_with_branch(self): + def test_with_branch_header(self): + url = reverse('dcim-api:site-list') branch = Branch.objects.first() self.assertIsNotNone(branch, "Branch was not created") + + # Regular API query + response = self.client.get(url, **self.header) + results = self.get_results(response) + self.assertEqual(len(results), 1) + self.assertEqual(results[0]['name'], 'Site 1') + + # Branch-aware API query header = { **self.header, f'HTTP_X_NETBOX_BRANCH': branch.schema_id, } + response = self.client.get(url, **header) + results = self.get_results(response) + self.assertEqual(len(results), 1) + self.assertEqual(results[0]['name'], 'Site 2') - # Sanity checks - self.assertEqual(Site.objects.count(), 1) - self.assertEqual(Site.objects.using(branch.connection_name).count(), 1) - + def test_with_branch_cookie(self): url = reverse('dcim-api:site-list') - response = self.client.get(url, **header) + branch = Branch.objects.first() + self.assertIsNotNone(branch, "Branch was not created") + + # Regular API query + response = self.client.get(url, **self.header) results = self.get_results(response) + self.assertEqual(len(results), 1) + self.assertEqual(results[0]['name'], 'Site 1') + # Branch-aware API query + self.client.cookies.load({ + COOKIE_NAME: branch.schema_id, + }) + response = self.client.get(url, **self.header) + results = self.get_results(response) self.assertEqual(len(results), 1) self.assertEqual(results[0]['name'], 'Site 2') diff --git a/netbox_branching/utilities.py b/netbox_branching/utilities.py index 74431b2..4beb534 100644 --- a/netbox_branching/utilities.py +++ b/netbox_branching/utilities.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from django.db.models import ForeignKey, ManyToManyField +from django.urls import reverse from .contextvars import active_branch @@ -15,6 +16,7 @@ 'deactivate_branch', 'get_branchable_object_types', 'get_tables_to_replicate', + 'is_api_request', 'record_applied_change', 'update_object', ) @@ -166,3 +168,10 @@ def record_applied_change(instance, branch, **kwargs): from .models import AppliedChange AppliedChange.objects.update_or_create(change=instance, defaults={'branch': branch}) + + +def is_api_request(request): + """ + Returns True if the given request is a REST or GraphQL API request. + """ + return request.path_info.startswith(reverse('api-root')) or request.path_info.startswith(reverse('graphql')) From 90aab66619f59abaa0290db88a8f09848b5fa052 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 16 Sep 2024 16:10:37 -0400 Subject: [PATCH 04/13] Closes #129: Introduce pre-event signals for branch actions --- netbox_branching/models/branches.py | 35 ++++++++++++++++------- netbox_branching/signal_receivers.py | 10 +++---- netbox_branching/signals.py | 42 +++++++++++++++------------- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/netbox_branching/models/branches.py b/netbox_branching/models/branches.py index a8a90fb..60c389c 100644 --- a/netbox_branching/models/branches.py +++ b/netbox_branching/models/branches.py @@ -251,6 +251,9 @@ def sync(self, user, commit=True): if not self.ready: raise Exception(f"Branch {self} is not ready to sync") + # Emit pre-sync signal + pre_sync.send(sender=self.__class__, branch=self, user=user) + # Retrieve unsynced changes before we update the Branch's status if changes := self.get_unsynced_changes().order_by('time'): logger.info(f"Found {len(changes)} changes to sync") @@ -288,8 +291,8 @@ def sync(self, user, commit=True): logger.debug(f"Recording branch event: {BranchEventTypeChoices.SYNCED}") BranchEvent.objects.create(branch=self, user=user, type=BranchEventTypeChoices.SYNCED) - # Emit branch_synced signal - branch_synced.send(sender=self.__class__, branch=self, user=user) + # Emit post-sync signal + post_sync.send(sender=self.__class__, branch=self, user=user) logger.info('Syncing completed') @@ -306,6 +309,9 @@ def merge(self, user, commit=True): if not self.ready: raise Exception(f"Branch {self} is not ready to merge") + # Emit pre-merge signal + pre_merge.send(sender=self.__class__, branch=self, user=user) + # Retrieve staged changes before we update the Branch's status if changes := self.get_unmerged_changes().order_by('time'): logger.info(f"Found {len(changes)} changes to merge") @@ -354,8 +360,8 @@ def merge(self, user, commit=True): logger.debug(f"Recording branch event: {BranchEventTypeChoices.MERGED}") BranchEvent.objects.create(branch=self, user=user, type=BranchEventTypeChoices.MERGED) - # Emit branch_merged signal - branch_merged.send(sender=self.__class__, branch=self, user=user) + # Emit post-merge signal + post_merge.send(sender=self.__class__, branch=self, user=user) logger.info('Merging completed') @@ -375,6 +381,9 @@ def revert(self, user, commit=True): if not self.merged: raise Exception(f"Only merged branches can be reverted.") + # Emit pre-revert signal + pre_revert.send(sender=self.__class__, branch=self, user=user) + # Retrieve applied changes before we update the Branch's status if changes := self.get_changes().order_by('-time'): logger.info(f"Found {len(changes)} changes to revert") @@ -423,8 +432,8 @@ def revert(self, user, commit=True): logger.debug(f"Recording branch event: {BranchEventTypeChoices.REVERTED}") BranchEvent.objects.create(branch=self, user=user, type=BranchEventTypeChoices.REVERTED) - # Emit branch_reverted signal - branch_reverted.send(sender=self.__class__, branch=self, user=user) + # Emit post-revert signal + post_revert.send(sender=self.__class__, branch=self, user=user) logger.info('Reversion completed') @@ -440,6 +449,9 @@ def provision(self, user): logger = logging.getLogger('netbox_branching.branch.provision') logger.info(f'Provisioning branch {self} ({self.schema_name})') + # Emit pre-provision signal + pre_provision.send(sender=self.__class__, branch=self, user=user) + # Update Branch status Branch.objects.filter(pk=self.pk).update(status=BranchStatusChoices.PROVISIONING) @@ -516,8 +528,8 @@ def provision(self, user): raise e - # Emit branch_provisioned signal - branch_provisioned.send(sender=self.__class__, branch=self, user=user) + # Emit post-provision signal + post_provision.send(sender=self.__class__, branch=self, user=user) logger.info('Provisioning completed') @@ -545,6 +557,9 @@ def deprovision(self): logger = logging.getLogger('netbox_branching.branch.provision') logger.info(f'Deprovisioning branch {self} ({self.schema_name})') + # Emit pre-deprovision signal + pre_deprovision.send(sender=self.__class__, branch=self) + with connection.cursor() as cursor: # Delete the schema and all its tables logger.debug(f'Deleting schema {self.schema_name}') @@ -552,8 +567,8 @@ def deprovision(self): f"DROP SCHEMA IF EXISTS {self.schema_name} CASCADE" ) - # Emit branch_deprovisioned signal - branch_deprovisioned.send(sender=self.__class__, branch=self) + # Emit post-deprovision signal + post_deprovision.send(sender=self.__class__, branch=self) logger.info('Deprovisioning completed') diff --git a/netbox_branching/signal_receivers.py b/netbox_branching/signal_receivers.py index f3d79c6..026e6a6 100644 --- a/netbox_branching/signal_receivers.py +++ b/netbox_branching/signal_receivers.py @@ -124,11 +124,11 @@ def handle_branch_event(event_type, branch, user=None, **kwargs): ) -branch_provisioned.connect(partial(handle_branch_event, event_type=BRANCH_PROVISIONED)) -branch_synced.connect(partial(handle_branch_event, event_type=BRANCH_SYNCED)) -branch_merged.connect(partial(handle_branch_event, event_type=BRANCH_MERGED)) -branch_reverted.connect(partial(handle_branch_event, event_type=BRANCH_REVERTED)) -branch_deprovisioned.connect(partial(handle_branch_event, event_type=BRANCH_DEPROVISIONED)) +post_provision.connect(partial(handle_branch_event, event_type=BRANCH_PROVISIONED)) +post_deprovision.connect(partial(handle_branch_event, event_type=BRANCH_DEPROVISIONED)) +post_sync.connect(partial(handle_branch_event, event_type=BRANCH_SYNCED)) +post_merge.connect(partial(handle_branch_event, event_type=BRANCH_MERGED)) +post_revert.connect(partial(handle_branch_event, event_type=BRANCH_REVERTED)) @receiver(pre_delete, sender=Branch) diff --git a/netbox_branching/signals.py b/netbox_branching/signals.py index cbc99d6..063f6c4 100644 --- a/netbox_branching/signals.py +++ b/netbox_branching/signals.py @@ -1,26 +1,28 @@ from django.dispatch import Signal -from .events import * - __all__ = ( - 'branch_deprovisioned', - 'branch_merged', - 'branch_provisioned', - 'branch_reverted', - 'branch_synced', + 'post_deprovision', + 'post_merge', + 'post_provision', + 'post_revert', + 'post_sync', + 'pre_deprovision', + 'pre_merge', + 'pre_provision', + 'pre_revert', + 'pre_sync', ) +# Pre-event signals +pre_provision = Signal() +pre_deprovision = Signal() +pre_sync = Signal() +pre_merge = Signal() +pre_revert = Signal() -branch_provisioned = Signal() -branch_deprovisioned = Signal() -branch_synced = Signal() -branch_merged = Signal() -branch_reverted = Signal() - -branch_signals = { - branch_provisioned: BRANCH_PROVISIONED, - branch_deprovisioned: BRANCH_DEPROVISIONED, - branch_synced: BRANCH_SYNCED, - branch_merged: BRANCH_MERGED, - branch_reverted: BRANCH_REVERTED, -} +# Post-event signals +post_provision = Signal() +post_deprovision = Signal() +post_sync = Signal() +post_merge = Signal() +post_revert = Signal() From e6134e9bb7abc212aece0f7f4f9d524a8b87584e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 17 Sep 2024 15:03:46 -0400 Subject: [PATCH 05/13] Update GitHub issue links --- docs/changelog.md | 76 +++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index a384c0a..47df39e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,20 +4,20 @@ ### Enhancements -* [#83](https://github.com/netboxlabs/nbl-netbox-branching/issues/83) - Add a "share" button under object views when a branch is active -* [#84](https://github.com/netboxlabs/nbl-netbox-branching/issues/84) - Introduce the `max_working_branches` configuration parameter -* [#88](https://github.com/netboxlabs/nbl-netbox-branching/issues/88) - Add branching support for NetBox's graphQL API -* [#90](https://github.com/netboxlabs/nbl-netbox-branching/issues/90) - Introduce the ability to archive & deprovision merged branches without deleting them -* [#97](https://github.com/netboxlabs/nbl-netbox-branching/issues/97) - Introduce the `exempt_models` config parameter to disable branching support for plugin models -* [#116](https://github.com/netboxlabs/nbl-netbox-branching/issues/116) - Disable branching support for applicable core models +* [#83](https://github.com/netboxlabs/netbox-branching/issues/83) - Add a "share" button under object views when a branch is active +* [#84](https://github.com/netboxlabs/netbox-branching/issues/84) - Introduce the `max_working_branches` configuration parameter +* [#88](https://github.com/netboxlabs/netbox-branching/issues/88) - Add branching support for NetBox's graphQL API +* [#90](https://github.com/netboxlabs/netbox-branching/issues/90) - Introduce the ability to archive & deprovision merged branches without deleting them +* [#97](https://github.com/netboxlabs/netbox-branching/issues/97) - Introduce the `exempt_models` config parameter to disable branching support for plugin models +* [#116](https://github.com/netboxlabs/netbox-branching/issues/116) - Disable branching support for applicable core models ### Bug Fixes -* [#81](https://github.com/netboxlabs/nbl-netbox-branching/issues/81) - Fix event rule triggering for the `branch_reverted` event -* [#91](https://github.com/netboxlabs/nbl-netbox-branching/issues/91) - Disregard the active branch (if any) when alerting on changes under object views -* [#94](https://github.com/netboxlabs/nbl-netbox-branching/issues/94) - Fix branch merging after modifying an object with custom field data -* [#101](https://github.com/netboxlabs/nbl-netbox-branching/issues/101) - Permit (but warn about) database queries issued before branching support has been initialized -* [#102](https://github.com/netboxlabs/nbl-netbox-branching/issues/102) - Record individual object actions in branch job logs +* [#81](https://github.com/netboxlabs/netbox-branching/issues/81) - Fix event rule triggering for the `branch_reverted` event +* [#91](https://github.com/netboxlabs/netbox-branching/issues/91) - Disregard the active branch (if any) when alerting on changes under object views +* [#94](https://github.com/netboxlabs/netbox-branching/issues/94) - Fix branch merging after modifying an object with custom field data +* [#101](https://github.com/netboxlabs/netbox-branching/issues/101) - Permit (but warn about) database queries issued before branching support has been initialized +* [#102](https://github.com/netboxlabs/netbox-branching/issues/102) - Record individual object actions in branch job logs --- @@ -25,18 +25,18 @@ ### Enhancements -* [#52](https://github.com/netboxlabs/nbl-netbox-branching/issues/52) - Introduce the `max_branches` config parameter -* [#71](https://github.com/netboxlabs/nbl-netbox-branching/issues/71) - Ensure the consistent application of logging messages -* [#76](https://github.com/netboxlabs/nbl-netbox-branching/issues/76) - Validate required configuration items on initialization +* [#52](https://github.com/netboxlabs/netbox-branching/issues/52) - Introduce the `max_branches` config parameter +* [#71](https://github.com/netboxlabs/netbox-branching/issues/71) - Ensure the consistent application of logging messages +* [#76](https://github.com/netboxlabs/netbox-branching/issues/76) - Validate required configuration items on initialization ### Bug Fixes -* [#57](https://github.com/netboxlabs/nbl-netbox-branching/issues/57) - Avoid recording ChangeDiff records for unsupported object types -* [#59](https://github.com/netboxlabs/nbl-netbox-branching/issues/59) - `BranchAwareRouter` should consider branching support for model when determining database connection to use -* [#61](https://github.com/netboxlabs/nbl-netbox-branching/issues/61) - Fix transaction rollback when performing a dry run sync -* [#66](https://github.com/netboxlabs/nbl-netbox-branching/issues/66) - Capture object representation on ChangeDiff when creating a new object within a branch -* [#69](https://github.com/netboxlabs/nbl-netbox-branching/issues/69) - Represent null values for ChangeDiff fields consistently in REST API -* [#73](https://github.com/netboxlabs/nbl-netbox-branching/issues/73) - Ensure all relevant branch diffs are updated when an object is modified in main +* [#57](https://github.com/netboxlabs/netbox-branching/issues/57) - Avoid recording ChangeDiff records for unsupported object types +* [#59](https://github.com/netboxlabs/netbox-branching/issues/59) - `BranchAwareRouter` should consider branching support for model when determining database connection to use +* [#61](https://github.com/netboxlabs/netbox-branching/issues/61) - Fix transaction rollback when performing a dry run sync +* [#66](https://github.com/netboxlabs/netbox-branching/issues/66) - Capture object representation on ChangeDiff when creating a new object within a branch +* [#69](https://github.com/netboxlabs/netbox-branching/issues/69) - Represent null values for ChangeDiff fields consistently in REST API +* [#73](https://github.com/netboxlabs/netbox-branching/issues/73) - Ensure all relevant branch diffs are updated when an object is modified in main --- @@ -44,10 +44,10 @@ ### Bug Fixes -* [#42](https://github.com/netboxlabs/nbl-netbox-branching/issues/42) - Fix exception raised when viewing custom scripts -* [#44](https://github.com/netboxlabs/nbl-netbox-branching/issues/44) - Handle truncated SQL sequence names to avoid exceptions during branch provisioning -* [#48](https://github.com/netboxlabs/nbl-netbox-branching/issues/48) - Ensure background job is terminated in the event branch provisioning errors -* [#50](https://github.com/netboxlabs/nbl-netbox-branching/issues/50) - Branch state should remain as "merged" after dry-run revert +* [#42](https://github.com/netboxlabs/netbox-branching/issues/42) - Fix exception raised when viewing custom scripts +* [#44](https://github.com/netboxlabs/netbox-branching/issues/44) - Handle truncated SQL sequence names to avoid exceptions during branch provisioning +* [#48](https://github.com/netboxlabs/netbox-branching/issues/48) - Ensure background job is terminated in the event branch provisioning errors +* [#50](https://github.com/netboxlabs/netbox-branching/issues/50) - Branch state should remain as "merged" after dry-run revert --- @@ -55,23 +55,23 @@ ### Enhancements -* [#2](https://github.com/netboxlabs/nbl-netbox-branching/issues/2) - Enable the ability to revert a previously merged branch -* [#3](https://github.com/netboxlabs/nbl-netbox-branching/issues/3) - Require review & acknowledgment of conflicts before syncing or merging a branch -* [#4](https://github.com/netboxlabs/nbl-netbox-branching/issues/4) - Include a three-way diff summary in the REST API representation of a modified object -* [#13](https://github.com/netboxlabs/nbl-netbox-branching/issues/13) - Add a link to the active branch in the branch selector dropdown -* [#15](https://github.com/netboxlabs/nbl-netbox-branching/issues/15) - Default to performing a "dry run" for branch sync & merge -* [#17](https://github.com/netboxlabs/nbl-netbox-branching/issues/17) - Utilize NetBox's `JobRunner` class for background jobs -* [#29](https://github.com/netboxlabs/nbl-netbox-branching/issues/29) - Register a branch column on NetBox's global changelog table -* [#36](https://github.com/netboxlabs/nbl-netbox-branching/issues/36) - Run the branch provisioning process within an isolated transaction +* [#2](https://github.com/netboxlabs/netbox-branching/issues/2) - Enable the ability to revert a previously merged branch +* [#3](https://github.com/netboxlabs/netbox-branching/issues/3) - Require review & acknowledgment of conflicts before syncing or merging a branch +* [#4](https://github.com/netboxlabs/netbox-branching/issues/4) - Include a three-way diff summary in the REST API representation of a modified object +* [#13](https://github.com/netboxlabs/netbox-branching/issues/13) - Add a link to the active branch in the branch selector dropdown +* [#15](https://github.com/netboxlabs/netbox-branching/issues/15) - Default to performing a "dry run" for branch sync & merge +* [#17](https://github.com/netboxlabs/netbox-branching/issues/17) - Utilize NetBox's `JobRunner` class for background jobs +* [#29](https://github.com/netboxlabs/netbox-branching/issues/29) - Register a branch column on NetBox's global changelog table +* [#36](https://github.com/netboxlabs/netbox-branching/issues/36) - Run the branch provisioning process within an isolated transaction ### Bug Fixes -* [#10](https://github.com/netboxlabs/nbl-netbox-branching/issues/10) - Fix branch merge failure when deleted object was modified in another branch -* [#11](https://github.com/netboxlabs/nbl-netbox-branching/issues/11) - Fix quick search functionality for branch diffs tab -* [#16](https://github.com/netboxlabs/nbl-netbox-branching/issues/16) - Fix support for many-to-many assignments -* [#24](https://github.com/netboxlabs/nbl-netbox-branching/issues/24) - Correct the REST API schema for the sync, merge, and revert branch endpoints -* [#30](https://github.com/netboxlabs/nbl-netbox-branching/issues/30) - Include only unmerged branches with relevant changes in object view notifications -* [#31](https://github.com/netboxlabs/nbl-netbox-branching/issues/31) - Prevent the deletion of a branch in a transitional state +* [#10](https://github.com/netboxlabs/netbox-branching/issues/10) - Fix branch merge failure when deleted object was modified in another branch +* [#11](https://github.com/netboxlabs/netbox-branching/issues/11) - Fix quick search functionality for branch diffs tab +* [#16](https://github.com/netboxlabs/netbox-branching/issues/16) - Fix support for many-to-many assignments +* [#24](https://github.com/netboxlabs/netbox-branching/issues/24) - Correct the REST API schema for the sync, merge, and revert branch endpoints +* [#30](https://github.com/netboxlabs/netbox-branching/issues/30) - Include only unmerged branches with relevant changes in object view notifications +* [#31](https://github.com/netboxlabs/netbox-branching/issues/31) - Prevent the deletion of a branch in a transitional state --- From 34076894da61707701f5c16f273205c44a099ad6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 17 Sep 2024 15:05:55 -0400 Subject: [PATCH 06/13] Update changelog --- docs/changelog.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 47df39e..48dfe4f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,19 @@ # Change Log +## v0.5.1 + +### Enhancements + +* [#123](https://github.com/netboxlabs/netbox-branching/issues/123) - Introduce template tags for branch action buttons +* [#129](https://github.com/netboxlabs/netbox-branching/issues/129) - Implement pre-event signals for branch actions + +### Bug Fixes + +* [#119](https://github.com/netboxlabs/netbox-branching/issues/119) - Fix the dynamic selection of related objects in forms while a branch is active +* [#120](https://github.com/netboxlabs/netbox-branching/issues/120) - `max_branches` config parameter should disregard archived branches + +--- + ## v0.5.0 ### Enhancements From 9021ae36d129cffaabd864b3c2c08721a2342dad Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 23 Sep 2024 11:57:03 -0400 Subject: [PATCH 07/13] Fixes #140: Correct representation of branch status in REST API --- netbox_branching/api/serializers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/netbox_branching/api/serializers.py b/netbox_branching/api/serializers.py index 2336f7c..d7b24c6 100644 --- a/netbox_branching/api/serializers.py +++ b/netbox_branching/api/serializers.py @@ -5,7 +5,7 @@ from netbox.api.exceptions import SerializerNotFound from netbox.api.fields import ChoiceField, ContentTypeField from netbox.api.serializers import NetBoxModelSerializer -from netbox_branching.choices import BranchEventTypeChoices +from netbox_branching.choices import BranchEventTypeChoices, BranchStatusChoices from netbox_branching.models import ChangeDiff, Branch, BranchEvent from users.api.serializers import UserSerializer from utilities.api import get_serializer_for_model @@ -30,6 +30,9 @@ class BranchSerializer(NetBoxModelSerializer): nested=True, read_only=True ) + status = ChoiceField( + choices=BranchStatusChoices + ) class Meta: model = Branch From 0e3d9f8316aeebdcc86bb3b4e73d69665fbc9675 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 23 Sep 2024 13:18:49 -0400 Subject: [PATCH 08/13] Fixes #142: Fix tab record counts for archived branches --- netbox_branching/models/branches.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/netbox_branching/models/branches.py b/netbox_branching/models/branches.py index 60c389c..be5712d 100644 --- a/netbox_branching/models/branches.py +++ b/netbox_branching/models/branches.py @@ -201,6 +201,8 @@ def get_unsynced_changes(self): """ Return a queryset of all ObjectChange records created in main since the Branch was last synced or created. """ + if self.status not in BranchStatusChoices.WORKING: + return ObjectChange.objects.none() return ObjectChange.objects.using(DEFAULT_DB_ALIAS).exclude( application__branch=self ).filter( @@ -212,7 +214,7 @@ def get_unmerged_changes(self): """ Return a queryset of all unmerged ObjectChange records within the Branch schema. """ - if self.status == BranchStatusChoices.MERGED: + if self.status not in BranchStatusChoices.WORKING: return ObjectChange.objects.none() return ObjectChange.objects.using(self.connection_name) @@ -220,7 +222,7 @@ def get_merged_changes(self): """ Return a queryset of all merged ObjectChange records for the Branch. """ - if self.status != BranchStatusChoices.MERGED: + if self.status not in (BranchStatusChoices.MERGED, BranchStatusChoices.ARCHIVED): return ObjectChange.objects.none() return ObjectChange.objects.using(DEFAULT_DB_ALIAS).filter( application__branch=self From ecb66371cd9201e2dc840ef282b3c3acd97f5dcd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 23 Sep 2024 13:24:38 -0400 Subject: [PATCH 09/13] Fixes #138: Fix the ID column of the change diffs table --- netbox_branching/tables/tables.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/netbox_branching/tables/tables.py b/netbox_branching/tables/tables.py index efdecd0..f1df9e6 100644 --- a/netbox_branching/tables/tables.py +++ b/netbox_branching/tables/tables.py @@ -83,9 +83,9 @@ def render_is_active(self, value): class ChangeDiffTable(NetBoxTable): - name = tables.Column( - verbose_name=_('Name'), - linkify=True + # TODO: Revert to the default "id" column when a detail view for ChangeDiff is implemented + id = tables.Column( + verbose_name=_('ID') ) branch = tables.Column( verbose_name=_('Branch'), @@ -122,8 +122,8 @@ class ChangeDiffTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = ChangeDiff fields = ( - 'branch', 'object_type', 'object', 'action', 'conflicts', 'original_diff', 'modified_diff', 'current_diff', - 'last_updated', 'actions', + 'id', 'branch', 'object_type', 'object', 'action', 'conflicts', 'original_diff', 'modified_diff', + 'current_diff', 'last_updated', 'actions', ) default_columns = ('branch', 'object', 'action', 'conflicts', 'original_diff', 'modified_diff', 'current_diff') From 175a43f6ec277f004c60ae1628e94e3c0d8c24c0 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 24 Sep 2024 10:47:43 -0400 Subject: [PATCH 10/13] Closes #139: Tweak installation instructions --- README.md | 2 +- docs/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 350c238..5196850 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ PLUGINS = [ ] ``` -5. Create `local_settings.py` to override the `DATABASES` & `DATABASE_ROUTERS` settings. This enables dynamic schema support. +5. Create `local_settings.py` (in the same directory as `settings.py`) to override the `DATABASES` & `DATABASE_ROUTERS` settings. This enables dynamic schema support. ```python from netbox_branching.utilities import DynamicSchemaDict diff --git a/docs/index.md b/docs/index.md index 162404f..200c688 100644 --- a/docs/index.md +++ b/docs/index.md @@ -124,7 +124,7 @@ PLUGINS = [ This plugin employs dynamic schema resolution, which requires that we override two low-level Django settings. First, we'll wrap NetBox's configured `DATABASE` parameter with `DynamicSchemaDict` to support dynamic schemas. Second, we'll employ the plugin's custom database router. -Create a new file named `local_settings.py` in the same directory as `configuration.py`, and add the content below. +Create a new file named `local_settings.py` in the same directory as `settings.py`, and add the content below. ```python from netbox_branching.utilities import DynamicSchemaDict From d45bc995b40910628cbcb913ac461f605767d1d7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 24 Sep 2024 17:16:11 -0400 Subject: [PATCH 11/13] Update changelog --- docs/changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 48dfe4f..d5957af 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -11,6 +11,9 @@ * [#119](https://github.com/netboxlabs/netbox-branching/issues/119) - Fix the dynamic selection of related objects in forms while a branch is active * [#120](https://github.com/netboxlabs/netbox-branching/issues/120) - `max_branches` config parameter should disregard archived branches +* [#138](https://github.com/netboxlabs/netbox-branching/issues/138) - Fix rendering the ID column of the change diffs table +* [#140](https://github.com/netboxlabs/netbox-branching/issues/140) - Fix representation of branch status in REST API +* [#142](https://github.com/netboxlabs/netbox-branching/issues/142) - Fix tab record counts for archived branches --- From 49267c39b536d34e5875a209736847d6a56a1d3b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 24 Sep 2024 15:47:25 -0400 Subject: [PATCH 12/13] Replicate dcim_cablepath table for branch --- netbox_branching/constants.py | 6 ++++++ netbox_branching/database.py | 5 ----- netbox_branching/utilities.py | 5 +++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/netbox_branching/constants.py b/netbox_branching/constants.py index ea9e7fa..938963d 100644 --- a/netbox_branching/constants.py +++ b/netbox_branching/constants.py @@ -10,6 +10,12 @@ # URL query parameter name QUERY_PARAM = '_branch' +# Tables which must be replicated within a branch even though their +# models don't directly support branching. +REPLICATE_TABLES = ( + 'dcim_cablepath', +) + # Models for which branching support is explicitly disabled EXEMPT_MODELS = ( # Exempt applicable core NetBox models diff --git a/netbox_branching/database.py b/netbox_branching/database.py index 842f881..3c749fc 100644 --- a/netbox_branching/database.py +++ b/netbox_branching/database.py @@ -21,11 +21,6 @@ def _get_db(self, model, **hints): warnings.warn(f"Routing database query for {model} before branching support is initialized.") return - # Bail if the model does not support branching - app_label, model_name = model._meta.label.lower().split('.') - if model_name not in registry['model_features']['branching'].get(app_label, []): - return - # Return the schema for the active branch (if any) if branch := active_branch.get(): return f'schema_{branch.schema_name}' diff --git a/netbox_branching/utilities.py b/netbox_branching/utilities.py index 4beb534..ea38cea 100644 --- a/netbox_branching/utilities.py +++ b/netbox_branching/utilities.py @@ -6,6 +6,7 @@ from django.db.models import ForeignKey, ManyToManyField from django.urls import reverse +from .constants import REPLICATE_TABLES from .contextvars import active_branch __all__ = ( @@ -81,9 +82,9 @@ def get_branchable_object_types(): def get_tables_to_replicate(): """ - Returned an ordered list of database tables to replicate when provisioning a new schema. + Return an ordered list of database tables to replicate when provisioning a new schema. """ - tables = set() + tables = set(REPLICATE_TABLES) branch_aware_models = [ ot.model_class() for ot in get_branchable_object_types() From 2f76ba0a9846f7512965a24748e8bb332fa0826a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 25 Sep 2024 13:40:56 -0400 Subject: [PATCH 13/13] Release v0.5.1 --- docs/changelog.md | 1 + netbox_branching/__init__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index d5957af..4d79fed 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -9,6 +9,7 @@ ### Bug Fixes +* [#98](https://github.com/netboxlabs/netbox-branching/issues/98) - Cable changes in branch should not impact main schema * [#119](https://github.com/netboxlabs/netbox-branching/issues/119) - Fix the dynamic selection of related objects in forms while a branch is active * [#120](https://github.com/netboxlabs/netbox-branching/issues/120) - `max_branches` config parameter should disregard archived branches * [#138](https://github.com/netboxlabs/netbox-branching/issues/138) - Fix rendering the ID column of the change diffs table diff --git a/netbox_branching/__init__.py b/netbox_branching/__init__.py index de903be..2d3054c 100644 --- a/netbox_branching/__init__.py +++ b/netbox_branching/__init__.py @@ -9,7 +9,7 @@ class AppConfig(PluginConfig): name = 'netbox_branching' verbose_name = 'NetBox Branching' description = 'A git-like branching implementation for NetBox' - version = '0.5.0' + version = '0.5.1' base_url = 'branching' min_version = '4.1' middleware = [ diff --git a/pyproject.toml b/pyproject.toml index f20d422..f1d8b91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "netboxlabs-netbox-branching" -version = "0.5.0" +version = "0.5.1" description = "A git-like branching implementation for NetBox" readme = "README.md" requires-python = ">=3.10"