2014-11-27 14:11:47 +01:00
|
|
|
import os
|
2014-12-04 21:04:41 +01:00
|
|
|
import codecs
|
2014-12-11 21:13:24 +01:00
|
|
|
import time
|
2014-11-21 10:41:29 +01:00
|
|
|
|
2014-12-09 16:54:02 +01:00
|
|
|
#
|
|
|
|
# Create output HTML files
|
|
|
|
#
|
|
|
|
|
2014-11-27 19:38:41 +01:00
|
|
|
class DisplayHTMLRaw(object):
|
2014-11-21 16:56:58 +01:00
|
|
|
|
2014-12-08 14:13:26 +01:00
|
|
|
def __init__(self, iwla, html=u''):
|
|
|
|
self.iwla = iwla
|
2014-11-27 19:38:41 +01:00
|
|
|
self.html = html
|
|
|
|
|
|
|
|
def setRawHTML(self, html):
|
|
|
|
self.html = html
|
2014-11-21 16:56:58 +01:00
|
|
|
|
2014-12-01 21:45:26 +01:00
|
|
|
def _buildHTML(self):
|
|
|
|
pass
|
|
|
|
|
2014-11-27 19:38:41 +01:00
|
|
|
def _build(self, f, html):
|
|
|
|
if html: f.write(html)
|
2014-12-01 21:45:26 +01:00
|
|
|
|
2014-11-21 16:56:58 +01:00
|
|
|
def build(self, f):
|
2014-12-11 21:13:24 +01:00
|
|
|
# t1 = time.time()
|
2014-12-01 21:45:26 +01:00
|
|
|
self._buildHTML()
|
2014-12-11 21:13:24 +01:00
|
|
|
# t2 = time.time()
|
|
|
|
# print 'Time for _buildHTML : %d seconds' % (t2-t1)
|
|
|
|
# t1 = time.time()
|
2014-12-01 21:45:26 +01:00
|
|
|
self._build(f, self.html)
|
2014-12-11 21:13:24 +01:00
|
|
|
# t2 = time.time()
|
|
|
|
# print 'Time for _build : %d seconds' % (t2-t1)
|
2014-11-21 16:56:58 +01:00
|
|
|
|
2014-11-27 19:38:41 +01:00
|
|
|
class DisplayHTMLBlock(DisplayHTMLRaw):
|
2014-11-25 16:59:29 +01:00
|
|
|
|
2014-12-08 14:13:26 +01:00
|
|
|
def __init__(self, iwla, title=''):
|
|
|
|
super(DisplayHTMLBlock, self).__init__(iwla, html='')
|
2014-11-27 19:38:41 +01:00
|
|
|
self.title = title
|
2014-12-04 21:04:41 +01:00
|
|
|
self.cssclass = u'iwla_block'
|
|
|
|
self.title_cssclass = u'iwla_block_title'
|
|
|
|
self.value_cssclass = u'iwla_block_value'
|
2014-11-25 16:59:29 +01:00
|
|
|
|
2014-11-27 19:38:41 +01:00
|
|
|
def getTitle(self):
|
|
|
|
return self.title
|
|
|
|
|
|
|
|
def setTitle(self, value):
|
2014-12-04 21:04:41 +01:00
|
|
|
self.title = unicode(value)
|
2014-11-27 19:38:41 +01:00
|
|
|
|
|
|
|
def setCSSClass(self, cssclass):
|
2014-12-04 21:04:41 +01:00
|
|
|
self.cssclass = unicode(cssclass)
|
2014-11-27 19:38:41 +01:00
|
|
|
|
|
|
|
def setTitleCSSClass(self, cssclass):
|
2014-12-04 21:04:41 +01:00
|
|
|
self.title_cssclass = unicode(cssclass)
|
2014-11-27 19:38:41 +01:00
|
|
|
|
|
|
|
def setValueCSSClass(self, cssclass):
|
2014-12-04 21:04:41 +01:00
|
|
|
self.value_cssclass = unicode(cssclass)
|
2014-11-25 16:59:29 +01:00
|
|
|
|
2014-12-01 21:45:26 +01:00
|
|
|
def _buildHTML(self):
|
2014-12-04 21:04:41 +01:00
|
|
|
html = u'<div class="%s">' % (self.cssclass)
|
2014-11-27 19:38:41 +01:00
|
|
|
if self.title:
|
2014-12-04 21:04:41 +01:00
|
|
|
html += u'<div class="%s">%s</div>' % (self.title_cssclass, self.title)
|
|
|
|
html += u'<div class="%s">%s</div>' % (self.value_cssclass, self.html)
|
|
|
|
html += u'</div>'
|
2014-11-27 19:38:41 +01:00
|
|
|
|
2014-12-01 21:45:26 +01:00
|
|
|
self.html = html
|
2014-11-25 16:59:29 +01:00
|
|
|
|
2014-11-21 16:56:58 +01:00
|
|
|
class DisplayHTMLBlockTable(DisplayHTMLBlock):
|
|
|
|
|
2014-12-08 14:13:26 +01:00
|
|
|
def __init__(self, iwla, title, cols):
|
|
|
|
super(DisplayHTMLBlockTable, self).__init__(iwla=iwla, title=title)
|
2014-12-04 21:04:41 +01:00
|
|
|
self.cols = listToStr(cols)
|
2014-11-21 16:56:58 +01:00
|
|
|
self.rows = []
|
2014-12-04 21:04:41 +01:00
|
|
|
self.cols_cssclasses = [u''] * len(cols)
|
2014-11-27 19:38:41 +01:00
|
|
|
self.rows_cssclasses = []
|
2014-12-04 21:04:41 +01:00
|
|
|
self.table_css = u'iwla_table'
|
2014-11-21 16:56:58 +01:00
|
|
|
|
|
|
|
def appendRow(self, row):
|
2014-11-24 13:44:04 +01:00
|
|
|
self.rows.append(listToStr(row))
|
2014-12-04 21:04:41 +01:00
|
|
|
self.rows_cssclasses.append([u''] * len(row))
|
2014-11-27 19:38:41 +01:00
|
|
|
|
2014-12-05 16:03:09 +01:00
|
|
|
def getNbRows(self):
|
|
|
|
return len(self.rows)
|
|
|
|
|
|
|
|
def getNbCols(self):
|
|
|
|
return len(self.cols)
|
|
|
|
|
2014-11-27 21:40:23 +01:00
|
|
|
def getCellValue(self, row, col):
|
2014-11-27 19:38:41 +01:00
|
|
|
if row < 0 or col < 0 or\
|
|
|
|
row >= len(self.rows) or col >= len(self.cols):
|
2014-11-27 21:40:23 +01:00
|
|
|
raise ValueError('Invalid indices %d,%d' % (row, col))
|
2014-11-27 19:38:41 +01:00
|
|
|
|
|
|
|
return self.rows[row][col]
|
|
|
|
|
2014-11-27 21:40:23 +01:00
|
|
|
def setCellValue(self, row, col, value):
|
2014-11-27 19:38:41 +01:00
|
|
|
if row < 0 or col < 0 or\
|
|
|
|
row >= len(self.rows) or col >= len(self.cols):
|
2014-11-27 21:40:23 +01:00
|
|
|
raise ValueError('Invalid indices %d,%d' % (row, col))
|
2014-11-27 19:38:41 +01:00
|
|
|
|
2014-12-04 21:04:41 +01:00
|
|
|
self.rows[row][col] = unicode(value)
|
2014-11-21 16:56:58 +01:00
|
|
|
|
2014-11-27 21:40:23 +01:00
|
|
|
def setCellCSSClass(self, row, col, value):
|
2014-11-27 19:38:41 +01:00
|
|
|
if row < 0 or col < 0 or\
|
|
|
|
row >= len(self.rows) or col >= len(self.cols):
|
2014-11-27 21:40:23 +01:00
|
|
|
raise ValueError('Invalid indices %d,%d' % (row, col))
|
2014-11-27 19:38:41 +01:00
|
|
|
|
2014-12-04 21:04:41 +01:00
|
|
|
self.rows_cssclasses[row][col] = unicode(value)
|
2014-11-27 19:38:41 +01:00
|
|
|
|
2014-11-27 21:40:23 +01:00
|
|
|
def getCellCSSClass(self, row, col):
|
|
|
|
if row < 0 or col < 0 or\
|
|
|
|
row >= len(self.rows) or col >= len(self.cols):
|
|
|
|
raise ValueError('Invalid indices %d,%d' % (row, col))
|
|
|
|
|
|
|
|
return self.rows_cssclasses[row][col]
|
|
|
|
|
|
|
|
def getColCSSClass(self, col):
|
|
|
|
if col < 0 or col >= len(self.cols):
|
|
|
|
raise ValueError('Invalid indice %d' % (col))
|
|
|
|
|
|
|
|
return self.cols_cssclasses[col]
|
|
|
|
|
|
|
|
def setRowCSSClass(self, row, value):
|
2014-11-27 19:38:41 +01:00
|
|
|
if row < 0 or row >= len(self.rows):
|
2014-11-27 21:40:23 +01:00
|
|
|
raise ValueError('Invalid indice %d' % (row))
|
2014-11-27 19:38:41 +01:00
|
|
|
|
2014-12-04 21:04:41 +01:00
|
|
|
self.rows_cssclasses[row] = [unicode(value)] * len(self.rows_cssclasses[row])
|
2014-11-27 21:40:23 +01:00
|
|
|
|
|
|
|
def setColCSSClass(self, col, value):
|
|
|
|
if col < 0 or col >= len(self.cols):
|
|
|
|
raise ValueError('Invalid indice %d' % (col))
|
|
|
|
|
2014-12-04 21:04:41 +01:00
|
|
|
self.cols_cssclasses[col] = unicode(value)
|
2014-11-27 21:40:23 +01:00
|
|
|
|
|
|
|
def setColsCSSClass(self, values):
|
|
|
|
if len(values) != len(self.cols):
|
|
|
|
raise ValueError('Invalid values size')
|
|
|
|
|
2014-12-04 21:47:11 +01:00
|
|
|
self.cols_cssclasses = listToStr(values)
|
2014-11-27 21:40:23 +01:00
|
|
|
|
2014-12-01 21:45:26 +01:00
|
|
|
def _buildHTML(self):
|
2014-12-04 21:04:41 +01:00
|
|
|
style = u''
|
|
|
|
if self.table_css: style = u' class="%s"' % (self.table_css)
|
|
|
|
html = u'<table%s>' % (style)
|
2014-12-01 21:45:26 +01:00
|
|
|
if self.cols:
|
2014-12-04 21:04:41 +01:00
|
|
|
html += u'<tr>'
|
2014-12-01 21:45:26 +01:00
|
|
|
for i in range (0, len(self.cols)):
|
|
|
|
title = self.cols[i]
|
|
|
|
style = self.getColCSSClass(i)
|
2014-12-04 21:04:41 +01:00
|
|
|
if style: style = u' class="%s"' % (style)
|
|
|
|
html += u'<th%s>%s</th>' % (style, title)
|
|
|
|
html += u'</tr>'
|
2014-12-01 21:45:26 +01:00
|
|
|
for i in range(0, len(self.rows)):
|
|
|
|
row = self.rows[i]
|
2014-12-04 21:04:41 +01:00
|
|
|
html += u'<tr>'
|
2014-12-01 21:45:26 +01:00
|
|
|
for j in range(0, len(row)):
|
|
|
|
v = row[j]
|
|
|
|
style = self.getCellCSSClass(i, j)
|
2014-12-04 21:04:41 +01:00
|
|
|
if style: style = u' class="%s"' % (style)
|
|
|
|
html += u'<td%s>%s</td>' % (style, v)
|
|
|
|
html += u'</tr>'
|
|
|
|
html += u'</table>'
|
2014-12-01 21:45:26 +01:00
|
|
|
|
2014-12-02 16:53:54 +01:00
|
|
|
self.html += html
|
2014-12-01 21:45:26 +01:00
|
|
|
|
|
|
|
super(DisplayHTMLBlockTable, self)._buildHTML()
|
|
|
|
|
|
|
|
class DisplayHTMLBlockTableWithGraph(DisplayHTMLBlockTable):
|
|
|
|
|
2014-12-08 14:13:26 +01:00
|
|
|
def __init__(self, iwla, title, cols, short_titles=None, nb_valid_rows=0, graph_cols=None):
|
|
|
|
super(DisplayHTMLBlockTableWithGraph, self).__init__(iwla=iwla, title=title, cols=cols)
|
2014-12-03 21:58:55 +01:00
|
|
|
self.short_titles = short_titles or []
|
2014-12-04 21:04:41 +01:00
|
|
|
self.short_titles = listToStr(self.short_titles)
|
2014-12-01 21:45:26 +01:00
|
|
|
self.nb_valid_rows = nb_valid_rows
|
2014-12-08 14:13:26 +01:00
|
|
|
self.icon_path = self.iwla.getConfValue('icon_path', '/')
|
2014-12-02 16:53:54 +01:00
|
|
|
self.raw_rows = []
|
2014-12-02 21:16:27 +01:00
|
|
|
self.maxes = [0] * len(cols)
|
2014-12-04 21:04:41 +01:00
|
|
|
self.table_graph_css = u'iwla_graph_table'
|
|
|
|
self.td_img_css = u'iwla_td_img'
|
2014-12-04 19:15:15 +01:00
|
|
|
self.graph_cols = graph_cols or []
|
2014-12-02 16:53:54 +01:00
|
|
|
|
|
|
|
def appendRow(self, row):
|
|
|
|
self.raw_rows.append(row)
|
|
|
|
super(DisplayHTMLBlockTableWithGraph, self).appendRow(row)
|
|
|
|
|
|
|
|
def appendShortTitle(self, short_title):
|
2014-12-04 21:04:41 +01:00
|
|
|
self.short_titles.append(unicode(short_title))
|
2014-12-02 16:53:54 +01:00
|
|
|
|
|
|
|
def setShortTitle(self, short_titles):
|
2014-12-04 21:04:41 +01:00
|
|
|
self.short_titles = listToStr(short_titles)
|
2014-12-01 21:45:26 +01:00
|
|
|
|
|
|
|
def setNbValidRows(self, nb_valid_rows):
|
|
|
|
self.nb_valid_rows = nb_valid_rows
|
2014-11-21 16:56:58 +01:00
|
|
|
|
2014-12-02 16:53:54 +01:00
|
|
|
def _computeMax(self):
|
|
|
|
for i in range(0, self.nb_valid_rows):
|
|
|
|
row = self.raw_rows[i]
|
|
|
|
for j in range(1, len(row)):
|
|
|
|
if row[j] > self.maxes[j]:
|
|
|
|
self.maxes[j] = row[j]
|
|
|
|
|
|
|
|
def _getIconFromStyle(self, style):
|
2014-12-04 21:04:41 +01:00
|
|
|
if style.startswith(u'iwla_page'): icon = u'vp.png'
|
|
|
|
elif style.startswith(u'iwla_hit'): icon = u'vh.png'
|
|
|
|
elif style.startswith(u'iwla_bandwidth'): icon = u'vk.png'
|
|
|
|
elif style.startswith(u'iwla_visitor'): icon = u'vu.png'
|
|
|
|
elif style.startswith(u'iwla_visit'): icon = u'vv.png'
|
2014-12-02 16:53:54 +01:00
|
|
|
else: return ''
|
|
|
|
|
2014-12-08 14:13:26 +01:00
|
|
|
return u'/%s/%s' % (self.icon_path, icon)
|
2014-12-02 16:53:54 +01:00
|
|
|
|
|
|
|
def _buildHTML(self):
|
|
|
|
self._computeMax()
|
|
|
|
|
2014-12-04 21:04:41 +01:00
|
|
|
style = u''
|
|
|
|
if self.table_graph_css: style = u' class="%s"' % (self.table_graph_css)
|
|
|
|
html = u'<table%s>' % (style)
|
|
|
|
html += u'<tr>'
|
2014-12-02 16:53:54 +01:00
|
|
|
for i in range(0, self.nb_valid_rows):
|
|
|
|
row = self.rows[i]
|
2014-12-04 21:04:41 +01:00
|
|
|
css = u''
|
|
|
|
if self.td_img_css: css=u' class="%s"' % (self.td_img_css)
|
|
|
|
html += u'<td%s>' % (css)
|
2014-12-04 19:15:15 +01:00
|
|
|
for j in self.graph_cols:
|
2014-12-02 16:53:54 +01:00
|
|
|
style = self.getColCSSClass(j)
|
|
|
|
icon = self._getIconFromStyle(style)
|
|
|
|
if not icon: continue
|
2014-12-04 21:04:41 +01:00
|
|
|
if style: style = u' class="%s"' % (style)
|
|
|
|
alt = u'%s: %s' % (row[j], self.cols[j])
|
2014-12-02 16:53:54 +01:00
|
|
|
if self.maxes[j]:
|
2014-12-02 21:53:20 +01:00
|
|
|
height = int((self.raw_rows[i][j] * 100) / self.maxes[j]) or 1
|
2014-12-02 16:53:54 +01:00
|
|
|
else:
|
2014-12-02 21:53:20 +01:00
|
|
|
height = 1
|
2014-12-04 21:04:41 +01:00
|
|
|
html += u'<img%s src="%s" height="%d" width="6" alt="%s" title="%s" />' % (style, icon, height, alt, alt)
|
|
|
|
html += u'</td>'
|
|
|
|
html += u'</tr>'
|
|
|
|
html += u'<tr>'
|
2014-12-02 16:53:54 +01:00
|
|
|
for i in range(0, len(self.short_titles)):
|
2014-12-02 21:16:27 +01:00
|
|
|
style = self.getCellCSSClass(i, 0)
|
2014-12-04 21:04:41 +01:00
|
|
|
if style: style = u' class="%s"' % (style)
|
|
|
|
html += u'<td%s>%s</td>' % (style, self.short_titles[i])
|
|
|
|
html += u'</tr>'
|
|
|
|
html += u'</table>'
|
2014-12-02 16:53:54 +01:00
|
|
|
|
|
|
|
self.html += html
|
|
|
|
|
|
|
|
super(DisplayHTMLBlockTableWithGraph, self)._buildHTML()
|
|
|
|
|
2014-11-21 16:56:58 +01:00
|
|
|
class DisplayHTMLPage(object):
|
|
|
|
|
2014-12-08 14:13:26 +01:00
|
|
|
def __init__(self, iwla, title, filename, css_path):
|
|
|
|
self.iwla = iwla
|
2014-12-04 21:04:41 +01:00
|
|
|
self.title = unicode(title)
|
2014-11-21 16:56:58 +01:00
|
|
|
self.filename = filename
|
|
|
|
self.blocks = []
|
2014-12-04 21:04:41 +01:00
|
|
|
self.css_path = listToStr(css_path)
|
2014-11-21 16:56:58 +01:00
|
|
|
|
|
|
|
def getFilename(self):
|
|
|
|
return self.filename;
|
|
|
|
|
2014-11-27 19:38:41 +01:00
|
|
|
def getBlock(self, title):
|
|
|
|
for b in self.blocks:
|
|
|
|
if title == b.getTitle():
|
|
|
|
return b
|
|
|
|
return None
|
|
|
|
|
2014-11-21 16:56:58 +01:00
|
|
|
def appendBlock(self, block):
|
|
|
|
self.blocks.append(block)
|
|
|
|
|
|
|
|
def build(self, root):
|
2014-12-10 22:17:11 +01:00
|
|
|
filename = os.path.join(root, self.filename)
|
2014-11-27 14:11:47 +01:00
|
|
|
|
|
|
|
base = os.path.dirname(filename)
|
|
|
|
if not os.path.exists(base):
|
|
|
|
os.makedirs(base)
|
|
|
|
|
2014-12-04 21:04:41 +01:00
|
|
|
f = codecs.open(filename, 'w', 'utf-8')
|
|
|
|
f.write(u'<!DOCTYPE html>')
|
|
|
|
f.write(u'<html>')
|
|
|
|
f.write(u'<head>')
|
|
|
|
f.write(u'<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />')
|
2014-11-30 19:05:17 +01:00
|
|
|
for css in self.css_path:
|
2014-12-04 21:47:11 +01:00
|
|
|
f.write(u'<link rel="stylesheet" href="/%s"/>' % (css))
|
2014-11-27 19:38:41 +01:00
|
|
|
if self.title:
|
2014-12-04 21:04:41 +01:00
|
|
|
f.write(u'<title>%s</title>' % (self.title))
|
|
|
|
f.write(u'</head>')
|
2014-11-21 16:56:58 +01:00
|
|
|
for block in self.blocks:
|
|
|
|
block.build(f)
|
2014-12-08 14:13:26 +01:00
|
|
|
f.write(u'<center>Generated by <a href="%s">IWLA %s</a></center>' %
|
|
|
|
("http://indefero.soutade.fr/p/iwla", self.iwla.getVersion()))
|
2014-12-04 21:04:41 +01:00
|
|
|
f.write(u'</body></html>')
|
2014-11-21 16:56:58 +01:00
|
|
|
f.close()
|
|
|
|
|
|
|
|
class DisplayHTMLBuild(object):
|
|
|
|
|
2014-11-30 19:05:17 +01:00
|
|
|
def __init__(self, iwla):
|
2014-11-21 16:56:58 +01:00
|
|
|
self.pages = []
|
2014-11-30 19:05:17 +01:00
|
|
|
self.iwla = iwla
|
2014-11-21 16:56:58 +01:00
|
|
|
|
2014-12-08 14:13:26 +01:00
|
|
|
def createPage(self, *args):
|
|
|
|
return DisplayHTMLPage(self.iwla, *args)
|
|
|
|
|
|
|
|
def createBlock(self, _class, *args):
|
|
|
|
return _class(self.iwla, *args)
|
|
|
|
|
2014-11-21 16:56:58 +01:00
|
|
|
def getPage(self, filename):
|
|
|
|
for page in self.pages:
|
|
|
|
if page.getFilename() == filename:
|
|
|
|
return page
|
|
|
|
return None
|
|
|
|
|
|
|
|
def addPage(self, page):
|
|
|
|
self.pages.append(page)
|
|
|
|
|
|
|
|
def build(self, root):
|
2014-11-30 19:05:17 +01:00
|
|
|
display_root = self.iwla.getConfValue('DISPLAY_ROOT', '')
|
2014-12-14 15:28:12 +01:00
|
|
|
if not os.path.exists(display_root):
|
|
|
|
os.makedirs(display_root)
|
2014-11-30 19:05:17 +01:00
|
|
|
for res_path in self.iwla.getResourcesPath():
|
|
|
|
target = os.path.abspath(res_path)
|
|
|
|
link_name = os.path.join(display_root, res_path)
|
|
|
|
if not os.path.exists(link_name):
|
|
|
|
os.symlink(target, link_name)
|
|
|
|
|
2014-11-21 16:56:58 +01:00
|
|
|
for page in self.pages:
|
2014-12-11 22:31:40 +01:00
|
|
|
# print 'Build %s' % (page.filename)
|
2014-11-21 16:56:58 +01:00
|
|
|
page.build(root)
|
2014-12-11 22:31:40 +01:00
|
|
|
# print 'Built'
|
2014-11-24 13:44:04 +01:00
|
|
|
|
2014-12-09 16:54:02 +01:00
|
|
|
#
|
|
|
|
# Global functions
|
|
|
|
#
|
|
|
|
|
2014-11-24 13:44:04 +01:00
|
|
|
def bytesToStr(bytes):
|
2014-12-04 21:04:41 +01:00
|
|
|
suffixes = [u'', u' kB', u' MB', u' GB', u' TB']
|
2014-11-24 13:44:04 +01:00
|
|
|
|
|
|
|
for i in range(0, len(suffixes)):
|
|
|
|
if bytes < 1024: break
|
|
|
|
bytes /= 1024.0
|
|
|
|
|
|
|
|
if i:
|
2014-12-04 21:04:41 +01:00
|
|
|
return u'%.02f%s' % (bytes, suffixes[i])
|
2014-11-24 13:44:04 +01:00
|
|
|
else:
|
2014-12-04 21:04:41 +01:00
|
|
|
return u'%d%s' % (bytes, suffixes[i])
|
2014-11-24 13:44:04 +01:00
|
|
|
|
|
|
|
def _toStr(v):
|
2014-12-04 21:04:41 +01:00
|
|
|
if type(v) != unicode: return unicode(v)
|
2014-11-24 13:44:04 +01:00
|
|
|
else: return v
|
|
|
|
|
|
|
|
def listToStr(l): return map(lambda(v) : _toStr(v), l)
|
2014-12-04 21:04:41 +01:00
|
|
|
|
|
|
|
def generateHTMLLink(url, name=None, max_length=100, prefix=u'http'):
|
|
|
|
url = unicode(url)
|
|
|
|
if not name: name = unicode(url)
|
|
|
|
if not url.startswith(prefix): url = u'%s://%s' % (prefix, url)
|
|
|
|
return u'<a href="%s">%s</a>' % (url, name[:max_length])
|