VirtualBox

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

最後變更 在這個檔案從83390是 83366,由 vboxsync 提交於 5 年 前

TestManager/wui: burn fix.

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

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