From c7286d14062443dcf1cc68d1dc3d1f75045732a7 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 8 May 2018 21:45:53 +0400 Subject: [PATCH] nulib: corriger l'affichage du menu --- lib/nulib/python/nulib/web/api.py | 20 +++- lib/nulib/python/nulib/web/bootstrap.py | 125 ++++++++++++------------ lib/nulib/python/nulib/web/ui.py | 66 +++++++++---- 3 files changed, 125 insertions(+), 86 deletions(-) diff --git a/lib/nulib/python/nulib/web/api.py b/lib/nulib/python/nulib/web/api.py index 66fb2c5..8243fa4 100644 --- a/lib/nulib/python/nulib/web/api.py +++ b/lib/nulib/python/nulib/web/api.py @@ -2,7 +2,7 @@ __all__ = ( '_weburl', '_ensure_session', - 'u', 'uval', + 'u', 'uval', 'uval_would_escape', ) from types import UnicodeType, StringType @@ -28,11 +28,21 @@ def u(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() +def uval_would_escape(value): + """Tester si uval() mettrait value en échappement + """ + if value is None: return False + if hasattr(value, 'render'): return False + elif callable(value): return False + return True + +def uval(value, escape=True): + """Transformer value en unicode pour affichage. + Si value est un objet qui a une méthode render() ou qui est callable(), + alors utiliser le résultat comme valeur sinon utiliser web.websafe() """ if value is None: return None if hasattr(value, 'render'): value = value.render() - if callable(value): value = value() + elif callable(value): value = value() + elif escape: value = web.websafe(value) return u(value) diff --git a/lib/nulib/python/nulib/web/bootstrap.py b/lib/nulib/python/nulib/web/bootstrap.py index 2b4b180..e191f87 100644 --- a/lib/nulib/python/nulib/web/bootstrap.py +++ b/lib/nulib/python/nulib/web/bootstrap.py @@ -3,7 +3,7 @@ __all__ = ( 'load', 'table', 'tr', 'td', - 'Menu', 'Action', + 'Menu', 'Action', 'litteral', 'p', 'About', 'Alert', 'set_alert', ) @@ -15,33 +15,31 @@ from ..base import isseq, seqof, Undef from ..ext import web from . import ui -from .api import _ensure_session, u, uval +from .api import _ensure_session, u, uval, uval_would_escape from .model import Row, TableSchema -from .ui import Action, p - -Undef = Undef() # spécifique à ce module +from .ui import Action, litteral, p def load(prefix=None, icons=True, charset="utf-8"): if prefix is None: prefix = "/static/bootstrap/" elif not prefix.endswith("/"): prefix = prefix + "/" lines = [ - '' % charset, - '', - '', + u"""""" % charset, + u"""""", + u"""""", ] if icons: lines.extend([ - '' % prefix, - '' % prefix, + u"""""" % prefix, + u"""""" % prefix, ]) lines.extend([ - ui.css("%sbootstrap/css/bootstrap.min.css" % prefix), - ui.css("%sbslocal.css" % prefix), - ui.js("%sjquery.min.js" % prefix), - ui.js("%sbootstrap/js/bootstrap.min.js" % prefix), + ui.css(u"%sbootstrap/css/bootstrap.min.css" % prefix), + ui.css(u"%sbslocal.css" % prefix), + ui.js(u"%sjquery.min.js" % prefix), + ui.js(u"%sbootstrap/js/bootstrap.min.js" % prefix), u"", ]) return u"\n".join(lines) @@ -63,7 +61,7 @@ def trd_joinc(css): classes = [] for css in seqof(css): classes.append(TRD_CLASS_MAP.get(css, css)) - css = ' '.join(classes) + css = " ".join(classes) return css def td(value, schema=None, css=Undef, tag=Undef, **kw): @@ -72,9 +70,9 @@ def td(value, schema=None, css=Undef, tag=Undef, **kw): if schema: value = schema.html(value) if css is Undef: css = schema.css(value) - if tag is Undef: tag = 'td' + if tag is Undef: tag = "td" css = trd_joinc(css) - return u'<%s%s>%s' % (tag, ui.classif(css), value, tag) + 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') @@ -84,7 +82,7 @@ def tr(values, schema=None, css=Undef, celltag=Undef, **kw): tdschema = lambda value, index: schema.cell_schema(value, index) css = trd_joinc(css) lines = [] - lines.append(u'' % ui.classif(css)) + lines.append(u"" % ui.classif(css)) index = 0 if isinstance(values, dict) or isinstance(values, Row): if schema is not None: @@ -100,8 +98,8 @@ def tr(values, schema=None, css=Undef, celltag=Undef, **kw): for value in values: lines.append(td(value, tdschema(value, index), tag=celltag)) index += 1 - lines.append(u'') - return u'\n'.join(lines) + lines.append(u"") + return u"\n".join(lines) TABLE_CLASS_MAP = dict( auto="table-auto", a="table-auto", @@ -114,10 +112,10 @@ def table_joinc(css): if css is Undef: css = None if type(css) is StringType: css = css.split(",") - classes = ['table'] + classes = ["table"] for css in seqof(css): classes.append(TABLE_CLASS_MAP.get(css, css)) - css = ' '.join(classes) + css = " ".join(classes) return css def table(rows, schema=None, css=Undef, **kw): @@ -127,17 +125,17 @@ def table(rows, schema=None, css=Undef, **kw): 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'') + 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) + lines.append(u"") + lines.append(u"") + return u"\n".join(lines) ################################################################################ # Menu @@ -150,13 +148,13 @@ NAVBAR_TYPE_MAP = { class Menu(ui.Menu): """Un menu""" - def __init__(self, title, mitems=None, profiles=None, - id=None, in_profiles=None, css=None, - navbar_type=None, container_fluid=None, **kw): - if navbar_type is None: navbar_type = kw.pop('t', None) - if container_fluid is None: container_fluid = kw.pop('f', None) - super(Menu, self).__init__(title, mitems, profiles, id, in_profiles, css, **kw) - if navbar_type is None: navbar_type = 'static' + def __init__(self, text=None, mitems=None, profiles=None, + id=None, in_profiles=Undef, css=Undef, title=Undef, + navbar_type=Undef, container_fluid=Undef, **kw): + navbar_type = Undef.sa(navbar_type, kw, 't', None) + container_fluid = Undef.sa(container_fluid, kw, 'f', None) + super(Menu, self).__init__(text, mitems, profiles, id, in_profiles, css, title, **kw) + if navbar_type is None: navbar_type = "static" self.navbar_type = navbar_type if container_fluid is None: container_fluid = True self.container_fluid = container_fluid @@ -169,12 +167,12 @@ class Menu(ui.Menu): elif mitem.in_profiles is not None: if sel_profile not in mitem.in_profiles: return [] - active = ' class="active"' if mitem.active else '' - url = web.websafe(mitem.url) - css = ui.classif(mitem.css) + li_class = ui.classif("active", mitem.active) + url = uval(mitem.url) + a_class = ui.classif(mitem.css) accesskey = ui.accesskeyif(mitem.accesskey) - title = web.websafe(mitem.title) - return [u"""%s""" % (active, url, css, accesskey, title)] + text = uval(mitem.text) + return [u"""%s""" % (li_class, url, a_class, accesskey, text)] def __dropdown(self, menu, profile): children = [] @@ -183,11 +181,11 @@ class Menu(ui.Menu): if not children: # ne pas afficher un menu vide return [] - active = ' active' if menu.active else '' - lines = [u"""
  • """ % li_class] + firsturl = uval(menu.mitems[0].url) + text = uval(menu.text) + lines.append(u"""%s """ % (firsturl, text)) lines.append(u"""""") @@ -212,10 +210,10 @@ class Menu(ui.Menu): if self.__is_navbar_fixed(navbar_type): lines.append(u"""""") css = u'' - css = ui.addclassif('navbar-fixed-top', self.__is_navbar_fixed(navbar_type), css) - css = ui.addclassif('navbar-static-top', self.__is_navbar_static(navbar_type), css) + css = ui.addclassif("navbar-fixed-top", self.__is_navbar_fixed(navbar_type), css) + css = ui.addclassif("navbar-static-top", self.__is_navbar_static(navbar_type), css) if self.profiles is not None and self.sel_profile is not None: - css = ui.addclassif('%s-profile' % self.sel_profile, None, css) + css = ui.addclassif("%s-profile" % self.sel_profile, None, css) css = ui.addclassif(self.css, None, css) lines.append(u""" @@ -312,7 +315,7 @@ jQuery.noConflict()(function($) { if onShowDetails is not None: if callable(onShowDetails): onShowDetails = onShowDetails() lines.append(onShowDetails) - lines.append("""\ + lines.append(u"""\ return false; }); }); @@ -432,8 +435,8 @@ class Alert(object): if callable(action): action = action() if isinstance(action, Action): url = web.websafe(action.url) - title = web.websafe(action.title or u"Cliquez ici pour continuer") - action = u"""

    %s

    """ % (url, title) + text = web.websafe(action.text or u"Cliquez ici pour continuer") + action = u"""

    %s

    """ % (url, text) lines.append(p(action)) if exc_info is not None: lines.append(u"""
    Pour information, le message d'erreur technique est   """) diff --git a/lib/nulib/python/nulib/web/ui.py b/lib/nulib/python/nulib/web/ui.py index dc81967..8b70804 100644 --- a/lib/nulib/python/nulib/web/ui.py +++ b/lib/nulib/python/nulib/web/ui.py @@ -5,14 +5,15 @@ __all__ = ( 'accesskeyif', 'classif', 'addclassif', 'favicon', 'css', 'js', 'jscss', - 'p', + 'litteral', 'p', 'Action', 'Menu', ) import urlparse, urllib -from nulib.base import odict, isseq, seqof -from nulib.web import web +from ..base import Undef, odict, isseq, seqof +from ..ext import web + from .api import u def checked(b): @@ -57,8 +58,17 @@ def jscss(href): csshref = href return u"%s\n%s" % (js(jshref), css(csshref)) +class litteral(object): + """Objet proxy vers un texte littéral. + à utiliser avec uval() pour ne pas mettre en échappement la valeur + """ + def __init__(self, text): self.text = text + def __call__(self): return self.text + __unicode__ = __call__ + def p(text): """Si text commence par un tag, le laisser tel quel, sinon le mettre entre

    et

    + text doit avoir le cas échéant déjà été traité avec uval """ text = u(text) if text[0:1] == '<': return text @@ -71,7 +81,8 @@ class Action(odict): """Un lien ou une action d'un formulaire url: cible de l'action ou du lien (avec querystring) - title: titre du lien ou de l'élément de menu + text: titre du lien ou de l'élément de menu + title: description de l'action. vaut text par défaut id: identifiant dans un menu in_profiles (p): profils dans lequel l'action est valide. le premier profil est le profil par défaut @@ -112,28 +123,39 @@ class Action(odict): self[querykey] = query self[urlkey] = url - def __init__(self, url, title=None, id=None, in_profiles=None, css=None, accesskey=None, query=None, method=None, **kw): + def __init__(self, url, text=Undef, id=Undef, in_profiles=Undef, css=Undef, accesskey=Undef, query=Undef, title=Undef, method=Undef, **kw): + text = Undef.sa(text, kw, 't', None) + id = Undef.sa(id, kw, None, None) + in_profiles = Undef.sa(in_profiles, kw, 'p', None) + css = Undef.sa(css, kw, 'c', None) + accesskey = Undef.sa(accesskey, kw, 'ak', None) + query = Undef.sa(query, kw, 'q', None) + title = Undef.sa(title, kw, 'nt') + method = Undef.sa(method, kw, 'm', None) super(Action, self).__init__(**kw) if isseq(url): - if url[1:2]: title = url[1] + if url[1:2]: text = url[1] if url[2:3]: id = url[2] if url[3:4]: in_profiles = url[3] if url[4:5]: css = url[4] if url[5:6]: accesskey = url[5] - if url[6:7]: method = url[6] + if url[6:7]: title = url[6] + if url[7:8]: method = url[7] url = url[0] if url[0:1] else None if url is None: raise ValueError("url is required") - if title is None: title = url + if text is None: text = url if in_profiles is None: in_profiles = kw.pop('p', None) if css is None: css = kw.pop('c', None) if accesskey is None: accesskey = kw.pop('ak', None) if query is None: query = kw.pop('q', None) + if title is Undef: title = text if method is None: method = 'get' - self.title = title + self.text = text self.id = id self.css = css self.accesskey = accesskey + self.title = title self.method = method self.url = url @@ -207,26 +229,30 @@ class Menu(odict): self.nextid += 1 return nextid - def __init__(self, title, mitems=None, profiles=None, - id=None, in_profiles=None, css=None, **kw): - if in_profiles is None: in_profiles = kw.pop('p', None) - if css is None: css = kw.pop('c', None) + def __init__(self, text=None, mitems=None, profiles=None, + id=None, in_profiles=Undef, css=Undef, title=Undef, **kw): + in_profiles = Undef.sa(in_profiles, kw, 'p', None) + css = Undef.sa(css, kw, 'c', None) + title = Undef.sa(title, kw, 'nt') super(Menu, self).__init__(**kw) self.__dict__['nextid'] = 0 self.__dict__['idmap'] = {} - if title is None: title = u"" + + if text is None: text = u"" if id is None: id = self.__nextid() - self.title = title - self.mitems = [] + if title is Undef: title = text + self.text = text profiles = seqof(profiles, None) self.profiles = profiles self.default_profile = profiles[0] if profiles is not None else None self.id = str(id) self.in_profiles = seqof(p, None) self.css = css + self.title = title self.sel_id = None self.sel_profile = None self.active = False + self.mitems = [] for mitem in seqof(mitems): self.add(mitem) @@ -252,8 +278,8 @@ class Menu(odict): else: mitem.active = False - def select(self, id, profile=None, **kw): - if profile is None: profile = kw.pop('p', None) + def select(self, id, profile=Undef, **kw): + profile = Undef.sa(profile, kw, 'p', None) # d'abord déselectionner tout le monde self.reset_selection() # ensuite chercher le mitem à sélectionner @@ -277,10 +303,10 @@ class Menu(odict): return True return False - def get_mitem(self, id=None, **kw): + def get_mitem(self, id=Undef, **kw): """retourner l'élément de menu correspondant à la sélection courante """ - if id is None: id = self.sel_id + id = Undef.sa(id, kw, None, self.sel_id) if id is None: return None id = str(id) mitem = self.idmap.get(id, None)