Parameter validation via plugin (#4958)

* Expose part parameter validation to plugins

- Allow ValidationMixin plugins to validate part parameter values

* Update sample plugin

* Catch and re-throw error

* Update docs

* Improve plugin docs

* Simplify validation sample

* Calculate numeric value first
This commit is contained in:
Oliver 2023-06-03 21:27:31 +10:00 committed by GitHub
parent 1d85b70313
commit b0338e181e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 103 additions and 26 deletions

View File

@ -572,7 +572,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
"""Ensure that the IPN (internal part number) is valid for this Part"
- Validation is handled by custom plugins
- By default, no validation checks are perfomed
- By default, no validation checks are performed
"""
from plugin.registry import registry
@ -3505,6 +3505,24 @@ class PartParameter(MetadataMixin, models.Model):
'data': _('Invalid choice for parameter value')
})
self.calculate_numeric_value()
# Run custom validation checks (via plugins)
from plugin.registry import registry
for plugin in registry.with_mixin('validation'):
# Note: The validate_part_parameter function may raise a ValidationError
try:
result = plugin.validate_part_parameter(self, self.data)
if result:
break
except ValidationError as exc:
# Re-throw the ValidationError against the 'data' field
raise ValidationError({
'data': exc.message
})
def calculate_numeric_value(self):
"""Calculate a numeric value for the parameter data.

View File

@ -21,6 +21,7 @@ class ValidationMixin:
- Part names
- Part IPN (internal part number) values
- Part parameter values
- Serial numbers
- Batch codes
@ -150,6 +151,21 @@ class ValidationMixin:
"""
return None
def validate_part_parameter(self, parameter, data):
"""Validate a parameter value.
Arguments:
parameter: The parameter we are validating
data: The proposed parameter value
Returns:
None or True (refer to class docstring)
Raises:
ValidationError if the proposed parameter value is objectionable
"""
pass
class NavigationMixin:
"""Mixin that enables custom navigation links with the plugin."""

View File

