Since version 2.5.1, protecting assets from accidental changes in the NetBox web interface is handled via NetBox's Custom Validation rules. This allows you to restrict modifications to specific fields (like serial or asset_tag) for assets carrying a specific tag (e.g., no-edit).
Note: These restrictions apply only to the web interface. Deletions and modifications via the API or Import form remain possible.
from extras.validators import CustomValidator
from utilities.data import shallow_compare_dict
class TagEditProtector(CustomValidator):
def __init__(self, protection_tag, protected_fields=None):
super().__init__(validation_rules=None)
self.protection_tag = protection_tag
self.protected_fields = protected_fields
def validate(self, instance, request):
# permit changes via API
if request and request.path.startswith('/api/'):
return
# permit changes via import
if request and '/import/' in request.path:
return
# see ChangeLoggingMixin.snapshot()
exclude = [
'last_updated',
]
if not instance.pk:
# creating new object
return
if self.protection_tag not in instance.tags.all().values_list('slug', flat=True):
# if not tagged with protection_tag, no limitations
return
fail_msg = f'Cannot change {instance}. Protected by tag {self.protection_tag}'
violation = None
if not self.protected_fields:
# consider all fields protected
violation = fail_msg
else:
# Get fields that were changed
prechange_data = instance._prechange_snapshot
postchange_data = instance.serialize_object(exclude=exclude)
diff = shallow_compare_dict(prechange_data, postchange_data)
violated_fields = set(diff.keys()).intersection(self.protected_fields)
if violated_fields:
violation = dict(map(lambda f: (f, fail_msg), violated_fields))
if violation:
self.fail(violation)
class TagDeleteProtector(CustomValidator):
def __init__(self, protection_tag):
super().__init__(validation_rules=None)
self.protection_tag = protection_tag
def validate(self, instance, request):
if request and request.path.startswith('/api/'):
return
if self.protection_tag not in instance.tags.all().values_list('slug', flat=True):
# if not tagged with protection_tag, no limitations
return
self.fail(f'Cannot delete {instance}. Protected by tag {self.protection_tag}')
CUSTOM_VALIDATORS = {
'netbox_inventory.asset': [
TagEditProtector('no-edit', ('serial', 'asset_tag')),
],
}
PROTECTION_RULES = {
'netbox_inventory.asset': [
TagDeleteProtector('no-edit'),
]
}