VirtualBox

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

Last change on this file since 57761 was 56807, checked in by vboxsync, 9 years ago

unwanted commit.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 35.8 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: wuicontentbase.py 56807 2015-07-05 16:58:48Z vboxsync $
3
4"""
5Test Manager Web-UI - Content Base Classes.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2015 Oracle Corporation
11
12This file is part of VirtualBox Open Source Edition (OSE), as
13available from http://www.virtualbox.org. This file is free software;
14you can redistribute it and/or modify it under the terms of the GNU
15General Public License (GPL) as published by the Free Software
16Foundation, in version 2 as it comes in the "COPYING" file of the
17VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
20The contents of this file may alternatively be used under the terms
21of the Common Development and Distribution License Version 1.0
22(CDDL) only, as it comes in the "COPYING.CDDL" file of the
23VirtualBox OSE distribution, in which case the provisions of the
24CDDL are applicable instead of those of the GPL.
25
26You may elect to license modified versions of this file under the
27terms and conditions of either the GPL or the CDDL or both.
28"""
29__version__ = "$Revision: 56807 $"
30
31
32# Standard python imports.
33import copy;
34
35# Validation Kit imports.
36from common import webutils;
37from testmanager import config;
38from testmanager.webui.wuibase import WuiDispatcherBase, WuiException
39from testmanager.webui.wuihlpform import WuiHlpForm;
40from testmanager.core import db;
41
42
43class WuiHtmlBase(object): # pylint: disable=R0903
44 """
45 Base class for HTML objects.
46 """
47
48 def __init__(self):
49 """Dummy init to shut up pylint."""
50 pass;
51
52 def toHtml(self):
53 """
54 Must be overridden by sub-classes.
55 """
56 assert False;
57 return '';
58
59 def __str__(self):
60 """ String representation is HTML, simplifying formatting and such. """
61 return self.toHtml();
62
63
64class WuiLinkBase(WuiHtmlBase): # pylint: disable=R0903
65 """
66 For passing links from WuiListContentBase._formatListEntry.
67 """
68
69 def __init__(self, sName, sUrlBase, dParams = None, sConfirm = None, sTitle = None,
70 sFragmentId = None, fBracketed = True, sExtraAttrs = ''):
71 WuiHtmlBase.__init__(self);
72 self.sName = sName
73 self.sUrl = sUrlBase
74 self.sConfirm = sConfirm;
75 self.sTitle = sTitle;
76 self.fBracketed = fBracketed;
77 self.sExtraAttrs = sExtraAttrs;
78
79 if dParams is not None and len(dParams) > 0:
80 # Do some massaging of None arguments.
81 dParams = dict(dParams);
82 for sKey in dParams:
83 if dParams[sKey] is None:
84 dParams[sKey] = '';
85 self.sUrl += '?' + webutils.encodeUrlParams(dParams)
86
87 if sFragmentId is not None:
88 self.sUrl += '#' + sFragmentId;
89
90 def setBracketed(self, fBracketed):
91 """Changes the bracketing style."""
92 self.fBracketed = fBracketed;
93 return True;
94
95 def toHtml(self):
96 """
97 Returns a simple HTML anchor element.
98 """
99 sExtraAttrs = self.sExtraAttrs;
100 if self.sConfirm is not None:
101 sExtraAttrs += 'onclick=\'return confirm("%s");\' ' % (webutils.escapeAttr(self.sConfirm),);
102 if self.sTitle is not None:
103 sExtraAttrs += 'title="%s" ' % (webutils.escapeAttr(self.sTitle),);
104 if len(sExtraAttrs) > 0 and sExtraAttrs[-1] != ' ':
105 sExtraAttrs += ' ';
106
107 sFmt = '[<a %shref="%s">%s</a>]';
108 if not self.fBracketed:
109 sFmt = '<a %shref="%s">%s</a>';
110 return sFmt % (sExtraAttrs, webutils.escapeAttr(self.sUrl), webutils.escapeElem(self.sName));
111
112class WuiTmLink(WuiLinkBase): # pylint: disable=R0903
113 """ Local link to the test manager. """
114
115 kdDbgParams = None;
116
117 def __init__(self, sName, sUrlBase, dParams = None, sConfirm = None, sTitle = None,
118 sFragmentId = None, fBracketed = True):
119
120 # Add debug parameters if necessary.
121 if self.kdDbgParams is not None and len(self.kdDbgParams) > 0:
122 if dParams is None or len(dParams) == 0:
123 dParams = dict(self.kdDbgParams);
124 else:
125 dParams = dict(dParams);
126 for sKey in self.kdDbgParams:
127 if sKey not in dParams:
128 dParams[sKey] = self.kdDbgParams[sKey];
129
130 WuiLinkBase.__init__(self, sName, sUrlBase, dParams, sConfirm, sTitle, sFragmentId, fBracketed);
131
132
133class WuiSvnLink(WuiLinkBase): # pylint: disable=R0903
134 """
135 For linking to a SVN revision.
136 """
137 def __init__(self, iRevision, sName = None, fBracketed = True, sExtraAttrs = ''):
138 if sName is None:
139 sName = 'r%s' % (iRevision,);
140 WuiLinkBase.__init__(self, sName, config.g_ksTracLogUrlPrefix, { 'rev': iRevision,},
141 fBracketed = fBracketed, sExtraAttrs = sExtraAttrs);
142
143class WuiSvnLinkWithTooltip(WuiSvnLink): # pylint: disable=R0903
144 """
145 For linking to a SVN revision with changelog tooltip.
146 """
147 def __init__(self, iRevision, sRepository, sName = None, fBracketed = True):
148 sExtraAttrs = ' onmouseover="return svnHistoryTooltipShow(event,\'%s\',%s);" onmouseout="return tooltipHide();"' \
149 % ( sRepository, iRevision, );
150 WuiSvnLink.__init__(self, iRevision, sName = sName, fBracketed = fBracketed, sExtraAttrs = sExtraAttrs);
151
152class WuiBuildLogLink(WuiLinkBase):
153 """
154 For linking to a build log.
155 """
156 def __init__(self, sUrl, sName = None, fBracketed = True):
157 assert sUrl is not None; assert len(sUrl) > 0;
158 if sName is None:
159 sName = 'Build log';
160 if not webutils.hasSchema(sUrl):
161 WuiLinkBase.__init__(self, sName, config.g_ksBuildLogUrlPrefix + sUrl, fBracketed = fBracketed);
162 else:
163 WuiLinkBase.__init__(self, sName, sUrl, fBracketed = fBracketed);
164
165class WuiRawHtml(WuiHtmlBase): # pylint: disable=R0903
166 """
167 For passing raw html from WuiListContentBase._formatListEntry.
168 """
169 def __init__(self, sHtml):
170 self.sHtml = sHtml;
171 WuiHtmlBase.__init__(self);
172
173 def toHtml(self):
174 return self.sHtml;
175
176class WuiSpanText(WuiRawHtml): # pylint: disable=R0903
177 """
178 Outputs the given text within a span of the given CSS class.
179 """
180 def __init__(self, sSpanClass, sText):
181 WuiRawHtml.__init__(self,
182 u'<span class="%s">%s</span>'
183 % (webutils.escapeAttr(sSpanClass), webutils.escapeElem(sText),));
184
185
186class WuiContentBase(object): # pylint: disable=R0903
187 """
188 Base for the content classes.
189 """
190
191 ## The text/symbol for a very short edit link.
192 ksShortEditLink = u'\u270D'
193 ## The text/symbol for a very short details link.
194 ksShortDetailsLink = u'\u2318'
195
196
197 def __init__(self, fnDPrint = None, oDisp = None):
198 self._oDisp = oDisp; # WuiDispatcherBase.
199 self._fnDPrint = fnDPrint;
200 if fnDPrint is None and oDisp is not None:
201 self._fnDPrint = oDisp.dprint;
202
203 def dprint(self, sText):
204 """ Debug printing. """
205 if self._fnDPrint:
206 self._fnDPrint(sText);
207
208 def formatTsShort(self, oTs):
209 """
210 Formats a timestamp (db rep) into a short form.
211 """
212 oTsZulu = db.dbTimestampToZuluDatetime(oTs);
213 sTs = oTsZulu.strftime('%Y-%m-%d %H:%M:%SZ');
214 return unicode(sTs).replace('-', u'\u2011').replace(' ', u'\u00a0');
215
216 def formatIntervalShort(self, oInterval):
217 """
218 Formats an interval (db rep) into a short form.
219 """
220 # default formatting for negative intervals.
221 if oInterval.days < 0:
222 return str(oInterval);
223
224 # Figure the hour, min and sec counts.
225 cHours = oInterval.seconds / 3600;
226 cMinutes = (oInterval.seconds % 3600) / 60;
227 cSeconds = oInterval.seconds - cHours * 3600 - cMinutes * 60;
228
229 # Tailor formatting to the interval length.
230 if oInterval.days > 0:
231 if oInterval.days > 1:
232 return '%d days, %d:%02d:%02d' % (oInterval.days, cHours, cMinutes, cSeconds);
233 return '1 day, %d:%02d:%02d' % (cHours, cMinutes, cSeconds);
234 if cMinutes > 0 or cSeconds >= 30 or cHours > 0:
235 return '%d:%02d:%02d' % (cHours, cMinutes, cSeconds);
236 if cSeconds >= 10:
237 return '%d.%ds' % (cSeconds, oInterval.microseconds / 100000);
238 if cSeconds > 0:
239 return '%d.%02ds' % (cSeconds, oInterval.microseconds / 10000);
240 return '%d ms' % (oInterval.microseconds / 1000,);
241
242 @staticmethod
243 def genericPageWalker(iCurItem, cItems, sHrefFmt, cWidth = 11, iBase = 1, sItemName = 'page'):
244 """
245 Generic page walker generator.
246
247 sHrefFmt has three %s sequences:
248 1. The first is the page number link parameter (0-based).
249 2. The title text, iBase-based number or text.
250 3. The link text, iBase-based number or text.
251 """
252
253 # Calc display range.
254 iStart = 0 if iCurItem - cWidth / 2 <= cWidth / 4 else iCurItem - cWidth / 2;
255 iEnd = iStart + cWidth;
256 if iEnd > cItems:
257 iEnd = cItems;
258 if cItems > cWidth:
259 iStart = cItems - cWidth;
260
261 sHtml = u'';
262
263 # Previous page (using << >> because &laquo; and &raquo are too tiny).
264 if iCurItem > 0:
265 sHtml += '%s&nbsp;&nbsp;' % sHrefFmt % (iCurItem - 1, 'previous ' + sItemName, '&lt;&lt;');
266 else:
267 sHtml += '&lt;&lt;&nbsp;&nbsp;';
268
269 # 1 2 3 4...
270 if iStart > 0:
271 sHtml += '%s&nbsp; ... &nbsp;\n' % (sHrefFmt % (0, 'first %s' % (sItemName,), 0 + iBase),);
272
273 sHtml += '&nbsp;\n'.join(sHrefFmt % (i, '%s %d' % (sItemName, i + iBase), i + iBase) if i != iCurItem
274 else unicode(i + iBase)
275 for i in range(iStart, iEnd));
276 if iEnd < cItems:
277 sHtml += '&nbsp; ... &nbsp;%s\n' % (sHrefFmt % (cItems - 1, 'last %s' % (sItemName,), cItems - 1 + iBase));
278
279 # Next page.
280 if iCurItem + 1 < cItems:
281 sHtml += '&nbsp;&nbsp;%s' % sHrefFmt % (iCurItem + 1, 'next ' + sItemName, '&gt;&gt;');
282 else:
283 sHtml += '&nbsp;&nbsp;&gt;&gt;';
284
285 return sHtml;
286
287class WuiSingleContentBase(WuiContentBase): # pylint: disable=R0903
288 """
289 Base for the content classes working on a single data object (oData).
290 """
291 def __init__(self, oData, oDisp = None, fnDPrint = None):
292 WuiContentBase.__init__(self, oDisp = oDisp, fnDPrint = fnDPrint);
293 self._oData = oData; # Usually ModelDataBase.
294
295
296class WuiFormContentBase(WuiSingleContentBase): # pylint: disable=R0903
297 """
298 Base class for simple input form content classes (single data object).
299 """
300
301 ## @name Form mode.
302 ## @{
303 ksMode_Add = 'add';
304 ksMode_Edit = 'edit';
305 ksMode_Show = 'show';
306 ## @}
307
308 ## Default action mappings.
309 kdSubmitActionMappings = {
310 ksMode_Add: 'AddPost',
311 ksMode_Edit: 'EditPost',
312 };
313
314 def __init__(self, oData, sMode, sCoreName, oDisp, sTitle, sId = None, fEditable = True, sSubmitAction = None):
315 WuiSingleContentBase.__init__(self, copy.copy(oData), oDisp);
316 assert sMode in [self.ksMode_Add, self.ksMode_Edit, self.ksMode_Show];
317 assert len(sTitle) > 1;
318 assert sId is None or len(sId) > 0;
319
320 self._sMode = sMode;
321 self._sCoreName = sCoreName;
322 self._sActionBase = 'ksAction' + sCoreName;
323 self._sTitle = sTitle;
324 self._sId = sId if sId is not None else (type(oData).__name__.lower() + 'form');
325 self._fEditable = fEditable;
326 self._sSubmitAction = sSubmitAction;
327 if sSubmitAction is None and sMode != self.ksMode_Show:
328 self._sSubmitAction = getattr(oDisp, self._sActionBase + self.kdSubmitActionMappings[sMode]);
329
330
331 def _populateForm(self, oForm, oData):
332 """
333 Populates the form. oData has parameter NULL values.
334 This must be reimplemented by the child.
335 """
336 _ = oForm; _ = oData;
337 raise Exception('Reimplement me!');
338
339 def _calcChangeLogEntryLinks(self, aoEntries, iEntry):
340 """
341 Returns an array of links to go with the change log entry.
342 """
343 _ = aoEntries; _ = iEntry;
344 ## @todo detect deletion and recreation.
345 ## @todo view details link.
346 ## @todo restore link (need new action)
347 ## @todo clone link.
348 return [];
349
350 def _guessChangeLogEntryDescription(self, aoEntries, iEntry):
351 """
352 Guesses the action + author that caused the change log entry.
353 Returns descriptive string.
354 """
355 oEntry = aoEntries[iEntry];
356
357 # Figure the author of the change.
358 if oEntry.sAuthor is not None:
359 sAuthor = '%s (#%s)' % (oEntry.sAuthor, oEntry.uidAuthor,);
360 elif oEntry.uidAuthor is not None:
361 sAuthor = '#%d (??)' % (oEntry.uidAuthor,);
362 else:
363 sAuthor = None;
364
365 # Figure the action.
366 if oEntry.oOldRaw is None:
367 if sAuthor is None:
368 return 'Created by batch job.';
369 return 'Created by %s.' % (sAuthor,);
370
371 if sAuthor is None:
372 return 'Automatically updated.'
373 return 'Modified by %s.' % (sAuthor,);
374
375 def _formatChangeLogEntry(self, aoEntries, iEntry):
376 """
377 Formats one change log entry into one or more HTML table rows.
378
379 Note! The parameters are given as array + index in case someone wishes
380 to access adjacent entries later in order to generate better
381 change descriptions.
382 """
383 oEntry = aoEntries[iEntry];
384
385 # The primary row.
386 sRowClass = 'tmodd' if (iEntry + 1) & 1 else 'tmeven';
387 sContent = ' <tr class="%s">\n' \
388 ' <td rowspan="%d">%s</td>\n' \
389 ' <td rowspan="%d">%s</td>\n' \
390 ' <td colspan="3">%s%s</td>\n' \
391 ' </tr>\n' \
392 % ( sRowClass,
393 len(oEntry.aoChanges) + 1, webutils.escapeElem(self.formatTsShort(oEntry.tsEffective)),
394 len(oEntry.aoChanges) + 1, webutils.escapeElem(self.formatTsShort(oEntry.tsExpire)),
395 self._guessChangeLogEntryDescription(aoEntries, iEntry),
396 ' '.join(oLink.toHtml() for oLink in self._calcChangeLogEntryLinks(aoEntries, iEntry)),);
397
398 # Additional rows for each changed attribute.
399 j = 0;
400 for oChange in oEntry.aoChanges:
401 sContent += ' <tr class="%s%s"><td>%s</td><td>%s</td><td>%s</td></tr>\n' \
402 % ( sRowClass, 'odd' if j & 1 else 'even',
403 webutils.escapeElem(oChange.sAttr),
404 webutils.escapeElem(oChange.sOldText),
405 webutils.escapeElem(oChange.sNewText), );
406 j += 1;
407
408 return sContent;
409
410 def _showChangeLogNavi(self, fMoreEntries, iPageNo, cEntriesPerPage, tsNow, sWhere):
411 """
412 Returns the HTML for the change log navigator.
413 Note! See also _generateNavigation.
414 """
415 sNavigation = '<div class="tmlistnav-%s">\n' % sWhere;
416 sNavigation += ' <table class="tmlistnavtab">\n' \
417 ' <tr>\n';
418 dParams = self._oDisp.getParameters();
419 dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage] = cEntriesPerPage;
420 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo;
421 if tsNow is not None:
422 dParams[WuiDispatcherBase.ksParamEffectiveDate] = tsNow;
423
424 # Prev and combo box in one cell. Both inside the form for formatting reasons.
425 sNavigation += ' <td align="left">\n' \
426 ' <form name="ChangeLogEntriesPerPageForm" method="GET">\n'
427
428 # Prev
429 if iPageNo > 0:
430 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo - 1;
431 sNavigation += '<a href="?%s#tmchangelog">Previous</a>\n' \
432 % (webutils.encodeUrlParams(dParams),);
433 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo;
434 else:
435 sNavigation += 'Previous\n';
436
437 # Entries per page selector.
438 del dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage];
439 sNavigation += '&nbsp; &nbsp;\n' \
440 ' <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \
441 'this.options[this.selectedIndex].value + \'#tmchangelog\';" ' \
442 'title="Max change log entries per page">\n' \
443 % (WuiDispatcherBase.ksParamChangeLogEntriesPerPage,
444 webutils.encodeUrlParams(dParams),
445 WuiDispatcherBase.ksParamChangeLogEntriesPerPage);
446 dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage] = cEntriesPerPage;
447
448 for iEntriesPerPage in [2, 4, 8, 16, 32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 8192]:
449 sNavigation += ' <option value="%d" %s>%d entries per page</option>\n' \
450 % ( iEntriesPerPage,
451 'selected="selected"' if iEntriesPerPage == cEntriesPerPage else '',
452 iEntriesPerPage );
453 sNavigation += ' </select>\n';
454
455 # End of cell (and form).
456 sNavigation += ' </form>\n' \
457 ' </td>\n';
458
459 # Next
460 if fMoreEntries:
461 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo + 1;
462 sNavigation += ' <td align="right"><a href="?%s#tmchangelog">Next</a></td>\n' \
463 % (webutils.encodeUrlParams(dParams),);
464 else:
465 sNavigation += ' <td align="right">Next</td>\n';
466
467 sNavigation += ' </tr>\n' \
468 ' </table>\n' \
469 '</div>\n';
470 return sNavigation;
471
472 def showChangeLog(self, aoEntries, fMoreEntries, iPageNo, cEntriesPerPage, tsNow, fShowNavigation = True):
473 """
474 Render the change log, returning raw HTML.
475 aoEntries is an array of ChangeLogEntry.
476 """
477 sContent = '\n' \
478 '<hr>\n' \
479 '<div id="tmchangelog">\n' \
480 ' <h3>Change Log </h3>\n';
481 if fShowNavigation:
482 sContent += self._showChangeLogNavi(fMoreEntries, iPageNo, cEntriesPerPage, tsNow, 'top');
483 sContent += ' <table class="tmtable tmchangelog">\n' \
484 ' <thead class="tmheader">' \
485 ' <tr>' \
486 ' <th rowspan="2">When</th>\n' \
487 ' <th rowspan="2">Expire (excl)</th>\n' \
488 ' <th colspan="3">Changes</th>\n' \
489 ' </tr>\n' \
490 ' <tr>\n' \
491 ' <th>Attribute</th>\n' \
492 ' <th>Old value</th>\n' \
493 ' <th>New value</th>\n' \
494 ' </tr>\n' \
495 ' </thead>\n' \
496 ' <tbody>\n';
497
498 for iEntry in range(len(aoEntries)):
499 sContent += self._formatChangeLogEntry(aoEntries, iEntry);
500
501 sContent += ' <tbody>\n' \
502 ' </table>\n';
503 if fShowNavigation and len(aoEntries) >= 8:
504 sContent += self._showChangeLogNavi(fMoreEntries, iPageNo, cEntriesPerPage, tsNow, 'bottom');
505 sContent += '</div>\n\n';
506 return sContent;
507
508 def showForm(self, dErrors = None, sErrorMsg = None):
509 """
510 Render the form.
511 """
512 oForm = WuiHlpForm(self._sId,
513 '?' + webutils.encodeUrlParams({WuiDispatcherBase.ksParamAction: self._sSubmitAction}),
514 dErrors if dErrors is not None else dict(),
515 fReadOnly = self._sMode == self.ksMode_Show);
516
517 self._oData.convertToParamNull();
518
519 # If form cannot be constructed due to some reason we
520 # need to show this reason
521 try:
522 self._populateForm(oForm, self._oData);
523 except WuiException, oXcpt:
524 sContent = unicode(oXcpt)
525 else:
526 sContent = oForm.finalize();
527
528 # Add action to the top.
529 aoActions = [];
530 if self._sMode == self.ksMode_Show and self._fEditable:
531 # Remove _idGen and effective date since we're always editing the current data,
532 # and make sure the primary ID is present.
533 dParams = self._oDisp.getParameters();
534 if hasattr(self._oData, 'ksIdGenAttr'):
535 sIdGenParam = getattr(self._oData, 'ksParam_' + self._oData.ksIdGenAttr);
536 if sIdGenParam in dParams:
537 del dParams[sIdGenParam];
538 if WuiDispatcherBase.ksParamEffectiveDate in dParams:
539 del dParams[WuiDispatcherBase.ksParamEffectiveDate];
540 dParams[getattr(self._oData, 'ksParam_' + self._oData.ksIdAttr)] = getattr(self._oData, self._oData.ksIdAttr);
541
542 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Edit');
543 aoActions.append(WuiTmLink('Edit', '', dParams));
544
545 # Add clone operation if available. This uses the same data selection as for showing details.
546 if hasattr(self._oDisp, self._sActionBase + 'Clone'):
547 dParams = self._oDisp.getParameters();
548 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Clone');
549 aoActions.append(WuiTmLink('Clone', '', dParams));
550
551 elif self._sMode == self.ksMode_Edit:
552 # Details views the details at a given time, so we need either idGen or an effecive date + regular id.
553 dParams = {};
554 if hasattr(self._oData, 'ksIdGenAttr'):
555 sIdGenParam = getattr(self._oData, 'ksParam_' + self._oData.ksIdGenAttr);
556 dParams[sIdGenParam] = getattr(self._oData, self._oData.ksIdGenAttr);
557 elif hasattr(self._oData, 'tsEffective'):
558 dParams[WuiDispatcherBase.ksParamEffectiveDate] = self._oData.tsEffective;
559 dParams[getattr(self._oData, 'ksParam_' + self._oData.ksIdAttr)] = getattr(self._oData, self._oData.ksIdAttr);
560 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Edit');
561 aoActions.append(WuiTmLink('Details', '', dParams));
562
563 if len(aoActions) > 0:
564 sActionLinks = '<p>%s</p>' % (' '.join(unicode(oLink) for oLink in aoActions));
565 sContent = sActionLinks + sContent;
566
567 # Add error info to the top.
568 if sErrorMsg is not None:
569 sContent = '<p class="tmerrormsg">' + webutils.escapeElem(sErrorMsg) + '</p>\n' + sContent;
570
571 return (self._sTitle, sContent);
572
573 def getListOfItems(self, asListItems = tuple(), asSelectedItems = tuple()):
574 """
575 Format generic list which should be used by HTML form
576 """
577 aoRet = []
578 for sListItem in asListItems:
579 fEnabled = True if sListItem in asSelectedItems else False
580 aoRet.append((sListItem, fEnabled, sListItem))
581 return aoRet
582
583
584class WuiListContentBase(WuiContentBase):
585 """
586 Base for the list content classes.
587 """
588
589 def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, sId = None, fnDPrint = None, oDisp = None):
590 WuiContentBase.__init__(self, fnDPrint = fnDPrint, oDisp = oDisp);
591 self._aoEntries = aoEntries; ## @todo should replace this with a Logic object and define methods for querying.
592 self._iPage = iPage;
593 self._cItemsPerPage = cItemsPerPage;
594 self._tsEffectiveDate = tsEffectiveDate;
595 self._sTitle = sTitle; assert len(sTitle) > 1;
596 if sId is None:
597 sId = sTitle.strip().replace(' ', '').lower();
598 assert len(sId.strip()) > 0;
599 self._sId = sId;
600 self._asColumnHeaders = [];
601 self._asColumnAttribs = [];
602
603 def _formatListEntry(self, iEntry):
604 """
605 Formats the specified list entry as a list of column values.
606 Returns HTML for a table row.
607
608 The child class really need to override this!
609 """
610 # ASSUMES ModelDataBase children.
611 asRet = [];
612 for sAttr in self._aoEntries[0].getDataAttributes():
613 asRet.append(getattr(self._aoEntries[iEntry], sAttr));
614 return asRet;
615
616 def _formatListEntryHtml(self, iEntry):
617 """
618 Formats the specified list entry as HTML.
619 Returns HTML for a table row.
620
621 The child class can override this to
622 """
623 if (iEntry + 1) & 1:
624 sRow = u' <tr class="tmodd">\n';
625 else:
626 sRow = u' <tr class="tmeven">\n';
627
628 aoValues = self._formatListEntry(iEntry);
629 assert len(aoValues) == len(self._asColumnHeaders), '%s vs %s' % (len(aoValues), len(self._asColumnHeaders));
630
631 for i in range(len(aoValues)):
632 if i < len(self._asColumnAttribs) and len(self._asColumnAttribs[i]) > 0:
633 sRow += u' <td ' + self._asColumnAttribs[i] + '>';
634 else:
635 sRow += u' <td>';
636
637 if isinstance(aoValues[i], WuiHtmlBase):
638 sRow += aoValues[i].toHtml();
639 elif isinstance(aoValues[i], list):
640 if len(aoValues[i]) > 0:
641 for oElement in aoValues[i]:
642 if isinstance(oElement, WuiHtmlBase):
643 sRow += oElement.toHtml();
644 elif db.isDbTimestamp(oElement):
645 sRow += webutils.escapeElem(self.formatTsShort(oElement));
646 else:
647 sRow += webutils.escapeElem(unicode(oElement));
648 sRow += ' ';
649 elif db.isDbTimestamp(aoValues[i]):
650 sRow += webutils.escapeElem(self.formatTsShort(aoValues[i]));
651 elif db.isDbInterval(aoValues[i]):
652 sRow += webutils.escapeElem(self.formatIntervalShort(aoValues[i]));
653 elif aoValues[i] is not None:
654 sRow += webutils.escapeElem(unicode(aoValues[i]));
655
656 sRow += u'</td>\n';
657
658 return sRow + u' </tr>\n';
659
660 def _generateTimeNavigation(self, sWhere):
661 """
662 Returns HTML for time navigation.
663
664 Note! Views without a need for a timescale just stubs this method.
665 """
666 _ = sWhere;
667 sNavigation = '';
668
669 dParams = self._oDisp.getParameters();
670 dParams[WuiDispatcherBase.ksParamItemsPerPage] = self._cItemsPerPage;
671 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage;
672
673 if WuiDispatcherBase.ksParamEffectiveDate in dParams:
674 del dParams[WuiDispatcherBase.ksParamEffectiveDate];
675 sNavigation += ' [<a href="?%s">Now</a>]' % (webutils.encodeUrlParams(dParams),);
676
677 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-00 01:00:00.00';
678 sNavigation += ' [<a href="?%s">1</a>' % (webutils.encodeUrlParams(dParams),);
679
680 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-00 02:00:00.00';
681 sNavigation += ', <a href="?%s">2</a>' % (webutils.encodeUrlParams(dParams),);
682
683 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-00 06:00:00.00';
684 sNavigation += ', <a href="?%s">6</a>' % (webutils.encodeUrlParams(dParams),);
685
686 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-00 12:00:00.00';
687 sNavigation += ', <a href="?%s">12</a>' % (webutils.encodeUrlParams(dParams),);
688
689 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-01 00:00:00.00';
690 sNavigation += ', or <a href="?%s">24</a> hours ago]' % (webutils.encodeUrlParams(dParams),);
691
692
693 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-02 00:00:00.00';
694 sNavigation += ' [<a href="?%s">2</a>' % (webutils.encodeUrlParams(dParams),);
695
696 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-03 00:00:00.00';
697 sNavigation += ', <a href="?%s">3</a>' % (webutils.encodeUrlParams(dParams),);
698
699 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-05 00:00:00.00';
700 sNavigation += ', <a href="?%s">5</a>' % (webutils.encodeUrlParams(dParams),);
701
702 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-07 00:00:00.00';
703 sNavigation += ', <a href="?%s">7</a>' % (webutils.encodeUrlParams(dParams),);
704
705 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-14 00:00:00.00';
706 sNavigation += ', <a href="?%s">14</a>' % (webutils.encodeUrlParams(dParams),);
707
708 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-21 00:00:00.00';
709 sNavigation += ', <a href="?%s">21</a>' % (webutils.encodeUrlParams(dParams),);
710
711 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-28 00:00:00.00';
712 sNavigation += ', or <a href="?%s">28</a> days ago]' % (webutils.encodeUrlParams(dParams),);
713
714 return sNavigation;
715
716
717 def _generateNavigation(self, sWhere):
718 """
719 Return HTML for navigation.
720 """
721
722 #
723 # ASSUMES the dispatcher/controller code fetches one entry more than
724 # needed to fill the page to indicate further records.
725 #
726 sNavigation = '<div class="tmlistnav-%s">\n' % sWhere;
727 sNavigation += ' <table class="tmlistnavtab">\n' \
728 ' <tr>\n';
729 dParams = self._oDisp.getParameters();
730 dParams[WuiDispatcherBase.ksParamItemsPerPage] = self._cItemsPerPage;
731 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage;
732 if self._tsEffectiveDate is not None:
733 dParams[WuiDispatcherBase.ksParamEffectiveDate] = self._tsEffectiveDate;
734
735 # Prev
736 if self._iPage > 0:
737 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage - 1;
738 sNavigation += ' <td align="left"><a href="?%s">Previous</a></td>\n' % (webutils.encodeUrlParams(dParams),);
739 else:
740 sNavigation += ' <td></td>\n';
741
742 # Time scale.
743 sNavigation += '<td align="center" class="tmtimenav">';
744 sNavigation += self._generateTimeNavigation(sWhere);
745 sNavigation += '</td>';
746
747 # Next
748 if len(self._aoEntries) > self._cItemsPerPage:
749 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage + 1;
750 sNavigation += ' <td align="right"><a href="?%s">Next</a></td>\n' % (webutils.encodeUrlParams(dParams),);
751 else:
752 sNavigation += ' <td></td>\n';
753
754 sNavigation += ' </tr>\n' \
755 ' </table>\n' \
756 '</div>\n';
757 return sNavigation;
758
759 def _generateTable(self):
760 """
761 show worker that just generates the table.
762 """
763
764 #
765 # Create a table.
766 # If no colum headers are provided, fall back on database field
767 # names, ASSUMING that the entries are ModelDataBase children.
768 # Note! the cellspacing is for IE8.
769 #
770 sPageBody = '<table class="tmtable" id="' + self._sId + '" cellspacing="0">\n';
771
772 if len(self._asColumnHeaders) == 0:
773 self._asColumnHeaders = self._aoEntries[0].getDataAttributes();
774
775 sPageBody += ' <thead class="tmheader"><tr>';
776 for oHeader in self._asColumnHeaders:
777 if isinstance(oHeader, WuiHtmlBase):
778 sPageBody += '<th>' + oHeader.toHtml() + '</th>';
779 else:
780 sPageBody += '<th>' + webutils.escapeElem(oHeader) + '</th>';
781 sPageBody += '</tr><thead>\n';
782
783 #
784 # Format the body and close the table.
785 #
786 sPageBody += ' <tbody>\n';
787 for iEntry in range(min(len(self._aoEntries), self._cItemsPerPage)):
788 sPageBody += self._formatListEntryHtml(iEntry);
789 sPageBody += ' </tbody>\n' \
790 '</table>\n';
791 return sPageBody;
792
793 def _composeTitle(self):
794 """Composes the title string (return value)."""
795 sTitle = self._sTitle;
796 if self._iPage != 0:
797 sTitle += ' (page ' + unicode(self._iPage + 1) + ')'
798 if self._tsEffectiveDate is not None:
799 sTitle += ' as per ' + unicode(self._tsEffectiveDate); ## @todo shorten this.
800 return sTitle;
801
802
803 def show(self, fShowNavigation = True):
804 """
805 Displays the list.
806 Returns (Title, HTML) on success, raises exception on error.
807 """
808
809 sPageBody = ''
810 if fShowNavigation:
811 sPageBody += self._generateNavigation('top');
812
813 if len(self._aoEntries):
814 sPageBody += self._generateTable();
815 if fShowNavigation:
816 sPageBody += self._generateNavigation('bottom');
817 else:
818 sPageBody += '<p>No entries.</p>'
819
820 return (self._composeTitle(), sPageBody);
821
822
823class WuiListContentWithActionBase(WuiListContentBase):
824 """
825 Base for the list content with action classes.
826 """
827
828 def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, sId = None, fnDPrint = None, oDisp = None):
829 WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle,
830 sId = sId, fnDPrint = fnDPrint, oDisp = oDisp);
831 self._aoActions = None; # List of [ oValue, sText, sHover ] provided by the child class.
832 self._sAction = None; # Set by the child class.
833 self._sCheckboxName = None; # Set by the child class.
834 self._asColumnHeaders = [ WuiRawHtml('<input type="checkbox" onClick="toggle%s(this)">'
835 % ('' if sId is None else sId)), ];
836 self._asColumnAttribs = [ 'align="center"', ];
837
838 def _getCheckBoxColumn(self, iEntry, sValue):
839 """
840 Used by _formatListEntry implementations, returns a WuiRawHtmlBase object.
841 """
842 _ = iEntry;
843 return WuiRawHtml('<input type="checkbox" name="%s" value="%s">'
844 % (webutils.escapeAttr(self._sCheckboxName), webutils.escapeAttr(unicode(sValue))));
845
846 def show(self, fShowNavigation=True):
847 """
848 Displays the list.
849 Returns (Title, HTML) on success, raises exception on error.
850 """
851 assert self._aoActions is not None;
852 assert self._sAction is not None;
853
854 sPageBody = '<script language="JavaScript">\n' \
855 'function toggle%s(oSource) {\n' \
856 ' aoCheckboxes = document.getElementsByName(\'%s\');\n' \
857 ' for(var i in aoCheckboxes)\n' \
858 ' aoCheckboxes[i].checked = oSource.checked;\n' \
859 '}\n' \
860 '</script>\n' \
861 % ('' if self._sId is None else self._sId, self._sCheckboxName,);
862 if fShowNavigation:
863 sPageBody += self._generateNavigation('top');
864 if len(self._aoEntries) > 0:
865
866 sPageBody += '<form action="?%s" method="post" class="tmlistactionform">\n' \
867 % (webutils.encodeUrlParams({WuiDispatcherBase.ksParamAction: self._sAction,}),);
868 sPageBody += self._generateTable();
869
870 sPageBody += ' <label>Actions</label>\n' \
871 ' <select name="%s" id="%s-action-combo" class="tmlistactionform-combo">\n' \
872 % (webutils.escapeAttr(WuiDispatcherBase.ksParamListAction), webutils.escapeAttr(self._sId),);
873 for oValue, sText, _ in self._aoActions:
874 sPageBody += ' <option value="%s">%s</option>\n' \
875 % (webutils.escapeAttr(unicode(oValue)), webutils.escapeElem(sText), );
876 sPageBody += ' </select>\n';
877 sPageBody += ' <input type="submit"></input>\n';
878 sPageBody += '</form>\n';
879 if fShowNavigation:
880 sPageBody += self._generateNavigation('bottom');
881 else:
882 sPageBody += '<p>No entries.</p>'
883
884 return (self._composeTitle(), sPageBody);
885
Note: See TracBrowser for help on using the repository browser.

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