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

1"""Validation mixin class definition.""" 

2 

3from typing import Optional 

4 

5from django.core.exceptions import ValidationError 

6from django.db.models import Model 

7 

8import common.models 

9import part.models 

10import stock.models 

11from plugin import PluginMixinEnum 

12 

13 

14class ValidationMixin: 

15 """Mixin class that allows custom validation for various parts of InvenTree. 

16 

17 Any model which inherits from the PluginValidationMixin class is exposed here, 

18 via the 'validate_model_instance' method (see below). 

19 

20 Additionally, custom generation and validation functionality is provided for: 

21 

22 - Part names 

23 - Part IPN (internal part number) values 

24 - Part parameter values 

25 - Serial numbers 

26 - Batch codes 

27 

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 

33 

34 Implementing plugins may override any of the following methods which are of interest. 

35 

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) 

40 

41 """ 

42 

43 class MixinMeta: 

44 """Metaclass for this mixin.""" 

45 

46 MIXIN_NAME = 'Validation' 

47 

48 def __init__(self): 

49 """Register the mixin.""" 

50 super().__init__() 

51 self.add_mixin(PluginMixinEnum.VALIDATION, True, __class__) 

52 

53 def raise_error(self, message): 

54 """Raise a ValidationError with the given message.""" 

55 raise ValidationError(message) 

56 

57 def validate_model_deletion(self, instance: Model) -> None: 

58 """Run custom validation when a model instance is being deleted. 

59 

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. 

62 

63 Arguments: 

64 instance: The model instance to validate 

65 

66 Returns: 

67 None: or True (refer to class docstring) 

68 

69 Raises: 

70 ValidationError: If the instance cannot be deleted 

71 """ 

72 return None 

73 

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. 

78 

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. 

81 

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) 

85 

86 Returns: 

87 None: or True (refer to class docstring) 

88 

89 Raises: 

90 ValidationError: If the instance is invalid 

91 """ 

92 return None 

93 

94 def validate_part_name(self, name: str, part: part.models.Part) -> Optional[bool]: 

95 """Perform validation on a proposed Part name. 

96 

97 Arguments: 

98 name: The proposed part name 

99 part: The part instance we are validating against 

100 

101 Returns: 

102 None or True (refer to class docstring) 

103 

104 Raises: 

105 ValidationError: If the proposed name is objectionable 

106 """ 

107 return None 

108 

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). 

111 

112 Arguments: 

113 ipn: The proposed part IPN 

114 part: The Part instance we are validating against 

115 

116 Returns: 

117 None or True (refer to class docstring) 

118 

119 Raises: 

120 ValidationError: If the proposed IPN is objectionable 

121 """ 

122 return None 

123 

124 def validate_batch_code( 

125 self, batch_code: str, item: stock.models.StockItem 

126 ) -> Optional[bool]: 

127 """Validate the supplied batch code. 

128 

129 Arguments: 

130 batch_code: The proposed batch code (string) 

131 item: The StockItem instance we are validating against 

132 

133 Returns: 

134 None or True (refer to class docstring) 

135 

136 Raises: 

137 ValidationError: If the proposed batch code is objectionable 

138 """ 

139 return None 

140 

141 def generate_batch_code(self, **kwargs) -> Optional[str]: 

142 """Generate a new batch code. 

143 

144 This method is called when a new batch code is required. 

145 

146 kwargs: 

147 Any additional keyword arguments which are passed through to the plugin, based on the context of the caller 

148 

149 Returns: 

150 A new batch code (string) or None 

151 """ 

152 return None 

153 

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. 

161 

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) 

166 

167 Returns: 

168 None or True (refer to class docstring) 

169 

170 Raises: 

171 ValidationError: If the proposed serial is objectionable 

172 """ 

173 return None 

174 

175 def convert_serial_to_int(self, serial: str) -> Optional[int]: 

176 """Convert a serial number (string) into an integer representation. 

177 

178 This integer value is used for efficient sorting based on serial numbers. 

179 

180 A plugin which implements this method can either return: 

181 

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 

185 

186 Note that there is no requirement for the returned integer value to be unique. 

187 

188 Arguments: 

189 serial: Serial value (string) 

190 

191 Returns: 

192 integer representation of the serial number, or None 

193 """ 

194 return None 

195 

196 def get_latest_serial_number(self, part, **kwargs) -> Optional[str]: 

197 """Return the 'latest' serial number for a given Part instance. 

198 

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 

202 

203 Arguments: 

204 part: The Part instance for which the latest serial number is being requested 

205 

206 Returns: 

207 The latest serial number (string), or None 

208 """ 

209 # Default implementation returns None 

210 return None 

211 

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. 

216 

217 A plugin which implements this method can either return: 

218 

219 - A string which represents the "next" serial number in the sequence 

220 - None (null value) if the next value could not be determined 

221 

222 Arguments: 

223 serial: Current serial value (string) 

224 part: The Part instance for which this serial number is being incremented 

225 

226 Returns: 

227 The next serial number in the sequence (string), or None 

228 """ 

229 return None 

230 

231 def validate_parameter( 

232 self, parameter: common.models.Parameter, data: str 

233 ) -> Optional[bool]: 

234 """Validate a parameter value. 

235 

236 Arguments: 

237 parameter: The parameter we are validating 

238 data: The proposed parameter value 

239 

240 Returns: 

241 None or True (refer to class docstring) 

242 

243 Raises: 

244 ValidationError: If the proposed parameter value is objectionable 

245 """ 

246 return None