VirtualBox

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

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

TestManager/changelogs: Made the tsEffective value a link (typically works).

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 51.9 KB
 
1# -*- coding: utf-8 -*-
2# $Id: wuicontentbase.py 83391 2020-03-24 16:59:31Z 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: 83391 $"
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, sUrl, dParams):
488 """
489 Formats one change log entry into one or more HTML table rows.
490
491 The sUrl and dParams arguments are used to construct links to historical
492 data using the tsEffective value. If no links wanted, they'll both be None.
493
494 Note! The parameters are given as array + index in case someone wishes
495 to access adjacent entries later in order to generate better
496 change descriptions.
497 """
498 oEntry = aoEntries[iEntry];
499
500 # Turn the effective date into a URL if we can:
501 if sUrl:
502 dParams[WuiDispatcherBase.ksParamEffectiveDate] = oEntry.tsEffective;
503 sEffective = WuiLinkBase(WuiFormContentBase.formatTsShort(oEntry.tsEffective), sUrl,
504 dParams, fBracketed = False).toHtml();
505 else:
506 sEffective = webutils.escapeElem(WuiFormContentBase.formatTsShort(oEntry.tsEffective))
507
508 # The primary row.
509 sRowClass = 'tmodd' if (iEntry + 1) & 1 else 'tmeven';
510 sContent = ' <tr class="%s">\n' \
511 ' <td rowspan="%d">%s</td>\n' \
512 ' <td rowspan="%d">%s</td>\n' \
513 ' <td colspan="3">%s%s</td>\n' \
514 ' </tr>\n' \
515 % ( sRowClass,
516 len(oEntry.aoChanges) + 1, sEffective,
517 len(oEntry.aoChanges) + 1, webutils.escapeElem(WuiFormContentBase.formatTsShort(oEntry.tsExpire)),
518 WuiFormContentBase._guessChangeLogEntryDescription(aoEntries, iEntry),
519 ' '.join(oLink.toHtml() for oLink in WuiFormContentBase._calcChangeLogEntryLinks(aoEntries, iEntry)),);
520
521 # Additional rows for each changed attribute.
522 j = 0;
523 for oChange in oEntry.aoChanges:
524 if isinstance(oChange, AttributeChangeEntryPre):
525 sContent += ' <tr class="%s%s"><td>%s</td>'\
526 '<td><div class="tdpre"><pre>%s</pre></div></td>' \
527 '<td><div class="tdpre"><pre>%s</pre></div></td></tr>\n' \
528 % ( sRowClass, 'odd' if j & 1 else 'even',
529 webutils.escapeElem(oChange.sAttr),
530 webutils.escapeElem(oChange.sOldText),
531 webutils.escapeElem(oChange.sNewText), );
532 else:
533 sContent += ' <tr class="%s%s"><td>%s</td><td>%s</td><td>%s</td></tr>\n' \
534 % ( sRowClass, 'odd' if j & 1 else 'even',
535 webutils.escapeElem(oChange.sAttr),
536 webutils.escapeElem(oChange.sOldText),
537 webutils.escapeElem(oChange.sNewText), );
538 j += 1;
539
540 return sContent;
541
542 def _showChangeLogNavi(self, fMoreEntries, iPageNo, cEntriesPerPage, tsNow, sWhere):
543 """
544 Returns the HTML for the change log navigator.
545 Note! See also _generateNavigation.
546 """
547 sNavigation = '<div class="tmlistnav-%s">\n' % sWhere;
548 sNavigation += ' <table class="tmlistnavtab">\n' \
549 ' <tr>\n';
550 dParams = self._oDisp.getParameters();
551 dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage] = cEntriesPerPage;
552 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo;
553 if tsNow is not None:
554 dParams[WuiDispatcherBase.ksParamEffectiveDate] = tsNow;
555
556 # Prev and combo box in one cell. Both inside the form for formatting reasons.
557 sNavigation += ' <td align="left">\n' \
558 ' <form name="ChangeLogEntriesPerPageForm" method="GET">\n'
559
560 # Prev
561 if iPageNo > 0:
562 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo - 1;
563 sNavigation += '<a href="?%s#tmchangelog">Previous</a>\n' \
564 % (webutils.encodeUrlParams(dParams),);
565 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo;
566 else:
567 sNavigation += 'Previous\n';
568
569 # Entries per page selector.
570 del dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage];
571 sNavigation += '&nbsp; &nbsp;\n' \
572 ' <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \
573 'this.options[this.selectedIndex].value + \'#tmchangelog\';" ' \
574 'title="Max change log entries per page">\n' \
575 % (WuiDispatcherBase.ksParamChangeLogEntriesPerPage,
576 webutils.encodeUrlParams(dParams),
577 WuiDispatcherBase.ksParamChangeLogEntriesPerPage);
578 dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage] = cEntriesPerPage;
579
580 for iEntriesPerPage in [2, 4, 8, 16, 32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 8192]:
581 sNavigation += ' <option value="%d" %s>%d entries per page</option>\n' \
582 % ( iEntriesPerPage,
583 'selected="selected"' if iEntriesPerPage == cEntriesPerPage else '',
584 iEntriesPerPage );
585 sNavigation += ' </select>\n';
586
587 # End of cell (and form).
588 sNavigation += ' </form>\n' \
589 ' </td>\n';
590
591 # Next
592 if fMoreEntries:
593 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo + 1;
594 sNavigation += ' <td align="right"><a href="?%s#tmchangelog">Next</a></td>\n' \
595 % (webutils.encodeUrlParams(dParams),);
596 else:
597 sNavigation += ' <td align="right">Next</td>\n';
598
599 sNavigation += ' </tr>\n' \
600 ' </table>\n' \
601 '</div>\n';
602 return sNavigation;
603
604 def setRedirectTo(self, sRedirectTo):
605 """
606 For setting the hidden redirect-to field.
607 """
608 self._sRedirectTo = sRedirectTo;
609 return True;
610
611 def showChangeLog(self, aoEntries, fMoreEntries, iPageNo, cEntriesPerPage, tsNow, fShowNavigation = True):
612 """
613 Render the change log, returning raw HTML.
614 aoEntries is an array of ChangeLogEntry.
615 """
616 sContent = '\n' \
617 '<hr>\n' \
618 '<div id="tmchangelog">\n' \
619 ' <h3>Change Log </h3>\n';
620 if fShowNavigation:
621 sContent += self._showChangeLogNavi(fMoreEntries, iPageNo, cEntriesPerPage, tsNow, 'top');
622 sContent += ' <table class="tmtable tmchangelog">\n' \
623 ' <thead class="tmheader">' \
624 ' <tr>' \
625 ' <th rowspan="2">When</th>\n' \
626 ' <th rowspan="2">Expire (excl)</th>\n' \
627 ' <th colspan="3">Changes</th>\n' \
628 ' </tr>\n' \
629 ' <tr>\n' \
630 ' <th>Attribute</th>\n' \
631 ' <th>Old value</th>\n' \
632 ' <th>New value</th>\n' \
633 ' </tr>\n' \
634 ' </thead>\n' \
635 ' <tbody>\n';
636
637 if self._sMode == self.ksMode_Show:
638 sUrl = self._oDisp.getUrlNoParams();
639 dParams = self._oDisp.getParameters();
640 else:
641 sUrl = None;
642 dParams = None;
643
644 for iEntry, _ in enumerate(aoEntries):
645 sContent += self.formatChangeLogEntry(aoEntries, iEntry, sUrl, dParams);
646
647 sContent += ' <tbody>\n' \
648 ' </table>\n';
649 if fShowNavigation and len(aoEntries) >= 8:
650 sContent += self._showChangeLogNavi(fMoreEntries, iPageNo, cEntriesPerPage, tsNow, 'bottom');
651 sContent += '</div>\n\n';
652 return sContent;
653
654 def _generateTopRowFormActions(self, oData):
655 """
656 Returns a list of WuiTmLinks.
657 """
658 aoActions = [];
659 if self._sMode == self.ksMode_Show and self._fEditable:
660 # Remove _idGen and effective date since we're always editing the current data,
661 # and make sure the primary ID is present.
662 dParams = self._oDisp.getParameters();
663 if hasattr(oData, 'ksIdGenAttr'):
664 sIdGenParam = getattr(oData, 'ksParam_' + oData.ksIdGenAttr);
665 if sIdGenParam in dParams:
666 del dParams[sIdGenParam];
667 if WuiDispatcherBase.ksParamEffectiveDate in dParams:
668 del dParams[WuiDispatcherBase.ksParamEffectiveDate];
669 dParams[getattr(oData, 'ksParam_' + oData.ksIdAttr)] = getattr(oData, oData.ksIdAttr);
670
671 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Edit');
672 aoActions.append(WuiTmLink('Edit', '', dParams));
673
674 # Add clone operation if available. This uses the same data selection as for showing details.
675 if hasattr(self._oDisp, self._sActionBase + 'Clone'):
676 dParams = self._oDisp.getParameters();
677 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Clone');
678 aoActions.append(WuiTmLink('Clone', '', dParams));
679
680 elif self._sMode == self.ksMode_Edit:
681 # Details views the details at a given time, so we need either idGen or an effecive date + regular id.
682 dParams = {};
683 if hasattr(oData, 'ksIdGenAttr'):
684 sIdGenParam = getattr(oData, 'ksParam_' + oData.ksIdGenAttr);
685 dParams[sIdGenParam] = getattr(oData, oData.ksIdGenAttr);
686 elif hasattr(oData, 'tsEffective'):
687 dParams[WuiDispatcherBase.ksParamEffectiveDate] = oData.tsEffective;
688 dParams[getattr(oData, 'ksParam_' + oData.ksIdAttr)] = getattr(oData, oData.ksIdAttr);
689 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Details');
690 aoActions.append(WuiTmLink('Details', '', dParams));
691
692 # Add delete operation if available.
693 if hasattr(self._oDisp, self._sActionBase + 'DoRemove'):
694 dParams = self._oDisp.getParameters();
695 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'DoRemove');
696 dParams[getattr(oData, 'ksParam_' + oData.ksIdAttr)] = getattr(oData, oData.ksIdAttr);
697 aoActions.append(WuiTmLink('Delete', '', dParams, sConfirm = "Are you absolutely sure?"));
698
699 return aoActions;
700
701 def showForm(self, dErrors = None, sErrorMsg = None):
702 """
703 Render the form.
704 """
705 oForm = WuiHlpForm(self._sId,
706 '?' + webutils.encodeUrlParams({WuiDispatcherBase.ksParamAction: self._sSubmitAction}),
707 dErrors if dErrors is not None else dict(),
708 fReadOnly = self._sMode == self.ksMode_Show);
709
710 self._oData.convertToParamNull();
711
712 # If form cannot be constructed due to some reason we
713 # need to show this reason
714 try:
715 self._populateForm(oForm, self._oData);
716 if self._sRedirectTo is not None:
717 oForm.addTextHidden(self._oDisp.ksParamRedirectTo, self._sRedirectTo);
718 except WuiException as oXcpt:
719 sContent = unicode(oXcpt)
720 else:
721 sContent = oForm.finalize();
722
723 # Add any post form content.
724 atPostFormContent = self._generatePostFormContent(self._oData);
725 if atPostFormContent:
726 for iSection, tSection in enumerate(atPostFormContent):
727 (sSectionTitle, sSectionContent) = tSection;
728 sContent += u'<div id="postform-%d" class="tmformpostsection">\n' % (iSection,);
729 if sSectionTitle:
730 sContent += '<h3 class="tmformpostheader">%s</h3>\n' % (webutils.escapeElem(sSectionTitle),);
731 sContent += u' <div id="postform-%d-content" class="tmformpostcontent">\n' % (iSection,);
732 sContent += sSectionContent;
733 sContent += u' </div>\n' \
734 u'</div>\n';
735
736 # Add action to the top.
737 aoActions = self._generateTopRowFormActions(self._oData);
738 if aoActions:
739 sActionLinks = '<p>%s</p>' % (' '.join(unicode(oLink) for oLink in aoActions));
740 sContent = sActionLinks + sContent;
741
742 # Add error info to the top.
743 if sErrorMsg is not None:
744 sContent = '<p class="tmerrormsg">' + webutils.escapeElem(sErrorMsg) + '</p>\n' + sContent;
745
746 return (self._sTitle, sContent);
747
748 def getListOfItems(self, asListItems = tuple(), asSelectedItems = tuple()):
749 """
750 Format generic list which should be used by HTML form
751 """
752 aoRet = []
753 for sListItem in asListItems:
754 fEnabled = sListItem in asSelectedItems;
755 aoRet.append((sListItem, fEnabled, sListItem))
756 return aoRet
757
758
759class WuiListContentBase(WuiContentBase):
760 """
761 Base for the list content classes.
762 """
763
764 def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, # pylint: disable=too-many-arguments
765 sId = None, fnDPrint = None, oDisp = None, aiSelectedSortColumns = None, fTimeNavigation = True):
766 WuiContentBase.__init__(self, fnDPrint = fnDPrint, oDisp = oDisp);
767 self._aoEntries = aoEntries; ## @todo should replace this with a Logic object and define methods for querying.
768 self._iPage = iPage;
769 self._cItemsPerPage = cItemsPerPage;
770 self._tsEffectiveDate = tsEffectiveDate;
771 self._fTimeNavigation = fTimeNavigation;
772 self._sTitle = sTitle; assert len(sTitle) > 1;
773 if sId is None:
774 sId = sTitle.strip().replace(' ', '').lower();
775 assert sId.strip();
776 self._sId = sId;
777 self._asColumnHeaders = [];
778 self._asColumnAttribs = [];
779 self._aaiColumnSorting = []; ##< list of list of integers
780 self._aiSelectedSortColumns = aiSelectedSortColumns; ##< list of integers
781
782 def _formatCommentCell(self, sComment, cMaxLines = 3, cchMaxLine = 63):
783 """
784 Helper functions for formatting comment cell.
785 Returns None or WuiRawHtml instance.
786 """
787 # Nothing to do for empty comments.
788 if sComment is None:
789 return None;
790 sComment = sComment.strip();
791 if not sComment:
792 return None;
793
794 # Restrict the text if necessary, making the whole text available thru mouse-over.
795 ## @todo this would be better done by java script or smth, so it could automatically adjust to the table size.
796 if len(sComment) > cchMaxLine or sComment.count('\n') >= cMaxLines:
797 sShortHtml = '';
798 for iLine, sLine in enumerate(sComment.split('\n')):
799 if iLine >= cMaxLines:
800 break;
801 if iLine > 0:
802 sShortHtml += '<br>\n';
803 if len(sLine) > cchMaxLine:
804 sShortHtml += webutils.escapeElem(sLine[:(cchMaxLine - 3)]);
805 sShortHtml += '...';
806 else:
807 sShortHtml += webutils.escapeElem(sLine);
808 return WuiRawHtml('<span class="tmcomment" title="%s">%s</span>' % (webutils.escapeAttr(sComment), sShortHtml,));
809
810 return WuiRawHtml('<span class="tmcomment">%s</span>' % (webutils.escapeElem(sComment).replace('\n', '<br>'),));
811
812 def _formatListEntry(self, iEntry):
813 """
814 Formats the specified list entry as a list of column values.
815 Returns HTML for a table row.
816
817 The child class really need to override this!
818 """
819 # ASSUMES ModelDataBase children.
820 asRet = [];
821 for sAttr in self._aoEntries[0].getDataAttributes():
822 asRet.append(getattr(self._aoEntries[iEntry], sAttr));
823 return asRet;
824
825 def _formatListEntryHtml(self, iEntry):
826 """
827 Formats the specified list entry as HTML.
828 Returns HTML for a table row.
829
830 The child class can override this to
831 """
832 if (iEntry + 1) & 1:
833 sRow = u' <tr class="tmodd">\n';
834 else:
835 sRow = u' <tr class="tmeven">\n';
836
837 aoValues = self._formatListEntry(iEntry);
838 assert len(aoValues) == len(self._asColumnHeaders), '%s vs %s' % (len(aoValues), len(self._asColumnHeaders));
839
840 for i, _ in enumerate(aoValues):
841 if i < len(self._asColumnAttribs) and self._asColumnAttribs[i]:
842 sRow += u' <td ' + self._asColumnAttribs[i] + '>';
843 else:
844 sRow += u' <td>';
845
846 if isinstance(aoValues[i], WuiHtmlBase):
847 sRow += aoValues[i].toHtml();
848 elif isinstance(aoValues[i], list):
849 if aoValues[i]:
850 for oElement in aoValues[i]:
851 if isinstance(oElement, WuiHtmlBase):
852 sRow += oElement.toHtml();
853 elif db.isDbTimestamp(oElement):
854 sRow += webutils.escapeElem(self.formatTsShort(oElement));
855 else:
856 sRow += webutils.escapeElem(unicode(oElement));
857 sRow += ' ';
858 elif db.isDbTimestamp(aoValues[i]):
859 sRow += webutils.escapeElem(self.formatTsShort(aoValues[i]));
860 elif db.isDbInterval(aoValues[i]):
861 sRow += webutils.escapeElem(self.formatIntervalShort(aoValues[i]));
862 elif aoValues[i] is not None:
863 sRow += webutils.escapeElem(unicode(aoValues[i]));
864
865 sRow += u'</td>\n';
866
867 return sRow + u' </tr>\n';
868
869 @staticmethod
870 def generateTimeNavigationComboBox(sWhere, dParams, tsEffective):
871 """
872 Generates the HTML for the xxxx ago combo box form.
873 """
874 sNavigation = '<form name="TmTimeNavCombo-%s" method="GET">\n' % (sWhere,);
875 sNavigation += ' <select name="%s" onchange="window.location=' % (WuiDispatcherBase.ksParamEffectiveDate);
876 sNavigation += '\'?%s&%s=\' + ' % (webutils.encodeUrlParams(dParams), WuiDispatcherBase.ksParamEffectiveDate)
877 sNavigation += 'this.options[this.selectedIndex].value;" title="Effective date">\n';
878
879 aoWayBackPoints = [
880 ('+0000-00-00 00:00:00.00', 'Now', ' title="Present Day. Present Time."'), # lain :)
881
882 ('-0000-00-00 01:00:00.00', '1 hour ago', ''),
883 ('-0000-00-00 02:00:00.00', '2 hours ago', ''),
884 ('-0000-00-00 03:00:00.00', '3 hours ago', ''),
885
886 ('-0000-00-01 00:00:00.00', '1 day ago', ''),
887 ('-0000-00-02 00:00:00.00', '2 days ago', ''),
888 ('-0000-00-03 00:00:00.00', '3 days ago', ''),
889
890 ('-0000-00-07 00:00:00.00', '1 week ago', ''),
891 ('-0000-00-14 00:00:00.00', '2 weeks ago', ''),
892 ('-0000-00-21 00:00:00.00', '3 weeks ago', ''),
893
894 ('-0000-01-00 00:00:00.00', '1 month ago', ''),
895 ('-0000-02-00 00:00:00.00', '2 months ago', ''),
896 ('-0000-03-00 00:00:00.00', '3 months ago', ''),
897 ('-0000-04-00 00:00:00.00', '4 months ago', ''),
898 ('-0000-05-00 00:00:00.00', '5 months ago', ''),
899 ('-0000-06-00 00:00:00.00', 'Half a year ago', ''),
900
901 ('-0001-00-00 00:00:00.00', '1 year ago', ''),
902 ]
903 fSelected = False;
904 for sTimestamp, sWayBackPointCaption, sExtraAttrs in aoWayBackPoints:
905 if sTimestamp == tsEffective:
906 fSelected = True;
907 sNavigation += ' <option value="%s"%s%s>%s</option>\n' \
908 % (webutils.quoteUrl(sTimestamp),
909 ' selected="selected"' if sTimestamp == tsEffective else '',
910 sExtraAttrs, sWayBackPointCaption, );
911 if not fSelected and tsEffective != '':
912 sNavigation += ' <option value="%s" selected>%s</option>\n' \
913 % (webutils.quoteUrl(tsEffective), WuiContentBase.formatTsShort(tsEffective))
914
915 sNavigation += ' </select>\n' \
916 '</form>\n';
917 return sNavigation;
918
919 @staticmethod
920 def generateTimeNavigationDateTime(sWhere, dParams, sNow):
921 """
922 Generates HTML for a form with date + time input fields.
923
924 Note! Modifies dParams!
925 """
926
927 #
928 # Date + time input fields. We use a java script helper to combine the two
929 # into a hidden field as there is no portable datetime input field type.
930 #
931 sNavigation = '<form method="get" action="?" onchange="timeNavigationUpdateHiddenEffDate(this,\'%s\')">' % (sWhere,);
932 if sNow is None:
933 sNow = utils.getIsoTimestamp();
934 else:
935 sNow = utils.normalizeIsoTimestampToZulu(sNow);
936 asSplit = sNow.split('T');
937 sNavigation += ' <input type="date" value="%s" id="EffDate%s"/> ' % (asSplit[0], sWhere, );
938 sNavigation += ' <input type="time" value="%s" id="EffTime%s"/> ' % (asSplit[1][:8], sWhere,);
939 sNavigation += ' <input type="hidden" name="%s" value="%s" id="EffDateTime%s"/>' \
940 % (WuiDispatcherBase.ksParamEffectiveDate, webutils.escapeAttr(sNow), sWhere);
941 for sKey in dParams:
942 sNavigation += ' <input type="hidden" name="%s" value="%s"/>' \
943 % (webutils.escapeAttr(sKey), webutils.escapeAttrToStr(dParams[sKey]));
944 sNavigation += ' <input type="submit" value="Set"/>\n' \
945 '</form>\n';
946 return sNavigation;
947
948 ## @todo move to better place! WuiMain uses it.
949 @staticmethod
950 def generateTimeNavigation(sWhere, dParams, tsEffectiveAbs, sPreamble = '', sPostamble = '', fKeepPageNo = False):
951 """
952 Returns HTML for time navigation.
953
954 Note! Modifies dParams!
955 Note! Views without a need for a timescale just stubs this method.
956 """
957 sNavigation = '<div class="tmtimenav-%s tmtimenav">%s' % (sWhere, sPreamble,);
958
959 #
960 # Prepare the URL parameters.
961 #
962 if WuiDispatcherBase.ksParamPageNo in dParams: # Forget about page No when changing a period
963 del dParams[WuiDispatcherBase.ksParamPageNo]
964 if not fKeepPageNo and WuiDispatcherBase.ksParamEffectiveDate in dParams:
965 tsEffectiveParam = dParams[WuiDispatcherBase.ksParamEffectiveDate];
966 del dParams[WuiDispatcherBase.ksParamEffectiveDate];
967 else:
968 tsEffectiveParam = ''
969
970 #
971 # Generate the individual parts.
972 #
973 sNavigation += WuiListContentBase.generateTimeNavigationDateTime(sWhere, dParams, tsEffectiveAbs);
974 sNavigation += WuiListContentBase.generateTimeNavigationComboBox(sWhere, dParams, tsEffectiveParam);
975
976 sNavigation += '%s</div>' % (sPostamble,);
977 return sNavigation;
978
979 def _generateTimeNavigation(self, sWhere, sPreamble = '', sPostamble = ''):
980 """
981 Returns HTML for time navigation.
982
983 Note! Views without a need for a timescale just stubs this method.
984 """
985 return self.generateTimeNavigation(sWhere, self._oDisp.getParameters(), self._oDisp.getEffectiveDateParam(),
986 sPreamble, sPostamble)
987
988 @staticmethod
989 def generateItemPerPageSelector(sWhere, dParams, cCurItemsPerPage):
990 """
991 Generate HTML code for items per page selector.
992 Note! Modifies dParams!
993 """
994
995 # Drop the current page count parameter.
996 if WuiDispatcherBase.ksParamItemsPerPage in dParams:
997 del dParams[WuiDispatcherBase.ksParamItemsPerPage];
998
999 # Remove the current page number.
1000 if WuiDispatcherBase.ksParamPageNo in dParams:
1001 del dParams[WuiDispatcherBase.ksParamPageNo];
1002
1003 sHtmlItemsPerPageSelector = '<form name="TmItemsPerPageForm-%s" method="GET" class="tmitemsperpage-%s tmitemsperpage">\n'\
1004 ' <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \
1005 'this.options[this.selectedIndex].value;" title="Max items per page">\n' \
1006 % (sWhere, WuiDispatcherBase.ksParamItemsPerPage, sWhere,
1007 webutils.encodeUrlParams(dParams),
1008 WuiDispatcherBase.ksParamItemsPerPage)
1009
1010 acItemsPerPage = [16, 32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096];
1011 for cItemsPerPage in acItemsPerPage:
1012 sHtmlItemsPerPageSelector += ' <option value="%d" %s>%d per page</option>\n' \
1013 % (cItemsPerPage,
1014 'selected="selected"' if cItemsPerPage == cCurItemsPerPage else '',
1015 cItemsPerPage)
1016 sHtmlItemsPerPageSelector += ' </select>\n' \
1017 '</form>\n';
1018
1019 return sHtmlItemsPerPageSelector
1020
1021
1022 def _generateNavigation(self, sWhere):
1023 """
1024 Return HTML for navigation.
1025 """
1026
1027 #
1028 # ASSUMES the dispatcher/controller code fetches one entry more than
1029 # needed to fill the page to indicate further records.
1030 #
1031 sNavigation = '<div class="tmlistnav-%s">\n' % sWhere;
1032 sNavigation += ' <table class="tmlistnavtab">\n' \
1033 ' <tr>\n';
1034 dParams = self._oDisp.getParameters();
1035 dParams[WuiDispatcherBase.ksParamItemsPerPage] = self._cItemsPerPage;
1036 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage;
1037 if self._tsEffectiveDate is not None:
1038 dParams[WuiDispatcherBase.ksParamEffectiveDate] = self._tsEffectiveDate;
1039
1040 # Prev
1041 if self._iPage > 0:
1042 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage - 1;
1043 sNavigation += ' <td align="left"><a href="?%s">Previous</a></td>\n' % (webutils.encodeUrlParams(dParams),);
1044 else:
1045 sNavigation += ' <td></td>\n';
1046
1047 # Time scale.
1048 if self._fTimeNavigation:
1049 sNavigation += '<td align="center" class="tmtimenav">';
1050 sNavigation += self._generateTimeNavigation(sWhere);
1051 sNavigation += '</td>';
1052
1053 # page count and next.
1054 sNavigation += '<td align="right" class="tmnextanditemsperpage">\n';
1055
1056 if len(self._aoEntries) > self._cItemsPerPage:
1057 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage + 1;
1058 sNavigation += ' <a href="?%s">Next</a>\n' % (webutils.encodeUrlParams(dParams),);
1059 sNavigation += self.generateItemPerPageSelector(sWhere, dParams, self._cItemsPerPage);
1060 sNavigation += '</td>\n';
1061 sNavigation += ' </tr>\n' \
1062 ' </table>\n' \
1063 '</div>\n';
1064 return sNavigation;
1065
1066 def _checkSortingByColumnAscending(self, aiColumns):
1067 """
1068 Checks if we're sorting by this column.
1069
1070 Returns 0 if not sorting by this, negative if descending, positive if ascending. The
1071 value indicates the priority (nearer to 0 is higher).
1072 """
1073 if len(aiColumns) <= len(self._aiSelectedSortColumns):
1074 aiColumns = list(aiColumns);
1075 aiNegColumns = list([-i for i in aiColumns]);
1076 i = 0;
1077 while i + len(aiColumns) <= len(self._aiSelectedSortColumns):
1078 aiSub = list(self._aiSelectedSortColumns[i : i + len(aiColumns)]);
1079 if aiSub == aiColumns:
1080 return 1 + i;
1081 if aiSub == aiNegColumns:
1082 return -1 - i;
1083 i += 1;
1084 return 0;
1085
1086 def _generateTableHeaders(self):
1087 """
1088 Generate table headers.
1089 Returns raw html string.
1090 Overridable.
1091 """
1092
1093 sHtml = ' <thead class="tmheader"><tr>';
1094 for iHeader, oHeader in enumerate(self._asColumnHeaders):
1095 if isinstance(oHeader, WuiHtmlBase):
1096 sHtml += '<th>' + oHeader.toHtml() + '</th>';
1097 elif iHeader < len(self._aaiColumnSorting) and self._aaiColumnSorting[iHeader] is not None:
1098 sHtml += '<th>'
1099 iSorting = self._checkSortingByColumnAscending(self._aaiColumnSorting[iHeader]);
1100 if iSorting > 0:
1101 sDirection = '&nbsp;&#x25b4;' if iSorting == 1 else '<small>&nbsp;&#x25b5;</small>';
1102 sSortParams = ','.join([str(-i) for i in self._aaiColumnSorting[iHeader]]);
1103 else:
1104 sDirection = '';
1105 if iSorting < 0:
1106 sDirection = '&nbsp;&#x25be;' if iSorting == -1 else '<small>&nbsp;&#x25bf;</small>'
1107 sSortParams = ','.join([str(i) for i in self._aaiColumnSorting[iHeader]]);
1108 sHtml += '<a href="javascript:ahrefActionSortByColumns(\'%s\',[%s]);">' \
1109 % (WuiDispatcherBase.ksParamSortColumns, sSortParams);
1110 sHtml += webutils.escapeElem(oHeader) + '</a>' + sDirection + '</th>';
1111 else:
1112 sHtml += '<th>' + webutils.escapeElem(oHeader) + '</th>';
1113 sHtml += '</tr><thead>\n';
1114 return sHtml
1115
1116 def _generateTable(self):
1117 """
1118 show worker that just generates the table.
1119 """
1120
1121 #
1122 # Create a table.
1123 # If no colum headers are provided, fall back on database field
1124 # names, ASSUMING that the entries are ModelDataBase children.
1125 # Note! the cellspacing is for IE8.
1126 #
1127 sPageBody = '<table class="tmtable" id="' + self._sId + '" cellspacing="0">\n';
1128
1129 if not self._asColumnHeaders:
1130 self._asColumnHeaders = self._aoEntries[0].getDataAttributes();
1131
1132 sPageBody += self._generateTableHeaders();
1133
1134 #
1135 # Format the body and close the table.
1136 #
1137 sPageBody += ' <tbody>\n';
1138 for iEntry in range(min(len(self._aoEntries), self._cItemsPerPage)):
1139 sPageBody += self._formatListEntryHtml(iEntry);
1140 sPageBody += ' </tbody>\n' \
1141 '</table>\n';
1142 return sPageBody;
1143
1144 def _composeTitle(self):
1145 """Composes the title string (return value)."""
1146 sTitle = self._sTitle;
1147 if self._iPage != 0:
1148 sTitle += ' (page ' + unicode(self._iPage + 1) + ')'
1149 if self._tsEffectiveDate is not None:
1150 sTitle += ' as per ' + unicode(self.formatTsShort(self._tsEffectiveDate));
1151 return sTitle;
1152
1153
1154 def show(self, fShowNavigation = True):
1155 """
1156 Displays the list.
1157 Returns (Title, HTML) on success, raises exception on error.
1158 """
1159
1160 sPageBody = ''
1161 if fShowNavigation:
1162 sPageBody += self._generateNavigation('top');
1163
1164 if self._aoEntries:
1165 sPageBody += self._generateTable();
1166 if fShowNavigation:
1167 sPageBody += self._generateNavigation('bottom');
1168 else:
1169 sPageBody += '<p>No entries.</p>'
1170
1171 return (self._composeTitle(), sPageBody);
1172
1173
1174class WuiListContentWithActionBase(WuiListContentBase):
1175 """
1176 Base for the list content with action classes.
1177 """
1178
1179 def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, # pylint: disable=too-many-arguments
1180 sId = None, fnDPrint = None, oDisp = None, aiSelectedSortColumns = None):
1181 WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, sId = sId,
1182 fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns);
1183 self._aoActions = None; # List of [ oValue, sText, sHover ] provided by the child class.
1184 self._sAction = None; # Set by the child class.
1185 self._sCheckboxName = None; # Set by the child class.
1186 self._asColumnHeaders = [ WuiRawHtml('<input type="checkbox" onClick="toggle%s(this)">'
1187 % ('' if sId is None else sId)), ];
1188 self._asColumnAttribs = [ 'align="center"', ];
1189 self._aaiColumnSorting = [ None, ];
1190
1191 def _getCheckBoxColumn(self, iEntry, sValue):
1192 """
1193 Used by _formatListEntry implementations, returns a WuiRawHtmlBase object.
1194 """
1195 _ = iEntry;
1196 return WuiRawHtml('<input type="checkbox" name="%s" value="%s">'
1197 % (webutils.escapeAttr(self._sCheckboxName), webutils.escapeAttr(unicode(sValue))));
1198
1199 def show(self, fShowNavigation=True):
1200 """
1201 Displays the list.
1202 Returns (Title, HTML) on success, raises exception on error.
1203 """
1204 assert self._aoActions is not None;
1205 assert self._sAction is not None;
1206
1207 sPageBody = '<script language="JavaScript">\n' \
1208 'function toggle%s(oSource) {\n' \
1209 ' aoCheckboxes = document.getElementsByName(\'%s\');\n' \
1210 ' for(var i in aoCheckboxes)\n' \
1211 ' aoCheckboxes[i].checked = oSource.checked;\n' \
1212 '}\n' \
1213 '</script>\n' \
1214 % ('' if self._sId is None else self._sId, self._sCheckboxName,);
1215 if fShowNavigation:
1216 sPageBody += self._generateNavigation('top');
1217 if self._aoEntries:
1218
1219 sPageBody += '<form action="?%s" method="post" class="tmlistactionform">\n' \
1220 % (webutils.encodeUrlParams({WuiDispatcherBase.ksParamAction: self._sAction,}),);
1221 sPageBody += self._generateTable();
1222
1223 sPageBody += ' <label>Actions</label>\n' \
1224 ' <select name="%s" id="%s-action-combo" class="tmlistactionform-combo">\n' \
1225 % (webutils.escapeAttr(WuiDispatcherBase.ksParamListAction), webutils.escapeAttr(self._sId),);
1226 for oValue, sText, _ in self._aoActions:
1227 sPageBody += ' <option value="%s">%s</option>\n' \
1228 % (webutils.escapeAttr(unicode(oValue)), webutils.escapeElem(sText), );
1229 sPageBody += ' </select>\n';
1230 sPageBody += ' <input type="submit"></input>\n';
1231 sPageBody += '</form>\n';
1232 if fShowNavigation:
1233 sPageBody += self._generateNavigation('bottom');
1234 else:
1235 sPageBody += '<p>No entries.</p>'
1236
1237 return (self._composeTitle(), sPageBody);
1238
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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