diff --git a/lib/nulib/python/nulib/base.py b/lib/nulib/python/nulib/base.py index 075b77f..6b28271 100644 --- a/lib/nulib/python/nulib/base.py +++ b/lib/nulib/python/nulib/base.py @@ -36,7 +36,36 @@ mydir, myname = path.split(myself) # Fonctions diverses +_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): return 'Undef' def __call__(self): diff --git a/lib/nulib/python/nulib/web/api.py b/lib/nulib/python/nulib/web/api.py index 24ee87d..66fb2c5 100644 --- a/lib/nulib/python/nulib/web/api.py +++ b/lib/nulib/python/nulib/web/api.py @@ -1,7 +1,11 @@ # -*- 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 def _weburl(path, __BASEURL, **kw): @@ -14,3 +18,21 @@ def _ensure_session(page): if session is None: raise ValueError("no session configured") page.session = 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) diff --git a/lib/nulib/python/nulib/web/bootstrap.py b/lib/nulib/python/nulib/web/bootstrap.py index a34ac48..2b4b180 100644 --- a/lib/nulib/python/nulib/web/bootstrap.py +++ b/lib/nulib/python/nulib/web/bootstrap.py @@ -2,19 +2,22 @@ __all__ = ( 'load', + 'table', 'tr', 'td', + 'Menu', 'Action', 'About', 'Alert', 'set_alert', - 'Menu', 'Action', ) import sys, traceback +from types import StringType -from ..base import seqof, Undef +from ..base import isseq, seqof, Undef from ..ext import web from . import ui -from .ui import Action, u, p -from .api import _ensure_session +from .api import _ensure_session, u, uval +from .model import Row, TableSchema +from .ui import Action, p Undef = Undef() # spécifique à ce module @@ -43,12 +46,98 @@ def load(prefix=None, icons=True, charset="utf-8"): ]) 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 - """ - if value is Undef and name is not None: value = kw.pop(name, Undef) - if value is Undef: value = default - return value +################################################################################ +# Table + +TRD_CLASS_MAP = dict( + active="active", + 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' % (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'' % 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'') + 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'' % ui.classif(css)) + lines.append(u'') + lines.append(tr(schema.headers(), schema.header_row_schema(), celltag='th')) + lines.append(u'') + index = 0 + for row in rows: + lines.append(tr(row, schema.body_row_schema(index))) + index += 1 + lines.append(u'') + lines.append(u'') + return u'\n'.join(lines) ################################################################################ # Menu @@ -265,12 +354,12 @@ class Alert(object): showtb = None 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 == (None, None, None): exc_info = None - type = sa(type, kw, 't', "error") - closeable = sa(closeable, kw, 'c', False) - escape = sa(escape, kw, 'x', None) + type = Undef.sa(type, kw, 't', "error") + closeable = Undef.sa(closeable, kw, 'c', False) + escape = Undef.sa(escape, kw, 'x', None) self.msg = msg self.exc_info = exc_info @@ -281,7 +370,7 @@ class Alert(object): self.showtb = showtb 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: # si on ne spécifie pas de message, alors prendre la valeur actuelle msg = self.msg @@ -290,11 +379,11 @@ class Alert(object): # 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 == (None, None, None): exc_info = None - type = sa(type, kw, 't', self.type) - closeable = sa(closeable, kw, 'c', self.closeable) - escape = sa(escape, kw, 'x', self.escape) - action = sa(action, kw, None, self.action) - showtb = sa(showtb, kw, None, self.showtb) + type = Undef.sa(type, kw, 't', self.type) + closeable = Undef.sa(closeable, kw, 'c', self.closeable) + escape = Undef.sa(escape, kw, 'x', self.escape) + action = Undef.sa(action, kw, None, self.action) + showtb = Undef.sa(showtb, kw, None, self.showtb) self.msg = msg self.exc_info = exc_info @@ -306,7 +395,7 @@ class Alert(object): return self 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: # si on ne spécifie pas de message, alors prendre la valeur initiale msg = self.msg @@ -314,11 +403,11 @@ class Alert(object): else: # si on spécifie un message, alors prendre aussi l'exception courante if exc_info is Undef: exc_info = sys.exc_info() - type = sa(type, kw, 't', self.type) - closeable = sa(closeable, kw, 'c', self.closeable) - escape = sa(escape, kw, 'x', self.escape) - action = sa(action, kw, None, self.action) - showtb = sa(showtb, kw, None, self.showtb) + type = Undef.sa(type, kw, 't', self.type) + closeable = Undef.sa(closeable, kw, 'c', self.closeable) + escape = Undef.sa(escape, kw, 'x', self.escape) + action = Undef.sa(action, kw, None, self.action) + showtb = Undef.sa(showtb, kw, None, self.showtb) if callable(msg): # si msg est callable, par défaut ne pas mettre le résultat en diff --git a/lib/nulib/python/nulib/web/model.py b/lib/nulib/python/nulib/web/model.py index 266fed5..a1f3318 100644 --- a/lib/nulib/python/nulib/web/model.py +++ b/lib/nulib/python/nulib/web/model.py @@ -3,14 +3,19 @@ __all__ = ( 'LazyObject', 'dbconfig', 'fixsqlite', 'lazydb', 'oradbinfos', - 'incd', 'excd', 'Row', 'RowCtl', + 'incd', 'excd', 'ColSchema', 'Col', 'Row', 'RowCtl', 'Migration', ) from os import path +from types import StringType import csv +from ..base import Undef, odict, seqof from ..ext import web +from .api import uval + +Undef = Undef() # spécifique à ce module class LazyObject(object): """Un objet proxy vers un autre objet qui est construit avec retard @@ -84,6 +89,69 @@ class oradbinfos(object): def __unicode__(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'%s' % (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): """un proxy vers une ligne de web.database @@ -94,8 +162,24 @@ class Row(object): def __init__(self, row): self.__dict__['_row'] = row - def __contains__(self, name): - return name in self._row + def keys(self): return self._row.keys() + 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): row = self._row try: @@ -128,9 +212,36 @@ class Row(object): def __delitem__(self, 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 _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): td = dict() if not names: diff --git a/lib/nulib/python/nulib/web/ui.py b/lib/nulib/python/nulib/web/ui.py index 1e21d66..dc81967 100644 --- a/lib/nulib/python/nulib/web/ui.py +++ b/lib/nulib/python/nulib/web/ui.py @@ -5,15 +5,15 @@ __all__ = ( 'accesskeyif', 'classif', 'addclassif', 'favicon', 'css', 'js', 'jscss', - 'u', 'p', + 'p', 'Action', 'Menu', ) -from types import StringType, UnicodeType import urlparse, urllib from nulib.base import odict, isseq, seqof from nulib.web import web +from .api import u def checked(b): return u' checked="checked"' if b else u'' @@ -45,7 +45,7 @@ def css(href, media=None): return ur'' % (href, media or u'') def js(href): return ur'' % href -def jscss(href, min=False): +def jscss(href): if href.endswith(".min.js"): jshref = href csshref = "%s.css" % href[:-len(".min.js")] @@ -57,15 +57,6 @@ def jscss(href, min=False): csshref = href 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): """Si text commence par un tag, le laisser tel quel, sinon le mettre entre

et

"""