VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py@ 88839

Last change on this file since 88839 was 84921, checked in by vboxsync, 4 years ago

ValKit: pylint 2.5.3 adjustments.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 53.6 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: wuicontentbase.py 84921 2020-06-23 20:30:13Z vboxsync $
3
4"""
5Test Manager Web-UI - Content Base Classes.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2020 Oracle Corporation
11
12This file is part of VirtualBox Open Source Edition (OSE), as
13available from http://www.virtualbox.org. This file is free software;
14you can redistribute it and/or modify it under the terms of the GNU
15General Public License (GPL) as published by the Free Software
16Foundation, in version 2 as it comes in the "COPYING" file of the
17VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
20The contents of this file may alternatively be used under the terms
21of the Common Development and Distribution License Version 1.0
22(CDDL) only, as it comes in the "COPYING.CDDL" file of the
23VirtualBox OSE distribution, in which case the provisions of the
24CDDL are applicable instead of those of the GPL.
25
26You may elect to license modified versions of this file under the
27terms and conditions of either the GPL or the CDDL or both.
28"""
29__version__ = "$Revision: 84921 $"
30
31
32# Standard python imports.
33import copy;
34import sys;
35
36# Validation Kit imports.
37from common import utils, webutils;
38from testmanager import config;
39from testmanager.webui.wuibase import WuiDispatcherBase, WuiException;
40from testmanager.webui.wuihlpform import WuiHlpForm;
41from testmanager.core import db;
42from testmanager.core.base import AttributeChangeEntryPre;
43
44# Python 3 hacks:
45if sys.version_info[0] >= 3:
46 unicode = str; # pylint: disable=redefined-builtin,invalid-name
47
48
49class WuiHtmlBase(object): # pylint: disable=too-few-public-methods
50 """
51 Base class for HTML objects.
52 """
53
54 def __init__(self):
55 """Dummy init to shut up pylint."""
56 pass; # pylint: disable=unnecessary-pass
57
58 def toHtml(self):
59
60 """
61 Must be overridden by sub-classes.
62 """
63 assert False;
64 return '';
65
66 def __str__(self):
67 """ String representation is HTML, simplifying formatting and such. """
68 return self.toHtml();
69
70
71class WuiLinkBase(WuiHtmlBase): # pylint: disable=too-few-public-methods
72 """
73 For passing links from WuiListContentBase._formatListEntry.
74 """
75
76 def __init__(self, sName, sUrlBase, dParams = None, sConfirm = None, sTitle = None,
77 sFragmentId = None, fBracketed = True, sExtraAttrs = ''):
78 WuiHtmlBase.__init__(self);
79 self.sName = sName
80 self.sUrl = sUrlBase
81 self.sConfirm = sConfirm;
82 self.sTitle = sTitle;
83 self.fBracketed = fBracketed;
84 self.sExtraAttrs = sExtraAttrs;
85
86 if dParams:
87 # Do some massaging of None arguments.
88 dParams = dict(dParams);
89 for sKey in dParams:
90 if dParams[sKey] is None:
91 dParams[sKey] = '';
92 self.sUrl += '?' + webutils.encodeUrlParams(dParams);
93
94 if sFragmentId is not None:
95 self.sUrl += '#' + sFragmentId;
96
97 def setBracketed(self, fBracketed):
98 """Changes the bracketing style."""
99 self.fBracketed = fBracketed;
100 return True;
101
102 def toHtml(self):
103 """
104 Returns a simple HTML anchor element.
105 """
106 sExtraAttrs = self.sExtraAttrs;
107 if self.sConfirm is not None:
108 sExtraAttrs += 'onclick=\'return confirm("%s");\' ' % (webutils.escapeAttr(self.sConfirm),);
109 if self.sTitle is not None:
110 sExtraAttrs += 'title="%s" ' % (webutils.escapeAttr(self.sTitle),);
111 if sExtraAttrs and sExtraAttrs[-1] != ' ':
112 sExtraAttrs += ' ';
113
114 sFmt = '[<a %shref="%s">%s</a>]';
115 if not self.fBracketed:
116 sFmt = '<a %shref="%s">%s</a>';
117 return sFmt % (sExtraAttrs, webutils.escapeAttr(self.sUrl), webutils.escapeElem(self.sName));
118
119 @staticmethod
120 def estimateStringWidth(sString):
121 """
122 Takes a string and estimate it's width so the caller can pad with
123 U+2002 before tab in a title text. This is very very rough.
124 """
125 cchWidth = 0;
126 for ch in sString:
127 if ch.isupper() or ch in u'wm\u2007\u2003\u2001\u3000':
128 cchWidth += 2;
129 else:
130 cchWidth += 1;
131 return cchWidth;
132
133 @staticmethod
134 def getStringWidthPaddingEx(cchWidth, cchMaxWidth):
135 """ Works with estiamteStringWidth(). """
136 if cchWidth + 2 <= cchMaxWidth:
137 return u'\u2002' * ((cchMaxWidth - cchWidth) * 2 // 3)
138 return u'';
139
140 @staticmethod
141 def getStringWidthPadding(sString, cchMaxWidth):
142 """ Works with estiamteStringWidth(). """
143 return WuiLinkBase.getStringWidthPaddingEx(WuiLinkBase.estimateStringWidth(sString), cchMaxWidth);
144
145 @staticmethod
146 def padStringToWidth(sString, cchMaxWidth):
147 """ Works with estimateStringWidth. """
148 cchWidth = WuiLinkBase.estimateStringWidth(sString);
149 if cchWidth < cchMaxWidth:
150 return sString + WuiLinkBase.getStringWidthPaddingEx(cchWidth, cchMaxWidth);
151 return sString;
152
153
154class WuiTmLink(WuiLinkBase): # pylint: disable=too-few-public-methods
155 """ Local link to the test manager. """
156
157 kdDbgParams = [];
158
159 def __init__(self, sName, sUrlBase, dParams = None, sConfirm = None, sTitle = None,
160 sFragmentId = None, fBracketed = True):
161
162 # Add debug parameters if necessary.
163 if self.kdDbgParams:
164 if not dParams:
165 dParams = dict(self.kdDbgParams);
166 else:
167 dParams = dict(dParams);
168 for sKey in self.kdDbgParams:
169 if sKey not in dParams:
170 dParams[sKey] = self.kdDbgParams[sKey];
171
172 WuiLinkBase.__init__(self, sName, sUrlBase, dParams, sConfirm, sTitle, sFragmentId, fBracketed);
173
174
175class WuiAdminLink(WuiTmLink): # pylint: disable=too-few-public-methods
176 """ Local link to the test manager's admin portion. """
177
178 def __init__(self, sName, sAction, tsEffectiveDate = None, dParams = None, sConfirm = None, sTitle = None,
179 sFragmentId = None, fBracketed = True):
180 from testmanager.webui.wuiadmin import WuiAdmin;
181 if not dParams:
182 dParams = dict();
183 else:
184 dParams = dict(dParams);
185 if sAction is not None:
186 dParams[WuiAdmin.ksParamAction] = sAction;
187 if tsEffectiveDate is not None:
188 dParams[WuiAdmin.ksParamEffectiveDate] = tsEffectiveDate;
189 WuiTmLink.__init__(self, sName, WuiAdmin.ksScriptName, dParams = dParams, sConfirm = sConfirm, sTitle = sTitle,
190 sFragmentId = sFragmentId, fBracketed = fBracketed);
191
192class WuiMainLink(WuiTmLink): # pylint: disable=too-few-public-methods
193 """ Local link to the test manager's main portion. """
194
195 def __init__(self, sName, sAction, dParams = None, sConfirm = None, sTitle = None, sFragmentId = None, fBracketed = True):
196 if not dParams:
197 dParams = dict();
198 else:
199 dParams = dict(dParams);
200 from testmanager.webui.wuimain import WuiMain;
201 if sAction is not None:
202 dParams[WuiMain.ksParamAction] = sAction;
203 WuiTmLink.__init__(self, sName, WuiMain.ksScriptName, dParams = dParams, sConfirm = sConfirm, sTitle = sTitle,
204 sFragmentId = sFragmentId, fBracketed = fBracketed);
205
206class WuiSvnLink(WuiLinkBase): # pylint: disable=too-few-public-methods
207 """
208 For linking to a SVN revision.
209 """
210 def __init__(self, iRevision, sName = None, fBracketed = True, sExtraAttrs = ''):
211 if sName is None:
212 sName = 'r%s' % (iRevision,);
213 WuiLinkBase.__init__(self, sName, config.g_ksTracLogUrlPrefix, { 'rev': iRevision,},
214 fBracketed = fBracketed, sExtraAttrs = sExtraAttrs);
215
216class WuiSvnLinkWithTooltip(WuiSvnLink): # pylint: disable=too-few-public-methods
217 """
218 For linking to a SVN revision with changelog tooltip.
219 """
220 def __init__(self, iRevision, sRepository, sName = None, fBracketed = True):
221 sExtraAttrs = ' onmouseover="return svnHistoryTooltipShow(event,\'%s\',%s);" onmouseout="return tooltipHide();"' \
222 % ( sRepository, iRevision, );
223 WuiSvnLink.__init__(self, iRevision, sName = sName, fBracketed = fBracketed, sExtraAttrs = sExtraAttrs);
224
225class WuiBuildLogLink(WuiLinkBase):
226 """
227 For linking to a build log.
228 """
229 def __init__(self, sUrl, sName = None, fBracketed = True):
230 assert sUrl;
231 if sName is None:
232 sName = 'Build log';
233 if not webutils.hasSchema(sUrl):
234 WuiLinkBase.__init__(self, sName, config.g_ksBuildLogUrlPrefix + sUrl, fBracketed = fBracketed);
235 else:
236 WuiLinkBase.__init__(self, sName, sUrl, fBracketed = fBracketed);
237
238class WuiRawHtml(WuiHtmlBase): # pylint: disable=too-few-public-methods
239 """
240 For passing raw html from WuiListContentBase._formatListEntry.
241 """
242 def __init__(self, sHtml):
243 self.sHtml = sHtml;
244 WuiHtmlBase.__init__(self);
245
246 def toHtml(self):
247 return self.sHtml;
248
249class WuiHtmlKeeper(WuiHtmlBase): # pylint: disable=too-few-public-methods
250 """
251 For keeping a list of elements, concatenating their toHtml output together.
252 """
253 def __init__(self, aoInitial = None, sSep = ' '):
254 WuiHtmlBase.__init__(self);
255 self.sSep = sSep;
256 self.aoKept = [];
257 if aoInitial is not None:
258 if isinstance(aoInitial, WuiHtmlBase):
259 self.aoKept.append(aoInitial);
260 else:
261 self.aoKept.extend(aoInitial);
262
263 def append(self, oObject):
264 """ Appends one objects. """
265 self.aoKept.append(oObject);
266
267 def extend(self, aoObjects):
268 """ Appends a list of objects. """
269 self.aoKept.extend(aoObjects);
270
271 def toHtml(self):
272 return self.sSep.join(oObj.toHtml() for oObj in self.aoKept);
273
274class WuiSpanText(WuiRawHtml): # pylint: disable=too-few-public-methods
275 """
276 Outputs the given text within a span of the given CSS class.
277 """
278 def __init__(self, sSpanClass, sText, sTitle = None):
279 if sTitle is None:
280 WuiRawHtml.__init__(self,
281 u'<span class="%s">%s</span>'
282 % ( webutils.escapeAttr(sSpanClass), webutils.escapeElem(sText),));
283 else:
284 WuiRawHtml.__init__(self,
285 u'<span class="%s" title="%s">%s</span>'
286 % ( webutils.escapeAttr(sSpanClass), webutils.escapeAttr(sTitle), webutils.escapeElem(sText),));
287
288class WuiElementText(WuiRawHtml): # pylint: disable=too-few-public-methods
289 """
290 Outputs the given element text.
291 """
292 def __init__(self, sText):
293 WuiRawHtml.__init__(self, webutils.escapeElem(sText));
294
295
296class WuiContentBase(object): # pylint: disable=too-few-public-methods
297 """
298 Base for the content classes.
299 """
300
301 ## The text/symbol for a very short add link.
302 ksShortAddLink = u'\u2795'
303 ## HTML hex entity string for ksShortAddLink.
304 ksShortAddLinkHtml = '&#x2795;;'
305 ## The text/symbol for a very short edit link.
306 ksShortEditLink = u'\u270D'
307 ## HTML hex entity string for ksShortDetailsLink.
308 ksShortEditLinkHtml = '&#x270d;'
309 ## The text/symbol for a very short details link.
310 ksShortDetailsLink = u'\U0001f6c8\ufe0e'
311 ## HTML hex entity string for ksShortDetailsLink.
312 ksShortDetailsLinkHtml = '&#x1f6c8;;&#xfe0e;'
313 ## The text/symbol for a very short change log / details / previous page link.
314 ksShortChangeLogLink = u'\u2397'
315 ## HTML hex entity string for ksShortDetailsLink.
316 ksShortChangeLogLinkHtml = '&#x2397;'
317 ## The text/symbol for a very short reports link.
318 ksShortReportLink = u'\U0001f4ca\ufe0e'
319 ## HTML hex entity string for ksShortReportLink.
320 ksShortReportLinkHtml = '&#x1f4ca;&#xfe0e;'
321 ## The text/symbol for a very short test results link.
322 ksShortTestResultsLink = u'\U0001f5d0\ufe0e'
323
324
325 def __init__(self, fnDPrint = None, oDisp = None):
326 self._oDisp = oDisp; # WuiDispatcherBase.
327 self._fnDPrint = fnDPrint;
328 if fnDPrint is None and oDisp is not None:
329 self._fnDPrint = oDisp.dprint;
330
331 def dprint(self, sText):
332 """ Debug printing. """
333 if self._fnDPrint:
334 self._fnDPrint(sText);
335
336 @staticmethod
337 def formatTsShort(oTs):
338 """
339 Formats a timestamp (db rep) into a short form.
340 """
341 oTsZulu = db.dbTimestampToZuluDatetime(oTs);
342 sTs = oTsZulu.strftime('%Y-%m-%d %H:%M:%SZ');
343 return unicode(sTs).replace('-', u'\u2011').replace(' ', u'\u00a0');
344
345 def getNowTs(self):
346 """ Gets a database compatible current timestamp from python. See db.dbTimestampPythonNow(). """
347 return db.dbTimestampPythonNow();
348
349 def formatIntervalShort(self, oInterval):
350 """
351 Formats an interval (db rep) into a short form.
352 """
353 # default formatting for negative intervals.
354 if oInterval.days < 0:
355 return str(oInterval);
356
357 # Figure the hour, min and sec counts.
358 cHours = oInterval.seconds // 3600;
359 cMinutes = (oInterval.seconds % 3600) // 60;
360 cSeconds = oInterval.seconds - cHours * 3600 - cMinutes * 60;
361
362 # Tailor formatting to the interval length.
363 if oInterval.days > 0:
364 if oInterval.days > 1:
365 return '%d days, %d:%02d:%02d' % (oInterval.days, cHours, cMinutes, cSeconds);
366 return '1 day, %d:%02d:%02d' % (cHours, cMinutes, cSeconds);
367 if cMinutes > 0 or cSeconds >= 30 or cHours > 0:
368 return '%d:%02d:%02d' % (cHours, cMinutes, cSeconds);
369 if cSeconds >= 10:
370 return '%d.%ds' % (cSeconds, oInterval.microseconds // 100000);
371 if cSeconds > 0:
372 return '%d.%02ds' % (cSeconds, oInterval.microseconds // 10000);
373 return '%d ms' % (oInterval.microseconds // 1000,);
374
375 @staticmethod
376 def genericPageWalker(iCurItem, cItems, sHrefFmt, cWidth = 11, iBase = 1, sItemName = 'page'):
377 """
378 Generic page walker generator.
379
380 sHrefFmt has three %s sequences:
381 1. The first is the page number link parameter (0-based).
382 2. The title text, iBase-based number or text.
383 3. The link text, iBase-based number or text.
384 """
385
386 # Calc display range.
387 iStart = 0 if iCurItem - cWidth // 2 <= cWidth // 4 else iCurItem - cWidth // 2;
388 iEnd = iStart + cWidth;
389 if iEnd > cItems:
390 iEnd = cItems;
391 if cItems > cWidth:
392 iStart = cItems - cWidth;
393
394 sHtml = u'';
395
396 # Previous page (using << >> because &laquo; and &raquo are too tiny).
397 if iCurItem > 0:
398 sHtml += '%s&nbsp;&nbsp;' % sHrefFmt % (iCurItem - 1, 'previous ' + sItemName, '&lt;&lt;');
399 else:
400 sHtml += '&lt;&lt;&nbsp;&nbsp;';
401
402 # 1 2 3 4...
403 if iStart > 0:
404 sHtml += '%s&nbsp; ... &nbsp;\n' % (sHrefFmt % (0, 'first %s' % (sItemName,), 0 + iBase),);
405
406 sHtml += '&nbsp;\n'.join(sHrefFmt % (i, '%s %d' % (sItemName, i + iBase), i + iBase) if i != iCurItem
407 else unicode(i + iBase)
408 for i in range(iStart, iEnd));
409 if iEnd < cItems:
410 sHtml += '&nbsp; ... &nbsp;%s\n' % (sHrefFmt % (cItems - 1, 'last %s' % (sItemName,), cItems - 1 + iBase));
411
412 # Next page.
413 if iCurItem + 1 < cItems:
414 sHtml += '&nbsp;&nbsp;%s' % sHrefFmt % (iCurItem + 1, 'next ' + sItemName, '&gt;&gt;');
415 else:
416 sHtml += '&nbsp;&nbsp;&gt;&gt;';
417
418 return sHtml;
419
420class WuiSingleContentBase(WuiContentBase): # pylint: disable=too-few-public-methods
421 """
422 Base for the content classes working on a single data object (oData).
423 """
424 def __init__(self, oData, oDisp = None, fnDPrint = None):
425 WuiContentBase.__init__(self, oDisp = oDisp, fnDPrint = fnDPrint);
426 self._oData = oData; # Usually ModelDataBase.
427
428
429class WuiFormContentBase(WuiSingleContentBase): # pylint: disable=too-few-public-methods
430 """
431 Base class for simple input form content classes (single data object).
432 """
433
434 ## @name Form mode.
435 ## @{
436 ksMode_Add = 'add';
437 ksMode_Edit = 'edit';
438 ksMode_Show = 'show';
439 ## @}
440
441 ## Default action mappings.
442 kdSubmitActionMappings = {
443 ksMode_Add: 'AddPost',
444 ksMode_Edit: 'EditPost',
445 };
446
447 def __init__(self, oData, sMode, sCoreName, oDisp, sTitle, sId = None, fEditable = True, sSubmitAction = None):
448 WuiSingleContentBase.__init__(self, copy.copy(oData), oDisp);
449 assert sMode in [self.ksMode_Add, self.ksMode_Edit, self.ksMode_Show];
450 assert len(sTitle) > 1;
451 assert sId is None or sId;
452
453 self._sMode = sMode;
454 self._sCoreName = sCoreName;
455 self._sActionBase = 'ksAction' + sCoreName;
456 self._sTitle = sTitle;
457 self._sId = sId if sId is not None else (type(oData).__name__.lower() + 'form');
458 self._fEditable = fEditable and (oDisp is None or not oDisp.isReadOnlyUser())
459 self._sSubmitAction = sSubmitAction;
460 if sSubmitAction is None and sMode != self.ksMode_Show:
461 self._sSubmitAction = getattr(oDisp, self._sActionBase + self.kdSubmitActionMappings[sMode]);
462 self._sRedirectTo = None;
463
464
465 def _populateForm(self, oForm, oData):
466 """
467 Populates the form. oData has parameter NULL values.
468 This must be reimplemented by the child.
469 """
470 _ = oForm; _ = oData;
471 raise Exception('Reimplement me!');
472
473 def _generatePostFormContent(self, oData):
474 """
475 Generate optional content that comes below the form.
476 Returns a list of tuples, where the first tuple element is the title
477 and the second the content. I.e. similar to show() output.
478 """
479 _ = oData;
480 return [];
481
482 @staticmethod
483 def _calcChangeLogEntryLinks(aoEntries, iEntry):
484 """
485 Returns an array of links to go with the change log entry.
486 """
487 _ = aoEntries; _ = iEntry;
488 ## @todo detect deletion and recreation.
489 ## @todo view details link.
490 ## @todo restore link (need new action)
491 ## @todo clone link.
492 return [];
493
494 @staticmethod
495 def _guessChangeLogEntryDescription(aoEntries, iEntry):
496 """
497 Guesses the action + author that caused the change log entry.
498 Returns descriptive string.
499 """
500 oEntry = aoEntries[iEntry];
501
502 # Figure the author of the change.
503 if oEntry.sAuthor is not None:
504 sAuthor = '%s (#%s)' % (oEntry.sAuthor, oEntry.uidAuthor,);
505 elif oEntry.uidAuthor is not None:
506 sAuthor = '#%d (??)' % (oEntry.uidAuthor,);
507 else:
508 sAuthor = None;
509
510 # Figure the action.
511 if oEntry.oOldRaw is None:
512 if sAuthor is None:
513 return 'Created by batch job.';
514 return 'Created by %s.' % (sAuthor,);
515
516 if sAuthor is None:
517 return 'Automatically updated.'
518 return 'Modified by %s.' % (sAuthor,);
519
520 @staticmethod
521 def formatChangeLogEntry(aoEntries, iEntry, sUrl, dParams):
522 """
523 Formats one change log entry into one or more HTML table rows.
524
525 The sUrl and dParams arguments are used to construct links to historical
526 data using the tsEffective value. If no links wanted, they'll both be None.
527
528 Note! The parameters are given as array + index in case someone wishes
529 to access adjacent entries later in order to generate better
530 change descriptions.
531 """
532 oEntry = aoEntries[iEntry];
533
534 # Turn the effective date into a URL if we can:
535 if sUrl:
536 dParams[WuiDispatcherBase.ksParamEffectiveDate] = oEntry.tsEffective;
537 sEffective = WuiLinkBase(WuiFormContentBase.formatTsShort(oEntry.tsEffective), sUrl,
538 dParams, fBracketed = False).toHtml();
539 else:
540 sEffective = webutils.escapeElem(WuiFormContentBase.formatTsShort(oEntry.tsEffective))
541
542 # The primary row.
543 sRowClass = 'tmodd' if (iEntry + 1) & 1 else 'tmeven';
544 sContent = ' <tr class="%s">\n' \
545 ' <td rowspan="%d">%s</td>\n' \
546 ' <td rowspan="%d">%s</td>\n' \
547 ' <td colspan="3">%s%s</td>\n' \
548 ' </tr>\n' \
549 % ( sRowClass,
550 len(oEntry.aoChanges) + 1, sEffective,
551 len(oEntry.aoChanges) + 1, webutils.escapeElem(WuiFormContentBase.formatTsShort(oEntry.tsExpire)),
552 WuiFormContentBase._guessChangeLogEntryDescription(aoEntries, iEntry),
553 ' '.join(oLink.toHtml() for oLink in WuiFormContentBase._calcChangeLogEntryLinks(aoEntries, iEntry)),);
554
555 # Additional rows for each changed attribute.
556 j = 0;
557 for oChange in oEntry.aoChanges:
558 if isinstance(oChange, AttributeChangeEntryPre):
559 sContent += ' <tr class="%s%s"><td>%s</td>'\
560 '<td><div class="tdpre">%s%s%s</div></td>' \
561 '<td><div class="tdpre">%s%s%s</div></td></tr>\n' \
562 % ( sRowClass, 'odd' if j & 1 else 'even',
563 webutils.escapeElem(oChange.sAttr),
564 '<pre>' if oChange.sOldText else '',
565 webutils.escapeElem(oChange.sOldText),
566 '</pre>' if oChange.sOldText else '',
567 '<pre>' if oChange.sNewText else '',
568 webutils.escapeElem(oChange.sNewText),
569 '</pre>' if oChange.sNewText else '', );
570 else:
571 sContent += ' <tr class="%s%s"><td>%s</td><td>%s</td><td>%s</td></tr>\n' \
572 % ( sRowClass, 'odd' if j & 1 else 'even',
573 webutils.escapeElem(oChange.sAttr),
574 webutils.escapeElem(oChange.sOldText),
575 webutils.escapeElem(oChange.sNewText), );
576 j += 1;
577
578 return sContent;
579
580 def _showChangeLogNavi(self, fMoreEntries, iPageNo, cEntriesPerPage, tsNow, sWhere):
581 """
582 Returns the HTML for the change log navigator.
583 Note! See also _generateNavigation.
584 """
585 sNavigation = '<div class="tmlistnav-%s">\n' % sWhere;
586 sNavigation += ' <table class="tmlistnavtab">\n' \
587 ' <tr>\n';
588 dParams = self._oDisp.getParameters();
589 dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage] = cEntriesPerPage;
590 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo;
591 if tsNow is not None:
592 dParams[WuiDispatcherBase.ksParamEffectiveDate] = tsNow;
593
594 # Prev and combo box in one cell. Both inside the form for formatting reasons.
595 sNavigation += ' <td>\n' \
596 ' <form name="ChangeLogEntriesPerPageForm" method="GET">\n'
597
598 # Prev
599 if iPageNo > 0:
600 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo - 1;
601 sNavigation += '<a href="?%s#tmchangelog">Previous</a>\n' \
602 % (webutils.encodeUrlParams(dParams),);
603 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo;
604 else:
605 sNavigation += 'Previous\n';
606
607 # Entries per page selector.
608 del dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage];
609 sNavigation += '&nbsp; &nbsp;\n' \
610 ' <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \
611 'this.options[this.selectedIndex].value + \'#tmchangelog\';" ' \
612 'title="Max change log entries per page">\n' \
613 % (WuiDispatcherBase.ksParamChangeLogEntriesPerPage,
614 webutils.encodeUrlParams(dParams),
615 WuiDispatcherBase.ksParamChangeLogEntriesPerPage);
616 dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage] = cEntriesPerPage;
617
618 for iEntriesPerPage in [2, 4, 8, 16, 32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 8192]:
619 sNavigation += ' <option value="%d" %s>%d entries per page</option>\n' \
620 % ( iEntriesPerPage,
621 'selected="selected"' if iEntriesPerPage == cEntriesPerPage else '',
622 iEntriesPerPage );
623 sNavigation += ' </select>\n';
624
625 # End of cell (and form).
626 sNavigation += ' </form>\n' \
627 ' </td>\n';
628
629 # Next
630 if fMoreEntries:
631 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo + 1;
632 sNavigation += ' <td><a href="?%s#tmchangelog">Next</a></td>\n' \
633 % (webutils.encodeUrlParams(dParams),);
634 else:
635 sNavigation += ' <td>Next</td>\n';
636
637 sNavigation += ' </tr>\n' \
638 ' </table>\n' \
639 '</div>\n';
640 return sNavigation;
641
642 def setRedirectTo(self, sRedirectTo):
643 """
644 For setting the hidden redirect-to field.
645 """
646 self._sRedirectTo = sRedirectTo;
647 return True;
648
649 def showChangeLog(self, aoEntries, fMoreEntries, iPageNo, cEntriesPerPage, tsNow, fShowNavigation = True):
650 """
651 Render the change log, returning raw HTML.
652 aoEntries is an array of ChangeLogEntry.
653 """
654 sContent = '\n' \
655 '<hr>\n' \
656 '<div id="tmchangelog">\n' \
657 ' <h3>Change Log </h3>\n';
658 if fShowNavigation:
659 sContent += self._showChangeLogNavi(fMoreEntries, iPageNo, cEntriesPerPage, tsNow, 'top');
660 sContent += ' <table class="tmtable tmchangelog">\n' \
661 ' <thead class="tmheader">' \
662 ' <tr>' \
663 ' <th rowspan="2">When</th>\n' \
664 ' <th rowspan="2">Expire (excl)</th>\n' \
665 ' <th colspan="3">Changes</th>\n' \
666 ' </tr>\n' \
667 ' <tr>\n' \
668 ' <th>Attribute</th>\n' \
669 ' <th>Old value</th>\n' \
670 ' <th>New value</th>\n' \
671 ' </tr>\n' \
672 ' </thead>\n' \
673 ' <tbody>\n';
674
675 if self._sMode == self.ksMode_Show:
676 sUrl = self._oDisp.getUrlNoParams();
677 dParams = self._oDisp.getParameters();
678 else:
679 sUrl = None;
680 dParams = None;
681
682 for iEntry, _ in enumerate(aoEntries):
683 sContent += self.formatChangeLogEntry(aoEntries, iEntry, sUrl, dParams);
684
685 sContent += ' </tbody>\n' \
686 ' </table>\n';
687 if fShowNavigation and len(aoEntries) >= 8:
688 sContent += self._showChangeLogNavi(fMoreEntries, iPageNo, cEntriesPerPage, tsNow, 'bottom');
689 sContent += '</div>\n\n';
690 return sContent;
691
692 def _generateTopRowFormActions(self, oData):
693 """
694 Returns a list of WuiTmLinks.
695 """
696 aoActions = [];
697 if self._sMode == self.ksMode_Show and self._fEditable:
698 # Remove _idGen and effective date since we're always editing the current data,
699 # and make sure the primary ID is present. Also remove change log stuff.
700 dParams = self._oDisp.getParameters();
701 if hasattr(oData, 'ksIdGenAttr'):
702 sIdGenParam = getattr(oData, 'ksParam_' + oData.ksIdGenAttr);
703 if sIdGenParam in dParams:
704 del dParams[sIdGenParam];
705 for sParam in [ WuiDispatcherBase.ksParamEffectiveDate, ] + list(WuiDispatcherBase.kasChangeLogParams):
706 if sParam in dParams:
707 del dParams[sParam];
708 dParams[getattr(oData, 'ksParam_' + oData.ksIdAttr)] = getattr(oData, oData.ksIdAttr);
709
710 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Edit');
711 aoActions.append(WuiTmLink('Edit', '', dParams));
712
713 # Add clone operation if available. This uses the same data selection as for showing details. No change log.
714 if hasattr(self._oDisp, self._sActionBase + 'Clone'):
715 dParams = self._oDisp.getParameters();
716 for sParam in WuiDispatcherBase.kasChangeLogParams:
717 if sParam in dParams:
718 del dParams[sParam];
719 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Clone');
720 aoActions.append(WuiTmLink('Clone', '', dParams));
721
722 elif self._sMode == self.ksMode_Edit:
723 # Details views the details at a given time, so we need either idGen or an effecive date + regular id.
724 dParams = {};
725 if hasattr(oData, 'ksIdGenAttr'):
726 sIdGenParam = getattr(oData, 'ksParam_' + oData.ksIdGenAttr);
727 dParams[sIdGenParam] = getattr(oData, oData.ksIdGenAttr);
728 elif hasattr(oData, 'tsEffective'):
729 dParams[WuiDispatcherBase.ksParamEffectiveDate] = oData.tsEffective;
730 dParams[getattr(oData, 'ksParam_' + oData.ksIdAttr)] = getattr(oData, oData.ksIdAttr);
731 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Details');
732 aoActions.append(WuiTmLink('Details', '', dParams));
733
734 # Add delete operation if available.
735 if hasattr(self._oDisp, self._sActionBase + 'DoRemove'):
736 dParams = self._oDisp.getParameters();
737 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'DoRemove');
738 dParams[getattr(oData, 'ksParam_' + oData.ksIdAttr)] = getattr(oData, oData.ksIdAttr);
739 aoActions.append(WuiTmLink('Delete', '', dParams, sConfirm = "Are you absolutely sure?"));
740
741 return aoActions;
742
743 def showForm(self, dErrors = None, sErrorMsg = None):
744 """
745 Render the form.
746 """
747 oForm = WuiHlpForm(self._sId,
748 '?' + webutils.encodeUrlParams({WuiDispatcherBase.ksParamAction: self._sSubmitAction}),
749 dErrors if dErrors is not None else dict(),
750 fReadOnly = self._sMode == self.ksMode_Show);
751
752 self._oData.convertToParamNull();
753
754 # If form cannot be constructed due to some reason we
755 # need to show this reason
756 try:
757 self._populateForm(oForm, self._oData);
758 if self._sRedirectTo is not None:
759 oForm.addTextHidden(self._oDisp.ksParamRedirectTo, self._sRedirectTo);
760 except WuiException as oXcpt:
761 sContent = unicode(oXcpt)
762 else:
763 sContent = oForm.finalize();
764
765 # Add any post form content.
766 atPostFormContent = self._generatePostFormContent(self._oData);
767 if atPostFormContent:
768 for iSection, tSection in enumerate(atPostFormContent):
769 (sSectionTitle, sSectionContent) = tSection;
770 sContent += u'<div id="postform-%d" class="tmformpostsection">\n' % (iSection,);
771 if sSectionTitle:
772 sContent += '<h3 class="tmformpostheader">%s</h3>\n' % (webutils.escapeElem(sSectionTitle),);
773 sContent += u' <div id="postform-%d-content" class="tmformpostcontent">\n' % (iSection,);
774 sContent += sSectionContent;
775 sContent += u' </div>\n' \
776 u'</div>\n';
777
778 # Add action to the top.
779 aoActions = self._generateTopRowFormActions(self._oData);
780 if aoActions:
781 sActionLinks = '<p>%s</p>' % (' '.join(unicode(oLink) for oLink in aoActions));
782 sContent = sActionLinks + sContent;
783
784 # Add error info to the top.
785 if sErrorMsg is not None:
786 sContent = '<p class="tmerrormsg">' + webutils.escapeElem(sErrorMsg) + '</p>\n' + sContent;
787
788 return (self._sTitle, sContent);
789
790 def getListOfItems(self, asListItems = tuple(), asSelectedItems = tuple()):
791 """
792 Format generic list which should be used by HTML form
793 """
794 aoRet = []
795 for sListItem in asListItems:
796 fEnabled = sListItem in asSelectedItems;
797 aoRet.append((sListItem, fEnabled, sListItem))
798 return aoRet
799
800
801class WuiListContentBase(WuiContentBase):
802 """
803 Base for the list content classes.
804 """
805
806 def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, # pylint: disable=too-many-arguments
807 sId = None, fnDPrint = None, oDisp = None, aiSelectedSortColumns = None, fTimeNavigation = True):
808 WuiContentBase.__init__(self, fnDPrint = fnDPrint, oDisp = oDisp);
809 self._aoEntries = aoEntries; ## @todo should replace this with a Logic object and define methods for querying.
810 self._iPage = iPage;
811 self._cItemsPerPage = cItemsPerPage;
812 self._tsEffectiveDate = tsEffectiveDate;
813 self._fTimeNavigation = fTimeNavigation;
814 self._sTitle = sTitle; assert len(sTitle) > 1;
815 if sId is None:
816 sId = sTitle.strip().replace(' ', '').lower();
817 assert sId.strip();
818 self._sId = sId;
819 self._asColumnHeaders = [];
820 self._asColumnAttribs = [];
821 self._aaiColumnSorting = []; ##< list of list of integers
822 self._aiSelectedSortColumns = aiSelectedSortColumns; ##< list of integers
823
824 def _formatCommentCell(self, sComment, cMaxLines = 3, cchMaxLine = 63):
825 """
826 Helper functions for formatting comment cell.
827 Returns None or WuiRawHtml instance.
828 """
829 # Nothing to do for empty comments.
830 if sComment is None:
831 return None;
832 sComment = sComment.strip();
833 if not sComment:
834 return None;
835
836 # Restrict the text if necessary, making the whole text available thru mouse-over.
837 ## @todo this would be better done by java script or smth, so it could automatically adjust to the table size.
838 if len(sComment) > cchMaxLine or sComment.count('\n') >= cMaxLines:
839 sShortHtml = '';
840 for iLine, sLine in enumerate(sComment.split('\n')):
841 if iLine >= cMaxLines:
842 break;
843 if iLine > 0:
844 sShortHtml += '<br>\n';
845 if len(sLine) > cchMaxLine:
846 sShortHtml += webutils.escapeElem(sLine[:(cchMaxLine - 3)]);
847 sShortHtml += '...';
848 else:
849 sShortHtml += webutils.escapeElem(sLine);
850 return WuiRawHtml('<span class="tmcomment" title="%s">%s</span>' % (webutils.escapeAttr(sComment), sShortHtml,));
851
852 return WuiRawHtml('<span class="tmcomment">%s</span>' % (webutils.escapeElem(sComment).replace('\n', '<br>'),));
853
854 def _formatListEntry(self, iEntry):
855 """
856 Formats the specified list entry as a list of column values.
857 Returns HTML for a table row.
858
859 The child class really need to override this!
860 """
861 # ASSUMES ModelDataBase children.
862 asRet = [];
863 for sAttr in self._aoEntries[0].getDataAttributes():
864 asRet.append(getattr(self._aoEntries[iEntry], sAttr));
865 return asRet;
866
867 def _formatListEntryHtml(self, iEntry):
868 """
869 Formats the specified list entry as HTML.
870 Returns HTML for a table row.
871
872 The child class can override this to
873 """
874 if (iEntry + 1) & 1:
875 sRow = u' <tr class="tmodd">\n';
876 else:
877 sRow = u' <tr class="tmeven">\n';
878
879 aoValues = self._formatListEntry(iEntry);
880 assert len(aoValues) == len(self._asColumnHeaders), '%s vs %s' % (len(aoValues), len(self._asColumnHeaders));
881
882 for i, _ in enumerate(aoValues):
883 if i < len(self._asColumnAttribs) and self._asColumnAttribs[i]:
884 sRow += u' <td ' + self._asColumnAttribs[i] + '>';
885 else:
886 sRow += u' <td>';
887
888 if isinstance(aoValues[i], WuiHtmlBase):
889 sRow += aoValues[i].toHtml();
890 elif isinstance(aoValues[i], list):
891 if aoValues[i]:
892 for oElement in aoValues[i]:
893 if isinstance(oElement, WuiHtmlBase):
894 sRow += oElement.toHtml();
895 elif db.isDbTimestamp(oElement):
896 sRow += webutils.escapeElem(self.formatTsShort(oElement));
897 else:
898 sRow += webutils.escapeElem(unicode(oElement));
899 sRow += ' ';
900 elif db.isDbTimestamp(aoValues[i]):
901 sRow += webutils.escapeElem(self.formatTsShort(aoValues[i]));
902 elif db.isDbInterval(aoValues[i]):
903 sRow += webutils.escapeElem(self.formatIntervalShort(aoValues[i]));
904 elif aoValues[i] is not None:
905 sRow += webutils.escapeElem(unicode(aoValues[i]));
906
907 sRow += u'</td>\n';
908
909 return sRow + u' </tr>\n';
910
911 @staticmethod
912 def generateTimeNavigationComboBox(sWhere, dParams, tsEffective):
913 """
914 Generates the HTML for the xxxx ago combo box form.
915 """
916 sNavigation = '<form name="TmTimeNavCombo-%s" method="GET">\n' % (sWhere,);
917 sNavigation += ' <select name="%s" onchange="window.location=' % (WuiDispatcherBase.ksParamEffectiveDate);
918 sNavigation += '\'?%s&%s=\' + ' % (webutils.encodeUrlParams(dParams), WuiDispatcherBase.ksParamEffectiveDate)
919 sNavigation += 'this.options[this.selectedIndex].value;" title="Effective date">\n';
920
921 aoWayBackPoints = [
922 ('+0000-00-00 00:00:00.00', 'Now', ' title="Present Day. Present Time."'), # lain :)
923
924 ('-0000-00-00 01:00:00.00', '1 hour ago', ''),
925 ('-0000-00-00 02:00:00.00', '2 hours ago', ''),
926 ('-0000-00-00 03:00:00.00', '3 hours ago', ''),
927
928 ('-0000-00-01 00:00:00.00', '1 day ago', ''),
929 ('-0000-00-02 00:00:00.00', '2 days ago', ''),
930 ('-0000-00-03 00:00:00.00', '3 days ago', ''),
931
932 ('-0000-00-07 00:00:00.00', '1 week ago', ''),
933 ('-0000-00-14 00:00:00.00', '2 weeks ago', ''),
934 ('-0000-00-21 00:00:00.00', '3 weeks ago', ''),
935
936 ('-0000-01-00 00:00:00.00', '1 month ago', ''),
937 ('-0000-02-00 00:00:00.00', '2 months ago', ''),
938 ('-0000-03-00 00:00:00.00', '3 months ago', ''),
939 ('-0000-04-00 00:00:00.00', '4 months ago', ''),
940 ('-0000-05-00 00:00:00.00', '5 months ago', ''),
941 ('-0000-06-00 00:00:00.00', 'Half a year ago', ''),
942
943 ('-0001-00-00 00:00:00.00', '1 year ago', ''),
944 ]
945 fSelected = False;
946 for sTimestamp, sWayBackPointCaption, sExtraAttrs in aoWayBackPoints:
947 if sTimestamp == tsEffective:
948 fSelected = True;
949 sNavigation += ' <option value="%s"%s%s>%s</option>\n' \
950 % (webutils.quoteUrl(sTimestamp),
951 ' selected="selected"' if sTimestamp == tsEffective else '',
952 sExtraAttrs, sWayBackPointCaption, );
953 if not fSelected and tsEffective != '':
954 sNavigation += ' <option value="%s" selected>%s</option>\n' \
955 % (webutils.quoteUrl(tsEffective), WuiContentBase.formatTsShort(tsEffective))
956
957 sNavigation += ' </select>\n' \
958 '</form>\n';
959 return sNavigation;
960
961 @staticmethod
962 def generateTimeNavigationDateTime(sWhere, dParams, sNow):
963 """
964 Generates HTML for a form with date + time input fields.
965
966 Note! Modifies dParams!
967 """
968
969 #
970 # Date + time input fields. We use a java script helper to combine the two
971 # into a hidden field as there is no portable datetime input field type.
972 #
973 sNavigation = '<form method="get" action="?" onchange="timeNavigationUpdateHiddenEffDate(this,\'%s\')">' % (sWhere,);
974 if sNow is None:
975 sNow = utils.getIsoTimestamp();
976 else:
977 sNow = utils.normalizeIsoTimestampToZulu(sNow);
978 asSplit = sNow.split('T');
979 sNavigation += ' <input type="date" value="%s" id="EffDate%s"/> ' % (asSplit[0], sWhere, );
980 sNavigation += ' <input type="time" value="%s" id="EffTime%s"/> ' % (asSplit[1][:8], sWhere,);
981 sNavigation += ' <input type="hidden" name="%s" value="%s" id="EffDateTime%s"/>' \
982 % (WuiDispatcherBase.ksParamEffectiveDate, webutils.escapeAttr(sNow), sWhere);
983 for sKey in dParams:
984 sNavigation += ' <input type="hidden" name="%s" value="%s"/>' \
985 % (webutils.escapeAttr(sKey), webutils.escapeAttrToStr(dParams[sKey]));
986 sNavigation += ' <input type="submit" value="Set"/>\n' \
987 '</form>\n';
988 return sNavigation;
989
990 ## @todo move to better place! WuiMain uses it.
991 @staticmethod
992 def generateTimeNavigation(sWhere, dParams, tsEffectiveAbs, sPreamble = '', sPostamble = '', fKeepPageNo = False):
993 """
994 Returns HTML for time navigation.
995
996 Note! Modifies dParams!
997 Note! Views without a need for a timescale just stubs this method.
998 """
999 sNavigation = '<div class="tmtimenav-%s tmtimenav">%s' % (sWhere, sPreamble,);
1000
1001 #
1002 # Prepare the URL parameters.
1003 #
1004 if WuiDispatcherBase.ksParamPageNo in dParams: # Forget about page No when changing a period
1005 del dParams[WuiDispatcherBase.ksParamPageNo]
1006 if not fKeepPageNo and WuiDispatcherBase.ksParamEffectiveDate in dParams:
1007 tsEffectiveParam = dParams[WuiDispatcherBase.ksParamEffectiveDate];
1008 del dParams[WuiDispatcherBase.ksParamEffectiveDate];
1009 else:
1010 tsEffectiveParam = ''
1011
1012 #
1013 # Generate the individual parts.
1014 #
1015 sNavigation += WuiListContentBase.generateTimeNavigationDateTime(sWhere, dParams, tsEffectiveAbs);
1016 sNavigation += WuiListContentBase.generateTimeNavigationComboBox(sWhere, dParams, tsEffectiveParam);
1017
1018 sNavigation += '%s</div>' % (sPostamble,);
1019 return sNavigation;
1020
1021 def _generateTimeNavigation(self, sWhere, sPreamble = '', sPostamble = ''):
1022 """
1023 Returns HTML for time navigation.
1024
1025 Note! Views without a need for a timescale just stubs this method.
1026 """
1027 return self.generateTimeNavigation(sWhere, self._oDisp.getParameters(), self._oDisp.getEffectiveDateParam(),
1028 sPreamble, sPostamble)
1029
1030 @staticmethod
1031 def generateItemPerPageSelector(sWhere, dParams, cCurItemsPerPage):
1032 """
1033 Generate HTML code for items per page selector.
1034 Note! Modifies dParams!
1035 """
1036
1037 # Drop the current page count parameter.
1038 if WuiDispatcherBase.ksParamItemsPerPage in dParams:
1039 del dParams[WuiDispatcherBase.ksParamItemsPerPage];
1040
1041 # Remove the current page number.
1042 if WuiDispatcherBase.ksParamPageNo in dParams:
1043 del dParams[WuiDispatcherBase.ksParamPageNo];
1044
1045 sHtmlItemsPerPageSelector = '<form name="TmItemsPerPageForm-%s" method="GET" class="tmitemsperpage-%s tmitemsperpage">\n'\
1046 ' <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \
1047 'this.options[this.selectedIndex].value;" title="Max items per page">\n' \
1048 % (sWhere, WuiDispatcherBase.ksParamItemsPerPage, sWhere,
1049 webutils.encodeUrlParams(dParams),
1050 WuiDispatcherBase.ksParamItemsPerPage)
1051
1052 acItemsPerPage = [16, 32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096];
1053 for cItemsPerPage in acItemsPerPage:
1054 sHtmlItemsPerPageSelector += ' <option value="%d" %s>%d per page</option>\n' \
1055 % (cItemsPerPage,
1056 'selected="selected"' if cItemsPerPage == cCurItemsPerPage else '',
1057 cItemsPerPage)
1058 sHtmlItemsPerPageSelector += ' </select>\n' \
1059 '</form>\n';
1060
1061 return sHtmlItemsPerPageSelector
1062
1063
1064 def _generateNavigation(self, sWhere):
1065 """
1066 Return HTML for navigation.
1067 """
1068
1069 #
1070 # ASSUMES the dispatcher/controller code fetches one entry more than
1071 # needed to fill the page to indicate further records.
1072 #
1073 sNavigation = '<div class="tmlistnav-%s">\n' % sWhere;
1074 sNavigation += ' <table class="tmlistnavtab">\n' \
1075 ' <tr>\n';
1076 dParams = self._oDisp.getParameters();
1077 dParams[WuiDispatcherBase.ksParamItemsPerPage] = self._cItemsPerPage;
1078 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage;
1079 if self._tsEffectiveDate is not None:
1080 dParams[WuiDispatcherBase.ksParamEffectiveDate] = self._tsEffectiveDate;
1081
1082 # Prev
1083 if self._iPage > 0:
1084 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage - 1;
1085 sNavigation += ' <td align="left"><a href="?%s">Previous</a></td>\n' % (webutils.encodeUrlParams(dParams),);
1086 else:
1087 sNavigation += ' <td></td>\n';
1088
1089 # Time scale.
1090 if self._fTimeNavigation:
1091 sNavigation += '<td align="center" class="tmtimenav">';
1092 sNavigation += self._generateTimeNavigation(sWhere);
1093 sNavigation += '</td>';
1094
1095 # page count and next.
1096 sNavigation += '<td align="right" class="tmnextanditemsperpage">\n';
1097
1098 if len(self._aoEntries) > self._cItemsPerPage:
1099 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage + 1;
1100 sNavigation += ' <a href="?%s">Next</a>\n' % (webutils.encodeUrlParams(dParams),);
1101 sNavigation += self.generateItemPerPageSelector(sWhere, dParams, self._cItemsPerPage);
1102 sNavigation += '</td>\n';
1103 sNavigation += ' </tr>\n' \
1104 ' </table>\n' \
1105 '</div>\n';
1106 return sNavigation;
1107
1108 def _checkSortingByColumnAscending(self, aiColumns):
1109 """
1110 Checks if we're sorting by this column.
1111
1112 Returns 0 if not sorting by this, negative if descending, positive if ascending. The
1113 value indicates the priority (nearer to 0 is higher).
1114 """
1115 if len(aiColumns) <= len(self._aiSelectedSortColumns):
1116 aiColumns = list(aiColumns);
1117 aiNegColumns = list([-i for i in aiColumns]);
1118 i = 0;
1119 while i + len(aiColumns) <= len(self._aiSelectedSortColumns):
1120 aiSub = list(self._aiSelectedSortColumns[i : i + len(aiColumns)]);
1121 if aiSub == aiColumns:
1122 return 1 + i;
1123 if aiSub == aiNegColumns:
1124 return -1 - i;
1125 i += 1;
1126 return 0;
1127
1128 def _generateTableHeaders(self):
1129 """
1130 Generate table headers.
1131 Returns raw html string.
1132 Overridable.
1133 """
1134
1135 sHtml = ' <thead class="tmheader"><tr>';
1136 for iHeader, oHeader in enumerate(self._asColumnHeaders):
1137 if isinstance(oHeader, WuiHtmlBase):
1138 sHtml += '<th>' + oHeader.toHtml() + '</th>';
1139 elif iHeader < len(self._aaiColumnSorting) and self._aaiColumnSorting[iHeader] is not None:
1140 sHtml += '<th>'
1141 iSorting = self._checkSortingByColumnAscending(self._aaiColumnSorting[iHeader]);
1142 if iSorting > 0:
1143 sDirection = '&nbsp;&#x25b4;' if iSorting == 1 else '<small>&nbsp;&#x25b5;</small>';
1144 sSortParams = ','.join([str(-i) for i in self._aaiColumnSorting[iHeader]]);
1145 else:
1146 sDirection = '';
1147 if iSorting < 0:
1148 sDirection = '&nbsp;&#x25be;' if iSorting == -1 else '<small>&nbsp;&#x25bf;</small>'
1149 sSortParams = ','.join([str(i) for i in self._aaiColumnSorting[iHeader]]);
1150 sHtml += '<a href="javascript:ahrefActionSortByColumns(\'%s\',[%s]);">' \
1151 % (WuiDispatcherBase.ksParamSortColumns, sSortParams);
1152 sHtml += webutils.escapeElem(oHeader) + '</a>' + sDirection + '</th>';
1153 else:
1154 sHtml += '<th>' + webutils.escapeElem(oHeader) + '</th>';
1155 sHtml += '</tr><thead>\n';
1156 return sHtml
1157
1158 def _generateTable(self):
1159 """
1160 show worker that just generates the table.
1161 """
1162
1163 #
1164 # Create a table.
1165 # If no colum headers are provided, fall back on database field
1166 # names, ASSUMING that the entries are ModelDataBase children.
1167 # Note! the cellspacing is for IE8.
1168 #
1169 sPageBody = '<table class="tmtable" id="' + self._sId + '" cellspacing="0">\n';
1170
1171 if not self._asColumnHeaders:
1172 self._asColumnHeaders = self._aoEntries[0].getDataAttributes();
1173
1174 sPageBody += self._generateTableHeaders();
1175
1176 #
1177 # Format the body and close the table.
1178 #
1179 sPageBody += ' <tbody>\n';
1180 for iEntry in range(min(len(self._aoEntries), self._cItemsPerPage)):
1181 sPageBody += self._formatListEntryHtml(iEntry);
1182 sPageBody += ' </tbody>\n' \
1183 '</table>\n';
1184 return sPageBody;
1185
1186 def _composeTitle(self):
1187 """Composes the title string (return value)."""
1188 sTitle = self._sTitle;
1189 if self._iPage != 0:
1190 sTitle += ' (page ' + unicode(self._iPage + 1) + ')'
1191 if self._tsEffectiveDate is not None:
1192 sTitle += ' as per ' + unicode(self.formatTsShort(self._tsEffectiveDate));
1193 return sTitle;
1194
1195
1196 def show(self, fShowNavigation = True):
1197 """
1198 Displays the list.
1199 Returns (Title, HTML) on success, raises exception on error.
1200 """
1201
1202 sPageBody = ''
1203 if fShowNavigation:
1204 sPageBody += self._generateNavigation('top');
1205
1206 if self._aoEntries:
1207 sPageBody += self._generateTable();
1208 if fShowNavigation:
1209 sPageBody += self._generateNavigation('bottom');
1210 else:
1211 sPageBody += '<p>No entries.</p>'
1212
1213 return (self._composeTitle(), sPageBody);
1214
1215
1216class WuiListContentWithActionBase(WuiListContentBase):
1217 """
1218 Base for the list content with action classes.
1219 """
1220
1221 def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, # pylint: disable=too-many-arguments
1222 sId = None, fnDPrint = None, oDisp = None, aiSelectedSortColumns = None):
1223 WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, sId = sId,
1224 fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns);
1225 self._aoActions = None; # List of [ oValue, sText, sHover ] provided by the child class.
1226 self._sAction = None; # Set by the child class.
1227 self._sCheckboxName = None; # Set by the child class.
1228 self._asColumnHeaders = [ WuiRawHtml('<input type="checkbox" onClick="toggle%s(this)">'
1229 % ('' if sId is None else sId)), ];
1230 self._asColumnAttribs = [ 'align="center"', ];
1231 self._aaiColumnSorting = [ None, ];
1232
1233 def _getCheckBoxColumn(self, iEntry, sValue):
1234 """
1235 Used by _formatListEntry implementations, returns a WuiRawHtmlBase object.
1236 """
1237 _ = iEntry;
1238 return WuiRawHtml('<input type="checkbox" name="%s" value="%s">'
1239 % (webutils.escapeAttr(self._sCheckboxName), webutils.escapeAttr(unicode(sValue))));
1240
1241 def show(self, fShowNavigation=True):
1242 """
1243 Displays the list.
1244 Returns (Title, HTML) on success, raises exception on error.
1245 """
1246 assert self._aoActions is not None;
1247 assert self._sAction is not None;
1248
1249 sPageBody = '<script language="JavaScript">\n' \
1250 'function toggle%s(oSource) {\n' \
1251 ' aoCheckboxes = document.getElementsByName(\'%s\');\n' \
1252 ' for(var i in aoCheckboxes)\n' \
1253 ' aoCheckboxes[i].checked = oSource.checked;\n' \
1254 '}\n' \
1255 '</script>\n' \
1256 % ('' if self._sId is None else self._sId, self._sCheckboxName,);
1257 if fShowNavigation:
1258 sPageBody += self._generateNavigation('top');
1259 if self._aoEntries:
1260
1261 sPageBody += '<form action="?%s" method="post" class="tmlistactionform">\n' \
1262 % (webutils.encodeUrlParams({WuiDispatcherBase.ksParamAction: self._sAction,}),);
1263 sPageBody += self._generateTable();
1264
1265 sPageBody += ' <label>Actions</label>\n' \
1266 ' <select name="%s" id="%s-action-combo" class="tmlistactionform-combo">\n' \
1267 % (webutils.escapeAttr(WuiDispatcherBase.ksParamListAction), webutils.escapeAttr(self._sId),);
1268 for oValue, sText, _ in self._aoActions:
1269 sPageBody += ' <option value="%s">%s</option>\n' \
1270 % (webutils.escapeAttr(unicode(oValue)), webutils.escapeElem(sText), );
1271 sPageBody += ' </select>\n';
1272 sPageBody += ' <input type="submit"></input>\n';
1273 sPageBody += '</form>\n';
1274 if fShowNavigation:
1275 sPageBody += self._generateNavigation('bottom');
1276 else:
1277 sPageBody += '<p>No entries.</p>'
1278
1279 return (self._composeTitle(), sPageBody);
1280
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