VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/base.py@ 61468

Last change on this file since 61468 was 61468, checked in by vboxsync, 9 years ago

testmanager: Adding sComment and fRawMode fields to TestBoxes and moves the strings into a separate shared string table.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette