diff --git a/lib/nulib/python/nulib/web/bootstrap.py b/lib/nulib/python/nulib/web/bootstrap.py index 256f82b..ffba723 100644 --- a/lib/nulib/python/nulib/web/bootstrap.py +++ b/lib/nulib/python/nulib/web/bootstrap.py @@ -46,14 +46,20 @@ def load(prefix=None, icons=True, charset="utf-8"): ################################################################################ # Menu +NAVBAR_TYPE_MAP = { + 'fixed': 'fixed-top', + 'static': 'static-top', +} + class Menu(ui.Menu): """Un menu""" def __init__(self, title, mitems=None, profiles=None, - id=None, p=None, c=None, t=None, **kw): - super(Menu, self).__init__(title, mitems, profiles, id, p, c, **kw) - if t is None: t = 'static' - self.navbar_type = t + id=None, in_profiles=None, css=None, navbar_type=None, **kw): + if navbar_type is None: navbar_type = kw.pop('t', None) + super(Menu, self).__init__(title, mitems, profiles, id, in_profiles, css, **kw) + if navbar_type is None: navbar_type = 'static' + self.navbar_type = navbar_type def __mitem(self, mitem, sel_profile): # ne pas afficher un menu qui n'est pas dans le bon profil @@ -87,16 +93,20 @@ class Menu(ui.Menu): lines.append(u"""""") return lines - def __call__(self, s=None, p=None, t=None): - if t is None: t = self.navbar_type - if s is not None: self.select(s, p) + def render(self, select=None, profile=None, navbar_type=None, **kw): + if select is None: select = kw.pop('s', None) + if profile is None: profile = kw.pop('p', None) + if navbar_type is None: navbar_type = kw.pop('t', None) + + if navbar_type is None: navbar_type = self.navbar_type + if select is not None: self.select(select, profile) selection = self.get_mitem() lines = [] - if t == 'fixed' or t == 'fixed-top': + if navbar_type == 'fixed' or navbar_type == 'fixed-top': lines.append(u"""""") css = u'' - css = ui.addclassif('navbar-fixed-top', t == 'fixed' or t == 'fixed-top', css) - css = ui.addclassif('navbar-static-top', t == 'static' or t == 'static-top', css) + css = ui.addclassif('navbar-fixed-top', navbar_type == 'fixed' or navbar_type == 'fixed-top', css) + css = ui.addclassif('navbar-static-top', navbar_type == 'static' or navbar_type == 'static-top', 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(self.css, None, css) @@ -145,10 +155,9 @@ class Menu(ui.Menu): """) return u"\n".join(lines) + def __unicode__(self): return self.render() - def __unicode__(self): return self() - -def set_menu(menu): +def set_menu(menu, **params): """Décorateur qui permet d'initialiser une instance de menu dans la session """ def decorator(method): @@ -159,16 +168,24 @@ def set_menu(menu): return wrapper return decorator -def menu(id=None, p=None): - """Décorateur qui permet de mettre à jour la sélection et le profil dans le menu +def menu(id=None, profile=None, **params): + """Décorateur qui permet de mettre à jour la sélection et le profil dans + le menu + + par défaut, prendre le profil courant dans la session. le profil courant + est initialisé par le décorateur @set_profile """ + if profile is None: profile = params.pop('p', None) def decorator(method): def wrapper(self, *args, **kw): session = _ensure_session(self) if 'menu' not in session: - raise ValueError("menu is required") + raise ValueError("menu est requis") + sel_profile = profile + if sel_profile is None and 'profile' in session: + sel_profile = session.profile if id is not None: - session.menu.select(id, p) + session.menu.select(id, sel_profile) return method(self, *args, **kw) return wrapper return decorator @@ -229,64 +246,74 @@ class Alert(object): action = None showtb = None - def __init__(self, msg=None, e=Undef, t="error", c=False, x=None, action=None, showtb=True): - self(msg, e, t, c, x, action, showtb) + def __init__(self, msg=None, exc_info=Undef, type="error", closeable=False, escape=None, action=None, showtb=True, **kw): + self(msg, exc_info, type, closeable, escape, action, showtb, **kw) + + def __call__(self, msg=Undef, exc_info=Undef, type=Undef, closeable=Undef, escape=Undef, action=Undef, showtb=Undef, **kw): + if exc_info is Undef: exc_info = kw.pop('e', Undef) + if type is Undef: type = kw.pop('t', Undef) + if closeable is Undef: closeable = kw.pop('c', Undef) + if escape is Undef: escape = kw.pop('x', Undef) - def __call__(self, msg=Undef, e=Undef, t=Undef, c=Undef, x=Undef, action=Undef, showtb=Undef): if msg is Undef: # si on ne spécifie pas de message, alors prendre la valeur actuelle msg = self.msg - if e is Undef: e = self.exc_info + if exc_info is Undef: exc_info = self.exc_info else: # si on spécifie un message, alors prendre aussi l'exception courante - if e is Undef: e = sys.exc_info() - if e == (None, None, None): e = None - if t is Undef: t = self.type - if c is Undef: c = self.closeable - if x is Undef: x = self.escape + if exc_info is Undef: exc_info = sys.exc_info() + if exc_info == (None, None, None): exc_info = None + if type is Undef: type = self.type + if closeable is Undef: closeable = self.closeable + if escape is Undef: escape = self.escape if action is Undef: action = self.action if showtb is Undef: showtb = self.showtb self.msg = msg - self.exc_info = e - self.type = ALERT_TYPE_MAP.get(t, t) - self.closeable = c - self.escape = x + self.exc_info = exc_info + self.type = ALERT_TYPE_MAP.get(type, type) + self.closeable = closeable + self.escape = escape self.action = action self.showtb = showtb return self - def render(self, msg=Undef, e=Undef, t=Undef, c=Undef, x=Undef, action=Undef, showtb=Undef): + def render(self, msg=Undef, exc_info=Undef, type=Undef, closeable=Undef, escape=Undef, action=Undef, showtb=Undef, **kw): + if exc_info is Undef: exc_info = kw.pop('e', Undef) + if type is Undef: type = kw.pop('t', Undef) + if closeable is Undef: closeable = kw.pop('c', Undef) + if escape is Undef: escape = kw.pop('x', Undef) + if msg is Undef: # si on ne spécifie pas de message, alors prendre la valeur initiale msg = self.msg - if e is Undef: e = self.exc_info + if exc_info is Undef: exc_info = self.exc_info else: # si on spécifie un message, alors prendre aussi l'exception courante - if e is Undef: e = sys.exc_info() - if t is Undef: t = self.type - if c is Undef: c = self.closeable - if x is Undef: x = self.escape + if exc_info is Undef: exc_info = sys.exc_info() + if type is Undef: type = self.type + if closeable is Undef: closeable = self.closeable + if escape is Undef: escape = self.escape if action is Undef: action = self.action if showtb is Undef: showtb = self.showtb if callable(msg): # si msg est callable, par défaut ne pas mettre le résultat en # échappement - if x is None: x = False + if escape is None: escape = False msg = msg() - if x is None: x = True + if escape is None: escape = True - if msg is None and e is not None: + if msg is None and exc_info is not None: msg = u"Une erreur inattendue s'est produite" if msg is None: return u"" - if x: msg = web.websafe(msg) + if escape: msg = web.websafe(msg) lines = [] - css = u"alert alert-%s" % t - css = ui.addclassif("alert-dismissible", c, css) + css = u"alert alert-%s" % ALERT_TYPE_MAP.get(type, type) + css = ui.addclassif("alert-dismissible", closeable, css) lines.append(u"""""") return u"\n".join([u(line) for line in lines]) def __unicode__(self): return self.render() -def set_alert(template=None, action=Undef, delay=None): +def set_alert(template=None, action=Undef, delay=None, **params): """Décorateur qui permet de gérer automatiquement une instance de Alert dans la page. @@ -357,10 +384,14 @@ def set_alert(template=None, action=Undef, delay=None): lines.append(u"""\ %(title)s - +""" % locals()) + if web.config.have_session: + session = web.config._session + if 'menu' in session: lines.append(session.menu.render()) + lines.append(u"""\

