Skip to content
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions netbox_branching/signal_receivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
from django.dispatch import receiver
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError

from core.choices import ObjectChangeActionChoices
from core.models import ObjectChange, ObjectType
from extras.events import process_event_rules
from extras.models import EventRule
from netbox.signals import post_clean
from netbox_branching import signals
from utilities.exceptions import AbortRequest
from utilities.serialization import serialize_object
Expand All @@ -25,9 +27,46 @@
'handle_branch_event',
'record_change_diff',
'validate_branch_deletion',
'validate_branching_operations',
'validate_object_deletion_in_branch',
)


@receiver(post_clean)
def validate_branching_operations(sender, instance, **kwargs):
"""
Validate that branching operations are valid (e.g., not modifying deleted objects).
"""
branch = active_branch.get()

# Only validate if we're in a branch and this model supports branching
if branch is None:
return

# Check if this model supports branching
object_type = ObjectType.objects.get(app_label=instance._meta.app_label, model=instance._meta.model_name)
if 'branching' not in object_type.features:
return
Comment thread
arthanson marked this conversation as resolved.

# For updates, check if the object exists in main
if hasattr(instance, 'pk') and instance.pk is not None:
model = instance.__class__
with deactivate_branch():
try:
# Try to get the object from main database
model.objects.get(pk=instance.pk)
except model.DoesNotExist:
raise ValidationError(
_(
"Cannot modify {model_name} '{object_name}' because it has been deleted in the main branch. "
"Sync with the main branch to update."
).format(
model_name=model._meta.verbose_name,
object_name=str(instance)
)
)


@receiver(post_save, sender=ObjectChange)
def record_change_diff(instance, **kwargs):
"""
Expand Down Expand Up @@ -79,6 +118,20 @@ def record_change_diff(instance, **kwargs):
current_data = None
else:
model = instance.changed_object_type.model_class()
# Check if the object exists in main before trying to get it
try:
model.objects.using(DEFAULT_DB_ALIAS).get(pk=instance.changed_object_id)
except model.DoesNotExist:
raise AbortRequest(
_(
"Cannot {action} {model_name} '{object_name}' because it has been deleted "
"in the main branch. Sync with the main branch to update."
).format(
action=instance.action.lower(),
model_name=model._meta.verbose_name,
object_name=str(instance.changed_object)
)
)
with deactivate_branch():
obj = model.objects.get(pk=instance.changed_object_id)
if hasattr(obj, 'serialize_object'):
Expand Down Expand Up @@ -136,6 +189,46 @@ def handle_branch_event(event_type, branch, user=None, **kwargs):
signals.post_revert.connect(partial(handle_branch_event, event_type=BRANCH_REVERTED))


@receiver(pre_delete)
def validate_object_deletion_in_branch(sender, instance, **kwargs):
"""
Validate that objects being deleted in a branch still exist in main.
"""
# Skip Branch objects - they have their own validation
if sender == Branch:
return

# Only validate if we're in a branch
branch = active_branch.get()
if branch is None:
return

# Check if this model supports branching
try:
object_type = ObjectType.objects.get(app_label=instance._meta.app_label, model=instance._meta.model_name)
Comment thread
arthanson marked this conversation as resolved.
Outdated
if 'branching' not in object_type.features:
return
except ObjectType.DoesNotExist:
return

# For deletions, check if the object exists in main
if hasattr(instance, 'pk') and instance.pk is not None:
model = instance.__class__
try:
# Try to get the object from main database (explicitly use default connection)
model.objects.using(DEFAULT_DB_ALIAS).get(pk=instance.pk)
Comment thread
arthanson marked this conversation as resolved.
Outdated
except model.DoesNotExist:
raise AbortRequest(
_(
"Cannot delete {model_name} '{object_name}' because it has been deleted in the main branch. "
"Sync with the main branch to update."
).format(
model_name=model._meta.verbose_name,
object_name=str(instance)
)
)


@receiver(pre_delete, sender=Branch)
def validate_branch_deletion(sender, instance, **kwargs):
"""
Expand Down