mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Fixes #15090: Run deletion protection rules prior to enqueueing events
This commit is contained in:
@ -1,4 +1,3 @@
|
|||||||
import importlib
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -13,7 +12,7 @@ from core.signals import job_end, job_start
|
|||||||
from extras.constants import EVENT_JOB_END, EVENT_JOB_START
|
from extras.constants import EVENT_JOB_END, EVENT_JOB_START
|
||||||
from extras.events import process_event_rules
|
from extras.events import process_event_rules
|
||||||
from extras.models import EventRule
|
from extras.models import EventRule
|
||||||
from extras.validators import CustomValidator
|
from extras.validators import run_validators
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from netbox.context import current_request, events_queue
|
from netbox.context import current_request, events_queue
|
||||||
from netbox.models.features import ChangeLoggingMixin
|
from netbox.models.features import ChangeLoggingMixin
|
||||||
@ -110,6 +109,18 @@ def handle_deleted_object(sender, instance, **kwargs):
|
|||||||
"""
|
"""
|
||||||
Fires when an object is deleted.
|
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
|
# Get the current request, or bail if not set
|
||||||
request = current_request.get()
|
request = current_request.get()
|
||||||
if request is None:
|
if request is None:
|
||||||
@ -207,45 +218,17 @@ m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.content_type
|
|||||||
# Custom validation
|
# 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)
|
@receiver(post_clean)
|
||||||
def run_save_validators(sender, instance, **kwargs):
|
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}'
|
model_name = f'{sender._meta.app_label}.{sender._meta.model_name}'
|
||||||
validators = get_config().CUSTOM_VALIDATORS.get(model_name, [])
|
validators = get_config().CUSTOM_VALIDATORS.get(model_name, [])
|
||||||
|
|
||||||
run_validators(instance, validators)
|
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
|
# Tags
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import importlib
|
||||||
|
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -149,3 +151,21 @@ class CustomValidator:
|
|||||||
if field is not None:
|
if field is not None:
|
||||||
raise ValidationError({field: message})
|
raise ValidationError({field: message})
|
||||||
raise ValidationError(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)
|
||||||
|
Reference in New Issue
Block a user