2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-05 13:10:57 +00:00

Extend functionality of custom validation plugins (#4391)

* Pass "Part" instance to plugins when calling validate_serial_number

* Pass part instance through when validating IPN

* Improve custom part name validation

- Pass the Part instance through to the plugins
- Validation is performed at the model instance level
- Updates to sample plugin code

* Pass StockItem through when validating batch code

* Pass Part instance through when calling validate_serial_number

* Bug fix

* Update unit tests

* Unit test fixes

* Fixes for unit tests

* More unit test fixes

* More unit tests

* Furrther unit test fixes

* Simplify custom batch code validation

* Further improvements to unit tests

* Further unit test
This commit is contained in:
Oliver
2023-03-07 22:43:12 +11:00
committed by GitHub
parent edae82caa5
commit abeb85cbb3
22 changed files with 193 additions and 137 deletions

View File

@ -6,6 +6,7 @@ import decimal
import hashlib
import logging
import os
import re
from datetime import datetime, timedelta
from decimal import Decimal, InvalidOperation
@ -538,7 +539,60 @@ class Part(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
return result
def validate_serial_number(self, serial: str, stock_item=None, check_duplicates=True, raise_error=False):
def validate_name(self, raise_error=True):
"""Validate the name field for this Part instance
This function is exposed to any Validation plugins, and thus can be customized.
"""
from plugin.registry import registry
for plugin in registry.with_mixin('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,
})
def validate_ipn(self, raise_error=True):
"""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
"""
from plugin.registry import registry
for plugin in registry.with_mixin('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
})
# If we get to here, none of the plugins have raised an error
pattern = common.models.InvenTreeSetting.get_setting('PART_IPN_REGEX', '', create=False).strip()
if pattern:
match = re.search(pattern, self.IPN)
if match is None:
raise ValidationError(_('IPN must match regex pattern {pat}').format(pat=pattern))
def validate_serial_number(self, serial: str, stock_item=None, check_duplicates=True, raise_error=False, **kwargs):
"""Validate a serial number against this Part instance.
Note: This function is exposed to any Validation plugins, and thus can be customized.
@ -570,7 +624,7 @@ class Part(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
for plugin in registry.with_mixin('validation'):
# Run the serial number through each custom validator
# If the plugin returns 'True' we will skip any subsequent validation
if plugin.validate_serial_number(serial):
if plugin.validate_serial_number(serial, self):
return True
except ValidationError as exc:
if raise_error:
@ -620,7 +674,7 @@ class Part(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
conflicts = []
for serial in serials:
if not self.validate_serial_number(serial):
if not self.validate_serial_number(serial, part=self):
conflicts.append(serial)
return conflicts
@ -765,6 +819,12 @@ class Part(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
if type(self.IPN) is str:
self.IPN = self.IPN.strip()
# Run custom validation for the IPN field
self.validate_ipn()
# Run custom validation for the name field
self.validate_name()
if self.trackable:
for part in self.get_used_in().all():
@ -777,7 +837,6 @@ class Part(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
max_length=100, blank=False,
help_text=_('Part name'),
verbose_name=_('Name'),
validators=[validators.validate_part_name]
)
is_template = models.BooleanField(
@ -821,7 +880,6 @@ class Part(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
max_length=100, blank=True, null=True,
verbose_name=_('IPN'),
help_text=_('Internal Part Number'),
validators=[validators.validate_part_ipn]
)
revision = models.CharField(