nulib: génération d'une table

This commit is contained in:
Jephté Clain 2018-05-08 15:20:45 +04:00
parent 51f4e92c3d
commit 0bbcef690b
5 changed files with 285 additions and 43 deletions

View File

@ -36,7 +36,36 @@ mydir, myname = path.split(myself)
# Fonctions diverses # Fonctions diverses
_undef = object()
class Undef(object): class Undef(object):
def sa(self, value, kw, name, default=_undef):
"""si value est Undef, récupérer la valeur avec le nom court name dans kw
"""
if default is _undef: default = self
if value is self and name is not None: value = kw.pop(name, self)
if value is self: value = default
return value
def __nonzero__(self):
return False
def __len__(self):
return 0
def __lt__(self, other):
if other: return True
else: return False
def __le__(self, other):
return True
def __eq__(self, other):
if other: return False
else: return True
def __ne__(self, other):
if other: return True
else: return False
def __gt__(self, other):
if other: return False
else: return True
def __ge__(self, other):
return True
def __repr__(self): def __repr__(self):
return 'Undef' return 'Undef'
def __call__(self): def __call__(self):

View File

@ -1,7 +1,11 @@
# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 # -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
__all__ = ('_weburl', '_ensure_session') __all__ = (
'_weburl', '_ensure_session',
'u', 'uval',
)
from types import UnicodeType, StringType
from ..ext import web from ..ext import web
def _weburl(path, __BASEURL, **kw): def _weburl(path, __BASEURL, **kw):
@ -14,3 +18,21 @@ def _ensure_session(page):
if session is None: raise ValueError("no session configured") if session is None: raise ValueError("no session configured")
page.session = session page.session = session
return session return session
def u(text):
"""Si text n'est pas de l'unicode, le convertir en unicode avec l'encoding utf-8
"""
t = type(text)
if t is not UnicodeType:
if t is not StringType: text = str(text)
text = unicode(text, "utf-8")
return text
def uval(value):
"""Transformer value en unicode, en particulier s'il est callable ou s'il
a une méthode render()
"""
if value is None: return None
if hasattr(value, 'render'): value = value.render()
if callable(value): value = value()
return u(value)

View File

