VirtualBox

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

Last change on this file since 65132 was 65052, checked in by vboxsync, 8 years ago

try make doxygen happy...

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