diff --git a/src/backend/InvenTree/InvenTree/helpers.py b/src/backend/InvenTree/InvenTree/helpers.py index 502ac9c3b0..7b7cef24d5 100644 --- a/src/backend/InvenTree/InvenTree/helpers.py +++ b/src/backend/InvenTree/InvenTree/helpers.py @@ -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 diff --git a/src/backend/InvenTree/InvenTree/models.py b/src/backend/InvenTree/InvenTree/models.py index b6f5692c0a..2155b2296a 100644 --- a/src/backend/InvenTree/InvenTree/models.py +++ b/src/backend/InvenTree/InvenTree/models.py @@ -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) diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index a53d2640b4..022efae6a4 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -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 diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py index 48b0bb0399..55e26db261 100644 --- a/src/backend/InvenTree/part/models.py +++ b/src/backend/InvenTree/part/models.py @@ -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): diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index a89a94d26d..94838ea09b 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -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).