VirtualBox

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

Last change on this file since 55341 was 52776, checked in by vboxsync, 10 years ago

fix OSE

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.6 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: wuicontentbase.py 52776 2014-09-17 14:51:43Z vboxsync $
3
4"""
5Test Manager Web-UI - Content Base Classes.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2014 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: 52776 $"
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 @staticmethod
217 def genericPageWalker(iCurItem, cItems, sHrefFmt, cWidth = 11, iBase = 1, sItemName = 'page'):
218 """
219 Generic page walker generator.
220
221 sHrefFmt has three %s sequences:
222 1. The first is the page number link parameter (0-based).
223 2. The title text, iBase-based number or text.
224 3. The link text, iBase-based number or text.
225 """
226
227 # Calc display range.
228 iStart = 0 if iCurItem - cWidth / 2 <= cWidth / 4 else iCurItem - cWidth / 2;
229 iEnd = iStart + cWidth;
230 if iEnd > cItems:
231 iEnd = cItems;
232 if cItems > cWidth:
233 iStart = cItems - cWidth;
234
235 sHtml = u'';
236
237 # Previous page (using << >> because &laquo; and &raquo are too tiny).
238 if iCurItem > 0:
239 sHtml += '%s&nbsp;&nbsp;' % sHrefFmt % (iCurItem - 1, 'previous ' + sItemName, '&lt;&lt;');
240 else:
241 sHtml += '&lt;&lt;&nbsp;&nbsp;';
242
243 # 1 2 3 4...
244 if iStart > 0:
245 sHtml += '%s&nbsp; ... &nbsp;\n' % (sHrefFmt % (0, 'first %s' % (sItemName,), 0 + iBase),);
246
247 sHtml += '&nbsp;\n'.join(sHrefFmt % (i, '%s %d' % (sItemName, i + iBase), i + iBase) if i != iCurItem
248 else unicode(i + iBase)
249 for i in range(iStart, iEnd));
250 if iEnd < cItems:
251 sHtml += '&nbsp; ... &nbsp;%s\n' % (sHrefFmt % (cItems - 1, 'last %s' % (sItemName,), cItems - 1 + iBase));
252
253 # Next page.
254 if iCurItem + 1 < cItems:
255 sHtml += '&nbsp;&nbsp;%s' % sHrefFmt % (iCurItem + 1, 'next ' + sItemName, '&gt;&gt;');
256 else:
257 sHtml += '&nbsp;&nbsp;&gt;&gt;';
258
259 return sHtml;
260
261class WuiSingleContentBase(WuiContentBase): # pylint: disable=R0903
262 """
263 Base for the content classes working on a single data object (oData).
264 """
265 def __init__(self, oData, oDisp = None, fnDPrint = None):
266 WuiContentBase.__init__(self, oDisp = oDisp, fnDPrint = fnDPrint);
267 self._oData = oData; # Usually ModelDataBase.
268
269
270class WuiFormContentBase(WuiSingleContentBase): # pylint: disable=R0903
271 """
272 Base class for simple input form content classes (single data object).
273 """
274
275 ## @name Form mode.
276 ## @{
277 ksMode_Add = 'add';
278 ksMode_Edit = 'edit';
279 ksMode_Show = 'show';
280 ## @}
281
282 ## Default action mappings.
283 kdSubmitActionMappings = {
284 ksMode_Add: 'AddPost',
285 ksMode_Edit: 'EditPost',
286 };
287
288 def __init__(self, oData, sMode, sCoreName, oDisp, sTitle, sId = None, fEditable = True, sSubmitAction = None):
289 WuiSingleContentBase.__init__(self, copy.copy(oData), oDisp);
290 assert sMode in [self.ksMode_Add, self.ksMode_Edit, self.ksMode_Show];
291 assert len(sTitle) > 1;
292 assert sId is None or len(sId) > 0;
293
294 self._sMode = sMode;
295 self._sCoreName = sCoreName;
296 self._sActionBase = 'ksAction' + sCoreName;
297 self._sTitle = sTitle;
298 self._sId = sId if sId is not None else (type(oData).__name__.lower() + 'form');
299 self._fEditable = fEditable;
300 self._sSubmitAction = sSubmitAction;
301 if sSubmitAction is None and sMode != self.ksMode_Show:
302 self._sSubmitAction = getattr(oDisp, self._sActionBase + self.kdSubmitActionMappings[sMode]);
303
304
305 def _populateForm(self, oForm, oData):
306 """
307 Populates the form. oData has parameter NULL values.
308 This must be reimplemented by the child.
309 """
310 _ = oForm; _ = oData;
311 raise Exception('Reimplement me!');
312
313 def _calcChangeLogEntryLinks(self, aoEntries, iEntry):
314 """
315 Returns an array of links to go with the change log entry.
316 """
317 _ = aoEntries; _ = iEntry;
318 ## @todo detect deletion and recreation.
319 ## @todo view details link.
320 ## @todo restore link (need new action)
321 ## @todo clone link.
322 return [];
323
324 def _guessChangeLogEntryDescription(self, aoEntries, iEntry):
325 """
326 Guesses the action + author that caused the change log entry.
327 Returns descriptive string.
328 """
329 oEntry = aoEntries[iEntry];
330
331 # Figure the author of the change.
332 if oEntry.sAuthor is not None:
333 sAuthor = '%s (#%s)' % (oEntry.sAuthor, oEntry.uidAuthor,);
334 elif oEntry.uidAuthor is not None:
335 sAuthor = '#%d (??)' % (oEntry.uidAuthor,);
336 else:
337 sAuthor = None;
338
339 # Figure the action.
340 if oEntry.oOldRaw is None:
341 if sAuthor is None:
342 return 'Created by batch job.';
343 return 'Created by %s.' % (sAuthor,);
344
345 if sAuthor is None:
346 return 'Automatically updated.'
347 return 'Modified by %s.' % (sAuthor,);
348
349 def _formatChangeLogEntry(self, aoEntries, iEntry):
350 """
351 Formats one change log entry into one or more HTML table rows.
352
353 Note! The parameters are given as array + index in case someone wishes
354 to access adjacent entries later in order to generate better
355 change descriptions.
356 """
357 oEntry = aoEntries[iEntry];
358
359 # The primary row.
360 sRowClass = 'tmodd' if (iEntry + 1) & 1 else 'tmeven';
361 sContent = ' <tr class="%s">\n' \
362 ' <td rowspan="%d">%s</td>\n' \
363 ' <td rowspan="%d">%s</td>\n' \
364 ' <td colspan="3">%s%s</td>\n' \
365 ' </tr>\n' \
366 % ( sRowClass,
367 len(oEntry.aoChanges) + 1, webutils.escapeElem(self.formatTsShort(oEntry.tsEffective)),
368 len(oEntry.aoChanges) + 1, webutils.escapeElem(self.formatTsShort(oEntry.tsExpire)),
369 self._guessChangeLogEntryDescription(aoEntries, iEntry),
370 ' '.join(oLink.toHtml() for oLink in self._calcChangeLogEntryLinks(aoEntries, iEntry)),);
371
372 # Additional rows for each changed attribute.
373 j = 0;
374 for oChange in oEntry.aoChanges:
375 sContent += ' <tr class="%s%s"><td>%s</td><td>%s</td><td>%s</td></tr>\n' \
376 % ( sRowClass, 'odd' if j & 1 else 'even',
377 webutils.escapeElem(oChange.sAttr),
378 webutils.escapeElem(oChange.sOldText),
379 webutils.escapeElem(oChange.sNewText), );
380 j += 1;
381
382 return sContent;
383
384 def _showChangeLogNavi(self, fMoreEntries, iPageNo, cEntriesPerPage, tsNow, sWhere):
385 """
386 Returns the HTML for the change log navigator.
387 Note! See also _generateNavigation.
388 """
389 sNavigation = '<div class="tmlistnav-%s">\n' % sWhere;
390 sNavigation += ' <table class="tmlistnavtab">\n' \
391 ' <tr>\n';
392 dParams = self._oDisp.getParameters();
393 dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage] = cEntriesPerPage;
394 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo;
395 if tsNow is not None:
396 dParams[WuiDispatcherBase.ksParamEffectiveDate] = tsNow;
397
398 # Prev and combo box in one cell. Both inside the form for formatting reasons.
399 sNavigation += ' <td align="left">\n' \
400 ' <form name="ChangeLogEntriesPerPageForm" method="GET">\n'
401
402 # Prev
403 if iPageNo > 0:
404 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo - 1;
405 sNavigation += '<a href="?%s#tmchangelog">Previous</a>\n' \
406 % (webutils.encodeUrlParams(dParams),);
407 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo;
408 else:
409 sNavigation += 'Previous\n';
410
411 # Entries per page selector.
412 del dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage];
413 sNavigation += '&nbsp; &nbsp;\n' \
414 ' <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \
415 'this.options[this.selectedIndex].value + \'#tmchangelog\';" ' \
416 'title="Max change log entries per page">\n' \
417 % (WuiDispatcherBase.ksParamChangeLogEntriesPerPage,
418 webutils.encodeUrlParams(dParams),
419 WuiDispatcherBase.ksParamChangeLogEntriesPerPage);
420 dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage] = cEntriesPerPage;
421
422 for iEntriesPerPage in [2, 4, 8, 16, 32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 8192]:
423 sNavigation += ' <option value="%d" %s>%d entries per page</option>\n' \
424 % ( iEntriesPerPage,
425 'selected="selected"' if iEntriesPerPage == cEntriesPerPage else '',
426 iEntriesPerPage );
427 sNavigation += ' </select>\n';
428
429 # End of cell (and form).
430 sNavigation += ' </form>\n' \
431 ' </td>\n';
432
433 # Next
434 if fMoreEntries:
435 dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo + 1;
436 sNavigation += ' <td align="right"><a href="?%s#tmchangelog">Next</a></td>\n' \
437 % (webutils.encodeUrlParams(dParams),);
438 else:
439 sNavigation += ' <td align="right">Next</td>\n';
440
441 sNavigation += ' </tr>\n' \
442 ' </table>\n' \
443 '</div>\n';
444 return sNavigation;
445
446 def showChangeLog(self, aoEntries, fMoreEntries, iPageNo, cEntriesPerPage, tsNow, fShowNavigation = True):
447 """
448 Render the change log, returning raw HTML.
449 aoEntries is an array of ChangeLogEntry.
450 """
451 sContent = '\n' \
452 '<hr>\n' \
453 '<div id="tmchangelog">\n' \
454 ' <h3>Change Log </h3>\n';
455 if fShowNavigation:
456 sContent += self._showChangeLogNavi(fMoreEntries, iPageNo, cEntriesPerPage, tsNow, 'top');
457 sContent += ' <table class="tmtable tmchangelog">\n' \
458 ' <thead class="tmheader">' \
459 ' <tr>' \
460 ' <th rowspan="2">When</th>\n' \
461 ' <th rowspan="2">Expire (excl)</th>\n' \
462 ' <th colspan="3">Changes</th>\n' \
463 ' </tr>\n' \
464 ' <tr>\n' \
465 ' <th>Attribute</th>\n' \
466 ' <th>Old value</th>\n' \
467 ' <th>New value</th>\n' \
468 ' </tr>\n' \
469 ' </thead>\n' \
470 ' <tbody>\n';
471
472 for iEntry in range(len(aoEntries)):
473 sContent += self._formatChangeLogEntry(aoEntries, iEntry);
474
475 sContent += ' <tbody>\n' \
476 ' </table>\n';
477 if fShowNavigation and len(aoEntries) >= 8:
478 sContent += self._showChangeLogNavi(fMoreEntries, iPageNo, cEntriesPerPage, tsNow, 'bottom');
479 sContent += '</div>\n\n';
480 return sContent;
481
482 def showForm(self, dErrors = None, sErrorMsg = None):
483 """
484 Render the form.
485 """
486 oForm = WuiHlpForm(self._sId,
487 '?' + webutils.encodeUrlParams({WuiDispatcherBase.ksParamAction: self._sSubmitAction}),
488 dErrors if dErrors is not None else dict(),
489 fReadOnly = self._sMode == self.ksMode_Show);
490
491 self._oData.convertToParamNull();
492
493 # If form cannot be constructed due to some reason we
494 # need to show this reason
495 try:
496 self._populateForm(oForm, self._oData);
497 except WuiException, oXcpt:
498 sContent = unicode(oXcpt)
499 else:
500 sContent = oForm.finalize();
501
502 # Add action to the top.
503 aoActions = [];
504 if self._sMode == self.ksMode_Show and self._fEditable:
505 # Remove _idGen and effective date since we're always editing the current data,
506 # and make sure the primary ID is present.
507 dParams = self._oDisp.getParameters();
508 if hasattr(self._oData, 'ksIdGenAttr'):
509 sIdGenParam = getattr(self._oData, 'ksParam_' + self._oData.ksIdGenAttr);
510 if sIdGenParam in dParams:
511 del dParams[sIdGenParam];
512 if WuiDispatcherBase.ksParamEffectiveDate in dParams:
513 del dParams[WuiDispatcherBase.ksParamEffectiveDate];
514 dParams[getattr(self._oData, 'ksParam_' + self._oData.ksIdAttr)] = getattr(self._oData, self._oData.ksIdAttr);
515
516 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Edit');
517 aoActions.append(WuiTmLink('Edit', '', dParams));
518
519 # Add clone operation if available. This uses the same data selection as for showing details.
520 if hasattr(self._oDisp, self._sActionBase + 'Clone'):
521 dParams = self._oDisp.getParameters();
522 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Clone');
523 aoActions.append(WuiTmLink('Clone', '', dParams));
524
525 elif self._sMode == self.ksMode_Edit:
526 # Details views the details at a given time, so we need either idGen or an effecive date + regular id.
527 dParams = {};
528 if hasattr(self._oData, 'ksIdGenAttr'):
529 sIdGenParam = getattr(self._oData, 'ksParam_' + self._oData.ksIdGenAttr);
530 dParams[sIdGenParam] = getattr(self._oData, self._oData.ksIdGenAttr);
531 elif hasattr(self._oData, 'tsEffective'):
532 dParams[WuiDispatcherBase.ksParamEffectiveDate] = self._oData.tsEffective;
533 dParams[getattr(self._oData, 'ksParam_' + self._oData.ksIdAttr)] = getattr(self._oData, self._oData.ksIdAttr);
534 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Edit');
535 aoActions.append(WuiTmLink('Details', '', dParams));
536
537 if len(aoActions) > 0:
538 sActionLinks = '<p>%s</p>' % (' '.join(unicode(oLink) for oLink in aoActions));
539 sContent = sActionLinks + sContent;
540
541 # Add error info to the top.
542 if sErrorMsg is not None:
543 sContent = '<p class="tmerrormsg">' + webutils.escapeElem(sErrorMsg) + '</p>\n' + sContent;
544
545 return (self._sTitle, sContent);
546
547 def getListOfItems(self, asListItems = tuple(), asSelectedItems = tuple()):
548 """
549 Format generic list which should be used by HTML form
550 """
551 aoRet = []
552 for sListItem in asListItems:
553 fEnabled = True if sListItem in asSelectedItems else False
554 aoRet.append((sListItem, fEnabled, sListItem))
555 return aoRet
556
557
558class WuiListContentBase(WuiContentBase):
559 """
560 Base for the list content classes.
561 """
562
563 def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, sId = None, fnDPrint = None, oDisp = None):
564 WuiContentBase.__init__(self, fnDPrint = fnDPrint, oDisp = oDisp);
565 self._aoEntries = aoEntries; ## @todo should replace this with a Logic object and define methods for querying.
566 self._iPage = iPage;
567 self._cItemsPerPage = cItemsPerPage;
568 self._tsEffectiveDate = tsEffectiveDate;
569 self._sTitle = sTitle; assert len(sTitle) > 1;
570 if sId is None:
571 sId = sTitle.strip().replace(' ', '').lower();
572 assert len(sId.strip()) > 0;
573 self._sId = sId;
574 self._asColumnHeaders = [];
575 self._asColumnAttribs = [];
576
577 def _formatListEntry(self, iEntry):
578 """
579 Formats the specified list entry as a list of column values.
580 Returns HTML for a table row.
581
582 The child class really need to override this!
583 """
584 # ASSUMES ModelDataBase children.
585 asRet = [];
586 for sAttr in self._aoEntries[0].getDataAttributes():
587 asRet.append(getattr(self._aoEntries[iEntry], sAttr));
588 return asRet;
589
590 def _formatListEntryHtml(self, iEntry):
591 """
592 Formats the specified list entry as HTML.
593 Returns HTML for a table row.
594
595 The child class can override this to
596 """
597 if (iEntry + 1) & 1:
598 sRow = u' <tr class="tmodd">\n';
599 else:
600 sRow = u' <tr class="tmeven">\n';
601
602 aoValues = self._formatListEntry(iEntry);
603 assert len(aoValues) == len(self._asColumnHeaders), '%s vs %s' % (len(aoValues), len(self._asColumnHeaders));
604
605 for i in range(len(aoValues)):
606 if i < len(self._asColumnAttribs) and len(self._asColumnAttribs[i]) > 0:
607 sRow += u' <td ' + self._asColumnAttribs[i] + '>';
608 else:
609 sRow += u' <td>';
610
611 if isinstance(aoValues[i], WuiHtmlBase):
612 sRow += aoValues[i].toHtml();
613 elif isinstance(aoValues[i], list):
614 if len(aoValues[i]) > 0:
615 for oElement in aoValues[i]:
616 if isinstance(oElement, WuiHtmlBase):
617 sRow += oElement.toHtml();
618 elif db.isDbTimestamp(oElement):
619 sRow += webutils.escapeElem(self.formatTsShort(oElement));
620 else:
621 sRow += webutils.escapeElem(unicode(oElement));
622 sRow += ' ';
623 elif db.isDbTimestamp(aoValues[i]):
624 sRow += webutils.escapeElem(self.formatTsShort(aoValues[i]));
625 elif aoValues[i] is not None:
626 sRow += webutils.escapeElem(unicode(aoValues[i]));
627
628 sRow += u'</td>\n';
629
630 return sRow + u' </tr>\n';
631
632 def _generateTimeNavigation(self, sWhere):
633 """
634 Returns HTML for time navigation.
635
636 Note! Views without a need for a timescale just stubs this method.
637 """
638 _ = sWhere;
639 sNavigation = '';
640
641 dParams = self._oDisp.getParameters();
642 dParams[WuiDispatcherBase.ksParamItemsPerPage] = self._cItemsPerPage;
643 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage;
644
645 if WuiDispatcherBase.ksParamEffectiveDate in dParams:
646 del dParams[WuiDispatcherBase.ksParamEffectiveDate];
647 sNavigation += ' [<a href="?%s">Now</a>]' % (webutils.encodeUrlParams(dParams),);
648
649 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-00 01:00:00.00';
650 sNavigation += ' [<a href="?%s">1</a>' % (webutils.encodeUrlParams(dParams),);
651
652 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-00 02:00:00.00';
653 sNavigation += ', <a href="?%s">2</a>' % (webutils.encodeUrlParams(dParams),);
654
655 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-00 06:00:00.00';
656 sNavigation += ', <a href="?%s">6</a>' % (webutils.encodeUrlParams(dParams),);
657
658 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-00 12:00:00.00';
659 sNavigation += ', <a href="?%s">12</a>' % (webutils.encodeUrlParams(dParams),);
660
661 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-01 00:00:00.00';
662 sNavigation += ', or <a href="?%s">24</a> hours ago]' % (webutils.encodeUrlParams(dParams),);
663
664
665 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-02 00:00:00.00';
666 sNavigation += ' [<a href="?%s">2</a>' % (webutils.encodeUrlParams(dParams),);
667
668 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-03 00:00:00.00';
669 sNavigation += ', <a href="?%s">3</a>' % (webutils.encodeUrlParams(dParams),);
670
671 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-05 00:00:00.00';
672 sNavigation += ', <a href="?%s">5</a>' % (webutils.encodeUrlParams(dParams),);
673
674 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-07 00:00:00.00';
675 sNavigation += ', <a href="?%s">7</a>' % (webutils.encodeUrlParams(dParams),);
676
677 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-14 00:00:00.00';
678 sNavigation += ', <a href="?%s">14</a>' % (webutils.encodeUrlParams(dParams),);
679
680 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-21 00:00:00.00';
681 sNavigation += ', <a href="?%s">21</a>' % (webutils.encodeUrlParams(dParams),);
682
683 dParams[WuiDispatcherBase.ksParamEffectiveDate] = '-0000-00-28 00:00:00.00';
684 sNavigation += ', or <a href="?%s">28</a> days ago]' % (webutils.encodeUrlParams(dParams),);
685
686 return sNavigation;
687
688
689 def _generateNavigation(self, sWhere):
690 """
691 Return HTML for navigation.
692 """
693
694 #
695 # ASSUMES the dispatcher/controller code fetches one entry more than
696 # needed to fill the page to indicate further records.
697 #
698 sNavigation = '<div class="tmlistnav-%s">\n' % sWhere;
699 sNavigation += ' <table class="tmlistnavtab">\n' \
700 ' <tr>\n';
701 dParams = self._oDisp.getParameters();
702 dParams[WuiDispatcherBase.ksParamItemsPerPage] = self._cItemsPerPage;
703 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage;
704 if self._tsEffectiveDate is not None:
705 dParams[WuiDispatcherBase.ksParamEffectiveDate] = self._tsEffectiveDate;
706
707 # Prev
708 if self._iPage > 0:
709 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage - 1;
710 sNavigation += ' <td align="left"><a href="?%s">Previous</a></td>\n' % (webutils.encodeUrlParams(dParams),);
711 else:
712 sNavigation += ' <td></td>\n';
713
714 # Time scale.
715 sNavigation += '<td align="center" class="tmtimenav">';
716 sNavigation += self._generateTimeNavigation(sWhere);
717 sNavigation += '</td>';
718
719 # Next
720 if len(self._aoEntries) > self._cItemsPerPage:
721 dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage + 1;
722 sNavigation += ' <td align="right"><a href="?%s">Next</a></td>\n' % (webutils.encodeUrlParams(dParams),);
723 else:
724 sNavigation += ' <td></td>\n';
725
726 sNavigation += ' </tr>\n' \
727 ' </table>\n' \
728 '</div>\n';
729 return sNavigation;
730
731 def _generateTable(self):
732 """
733 show worker that just generates the table.
734 """
735
736 #
737 # Create a table.
738 # If no colum headers are provided, fall back on database field
739 # names, ASSUMING that the entries are ModelDataBase children.
740 # Note! the cellspacing is for IE8.
741 #
742 sPageBody = '<table class="tmtable" id="' + self._sId + '" cellspacing="0">\n';
743
744 if len(self._asColumnHeaders) == 0:
745 self._asColumnHeaders = self._aoEntries[0].getDataAttributes();
746
747 sPageBody += ' <thead class="tmheader"><tr>';
748 for oHeader in self._asColumnHeaders:
749 if isinstance(oHeader, WuiHtmlBase):
750 sPageBody += '<th>' + oHeader.toHtml() + '</th>';
751 else:
752 sPageBody += '<th>' + webutils.escapeElem(oHeader) + '</th>';
753 sPageBody += '</tr><thead>\n';
754
755 #
756 # Format the body and close the table.
757 #
758 sPageBody += ' <tbody>\n';
759 for iEntry in range(min(len(self._aoEntries), self._cItemsPerPage)):
760 sPageBody += self._formatListEntryHtml(iEntry);
761 sPageBody += ' </tbody>\n' \
762 '</table>\n';
763 return sPageBody;
764
765 def _composeTitle(self):
766 """Composes the title string (return value)."""
767 sTitle = self._sTitle;
768 if self._iPage != 0:
769 sTitle += ' (page ' + unicode(self._iPage + 1) + ')'
770 if self._tsEffectiveDate is not None:
771 sTitle += ' as per ' + unicode(self._tsEffectiveDate); ## @todo shorten this.
772 return sTitle;
773
774
775 def show(self, fShowNavigation = True):
776 """
777 Displays the list.
778 Returns (Title, HTML) on success, raises exception on error.
779 """
780
781 sPageBody = ''
782 if fShowNavigation:
783 sPageBody += self._generateNavigation('top');
784
785 if len(self._aoEntries):
786 sPageBody += self._generateTable();
787 if fShowNavigation:
788 sPageBody += self._generateNavigation('bottom');
789 else:
790 sPageBody += '<p>No entries.</p>'
791
792 return (self._composeTitle(), sPageBody);
793
794
795class WuiListContentWithActionBase(WuiListContentBase):
796 """
797 Base for the list content with action classes.
798 """
799
800 def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, sId = None, fnDPrint = None, oDisp = None):
801 WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle,
802 sId = sId, fnDPrint = fnDPrint, oDisp = oDisp);
803 self._aoActions = None; # List of [ oValue, sText, sHover ] provided by the child class.
804 self._sAction = None; # Set by the child class.
805 self._sCheckboxName = None; # Set by the child class.
806 self._asColumnHeaders = [ WuiRawHtml('<input type="checkbox" onClick="toggle%s(this)">'
807 % ('' if sId is None else sId)), ];
808 self._asColumnAttribs = [ 'align="center"', ];
809
810 def _getCheckBoxColumn(self, iEntry, sValue):
811 """
812 Used by _formatListEntry implementations, returns a WuiRawHtmlBase object.
813 """
814 _ = iEntry;
815 return WuiRawHtml('<input type="checkbox" name="%s" value="%s">'
816 % (webutils.escapeAttr(self._sCheckboxName), webutils.escapeAttr(unicode(sValue))));
817
818 def show(self, fShowNavigation=True):
819 """
820 Displays the list.
821 Returns (Title, HTML) on success, raises exception on error.
822 """
823 assert self._aoActions is not None;
824 assert self._sAction is not None;
825
826 sPageBody = '<script language="JavaScript">\n' \
827 'function toggle%s(oSource) {\n' \
828 ' aoCheckboxes = document.getElementsByName(\'%s\');\n' \
829 ' for(var i in aoCheckboxes)\n' \
830 ' aoCheckboxes[i].checked = oSource.checked;\n' \
831 '}\n' \
832 '</script>\n' \
833 % ('' if self._sId is None else self._sId, self._sCheckboxName,);
834 if fShowNavigation:
835 sPageBody += self._generateNavigation('top');
836 if len(self._aoEntries) > 0:
837
838 sPageBody += '<form action="?%s" method="post" class="tmlistactionform">\n' \
839 % (webutils.encodeUrlParams({WuiDispatcherBase.ksParamAction: self._sAction,}),);
840 sPageBody += self._generateTable();
841
842 sPageBody += ' <label>Actions</label>\n' \
843 ' <select name="%s" id="%s-action-combo" class="tmlistactionform-combo">\n' \
844 % (webutils.escapeAttr(WuiDispatcherBase.ksParamListAction), webutils.escapeAttr(self._sId),);
845 for oValue, sText, _ in self._aoActions:
846 sPageBody += ' <option value="%s">%s</option>\n' \
847 % (webutils.escapeAttr(unicode(oValue)), webutils.escapeElem(sText), );
848 sPageBody += ' </select>\n';
849 sPageBody += ' <input type="submit"></input>\n';
850 sPageBody += '</form>\n';
851 if fShowNavigation:
852 sPageBody += self._generateNavigation('bottom');
853 else:
854 sPageBody += '<p>No entries.</p>'
855
856 return (self._composeTitle(), sPageBody);
857
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