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 |
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)) |