2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-08-07 20:32:12 +00:00

Add custom state field validation

This commit is contained in:
Oliver Walters
2024-12-28 03:41:38 +00:00
parent 5be8d7ce70
commit ad79c79983
6 changed files with 59 additions and 5 deletions

View File

@@ -319,6 +319,7 @@ class Build(
verbose_name=_('Build Status'), verbose_name=_('Build Status'),
default=BuildStatus.PENDING.value, default=BuildStatus.PENDING.value,
choices=BuildStatus.items(), choices=BuildStatus.items(),
status_class=BuildStatus,
validators=[MinValueValidator(0)], validators=[MinValueValidator(0)],
help_text=_('Build status code'), help_text=_('Build status code'),
) )

View File

@@ -90,6 +90,20 @@ class InvenTreeCustomStatusModelField(models.PositiveIntegerField):
Models using this model field must also include the InvenTreeCustomStatusSerializerMixin in all serializers that create or update the value. Models using this model field must also include the InvenTreeCustomStatusSerializerMixin in all serializers that create or update the value.
""" """
def __init__(self, *args, **kwargs):
"""Initialize the field."""
from generic.states.validators import CustomStatusCodeValidator
self.status_class = kwargs.pop('status_class', None)
validators = kwargs.pop('validators', None) or []
if self.status_class:
validators.append(CustomStatusCodeValidator(status_class=self.status_class))
kwargs['validators'] = validators
super().__init__(*args, **kwargs)
def deconstruct(self): def deconstruct(self):
"""Deconstruct the field for migrations.""" """Deconstruct the field for migrations."""
name, path, args, kwargs = super().deconstruct() name, path, args, kwargs = super().deconstruct()
@@ -109,14 +123,23 @@ class InvenTreeCustomStatusModelField(models.PositiveIntegerField):
"""Ensure that the value is not an empty string.""" """Ensure that the value is not an empty string."""
if value == '': if value == '':
value = None value = None
return super().clean(value, model_instance) return super().clean(value, model_instance)
def add_field(self, cls, name): def add_field(self, cls, name):
"""Adds custom_key_field to the model class to save additional status information.""" """Adds custom_key_field to the model class to save additional status information."""
from generic.states.validators import CustomStatusCodeValidator
validators = []
if self.status_class:
validators.append(CustomStatusCodeValidator(status_class=self.status_class))
custom_key_field = ExtraInvenTreeCustomStatusModelField( custom_key_field = ExtraInvenTreeCustomStatusModelField(
default=None, default=None,
verbose_name=_('Custom status key'), verbose_name=_('Custom status key'),
help_text=_('Additional status information for this item'), help_text=_('Additional status information for this item'),
validators=validators,
blank=True, blank=True,
null=True, null=True,
) )
@@ -130,6 +153,10 @@ class ExtraInvenTreeCustomStatusModelField(models.PositiveIntegerField):
This is not intended to be used directly, if you want to support custom states in your model use InvenTreeCustomStatusModelField. This is not intended to be used directly, if you want to support custom states in your model use InvenTreeCustomStatusModelField.
""" """
def __init__(self, *args, **kwargs):
"""Initialize the field."""
super().__init__(*args, **kwargs)
class InvenTreeCustomStatusSerializerMixin: class InvenTreeCustomStatusSerializerMixin:
"""Mixin to ensure custom status fields are set. """Mixin to ensure custom status fields are set.

View File

@@ -117,7 +117,7 @@ class StatusCode(BaseEnum):
return [] return []
@classmethod @classmethod
def values(cls, key=None, custom=True): def values(cls, key=None):
"""Return a dict representation containing all required information.""" """Return a dict representation containing all required information."""
elements = [itm for itm in cls if cls._is_element(itm.name)] elements = [itm for itm in cls if cls._is_element(itm.name)]
@@ -167,14 +167,14 @@ class StatusCode(BaseEnum):
return data return data
@classmethod @classmethod
def keys(cls): def keys(cls, custom=True):
"""All status code keys.""" """All status code keys."""
return [x.value for x in cls.values()] return [el[0] for el in cls.items(custom=custom)]
@classmethod @classmethod
def labels(cls): def labels(cls, custom=True):
"""All status code labels.""" """All status code labels."""
return [x.label for x in cls.values()] return [el[1] for el in cls.items(custom=custom)]
@classmethod @classmethod
def names(cls): def names(cls):

