diff --git a/src/backend/InvenTree/build/models.py b/src/backend/InvenTree/build/models.py index 236cfbdb85..8b8af8eb51 100644 --- a/src/backend/InvenTree/build/models.py +++ b/src/backend/InvenTree/build/models.py @@ -319,6 +319,7 @@ class Build( verbose_name=_('Build Status'), default=BuildStatus.PENDING.value, choices=BuildStatus.items(), + status_class=BuildStatus, validators=[MinValueValidator(0)], help_text=_('Build status code'), ) diff --git a/src/backend/InvenTree/generic/states/fields.py b/src/backend/InvenTree/generic/states/fields.py index 7f67f5c14b..19d23b028e 100644 --- a/src/backend/InvenTree/generic/states/fields.py +++ b/src/backend/InvenTree/generic/states/fields.py @@ -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. """ + 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): """Deconstruct the field for migrations.""" name, path, args, kwargs = super().deconstruct() @@ -109,14 +123,23 @@ class InvenTreeCustomStatusModelField(models.PositiveIntegerField): """Ensure that the value is not an empty string.""" if value == '': value = None + return super().clean(value, model_instance) def add_field(self, cls, name): """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( default=None, verbose_name=_('Custom status key'), help_text=_('Additional status information for this item'), + validators=validators, blank=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. """ + def __init__(self, *args, **kwargs): + """Initialize the field.""" + super().__init__(*args, **kwargs) + class InvenTreeCustomStatusSerializerMixin: """Mixin to ensure custom status fields are set. diff --git a/src/backend/InvenTree/generic/states/states.py b/src/backend/InvenTree/generic/states/states.py index 3b92ea56c7..906ec5ec56 100644 --- a/src/backend/InvenTree/generic/states/states.py +++ b/src/backend/InvenTree/generic/states/states.py @@ -117,7 +117,7 @@ class StatusCode(BaseEnum): return [] @classmethod - def values(cls, key=None, custom=True): + def values(cls, key=None): """Return a dict representation containing all required information.""" elements = [itm for itm in cls if cls._is_element(itm.name)] @@ -167,14 +167,14 @@ class StatusCode(BaseEnum): return data @classmethod - def keys(cls): + def keys(cls, custom=True): """All status code keys.""" - return [x.value for x in cls.values()] + return [el[0] for el in cls.items(custom=custom)] @classmethod - def labels(cls): + def labels(cls, custom=True): """All status code labels.""" - return [x.label for x in cls.values()] + return [el[1] for el in cls.items(custom=custom)] @classmethod def names(cls): diff --git a/src/backend/InvenTree/generic/states/validators.py b/src/backend/InvenTree/generic/states/validators.py new file mode 100644 index 0000000000..98aab7400a --- /dev/null +++ b/src/backend/InvenTree/generic/states/validators.py @@ -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')) diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index 588f107f50..fafc707a58 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -483,6 +483,7 @@ class PurchaseOrder(TotalPriceMixin, Order): status = InvenTreeCustomStatusModelField( default=PurchaseOrderStatus.PENDING.value, choices=PurchaseOrderStatus.items(), + status_class=PurchaseOrderStatus, verbose_name=_('Status'), help_text=_('Purchase order status'), ) @@ -1029,6 +1030,7 @@ class SalesOrder(TotalPriceMixin, Order): status = InvenTreeCustomStatusModelField( default=SalesOrderStatus.PENDING.value, choices=SalesOrderStatus.items(), + status_class=SalesOrderStatus, verbose_name=_('Status'), help_text=_('Sales order status'), ) @@ -2231,6 +2233,7 @@ class ReturnOrder(TotalPriceMixin, Order): status = InvenTreeCustomStatusModelField( default=ReturnOrderStatus.PENDING.value, choices=ReturnOrderStatus.items(), + status_class=ReturnOrderStatus, verbose_name=_('Status'), help_text=_('Return order status'), ) @@ -2522,6 +2525,7 @@ class ReturnOrderLineItem(OrderLineItem): outcome = InvenTreeCustomStatusModelField( default=ReturnOrderLineStatus.PENDING.value, choices=ReturnOrderLineStatus.items(), + status_class=ReturnOrderLineStatus, verbose_name=_('Outcome'), help_text=_('Outcome for this line item'), ) diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 20d568e4bb..23e4d3d1f0 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -1020,6 +1020,7 @@ class StockItem( status = InvenTreeCustomStatusModelField( default=StockStatus.OK.value, + status_class=StockStatus, choices=StockStatus.items(), validators=[MinValueValidator(0)], )