Skip to content

Commit

Permalink
Fixes #15090: Run deletion protection rules prior to enqueueing events
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremystretch committed Feb 20, 2024
1 parent 056543e commit e44d77c
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 33 deletions.
49 changes: 16 additions & 33 deletions netbox/extras/signals.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import importlib
import logging

from django.contrib.contenttypes.models import ContentType
Expand All @@ -13,7 +12,7 @@
from extras.constants import EVENT_JOB_END, EVENT_JOB_START
from extras.events import process_event_rules
from extras.models import EventRule
from extras.validators import CustomValidator
from extras.validators import run_validators
from netbox.config import get_config
from netbox.context import current_request, events_queue
from netbox.models.features import ChangeLoggingMixin
Expand Down Expand Up @@ -110,6 +109,18 @@ def handle_deleted_object(sender, instance, **kwargs):
"""
Fires when an object is deleted.
"""
# Run any deletion protection rules for the object. Note that this must occur prior
# to queueing any events for the object being deleted, in case a validation error is
# raised, causing the deletion to fail.
model_name = f'{sender._meta.app_label}.{sender._meta.model_name}'
validators = get_config().PROTECTION_RULES.get(model_name, [])
try:
run_validators(instance, validators)
except ValidationError as e:
raise AbortRequest(
_("Deletion is prevented by a protection rule: {message}").format(message=e)
)

# Get the current request, or bail if not set
request = current_request.get()
if request is None:
Expand Down Expand Up @@ -207,45 +218,17 @@ def handle_cf_deleted(instance, **kwargs):
# Custom validation
#

def run_validators(instance, validators):

for validator in validators:

# Loading a validator class by dotted path
if type(validator) is str:
module, cls = validator.rsplit('.', 1)
validator = getattr(importlib.import_module(module), cls)()

# Constructing a new instance on the fly from a ruleset
elif type(validator) is dict:
validator = CustomValidator(validator)

validator(instance)


@receiver(post_clean)
def run_save_validators(sender, instance, **kwargs):
"""
Run any custom validation rules for the model prior to calling save().
"""
model_name = f'{sender._meta.app_label}.{sender._meta.model_name}'
validators = get_config().CUSTOM_VALIDATORS.get(model_name, [])

run_validators(instance, validators)


@receiver(pre_delete)
def run_delete_validators(sender, instance, **kwargs):
model_name = f'{sender._meta.app_label}.{sender._meta.model_name}'
validators = get_config().PROTECTION_RULES.get(model_name, [])

try:
run_validators(instance, validators)
except ValidationError as e:
raise AbortRequest(
_("Deletion is prevented by a protection rule: {message}").format(
message=e
)
)


#
# Tags
#
Expand Down
20 changes: 20 additions & 0 deletions netbox/extras/validators.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import importlib

from django.core import validators
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -149,3 +151,21 @@ def fail(self, message, field=None):
if field is not None:
raise ValidationError({field: message})
raise ValidationError(message)


def run_validators(instance, validators):
"""
Run the provided iterable of validators for the instance.
"""
for validator in validators:

# Loading a validator class by dotted path
if type(validator) is str:
module, cls = validator.rsplit('.', 1)
validator = getattr(importlib.import_module(module), cls)()

# Constructing a new instance on the fly from a ruleset
elif type(validator) is dict:
validator = CustomValidator(validator)

validator(instance)

0 comments on commit e44d77c

Please sign in to comment.