utils.py
brancholdstable
changeset 4985 02b52bf9f5f8
parent 4952 f32dcf3925d4
child 4961 03e083faefbf
equal deleted inserted replaced
4563:c25da7573ebd 4985:02b52bf9f5f8
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     6 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     6 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     7 """
     7 """
     8 __docformat__ = "restructuredtext en"
     8 __docformat__ = "restructuredtext en"
     9 
     9 
    10 from logilab.mtconverter import xml_escape
    10 import os
    11 
       
    12 import locale
       
    13 import sys
    11 import sys
    14 import decimal
    12 import decimal
    15 import datetime as pydatetime
    13 import datetime
    16 from md5 import md5
    14 import random
    17 from datetime import datetime, timedelta, date
    15 
    18 from time import time
    16 from logilab.mtconverter import xml_escape
    19 from random import randint, seed
    17 from logilab.common.deprecation import deprecated
    20 from calendar import monthrange, timegm
       
    21 
    18 
    22 # initialize random seed from current time
    19 # initialize random seed from current time
    23 seed()
    20 random.seed()
    24 try:
       
    25     strptime = datetime.strptime
       
    26 except AttributeError: # py < 2.5
       
    27     from time import strptime as time_strptime
       
    28     def strptime(value, format):
       
    29         return datetime(*time_strptime(value, format)[:6])
       
    30 
       
    31 def todate(somedate):
       
    32     """return a date from a date (leaving unchanged) or a datetime"""
       
    33     if isinstance(somedate, datetime):
       
    34         return date(somedate.year, somedate.month, somedate.day)
       
    35     assert isinstance(somedate, date), repr(somedate)
       
    36     return somedate
       
    37 
       
    38 def todatetime(somedate):
       
    39     """return a date from a date (leaving unchanged) or a datetime"""
       
    40     # take care, datetime is a subclass of date
       
    41     if isinstance(somedate, datetime):
       
    42         return somedate
       
    43     assert isinstance(somedate, date), repr(somedate)
       
    44     return datetime(somedate.year, somedate.month, somedate.day)
       
    45 
       
    46 def datetime2ticks(date):
       
    47     return timegm(date.timetuple()) * 1000
       
    48 
       
    49 ONEDAY = timedelta(days=1)
       
    50 ONEWEEK = timedelta(days=7)
       
    51 
       
    52 def days_in_month(date_):
       
    53     return monthrange(date_.year, date_.month)[1]
       
    54 
       
    55 def days_in_year(date_):
       
    56     feb = pydatetime.date(date_.year, 2, 1)
       
    57     if days_in_month(feb) == 29:
       
    58         return 366
       
    59     else:
       
    60         return 365
       
    61 
       
    62 def previous_month(date_, nbmonth=1):
       
    63     while nbmonth:
       
    64         date_ = first_day(date_) - ONEDAY
       
    65         nbmonth -= 1
       
    66     return date_
       
    67 
       
    68 def next_month(date_, nbmonth=1):
       
    69     while nbmonth:
       
    70         date_ = last_day(date_) + ONEDAY
       
    71         nbmonth -= 1
       
    72     return date_
       
    73 
       
    74 def first_day(date_):
       
    75     return date(date_.year, date_.month, 1)
       
    76 
       
    77 def last_day(date_):
       
    78     return date(date_.year, date_.month, days_in_month(date_))
       
    79 
       
    80 def date_range(begin, end, incday=None, incmonth=None):
       
    81     """yields each date between begin and end
       
    82     :param begin: the start date
       
    83     :param end: the end date
       
    84     :param incr: the step to use to iterate over dates. Default is
       
    85                  one day.
       
    86     :param include: None (means no exclusion) or a function taking a
       
    87                     date as parameter, and returning True if the date
       
    88                     should be included.
       
    89     """
       
    90     assert not (incday and incmonth)
       
    91     begin = todate(begin)
       
    92     end = todate(end)
       
    93     if incmonth:
       
    94         while begin < end:
       
    95             begin = next_month(begin, incmonth)
       
    96             yield begin
       
    97     else:
       
    98         if not incday:
       
    99             incr = ONEDAY
       
   100         else:
       
   101             incr = timedelta(incday)
       
   102         while begin <= end:
       
   103            yield begin
       
   104            begin += incr
       
   105 
       
   106 def ustrftime(date, fmt='%Y-%m-%d'):
       
   107     """like strftime, but returns a unicode string instead of an encoded
       
   108     string which' may be problematic with localized date.
       
   109 
       
   110     encoding is guessed by locale.getpreferredencoding()
       
   111     """
       
   112     # date format may depend on the locale
       
   113     encoding = locale.getpreferredencoding(do_setlocale=False) or 'UTF-8'
       
   114     return unicode(date.strftime(str(fmt)), encoding)
       
   115 
       
   116 
    21 
   117 if sys.version_info[:2] < (2, 5):
    22 if sys.version_info[:2] < (2, 5):
       
    23 
       
    24     from time import time
       
    25     from md5 import md5
       
    26     from random import randint
       
    27 
   118     def make_uid(key):
    28     def make_uid(key):
   119         """forge a unique identifier
    29         """forge a unique identifier
   120         not that unique on win32"""
    30         XXX not that unique on win32
   121         msg = str(key) + "%.10f" % time() + str(randint(0, 1000000))
    31         """
   122         return md5(msg).hexdigest()
    32         key = str(key)
       
    33         msg = key + "%.10f" % time() + str(randint(0, 1000000))
       
    34         return key + md5(msg).hexdigest()
       
    35 
   123 else:
    36 else:
       
    37 
   124     from uuid import uuid4
    38     from uuid import uuid4
       
    39 
   125     def make_uid(key):
    40     def make_uid(key):
   126         # remove dash, generated uid are used as identifier sometimes (sql table
    41         # remove dash, generated uid are used as identifier sometimes (sql table
   127         # names at least)
    42         # names at least)
   128         return str(key) + str(uuid4()).replace('-', '')
    43         return str(key) + str(uuid4()).replace('-', '')
   129 
    44 
   252     def add_post_inline_script(self, content):
   167     def add_post_inline_script(self, content):
   253         self.post_inlined_scripts.append(content)
   168         self.post_inlined_scripts.append(content)
   254 
   169 
   255     def add_onload(self, jscode, jsoncall=False):
   170     def add_onload(self, jscode, jsoncall=False):
   256         if jsoncall:
   171         if jsoncall:
   257             self.add_post_inline_script(u"""jQuery(CubicWeb).bind('ajax-loaded', function(event) {
   172             self.add_post_inline_script(u"""jQuery(CubicWeb).one('ajax-loaded', function(event) {
   258 %s
   173 %s
   259 });""" % jscode)
   174 });""" % jscode)
   260         else:
   175         else:
   261             self.add_post_inline_script(u"""jQuery(document).ready(function () {
   176             self.add_post_inline_script(u"""jQuery(document).ready(function () {
   262  %s
   177  %s
   279         :param cssfile: the stylesheet's URL
   194         :param cssfile: the stylesheet's URL
   280         """
   195         """
   281         if (cssfile, media) not in self.cssfiles:
   196         if (cssfile, media) not in self.cssfiles:
   282             self.cssfiles.append( (cssfile, media) )
   197             self.cssfiles.append( (cssfile, media) )
   283 
   198 
   284     def add_ie_css(self, cssfile, media='all'):
   199     def add_ie_css(self, cssfile, media='all', iespec=u'[if lt IE 8]'):
   285         """registers some IE specific CSS"""
   200         """registers some IE specific CSS"""
   286         if (cssfile, media) not in self.ie_cssfiles:
   201         if (cssfile, media, iespec) not in self.ie_cssfiles:
   287             self.ie_cssfiles.append( (cssfile, media) )
   202             self.ie_cssfiles.append( (cssfile, media, iespec) )
   288 
   203 
   289     def add_unload_pagedata(self):
   204     def add_unload_pagedata(self):
   290         """registers onunload callback to clean page data on server"""
   205         """registers onunload callback to clean page data on server"""
   291         if not self.pagedata_unload:
   206         if not self.pagedata_unload:
   292             self.post_inlined_scripts.append(self.js_unload_code)
   207             self.post_inlined_scripts.append(self.js_unload_code)
   312         for cssfile, media in self.cssfiles:
   227         for cssfile, media in self.cssfiles:
   313             w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
   228             w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
   314               (media, xml_escape(cssfile)))
   229               (media, xml_escape(cssfile)))
   315         # 3/ ie css if necessary
   230         # 3/ ie css if necessary
   316         if self.ie_cssfiles:
   231         if self.ie_cssfiles:
   317             w(u'<!--[if lt IE 8]>\n')
   232             for cssfile, media, iespec in self.ie_cssfiles:
   318             for cssfile, media in self.ie_cssfiles:
   233                 w(u'<!--%s>\n' % iespec)
   319                 w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
   234                 w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
   320                   (media, xml_escape(cssfile)))
   235                   (media, xml_escape(cssfile)))
   321             w(u'<![endif]--> \n')
   236             w(u'<![endif]--> \n')
   322         # 4/ js files
   237         # 4/ js files
   323         for jsfile in self.jsfiles:
   238         for jsfile in self.jsfiles:
   369                                                  self.htmltag,
   284                                                  self.htmltag,
   370                                                  self.head.getvalue(),
   285                                                  self.head.getvalue(),
   371                                                  self.body.getvalue())
   286                                                  self.body.getvalue())
   372 
   287 
   373 
   288 
   374 def can_do_pdf_conversion(__answer=[None]):
   289 def _pdf_conversion_availability():
   375     """pdf conversion depends on
       
   376     * pysixt (python package)
       
   377     * fop 0.9x
       
   378     """
       
   379     if __answer[0] is not None:
       
   380         return __answer[0]
       
   381     try:
   290     try:
   382         import pysixt
   291         import pysixt
   383     except ImportError:
   292     except ImportError:
   384         __answer[0] = False
       
   385         return False
   293         return False
   386     from subprocess import Popen, STDOUT
   294     from subprocess import Popen, STDOUT
   387     import os
   295     if not os.path.isfile('/usr/bin/fop'):
       
   296         return False
   388     try:
   297     try:
   389         Popen(['/usr/bin/fop', '-q'],
   298         Popen(['/usr/bin/fop', '-q'],
   390               stdout=open(os.devnull, 'w'),
   299               stdout=open(os.devnull, 'w'),
   391               stderr=STDOUT)
   300               stderr=STDOUT)
   392     except OSError, e:
   301     except OSError, e:
   393         print e
   302         getLogger('cubicweb').info('fop not usable (%s)', e)
   394         __answer[0] = False
       
   395         return False
   303         return False
   396     __answer[0] = True
       
   397     return True
   304     return True
       
   305 
       
   306 def can_do_pdf_conversion(__answer_cache=[]):
       
   307     """pdf conversion depends on
       
   308     * pysixt (python package)
       
   309     * fop 0.9x
       
   310 
       
   311     NOTE: actual check is done by _pdf_conversion_availability and
       
   312     result is cached
       
   313     """
       
   314     if not __answer_cache: # first time, not in cache
       
   315         __answer_cache.append(_pdf_conversion_availability())
       
   316     return __answer_cache[0]
   398 
   317 
   399 try:
   318 try:
   400     # may not be there if cubicweb-web not installed
   319     # may not be there if cubicweb-web not installed
   401     from simplejson import dumps, JSONEncoder
   320     from simplejson import dumps, JSONEncoder
   402 except ImportError:
   321 except ImportError:
   404 else:
   323 else:
   405 
   324 
   406     class CubicWebJsonEncoder(JSONEncoder):
   325     class CubicWebJsonEncoder(JSONEncoder):
   407         """define a simplejson encoder to be able to encode yams std types"""
   326         """define a simplejson encoder to be able to encode yams std types"""
   408         def default(self, obj):
   327         def default(self, obj):
   409             if isinstance(obj, pydatetime.datetime):
   328             if isinstance(obj, datetime.datetime):
   410                 return obj.strftime('%Y/%m/%d %H:%M:%S')
   329                 return obj.strftime('%Y/%m/%d %H:%M:%S')
   411             elif isinstance(obj, pydatetime.date):
   330             elif isinstance(obj, datetime.date):
   412                 return obj.strftime('%Y/%m/%d')
   331                 return obj.strftime('%Y/%m/%d')
   413             elif isinstance(obj, pydatetime.time):
   332             elif isinstance(obj, datetime.time):
   414                 return obj.strftime('%H:%M:%S')
   333                 return obj.strftime('%H:%M:%S')
   415             elif isinstance(obj, pydatetime.timedelta):
   334             elif isinstance(obj, datetime.timedelta):
   416                 return (obj.days * 24 * 60 * 60) + obj.seconds
   335                 return (obj.days * 24 * 60 * 60) + obj.seconds
   417             elif isinstance(obj, decimal.Decimal):
   336             elif isinstance(obj, decimal.Decimal):
   418                 return float(obj)
   337                 return float(obj)
   419             try:
   338             try:
   420                 return JSONEncoder.default(self, obj)
   339                 return JSONEncoder.default(self, obj)
   421             except TypeError:
   340             except TypeError:
   422                 # we never ever want to fail because of an unknown type,
   341                 # we never ever want to fail because of an unknown type,
   423                 # just return None in those cases.
   342                 # just return None in those cases.
   424                 return None
   343                 return None
       
   344 
       
   345 from logilab.common import date
       
   346 _THIS_MOD_NS = globals()
       
   347 for funcname in ('date_range', 'todate', 'todatetime', 'datetime2ticks',
       
   348                  'days_in_month', 'days_in_year', 'previous_month',
       
   349                  'next_month', 'first_day', 'last_day', 'ustrftime',
       
   350                  'strptime'):
       
   351     msg = '[3.6] %s has been moved to logilab.common.date' % funcname
       
   352     _THIS_MOD_NS[funcname] = deprecated(msg)(getattr(date, funcname))