diff --git a/netbox_branching/__init__.py b/netbox_branching/__init__.py index b0b7fc5..85fbc89 100644 --- a/netbox_branching/__init__.py +++ b/netbox_branching/__init__.py @@ -41,6 +41,7 @@ class AppConfig(PluginConfig): def ready(self): super().ready() from . import constants, events, search, signal_receivers + from .models import Branch from .utilities import DynamicSchemaDict # Validate required settings @@ -53,13 +54,14 @@ def ready(self): "netbox_branching: DATABASE_ROUTERS must contain 'netbox_branching.database.BranchAwareRouter'." ) - # Validate branch action validators + # Validate & register configured branch action validators for action in BRANCH_ACTIONS: for validator_path in get_plugin_config('netbox_branching', f'{action}_validators'): try: - import_string(validator_path) + func = import_string(validator_path) except ImportError: raise ImproperlyConfigured(f"Branch {action} validator not found: {validator_path}") + Branch.register_preaction_check(func, action) # Record all object types which support branching in the NetBox registry exempt_models = ( diff --git a/netbox_branching/models/branches.py b/netbox_branching/models/branches.py index 517b0cc..40f123c 100644 --- a/netbox_branching/models/branches.py +++ b/netbox_branching/models/branches.py @@ -26,7 +26,8 @@ from netbox_branching.contextvars import active_branch from netbox_branching.signals import * from netbox_branching.utilities import ( - ChangeSummary, activate_branch, get_branchable_object_types, get_tables_to_replicate, record_applied_change, + BranchActionIndicator, ChangeSummary, activate_branch, get_branchable_object_types, get_tables_to_replicate, + record_applied_change, ) from utilities.exceptions import AbortRequest, AbortTransaction from .changes import ObjectChange @@ -81,6 +82,13 @@ class Branch(JobsMixin, PrimaryModel): related_name='+' ) + _preaction_validators = { + 'sync': set(), + 'merge': set(), + 'revert': set(), + 'archive': set(), + } + class Meta: ordering = ('name',) verbose_name = _('branch') @@ -191,6 +199,15 @@ def _generate_schema_id(length=8): chars = [*string.ascii_lowercase, *string.digits] return ''.join(random.choices(chars, k=length)) + @classmethod + def register_preaction_check(cls, func, action): + """ + Register a validator to run before a specific branch action (i.e. sync or merge). + """ + if action not in BRANCH_ACTIONS: + raise ValueError(f"Invalid branch action: {action}") + cls._preaction_validators[action].add(func) + def get_changes(self): """ Return a queryset of all ObjectChange records created within the Branch. @@ -256,33 +273,39 @@ def _can_do_action(self, action): """ if action not in BRANCH_ACTIONS: raise Exception(f"Unrecognized branch action: {action}") - for validator_path in get_plugin_config('netbox_branching', f'{action}_validators'): - if not import_string(validator_path)(self): - return False - return True - @property + # Run any pre-action validators + for func in self._preaction_validators[action]: + if not (indicator := func(self)): + # Backward compatibility for pre-v0.6.0 validators + if type(indicator) is not BranchActionIndicator: + return BranchActionIndicator(False, _(f"Validation failed for {action}: {func}")) + return indicator + + return BranchActionIndicator(True) + + @cached_property def can_sync(self): """ Indicates whether the branch can be synced. """ return self._can_do_action('sync') - @property + @cached_property def can_merge(self): """ Indicates whether the branch can be merged. """ return self._can_do_action('merge') - @property + @cached_property def can_revert(self): """ Indicates whether the branch can be reverted. """ return self._can_do_action('revert') - @property + @cached_property def can_archive(self): """ Indicates whether the branch can be archived. diff --git a/netbox_branching/templates/netbox_branching/branch_action.html b/netbox_branching/templates/netbox_branching/branch_action.html index 885e2d0..535872b 100644 --- a/netbox_branching/templates/netbox_branching/branch_action.html +++ b/netbox_branching/templates/netbox_branching/branch_action.html @@ -20,9 +20,8 @@ {% if not action_permitted %}