Coverage for src/backend/InvenTree/plugin/base/integration/ValidationMixin.py: 62%
37 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1"""Validation mixin class definition."""
3from typing import Optional
5from django.core.exceptions import ValidationError
6from django.db.models import Model
8import common.models
9import part.models
10import stock.models
11from plugin import PluginMixinEnum
14class ValidationMixin:
15 """Mixin class that allows custom validation for various parts of InvenTree.
17 Any model which inherits from the PluginValidationMixin class is exposed here,
18 via the 'validate_model_instance' method (see below).
20 Additionally, custom generation and validation functionality is provided for:
22 - Part names
23 - Part IPN (internal part number) values
24 - Part parameter values
25 - Serial numbers
26 - Batch codes
28 Notes:
29 - Multiple ValidationMixin plugins can be used simultaneously
30 - The stub methods provided here generally return None (null value).
31 - The "first" plugin to return a non-null value for a particular method "wins"
32 - In the case of "validation" functions, all loaded plugins are checked until an exception is thrown
34 Implementing plugins may override any of the following methods which are of interest.
36 For 'validation' methods, there are three 'acceptable' outcomes:
37 - The method determines that the value is 'invalid' and raises a django.core.exceptions.ValidationError
38 - The method passes and returns None (the code then moves on to the next plugin)
39 - The method passes and returns True (and no subsequent plugins are checked)
41 """
43 class MixinMeta:
44 """Metaclass for this mixin."""
46 MIXIN_NAME = 'Validation'
48 def __init__(self):
49 """Register the mixin."""
50 super().__init__()
51 self.add_mixin(PluginMixinEnum.VALIDATION, True, __class__)
53 def raise_error(self, message):
54 """Raise a ValidationError with the given message."""
55 raise ValidationError(message)
57 def validate_model_deletion(self, instance: Model) -> None:
58 """Run custom validation when a model instance is being deleted.
60 This method is called when a model instance is being deleted.
61 It allows the plugin to raise a ValidationError if the instance cannot be deleted.
63 Arguments:
64 instance: The model instance to validate
66 Returns:
67 None: or True (refer to class docstring)
69 Raises:
70 ValidationError: If the instance cannot be deleted
71 """
72 return None
74 def validate_model_instance(
75 self, instance: Model, deltas: Optional[dict] = None
76 ) -> Optional[bool]:
77 """Run custom validation on a database model instance.
79 This method is called when a model instance is being validated.
80 It allows the plugin to raise a ValidationError on any field in the model.
82 Arguments:
83 instance: The model instance to validate
84 deltas: A dictionary of field names and updated values (if the instance is being updated)
86 Returns:
87 None: or True (refer to class docstring)
89 Raises:
90 ValidationError: If the instance is invalid
91 """
92 return None
94 def validate_part_name(self, name: str, part: part.models.Part) -> Optional[bool]:
95 """Perform validation on a proposed Part name.
97 Arguments:
98 name: The proposed part name
99 part: The part instance we are validating against
101 Returns:
102 None or True (refer to class docstring)
104 Raises:
105 ValidationError: If the proposed name is objectionable
106 """
107 return None
109 def validate_part_ipn(self, ipn: str, part: part.models.Part) -> Optional[bool]:
110 """Perform validation on a proposed Part IPN (internal part number).
112 Arguments:
113 ipn: The proposed part IPN
114 part: The Part instance we are validating against
116 Returns:
117 None or True (refer to class docstring)
119 Raises:
120 ValidationError: If the proposed IPN is objectionable
121 """
122 return None
124 def validate_batch_code(
125 self, batch_code: str, item: stock.models.StockItem
126 ) -> Optional[bool]:
127 """Validate the supplied batch code.
129 Arguments:
130 batch_code: The proposed batch code (string)
131 item: The StockItem instance we are validating against
133 Returns:
134 None or True (refer to class docstring)
136 Raises:
137 ValidationError: If the proposed batch code is objectionable
138 """
139 return None
141 def generate_batch_code(self, **kwargs) -> Optional[str]:
142 """Generate a new batch code.
144 This method is called when a new batch code is required.
146 kwargs:
147 Any additional keyword arguments which are passed through to the plugin, based on the context of the caller
149 Returns:
150 A new batch code (string) or None
151 """
152 return None
154 def validate_serial_number(
155 self,
156 serial: str,
157 part: part.models.Part,
158 stock_item: Optional[stock.models.StockItem] = None,
159 ) -> Optional[bool]:
160 """Validate the supplied serial number.
162 Arguments:
163 serial: The proposed serial number (string)
164 part: The Part instance for which this serial number is being validated
165 stock_item: The StockItem instance for which this serial number is being validated (if applicable)
167 Returns:
168 None or True (refer to class docstring)
170 Raises:
171 ValidationError: If the proposed serial is objectionable
172 """
173 return None
175 def convert_serial_to_int(self, serial: str) -> Optional[int]:
176 """Convert a serial number (string) into an integer representation.
178 This integer value is used for efficient sorting based on serial numbers.
180 A plugin which implements this method can either return:
182 - An integer based on the serial string, according to some algorithm
183 - A fixed value, such that serial number sorting reverts to the string representation
184 - None (null value) to let any other plugins perform the conversion
186 Note that there is no requirement for the returned integer value to be unique.
188 Arguments:
189 serial: Serial value (string)
191 Returns:
192 integer representation of the serial number, or None
193 """
194 return None
196 def get_latest_serial_number(self, part, **kwargs) -> Optional[str]:
197 """Return the 'latest' serial number for a given Part instance.
199 A plugin which implements this method can either return:
200 - A string which represents the "latest" serial number
201 - None (null value) if the latest value could not be determined
203 Arguments:
204 part: The Part instance for which the latest serial number is being requested
206 Returns:
207 The latest serial number (string), or None
208 """
209 # Default implementation returns None
210 return None
212 def increment_serial_number(
213 self, serial: str, part: Optional[part.models.Part] = None, **kwargs
214 ) -> Optional[str]:
215 """Return the next sequential serial based on the provided value.
217 A plugin which implements this method can either return:
219 - A string which represents the "next" serial number in the sequence
220 - None (null value) if the next value could not be determined
222 Arguments:
223 serial: Current serial value (string)
224 part: The Part instance for which this serial number is being incremented
226 Returns:
227 The next serial number in the sequence (string), or None
228 """
229 return None
231 def validate_parameter(
232 self, parameter: common.models.Parameter, data: str
233 ) -> Optional[bool]:
234 """Validate a parameter value.
236 Arguments:
237 parameter: The parameter we are validating
238 data: The proposed parameter value
240 Returns:
241 None or True (refer to class docstring)
243 Raises:
244 ValidationError: If the proposed parameter value is objectionable
245 """
246 return None