Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
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)