ATLAS Offline Software
WebPage.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
2 
3 """
4 This module provides a simple framework for generating CSS-based dynamic web
5 pages with a typical layout consisting of a header, navigation bar, content area
6 and a footer. It is compatible with different web servers using e.g. CGI, mod_python,
7 or CherryPy.
8 """
9 __author__ = 'Juerg Beringer'
10 __version__ = 'WebPage.py atlas/athena'
11 
12 
13 import time
14 from cgi import escape
15 
16 
17 #
18 # HTML templates
19 #
20 startPage = """\
21 %(contentType)s<?xml version="1.0" encoding="UTF-8"?>
22 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
23 <html xmlns="http://www.w3.org/1999/xhtml">
24 <head>
25 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
26 <title>%(pageTitle)s</title>
27 <link href="%(cssName)s" rel="stylesheet" type="text/css" />
28 %(pageHeaderSnippets)s\
29 </head>
30 <body>
31 """
32 
33 endPage = """\
34 </body>
35 </html>\
36 """
37 
38 
39 #
40 # Utilities for generating HTML snippets
41 #
42 def sep(s):
43  """Add separator to string s unless s is empty."""
44  return ' '+s if s else s
45 
46 def htmlDiv(id, contents='', attr='', keepEmptyDiv=True):
47  """Make a named div element containing contents. If
48  contents is empty, an empty string is returned,
49  unless keepEmtpyDiv is set True."""
50  if contents or keepEmptyDiv:
51  if id:
52  return '<div id="%s"%s>\n%s</div>\n' % (id,sep(attr),contents)
53  else:
54  return '<div%s>\n%s</div>\n' % (sep(attr),contents)
55  else:
56  return ''
57 
58 def htmlText(text, attr='', escapeText=False):
59  """Make a text consisting of an unnamed div. Special HTML characters
60  in the text are properly replaced (using escape from cgi) if
61  escapeText is set True."""
62  return '<div%s>%s</div>\n' % (sep(attr),escape(text) if escapeText else text)
63 
64 def htmlPre(text, attr='', escapeText=False):
65  """Make a preformatted text section. Special HTML characters
66  in the text are properly replaced (using escape from cgi) if
67  escapeText is set True."""
68  return '<pre%s>\n%s\n</pre>\n' % (sep(attr),escape(text) if escapeText else text)
69 
70 def htmlPara(text='', attr='', escapeText=False):
71  """Make a paragraph."""
72  if text or attr:
73  return '<p%s>%s</p>\n' % (sep(attr), escape(text) if escapeText else text)
74  else:
75  return '<p>\n'
76 
77 def htmlLink(text, link, attr='', escapeText=False):
78  return '<a href="%s"%s>%s</a>' % (link,sep(attr),escape(text) if escapeText else text)
79 
80 def htmlList(contents, attr='', listType='ul'):
81  """Enclose list contents (a string with one or more list items) with
82  the proper list tag. The type of the list is given by listType."""
83  return '<%s%s>\n%s</%s>\n' % (listType,sep(attr),contents,listType)
84 
85 def htmlLI(text, attr='', escapeText=False):
86  """Make a list item. Special HTML characters
87  in the text are properly replaced (using escape from cgi) if
88  escapeText is set True."""
89  return '<li%s>%s</li>\n' % (sep(attr), escape(text) if escapeText else text)
90 
91 class htmlTable:
92  """Make a table. Table row data is accumulated internally in a list.
93  The table can be made sortable (using jQuery's plugin Tablesorter)
94  by setting useTableSorter to True. Special HTML characters in cell
95  data are properly replaced (using escape from cgi) if escapeText
96  is set True.
97  """
98  def __init__(self, tableAttr='', defaultCellAttr = [], useTableSorter=False, escapeText=False):
99  self.tableAttr = tableAttr
100  self.defaultCellAttr = defaultCellAttr
101  self.useTableSorter = useTableSorter
102  self.escapeText = escapeText
103  self.rows = [] # list of HTML strings with complete rows of the table
104 
105  def __str__(self):
106  return self.getHtml()
107 
108  def appendRow(self, cellData, rowAttr=''):
109  """Append a row to the table. cellData is a list of the contents of
110  the cells in the row. The data for each cell is either a string
111  with the contents of the cell, or a tuple where the first element
112  is the contents of the cell and the second element contains any
113  HTML tags. Special HTML characters are properly replaced (using
114  escape from cgi) if escapeText was set True when creating the
115  table. If the table uses Tablesorter, <th> is used instead of
116  <td> in the first row."""
117  r = '<tr%s>\n' % sep(rowAttr)
118  if self.useTableSorter and len(self.rows)==0:
119  # First row using Tablesorter - must use <th> instead of <td>
120  cellFormat = '<th%s>%s</th>\n'
121  else:
122  cellFormat = '<td%s>%s</td>\n'
123  iCell = 0
124  for c in cellData:
125  cellAttr = self.defaultCellAttr[iCell] if iCell<len(self.defaultCellAttr) else ''
126  if isinstance(c,tuple):
127  r += cellFormat % (sep(c[1]),escape(str(c[0])) if self.escapeText else str(c[0]))
128  else:
129  r += cellFormat % (sep(cellAttr),escape(str(c)) if self.escapeText else str(c))
130  iCell += 1
131  r += '</tr>'
132  self.rows.append(r)
133 
134  def getHtml(self):
135  """Return the HTML code for the table."""
136  if len(self.rows)<1:
137  return ''
138  if self.useTableSorter:
139  if self.tableAttr:
140  h = '<table %s>\n' % self.tableAttr
141  else:
142  h = '<table class="tablesorter">\n'
143  h += '<thead>\n'
144  h += self.rows[0]
145  h += '\n</thead><tbody>\n'
146  h += '\n'.join(self.rows[1:])
147  h += '\n</tbody></table>\n'
148  else:
149  h = '<table%s>\n' % sep(self.tableAttr)
150  h += '\n'.join(self.rows)
151  h += '\n</table>\n'
152  return h
153 
154 def htmlFoldingSection(header, content, isClosed=True,
155  headerClass='section-header',
156  contentClass='section-content'):
157  """Generate the html for a folding section using the toggleSection JavaScript utility
158  from WebPageUtils.js and CSS classes section-closed, section-open, and hidden."""
159  if isClosed:
160  s = '<div class="section-closed" onclick="toggleSection(this);">'
161  else:
162  s = '<div class="section-open" onclick="toggleSection(this);">'
163  s += '<span class="%s">%s</span></div>\n' % (headerClass,header)
164  h = ' hidden' if isClosed else ''
165  s += '<div class="%s%s">\n%s</div>\n' % (contentClass,h,content)
166  return s
167 
168 def htmlForm(contents, action='', method='post', attr=''):
169  snippet = '<form action="%s" method="%s"%s>\n' % (action,method,sep(attr))
170  snippet += '<fieldset>\n'
171  snippet += contents
172  snippet += '</fieldset>\n</form>\n'
173  return snippet
174 
175 def htmlLabel(labelText, parName, attr=''):
176  """Make a label for parName. If labelText is None,
177  an empty string is returned."""
178  if labelText!=None:
179  return '<label for="%s"%s>%s</label>' % (parName,sep(attr),labelText)
180  else:
181  return ''
182 
183 def htmlSelect(labelText, parName, args, choiceList, hint=None, descriptionSeparator='::',
184  labelAttr='', attr=''):
185  """Make a select statement (including label with text)."""
186  snippet = htmlLabel(labelText,parName,labelAttr)
187  default = args[parName] if parName in args else ''
188  if not isinstance(default,list):
189  default = [default]
190  snippet += '<select name="%s"%s>\n' % (parName,sep(attr))
191  if hint:
192  snippet += '<option value="">%s</option>\n' % hint
193  for c in choiceList:
194  p = c.split(descriptionSeparator)
195  if len(p)==2:
196  (desc,val) = p
197  else:
198  (desc,val) = (c,c)
199  if val in default:
200  snippet += '<option selected="yes" value="%s">%s</option>\n' % (val,desc)
201  else:
202  snippet += '<option value="%s">%s</option>\n' % (val,desc)
203  snippet += '</select>\n'
204  return snippet
205 
206 def htmlCheckbox(labelText, parName, args, labelAttr='', attr=''):
207  """Make a checkbox (including label with text)."""
208  snippet = htmlLabel(labelText,parName,labelAttr)
209  checked = 'checked="checked"' if parName in args else ''
210  snippet += '<input type="checkbox" name="%s"%s%s/>\n' % (parName,sep(checked),sep(attr))
211  return snippet
212 
213 def htmlTextInput(labelText, parName, args, size=None, maxLength = None, labelAttr='', attr=''):
214  """Make a text input area (including label with text). Special HTML
215  characters in any default text are properly replaced."""
216  snippet = htmlLabel(labelText,parName,labelAttr)
217  snippet += '<input type="text" name="%s"' % parName
218  if parName in args:
219  snippet += ' value="%s"' % escape(args[parName],True)
220  if size!=None:
221  snippet += ' size="%s"' % size
222  if maxLength!=None:
223  snippet += ' maxlength="%s"' % maxLength
224  snippet += '%s/>\n' % sep(attr)
225  return snippet
226 
227 def htmlSubmit(text, parName, attr='', onlyOnce=False):
228  """Make a submit button. If onlyOnce is true, the button can only
229  be clicked once in order to prevent multiple clicking of the
230  submit button."""
231  if onlyOnce:
232  # FIXME: this doesn't work yet - it disables submission
233  s = '<input type="button" name="%s" value="%s"' % (parName,text)
234  s += ' onclick="this.form.submit()"'
235  s += '%s />\n' % sep(attr)
236  return s
237  else:
238  return '<input type="submit" name="%s" value="%s"%s />\n' % (parName,text,sep(attr))
239 
240 
241 #
242 # Utility classes
243 #
244 class WebPageConfigurationError(Exception):
245  def __init__(self,*args):
246  Exception.__init__(self,args)
247 
248 
250  """Class to store global configuration data for a tree of web pages. Data members
251  are used to store information used by the framework, while the dict is
252  intended to be used for application-specific configuration data."""
253 
254  def __init__(self, **args):
255  self.baseUrl = 'webapp' # Common base path
256  self.pageList = [] # List of all web pages
257  self.pageAttributes = {} # Optional attributes for web pages, such as specific link styles
258 
259  # Initialize application-specific configuration data, if any
260  for k in args:
261  self[k] = args[k]
262 
263 
264 #
265 # Base class for all web pages
266 #
267 class WebPage:
268  """Base class for creating CSS-based dynamic web pages."""
269 
270  def __init__(self, **args):
271  """Constructor. You may override any of the default values in self.pageConfig by
272  passing the corresponding value in a named variable."""
273  self.pageConfig = { 'contentType': '', # For plain CGI need: 'Content-type: text/html\n\n'
274  'pageName': '',
275  'pageTitle': '',
276  'pageHeaderSnippets': '',
277  'cssName': 'default.css',
278  'css_currentLink': 'acurrentlink',
279  'header': '',
280  'navigation': '',
281  'content': '',
282  'footer': '',
283  'keepEmptyHeader': False, # Set to true to anyway generate div if empty
284  'keepEmptyNavigation': False,
285  'keepEmptyContent': False,
286  'keepEmptyFooter': False
287  }
288  for k in args:
289  self.pageConfig[k] = args[k]
291  self.isConfigured = False
292  pass
293 
294  def pageName(self):
295  """Short cut to retrieve the name (the last element in the URL)
296  of the current page. This works only if the page has been
297  added into the page tree using addPage."""
298  return self.pageConfig['pageName']
299 
300  def addPage(self, name, page, **attrs):
301  """Add a new web page to the page tree. All pages added via addPage
302  share the same GlobalConfiguration object."""
303  page.globalConfig = self.globalConfig
304  page.pageConfig['pageName'] = name
305  self.globalConfig.pageList.append(name)
306  self.globalConfig.pageAttributes[name] = dict(attrs)
307  setattr(self,name,page) # Link page into page tree (for CherryPy)
308 
309  def addLink(self, name, alias, **attrs):
310  """Add a new link to the page tree. Links share page objects with
311  other pages through aliasing. This allows using different links
312  with different queries to the same page as if they were separate
313  pages. For links, highlighting of the current page is disabled
314  (if enabled, all links leading to the same page would be
315  highlighted)."""
316  self.globalConfig.pageList.append(name)
317  self.globalConfig.pageAttributes[name] = dict(attrs)
318  self.globalConfig.pageAttributes[name]['alias'] = alias
319 
320  def addToPageHeader(self, snippet):
321  """Add a snippet of code to the page header. Use this e.g. to include JavaScript libraries."""
322  self.pageConfig['pageHeaderSnippets'] += snippet
323 
324  def index(self, **args):
325  """Return the complete page."""
326  if not self.isConfigured:
327  self.configure()
328  s = self.override()
329  if not s:
330  self.pageConfig['timeStamp'] = time.strftime('%a %b %d %X %Z %Y')
331  contents = self.content(**args) # Make sure contents is run first (so it
332  # can change any pageConfig entries if desired
333  s = startPage % self.pageConfig
334  s = s + htmlDiv('header', self.header(**args),
335  keepEmptyDiv=self.pageConfig['keepEmptyHeader'])
336  s = s + htmlDiv('navigation', self.navigation(**args),
337  keepEmptyDiv=self.pageConfig['keepEmptyNavigation'])
338  s = s + htmlDiv('content', contents,
339  keepEmptyDiv=self.pageConfig['keepEmptyContent'])
340  s = s + htmlDiv('footer', self.footer(**args),
341  keepEmptyDiv=self.pageConfig['keepEmptyFooter'])
342  s = s + endPage
343  return s
344  # For CherryPy
345  index.exposed = True
346 
347  def configure(self):
348  """Final configuration of web application after all data is initialized."""
349  self.configureLinks()
350  self.isConfigured = True
351  return
352 
353  def configureLinks(self):
354  """Based on configuration data, for each web page create two link entries in pageConfig:
355  The first entry has the form url_PAGENAME and contains the complete URL to link to
356  a page. The second entry is named href_PAGENAME and contains style information and a
357  href= prefix with proper quotation marks in addition to the URL. These link entries
358  should be used to generate links in HTML pages with snippets like <a %(href_PAGENAME)s>
359  or <a href="%(url_PAGENAME)>. The link entries can only be generated once all
360  configuration data is available, ie configureLinks must be called from configure
361  and not from __init__."""
362  for p in self.globalConfig.pageList:
363  page = self.globalConfig.pageAttributes[p].get('alias',p)
364  query = self.globalConfig.pageAttributes[p].get('query','')
365  style = self.globalConfig.pageAttributes[p].get('style',None)
366  self.pageConfig['url_'+p] = '%s/%s/%s' % (self.globalConfig.baseUrl,page,query)
367  if p==self.pageName():
368  self.pageConfig['href_'+p] = 'href="%s/%s/%s" class="%s"' % (self.globalConfig.baseUrl,page,query,self.pageConfig['css_currentLink'])
369  else:
370  if style:
371  self.pageConfig['href_'+p] = 'href="%s/%s/%s" class="%s"' % (self.globalConfig.baseUrl,page,query,style)
372  else:
373  self.pageConfig['href_'+p] = 'href="%s/%s/%s"' % (self.globalConfig.baseUrl,page,query)
374 
375  def override(self):
376  """Override provides a hook where code to generate or redirect to an alternative
377  page can be placed by derived classes. If not override of the normal page is
378  desired, override should return None. Otherwise it should either raise an
379  appropriate exception or return a string containing the complete alternate page
380  to display."""
381  return None
382 
383  def header(self, **args):
384  """Generate the page header. Default value comes from self.pageConfig['header']."""
385  return self.pageConfig['header'] % self.pageConfig
386 
387  def navigation(self, **args):
388  """Generate the navigation bar. Default value comes from self.pageConfig['navigation']."""
389  return self.pageConfig['navigation'] % self.pageConfig
390 
391  def content(self, **args):
392  """Generate the page content. Default value comes from self.pageConfig['content']."""
393  return self.pageConfig['content'] % self.pageConfig
394 
395  def footer(self, **args):
396  """Generate the footer. Default value comes from self.pageConfig['footer']."""
397  return self.pageConfig['footer'] % self.pageConfig
398 
399 
400 # Example configuration file on how to run CherryPy with Apache mod_python:
401 #
402 # <Location "/">
403 # PythonPath "sys.path+['/whatever']"
404 # SetHandler python-program
405 # PythonHandler cherrypy._cpmodpy::handler
406 # PythonOption cherrypy.setup WebPage::setup_server
407 # PythonDebug On
408 # </Location>
409 #
410 #import cherrypy
411 #def setup_server():
412 # cherrypy.config.update({'environment': 'production',
413 # 'log.screen': False,
414 # 'server.socket_host': '127.0.0.1',
415 # 'log.error_file': '/tmp/site.log',
416 # 'show_tracebacks': False})
417 # cherrypy.tree.mount(WebPage())
418 
419 
420 # Test code
421 if __name__ == '__main__':
422 
424  def content(self):
425  return "Hello, world!"
426 
427  # For plain CGI
428  #import cgi
429  #import cgitb; cgitb.enable()
430  #p = HelloWorld(pageTitle = 'HelloWorld Test', contentType='Content-type: text/html\n\n')
431  #print p.index()
432 
433  # For local CherryPy (run as `python WebPage.py')
434  # NOTE: see http://www.cherrypy.org/wiki/StaticContent on how to
435  # serve static content such as the css file
436  import cherrypy
437  cherrypy.quickstart(HelloWorld(pageTitle='HelloWorld Test',contentType=''))
WebPage.htmlTable.__init__
def __init__(self, tableAttr='', defaultCellAttr=[], useTableSorter=False, escapeText=False)
Definition: WebPage.py:98
WebPage.WebPage.globalConfig
globalConfig
Definition: WebPage.py:290
WebPage.HelloWorld.content
def content(self)
Definition: WebPage.py:424
WebPage.WebPage.addToPageHeader
def addToPageHeader(self, snippet)
Definition: WebPage.py:320
WebPage.WebPage.addPage
def addPage(self, name, page, **attrs)
Definition: WebPage.py:300
WebPage.htmlLink
def htmlLink(text, link, attr='', escapeText=False)
Definition: WebPage.py:77
WebPage.WebPage.content
def content(self, **args)
Definition: WebPage.py:391
WebPage.htmlForm
def htmlForm(contents, action='', method='post', attr='')
Definition: WebPage.py:168
WebPage.WebPage
Definition: WebPage.py:267
WebPage.htmlTable.appendRow
def appendRow(self, cellData, rowAttr='')
Definition: WebPage.py:108
dumpHVPathFromNtuple.append
bool append
Definition: dumpHVPathFromNtuple.py:91
WebPage.WebPage.__init__
def __init__(self, **args)
Definition: WebPage.py:270
WebPage.htmlTable.getHtml
def getHtml(self)
Definition: WebPage.py:134
WebPage.WebPage.header
def header(self, **args)
Definition: WebPage.py:383
WebPage.WebPage.index
def index(self, **args)
Definition: WebPage.py:324
WebPage.htmlPara
def htmlPara(text='', attr='', escapeText=False)
Definition: WebPage.py:70
WebPage.WebPage.addLink
def addLink(self, name, alias, **attrs)
Definition: WebPage.py:309
WebPage.htmlTable.__str__
def __str__(self)
Definition: WebPage.py:105
WebPage.htmlTable.tableAttr
tableAttr
Definition: WebPage.py:99
WebPage.WebPageConfigurationError
Definition: WebPage.py:244
WebPage.WebPage.override
def override(self)
Definition: WebPage.py:375
WebPage.htmlTextInput
def htmlTextInput(labelText, parName, args, size=None, maxLength=None, labelAttr='', attr='')
Definition: WebPage.py:213
WebPage.htmlCheckbox
def htmlCheckbox(labelText, parName, args, labelAttr='', attr='')
Definition: WebPage.py:206
WebPage.GlobalConfiguration.__init__
def __init__(self, **args)
Definition: WebPage.py:254
WebPage.htmlDiv
def htmlDiv(id, contents='', attr='', keepEmptyDiv=True)
Definition: WebPage.py:46
WebPage.sep
def sep(s)
Definition: WebPage.py:42
WebPage.GlobalConfiguration
Definition: WebPage.py:249
WebPage.htmlList
def htmlList(contents, attr='', listType='ul')
Definition: WebPage.py:80
WebPage.WebPage.footer
def footer(self, **args)
Definition: WebPage.py:395
WebPage.htmlLI
def htmlLI(text, attr='', escapeText=False)
Definition: WebPage.py:85
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
WebPage.GlobalConfiguration.pageAttributes
pageAttributes
Definition: WebPage.py:257
WebPage.HelloWorld
Definition: WebPage.py:423
WebPage.GlobalConfiguration.pageList
pageList
Definition: WebPage.py:256
WebPage.htmlText
def htmlText(text, attr='', escapeText=False)
Definition: WebPage.py:58
WebPage.WebPage.isConfigured
isConfigured
Definition: WebPage.py:291
WebPage.WebPage.pageName
def pageName(self)
Definition: WebPage.py:294
WebPage.WebPage.navigation
def navigation(self, **args)
Definition: WebPage.py:387
WebPage.htmlSelect
def htmlSelect(labelText, parName, args, choiceList, hint=None, descriptionSeparator='::', labelAttr='', attr='')
Definition: WebPage.py:183
WebPage.htmlLabel
def htmlLabel(labelText, parName, attr='')
Definition: WebPage.py:175
get
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition: hcg.cxx:127
WebPage.GlobalConfiguration.baseUrl
baseUrl
Definition: WebPage.py:255
WebPage.htmlFoldingSection
def htmlFoldingSection(header, content, isClosed=True, headerClass='section-header', contentClass='section-content')
Definition: WebPage.py:154
str
Definition: BTagTrackIpAccessor.cxx:11
WebPage.htmlTable.useTableSorter
useTableSorter
Definition: WebPage.py:101
WebPage.htmlTable.rows
rows
Definition: WebPage.py:103
WebPage.htmlPre
def htmlPre(text, attr='', escapeText=False)
Definition: WebPage.py:64
WebPage.htmlTable.defaultCellAttr
defaultCellAttr
Definition: WebPage.py:100
WebPage.WebPageConfigurationError.__init__
def __init__(self, *args)
Definition: WebPage.py:245
WebPage.WebPage.configure
def configure(self)
Definition: WebPage.py:347
WebPage.htmlTable.escapeText
escapeText
Definition: WebPage.py:102
WebPage.htmlSubmit
def htmlSubmit(text, parName, attr='', onlyOnce=False)
Definition: WebPage.py:227
WebPage.WebPage.configureLinks
def configureLinks(self)
Definition: WebPage.py:353
WebPage.htmlTable
Definition: WebPage.py:91
WebPage.WebPage.pageConfig
pageConfig
Definition: WebPage.py:273