@ -2,19 +2,22 @@
__all__ = ( __all__ = (
'load', 'load',
'table', 'tr', 'td',
'Menu', 'Action',
'About', 'About',
'Alert', 'set_alert', 'Alert', 'set_alert',
'Menu', 'Action',
) )
import sys, traceback import sys, traceback
from types import StringType
from ..base import seqof, Undef from ..base import isseq, seqof, Undef
from ..ext import web from ..ext import web
from . import ui from . import ui
from .ui import Action, u, p from .api import _ensure_session, u, uval
from .api import _ensure_session from .model import Row, TableSchema
from .ui import Action, p
Undef = Undef() # spécifique à ce module Undef = Undef() # spécifique à ce module
@ -43,12 +46,98 @@ def load(prefix=None, icons=True, charset="utf-8"):
]) ])
return u"\n".join(lines) return u"\n".join(lines)
def sa(value, kw, name, default=Undef): ################################################################################
"""si value est indéfini, récupérer la valeur avec le nom court dans kw # Table
"""
if value is Undef and name is not None: value = kw.pop(name, Undef) TRD_CLASS_MAP = dict(
if value is Undef: value = default active="active",
return value success="success", done="success",
info="info",
warning="warning", warn="warning", notice="warning",
danger="danger", error="danger",
)
def trd_joinc(css):
if css is Undef: css = None
if type(css) is StringType:
css = css.split(",")
classes = []
for css in seqof(css):
classes.append(TRD_CLASS_MAP.get(css, css))
css = ' '.join(classes)
return css
def td(value, schema=None, css=Undef, tag=Undef, **kw):
css = Undef.sa(css, kw, 'c')
value = uval(value)
if schema:
value = schema.html(value)
if css is Undef: css = schema.css(value)
if tag is Undef: tag = 'td'
css = trd_joinc(css)
return u'<%s%s>%s</%s>' % (tag, ui.classif(css), value, tag)
def tr(values, schema=None, css=Undef, celltag=Undef, **kw):
css = Undef.sa(css, kw, 'c')
tdschema = lambda value, index: None
if schema is not None:
if css is Undef: css = schema.css(values)
tdschema = lambda value, index: schema.cell_schema(value, index)
css = trd_joinc(css)
lines = []
lines.append(u'<tr%s>' % ui.classif(css))
index = 0
if isinstance(values, dict) or isinstance(values, Row):
if schema is not None:
for col in schema.cols:
value = values[col.key]
lines.append(td(value, tdschema(value, index), tag=celltag))
index += 1
else:
for value in values.values():
lines.append(td(value, tdschema(value, index), tag=celltag))
index += 1
else:
for value in values:
lines.append(td(value, tdschema(value, index), tag=celltag))
index += 1
lines.append(u'</tr>')
return u'\n'.join(lines)
TABLE_CLASS_MAP = dict(
auto="table-auto", a="table-auto",
striped="table-striped", strip="table-striped", s="table-striped",
bordered="table-bordered", border="table-bordered", b="table-bordered",
hover="table-hover", h="table-hover",
condensed="table-condensed", c="table-condensed",
)
def table_joinc(css):
if css is Undef: css = None
if type(css) is StringType:
css = css.split(",")
classes = ['table']
for css in seqof(css):
classes.append(TABLE_CLASS_MAP.get(css, css))
css = ' '.join(classes)
return css
def table(rows, schema=None, css=Undef, **kw):
css = Undef.sa(css, kw, 'c')
if schema is None: schema = TableSchema.parse(rows)
elif isseq(schema): schema = TableSchema(schema)
if css is Undef: css = schema.css(rows)
css = table_joinc(css)
lines = []
lines.append(u'<table%s>' % ui.classif(css))
lines.append(u'<thead>')
lines.append(tr(schema.headers(), schema.header_row_schema(), celltag='th'))
lines.append(u'</thead><tbody>')
index = 0
for row in rows:
lines.append(tr(row, schema.body_row_schema(index)))
index += 1
lines.append(u'</tbody>')
lines.append(u'</table>')
return u'\n'.join(lines)
################################################################################ ################################################################################
# Menu # Menu
@ -265,12 +354,12 @@ class Alert(object):
showtb = None showtb = None
def __init__(self, msg=None, exc_info=Undef, type=Undef, closeable=Undef, escape=Undef, action=None, showtb=True, **kw): def __init__(self, msg=None, exc_info=Undef, type=Undef, closeable=Undef, escape=Undef, action=None, showtb=True, **kw):
exc_info = sa(exc_info, kw, 'e') exc_info = Undef.sa(exc_info, kw, 'e')
if exc_info is Undef: exc_info = sys.exc_info() if exc_info is Undef: exc_info = sys.exc_info()
if exc_info == (None, None, None): exc_info = None if exc_info == (None, None, None): exc_info = None
type = sa(type, kw, 't', "error") type = Undef.sa(type, kw, 't', "error")
closeable = sa(closeable, kw, 'c', False) closeable = Undef.sa(closeable, kw, 'c', False)
escape = sa(escape, kw, 'x', None) escape = Undef.sa(escape, kw, 'x', None)
self.msg = msg self.msg = msg
self.exc_info = exc_info self.exc_info = exc_info
@ -281,7 +370,7 @@ class Alert(object):
self.showtb = showtb self.showtb = showtb
def __call__(self, msg=Undef, exc_info=Undef, type=Undef, closeable=Undef, escape=Undef, action=Undef, showtb=Undef, **kw): def __call__(self, msg=Undef, exc_info=Undef, type=Undef, closeable=Undef, escape=Undef, action=Undef, showtb=Undef, **kw):
exc_info = sa(exc_info, kw, 'e') exc_info = Undef.sa(exc_info, kw, 'e')
if msg is Undef: if msg is Undef:
# si on ne spécifie pas de message, alors prendre la valeur actuelle # si on ne spécifie pas de message, alors prendre la valeur actuelle
msg = self.msg msg = self.msg
@ -290,11 +379,11 @@ class Alert(object):
# si on spécifie un message, alors prendre aussi l'exception courante # si on spécifie un message, alors prendre aussi l'exception courante
if exc_info is Undef: exc_info = sys.exc_info() if exc_info is Undef: exc_info = sys.exc_info()
if exc_info == (None, None, None): exc_info = None if exc_info == (None, None, None): exc_info = None
type = sa(type, kw, 't', self.type) type = Undef.sa(type, kw, 't', self.type)
closeable = sa(closeable, kw, 'c', self.closeable) closeable = Undef.sa(closeable, kw, 'c', self.closeable)
escape = sa(escape, kw, 'x', self.escape) escape = Undef.sa(escape, kw, 'x', self.escape)
action = sa(action, kw, None, self.action) action = Undef.sa(action, kw, None, self.action)
showtb = sa(showtb, kw, None, self.showtb) showtb = Undef.sa(showtb, kw, None, self.showtb)
self.msg = msg self.msg = msg
self.exc_info = exc_info self.exc_info = exc_info
@ -306,7 +395,7 @@ class Alert(object):
return self return self
def render(self, msg=Undef, exc_info=Undef, type=Undef, closeable=Undef, escape=Undef, action=Undef, showtb=Undef, **kw): def render(self, msg=Undef, exc_info=Undef, type=Undef, closeable=Undef, escape=Undef, action=Undef, showtb=Undef, **kw):
exc_info = sa(exc_info, kw, 'e') exc_info = Undef.sa(exc_info, kw, 'e')
if msg is Undef: if msg is Undef:
# si on ne spécifie pas de message, alors prendre la valeur initiale # si on ne spécifie pas de message, alors prendre la valeur initiale
msg = self.msg msg = self.msg
@ -314,11 +403,11 @@ class Alert(object):
else: else:
# si on spécifie un message, alors prendre aussi l'exception courante # si on spécifie un message, alors prendre aussi l'exception courante
if exc_info is Undef: exc_info = sys.exc_info() if exc_info is Undef: exc_info = sys.exc_info()
type = sa(type, kw, 't', self.type) type = Undef.sa(type, kw, 't', self.type)
closeable = sa(closeable, kw, 'c', self.closeable) closeable = Undef.sa(closeable, kw, 'c', self.closeable)
escape = sa(escape, kw, 'x', self.escape) escape = Undef.sa(escape, kw, 'x', self.escape)
action = sa(action, kw, None, self.action) action = Undef.sa(action, kw, None, self.action)
showtb = sa(showtb, kw, None, self.showtb) showtb = Undef.sa(showtb, kw, None, self.showtb)
if callable(msg): if callable(msg):
# si msg est callable, par défaut ne pas mettre le résultat en # si msg est callable, par défaut ne pas mettre le résultat en