View File

@@ -0,0 +1,21 @@
"""Validators for generic state management."""
from django.core.exceptions import ValidationError
from django.core.validators import BaseValidator
from django.utils.translation import gettext_lazy as _
class CustomStatusCodeValidator(BaseValidator):
"""Custom validator class for checking that a provided status code is valid."""
def __init__(self, *args, **kwargs):
"""Initialize the validator."""
self.status_class = kwargs.pop('status_class', None)
super().__init__(limit_value=None, **kwargs)
def __call__(self, value):
"""Check that the provided status code is valid."""
if status_class := self.status_class:
values = status_class.keys(custom=True)
if value not in values:
raise ValidationError(_('Invalid status code'))

View File

@@ -483,6 +483,7 @@ class PurchaseOrder(TotalPriceMixin, Order):
status = InvenTreeCustomStatusModelField( status = InvenTreeCustomStatusModelField(
default=PurchaseOrderStatus.PENDING.value, default=PurchaseOrderStatus.PENDING.value,
choices=PurchaseOrderStatus.items(), choices=PurchaseOrderStatus.items(),
status_class=PurchaseOrderStatus,
verbose_name=_('Status'), verbose_name=_('Status'),
help_text=_('Purchase order status'), help_text=_('Purchase order status'),
) )
@@ -1029,6 +1030,7 @@ class SalesOrder(TotalPriceMixin, Order):
status = InvenTreeCustomStatusModelField( status = InvenTreeCustomStatusModelField(
default=SalesOrderStatus.PENDING.value, default=SalesOrderStatus.PENDING.value,
choices=SalesOrderStatus.items(), choices=SalesOrderStatus.items(),
status_class=SalesOrderStatus,
verbose_name=_('Status'), verbose_name=_('Status'),
help_text=_('Sales order status'), help_text=_('Sales order status'),
) )
@@ -2231,6 +2233,7 @@ class ReturnOrder(TotalPriceMixin, Order):
status = InvenTreeCustomStatusModelField( status = InvenTreeCustomStatusModelField(
default=ReturnOrderStatus.PENDING.value, default=ReturnOrderStatus.PENDING.value,
choices=ReturnOrderStatus.items(), choices=ReturnOrderStatus.items(),
status_class=ReturnOrderStatus,
verbose_name=_('Status'), verbose_name=_('Status'),
help_text=_('Return order status'), help_text=_('Return order status'),
) )
@@ -2522,6 +2525,7 @@ class ReturnOrderLineItem(OrderLineItem):
outcome = InvenTreeCustomStatusModelField( outcome = InvenTreeCustomStatusModelField(
default=ReturnOrderLineStatus.PENDING.value, default=ReturnOrderLineStatus.PENDING.value,
choices=ReturnOrderLineStatus.items(), choices=ReturnOrderLineStatus.items(),
status_class=ReturnOrderLineStatus,
verbose_name=_('Outcome'), verbose_name=_('Outcome'),
help_text=_('Outcome for this line item'), help_text=_('Outcome for this line item'),
) )

View File

@@ -1020,6 +1020,7 @@ class StockItem(
status = InvenTreeCustomStatusModelField( status = InvenTreeCustomStatusModelField(
default=StockStatus.OK.value, default=StockStatus.OK.value,
status_class=StockStatus,
choices=StockStatus.items(), choices=StockStatus.items(),
validators=[MinValueValidator(0)], validators=[MinValueValidator(0)],
) )