VirtualBox

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

Last change on this file since 84352 was 84271, checked in by vboxsync, 5 years ago

TestManager/base.py: Copy & past fix in validateTs.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 57.4 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: base.py 84271 2020-05-12 14:39:15Z vboxsync $
3# pylint: disable=too-many-lines
4
5"""
6Test Manager Core - Base Class(es).
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2012-2020 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: 84271 $"
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=redefined-builtin,invalid-name
47
48
49class TMExceptionBase(Exception):
50 """
51 For exceptions raised by any TestManager component.
52 """
53 pass; # pylint: disable=unnecessary-pass
54
55
56class TMTooManyRows(TMExceptionBase):
57 """
58 Too many rows in the result.
59 Used by ModelLogicBase decendants.
60 """
61 pass; # pylint: disable=unnecessary-pass
62
63
64class TMRowNotFound(TMExceptionBase):
65 """
66 Database row not found.
67 Used by ModelLogicBase decendants.
68 """
69 pass; # pylint: disable=unnecessary-pass
70
71
72class TMRowAlreadyExists(TMExceptionBase):
73 """
74 Database row already exists (typically raised by addEntry).
75 Used by ModelLogicBase decendants.
76 """
77 pass; # pylint: disable=unnecessary-pass
78
79
80class TMInvalidData(TMExceptionBase):
81 """
82 Data validation failed.
83 Used by ModelLogicBase decendants.
84 """
85 pass; # pylint: disable=unnecessary-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; # pylint: disable=unnecessary-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; # pylint: disable=unnecessary-pass
103
104
105class ModelBase(object): # pylint: disable=too-few-public-methods
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=too-few-public-methods
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 if sPrefix in ['l', 'c',]:
201 return [long(-1), '', '-1',];
202 if sPrefix == 'f':
203 return ['',];
204 if sPrefix in ['enm', 'ip', 's', 'ts', 'uuid']:
205 return ['',];
206 if sPrefix in ['ai', 'aid', 'al', 'as']:
207 return [[], '', None]; ## @todo ??
208 if 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 oValue 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 oValue 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 not oValue 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 in (ModelDataBase.ksValidateFor_Add,
373 ModelDataBase.ksValidateFor_AddForeignId,
374 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 validateAndConvertEx(self, asAllowNullAttributes, oDb, enmValidateFor = ksValidateFor_Other):
407 """
408 Same as validateAndConvert but with custom allow-null list.
409 """
410 return self._validateAndConvertWorker(asAllowNullAttributes, oDb, enmValidateFor = enmValidateFor);
411
412 def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict):
413 """
414 Calculate the attribute value when initialized from a parameter.
415
416 Returns the new value, with parameter NULL values. Raises exception on
417 invalid parameter value.
418
419 Child classes can override to do special parameter conversion jobs.
420 """
421 sPrefix = self.getHungarianPrefix(sAttr);
422 asValidValues = getattr(self, 'kasValidValues_' + sAttr, None);
423 fAllowNull = sAttr in getattr(self, 'kasAllowNullAttributes', list());
424 if fStrict:
425 if sPrefix == 'f':
426 # HACK ALERT! Checkboxes are only present when checked, so we always have to provide a default.
427 oNewValue = oDisp.getStringParam(sParam, asValidValues, '0');
428 elif sPrefix[0] == 'a':
429 # HACK ALERT! Lists are not present if empty.
430 oNewValue = oDisp.getListOfStrParams(sParam, []);
431 else:
432 oNewValue = oDisp.getStringParam(sParam, asValidValues, None, fAllowNull = fAllowNull);
433 else:
434 if sPrefix[0] == 'a':
435 oNewValue = oDisp.getListOfStrParams(sParam, []);
436 else:
437 assert oValue is not None, 'sAttr=%s' % (sAttr,);
438 oNewValue = oDisp.getStringParam(sParam, asValidValues, oValue, fAllowNull = fAllowNull);
439 return oNewValue;
440
441 def initFromParams(self, oDisp, fStrict = True):
442 """
443 Initialize the object from parameters.
444 The input is not validated at all, except that all parameters must be
445 present when fStrict is True.
446
447 Returns self. Raises exception on invalid parameter value.
448
449 Note! The returned object has parameter NULL values, not database ones!
450 """
451
452 self.convertToParamNull()
453 for sAttr in self.getDataAttributes():
454 oValue = getattr(self, sAttr);
455 oNewValue = self.convertParamToAttribute(sAttr, getattr(self, 'ksParam_' + sAttr), oValue, oDisp, fStrict);
456 if oNewValue != oValue:
457 setattr(self, sAttr, oNewValue);
458 return self;
459
460 def areAttributeValuesEqual(self, sAttr, sPrefix, oValue1, oValue2):
461 """
462 Called to compare two attribute values and python thinks differs.
463
464 Returns True/False.
465
466 Child classes can override this to do special compares of things like arrays.
467 """
468 # Just in case someone uses it directly.
469 if oValue1 == oValue2:
470 return True;
471
472 #
473 # Timestamps can be both string (param) and object (db)
474 # depending on the data source. Compare string values to make
475 # sure we're doing the right thing here.
476 #
477 if sPrefix == 'ts':
478 return str(oValue1) == str(oValue2);
479
480 #
481 # Some generic code handling ModelDataBase children.
482 #
483 if isinstance(oValue1, list) and isinstance(oValue2, list):
484 if len(oValue1) == len(oValue2):
485 for i, _ in enumerate(oValue1):
486 if not isinstance(oValue1[i], ModelDataBase) \
487 or type(oValue1) is not type(oValue2):
488 return False;
489 if not oValue1[i].isEqual(oValue2[i]):
490 return False;
491 return True;
492
493 elif isinstance(oValue1, ModelDataBase) \
494 and type(oValue1) is type(oValue2):
495 return oValue1[i].isEqual(oValue2[i]);
496
497 _ = sAttr;
498 return False;
499
500 def isEqual(self, oOther):
501 """ Compares two instances. """
502 for sAttr in self.getDataAttributes():
503 if getattr(self, sAttr) != getattr(oOther, sAttr):
504 # Delegate the final decision to an overridable method.
505 if not self.areAttributeValuesEqual(sAttr, self.getHungarianPrefix(sAttr),
506 getattr(self, sAttr), getattr(oOther, sAttr)):
507 return False;
508 return True;
509
510 def isEqualEx(self, oOther, asExcludeAttrs):
511 """ Compares two instances, omitting the given attributes. """
512 for sAttr in self.getDataAttributes():
513 if sAttr not in asExcludeAttrs \
514 and getattr(self, sAttr) != getattr(oOther, sAttr):
515 # Delegate the final decision to an overridable method.
516 if not self.areAttributeValuesEqual(sAttr, self.getHungarianPrefix(sAttr),
517 getattr(self, sAttr), getattr(oOther, sAttr)):
518 return False;
519 return True;
520
521 def reinitToNull(self):
522 """
523 Reinitializes the object to (database) NULL values.
524 Returns self.
525 """
526 for sAttr in self.getDataAttributes():
527 setattr(self, sAttr, None);
528 return self;
529
530 def toString(self):
531 """
532 Stringifies the object.
533 Returns string representation.
534 """
535
536 sMembers = '';
537 for sAttr in self.getDataAttributes():
538 oValue = getattr(self, sAttr);
539 sMembers += ', %s=%s' % (sAttr, oValue);
540
541 oClass = type(self);
542 if sMembers == '':
543 return '<%s>' % (oClass.__name__);
544 return '<%s: %s>' % (oClass.__name__, sMembers[2:]);
545
546 def __str__(self):
547 return self.toString();
548
549
550
551 #
552 # New validation helpers.
553 #
554 # These all return (oValue, sError), where sError is None when the value
555 # is valid and an error message when not. On success and in case of
556 # range errors, oValue is converted into the requested type.
557 #
558
559 @staticmethod
560 def validateInt(sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, '']), fAllowNull = True):
561 """ Validates an integer field. """
562 if sValue in aoNilValues:
563 if fAllowNull:
564 return (None if sValue is None else aoNilValues[0], None);
565 return (sValue, 'Mandatory.');
566
567 try:
568 if utils.isString(sValue):
569 iValue = int(sValue, 0);
570 else:
571 iValue = int(sValue);
572 except:
573 return (sValue, 'Not an integer');
574
575 if iValue in aoNilValues:
576 return (aoNilValues[0], None if fAllowNull else 'Mandatory.');
577
578 if iValue < iMin:
579 return (iValue, 'Value too small (min %d)' % (iMin,));
580 if iValue > iMax:
581 return (iValue, 'Value too high (max %d)' % (iMax,));
582 return (iValue, None);
583
584 @staticmethod
585 def validateLong(sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, '']), fAllowNull = True):
586 """ Validates an long integer field. """
587 if sValue in aoNilValues:
588 if fAllowNull:
589 return (None if sValue is None else aoNilValues[0], None);
590 return (sValue, 'Mandatory.');
591 try:
592 if utils.isString(sValue):
593 lValue = long(sValue, 0);
594 else:
595 lValue = long(sValue);
596 except:
597 return (sValue, 'Not a long integer');
598
599 if lValue in aoNilValues:
600 return (aoNilValues[0], None if fAllowNull else 'Mandatory.');
601
602 if lMin is not None and lValue < lMin:
603 return (lValue, 'Value too small (min %d)' % (lMin,));
604 if lMax is not None and lValue > lMax:
605 return (lValue, 'Value too high (max %d)' % (lMax,));
606 return (lValue, None);
607
608 kdTimestampRegex = {
609 len('2012-10-08 01:54:06'): r'(\d{4})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d)$',
610 len('2012-10-08 01:54:06.00'): r'(\d{4})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d).\d{2}$',
611 len('2012-10-08 01:54:06.000'): r'(\d{4})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d).\d{3}$',
612 len('999999-12-31 00:00:00.00'): r'(\d{6})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d).\d{2}$',
613 len('9999-12-31 23:59:59.999999'): r'(\d{4})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d).\d{6}$',
614 len('9999-12-31T23:59:59.999999999'): r'(\d{4})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d).\d{9}$',
615 };
616
617 @staticmethod
618 def validateTs(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
619 """ Validates a timestamp field. """
620 if sValue in aoNilValues:
621 return (sValue, None if fAllowNull else 'Mandatory.');
622 if not utils.isString(sValue):
623 return (sValue, None);
624
625 # Validate and strip off the timezone stuff.
626 if sValue[-1] in 'Zz':
627 sStripped = sValue[:-1];
628 sValue = sStripped + 'Z';
629 elif len(sValue) >= 19 + 3:
630 oRes = re.match(r'^.*[+-](\d\d):(\d\d)$', sValue);
631 if oRes is not None:
632 if int(oRes.group(1)) > 12 or int(oRes.group(2)) >= 60:
633 return (sValue, 'Invalid timezone offset.');
634 sStripped = sValue[:-6];
635 else:
636 sStripped = sValue;
637 else:
638 sStripped = sValue;
639
640 # Used the stripped value length to find regular expression for validating and parsing the timestamp.
641 sError = None;
642 sRegExp = ModelDataBase.kdTimestampRegex.get(len(sStripped), None);
643 if sRegExp:
644 oRes = re.match(sRegExp, sStripped);
645 if oRes is not None:
646 iYear = int(oRes.group(1));
647 if iYear % 4 == 0 and (iYear % 100 != 0 or iYear % 400 == 0):
648 acDaysOfMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
649 else:
650 acDaysOfMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
651 iMonth = int(oRes.group(2));
652 iDay = int(oRes.group(3));
653 iHour = int(oRes.group(4));
654 iSec = int(oRes.group(5));
655 if iMonth > 12 or iMonth <= 0:
656 sError = 'Invalid timestamp month.';
657 elif iDay > acDaysOfMonth[iMonth - 1]:
658 sError = 'Invalid timestamp day-of-month (%02d has %d days).' % (iMonth, acDaysOfMonth[iMonth - 1]);
659 elif iHour > 23:
660 sError = 'Invalid timestamp hour.'
661 elif iSec >= 61:
662 sError = 'Invalid timestamp second.'
663 elif iSec >= 60:
664 sError = 'Invalid timestamp: no leap seconds, please.'
665 else:
666 sError = 'Invalid timestamp (validation regexp: %s).' % (sRegExp,);
667 else:
668 sError = 'Invalid timestamp length.';
669 return (sValue, sError);
670
671 @staticmethod
672 def validateIp(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
673 """ Validates an IP address field. """
674 if sValue in aoNilValues:
675 return (sValue, None if fAllowNull else 'Mandatory.');
676
677 if sValue == '::1':
678 return (sValue, None);
679
680 try:
681 socket.inet_pton(socket.AF_INET, sValue); # pylint: disable=no-member
682 except:
683 try:
684 socket.inet_pton(socket.AF_INET6, sValue); # pylint: disable=no-member
685 except:
686 return (sValue, 'Not a valid IP address.');
687
688 return (sValue, None);
689
690 @staticmethod
691 def validateBool(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
692 """ Validates a boolean field. """
693 if sValue in aoNilValues:
694 return (sValue, None if fAllowNull else 'Mandatory.');
695
696 if sValue in ('True', 'true', '1', True):
697 return (True, None);
698 if sValue in ('False', 'false', '0', False):
699 return (False, None);
700 return (sValue, 'Invalid boolean value.');
701
702 @staticmethod
703 def validateUuid(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
704 """ Validates an UUID field. """
705 if sValue in aoNilValues:
706 return (sValue, None if fAllowNull else 'Mandatory.');
707
708 try:
709 sValue = str(uuid.UUID(sValue));
710 except:
711 return (sValue, 'Invalid UUID value.');
712 return (sValue, None);
713
714 @staticmethod
715 def validateWord(sValue, cchMin = 1, cchMax = 64, asValid = None, aoNilValues = tuple([None, '']), fAllowNull = True):
716 """ Validates a word field. """
717 if sValue in aoNilValues:
718 return (sValue, None if fAllowNull else 'Mandatory.');
719
720 if re.search('[^a-zA-Z0-9_-]', sValue) is not None:
721 sError = 'Single word ([a-zA-Z0-9_-]), please.';
722 elif cchMin is not None and len(sValue) < cchMin:
723 sError = 'Too short, min %s chars' % (cchMin,);
724 elif cchMax is not None and len(sValue) > cchMax:
725 sError = 'Too long, max %s chars' % (cchMax,);
726 elif asValid is not None and sValue not in asValid:
727 sError = 'Invalid value "%s", must be one of: %s' % (sValue, asValid);
728 else:
729 sError = None;
730 return (sValue, sError);
731
732 @staticmethod
733 def validateStr(sValue, cchMin = 0, cchMax = 4096, aoNilValues = tuple([None, '']), fAllowNull = True,
734 fAllowUnicodeSymbols = False):
735 """ Validates a string field. """
736 if sValue in aoNilValues:
737 return (sValue, None if fAllowNull else 'Mandatory.');
738
739 if cchMin is not None and len(sValue) < cchMin:
740 sError = 'Too short, min %s chars' % (cchMin,);
741 elif cchMax is not None and len(sValue) > cchMax:
742 sError = 'Too long, max %s chars' % (cchMax,);
743 elif fAllowUnicodeSymbols is False and utils.hasNonAsciiCharacters(sValue):
744 sError = 'Non-ascii characters not allowed'
745 else:
746 sError = None;
747 return (sValue, sError);
748
749 @staticmethod
750 def validateEmail(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
751 """ Validates a email field."""
752 if sValue in aoNilValues:
753 return (sValue, None if fAllowNull else 'Mandatory.');
754
755 if re.match(r'.+@.+\..+', sValue) is None:
756 return (sValue,'Invalid e-mail format.');
757 return (sValue, None);
758
759 @staticmethod
760 def validateListOfSomething(asValues, aoNilValues = tuple([[], None]), fAllowNull = True):
761 """ Validate a list of some uniform values. Returns a copy of the list (if list it is). """
762 if asValues in aoNilValues or (not asValues and not fAllowNull):
763 return (asValues, None if fAllowNull else 'Mandatory.')
764
765 if not isinstance(asValues, list):
766 return (asValues, 'Invalid data type (%s).' % (type(asValues),));
767
768 asValues = list(asValues); # copy the list.
769 if asValues:
770 oType = type(asValues[0]);
771 for i in range(1, len(asValues)):
772 if type(asValues[i]) is not oType: # pylint: disable=unidiomatic-typecheck
773 return (asValues, 'Invalid entry data type ([0]=%s vs [%d]=%s).' % (oType, i, type(asValues[i])) );
774
775 return (asValues, None);
776
777 @staticmethod
778 def validateListOfStr(asValues, cchMin = None, cchMax = None, asValidValues = None,
779 aoNilValues = tuple([[], None]), fAllowNull = True):
780 """ Validates a list of text items."""
781 (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull);
782
783 if sError is None and asValues not in aoNilValues and asValues:
784 if not utils.isString(asValues[0]):
785 return (asValues, 'Invalid item data type.');
786
787 if not fAllowNull and cchMin is None:
788 cchMin = 1;
789
790 for sValue in asValues:
791 if asValidValues is not None and sValue not in asValidValues:
792 sThisErr = 'Invalid value "%s".' % (sValue,);
793 elif cchMin is not None and len(sValue) < cchMin:
794 sThisErr = 'Value "%s" is too short, min length is %u chars.' % (sValue, cchMin);
795 elif cchMax is not None and len(sValue) > cchMax:
796 sThisErr = 'Value "%s" is too long, max length is %u chars.' % (sValue, cchMax);
797 else:
798 continue;
799
800 if sError is None:
801 sError = sThisErr;
802 else:
803 sError += ' ' + sThisErr;
804
805 return (asValues, sError);
806
807 @staticmethod
808 def validateListOfInts(asValues, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([[], None]), fAllowNull = True):
809 """ Validates a list of integer items."""
810 (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull);
811
812 if sError is None and asValues not in aoNilValues and asValues:
813 for i, _ in enumerate(asValues):
814 sValue = asValues[i];
815
816 sThisErr = '';
817 try:
818 iValue = int(sValue);
819 except:
820 sThisErr = 'Invalid integer value "%s".' % (sValue,);
821 else:
822 asValues[i] = iValue;
823 if iValue < iMin:
824 sThisErr = 'Value %d is too small (min %d)' % (iValue, iMin,);
825 elif iValue > iMax:
826 sThisErr = 'Value %d is too high (max %d)' % (iValue, iMax,);
827 else:
828 continue;
829
830 if sError is None:
831 sError = sThisErr;
832 else:
833 sError += ' ' + sThisErr;
834
835 return (asValues, sError);
836
837
838
839 #
840 # Old validation helpers.
841 #
842
843 @staticmethod
844 def _validateInt(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])):
845 """ Validates an integer field. """
846 (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = True);
847 if sError is not None:
848 dErrors[sName] = sError;
849 return sValue;
850
851 @staticmethod
852 def _validateIntNN(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])):
853 """ Validates an integer field, not null. """
854 (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = False);
855 if sError is not None:
856 dErrors[sName] = sError;
857 return sValue;
858
859 @staticmethod
860 def _validateLong(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])):
861 """ Validates an long integer field. """
862 (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = False);
863 if sError is not None:
864 dErrors[sName] = sError;
865 return sValue;
866
867 @staticmethod
868 def _validateLongNN(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])):
869 """ Validates an long integer field, not null. """
870 (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = True);
871 if sError is not None:
872 dErrors[sName] = sError;
873 return sValue;
874
875 @staticmethod
876 def _validateTs(dErrors, sName, sValue):
877 """ Validates a timestamp field. """
878 (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = True);
879 if sError is not None:
880 dErrors[sName] = sError;
881 return sValue;
882
883 @staticmethod
884 def _validateTsNN(dErrors, sName, sValue):
885 """ Validates a timestamp field, not null. """
886 (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = False);
887 if sError is not None:
888 dErrors[sName] = sError;
889 return sValue;
890
891 @staticmethod
892 def _validateIp(dErrors, sName, sValue):
893 """ Validates an IP address field. """
894 (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = True);
895 if sError is not None:
896 dErrors[sName] = sError;
897 return sValue;
898
899 @staticmethod
900 def _validateIpNN(dErrors, sName, sValue):
901 """ Validates an IP address field, not null. """
902 (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = False);
903 if sError is not None:
904 dErrors[sName] = sError;
905 return sValue;
906
907 @staticmethod
908 def _validateBool(dErrors, sName, sValue):
909 """ Validates a boolean field. """
910 (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = True);
911 if sError is not None:
912 dErrors[sName] = sError;
913 return sValue;
914
915 @staticmethod
916 def _validateBoolNN(dErrors, sName, sValue):
917 """ Validates a boolean field, not null. """
918 (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = False);
919 if sError is not None:
920 dErrors[sName] = sError;
921 return sValue;
922
923 @staticmethod
924 def _validateUuid(dErrors, sName, sValue):
925 """ Validates an UUID field. """
926 (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = True);
927 if sError is not None:
928 dErrors[sName] = sError;
929 return sValue;
930
931 @staticmethod
932 def _validateUuidNN(dErrors, sName, sValue):
933 """ Validates an UUID field, not null. """
934 (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = False);
935 if sError is not None:
936 dErrors[sName] = sError;
937 return sValue;
938
939 @staticmethod
940 def _validateWord(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None):
941 """ Validates a word field. """
942 (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = True);
943 if sError is not None:
944 dErrors[sName] = sError;
945 return sValue;
946
947 @staticmethod
948 def _validateWordNN(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None):
949 """ Validates a boolean field, not null. """
950 (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = False);
951 if sError is not None:
952 dErrors[sName] = sError;
953 return sValue;
954
955 @staticmethod
956 def _validateStr(dErrors, sName, sValue, cchMin = 0, cchMax = 4096):
957 """ Validates a string field. """
958 (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = True);
959 if sError is not None:
960 dErrors[sName] = sError;
961 return sValue;
962
963 @staticmethod
964 def _validateStrNN(dErrors, sName, sValue, cchMin = 0, cchMax = 4096):
965 """ Validates a string field, not null. """
966 (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = False);
967 if sError is not None:
968 dErrors[sName] = sError;
969 return sValue;
970
971 @staticmethod
972 def _validateEmail(dErrors, sName, sValue):
973 """ Validates a email field."""
974 (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = True);
975 if sError is not None:
976 dErrors[sName] = sError;
977 return sValue;
978
979 @staticmethod
980 def _validateEmailNN(dErrors, sName, sValue):
981 """ Validates a email field."""
982 (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = False);
983 if sError is not None:
984 dErrors[sName] = sError;
985 return sValue;
986
987 @staticmethod
988 def _validateListOfStr(dErrors, sName, asValues, asValidValues = None):
989 """ Validates a list of text items."""
990 (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = True);
991 if sError is not None:
992 dErrors[sName] = sError;
993 return sValue;
994
995 @staticmethod
996 def _validateListOfStrNN(dErrors, sName, asValues, asValidValues = None):
997 """ Validates a list of text items, not null and len >= 1."""
998 (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = False);
999 if sError is not None:
1000 dErrors[sName] = sError;
1001 return sValue;
1002
1003 #
1004 # Various helpers.
1005 #
1006
1007 @staticmethod
1008 def formatSimpleNowAndPeriod(oDb, tsNow = None, sPeriodBack = None,
1009 sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'):
1010 """
1011 Formats a set of tsNow and sPeriodBack arguments for a standard testmanager
1012 table.
1013
1014 If sPeriodBack is given, the query is effective for the period
1015 (tsNow - sPeriodBack) thru (tsNow).
1016
1017 If tsNow isn't given, it defaults to current time.
1018
1019 Returns the final portion of a WHERE query (start with AND) and maybe an
1020 ORDER BY and LIMIT bit if sPeriodBack is given.
1021 """
1022 if tsNow is not None:
1023 if sPeriodBack is not None:
1024 sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (%s::timestamp - %s::interval)\n'
1025 ' AND tsEffective <= %s\n'
1026 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n'
1027 'LIMIT 1\n'
1028 , ( tsNow, sPeriodBack, tsNow));
1029 else:
1030 sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > %s\n'
1031 ' AND ' + sTablePrefix + sEffCol + ' <= %s\n'
1032 , ( tsNow, tsNow, ));
1033 else:
1034 if sPeriodBack is not None:
1035 sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (CURRENT_TIMESTAMP - %s::interval)\n'
1036 ' AND ' + sTablePrefix + sEffCol + ' <= CURRENT_TIMESTAMP\n'
1037 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n'
1038 'LIMIT 1\n'
1039 , ( sPeriodBack, ));
1040 else:
1041 sRet = ' AND ' + sTablePrefix + sExpCol + ' = \'infinity\'::timestamp\n';
1042 return sRet;
1043
1044 @staticmethod
1045 def formatSimpleNowAndPeriodQuery(oDb, sQuery, aBindArgs, tsNow = None, sPeriodBack = None,
1046 sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'):
1047 """
1048 Formats a simple query for a standard testmanager table with optional
1049 tsNow and sPeriodBack arguments.
1050
1051 The sQuery and sBindArgs are passed along to oDb.formatBindArgs to form
1052 the first part of the query. Must end with an open WHERE statement as
1053 we'll be adding the time part starting with 'AND something...'.
1054
1055 See formatSimpleNowAndPeriod for tsNow and sPeriodBack description.
1056
1057 Returns the final portion of a WHERE query (start with AND) and maybe an
1058 ORDER BY and LIMIT bit if sPeriodBack is given.
1059
1060 """
1061 return oDb.formatBindArgs(sQuery, aBindArgs) \
1062 + ModelDataBase.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix, sExpCol, sEffCol);
1063
1064 #
1065 # Sub-classes.
1066 #
1067
1068 class DispWrapper(object):
1069 """Proxy object."""
1070 def __init__(self, oDisp, sAttrFmt):
1071 self.oDisp = oDisp;
1072 self.sAttrFmt = sAttrFmt;
1073 def getStringParam(self, sName, asValidValues = None, sDefault = None, fAllowNull = False):
1074 """See WuiDispatcherBase.getStringParam."""
1075 return self.oDisp.getStringParam(self.sAttrFmt % (sName,), asValidValues, sDefault, fAllowNull = fAllowNull);
1076 def getListOfStrParams(self, sName, asDefaults = None):
1077 """See WuiDispatcherBase.getListOfStrParams."""
1078 return self.oDisp.getListOfStrParams(self.sAttrFmt % (sName,), asDefaults);
1079 def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None):
1080 """See WuiDispatcherBase.getListOfIntParams."""
1081 return self.oDisp.getListOfIntParams(self.sAttrFmt % (sName,), iMin, iMax, aiDefaults);
1082
1083
1084
1085
1086# pylint: disable=no-member,missing-docstring,too-few-public-methods
1087class ModelDataBaseTestCase(unittest.TestCase):
1088 """
1089 Base testcase for ModelDataBase decendants.
1090 Derive from this and override setUp.
1091 """
1092
1093 def setUp(self):
1094 """
1095 Override this! Don't call super!
1096 The subclasses are expected to set aoSamples to an array of instance
1097 samples. The first entry must be a default object, the subsequent ones
1098 are optional and their contents freely choosen.
1099 """
1100 self.aoSamples = [ModelDataBase(),];
1101
1102 def testEquality(self):
1103 for oSample in self.aoSamples:
1104 self.assertEqual(oSample.isEqual(copy.copy(oSample)), True);
1105 self.assertIsNotNone(oSample.isEqual(self.aoSamples[0]));
1106
1107 def testNullConversion(self):
1108 if not self.aoSamples[0].getDataAttributes():
1109 return;
1110 for oSample in self.aoSamples:
1111 oCopy = copy.copy(oSample);
1112 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1113 self.assertEqual(oCopy.isEqual(oSample), False);
1114 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1115 self.assertEqual(oCopy.isEqual(oSample), True, '\ngot : %s\nexpected: %s' % (oCopy, oSample,));
1116
1117 oCopy = copy.copy(oSample);
1118 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1119 oCopy2 = copy.copy(oCopy);
1120 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1121 self.assertEqual(oCopy.isEqual(oCopy2), True);
1122 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1123 self.assertEqual(oCopy.isEqual(oCopy2), True);
1124
1125 oCopy = copy.copy(oSample);
1126 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1127 oCopy2 = copy.copy(oCopy);
1128 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1129 self.assertEqual(oCopy.isEqual(oCopy2), True);
1130 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1131 self.assertEqual(oCopy.isEqual(oCopy2), True);
1132
1133 def testReinitToNull(self):
1134 oFirst = copy.copy(self.aoSamples[0]);
1135 self.assertEqual(oFirst.reinitToNull(), oFirst);
1136 for oSample in self.aoSamples:
1137 oCopy = copy.copy(oSample);
1138 self.assertEqual(oCopy.reinitToNull(), oCopy);
1139 self.assertEqual(oCopy.isEqual(oFirst), True);
1140
1141 def testValidateAndConvert(self):
1142 for oSample in self.aoSamples:
1143 oCopy = copy.copy(oSample);
1144 oCopy.convertToParamNull();
1145 dError1 = oCopy.validateAndConvert(None);
1146
1147 oCopy2 = copy.copy(oCopy);
1148 self.assertEqual(oCopy.validateAndConvert(None), dError1);
1149 self.assertEqual(oCopy.isEqual(oCopy2), True);
1150
1151 def testInitFromParams(self):
1152 class DummyDisp(object):
1153 def getStringParam(self, sName, asValidValues = None, sDefault = None, fAllowNull = False):
1154 _ = sName; _ = asValidValues; _ = fAllowNull;
1155 return sDefault;
1156 def getListOfStrParams(self, sName, asDefaults = None):
1157 _ = sName;
1158 return asDefaults;
1159 def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None):
1160 _ = sName; _ = iMin; _ = iMax;
1161 return aiDefaults;
1162
1163 for oSample in self.aoSamples:
1164 oCopy = copy.copy(oSample);
1165 self.assertEqual(oCopy.initFromParams(DummyDisp(), fStrict = False), oCopy);
1166
1167 def testToString(self):
1168 for oSample in self.aoSamples:
1169 self.assertIsNotNone(oSample.toString());
1170
1171
1172class FilterCriterionValueAndDescription(object):
1173 """
1174 A filter criterion value and its description.
1175 """
1176
1177 def __init__(self, oValue, sDesc, cTimes = None, sHover = None, fIrrelevant = False):
1178 self.oValue = oValue; ##< Typically the ID of something in the database.
1179 self.sDesc = sDesc; ##< What to display.
1180 self.cTimes = cTimes; ##< Number of times the value occurs in the result set. None if not given.
1181 self.sHover = sHover; ##< Optional hover/title string.
1182 self.fIrrelevant = fIrrelevant; ##< Irrelevant filter option, only present because it's selected
1183 self.aoSubs = []; ##< References to FilterCriterion.oSub.aoPossible.
1184
1185
1186class FilterCriterion(object):
1187 """
1188 A filter criterion.
1189 """
1190
1191 ## @name The state.
1192 ## @{
1193 ksState_NotSelected = 'not-selected';
1194 ksState_Selected = 'selected';
1195 ## @}
1196
1197 ## @name The kind of filtering.
1198 ## @{
1199 ## 'Element of' by default, 'not an element of' when fInverted is False.
1200 ksKind_ElementOfOrNot = 'element-of-or-not';
1201 ## The criterion is a special one and cannot be inverted.
1202 ksKind_Special = 'special';
1203 ## @}
1204
1205 ## @name The value type.
1206 ## @{
1207 ksType_UInt = 'uint'; ##< unsigned integer value.
1208 ksType_UIntNil = 'uint-nil'; ##< unsigned integer value, with nil.
1209 ksType_String = 'string'; ##< string value.
1210 ksType_Ranges = 'ranges'; ##< List of (unsigned) integer ranges.
1211 ## @}
1212
1213 def __init__(self, sName, sVarNm = None, sType = ksType_UInt, # pylint: disable=too-many-arguments
1214 sState = ksState_NotSelected, sKind = ksKind_ElementOfOrNot,
1215 sTable = None, sColumn = None, asTables = None, oSub = None):
1216 assert len(sVarNm) == 2; # required by wuimain.py for filtering.
1217 self.sName = sName;
1218 self.sState = sState;
1219 self.sType = sType;
1220 self.sKind = sKind;
1221 self.sVarNm = sVarNm;
1222 self.aoSelected = []; ##< User input from sVarNm. Single value, type according to sType.
1223 self.sInvVarNm = 'i' + sVarNm if sKind == self.ksKind_ElementOfOrNot else None;
1224 self.fInverted = False; ##< User input from sInvVarNm. Inverts the operation (-> not an element of).
1225 self.aoPossible = []; ##< type: list[FilterCriterionValueAndDescription]
1226 assert (sTable is None and asTables is None) or ((sTable is not None) != (asTables is not None)), \
1227 '%s %s' % (sTable, asTables);
1228 self.asTables = [sTable,] if sTable is not None else asTables;
1229 assert sColumn is None or len(self.asTables) == 1, '%s %s' % (self.asTables, sColumn);
1230 self.sColumn = sColumn; ##< Normally only applicable if one table.
1231 self.fExpanded = None; ##< Tristate (None, False, True)
1232 self.oSub = oSub; ##< type: FilterCriterion
1233
1234
1235class ModelFilterBase(ModelBase):
1236 """
1237 Base class for filters.
1238
1239 Filters are used to narrow down data that is displayed in a list or
1240 report. This class differs a little from ModelDataBase in that it is not
1241 tied to a database table, but one or more database queries that are
1242 typically rather complicated.
1243
1244 The filter object has two roles:
1245
1246 1. It is used by a ModelLogicBase descendant to store the available
1247 filtering options for data begin displayed.
1248
1249 2. It decodes and stores the filtering options submitted by the user so
1250 a ModeLogicBase descendant can use it to construct WHERE statements.
1251
1252 The ModelFilterBase class is related to the ModelDataBase class in that it
1253 decodes user parameters and stores data, however it is not a descendant.
1254
1255 Note! In order to reduce URL lengths, we use very very brief parameter
1256 names for the filters.
1257 """
1258
1259 def __init__(self):
1260 ModelBase.__init__(self);
1261 self.aCriteria = [] # type: list[FilterCriterion]
1262
1263 def _initFromParamsWorker(self, oDisp, oCriterion): # (,FilterCriterion)
1264 """ Worker for initFromParams. """
1265 if oCriterion.sType == FilterCriterion.ksType_UInt:
1266 oCriterion.aoSelected = oDisp.getListOfIntParams(oCriterion.sVarNm, iMin = 0, aiDefaults = []);
1267 elif oCriterion.sType == FilterCriterion.ksType_UIntNil:
1268 oCriterion.aoSelected = oDisp.getListOfIntParams(oCriterion.sVarNm, iMin = -1, aiDefaults = []);
1269 elif oCriterion.sType == FilterCriterion.ksType_String:
1270 oCriterion.aoSelected = oDisp.getListOfStrParams(oCriterion.sVarNm, asDefaults = []);
1271 if len(oCriterion.aoSelected) > 100:
1272 raise TMExceptionBase('Variable %s has %u value, max allowed is 100!'
1273 % (oCriterion.sVarNm, len(oCriterion.aoSelected)));
1274 for sValue in oCriterion.aoSelected:
1275 if len(sValue) > 64 \
1276 or '\'' in sValue \
1277 or sValue[-1] == '\\':
1278 raise TMExceptionBase('Variable %s has an illegal value "%s"!' % (oCriterion.sVarNm, sValue));
1279 elif oCriterion.sType == FilterCriterion.ksType_Ranges:
1280 def convertRangeNumber(sValue):
1281 """ Helper """
1282 sValue = sValue.strip();
1283 if sValue and sValue not in ('inf', 'Inf', 'INf', 'INF', 'InF', 'iNf', 'iNF', 'inF',):
1284 try: return int(sValue);
1285 except: pass;
1286 return None;
1287
1288 for sRange in oDisp.getStringParam(oCriterion.sVarNm, sDefault = '').split(','):
1289 sRange = sRange.strip();
1290 if sRange and sRange != '-' and any(ch.isdigit() for ch in sRange):
1291 asValues = sRange.split('-');
1292 if len(asValues) == 1:
1293 asValues = [asValues[0], asValues[0]];
1294 elif len(asValues) > 2:
1295 asValues = [asValues[0], asValues[-1]];
1296 tTuple = (convertRangeNumber(asValues[0]), convertRangeNumber(asValues[1]));
1297 if tTuple[0] is not None and tTuple[1] is not None and tTuple[0] > tTuple[1]:
1298 tTuple = (tTuple[1], tTuple[0]);
1299 oCriterion.aoSelected.append(tTuple);
1300 else:
1301 assert False;
1302 if oCriterion.aoSelected:
1303 oCriterion.sState = FilterCriterion.ksState_Selected;
1304 else:
1305 oCriterion.sState = FilterCriterion.ksState_NotSelected;
1306
1307 if oCriterion.sKind == FilterCriterion.ksKind_ElementOfOrNot:
1308 oCriterion.fInverted = oDisp.getBoolParam(oCriterion.sInvVarNm, fDefault = False);
1309
1310 if oCriterion.oSub is not None:
1311 self._initFromParamsWorker(oDisp, oCriterion.oSub);
1312 return;
1313
1314 def initFromParams(self, oDisp): # type: (WuiDispatcherBase) -> self
1315 """
1316 Initialize the object from parameters.
1317
1318 Returns self. Raises exception on invalid parameter value.
1319 """
1320
1321 for oCriterion in self.aCriteria:
1322 self._initFromParamsWorker(oDisp, oCriterion);
1323 return self;
1324
1325 def strainParameters(self, dParams, aAdditionalParams = None):
1326 """ Filters just the parameters relevant to this filter, returning a copy. """
1327
1328 # Collect the parameter names.
1329 dWanted = dict();
1330 for oCrit in self.aCriteria:
1331 dWanted[oCrit.sVarNm] = 1;
1332 if oCrit.sInvVarNm:
1333 dWanted[oCrit.sInvVarNm] = 1;
1334
1335 # Add additional stuff.
1336 if aAdditionalParams:
1337 for sParam in aAdditionalParams:
1338 dWanted[sParam] = 1;
1339
1340 # To the straining.
1341 dRet = dict();
1342 for sKey in dParams:
1343 if sKey in dWanted:
1344 dRet[sKey] = dParams[sKey];
1345 return dRet;
1346
1347
1348class ModelLogicBase(ModelBase): # pylint: disable=too-few-public-methods
1349 """
1350 Something all classes in the logic classes the logical model inherits from.
1351 """
1352
1353 def __init__(self, oDb):
1354 ModelBase.__init__(self);
1355
1356 #
1357 # Note! Do not create a connection here if None, we need to DB share
1358 # connection with all other logic objects so we can perform half
1359 # complex transactions involving several logic objects.
1360 #
1361 self._oDb = oDb;
1362
1363 def getDbConnection(self):
1364 """
1365 Gets the database connection.
1366 This should only be used for instantiating other ModelLogicBase children.
1367 """
1368 return self._oDb;
1369
1370 def _dbRowsToModelDataList(self, oModelDataType, aaoRows = None):
1371 """
1372 Helper for conerting a simple fetch into a list of ModelDataType python objects.
1373
1374 If aaoRows is None, we'll fetchAll from the database ourselves.
1375
1376 The oModelDataType must be a class derived from ModelDataBase and implement
1377 the initFormDbRow method.
1378
1379 Returns a list of oModelDataType instances.
1380 """
1381 assert issubclass(oModelDataType, ModelDataBase);
1382 aoRet = [];
1383 if aaoRows is None:
1384 aaoRows = self._oDb.fetchAll();
1385 for aoRow in aaoRows:
1386 aoRet.append(oModelDataType().initFromDbRow(aoRow));
1387 return aoRet;
1388
1389
1390
1391class AttributeChangeEntry(object): # pylint: disable=too-few-public-methods
1392 """
1393 Data class representing the changes made to one attribute.
1394 """
1395
1396 def __init__(self, sAttr, oNewRaw, oOldRaw, sNewText, sOldText):
1397 self.sAttr = sAttr;
1398 self.oNewRaw = oNewRaw;
1399 self.oOldRaw = oOldRaw;
1400 self.sNewText = sNewText;
1401 self.sOldText = sOldText;
1402
1403class AttributeChangeEntryPre(AttributeChangeEntry): # pylint: disable=too-few-public-methods
1404 """
1405 AttributeChangeEntry for preformatted values.
1406 """
1407
1408 def __init__(self, sAttr, oNewRaw, oOldRaw, sNewText, sOldText):
1409 AttributeChangeEntry.__init__(self, sAttr, oNewRaw, oOldRaw, sNewText, sOldText);
1410
1411class ChangeLogEntry(object): # pylint: disable=too-few-public-methods
1412 """
1413 A change log entry returned by the fetchChangeLog method typically
1414 implemented by ModelLogicBase child classes.
1415 """
1416
1417 def __init__(self, uidAuthor, sAuthor, tsEffective, tsExpire, oNewRaw, oOldRaw, aoChanges):
1418 self.uidAuthor = uidAuthor;
1419 self.sAuthor = sAuthor;
1420 self.tsEffective = tsEffective;
1421 self.tsExpire = tsExpire;
1422 self.oNewRaw = oNewRaw;
1423 self.oOldRaw = oOldRaw; # Note! NULL for the last entry.
1424 self.aoChanges = aoChanges;
1425
Note: See TracBrowser for help on using the repository browser.

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