@ -18,7 +18,7 @@ class CustomValidationMixin(SettingsMixin, ValidationMixin, InvenTreePlugin):
SLUG = "validator"
TITLE = "Custom Validator Plugin"
DESCRIPTION = "A sample plugin for demonstrating custom validation functionality"
VERSION = "0.2"
VERSION = "0.3.0"
SETTINGS = {
'ILLEGAL_PART_CHARS': {
@ -78,6 +78,17 @@ class CustomValidationMixin(SettingsMixin, ValidationMixin, InvenTreePlugin):
if self.get_setting('IPN_MUST_CONTAIN_Q') and 'Q' not in ipn:
raise ValidationError("IPN must contain 'Q'")
def validate_part_parameter(self, parameter, data):
"""Validate part parameter data.
These examples are silly, but serve to demonstrate how the feature could be used
"""
if parameter.template.name.lower() in ['length', 'width']:
d = int(data)
if d >= 100:
raise ValidationError("Value must be less than 100")
def validate_serial_number(self, serial: str, part):
"""Validate serial number for a given StockItem

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@ -8,6 +8,15 @@ The `EventMixin` class enables plugins to respond to certain triggered events.
When a certain (server-side) event occurs, the background worker passes the event information to any plugins which inherit from the `EventMixin` base class.
!!! tip "Enable Event Integration"
The *Enable Event Integration* option must first be enabled to allow plugins to respond to events.
{% with id="events", url="plugin/enable_events.png", description="Enable event integration" %}
{% include 'img.html' %}
{% endwith %}
### Example
Implementing classes must provide a `process_event` function:
```python
@ -29,7 +38,7 @@ class EventPlugin(EventMixin, InvenTreePlugin):
### Events
Events are passed through using a string identifier, e.g. 'build.completed'
Events are passed through using a string identifier, e.g. `build.completed`
The arguments (and keyword arguments) passed to the receiving function depend entirely on the type of event.

View File

@ -21,36 +21,37 @@ Panel content can be rendered by returning HTML directly, or by rendering from a
Each plugin can register templates simply by providing a 'templates' directory in its root path.
The convention is that each 'templates' directory contains a subdirectory with the same name as the plugin :
* e.g. templates/myplugin/my_template.html
The convention is that each 'templates' directory contains a subdirectory with the same name as the plugin (e.g. `templates/myplugin/my_template.html`)
In this case, the template can then be loaded (from any plugin!) by loading "myplugin/my_template.html".
In this case, the template can then be loaded (from any plugin!) by loading `myplugin/my_template.html`.
### Javascript
Custom code can be provided which will run when the particular panel is first loaded (by selecting it from the side menu).
To add some javascript code, you can add a reference to a function that will be called when the panel is loaded with the 'javascript' key in the panel description :
```
{
'title': "Updates",
'description': "Latest updates for this part",
'javascript': 'alert("You just loaded this panel!")',
}
To add some javascript code, you can add a reference to a function that will be called when the panel is loaded with the 'javascript' key in the panel description:
```python
{
'title': "Updates",
'description': "Latest updates for this part",
'javascript': 'alert("You just loaded this panel!")',
}
```
Or to add a template file that will be rendered as javascript code, from the plugin template folder, with the 'javascript_template' key in the panel description :
```
{
'title': "Updates",
'description': "Latest updates for this part",
'javascript_template': 'pluginTemplatePath/myJavascriptFile.js',
}
Or to add a template file that will be rendered as javascript code, from the plugin template folder, with the 'javascript_template' key in the panel description:
```python
{
'title': "Updates",
'description': "Latest updates for this part",
'javascript_template': 'pluginTemplatePath/myJavascriptFile.js',
}
```
note : see convention for template directory above.
Note : see convention for template directory above.
## Example Implementation

View File

@ -11,6 +11,13 @@ The ScheduleMixin class provides a plugin with the ability to call functions at
- Plugin member functions can be called
- Global functions can be specified using dotted notation
!!! tip "Enable Schedule Integration"
The *Enable Schedule Integration* option but be enabled, for scheduled plugin events to be activated.
{% with id="schedule", url="plugin/enable_schedule.png", description="Enable schedule integration" %}
{% include 'img.html' %}
{% endwith %}
### Example
An example of a plugin which supports scheduled tasks:
@ -50,3 +57,6 @@ class ScheduledTaskPlugin(ScheduleMixin, SettingsMixin, InvenTreePlugin):
secret_value = self.get_setting('SECRET')
print(f"foo - SECRET = {secret_value})
```
!!! info "More Info"
For more information on any of the methods described below, refer to the InvenTree source code. [A working example is available as a starting point](https://github.com/inventree/InvenTree/blob/master/InvenTree/plugin/samples/integration/scheduled_task.py).

View File

@ -11,8 +11,13 @@ The *SettingsMixin* allows the plugin to save and load persistent settings to th
Use the class constant `SETTINGS` for a dict of settings that should be added as global database settings.
The dict must be formatted similar to the following sample that shows how to use validator choices and default. Take a look at the settings defined in `InvenTree.common.models.InvenTreeSetting` for all possible parameters.
The dict must be formatted similar to the following sample that shows how to use validator choices and default.
Take a look at the settings defined in `InvenTree.common.models.InvenTreeSetting` for all possible parameters.
### Example
Below is a simple example of how a plugin can implement settings:
``` python
class PluginWithSettings(SettingsMixin, InvenTreePlugin):
@ -60,6 +65,9 @@ class PluginWithSettings(SettingsMixin, InvenTreePlugin):
}
```
!!! info "More Info"
For more information on any of the methods described below, refer to the InvenTree source code.
!!! tip "Hidden Settings"
Plugin settings can be hidden from the settings page by marking them as 'hidden'

View File

@ -9,14 +9,14 @@ The `ValidationMixin` class enables plugins to perform custom validation of vari
Any of the methods described below can be implemented in a custom plugin to provide functionality as required.
!!! info "More Info"
For more information on any of the methods described below, refer to the InvenTree source code.
For more information on any of the methods described below, refer to the InvenTree source code. [A working example is available as a starting point](https://github.com/inventree/InvenTree/blob/master/InvenTree/plugin/samples/integration/validation_sample.py).
!!! info "Multi Plugin Support"
It is possible to have multiple plugins loaded simultaneously which support validation methods. For example when validating a field, if one plugin returns a null value (`None`) then the *next* plugin (if available) will be queried.
### Part Name
By default, part names are not subject to any particular naming conventions or requirements. However if custom validation is required, the `validate_part_name` method can be implemente to ensure that a part name conforms to a required convention.
By default, part names are not subject to any particular naming conventions or requirements. However if custom validation is required, the `validate_part_name` method can be implement to ensure that a part name conforms to a required convention.
If the custom method determines that the part name is *objectionable*, it should throw a `ValidationError` which will be handled upstream by parent calling methods.
@ -24,6 +24,10 @@ If the custom method determines that the part name is *objectionable*, it should
Validation of the Part IPN (Internal Part Number) field is exposed to custom plugins via the `validate_part_IPN` method. Any plugins which extend the `ValidationMixin` class can implement this method, and raise a `ValidationError` if the IPN value does not match a required convention.
### Part Parameter Values
[Part parameters](../../part/parameter.md) can also have custom validation rules applied, by implementing the `validate_part_parameter` method. A plugin which implements this method should raise a `ValidationError` with an appropriate message if the part parameter value does not match a required convention.
### Batch Codes
[Batch codes](../../stock/tracking.md#batch-codes) can be generated and/or validated by custom plugins.
@ -36,7 +40,7 @@ The `generate_batch_code` method can be implemented to generate a new batch code
Requirements for serial numbers can vary greatly depending on the application. Rather than attempting to provide a "one size fits all" serial number implementation, InvenTree allows custom serial number schemes to be implemented via plugins.
The default InvenTree [serial numbering system](../../stock/tracking.md#serial-numbers) uses a simple algorithm to validate and increment serial numbers. More complex behaviours can be implemented using the `ValidationMixin` plugin class and the following custom methods:
The default InvenTree [serial numbering system](../../stock/tracking.md#serial-numbers) uses a simple algorithm to validate and increment serial numbers. More complex behaviors can be implemented using the `ValidationMixin` plugin class and the following custom methods:
#### Serial Number Validation