1 | # -*- coding: utf-8 -*-
|
---|
2 | # $Id: base.py 61280 2016-05-29 17:10:01Z vboxsync $
|
---|
3 | # pylint: disable=C0302
|
---|
4 |
|
---|
5 | """
|
---|
6 | Test Manager Core - Base Class(es).
|
---|
7 | """
|
---|
8 |
|
---|
9 | __copyright__ = \
|
---|
10 | """
|
---|
11 | Copyright (C) 2012-2015 Oracle Corporation
|
---|
12 |
|
---|
13 | This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
14 | available from http://www.215389.xyz. This file is free software;
|
---|
15 | you can redistribute it and/or modify it under the terms of the GNU
|
---|
16 | General Public License (GPL) as published by the Free Software
|
---|
17 | Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
18 | VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
19 | hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
20 |
|
---|
21 | The contents of this file may alternatively be used under the terms
|
---|
22 | of the Common Development and Distribution License Version 1.0
|
---|
23 | (CDDL) only, as it comes in the "COPYING.CDDL" file of the
|
---|
24 | VirtualBox OSE distribution, in which case the provisions of the
|
---|
25 | CDDL are applicable instead of those of the GPL.
|
---|
26 |
|
---|
27 | You may elect to license modified versions of this file under the
|
---|
28 | terms and conditions of either the GPL or the CDDL or both.
|
---|
29 | """
|
---|
30 | __version__ = "$Revision: 61280 $"
|
---|
31 |
|
---|
32 |
|
---|
33 | # Standard python imports.
|
---|
34 | import copy;
|
---|
35 | import re;
|
---|
36 | import socket;
|
---|
37 | import sys;
|
---|
38 | import uuid;
|
---|
39 | import unittest;
|
---|
40 |
|
---|
41 | # Validation Kit imports.
|
---|
42 | from common import utils;
|
---|
43 |
|
---|
44 | # Python 3 hacks:
|
---|
45 | if sys.version_info[0] >= 3:
|
---|
46 | long = int # pylint: disable=W0622,C0103
|
---|
47 |
|
---|
48 |
|
---|
49 | class TMExceptionBase(Exception):
|
---|
50 | """
|
---|
51 | For exceptions raised by any TestManager component.
|
---|
52 | """
|
---|
53 | pass;
|
---|
54 |
|
---|
55 |
|
---|
56 | class TMTooManyRows(TMExceptionBase):
|
---|
57 | """
|
---|
58 | Too many rows in the result.
|
---|
59 | Used by ModelLogicBase decendants.
|
---|
60 | """
|
---|
61 | pass;
|
---|
62 |
|
---|
63 |
|
---|
64 | class TMRowNotFound(TMExceptionBase):
|
---|
65 | """
|
---|
66 | Database row not found.
|
---|
67 | Used by ModelLogicBase decendants.
|
---|
68 | """
|
---|
69 | pass;
|
---|
70 |
|
---|
71 |
|
---|
72 | class TMRowAlreadyExists(TMExceptionBase):
|
---|
73 | """
|
---|
74 | Database row already exists (typically raised by addEntry).
|
---|
75 | Used by ModelLogicBase decendants.
|
---|
76 | """
|
---|
77 | pass;
|
---|
78 |
|
---|
79 |
|
---|
80 | class TMInvalidData(TMExceptionBase):
|
---|
81 | """
|
---|
82 | Data validation failed.
|
---|
83 | Used by ModelLogicBase decendants.
|
---|
84 | """
|
---|
85 | pass;
|
---|
86 |
|
---|
87 |
|
---|
88 | class TMRowInUse(TMExceptionBase):
|
---|
89 | """
|
---|
90 | Database row is in use and cannot be deleted.
|
---|
91 | Used by ModelLogicBase decendants.
|
---|
92 | """
|
---|
93 | pass;
|
---|
94 |
|
---|
95 |
|
---|
96 | class ModelBase(object): # pylint: disable=R0903
|
---|
97 | """
|
---|
98 | Something all classes in the logical model inherits from.
|
---|
99 |
|
---|
100 | Not sure if 'logical model' is the right term here.
|
---|
101 | Will see if it has any purpose later on...
|
---|
102 | """
|
---|
103 |
|
---|
104 | def __init__(self):
|
---|
105 | pass;
|
---|
106 |
|
---|
107 |
|
---|
108 | class ModelDataBase(ModelBase): # pylint: disable=R0903
|
---|
109 | """
|
---|
110 | Something all classes in the data classes in the logical model inherits from.
|
---|
111 | """
|
---|
112 |
|
---|
113 | ## Child classes can use this to list array attributes which should use
|
---|
114 | # an empty array ([]) instead of None as database NULL value.
|
---|
115 | kasAltArrayNull = [];
|
---|
116 |
|
---|
117 | ## validate
|
---|
118 | ## @{
|
---|
119 | ksValidateFor_Add = 'add';
|
---|
120 | ksValidateFor_AddForeignId = 'add-foreign-id';
|
---|
121 | ksValidateFor_Edit = 'edit';
|
---|
122 | ksValidateFor_Other = 'other';
|
---|
123 | ## @}
|
---|
124 |
|
---|
125 |
|
---|
126 | def __init__(self):
|
---|
127 | ModelBase.__init__(self);
|
---|
128 |
|
---|
129 |
|
---|
130 | #
|
---|
131 | # Standard methods implemented by combining python magic and hungarian prefixes.
|
---|
132 | #
|
---|
133 |
|
---|
134 | def getDataAttributes(self):
|
---|
135 | """
|
---|
136 | Returns a list of data attributes.
|
---|
137 | """
|
---|
138 | asRet = [];
|
---|
139 | asAttrs = dir(self);
|
---|
140 | for sAttr in asAttrs:
|
---|
141 | if sAttr[0] == '_' or sAttr[0] == 'k':
|
---|
142 | continue;
|
---|
143 | oValue = getattr(self, sAttr);
|
---|
144 | if callable(oValue):
|
---|
145 | continue;
|
---|
146 | asRet.append(sAttr);
|
---|
147 | return asRet;
|
---|
148 |
|
---|
149 | def initFromOther(self, oOther):
|
---|
150 | """
|
---|
151 | Initialize this object with the values from another instance (child
|
---|
152 | class instance is accepted).
|
---|
153 |
|
---|
154 | This serves as a kind of copy constructor.
|
---|
155 |
|
---|
156 | Returns self. May raise exception if the type of other object differs
|
---|
157 | or is damaged.
|
---|
158 | """
|
---|
159 | for sAttr in self.getDataAttributes():
|
---|
160 | setattr(self, sAttr, getattr(oOther, sAttr));
|
---|
161 | return self;
|
---|
162 |
|
---|
163 | @staticmethod
|
---|
164 | def getHungarianPrefix(sName):
|
---|
165 | """
|
---|
166 | Returns the hungarian prefix of the given name.
|
---|
167 | """
|
---|
168 | for i, _ in enumerate(sName):
|
---|
169 | if sName[i] not in ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
---|
170 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']:
|
---|
171 | assert re.search('^[A-Z][a-zA-Z0-9]*$', sName[i:]) is not None;
|
---|
172 | return sName[:i];
|
---|
173 | return sName;
|
---|
174 |
|
---|
175 | def getAttributeParamNullValues(self, sAttr):
|
---|
176 | """
|
---|
177 | Returns a list of parameter NULL values, with the preferred one being
|
---|
178 | the first element.
|
---|
179 |
|
---|
180 | Child classes can override this to handle one or more attributes specially.
|
---|
181 | """
|
---|
182 | sPrefix = self.getHungarianPrefix(sAttr);
|
---|
183 | if sPrefix in ['id', 'uid', 'i', 'off', 'pct']:
|
---|
184 | return [-1, '', '-1',];
|
---|
185 | elif sPrefix in ['l', 'c',]:
|
---|
186 | return [long(-1), '', '-1',];
|
---|
187 | elif sPrefix == 'f':
|
---|
188 | return ['',];
|
---|
189 | elif sPrefix in ['enm', 'ip', 's', 'ts', 'uuid']:
|
---|
190 | return ['',];
|
---|
191 | elif sPrefix in ['ai', 'aid', 'al', 'as']:
|
---|
192 | return [[], '', None]; ## @todo ??
|
---|
193 | elif sPrefix == 'bm':
|
---|
194 | return ['', [],]; ## @todo bitmaps.
|
---|
195 | raise TMExceptionBase('Unable to classify "%s" (prefix %s)' % (sAttr, sPrefix));
|
---|
196 |
|
---|
197 | def isAttributeNull(self, sAttr, oValue):
|
---|
198 | """
|
---|
199 | Checks if the specified attribute value indicates NULL.
|
---|
200 | Return True/False.
|
---|
201 |
|
---|
202 | Note! This isn't entirely kosher actually.
|
---|
203 | """
|
---|
204 | if oValue is None:
|
---|
205 | return True;
|
---|
206 | aoNilValues = self.getAttributeParamNullValues(sAttr);
|
---|
207 | return oValue in aoNilValues;
|
---|
208 |
|
---|
209 | def _convertAttributeFromParamNull(self, sAttr, oValue):
|
---|
210 | """
|
---|
211 | Converts an attribute from parameter NULL to database NULL value.
|
---|
212 | Returns the new attribute value.
|
---|
213 | """
|
---|
214 | aoNullValues = self.getAttributeParamNullValues(sAttr);
|
---|
215 | if oValue in aoNullValues:
|
---|
216 | oValue = None if sAttr not in self.kasAltArrayNull else [];
|
---|
217 | #
|
---|
218 | # Perform deep conversion on ModelDataBase object and lists of them.
|
---|
219 | #
|
---|
220 | elif isinstance(oValue, list) and len(oValue) > 0 and isinstance(oValue[0], ModelDataBase):
|
---|
221 | oValue = copy.copy(oValue);
|
---|
222 | for i, _ in enumerate(oValue):
|
---|
223 | assert isinstance(oValue[i], ModelDataBase);
|
---|
224 | oValue[i] = copy.copy(oValue[i]);
|
---|
225 | oValue[i].convertFromParamNull();
|
---|
226 |
|
---|
227 | elif isinstance(oValue, ModelDataBase):
|
---|
228 | oValue = copy.copy(oValue);
|
---|
229 | oValue.convertFromParamNull();
|
---|
230 |
|
---|
231 | return oValue;
|
---|
232 |
|
---|
233 | def convertFromParamNull(self):
|
---|
234 | """
|
---|
235 | Converts from parameter NULL values to database NULL values (None).
|
---|
236 | Returns self.
|
---|
237 | """
|
---|
238 | for sAttr in self.getDataAttributes():
|
---|
239 | oValue = getattr(self, sAttr);
|
---|
240 | oNewValue = self._convertAttributeFromParamNull(sAttr, oValue);
|
---|
241 | if oValue != oNewValue:
|
---|
242 | setattr(self, sAttr, oNewValue);
|
---|
243 | return self;
|
---|
244 |
|
---|
245 | def _convertAttributeToParamNull(self, sAttr, oValue):
|
---|
246 | """
|
---|
247 | Converts an attribute from database NULL to a sepcial value we can pass
|
---|
248 | thru parameter list.
|
---|
249 | Returns the new attribute value.
|
---|
250 | """
|
---|
251 | if oValue is None:
|
---|
252 | oValue = self.getAttributeParamNullValues(sAttr)[0];
|
---|
253 | #
|
---|
254 | # Perform deep conversion on ModelDataBase object and lists of them.
|
---|
255 | #
|
---|
256 | elif isinstance(oValue, list) and len(oValue) > 0 and isinstance(oValue[0], ModelDataBase):
|
---|
257 | oValue = copy.copy(oValue);
|
---|
258 | for i, _ in enumerate(oValue):
|
---|
259 | assert isinstance(oValue[i], ModelDataBase);
|
---|
260 | oValue[i] = copy.copy(oValue[i]);
|
---|
261 | oValue[i].convertToParamNull();
|
---|
262 |
|
---|
263 | elif isinstance(oValue, ModelDataBase):
|
---|
264 | oValue = copy.copy(oValue);
|
---|
265 | oValue.convertToParamNull();
|
---|
266 |
|
---|
267 | return oValue;
|
---|
268 |
|
---|
269 | def convertToParamNull(self):
|
---|
270 | """
|
---|
271 | Converts from database NULL values (None) to special values we can
|
---|
272 | pass thru parameters list.
|
---|
273 | Returns self.
|
---|
274 | """
|
---|
275 | for sAttr in self.getDataAttributes():
|
---|
276 | oValue = getattr(self, sAttr);
|
---|
277 | oNewValue = self._convertAttributeToParamNull(sAttr, oValue);
|
---|
278 | if oValue != oNewValue:
|
---|
279 | setattr(self, sAttr, oNewValue);
|
---|
280 | return self;
|
---|
281 |
|
---|
282 | def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb):
|
---|
283 | """
|
---|
284 | Validates and convert one attribute.
|
---|
285 | Returns the converted value.
|
---|
286 |
|
---|
287 | Child classes can override this to handle one or more attributes specially.
|
---|
288 | Note! oDb can be None.
|
---|
289 | """
|
---|
290 | sPrefix = self.getHungarianPrefix(sAttr);
|
---|
291 |
|
---|
292 | if sPrefix in ['id', 'uid']:
|
---|
293 | (oNewValue, sError) = self.validateInt( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
294 | elif sPrefix in ['i', 'off', 'pct']:
|
---|
295 | (oNewValue, sError) = self.validateInt( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
296 | iMin = getattr(self, 'kiMin_' + sAttr, 0),
|
---|
297 | iMax = getattr(self, 'kiMax_' + sAttr, 0x7ffffffe));
|
---|
298 | elif sPrefix in ['l', 'c']:
|
---|
299 | (oNewValue, sError) = self.validateLong(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
300 | lMin = getattr(self, 'klMin_' + sAttr, 0),
|
---|
301 | lMax = getattr(self, 'klMax_' + sAttr, None));
|
---|
302 | elif sPrefix == 'f':
|
---|
303 | if oValue is '' and not fAllowNull: oValue = '0'; # HACK ALERT! Checkboxes are only added when checked.
|
---|
304 | (oNewValue, sError) = self.validateBool(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
305 | elif sPrefix == 'ts':
|
---|
306 | (oNewValue, sError) = self.validateTs( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
307 | elif sPrefix == 'ip':
|
---|
308 | (oNewValue, sError) = self.validateIp( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
309 | elif sPrefix == 'uuid':
|
---|
310 | (oNewValue, sError) = self.validateUuid(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
311 | elif sPrefix == 'enm':
|
---|
312 | (oNewValue, sError) = self.validateWord(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
313 | asValid = getattr(self, 'kasValidValues_' + sAttr)); # The list is required.
|
---|
314 | elif sPrefix == 's':
|
---|
315 | (oNewValue, sError) = self.validateStr( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
316 | cchMin = getattr(self, 'kcchMin_' + sAttr, 0),
|
---|
317 | cchMax = getattr(self, 'kcchMax_' + sAttr, 4096),
|
---|
318 | fAllowUnicodeSymbols = getattr(self, 'kfAllowUnicode_' + sAttr, False) );
|
---|
319 | ## @todo al.
|
---|
320 | elif sPrefix == 'aid':
|
---|
321 | (oNewValue, sError) = self.validateListOfInts(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
322 | iMin = 1, iMax = 0x7ffffffe);
|
---|
323 | elif sPrefix == 'as':
|
---|
324 | (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
325 | asValidValues = getattr(self, 'kasValidValues_' + sAttr, None),
|
---|
326 | cchMin = getattr(self, 'kcchMin_' + sAttr, 0 if fAllowNull else 1),
|
---|
327 | cchMax = getattr(self, 'kcchMax_' + sAttr, 4096));
|
---|
328 |
|
---|
329 | elif sPrefix == 'bm':
|
---|
330 | ## @todo figure out bitfields.
|
---|
331 | (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
332 | else:
|
---|
333 | raise TMExceptionBase('Unable to classify "%s" (prefix %s)' % (sAttr, sPrefix));
|
---|
334 |
|
---|
335 | _ = sParam; _ = oDb;
|
---|
336 | return (oNewValue, sError);
|
---|
337 |
|
---|
338 | def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ksValidateFor_Other):
|
---|
339 | """
|
---|
340 | Worker for implementing validateAndConvert().
|
---|
341 | """
|
---|
342 | dErrors = dict();
|
---|
343 | for sAttr in self.getDataAttributes():
|
---|
344 | oValue = getattr(self, sAttr);
|
---|
345 | sParam = getattr(self, 'ksParam_' + sAttr);
|
---|
346 | aoNilValues = self.getAttributeParamNullValues(sAttr);
|
---|
347 | aoNilValues.append(None);
|
---|
348 |
|
---|
349 | (oNewValue, sError) = self._validateAndConvertAttribute(sAttr, sParam, oValue, aoNilValues,
|
---|
350 | sAttr in asAllowNullAttributes, oDb);
|
---|
351 | if oValue != oNewValue:
|
---|
352 | setattr(self, sAttr, oNewValue);
|
---|
353 | if sError is not None:
|
---|
354 | dErrors[sParam] = sError;
|
---|
355 |
|
---|
356 | # Check the NULL requirements of the primary ID(s) for the 'add' and 'edit' actions.
|
---|
357 | if enmValidateFor == ModelDataBase.ksValidateFor_Add \
|
---|
358 | or enmValidateFor == ModelDataBase.ksValidateFor_AddForeignId \
|
---|
359 | or enmValidateFor == ModelDataBase.ksValidateFor_Edit:
|
---|
360 | fMustBeNull = enmValidateFor == ModelDataBase.ksValidateFor_Add;
|
---|
361 | sAttr = getattr(self, 'ksIdAttr', None);
|
---|
362 | if sAttr is not None:
|
---|
363 | oValue = getattr(self, sAttr);
|
---|
364 | if self.isAttributeNull(sAttr, oValue) != fMustBeNull:
|
---|
365 | sParam = getattr(self, 'ksParam_' + sAttr);
|
---|
366 | sErrMsg = 'Must be NULL!' if fMustBeNull else 'Must not be NULL!'
|
---|
367 | if sParam in dErrors:
|
---|
368 | dErrors[sParam] += ' ' + sErrMsg;
|
---|
369 | else:
|
---|
370 | dErrors[sParam] = sErrMsg;
|
---|
371 |
|
---|
372 | return dErrors;
|
---|
373 |
|
---|
374 | def validateAndConvert(self, oDb, enmValidateFor = ksValidateFor_Other):
|
---|
375 | """
|
---|
376 | Validates the input and converts valid fields to their right type.
|
---|
377 | Returns a dictionary with per field reports, only invalid fields will
|
---|
378 | be returned, so an empty dictionary means that the data is valid.
|
---|
379 |
|
---|
380 | The dictionary keys are ksParam_*.
|
---|
381 |
|
---|
382 | Child classes can override _validateAndConvertAttribute to handle
|
---|
383 | selected fields specially. There are also a few class variables that
|
---|
384 | can be used to advice the validation: kcchMin_sAttr, kcchMax_sAttr,
|
---|
385 | kiMin_iAttr, kiMax_iAttr, klMin_lAttr, klMax_lAttr,
|
---|
386 | kasValidValues_enmAttr, and kasAllowNullAttributes.
|
---|
387 | """
|
---|
388 | return self._validateAndConvertWorker(getattr(self, 'kasAllowNullAttributes', list()), oDb,
|
---|
389 | enmValidateFor = enmValidateFor);
|
---|
390 |
|
---|
391 | def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict):
|
---|
392 | """
|
---|
393 | Calculate the attribute value when initialized from a parameter.
|
---|
394 |
|
---|
395 | Returns the new value, with parameter NULL values. Raises exception on
|
---|
396 | invalid parameter value.
|
---|
397 |
|
---|
398 | Child classes can override to do special parameter conversion jobs.
|
---|
399 | """
|
---|
400 | sPrefix = self.getHungarianPrefix(sAttr);
|
---|
401 | asValidValues = getattr(self, 'kasValidValues_' + sAttr, None);
|
---|
402 | if fStrict:
|
---|
403 | if sPrefix == 'f':
|
---|
404 | # HACK ALERT! Checkboxes are only present when checked, so we always have to provide a default.
|
---|
405 | oNewValue = oDisp.getStringParam(sParam, asValidValues, '0');
|
---|
406 | elif sPrefix[0] == 'a':
|
---|
407 | # HACK ALERT! Lists are not present if empty.
|
---|
408 | oNewValue = oDisp.getListOfStrParams(sParam, []);
|
---|
409 | else:
|
---|
410 | oNewValue = oDisp.getStringParam(sParam, asValidValues, None);
|
---|
411 | else:
|
---|
412 | if sPrefix[0] == 'a':
|
---|
413 | oNewValue = oDisp.getListOfStrParams(sParam, []);
|
---|
414 | else:
|
---|
415 | assert oValue is not None, 'sAttr=%s' % (sAttr,);
|
---|
416 | oNewValue = oDisp.getStringParam(sParam, asValidValues, oValue);
|
---|
417 | return oNewValue;
|
---|
418 |
|
---|
419 | def initFromParams(self, oDisp, fStrict = True):
|
---|
420 | """
|
---|
421 | Initialize the object from parameters.
|
---|
422 | The input is not validated at all, except that all parameters must be
|
---|
423 | present when fStrict is True.
|
---|
424 |
|
---|
425 | Returns self. Raises exception on invalid parameter value.
|
---|
426 |
|
---|
427 | Note! The returned object has parameter NULL values, not database ones!
|
---|
428 | """
|
---|
429 |
|
---|
430 | self.convertToParamNull()
|
---|
431 | for sAttr in self.getDataAttributes():
|
---|
432 | oValue = getattr(self, sAttr);
|
---|
433 | oNewValue = self.convertParamToAttribute(sAttr, getattr(self, 'ksParam_' + sAttr), oValue, oDisp, fStrict);
|
---|
434 | if oNewValue != oValue:
|
---|
435 | setattr(self, sAttr, oNewValue);
|
---|
436 | return self;
|
---|
437 |
|
---|
438 | def areAttributeValuesEqual(self, sAttr, sPrefix, oValue1, oValue2):
|
---|
439 | """
|
---|
440 | Called to compare two attribute values and python thinks differs.
|
---|
441 |
|
---|
442 | Returns True/False.
|
---|
443 |
|
---|
444 | Child classes can override this to do special compares of things like arrays.
|
---|
445 | """
|
---|
446 | # Just in case someone uses it directly.
|
---|
447 | if oValue1 == oValue2:
|
---|
448 | return True;
|
---|
449 |
|
---|
450 | #
|
---|
451 | # Timestamps can be both string (param) and object (db)
|
---|
452 | # depending on the data source. Compare string values to make
|
---|
453 | # sure we're doing the right thing here.
|
---|
454 | #
|
---|
455 | if sPrefix == 'ts':
|
---|
456 | return str(oValue1) == str(oValue2);
|
---|
457 |
|
---|
458 | #
|
---|
459 | # Some generic code handling ModelDataBase children.
|
---|
460 | #
|
---|
461 | if isinstance(oValue1, list) and isinstance(oValue2, list):
|
---|
462 | if len(oValue1) == len(oValue2):
|
---|
463 | for i, _ in enumerate(oValue1):
|
---|
464 | if not isinstance(oValue1[i], ModelDataBase) \
|
---|
465 | or type(oValue1) is not type(oValue2):
|
---|
466 | return False;
|
---|
467 | if not oValue1[i].isEqual(oValue2[i]):
|
---|
468 | return False;
|
---|
469 | return True;
|
---|
470 |
|
---|
471 | elif isinstance(oValue1, ModelDataBase) \
|
---|
472 | and type(oValue1) is type(oValue2):
|
---|
473 | return oValue1[i].isEqual(oValue2[i]);
|
---|
474 |
|
---|
475 | _ = sAttr;
|
---|
476 | return False;
|
---|
477 |
|
---|
478 | def isEqual(self, oOther):
|
---|
479 | """ Compares two instances. """
|
---|
480 | for sAttr in self.getDataAttributes():
|
---|
481 | if getattr(self, sAttr) != getattr(oOther, sAttr):
|
---|
482 | # Delegate the final decision to an overridable method.
|
---|
483 | if not self.areAttributeValuesEqual(sAttr, self.getHungarianPrefix(sAttr),
|
---|
484 | getattr(self, sAttr), getattr(oOther, sAttr)):
|
---|
485 | return False;
|
---|
486 | return True;
|
---|
487 |
|
---|
488 | def isEqualEx(self, oOther, asExcludeAttrs):
|
---|
489 | """ Compares two instances, omitting the given attributes. """
|
---|
490 | for sAttr in self.getDataAttributes():
|
---|
491 | if sAttr not in asExcludeAttrs \
|
---|
492 | and getattr(self, sAttr) != getattr(oOther, sAttr):
|
---|
493 | # Delegate the final decision to an overridable method.
|
---|
494 | if not self.areAttributeValuesEqual(sAttr, self.getHungarianPrefix(sAttr),
|
---|
495 | getattr(self, sAttr), getattr(oOther, sAttr)):
|
---|
496 | return False;
|
---|
497 | return True;
|
---|
498 |
|
---|
499 | def reinitToNull(self):
|
---|
500 | """
|
---|
501 | Reinitializes the object to (database) NULL values.
|
---|
502 | Returns self.
|
---|
503 | """
|
---|
504 | for sAttr in self.getDataAttributes():
|
---|
505 | setattr(self, sAttr, None);
|
---|
506 | return self;
|
---|
507 |
|
---|
508 | def toString(self):
|
---|
509 | """
|
---|
510 | Stringifies the object.
|
---|
511 | Returns string representation.
|
---|
512 | """
|
---|
513 |
|
---|
514 | sMembers = '';
|
---|
515 | for sAttr in self.getDataAttributes():
|
---|
516 | oValue = getattr(self, sAttr);
|
---|
517 | sMembers += ', %s=%s' % (sAttr, oValue);
|
---|
518 |
|
---|
519 | oClass = type(self);
|
---|
520 | if sMembers == '':
|
---|
521 | return '<%s>' % (oClass.__name__);
|
---|
522 | return '<%s: %s>' % (oClass.__name__, sMembers[2:]);
|
---|
523 |
|
---|
524 | def __str__(self):
|
---|
525 | return self.toString();
|
---|
526 |
|
---|
527 |
|
---|
528 |
|
---|
529 | #
|
---|
530 | # New validation helpers.
|
---|
531 | #
|
---|
532 | # These all return (oValue, sError), where sError is None when the value
|
---|
533 | # is valid and an error message when not. On success and in case of
|
---|
534 | # range errors, oValue is converted into the requested type.
|
---|
535 | #
|
---|
536 |
|
---|
537 | @staticmethod
|
---|
538 | def validateInt(sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, '']), fAllowNull = True):
|
---|
539 | """ Validates an integer field. """
|
---|
540 | if sValue in aoNilValues:
|
---|
541 | if fAllowNull:
|
---|
542 | return (None if sValue is None else aoNilValues[0], None);
|
---|
543 | return (sValue, 'Mandatory.');
|
---|
544 |
|
---|
545 | try:
|
---|
546 | if utils.isString(sValue):
|
---|
547 | iValue = int(sValue, 0);
|
---|
548 | else:
|
---|
549 | iValue = int(sValue);
|
---|
550 | except:
|
---|
551 | return (sValue, 'Not an integer');
|
---|
552 |
|
---|
553 | if iValue in aoNilValues:
|
---|
554 | return (aoNilValues[0], None if fAllowNull else 'Mandatory.');
|
---|
555 |
|
---|
556 | if iValue < iMin:
|
---|
557 | return (iValue, 'Value too small (min %d)' % (iMin,));
|
---|
558 | elif iValue > iMax:
|
---|
559 | return (iValue, 'Value too high (max %d)' % (iMax,));
|
---|
560 | return (iValue, None);
|
---|
561 |
|
---|
562 | @staticmethod
|
---|
563 | def validateLong(sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, '']), fAllowNull = True):
|
---|
564 | """ Validates an long integer field. """
|
---|
565 | if sValue in aoNilValues:
|
---|
566 | if fAllowNull:
|
---|
567 | return (None if sValue is None else aoNilValues[0], None);
|
---|
568 | return (sValue, 'Mandatory.');
|
---|
569 | try:
|
---|
570 | if utils.isString(sValue):
|
---|
571 | lValue = long(sValue, 0);
|
---|
572 | else:
|
---|
573 | lValue = long(sValue);
|
---|
574 | except:
|
---|
575 | return (sValue, 'Not a long integer');
|
---|
576 |
|
---|
577 | if lValue in aoNilValues:
|
---|
578 | return (aoNilValues[0], None if fAllowNull else 'Mandatory.');
|
---|
579 |
|
---|
580 | if lMin is not None and lValue < lMin:
|
---|
581 | return (lValue, 'Value too small (min %d)' % (lMin,));
|
---|
582 | elif lMax is not None and lValue > lMax:
|
---|
583 | return (lValue, 'Value too high (max %d)' % (lMax,));
|
---|
584 | return (lValue, None);
|
---|
585 |
|
---|
586 | @staticmethod
|
---|
587 | def validateTs(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
588 | """ Validates a timestamp field. """
|
---|
589 | if sValue in aoNilValues:
|
---|
590 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
591 | if not utils.isString(sValue):
|
---|
592 | return (sValue, None);
|
---|
593 |
|
---|
594 | sError = None;
|
---|
595 | if len(sValue) == len('2012-10-08 01:54:06.364207+02:00'):
|
---|
596 | oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d ([012]\d):[0-5]\d:([0-6]\d).\d{6}[+-](\d\d):(\d\d)', sValue);
|
---|
597 | if oRes is not None \
|
---|
598 | and ( int(oRes.group(6)) > 12 \
|
---|
599 | or int(oRes.group(7)) >= 60):
|
---|
600 | sError = 'Invalid timezone offset.';
|
---|
601 | elif len(sValue) == len('2012-10-08 01:54:06.00'):
|
---|
602 | oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d ([012]\d):[0-5]\d:([0-6]\d).\d{2}', sValue);
|
---|
603 | elif len(sValue) == len('9999-12-31 23:59:59.999999'):
|
---|
604 | oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d ([012]\d):[0-5]\d:([0-6]\d).\d{6}', sValue);
|
---|
605 | elif len(sValue) == len('999999-12-31 00:00:00.00'):
|
---|
606 | oRes = re.match(r'(\d{6})-([01]\d)-([0123])\d ([012]\d):[0-5]\d:([0-6]\d).\d{2}', sValue);
|
---|
607 | elif len(sValue) == len('9999-12-31T23:59:59.999999Z'):
|
---|
608 | oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d[Tt]([012]\d):[0-5]\d:([0-6]\d).\d{6}[Zz]', sValue);
|
---|
609 | elif len(sValue) == len('9999-12-31T23:59:59.999999999Z'):
|
---|
610 | oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d[Tt]([012]\d):[0-5]\d:([0-6]\d).\d{9}[Zz]', sValue);
|
---|
611 | else:
|
---|
612 | return (sValue, 'Invalid timestamp length.');
|
---|
613 |
|
---|
614 | if oRes is None:
|
---|
615 | sError = 'Invalid timestamp (format: 2012-10-08 01:54:06.364207+02:00).';
|
---|
616 | else:
|
---|
617 | iYear = int(oRes.group(1));
|
---|
618 | if iYear % 4 == 0 and (iYear % 100 != 0 or iYear % 400 == 0):
|
---|
619 | acDaysOfMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
---|
620 | else:
|
---|
621 | acDaysOfMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
---|
622 | iMonth = int(oRes.group(2));
|
---|
623 | iDay = int(oRes.group(3));
|
---|
624 | iHour = int(oRes.group(4));
|
---|
625 | iSec = int(oRes.group(5));
|
---|
626 | if iMonth > 12:
|
---|
627 | sError = 'Invalid timestamp month.';
|
---|
628 | elif iDay > acDaysOfMonth[iMonth - 1]:
|
---|
629 | sError = 'Invalid timestamp day-of-month (%02d has %d days).' % (iMonth, acDaysOfMonth[iMonth - 1]);
|
---|
630 | elif iHour > 23:
|
---|
631 | sError = 'Invalid timestamp hour.'
|
---|
632 | elif iSec >= 61:
|
---|
633 | sError = 'Invalid timestamp second.'
|
---|
634 | elif iSec >= 60:
|
---|
635 | sError = 'Invalid timestamp: no leap seconds, please.'
|
---|
636 | return (sValue, sError);
|
---|
637 |
|
---|
638 | @staticmethod
|
---|
639 | def validateIp(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
640 | """ Validates an IP address field. """
|
---|
641 | if sValue in aoNilValues:
|
---|
642 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
643 |
|
---|
644 | if sValue == '::1':
|
---|
645 | return (sValue, None);
|
---|
646 |
|
---|
647 | try:
|
---|
648 | socket.inet_pton(socket.AF_INET, sValue); # pylint: disable=E1101
|
---|
649 | except:
|
---|
650 | try:
|
---|
651 | socket.inet_pton(socket.AF_INET6, sValue); # pylint: disable=E1101
|
---|
652 | except:
|
---|
653 | return (sValue, 'Not a valid IP address.');
|
---|
654 |
|
---|
655 | return (sValue, None);
|
---|
656 |
|
---|
657 | @staticmethod
|
---|
658 | def validateBool(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
659 | """ Validates a boolean field. """
|
---|
660 | if sValue in aoNilValues:
|
---|
661 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
662 |
|
---|
663 | if sValue in ('True', 'true', '1', True):
|
---|
664 | return (True, None);
|
---|
665 | if sValue in ('False', 'false', '0', False):
|
---|
666 | return (False, None);
|
---|
667 | return (sValue, 'Invalid boolean value.');
|
---|
668 |
|
---|
669 | @staticmethod
|
---|
670 | def validateUuid(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
671 | """ Validates an UUID field. """
|
---|
672 | if sValue in aoNilValues:
|
---|
673 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
674 |
|
---|
675 | try:
|
---|
676 | sValue = str(uuid.UUID(sValue));
|
---|
677 | except:
|
---|
678 | return (sValue, 'Invalid UUID value.');
|
---|
679 | return (sValue, None);
|
---|
680 |
|
---|
681 | @staticmethod
|
---|
682 | def validateWord(sValue, cchMin = 1, cchMax = 64, asValid = None, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
683 | """ Validates a word field. """
|
---|
684 | if sValue in aoNilValues:
|
---|
685 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
686 |
|
---|
687 | if re.search('[^a-zA-Z0-9_-]', sValue) is not None:
|
---|
688 | sError = 'Single word ([a-zA-Z0-9_-]), please.';
|
---|
689 | elif cchMin is not None and len(sValue) < cchMin:
|
---|
690 | sError = 'Too short, min %s chars' % (cchMin,);
|
---|
691 | elif cchMax is not None and len(sValue) > cchMax:
|
---|
692 | sError = 'Too long, max %s chars' % (cchMax,);
|
---|
693 | elif asValid is not None and sValue not in asValid:
|
---|
694 | sError = 'Invalid value "%s", must be one of: %s' % (sValue, asValid);
|
---|
695 | else:
|
---|
696 | sError = None;
|
---|
697 | return (sValue, sError);
|
---|
698 |
|
---|
699 | @staticmethod
|
---|
700 | def validateStr(sValue, cchMin = 0, cchMax = 4096, aoNilValues = tuple([None, '']), fAllowNull = True,
|
---|
701 | fAllowUnicodeSymbols = False):
|
---|
702 | """ Validates a string field. """
|
---|
703 | if sValue in aoNilValues:
|
---|
704 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
705 |
|
---|
706 | if cchMin is not None and len(sValue) < cchMin:
|
---|
707 | sError = 'Too short, min %s chars' % (cchMin,);
|
---|
708 | elif cchMax is not None and len(sValue) > cchMax:
|
---|
709 | sError = 'Too long, max %s chars' % (cchMax,);
|
---|
710 | elif fAllowUnicodeSymbols is False and utils.hasNonAsciiCharacters(sValue):
|
---|
711 | sError = 'Non-ascii characters not allowed'
|
---|
712 | else:
|
---|
713 | sError = None;
|
---|
714 | return (sValue, sError);
|
---|
715 |
|
---|
716 | @staticmethod
|
---|
717 | def validateEmail(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
718 | """ Validates a email field."""
|
---|
719 | if sValue in aoNilValues:
|
---|
720 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
721 |
|
---|
722 | if re.match(r'.+@.+\..+', sValue) is None:
|
---|
723 | return (sValue,'Invalid e-mail format.');
|
---|
724 | return (sValue, None);
|
---|
725 |
|
---|
726 | @staticmethod
|
---|
727 | def validateListOfSomething(asValues, aoNilValues = tuple([[], None]), fAllowNull = True):
|
---|
728 | """ Validate a list of some uniform values. Returns a copy of the list (if list it is). """
|
---|
729 | if asValues in aoNilValues or (len(asValues) == 0 and not fAllowNull):
|
---|
730 | return (asValues, None if fAllowNull else 'Mandatory.')
|
---|
731 |
|
---|
732 | if not isinstance(asValues, list):
|
---|
733 | return (asValues, 'Invalid data type (%s).' % (type(asValues),));
|
---|
734 |
|
---|
735 | asValues = list(asValues); # copy the list.
|
---|
736 | if len(asValues) > 0:
|
---|
737 | oType = type(asValues[0]);
|
---|
738 | for i in range(1, len(asValues)):
|
---|
739 | if type(asValues[i]) is not oType: # pylint: disable=unidiomatic-typecheck
|
---|
740 | return (asValues, 'Invalid entry data type ([0]=%s vs [%d]=%s).' % (oType, i, type(asValues[i])) );
|
---|
741 |
|
---|
742 | return (asValues, None);
|
---|
743 |
|
---|
744 | @staticmethod
|
---|
745 | def validateListOfStr(asValues, cchMin = None, cchMax = None, asValidValues = None,
|
---|
746 | aoNilValues = tuple([[], None]), fAllowNull = True):
|
---|
747 | """ Validates a list of text items."""
|
---|
748 | (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull);
|
---|
749 |
|
---|
750 | if sError is None and asValues not in aoNilValues and len(asValues) > 0:
|
---|
751 | if not utils.isString(asValues[0]):
|
---|
752 | return (asValues, 'Invalid item data type.');
|
---|
753 |
|
---|
754 | if not fAllowNull and cchMin is None:
|
---|
755 | cchMin = 1;
|
---|
756 |
|
---|
757 | for sValue in asValues:
|
---|
758 | if asValidValues is not None and sValue not in asValidValues:
|
---|
759 | sThisErr = 'Invalid value "%s".' % (sValue,);
|
---|
760 | elif cchMin is not None and len(sValue) < cchMin:
|
---|
761 | sThisErr = 'Value "%s" is too short, min length is %u chars.' % (sValue, cchMin);
|
---|
762 | elif cchMax is not None and len(sValue) > cchMax:
|
---|
763 | sThisErr = 'Value "%s" is too long, max length is %u chars.' % (sValue, cchMax);
|
---|
764 | else:
|
---|
765 | continue;
|
---|
766 |
|
---|
767 | if sError is None:
|
---|
768 | sError = sThisErr;
|
---|
769 | else:
|
---|
770 | sError += ' ' + sThisErr;
|
---|
771 |
|
---|
772 | return (asValues, sError);
|
---|
773 |
|
---|
774 | @staticmethod
|
---|
775 | def validateListOfInts(asValues, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([[], None]), fAllowNull = True):
|
---|
776 | """ Validates a list of integer items."""
|
---|
777 | (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull);
|
---|
778 |
|
---|
779 | if sError is None and asValues not in aoNilValues and len(asValues) > 0:
|
---|
780 | for i, _ in enumerate(asValues):
|
---|
781 | sValue = asValues[i];
|
---|
782 |
|
---|
783 | sThisErr = '';
|
---|
784 | try:
|
---|
785 | iValue = int(sValue);
|
---|
786 | except:
|
---|
787 | sThisErr = 'Invalid integer value "%s".' % (sValue,);
|
---|
788 | else:
|
---|
789 | asValues[i] = iValue;
|
---|
790 | if iValue < iMin:
|
---|
791 | sThisErr = 'Value %d is too small (min %d)' % (iValue, iMin,);
|
---|
792 | elif iValue > iMax:
|
---|
793 | sThisErr = 'Value %d is too high (max %d)' % (iValue, iMax,);
|
---|
794 | else:
|
---|
795 | continue;
|
---|
796 |
|
---|
797 | if sError is None:
|
---|
798 | sError = sThisErr;
|
---|
799 | else:
|
---|
800 | sError += ' ' + sThisErr;
|
---|
801 |
|
---|
802 | return (asValues, sError);
|
---|
803 |
|
---|
804 |
|
---|
805 |
|
---|
806 | #
|
---|
807 | # Old validation helpers.
|
---|
808 | #
|
---|
809 |
|
---|
810 | @staticmethod
|
---|
811 | def _validateInt(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])):
|
---|
812 | """ Validates an integer field. """
|
---|
813 | (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = True);
|
---|
814 | if sError is not None:
|
---|
815 | dErrors[sName] = sError;
|
---|
816 | return sValue;
|
---|
817 |
|
---|
818 | @staticmethod
|
---|
819 | def _validateIntNN(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])):
|
---|
820 | """ Validates an integer field, not null. """
|
---|
821 | (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = False);
|
---|
822 | if sError is not None:
|
---|
823 | dErrors[sName] = sError;
|
---|
824 | return sValue;
|
---|
825 |
|
---|
826 | @staticmethod
|
---|
827 | def _validateLong(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])):
|
---|
828 | """ Validates an long integer field. """
|
---|
829 | (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = False);
|
---|
830 | if sError is not None:
|
---|
831 | dErrors[sName] = sError;
|
---|
832 | return sValue;
|
---|
833 |
|
---|
834 | @staticmethod
|
---|
835 | def _validateLongNN(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])):
|
---|
836 | """ Validates an long integer field, not null. """
|
---|
837 | (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = True);
|
---|
838 | if sError is not None:
|
---|
839 | dErrors[sName] = sError;
|
---|
840 | return sValue;
|
---|
841 |
|
---|
842 | @staticmethod
|
---|
843 | def _validateTs(dErrors, sName, sValue):
|
---|
844 | """ Validates a timestamp field. """
|
---|
845 | (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = True);
|
---|
846 | if sError is not None:
|
---|
847 | dErrors[sName] = sError;
|
---|
848 | return sValue;
|
---|
849 |
|
---|
850 | @staticmethod
|
---|
851 | def _validateTsNN(dErrors, sName, sValue):
|
---|
852 | """ Validates a timestamp field, not null. """
|
---|
853 | (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = False);
|
---|
854 | if sError is not None:
|
---|
855 | dErrors[sName] = sError;
|
---|
856 | return sValue;
|
---|
857 |
|
---|
858 | @staticmethod
|
---|
859 | def _validateIp(dErrors, sName, sValue):
|
---|
860 | """ Validates an IP address field. """
|
---|
861 | (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = True);
|
---|
862 | if sError is not None:
|
---|
863 | dErrors[sName] = sError;
|
---|
864 | return sValue;
|
---|
865 |
|
---|
866 | @staticmethod
|
---|
867 | def _validateIpNN(dErrors, sName, sValue):
|
---|
868 | """ Validates an IP address field, not null. """
|
---|
869 | (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = False);
|
---|
870 | if sError is not None:
|
---|
871 | dErrors[sName] = sError;
|
---|
872 | return sValue;
|
---|
873 |
|
---|
874 | @staticmethod
|
---|
875 | def _validateBool(dErrors, sName, sValue):
|
---|
876 | """ Validates a boolean field. """
|
---|
877 | (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = True);
|
---|
878 | if sError is not None:
|
---|
879 | dErrors[sName] = sError;
|
---|
880 | return sValue;
|
---|
881 |
|
---|
882 | @staticmethod
|
---|
883 | def _validateBoolNN(dErrors, sName, sValue):
|
---|
884 | """ Validates a boolean field, not null. """
|
---|
885 | (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = False);
|
---|
886 | if sError is not None:
|
---|
887 | dErrors[sName] = sError;
|
---|
888 | return sValue;
|
---|
889 |
|
---|
890 | @staticmethod
|
---|
891 | def _validateUuid(dErrors, sName, sValue):
|
---|
892 | """ Validates an UUID field. """
|
---|
893 | (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = True);
|
---|
894 | if sError is not None:
|
---|
895 | dErrors[sName] = sError;
|
---|
896 | return sValue;
|
---|
897 |
|
---|
898 | @staticmethod
|
---|
899 | def _validateUuidNN(dErrors, sName, sValue):
|
---|
900 | """ Validates an UUID field, not null. """
|
---|
901 | (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = False);
|
---|
902 | if sError is not None:
|
---|
903 | dErrors[sName] = sError;
|
---|
904 | return sValue;
|
---|
905 |
|
---|
906 | @staticmethod
|
---|
907 | def _validateWord(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None):
|
---|
908 | """ Validates a word field. """
|
---|
909 | (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = True);
|
---|
910 | if sError is not None:
|
---|
911 | dErrors[sName] = sError;
|
---|
912 | return sValue;
|
---|
913 |
|
---|
914 | @staticmethod
|
---|
915 | def _validateWordNN(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None):
|
---|
916 | """ Validates a boolean field, not null. """
|
---|
917 | (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = False);
|
---|
918 | if sError is not None:
|
---|
919 | dErrors[sName] = sError;
|
---|
920 | return sValue;
|
---|
921 |
|
---|
922 | @staticmethod
|
---|
923 | def _validateStr(dErrors, sName, sValue, cchMin = 0, cchMax = 4096):
|
---|
924 | """ Validates a string field. """
|
---|
925 | (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = True);
|
---|
926 | if sError is not None:
|
---|
927 | dErrors[sName] = sError;
|
---|
928 | return sValue;
|
---|
929 |
|
---|
930 | @staticmethod
|
---|
931 | def _validateStrNN(dErrors, sName, sValue, cchMin = 0, cchMax = 4096):
|
---|
932 | """ Validates a string field, not null. """
|
---|
933 | (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = False);
|
---|
934 | if sError is not None:
|
---|
935 | dErrors[sName] = sError;
|
---|
936 | return sValue;
|
---|
937 |
|
---|
938 | @staticmethod
|
---|
939 | def _validateEmail(dErrors, sName, sValue):
|
---|
940 | """ Validates a email field."""
|
---|
941 | (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = True);
|
---|
942 | if sError is not None:
|
---|
943 | dErrors[sName] = sError;
|
---|
944 | return sValue;
|
---|
945 |
|
---|
946 | @staticmethod
|
---|
947 | def _validateEmailNN(dErrors, sName, sValue):
|
---|
948 | """ Validates a email field."""
|
---|
949 | (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = False);
|
---|
950 | if sError is not None:
|
---|
951 | dErrors[sName] = sError;
|
---|
952 | return sValue;
|
---|
953 |
|
---|
954 | @staticmethod
|
---|
955 | def _validateListOfStr(dErrors, sName, asValues, asValidValues = None):
|
---|
956 | """ Validates a list of text items."""
|
---|
957 | (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = True);
|
---|
958 | if sError is not None:
|
---|
959 | dErrors[sName] = sError;
|
---|
960 | return sValue;
|
---|
961 |
|
---|
962 | @staticmethod
|
---|
963 | def _validateListOfStrNN(dErrors, sName, asValues, asValidValues = None):
|
---|
964 | """ Validates a list of text items, not null and len >= 1."""
|
---|
965 | (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = False);
|
---|
966 | if sError is not None:
|
---|
967 | dErrors[sName] = sError;
|
---|
968 | return sValue;
|
---|
969 |
|
---|
970 | #
|
---|
971 | # Various helpers.
|
---|
972 | #
|
---|
973 |
|
---|
974 | @staticmethod
|
---|
975 | def formatSimpleNowAndPeriod(oDb, tsNow = None, sPeriodBack = None,
|
---|
976 | sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'):
|
---|
977 | """
|
---|
978 | Formats a set of tsNow and sPeriodBack arguments for a standard testmanager
|
---|
979 | table.
|
---|
980 |
|
---|
981 | If sPeriodBack is given, the query is effective for the period
|
---|
982 | (tsNow - sPeriodBack) thru (tsNow).
|
---|
983 |
|
---|
984 | If tsNow isn't given, it defaults to current time.
|
---|
985 |
|
---|
986 | Returns the final portion of a WHERE query (start with AND) and maybe an
|
---|
987 | ORDER BY and LIMIT bit if sPeriodBack is given.
|
---|
988 | """
|
---|
989 | if tsNow is not None:
|
---|
990 | if sPeriodBack is not None:
|
---|
991 | sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (%s::timestamp - %s::interval)\n'
|
---|
992 | ' AND tsEffective <= %s\n'
|
---|
993 | 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n'
|
---|
994 | 'LIMIT 1\n'
|
---|
995 | , ( tsNow, sPeriodBack, tsNow));
|
---|
996 | else:
|
---|
997 | sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > %s\n'
|
---|
998 | ' AND ' + sTablePrefix + sEffCol + ' <= %s\n'
|
---|
999 | , ( tsNow, tsNow, ));
|
---|
1000 | else:
|
---|
1001 | if sPeriodBack is not None:
|
---|
1002 | sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (CURRENT_TIMESTAMP - %s::interval)\n'
|
---|
1003 | ' AND ' + sTablePrefix + sEffCol + ' <= CURRENT_TIMESTAMP\n'
|
---|
1004 | 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n'
|
---|
1005 | 'LIMIT 1\n'
|
---|
1006 | , ( sPeriodBack, ));
|
---|
1007 | else:
|
---|
1008 | sRet = ' AND ' + sTablePrefix + sExpCol + ' = \'infinity\'::timestamp\n';
|
---|
1009 | return sRet;
|
---|
1010 |
|
---|
1011 | @staticmethod
|
---|
1012 | def formatSimpleNowAndPeriodQuery(oDb, sQuery, aBindArgs, tsNow = None, sPeriodBack = None,
|
---|
1013 | sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'):
|
---|
1014 | """
|
---|
1015 | Formats a simple query for a standard testmanager table with optional
|
---|
1016 | tsNow and sPeriodBack arguments.
|
---|
1017 |
|
---|
1018 | The sQuery and sBindArgs are passed along to oDb.formatBindArgs to form
|
---|
1019 | the first part of the query. Must end with an open WHERE statement as
|
---|
1020 | we'll be adding the time part starting with 'AND something...'.
|
---|
1021 |
|
---|
1022 | See formatSimpleNowAndPeriod for tsNow and sPeriodBack description.
|
---|
1023 |
|
---|
1024 | Returns the final portion of a WHERE query (start with AND) and maybe an
|
---|
1025 | ORDER BY and LIMIT bit if sPeriodBack is given.
|
---|
1026 |
|
---|
1027 | """
|
---|
1028 | return oDb.formatBindArgs(sQuery, aBindArgs) \
|
---|
1029 | + ModelDataBase.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix, sExpCol, sEffCol);
|
---|
1030 |
|
---|
1031 | #
|
---|
1032 | # Sub-classes.
|
---|
1033 | #
|
---|
1034 |
|
---|
1035 | class DispWrapper(object):
|
---|
1036 | """Proxy object."""
|
---|
1037 | def __init__(self, oDisp, sAttrFmt):
|
---|
1038 | self.oDisp = oDisp;
|
---|
1039 | self.sAttrFmt = sAttrFmt;
|
---|
1040 | def getStringParam(self, sName, asValidValues = None, sDefault = None):
|
---|
1041 | """See WuiDispatcherBase.getStringParam."""
|
---|
1042 | return self.oDisp.getStringParam(self.sAttrFmt % (sName,), asValidValues, sDefault);
|
---|
1043 | def getListOfStrParams(self, sName, asDefaults = None):
|
---|
1044 | """See WuiDispatcherBase.getListOfStrParams."""
|
---|
1045 | return self.oDisp.getListOfStrParams(self.sAttrFmt % (sName,), asDefaults);
|
---|
1046 | def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None):
|
---|
1047 | """See WuiDispatcherBase.getListOfIntParams."""
|
---|
1048 | return self.oDisp.getListOfIntParams(self.sAttrFmt % (sName,), iMin, iMax, aiDefaults);
|
---|
1049 |
|
---|
1050 |
|
---|
1051 |
|
---|
1052 |
|
---|
1053 | # pylint: disable=E1101,C0111,R0903
|
---|
1054 | class ModelDataBaseTestCase(unittest.TestCase):
|
---|
1055 | """
|
---|
1056 | Base testcase for ModelDataBase decendants.
|
---|
1057 | Derive from this and override setUp.
|
---|
1058 | """
|
---|
1059 |
|
---|
1060 | def setUp(self):
|
---|
1061 | """
|
---|
1062 | Override this! Don't call super!
|
---|
1063 | The subclasses are expected to set aoSamples to an array of instance
|
---|
1064 | samples. The first entry must be a default object, the subsequent ones
|
---|
1065 | are optional and their contents freely choosen.
|
---|
1066 | """
|
---|
1067 | self.aoSamples = [ModelDataBase(),];
|
---|
1068 |
|
---|
1069 | def testEquality(self):
|
---|
1070 | for oSample in self.aoSamples:
|
---|
1071 | self.assertEqual(oSample.isEqual(copy.copy(oSample)), True);
|
---|
1072 | self.assertIsNotNone(oSample.isEqual(self.aoSamples[0]));
|
---|
1073 |
|
---|
1074 | def testNullConversion(self):
|
---|
1075 | if len(self.aoSamples[0].getDataAttributes()) == 0:
|
---|
1076 | return;
|
---|
1077 | for oSample in self.aoSamples:
|
---|
1078 | oCopy = copy.copy(oSample);
|
---|
1079 | self.assertEqual(oCopy.convertToParamNull(), oCopy);
|
---|
1080 | self.assertEqual(oCopy.isEqual(oSample), False);
|
---|
1081 | self.assertEqual(oCopy.convertFromParamNull(), oCopy);
|
---|
1082 | self.assertEqual(oCopy.isEqual(oSample), True, '\ngot : %s\nexpected: %s' % (oCopy, oSample,));
|
---|
1083 |
|
---|
1084 | oCopy = copy.copy(oSample);
|
---|
1085 | self.assertEqual(oCopy.convertToParamNull(), oCopy);
|
---|
1086 | oCopy2 = copy.copy(oCopy);
|
---|
1087 | self.assertEqual(oCopy.convertToParamNull(), oCopy);
|
---|
1088 | self.assertEqual(oCopy.isEqual(oCopy2), True);
|
---|
1089 | self.assertEqual(oCopy.convertToParamNull(), oCopy);
|
---|
1090 | self.assertEqual(oCopy.isEqual(oCopy2), True);
|
---|
1091 |
|
---|
1092 | oCopy = copy.copy(oSample);
|
---|
1093 | self.assertEqual(oCopy.convertFromParamNull(), oCopy);
|
---|
1094 | oCopy2 = copy.copy(oCopy);
|
---|
1095 | self.assertEqual(oCopy.convertFromParamNull(), oCopy);
|
---|
1096 | self.assertEqual(oCopy.isEqual(oCopy2), True);
|
---|
1097 | self.assertEqual(oCopy.convertFromParamNull(), oCopy);
|
---|
1098 | self.assertEqual(oCopy.isEqual(oCopy2), True);
|
---|
1099 |
|
---|
1100 | def testReinitToNull(self):
|
---|
1101 | oFirst = copy.copy(self.aoSamples[0]);
|
---|
1102 | self.assertEqual(oFirst.reinitToNull(), oFirst);
|
---|
1103 | for oSample in self.aoSamples:
|
---|
1104 | oCopy = copy.copy(oSample);
|
---|
1105 | self.assertEqual(oCopy.reinitToNull(), oCopy);
|
---|
1106 | self.assertEqual(oCopy.isEqual(oFirst), True);
|
---|
1107 |
|
---|
1108 | def testValidateAndConvert(self):
|
---|
1109 | for oSample in self.aoSamples:
|
---|
1110 | oCopy = copy.copy(oSample);
|
---|
1111 | oCopy.convertToParamNull();
|
---|
1112 | dError1 = oCopy.validateAndConvert(None);
|
---|
1113 |
|
---|
1114 | oCopy2 = copy.copy(oCopy);
|
---|
1115 | self.assertEqual(oCopy.validateAndConvert(None), dError1);
|
---|
1116 | self.assertEqual(oCopy.isEqual(oCopy2), True);
|
---|
1117 |
|
---|
1118 | def testInitFromParams(self):
|
---|
1119 | class DummyDisp(object):
|
---|
1120 | def getStringParam(self, sName, asValidValues = None, sDefault = None):
|
---|
1121 | _ = sName; _ = asValidValues;
|
---|
1122 | return sDefault;
|
---|
1123 | def getListOfStrParams(self, sName, asDefaults = None):
|
---|
1124 | _ = sName;
|
---|
1125 | return asDefaults;
|
---|
1126 | def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None):
|
---|
1127 | _ = sName; _ = iMin; _ = iMax;
|
---|
1128 | return aiDefaults;
|
---|
1129 |
|
---|
1130 | for oSample in self.aoSamples:
|
---|
1131 | oCopy = copy.copy(oSample);
|
---|
1132 | self.assertEqual(oCopy.initFromParams(DummyDisp(), fStrict = False), oCopy);
|
---|
1133 |
|
---|
1134 | def testToString(self):
|
---|
1135 | for oSample in self.aoSamples:
|
---|
1136 | self.assertIsNotNone(oSample.toString());
|
---|
1137 |
|
---|
1138 |
|
---|
1139 | class ModelLogicBase(ModelBase): # pylint: disable=R0903
|
---|
1140 | """
|
---|
1141 | Something all classes in the logic classes the logical model inherits from.
|
---|
1142 | """
|
---|
1143 |
|
---|
1144 | def __init__(self, oDb):
|
---|
1145 | ModelBase.__init__(self);
|
---|
1146 |
|
---|
1147 | #
|
---|
1148 | # Note! Do not create a connection here if None, we need to DB share
|
---|
1149 | # connection with all other logic objects so we can perform half
|
---|
1150 | # complex transactions involving several logic objects.
|
---|
1151 | #
|
---|
1152 | self._oDb = oDb;
|
---|
1153 |
|
---|
1154 | def getDbConnection(self):
|
---|
1155 | """
|
---|
1156 | Gets the database connection.
|
---|
1157 | This should only be used for instantiating other ModelLogicBase children.
|
---|
1158 | """
|
---|
1159 | return self._oDb;
|
---|
1160 |
|
---|
1161 |
|
---|
1162 | class AttributeChangeEntry(object): # pylint: disable=R0903
|
---|
1163 | """
|
---|
1164 | Data class representing the changes made to one attribute.
|
---|
1165 | """
|
---|
1166 |
|
---|
1167 | def __init__(self, sAttr, oNewRaw, oOldRaw, sNewText, sOldText):
|
---|
1168 | self.sAttr = sAttr;
|
---|
1169 | self.oNewRaw = oNewRaw;
|
---|
1170 | self.oOldRaw = oOldRaw;
|
---|
1171 | self.sNewText = sNewText;
|
---|
1172 | self.sOldText = sOldText;
|
---|
1173 |
|
---|
1174 | class ChangeLogEntry(object): # pylint: disable=R0903
|
---|
1175 | """
|
---|
1176 | A change log entry returned by the fetchChangeLog method typically
|
---|
1177 | implemented by ModelLogicBase child classes.
|
---|
1178 | """
|
---|
1179 |
|
---|
1180 | def __init__(self, uidAuthor, sAuthor, tsEffective, tsExpire, oNewRaw, oOldRaw, aoChanges):
|
---|
1181 | self.uidAuthor = uidAuthor;
|
---|
1182 | self.sAuthor = sAuthor;
|
---|
1183 | self.tsEffective = tsEffective;
|
---|
1184 | self.tsExpire = tsExpire;
|
---|
1185 | self.oNewRaw = oNewRaw;
|
---|
1186 | self.oOldRaw = oOldRaw; # Note! NULL for the last entry.
|
---|
1187 | self.aoChanges = aoChanges;
|
---|
1188 |
|
---|