VirtualBox

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

Last change on this file since 66998 was 65980, checked in by vboxsync, 8 years ago

testmanager/core: pylint 2.0.0 fixes.

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