%(title)s

""" % locals()) - lines.append(alert()) + lines.append(alert.render()) lines.append(u"""\
diff --git a/lib/nulib/python/nulib/web/pages.py b/lib/nulib/python/nulib/web/pages.py index fd22bcf..2009731 100644 --- a/lib/nulib/python/nulib/web/pages.py +++ b/lib/nulib/python/nulib/web/pages.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 __all__ = ( - 'nocache', 'auth', 'defaults', + 'nocache', 'auth', 'defaults', 'redirect', 'reset_session', 'set_session', 'session', 'check_session', + 'set_profile', 'Page', 'Application', ) @@ -45,7 +46,7 @@ class MetaPage(type): _fix_PATH(cls, name) HANDLER_CLASSES.append(cls) -def nocache(method): +def nocache(method, **params): """Décorateur pour s'assurer que le résultat de la méthode web n'est pas mis en cache """ def wrapper(self, *args, **kw): @@ -55,7 +56,7 @@ def nocache(method): return method(self, *args, **kw) return wrapper -def auth(authenticator=None, realm='nulib'): +def auth(authenticator=None, realm='nulib', **params): """Décorateur pour s'assurer que la méthode web est authentifiée. La fonction authenticator avec la signature (username, password) permet @@ -75,6 +76,22 @@ def auth(authenticator=None, realm='nulib'): return wrapper return decorator +def redirect(desturl, **params): + """rediriger vers l'url spécifiée si la méthode retourne None. + + L'url est enregistrée dans l'objet avec le nom desturl au cas où on + voudrait s'en servir dans la page retournée. + """ + def decorator(method): + def wrapper(self, *args, **kw): + self.desturl = desturl + self.desturl_params = params + result = method(self, *args, **kw) + if result is not None: return result + else: return web.redirect(web.url(desturl, **params)) + return wrapper + return decorator + def defaults(*required, **defaults): """Initialiser dans l'objet courant des variables à des valeurs par défaut, ou en les prenant parmi les paramètres de la requête. @@ -207,6 +224,22 @@ def check_session(_onerror=None, **validators): return wrapper return decorator +def set_profile(profile=None, **params): + """mettre à jour la valeur du profil courant + + s'assurer que l'objet courant contient la variable session. si les sessions + n'ont pas été configurées au niveau de l'application, une exception est + levée + """ + if profile is None: profile = params.pop('p', None) + def decorator(method): + def wrapper(self, *args, **kw): + session = _ensure_session(self) + session.profile = profile + return method(self, *args, **kw) + return wrapper + return decorator + def _fix_PATH(cls, name): if cls.PATH is None: if cls.PREFIX is None: @@ -491,6 +524,8 @@ class Application(object): session = web.session.Session(self.webapp, store, initializer=initializer) self.session = web.config._session = session + web.config.have_session = session is not None + tg = self.template_globals # session et application comme variables globales du template tg.setdefault("S", session) @@ -529,7 +564,7 @@ class Application(object): if args is None: args = [] options, args = get_args(map(_u, args), 'S:s:H:P:Dp:' + (self.OPTIONS or ''), - ['server-type=', 'server-socket=', 'host=', 'port=', 'debug', 'profile='] + list(self.LONG_OPTIONS or ())) + ['server-type=', 'server-socket=', 'host=', 'port=', 'debug', 'no-debug', 'profile='] + list(self.LONG_OPTIONS or ())) server_type = None server_socket = None for option, value in options: @@ -542,6 +577,8 @@ class Application(object): elif option in ('-D', '--debug'): self.DEBUG = True set_verbosity('--debug') + elif option in ('--no-debug',): + self.DEBUG = False elif option in ('-p', '--profile'): self.PROFILE = value self.args = args self.process_args(args) diff --git a/lib/nulib/python/nulib/web/ui.py b/lib/nulib/python/nulib/web/ui.py index 9f86282..1e21d66 100644 --- a/lib/nulib/python/nulib/web/ui.py +++ b/lib/nulib/python/nulib/web/ui.py @@ -82,11 +82,11 @@ class Action(odict): url: cible de l'action ou du lien (avec querystring) title: titre du lien ou de l'élément de menu id: identifiant dans un menu - p (in_profiles): profils dans lequel l'action est valide. le premier profil + in_profiles (p): profils dans lequel l'action est valide. le premier profil est le profil par défaut - c (css): classe css à appliquer au lien - ak (accesskey): accesskey à installer sur le lien - q (query): paramètres de la requête, sous forme de dictionnaire ou de chaine + css (c): classe css à appliquer au lien + accesskey (ak): accesskey à installer sur le lien + query (q): paramètres de la requête, sous forme de dictionnaire ou de chaine method: type de requête, get ou post PROFILE_url, PROFILE_query: valeurs spécifiques à certains profils, PROFILE étant une valeur de in_profiles @@ -121,74 +121,79 @@ class Action(odict): self[querykey] = query self[urlkey] = url - def __init__(self, url, title=None, id=None, p=None, c=None, ak=None, q=None, method=None, **kw): + def __init__(self, url, title=None, id=None, in_profiles=None, css=None, accesskey=None, query=None, method=None, **kw): super(Action, self).__init__(**kw) if isseq(url): if url[1:2]: title = url[1] if url[2:3]: id = url[2] - if url[3:4]: p = url[3] - if url[4:5]: c = url[4] - if url[5:6]: ak = url[5] + 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] 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 p is None: p = kw.get('in_profiles', None) - if c is None: c = kw.get('css', None) - if ak is None: ak = kw.get('accesskey', None) - if q is None: q = kw.get('query', None) + 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 method is None: method = 'get' self.title = title self.id = id - self.css = c - self.accesskey = ak + self.css = css + self.accesskey = accesskey self.method = method self.url = url - self.query = q + self.query = query self.__update_url('url', 'query', 'baseurl') - self.in_profiles = seqof(p, None) + self.in_profiles = seqof(in_profiles, None) if self.in_profiles is None: return - for p in self.in_profiles: - self.__update_url(*['%s_%s' % (p, key) for key in ('url', 'query', 'baseurl')]) - self.__update_url(*['to_%s_%s' % (p, key) for key in ('url', 'query', 'baseurl')]) + for profile in self.in_profiles: + self.__update_url(*['%s_%s' % (profile, key) for key in ('url', 'query', 'baseurl')]) + self.__update_url(*['to_%s_%s' % (profile, key) for key in ('url', 'query', 'baseurl')]) - def __get_key(self, key, p=None, to=False): - if p is not None: - pkey = '%s%s_%s' % ('to_' if to else '', p, key) + def __get_key(self, key, profile=None, to=False): + if profile is not None: + pkey = '%s%s_%s' % ('to_' if to else '', profile, key) value = self.get(pkey, None) if value is not None: return value return self[key] - def get_baseurl(self, p=None, to=False): + def get_baseurl(self, profile=None, to=False, **kw): """obtenir l'url de base """ - return self.__get_key('baseurl', p, to) - def get_query(self, p=None, to=False): + if profile is None: profile = kw.pop('p', None) + return self.__get_key('baseurl', profile, to) + def get_query(self, profile=None, to=False, **kw): """obtenir les paramètres de la requête sous forme de dictionnaire """ - return self.__get_key('query', p, to) - def get_url(self, p=None, to=False): + if profile is None: profile = kw.pop('p', None) + return self.__get_key('query', profile, to) + def get_url(self, profile=None, to=False, **kw): """obtenir l'url """ - return self.__get_key('url', p, to) + if profile is None: profile = kw.pop('p', None) + return self.__get_key('url', profile, to) - def get_qs(self, p=None, to=False, sep=None): + def get_qs(self, profile=None, to=False, sep=None, **kw): """obtenir les paramètres de la requête sous forme de query-string sep vaut par défaut '?' mais peut valoir '&' """ - query = self.get_query(p, to) + if profile is None: profile = kw.pop('p', None) + query = self.get_query(profile, to) if sep is None: sep = '?' return '%s%s' % (sep, urllib.urlencode(query, True)) qs = property(get_qs) get_querystring = get_qs; querystring = property(get_qs) - def get_inputs(self, p=None, to=False): + def get_inputs(self, profile=None, to=False, **kw): """obtenir les paramètres de la requête sous forme d'une liste de définitions d'éléments de formulaire de type hidden """ - query = self.get_query(p, to) + if profile is None: profile = kw.pop('p', None) + query = self.get_query(profile, to) Hidden = web.form.Hidden inputs = [] for name, value in query.items(): @@ -212,7 +217,9 @@ class Menu(odict): return nextid def __init__(self, title, mitems=None, profiles=None, - id=None, p=None, c=None, **kw): + 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) super(Menu, self).__init__(**kw) self.__dict__['nextid'] = 0 self.__dict__['idmap'] = {} @@ -225,7 +232,7 @@ class Menu(odict): self.default_profile = profiles[0] if profiles is not None else None self.id = str(id) self.in_profiles = seqof(p, None) - self.css = c + self.css = css self.sel_id = None self.sel_profile = None self.active = False @@ -244,7 +251,7 @@ class Menu(odict): self.mitems.append(mitem) self.idmap[mitem.id] = mitem - def reset_selection(self): + def reset_selection(self, **kw): self.sel_id = None self.sel_profile = self.default_profile self.active = False @@ -254,7 +261,8 @@ class Menu(odict): else: mitem.active = False - def select(self, id, p=None): + def select(self, id, profile=None, **kw): + if profile is None: profile = kw.pop('p', None) # d'abord déselectionner tout le monde self.reset_selection() # ensuite chercher le mitem à sélectionner @@ -262,10 +270,10 @@ class Menu(odict): mitem = self.idmap.get(id, None) if mitem is not None: self.sel_id = id - if p is None and mitem.in_profiles: - p = mitem.in_profiles[0] - if p is None: p = self.default_profile - self.sel_profile = p + if profile is None and mitem.in_profiles: + profile = mitem.in_profiles[0] + if profile is None: profile = self.default_profile + self.sel_profile = profile mitem.active = self.active = True return True else: @@ -278,7 +286,7 @@ class Menu(odict): return True return False - def get_mitem(self, id=None): + def get_mitem(self, id=None, **kw): """retourner l'élément de menu correspondant à la sélection courante """ if id is None: id = self.sel_id diff --git a/lib/nulib/templates/webpyapp/python/app/__init__.py b/lib/nulib/templates/webpyapp/python/app/__init__.py index 08d8771..24f5fe8 100644 --- a/lib/nulib/templates/webpyapp/python/app/__init__.py +++ b/lib/nulib/templates/webpyapp/python/app/__init__.py @@ -2,11 +2,13 @@ __all__ = ( 'web', 'Page', 'bs', - 'nocache', 'auth', 'defaults', + 'nocache', 'auth', 'defaults', 'redirect', 'reset_session', 'set_session', 'session', 'check_session', + 'set_profile', 'config', ) -from nulib.web import web, Page, bs, nocache, auth, defaults +from nulib.web import web, Page, bs, nocache, auth, defaults, redirect from nulib.web import reset_session, set_session, session, check_session +from nulib.web import set_profile from nulib.web.config_loader import config