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"""
""" % css)
- if c:
+ if closeable:
lines.append(u"""""")
lines.append(p(msg))
if action is not None:
@@ -296,19 +323,19 @@ class Alert(object):
title = web.websafe(action.title or u"Cliquez ici pour continuer")
action = u"""
""" % (url, title)
lines.append(p(action))
- if e is not None:
+ if exc_info is not None:
lines.append(u"""
Pour information, le message d'erreur technique est """)
- lines.extend(traceback.format_exception_only(*e[:2]))
+ lines.extend(traceback.format_exception_only(*exc_info[:2]))
if showtb:
lines.append(u"")
lines.append(u"""
""")
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"""\
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