mirror of
https://github.com/inventree/InvenTree.git
synced 2026-02-19 13:18:03 +00:00
* [bug] FIx for data migration Prevent writing of default currency to database during migration or backup * Ignore error
190 lines
6.8 KiB
Python
190 lines
6.8 KiB
Python
"""Sample plugin which demonstrates custom validation functionality."""
|
|
|
|
from datetime import datetime
|
|
|
|
from plugin import InvenTreePlugin
|
|
from plugin.mixins import SettingsMixin, ValidationMixin
|
|
|
|
|
|
class SampleValidatorPlugin(SettingsMixin, ValidationMixin, InvenTreePlugin):
|
|
"""A sample plugin class for demonstrating custom validation functions.
|
|
|
|
Simple of examples of custom validator code.
|
|
"""
|
|
|
|
NAME = 'SampleValidator'
|
|
SLUG = 'validator'
|
|
TITLE = 'Sample Validator Plugin'
|
|
DESCRIPTION = 'A sample plugin for demonstrating custom validation functionality'
|
|
VERSION = '0.3.0'
|
|
|
|
SETTINGS = {
|
|
'ILLEGAL_PART_CHARS': {
|
|
'name': 'Illegal Part Characters',
|
|
'description': 'Characters which are not allowed to appear in Part names',
|
|
'default': '!@#$%^&*()~`',
|
|
},
|
|
'IPN_MUST_CONTAIN_Q': {
|
|
'name': 'IPN Q Requirement',
|
|
'description': 'Part IPN field must contain the character Q',
|
|
'default': False,
|
|
'validator': bool,
|
|
},
|
|
'SERIAL_MUST_BE_PALINDROME': {
|
|
'name': 'Palindromic Serials',
|
|
'description': 'Serial numbers must be palindromic',
|
|
'default': False,
|
|
'validator': bool,
|
|
},
|
|
'SERIAL_MUST_MATCH_PART': {
|
|
'name': 'Serial must match part',
|
|
'description': 'First letter of serial number must match first letter of part name',
|
|
'default': False,
|
|
'validator': bool,
|
|
},
|
|
'BATCH_CODE_PREFIX': {
|
|
'name': 'Batch prefix',
|
|
'description': 'Required prefix for batch code',
|
|
'default': 'B',
|
|
},
|
|
'BOM_ITEM_INTEGER': {
|
|
'name': 'Integer Bom Quantity',
|
|
'description': 'Bom item quantity must be an integer',
|
|
'default': False,
|
|
'validator': bool,
|
|
},
|
|
}
|
|
|
|
def validate_model_instance(self, instance, deltas=None):
|
|
"""Run validation against any saved model.
|
|
|
|
- Check if the instance is a BomItem object
|
|
- Test if the quantity is an integer
|
|
"""
|
|
import part.models
|
|
|
|
# Print debug message to console (intentional)
|
|
print('Validating model instance:', instance.__class__, f'<{instance.pk}>')
|
|
|
|
if isinstance(instance, part.models.BomItem):
|
|
if self.get_setting('BOM_ITEM_INTEGER'):
|
|
if float(instance.quantity) != int(instance.quantity): # noqa: RUF069
|
|
self.raise_error({
|
|
'quantity': 'Bom item quantity must be an integer'
|
|
})
|
|
|
|
if isinstance(instance, part.models.Part):
|
|
# If the part description is being updated, prevent it from being reduced in length
|
|
|
|
if deltas and 'description' in deltas:
|
|
old_desc = deltas['description']['old']
|
|
new_desc = deltas['description']['new']
|
|
|
|
if len(new_desc) < len(old_desc):
|
|
self.raise_error({
|
|
'description': 'Part description cannot be shortened'
|
|
})
|
|
|
|
def validate_part_name(self, name: str, part):
|
|
"""Custom validation for Part name field.
|
|
|
|
Rules:
|
|
- Name must be shorter than the description field
|
|
- Name cannot contain illegal characters
|
|
|
|
These examples are silly, but serve to demonstrate how the feature could be used.
|
|
"""
|
|
if len(part.description) < len(name):
|
|
self.raise_error('Part description cannot be shorter than the name')
|
|
|
|
illegal_chars = self.get_setting('ILLEGAL_PART_CHARS')
|
|
|
|
for c in illegal_chars:
|
|
if c in name:
|
|
self.raise_error(f"Illegal character in part name: '{c}'")
|
|
|
|
def validate_part_ipn(self, ipn: str, part):
|
|
"""Validate part IPN.
|
|
|
|
These examples are silly, but serve to demonstrate how the feature could be used
|
|
"""
|
|
if self.get_setting('IPN_MUST_CONTAIN_Q') and 'Q' not in ipn:
|
|
self.raise_error("IPN must contain 'Q'")
|
|
|
|
def validate_parameter(self, parameter, data):
|
|
"""Validate 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:
|
|
self.raise_error('Value must be less than 100')
|
|
|
|
def validate_serial_number(self, serial: str, part, stock_item=None):
|
|
"""Validate serial number for a given StockItem.
|
|
|
|
These examples are silly, but serve to demonstrate how the feature could be used
|
|
"""
|
|
if self.get_setting('SERIAL_MUST_BE_PALINDROME'):
|
|
if serial != serial[::-1]:
|
|
self.raise_error('Serial must be a palindrome')
|
|
|
|
if self.get_setting('SERIAL_MUST_MATCH_PART'):
|
|
# Serial must start with the same letter as the linked part, for some reason
|
|
if serial[0] != part.name[0]:
|
|
self.raise_error('Serial number must start with same letter as part')
|
|
|
|
# Prevent serial numbers which are a multiple of 5
|
|
try:
|
|
sn = int(serial)
|
|
if sn % 5 == 0:
|
|
self.raise_error('Serial number cannot be a multiple of 5')
|
|
except ValueError:
|
|
pass
|
|
|
|
def increment_serial_number(self, serial: str, part=None, **kwargs):
|
|
"""Increment a serial number.
|
|
|
|
These examples are silly, but serve to demonstrate how the feature could be used
|
|
"""
|
|
try:
|
|
sn = int(serial)
|
|
sn += 1
|
|
|
|
# Skip any serial number which is a multiple of 5
|
|
if sn % 5 == 0:
|
|
sn += 1
|
|
|
|
return str(sn)
|
|
except ValueError:
|
|
pass
|
|
|
|
# Return "None" to defer to the next plugin or builtin functionality
|
|
return None
|
|
|
|
def validate_batch_code(self, batch_code: str, item):
|
|
"""Ensure that a particular batch code meets specification.
|
|
|
|
These examples are silly, but serve to demonstrate how the feature could be used
|
|
"""
|
|
prefix = self.get_setting('BATCH_CODE_PREFIX')
|
|
|
|
if len(batch_code) > 0 and prefix and not batch_code.startswith(prefix):
|
|
self.raise_error(f"Batch code must start with '{prefix}'")
|
|
|
|
def generate_batch_code(self, **kwargs):
|
|
"""Generate a new batch code."""
|
|
now = datetime.now()
|
|
batch = f'SAMPLE-BATCH-{now.year}:{now.month}:{now.day}'
|
|
|
|
# If a Part instance is provided, prepend the part name to the batch code
|
|
if part := kwargs.get('part'):
|
|
batch = f'{part.name}-{batch}'
|
|
|
|
# If a Build instance is provided, prepend the build number to the batch code
|
|
if build := kwargs.get('build_order'):
|
|
batch = f'{build.reference}-{batch}'
|
|
|
|
return batch
|