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