cubicweb/uilib.py
changeset 12567 26744ad37953
parent 12508 a8c1ea390400
equal deleted inserted replaced
12566:6b3523f81f42 12567:26744ad37953
    26 
    26 
    27 import csv
    27 import csv
    28 import re
    28 import re
    29 from io import StringIO
    29 from io import StringIO
    30 
    30 
    31 from six import PY2, PY3, text_type, binary_type, string_types, integer_types
       
    32 
       
    33 from logilab.mtconverter import xml_escape, html_unescape
    31 from logilab.mtconverter import xml_escape, html_unescape
    34 from logilab.common.date import ustrftime
    32 from logilab.common.date import ustrftime
    35 
    33 
    36 from cubicweb import _
    34 from cubicweb import _
    37 from cubicweb.utils import js_dumps
    35 from cubicweb.utils import js_dumps
    62     if props is not None and value and props.get('internationalizable'):
    60     if props is not None and value and props.get('internationalizable'):
    63         return req._(value)
    61         return req._(value)
    64     return value
    62     return value
    65 
    63 
    66 def print_int(value, req, props, displaytime=True):
    64 def print_int(value, req, props, displaytime=True):
    67     return text_type(value)
    65     return str(value)
    68 
    66 
    69 def print_date(value, req, props, displaytime=True):
    67 def print_date(value, req, props, displaytime=True):
    70     return ustrftime(value, req.property_value('ui.date-format'))
    68     return ustrftime(value, req.property_value('ui.date-format'))
    71 
    69 
    72 def print_time(value, req, props, displaytime=True):
    70 def print_time(value, req, props, displaytime=True):
    92 _('%d hours')
    90 _('%d hours')
    93 _('%d minutes')
    91 _('%d minutes')
    94 _('%d seconds')
    92 _('%d seconds')
    95 
    93 
    96 def print_timedelta(value, req, props, displaytime=True):
    94 def print_timedelta(value, req, props, displaytime=True):
    97     if isinstance(value, integer_types):
    95     if isinstance(value, int):
    98         # `date - date`, unlike `datetime - datetime` gives an int
    96         # `date - date`, unlike `datetime - datetime` gives an int
    99         # (number of days), not a timedelta
    97         # (number of days), not a timedelta
   100         # XXX should rql be fixed to return Int instead of Interval in
    98         # XXX should rql be fixed to return Int instead of Interval in
   101         #     that case? that would be probably the proper fix but we
    99         #     that case? that would be probably the proper fix but we
   102         #     loose information on the way...
   100         #     loose information on the way...
   122     if value:
   120     if value:
   123         return req._('yes')
   121         return req._('yes')
   124     return req._('no')
   122     return req._('no')
   125 
   123 
   126 def print_float(value, req, props, displaytime=True):
   124 def print_float(value, req, props, displaytime=True):
   127     return text_type(req.property_value('ui.float-format') % value) # XXX cast needed ?
   125     return str(req.property_value('ui.float-format') % value) # XXX cast needed ?
   128 
   126 
   129 PRINTERS = {
   127 PRINTERS = {
   130     'Bytes': print_bytes,
   128     'Bytes': print_bytes,
   131     'String': print_string,
   129     'String': print_string,
   132     'Int': print_int,
   130     'Int': print_int,
   330 
   328 
   331 class _JSId(object):
   329 class _JSId(object):
   332     def __init__(self, id, parent=None):
   330     def __init__(self, id, parent=None):
   333         self.id = id
   331         self.id = id
   334         self.parent = parent
   332         self.parent = parent
   335     def __unicode__(self):
   333     def __str__(self):
   336         if self.parent:
   334         if self.parent:
   337             return u'%s.%s' % (self.parent, self.id)
   335             return u'%s.%s' % (self.parent, self.id)
   338         return text_type(self.id)
   336         return str(self.id)
   339     __str__ = __unicode__ if PY3 else lambda self: self.__unicode__().encode('utf-8')
       
   340     def __getattr__(self, attr):
   337     def __getattr__(self, attr):
   341         return _JSId(attr, self)
   338         return _JSId(attr, self)
   342     def __call__(self, *args):
   339     def __call__(self, *args):
   343         return _JSCallArgs(args, self)
   340         return _JSCallArgs(args, self)
   344 
   341 
   345 class _JSCallArgs(_JSId):
   342 class _JSCallArgs(_JSId):
   346     def __init__(self, args, parent=None):
   343     def __init__(self, args, parent=None):
   347         assert isinstance(args, tuple)
   344         assert isinstance(args, tuple)
   348         self.args = args
   345         self.args = args
   349         self.parent = parent
   346         self.parent = parent
   350     def __unicode__(self):
   347     def __str__(self):
   351         args = []
   348         args = []
   352         for arg in self.args:
   349         for arg in self.args:
   353             args.append(js_dumps(arg))
   350             args.append(js_dumps(arg))
   354         if self.parent:
   351         if self.parent:
   355             return u'%s(%s)' % (self.parent, ','.join(args))
   352             return u'%s(%s)' % (self.parent, ','.join(args))
   356         return ','.join(args)
   353         return ','.join(args)
   357     __str__ = __unicode__ if PY3 else lambda self: self.__unicode__().encode('utf-8')
       
   358 
   354 
   359 class _JS(object):
   355 class _JS(object):
   360     def __getattr__(self, attr):
   356     def __getattr__(self, attr):
   361         return _JSId(attr)
   357         return _JSId(attr)
   362 
   358 
   385 
   381 
   386 HTML4_EMPTY_TAGS = frozenset(('base', 'meta', 'link', 'hr', 'br', 'param',
   382 HTML4_EMPTY_TAGS = frozenset(('base', 'meta', 'link', 'hr', 'br', 'param',
   387                               'img', 'area', 'input', 'col'))
   383                               'img', 'area', 'input', 'col'))
   388 
   384 
   389 def sgml_attributes(attrs):
   385 def sgml_attributes(attrs):
   390     return u' '.join(u'%s="%s"' % (attr, xml_escape(text_type(value)))
   386     return u' '.join(u'%s="%s"' % (attr, xml_escape(str(value)))
   391                      for attr, value in sorted(attrs.items())
   387                      for attr, value in sorted(attrs.items())
   392                      if value is not None)
   388                      if value is not None)
   393 
   389 
   394 def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs):
   390 def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs):
   395     """generation of a simple sgml tag (eg without children tags) easier
   391     """generation of a simple sgml tag (eg without children tags) easier
   403         except KeyError:
   399         except KeyError:
   404             pass
   400             pass
   405         value += u' ' + sgml_attributes(attrs)
   401         value += u' ' + sgml_attributes(attrs)
   406     if content:
   402     if content:
   407         if escapecontent:
   403         if escapecontent:
   408             content = xml_escape(text_type(content))
   404             content = xml_escape(str(content))
   409         value += u'>%s</%s>' % (content, tag)
   405         value += u'>%s</%s>' % (content, tag)
   410     else:
   406     else:
   411         if tag in HTML4_EMPTY_TAGS:
   407         if tag in HTML4_EMPTY_TAGS:
   412             value += u' />'
   408             value += u' />'
   413         else:
   409         else:
   432     from logilab.common.ureports import HTMLWriter
   428     from logilab.common.ureports import HTMLWriter
   433     formater = HTMLWriter(True)
   429     formater = HTMLWriter(True)
   434     stream = StringIO() #UStringIO() don't want unicode assertion
   430     stream = StringIO() #UStringIO() don't want unicode assertion
   435     formater.format(layout, stream)
   431     formater.format(layout, stream)
   436     res = stream.getvalue()
   432     res = stream.getvalue()
   437     if isinstance(res, binary_type):
   433     if isinstance(res, bytes):
   438         res = res.decode('UTF8')
   434         res = res.decode('UTF8')
   439     return res
   435     return res
   440 
   436 
   441 # traceback formatting ########################################################
   437 # traceback formatting ########################################################
   442 
   438 
   443 import traceback
   439 import traceback
   444 
   440 
   445 def exc_message(ex, encoding):
   441 def exc_message(ex, encoding):
   446     if PY3:
   442     excmsg = str(ex)
   447         excmsg = str(ex)
       
   448     else:
       
   449         try:
       
   450             excmsg = unicode(ex)
       
   451         except Exception:
       
   452             try:
       
   453                 excmsg = unicode(str(ex), encoding, 'replace')
       
   454             except Exception:
       
   455                 excmsg = unicode(repr(ex), encoding, 'replace')
       
   456     exctype = ex.__class__.__name__
   443     exctype = ex.__class__.__name__
   457     return u'%s: %s' % (exctype, excmsg)
   444     return u'%s: %s' % (exctype, excmsg)
   458 
   445 
   459 
   446 
   460 def rest_traceback(info, exception):
   447 def rest_traceback(info, exception):
   462     res = [u'Traceback\n---------\n::\n']
   449     res = [u'Traceback\n---------\n::\n']
   463     for stackentry in traceback.extract_tb(info[2]):
   450     for stackentry in traceback.extract_tb(info[2]):
   464         res.append(u'\tFile %s, line %s, function %s' % tuple(stackentry[:3]))
   451         res.append(u'\tFile %s, line %s, function %s' % tuple(stackentry[:3]))
   465         if stackentry[3]:
   452         if stackentry[3]:
   466             data = xml_escape(stackentry[3])
   453             data = xml_escape(stackentry[3])
   467             if PY2:
       
   468                 data = data.decode('utf-8', 'replace')
       
   469             res.append(u'\t  %s' % data)
   454             res.append(u'\t  %s' % data)
   470     res.append(u'\n')
   455     res.append(u'\n')
   471     try:
   456     try:
   472         res.append(u'\t Error: %s\n' % exception)
   457         res.append(u'\t Error: %s\n' % exception)
   473     except Exception:
   458     except Exception:
   499                        u'<b class="line">%s</b>, <b>function</b> '
   484                        u'<b class="line">%s</b>, <b>function</b> '
   500                        u'<b class="function">%s</b>:<br/>'%(
   485                        u'<b class="function">%s</b>:<br/>'%(
   501             xml_escape(stackentry[0]), stackentry[1], xml_escape(stackentry[2])))
   486             xml_escape(stackentry[0]), stackentry[1], xml_escape(stackentry[2])))
   502         if stackentry[3]:
   487         if stackentry[3]:
   503             string = xml_escape(stackentry[3])
   488             string = xml_escape(stackentry[3])
   504             if PY2:
       
   505                 string = string.decode('utf-8', 'replace')
       
   506             strings.append(u'&#160;&#160;%s<br/>\n' % (string))
   489             strings.append(u'&#160;&#160;%s<br/>\n' % (string))
   507         # add locals info for each entry
   490         # add locals info for each entry
   508         try:
   491         try:
   509             local_context = tcbk.tb_frame.f_locals
   492             local_context = tcbk.tb_frame.f_locals
   510             html_info = []
   493             html_info = []
   543 
   526 
   544     def write(self, data):
   527     def write(self, data):
   545         self.wfunc(data)
   528         self.wfunc(data)
   546 
   529 
   547     def writerow(self, row):
   530     def writerow(self, row):
   548         if PY3:
   531         self.writer.writerow(row)
   549             self.writer.writerow(row)
   532         return
   550             return
       
   551         csvrow = []
       
   552         for elt in row:
       
   553             if isinstance(elt, text_type):
       
   554                 csvrow.append(elt.encode(self.encoding))
       
   555             else:
       
   556                 csvrow.append(str(elt))
       
   557         self.writer.writerow(csvrow)
       
   558 
   533 
   559     def writerows(self, rows):
   534     def writerows(self, rows):
   560         for row in rows:
   535         for row in rows:
   561             self.writerow(row)
   536             self.writerow(row)
   562 
   537 
   568         self.maxsize = maxsize
   543         self.maxsize = maxsize
   569 
   544 
   570     def __call__(self, function):
   545     def __call__(self, function):
   571         def newfunc(*args, **kwargs):
   546         def newfunc(*args, **kwargs):
   572             ret = function(*args, **kwargs)
   547             ret = function(*args, **kwargs)
   573             if isinstance(ret, string_types):
   548             if isinstance(ret, str):
   574                 return ret[:self.maxsize]
   549                 return ret[:self.maxsize]
   575             return ret
   550             return ret
   576         return newfunc
   551         return newfunc
   577 
   552 
   578 
   553 
   579 def htmlescape(function):
   554 def htmlescape(function):
   580     def newfunc(*args, **kwargs):
   555     def newfunc(*args, **kwargs):
   581         ret = function(*args, **kwargs)
   556         ret = function(*args, **kwargs)
   582         assert isinstance(ret, string_types)
   557         assert isinstance(ret, str)
   583         return xml_escape(ret)
   558         return xml_escape(ret)
   584     return newfunc
   559     return newfunc