View File

@ -3,14 +3,19 @@
__all__ = ( __all__ = (
'LazyObject', 'LazyObject',
'dbconfig', 'fixsqlite', 'lazydb', 'oradbinfos', 'dbconfig', 'fixsqlite', 'lazydb', 'oradbinfos',
'incd', 'excd', 'Row', 'RowCtl', 'incd', 'excd', 'ColSchema', 'Col', 'Row', 'RowCtl',
'Migration', 'Migration',
) )
from os import path from os import path
from types import StringType
import csv import csv
from ..base import Undef, odict, seqof
from ..ext import web from ..ext import web
from .api import uval
Undef = Undef() # spécifique à ce module
class LazyObject(object): class LazyObject(object):
"""Un objet proxy vers un autre objet qui est construit avec retard """Un objet proxy vers un autre objet qui est construit avec retard
@ -84,6 +89,69 @@ class oradbinfos(object):
def __unicode__(self): def __unicode__(self):
return self() return self()
class ColSchema(odict):
def __init__(self, formatter=Undef, link=Undef, **kw):
formatter = Undef.sa(formatter, kw, 'f', None)
link = Undef.sa(link, kw, 'u', None)
super(ColSchema, self).__init__(**kw)
self.formatter = formatter
self.link = link
def css(self, value=Undef):
return None
def format(self, value, index=Undef):
formatter = self.formatter
if type(formatter) is StringType:
return formatter % value
elif formatter is not None:
return formatter(value)
else:
return value
def text(self, value, index=Undef):
return self.format(value, index)
def html(self, value, index=Undef):
link = uval(self.link)
value = self.format(value, index)
if link is None: return value
else: return u'<a href="%s">%s</a>' % (link, value)
class Col(odict):
def __init__(self, key, title=Undef, schema=Undef, formatter=Undef, link=Undef, **kw):
title = Undef.sa(title, kw, 't', None)
schema = Undef.sa(schema, kw, 's', None)
formatter = Undef.sa(formatter, kw, 'f')
link = Undef.sa(link, kw, 'u')
super(Col, self).__init__(**kw)
if title is None: title = key
if schema is None and (formatter is not Undef or link is not Undef):
schema = ColSchema(formatter, link)
self.key = key
self.title = title
self.schema = schema
def geti(values, index):
return values[index] if index >= 0 and index < len(values) else None
class RowSchema(odict):
def __init__(self, cols=Undef, **kw):
cols = seqof(Undef.sa(cols, kw, None, None))
super(RowSchema, self).__init__(**kw)
self.cols = cols
def col(self, index=Undef):
if index is not Undef:
return geti(self.cols, index)
return None
def cell_schema(self, value=Undef, index=Undef):
if index is not Undef:
col = geti(self.cols, index)
if col is not None: return col.schema
return None
def css(self, value=Undef):
return None
class Row(object): class Row(object):
"""un proxy vers une ligne de web.database """un proxy vers une ligne de web.database
@ -94,8 +162,24 @@ class Row(object):
def __init__(self, row): def __init__(self, row):
self.__dict__['_row'] = row self.__dict__['_row'] = row
def __contains__(self, name): def keys(self): return self._row.keys()
return name in self._row def values(self): return self._row.values()
def items(self): return self._row.items()
def has_key(self): return self._row.has_key()
def get(self): return self._row.get()
def clear(self): return self._row.clear()
def setdefault(self): return self._row.setdefault()
def iterkeys(self): return self._row.iterkeys()
def itervalues(self): return self._row.itervalues()
def iteritems(self): return self._row.iteritems()
def pop(self): return self._row.pop()
def popitem(self): return self._row.popitem()
def update(self): return self._row.update()
def __iter__(self): return self._row.__iter__()
def __contains__(self, key): return self._row.__contains__(key)
def copy(self): return self.__class__(self._row)
def __getattr__(self, name): def __getattr__(self, name):
row = self._row row = self._row
try: try:
@ -128,9 +212,36 @@ class Row(object):
def __delitem__(self, key): def __delitem__(self, key):
del self._row[key] del self._row[key]
def __repr__(self): return repr(self._row) def __repr__(self): return '<%s:%r>' % (self.__class__.__name__, self._row)
def __str__(self): return str(self._row) def __str__(self): return str(self._row)
def _firstof(values):
for value in values:
return value
return None
class TableSchema(odict):
@staticmethod
def parse(rows):
# on assume que rows est une liste de dictionnaires
cols = [Col(key) for key in _firstof(rows)] if rows else Undef
return TableSchema(cols)
def __init__(self, cols=Undef, **kw):
cols = seqof(Undef.sa(cols, kw, None, None))
super(TableSchema, self).__init__(**kw)
self.cols = cols
self.row_schemas = {}
self.row_schemas['header'] = RowSchema(cols)
self.row_schemas['body'] = RowSchema(cols)
def headers(self):
return [col.title for col in self.cols]
def header_row_schema(self):
return self.row_schemas["header"]
def body_row_schema(self, index):
return self.row_schemas["body"]
def incd(fd, *names): def incd(fd, *names):
td = dict() td = dict()
if not names: if not names:

View File

@ -5,15 +5,15 @@ __all__ = (
'accesskeyif', 'accesskeyif',
'classif', 'addclassif', 'classif', 'addclassif',
'favicon', 'css', 'js', 'jscss', 'favicon', 'css', 'js', 'jscss',
'u', 'p', 'p',
'Action', 'Menu', 'Action', 'Menu',
) )
from types import StringType, UnicodeType
import urlparse, urllib import urlparse, urllib
from nulib.base import odict, isseq, seqof from nulib.base import odict, isseq, seqof
from nulib.web import web from nulib.web import web
from .api import u
def checked(b): def checked(b):
return u' checked="checked"' if b else u'' return u' checked="checked"' if b else u''
@ -45,7 +45,7 @@ def css(href, media=None):
return ur'<link rel="stylesheet" href="%s" type="text/css"%s />' % (href, media or u'') return ur'<link rel="stylesheet" href="%s" type="text/css"%s />' % (href, media or u'')
def js(href): def js(href):
return ur'<script type="text/javascript" src="%s"></script>' % href return ur'<script type="text/javascript" src="%s"></script>' % href
def jscss(href, min=False): def jscss(href):
if href.endswith(".min.js"): if href.endswith(".min.js"):
jshref = href jshref = href
csshref = "%s.css" % href[:-len(".min.js")] csshref = "%s.css" % href[:-len(".min.js")]
@ -57,15 +57,6 @@ def jscss(href, min=False):
csshref = href csshref = href
return u"%s\n%s" % (js(jshref), css(csshref)) return u"%s\n%s" % (js(jshref), css(csshref))
def u(text):
"""Si text n'est pas de l'unicode, le convertir en unicode avec l'encoding utf-8
"""
t = type(text)
if t is not UnicodeType:
if t is not StringType: text = str(text)
text = unicode(text, "utf-8")
return text
def p(text): def p(text):
"""Si text commence par un tag, le laisser tel quel, sinon le mettre entre <p> et </p> """Si text commence par un tag, le laisser tel quel, sinon le mettre entre <p> et </p>
""" """