1 | # -*- coding: utf-8 -*-
|
---|
2 | # $Id: base.py 84271 2020-05-12 14:39:15Z vboxsync $
|
---|
3 | # pylint: disable=too-many-lines
|
---|
4 |
|
---|
5 | """
|
---|
6 | Test Manager Core - Base Class(es).
|
---|
7 | """
|
---|
8 |
|
---|
9 | __copyright__ = \
|
---|
10 | """
|
---|
11 | Copyright (C) 2012-2020 Oracle Corporation
|
---|
12 |
|
---|
13 | This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
14 | available from http://www.virtualbox.org. This file is free software;
|
---|
15 | you can redistribute it and/or modify it under the terms of the GNU
|
---|
16 | General Public License (GPL) as published by the Free Software
|
---|
17 | Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
18 | VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
19 | hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
20 |
|
---|
21 | The contents of this file may alternatively be used under the terms
|
---|
22 | of the Common Development and Distribution License Version 1.0
|
---|
23 | (CDDL) only, as it comes in the "COPYING.CDDL" file of the
|
---|
24 | VirtualBox OSE distribution, in which case the provisions of the
|
---|
25 | CDDL are applicable instead of those of the GPL.
|
---|
26 |
|
---|
27 | You may elect to license modified versions of this file under the
|
---|
28 | terms and conditions of either the GPL or the CDDL or both.
|
---|
29 | """
|
---|
30 | __version__ = "$Revision: 84271 $"
|
---|
31 |
|
---|
32 |
|
---|
33 | # Standard python imports.
|
---|
34 | import copy;
|
---|
35 | import re;
|
---|
36 | import socket;
|
---|
37 | import sys;
|
---|
38 | import uuid;
|
---|
39 | import unittest;
|
---|
40 |
|
---|
41 | # Validation Kit imports.
|
---|
42 | from common import utils;
|
---|
43 |
|
---|
44 | # Python 3 hacks:
|
---|
45 | if sys.version_info[0] >= 3:
|
---|
46 | long = int # pylint: disable=redefined-builtin,invalid-name
|
---|
47 |
|
---|
48 |
|
---|
49 | class TMExceptionBase(Exception):
|
---|
50 | """
|
---|
51 | For exceptions raised by any TestManager component.
|
---|
52 | """
|
---|
53 | pass; # pylint: disable=unnecessary-pass
|
---|
54 |
|
---|
55 |
|
---|
56 | class TMTooManyRows(TMExceptionBase):
|
---|
57 | """
|
---|
58 | Too many rows in the result.
|
---|
59 | Used by ModelLogicBase decendants.
|
---|
60 | """
|
---|
61 | pass; # pylint: disable=unnecessary-pass
|
---|
62 |
|
---|
63 |
|
---|
64 | class TMRowNotFound(TMExceptionBase):
|
---|
65 | """
|
---|
66 | Database row not found.
|
---|
67 | Used by ModelLogicBase decendants.
|
---|
68 | """
|
---|
69 | pass; # pylint: disable=unnecessary-pass
|
---|
70 |
|
---|
71 |
|
---|
72 | class TMRowAlreadyExists(TMExceptionBase):
|
---|
73 | """
|
---|
74 | Database row already exists (typically raised by addEntry).
|
---|
75 | Used by ModelLogicBase decendants.
|
---|
76 | """
|
---|
77 | pass; # pylint: disable=unnecessary-pass
|
---|
78 |
|
---|
79 |
|
---|
80 | class TMInvalidData(TMExceptionBase):
|
---|
81 | """
|
---|
82 | Data validation failed.
|
---|
83 | Used by ModelLogicBase decendants.
|
---|
84 | """
|
---|
85 | pass; # pylint: disable=unnecessary-pass
|
---|
86 |
|
---|
87 |
|
---|
88 | class TMRowInUse(TMExceptionBase):
|
---|
89 | """
|
---|
90 | Database row is in use and cannot be deleted.
|
---|
91 | Used by ModelLogicBase decendants.
|
---|
92 | """
|
---|
93 | pass; # pylint: disable=unnecessary-pass
|
---|
94 |
|
---|
95 |
|
---|
96 | class 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 |
|
---|
105 | class 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 |
|
---|
117 | class 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
|
---|
1087 | class 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 |
|
---|
1172 | class 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 |
|
---|
1186 | class 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 |
|
---|
1235 | class 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 |
|
---|
1348 | class 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 |
|
---|
1391 | class 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 |
|
---|
1403 | class 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 |
|
---|
1411 | class 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 |
|
---|