2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-05-28 03:49:20 +00:00

Plugin validation tweak (#12013)

* Prevent plugin validation actions during data import/export

* Simplify logic

* Further checks
This commit is contained in:
Oliver
2026-05-27 20:35:36 +10:00
committed by GitHub
parent 19182bacd0
commit 33483a3824
5 changed files with 128 additions and 102 deletions
+17 -15
View File
@@ -550,29 +550,31 @@ def increment_serial_number(serial, part=None):
incremented value, or None if incrementing could not be performed.
"""
from InvenTree.exceptions import log_error
from InvenTree.ready import isReadOnlyCommand
from plugin import PluginMixinEnum, registry
# Ensure we start with a string value
if serial is not None:
serial = str(serial).strip()
# First, let any plugins attempt to increment the serial number
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
try:
if not hasattr(plugin, 'increment_serial_number'):
continue
if not isReadOnlyCommand():
# First, let any plugins attempt to increment the serial number
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
try:
if not hasattr(plugin, 'increment_serial_number'):
continue
signature = inspect.signature(plugin.increment_serial_number)
signature = inspect.signature(plugin.increment_serial_number)
# Note: 2024-08-21 - The 'part' parameter has been added to the signature
if 'part' in signature.parameters:
result = plugin.increment_serial_number(serial, part=part)
else:
result = plugin.increment_serial_number(serial)
if result is not None:
return str(result)
except Exception:
log_error('increment_serial_number', plugin=plugin.slug)
# Note: 2024-08-21 - The 'part' parameter has been added to the signature
if 'part' in signature.parameters:
result = plugin.increment_serial_number(serial, part=part)
else:
result = plugin.increment_serial_number(serial)
if result is not None:
return str(result)
except Exception:
log_error('increment_serial_number', plugin=plugin.slug)
# If we get to here, no plugins were able to "increment" the provided serial value
# Attempt to perform increment according to some basic rules
+23 -9
View File
@@ -92,10 +92,23 @@ class PluginValidationMixin(DiffMixin):
Any model class which inherits from this mixin will be exposed to the plugin validation system.
"""
def should_plugin_validate(self):
"""Return True if this model instance should be validated by plugins.
The default implementation returns True, but this can be overridden in the implementing class if required.
"""
from InvenTree.ready import isReadOnlyCommand
# Prevent plugin validation when importing or exporting data
return not isReadOnlyCommand()
def run_plugin_validation(self):
"""Throw this model against the plugin validation interface."""
from plugin import PluginMixinEnum, registry
if not self.should_plugin_validate():
return
deltas = self.get_field_deltas()
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
@@ -139,15 +152,16 @@ class PluginValidationMixin(DiffMixin):
from InvenTree.exceptions import log_error
from plugin import PluginMixinEnum, registry
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
try:
plugin.validate_model_deletion(self)
except ValidationError as e:
# Plugin might raise a ValidationError to prevent deletion
raise e
except Exception:
log_error('validate_model_deletion', plugin=plugin.slug)
continue
if self.should_plugin_validate():
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
try:
plugin.validate_model_deletion(self)
except ValidationError as e:
# Plugin might raise a ValidationError to prevent deletion
raise e
except Exception:
log_error('validate_model_deletion', plugin=plugin.slug)
continue
super().delete(*args, **kwargs)
+4
View File
@@ -2875,6 +2875,10 @@ class Parameter(
except ValidationError as e:
raise ValidationError({'data': e.message})
if InvenTree.ready.isReadOnlyCommand():
# Skip plugin validation checks during read-only management commands
return
# Finally, run custom validation checks (via plugins)
from plugin import PluginMixinEnum, registry
+57 -52
View File
@@ -724,19 +724,21 @@ class Part(
"""
from plugin import PluginMixinEnum, registry
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
# Run the name through each custom validator
# If the plugin returns 'True' we will skip any subsequent validation
# Skip plugin validation checks during read-only management commands
if not InvenTree.ready.isReadOnlyCommand():
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
# Run the name through each custom validator
# If the plugin returns 'True' we will skip any subsequent validation
try:
result = plugin.validate_part_name(self.name, self)
if result:
return
except ValidationError as exc:
if raise_error:
raise ValidationError({'name': exc.message})
except Exception:
log_error('validate_part_name', plugin=plugin.slug)
try:
result = plugin.validate_part_name(self.name, self)
if result:
return
except ValidationError as exc:
if raise_error:
raise ValidationError({'name': exc.message})
except Exception:
log_error('validate_part_name', plugin=plugin.slug)
def validate_ipn(self, raise_error=True):
"""Ensure that the IPN (internal part number) is valid for this Part".
@@ -746,18 +748,20 @@ class Part(
"""
from plugin import PluginMixinEnum, registry
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
try:
result = plugin.validate_part_ipn(self.IPN, self)
# Skip plugin validation checks during read-only management commands
if not InvenTree.ready.isReadOnlyCommand():
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
try:
result = plugin.validate_part_ipn(self.IPN, self)
if result:
# A "true" result force skips any subsequent checks
break
except ValidationError as exc:
if raise_error:
raise ValidationError({'IPN': exc.message})
except Exception:
log_error('validate_part_ipn', plugin=plugin.slug)
if result:
# A "true" result force skips any subsequent checks
break
except ValidationError as exc:
if raise_error:
raise ValidationError({'IPN': exc.message})
except Exception:
log_error('validate_part_ipn', plugin=plugin.slug)
# If we get to here, none of the plugins have raised an error
pattern = get_global_setting('PART_IPN_REGEX', '', create=False).strip()
@@ -835,40 +839,41 @@ class Part(
Raises:
ValidationError if serial number is invalid and raise_error = True
"""
serial = str(serial).strip()
# First, throw the serial number against each of the loaded validation plugins
from plugin import PluginMixinEnum, registry
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
# Run the serial number through each custom validator
# If the plugin returns 'True' we will skip any subsequent validation
serial = str(serial).strip()
try:
result = False
if not InvenTree.ready.isReadOnlyCommand():
# First, throw the serial number against each of the loaded validation plugins
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
# Run the serial number through each custom validator
# If the plugin returns 'True' we will skip any subsequent validation
if hasattr(plugin, 'validate_serial_number'):
signature = inspect.signature(plugin.validate_serial_number)
try:
result = False
if 'stock_item' in signature.parameters:
# 2024-08-21: New method signature accepts a 'stock_item' parameter
result = plugin.validate_serial_number(
serial, self, stock_item=stock_item
)
if hasattr(plugin, 'validate_serial_number'):
signature = inspect.signature(plugin.validate_serial_number)
if 'stock_item' in signature.parameters:
# 2024-08-21: New method signature accepts a 'stock_item' parameter
result = plugin.validate_serial_number(
serial, self, stock_item=stock_item
)
else:
# Old method signature - does not accept a 'stock_item' parameter
result = plugin.validate_serial_number(serial, self)
if result is True:
return True
except ValidationError as exc:
if raise_error:
# Re-throw the error
raise exc
else:
# Old method signature - does not accept a 'stock_item' parameter
result = plugin.validate_serial_number(serial, self)
if result is True:
return True
except ValidationError as exc:
if raise_error:
# Re-throw the error
raise exc
else:
return False
except Exception:
log_error('validate_serial_number', plugin=plugin.slug)
return False
except Exception:
log_error('validate_serial_number', plugin=plugin.slug)
"""
If we are here, none of the loaded plugins (if any) threw an error or exited early
@@ -960,7 +965,7 @@ class Part(
"""
from plugin import PluginMixinEnum, registry
if allow_plugins:
if allow_plugins and not InvenTree.ready.isReadOnlyCommand():
# Check with plugin system
# If any plugin returns a non-null result, that takes priority
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
+27 -26
View File
@@ -779,24 +779,24 @@ class StockItem(
# First, let any plugins convert this serial number to an integer value
# If a non-null value is returned (by any plugin) we will use that
if not InvenTree.ready.isReadOnlyCommand():
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
try:
serial_int = plugin.convert_serial_to_int(serial)
except Exception:
InvenTree.exceptions.log_error(
'convert_serial_to_int', plugin=plugin.slug
)
serial_int = None
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
try:
serial_int = plugin.convert_serial_to_int(serial)
except Exception:
InvenTree.exceptions.log_error(
'convert_serial_to_int', plugin=plugin.slug
)
serial_int = None
# Save the first returned result
if serial_int is not None:
# Ensure that it is clipped within a range allowed in the database schema
clip = 0x7FFFFFFF
serial_int = abs(serial_int)
serial_int = min(serial_int, clip)
# Return the first non-null value
return serial_int
# Save the first returned result
if serial_int is not None:
# Ensure that it is clipped within a range allowed in the database schema
clip = 0x7FFFFFFF
serial_int = abs(serial_int)
serial_int = min(serial_int, clip)
# Return the first non-null value
return serial_int
# None of the plugins provided a valid integer value
if serial not in [None, '']:
@@ -922,15 +922,16 @@ class StockItem(
"""
from plugin import PluginMixinEnum, registry
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
try:
plugin.validate_batch_code(self.batch, self)
except ValidationError as exc:
raise ValidationError({'batch': exc.message})
except Exception:
InvenTree.exceptions.log_error(
'validate_batch_code', plugin=plugin.slug
)
if not InvenTree.ready.isReadOnlyCommand():
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
try:
plugin.validate_batch_code(self.batch, self)
except ValidationError as exc:
raise ValidationError({'batch': exc.message})
except Exception:
InvenTree.exceptions.log_error(
'validate_batch_code', plugin=plugin.slug
)
def clean(self):
"""Validate the StockItem object (separate to field validation).