1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 # Copyright (C) 2010-2014 OpenERP s.a. (<http://openerp.com>).
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
25 Miscellaneous tools used by OpenERP.
28 from functools import wraps
30 from contextlib import contextmanager
40 from collections import defaultdict, Mapping
41 from datetime import datetime
42 from itertools import islice, izip, groupby
43 from lxml import etree
44 from which import which
45 from threading import local
49 from html2text import html2text
53 from config import config
55 from .parse_version import parse_version
58 # get_encodings, ustr and exception_to_unicode were originally from tools.misc.
59 # There are moved to loglevels until we refactor tools.
60 from openerp.loglevels import get_encodings, ustr, exception_to_unicode # noqa
62 _logger = logging.getLogger(__name__)
64 # List of etree._Element subclasses that we choose to ignore when parsing XML.
65 # We include the *Base ones just in case, currently they seem to be subclasses of the _* ones.
66 SKIPPED_ELEMENT_TYPES = (etree._Comment, etree._ProcessingInstruction, etree.CommentBase, etree.PIBase)
68 def find_in_path(name):
69 path = os.environ.get('PATH', os.defpath).split(os.pathsep)
70 if config.get('bin_path') and config['bin_path'] != 'None':
71 path.append(config['bin_path'])
73 return which(name, path=os.pathsep.join(path))
77 def find_pg_tool(name):
79 if config['pg_path'] and config['pg_path'] != 'None':
80 path = config['pg_path']
82 return which(name, path=path)
86 def exec_pg_command(name, *args):
87 prog = find_pg_tool(name)
89 raise Exception('Couldn\'t find %s' % name)
90 args2 = (prog,) + args
92 with open(os.devnull) as dn:
93 return subprocess.call(args2, stdout=dn, stderr=subprocess.STDOUT)
95 def exec_pg_command_pipe(name, *args):
96 prog = find_pg_tool(name)
98 raise Exception('Couldn\'t find %s' % name)
99 # on win32, passing close_fds=True is not compatible
100 # with redirecting std[in/err/out]
101 pop = subprocess.Popen((prog,) + args, bufsize= -1,
102 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
103 close_fds=(os.name=="posix"))
104 return pop.stdin, pop.stdout
106 def exec_command_pipe(name, *args):
107 prog = find_in_path(name)
109 raise Exception('Couldn\'t find %s' % name)
110 # on win32, passing close_fds=True is not compatible
111 # with redirecting std[in/err/out]
112 pop = subprocess.Popen((prog,) + args, bufsize= -1,
113 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
114 close_fds=(os.name=="posix"))
115 return pop.stdin, pop.stdout
117 #----------------------------------------------------------
119 #----------------------------------------------------------
120 #file_path_root = os.getcwd()
121 #file_path_addons = os.path.join(file_path_root, 'addons')
123 def file_open(name, mode="r", subdir='addons', pathinfo=False):
124 """Open a file from the OpenERP root, using a subdir folder.
128 >>> file_open('hr/report/timesheer.xsl')
129 >>> file_open('addons/hr/report/timesheet.xsl')
130 >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
132 @param name name of the file
133 @param mode file open mode
134 @param subdir subdirectory
135 @param pathinfo if True returns tuple (fileobject, filepath)
137 @return fileobject if pathinfo is False else (fileobject, filepath)
139 import openerp.modules as addons
140 adps = addons.module.ad_paths
141 rtp = os.path.normcase(os.path.abspath(config['root_path']))
145 if os.path.isabs(name):
146 # It is an absolute path
147 # Is it below 'addons_path' or 'root_path'?
148 name = os.path.normcase(os.path.normpath(name))
149 for root in adps + [rtp]:
150 root = os.path.normcase(os.path.normpath(root)) + os.sep
151 if name.startswith(root):
152 base = root.rstrip(os.sep)
153 name = name[len(base) + 1:]
156 # It is outside the OpenERP root: skip zipfile lookup.
157 base, name = os.path.split(name)
158 return _fileopen(name, mode=mode, basedir=base, pathinfo=pathinfo, basename=basename)
160 if name.replace(os.sep, '/').startswith('addons/'):
164 name = os.path.join(subdir, name)
165 if name.replace(os.sep, '/').startswith('addons/'):
171 # First, try to locate in addons_path
175 return _fileopen(name2, mode=mode, basedir=adp,
176 pathinfo=pathinfo, basename=basename)
180 # Second, try to locate in root_path
181 return _fileopen(name, mode=mode, basedir=rtp, pathinfo=pathinfo, basename=basename)
184 def _fileopen(path, mode, basedir, pathinfo, basename=None):
185 name = os.path.normpath(os.path.join(basedir, path))
189 # Give higher priority to module directories, which is
190 # a more common case than zipped modules.
191 if os.path.isfile(name):
192 fo = open(name, mode)
197 # Support for loading modules in zipped form.
198 # This will not work for zipped modules that are sitting
199 # outside of known addons paths.
200 head = os.path.normpath(path)
202 while os.sep in head:
203 head, tail = os.path.split(head)
207 zipname = os.path.join(tail, zipname)
210 zpath = os.path.join(basedir, head + '.zip')
211 if zipfile.is_zipfile(zpath):
212 from cStringIO import StringIO
213 zfile = zipfile.ZipFile(zpath)
216 fo.write(zfile.read(os.path.join(
217 os.path.basename(head), zipname).replace(
226 if name.endswith('.rml'):
227 raise IOError('Report %r doesn\'t exist or deleted' % basename)
228 raise IOError('File not found: %s' % basename)
231 #----------------------------------------------------------
233 #----------------------------------------------------------
235 """Flatten a list of elements into a uniqu list
236 Author: Christophe Simonis (christophe@tinyerp.com)
245 >>> flatten( [[], [[]]] )
247 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
248 ['a', 'b', 'c', 'd', 'e', 'f']
249 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
251 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
255 return hasattr(x, "__iter__")
260 map(r.append, flatten(e))
265 def reverse_enumerate(l):
266 """Like enumerate but in the other sens
269 >>> a = ['a', 'b', 'c']
270 >>> it = reverse_enumerate(a)
278 Traceback (most recent call last):
279 File "<stdin>", line 1, in <module>
282 return izip(xrange(len(l)-1, -1, -1), reversed(l))
284 def topological_sort(elems):
285 """ Return a list of elements sorted so that their dependencies are listed
286 before them in the result.
288 :param elems: specifies the elements to sort with their dependencies; it is
289 a dictionary like `{element: dependencies}` where `dependencies` is a
290 collection of elements that must appear before `element`. The elements
291 of `dependencies` are not required to appear in `elems`; they will
292 simply not appear in the result.
294 :returns: a list with the keys of `elems` sorted according to their
297 # the algorithm is inspired by [Tarjan 1976],
298 # http://en.wikipedia.org/wiki/Topological_sorting#Algorithms
306 # first visit all dependencies of n, then append n to result
315 class UpdateableStr(local):
316 """ Class that stores an updateable string (used in wizards)
319 def __init__(self, string=''):
323 return str(self.string)
326 return str(self.string)
328 def __nonzero__(self):
329 return bool(self.string)
332 class UpdateableDict(local):
333 """Stores an updateable dict to use in wizards
336 def __init__(self, dict=None):
342 return str(self.dict)
345 return str(self.dict)
348 return self.dict.clear()
351 return self.dict.keys()
353 def __setitem__(self, i, y):
354 self.dict.__setitem__(i, y)
356 def __getitem__(self, i):
357 return self.dict.__getitem__(i)
360 return self.dict.copy()
363 return self.dict.iteritems()
366 return self.dict.iterkeys()
368 def itervalues(self):
369 return self.dict.itervalues()
371 def pop(self, k, d=None):
372 return self.dict.pop(k, d)
375 return self.dict.popitem()
377 def setdefault(self, k, d=None):
378 return self.dict.setdefault(k, d)
380 def update(self, E, **F):
381 return self.dict.update(E, F)
384 return self.dict.values()
386 def get(self, k, d=None):
387 return self.dict.get(k, d)
389 def has_key(self, k):
390 return self.dict.has_key(k)
393 return self.dict.items()
395 def __cmp__(self, y):
396 return self.dict.__cmp__(y)
398 def __contains__(self, k):
399 return self.dict.__contains__(k)
401 def __delitem__(self, y):
402 return self.dict.__delitem__(y)
405 return self.dict.__eq__(y)
408 return self.dict.__ge__(y)
411 return self.dict.__gt__(y)
414 return self.dict.__hash__()
417 return self.dict.__iter__()
420 return self.dict.__le__(y)
423 return self.dict.__len__()
426 return self.dict.__lt__(y)
429 return self.dict.__ne__(y)
431 class currency(float):
436 Don't use ! Use res.currency.round()
439 def __init__(self, value, accuracy=2, rounding=None):
441 rounding=10**-accuracy
442 self.rounding=rounding
443 self.accuracy=accuracy
445 def __new__(cls, value, accuracy=2, rounding=None):
446 return float.__new__(cls, round(value, accuracy))
449 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
450 # return str(display_value)
453 return s.replace('&','&').replace('<','<').replace('>','>')
455 def get_iso_codes(lang):
456 if lang.find('_') != -1:
457 if lang.split('_')[0] == lang.split('_')[1].lower():
458 lang = lang.split('_')[0]
462 'ab_RU': u'Abkhazian / аҧсуа',
463 'am_ET': u'Amharic / አምሃርኛ',
464 'ar_SY': u'Arabic / الْعَرَبيّة',
465 'bg_BG': u'Bulgarian / български език',
466 'bs_BA': u'Bosnian / bosanski jezik',
467 'ca_ES': u'Catalan / Català',
468 'cs_CZ': u'Czech / Čeština',
469 'da_DK': u'Danish / Dansk',
470 'de_DE': u'German / Deutsch',
471 'el_GR': u'Greek / Ελληνικά',
472 'en_CA': u'English (CA)',
473 'en_GB': u'English (UK)',
474 'en_US': u'English (US)',
475 'es_AR': u'Spanish (AR) / Español (AR)',
476 'es_BO': u'Spanish (BO) / Español (BO)',
477 'es_CL': u'Spanish (CL) / Español (CL)',
478 'es_CO': u'Spanish (CO) / Español (CO)',
479 'es_CR': u'Spanish (CR) / Español (CR)',
480 'es_DO': u'Spanish (DO) / Español (DO)',
481 'es_EC': u'Spanish (EC) / Español (EC)',
482 'es_ES': u'Spanish / Español',
483 'es_GT': u'Spanish (GT) / Español (GT)',
484 'es_HN': u'Spanish (HN) / Español (HN)',
485 'es_MX': u'Spanish (MX) / Español (MX)',
486 'es_NI': u'Spanish (NI) / Español (NI)',
487 'es_PA': u'Spanish (PA) / Español (PA)',
488 'es_PE': u'Spanish (PE) / Español (PE)',
489 'es_PR': u'Spanish (PR) / Español (PR)',
490 'es_PY': u'Spanish (PY) / Español (PY)',
491 'es_SV': u'Spanish (SV) / Español (SV)',
492 'es_UY': u'Spanish (UY) / Español (UY)',
493 'es_VE': u'Spanish (VE) / Español (VE)',
494 'et_EE': u'Estonian / Eesti keel',
495 'fa_IR': u'Persian / فارس',
496 'fi_FI': u'Finnish / Suomi',
497 'fr_BE': u'French (BE) / Français (BE)',
498 'fr_CA': u'French (CA) / Français (CA)',
499 'fr_CH': u'French (CH) / Français (CH)',
500 'fr_FR': u'French / Français',
501 'gl_ES': u'Galician / Galego',
502 'gu_IN': u'Gujarati / ગુજરાતી',
503 'he_IL': u'Hebrew / עִבְרִי',
504 'hi_IN': u'Hindi / हिंदी',
505 'hr_HR': u'Croatian / hrvatski jezik',
506 'hu_HU': u'Hungarian / Magyar',
507 'id_ID': u'Indonesian / Bahasa Indonesia',
508 'it_IT': u'Italian / Italiano',
509 'iu_CA': u'Inuktitut / ᐃᓄᒃᑎᑐᑦ',
510 'ja_JP': u'Japanese / 日本語',
511 'ko_KP': u'Korean (KP) / 한국어 (KP)',
512 'ko_KR': u'Korean (KR) / 한국어 (KR)',
513 'lo_LA': u'Lao / ພາສາລາວ',
514 'lt_LT': u'Lithuanian / Lietuvių kalba',
515 'lv_LV': u'Latvian / latviešu valoda',
516 'mk_MK': u'Macedonian / македонски јазик',
517 'ml_IN': u'Malayalam / മലയാളം',
518 'mn_MN': u'Mongolian / монгол',
519 'nb_NO': u'Norwegian Bokmål / Norsk bokmål',
520 'nl_NL': u'Dutch / Nederlands',
521 'nl_BE': u'Flemish (BE) / Vlaams (BE)',
522 'oc_FR': u'Occitan (FR, post 1500) / Occitan',
523 'pl_PL': u'Polish / Język polski',
524 'pt_BR': u'Portuguese (BR) / Português (BR)',
525 'pt_PT': u'Portuguese / Português',
526 'ro_RO': u'Romanian / română',
527 'ru_RU': u'Russian / русский язык',
528 'si_LK': u'Sinhalese / සිංහල',
529 'sl_SI': u'Slovenian / slovenščina',
530 'sk_SK': u'Slovak / Slovenský jazyk',
531 'sq_AL': u'Albanian / Shqip',
532 'sr_RS': u'Serbian (Cyrillic) / српски',
533 'sr@latin': u'Serbian (Latin) / srpski',
534 'sv_SE': u'Swedish / svenska',
535 'te_IN': u'Telugu / తెలుగు',
536 'tr_TR': u'Turkish / Türkçe',
537 'vi_VN': u'Vietnamese / Tiếng Việt',
538 'uk_UA': u'Ukrainian / українська',
539 'ur_PK': u'Urdu / اردو',
540 'zh_CN': u'Chinese (CN) / 简体中文',
541 'zh_HK': u'Chinese (HK)',
542 'zh_TW': u'Chinese (TW) / 正體字',
543 'th_TH': u'Thai / ภาษาไทย',
544 'tlh_TLH': u'Klingon',
547 def scan_languages():
548 """ Returns all languages supported by OpenERP for translation
550 :returns: a list of (lang_code, lang_name) pairs
551 :rtype: [(str, unicode)]
553 return sorted(ALL_LANGUAGES.iteritems(), key=lambda k: k[1])
555 def get_user_companies(cr, user):
556 def _get_company_children(cr, ids):
559 cr.execute('SELECT id FROM res_company WHERE parent_id IN %s', (tuple(ids),))
560 res = [x[0] for x in cr.fetchall()]
561 res.extend(_get_company_children(cr, res))
563 cr.execute('SELECT company_id FROM res_users WHERE id=%s', (user,))
564 user_comp = cr.fetchone()[0]
567 return [user_comp] + _get_company_children(cr, [user_comp])
571 Input number : account or invoice number
572 Output return: the same number completed with the recursive mod10
575 codec=[0,9,4,6,8,2,7,1,3,5]
581 report = codec[ (int(digit) + report) % 10 ]
582 return result + str((10 - report) % 10)
587 Return the size in a human readable format
591 units = ('bytes', 'Kb', 'Mb', 'Gb')
592 if isinstance(sz,basestring):
595 while s >= 1024 and i < len(units)-1:
598 return "%0.2f %s" % (s, units[i])
602 def wrapper(*args, **kwargs):
603 from pprint import pformat
605 vector = ['Call -> function: %r' % f]
606 for i, arg in enumerate(args):
607 vector.append(' arg %02d: %s' % (i, pformat(arg)))
608 for key, value in kwargs.items():
609 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
612 res = f(*args, **kwargs)
614 vector.append(' result: %s' % pformat(res))
615 vector.append(' time delta: %s' % (time.time() - timeb4))
616 _logger.debug('\n'.join(vector))
621 class profile(object):
622 def __init__(self, fname=None):
625 def __call__(self, f):
627 def wrapper(*args, **kwargs):
628 profile = cProfile.Profile()
629 result = profile.runcall(f, *args, **kwargs)
630 profile.dump_stats(self.fname or ("%s.cprof" % (f.func_name,)))
635 __icons_list = ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
636 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
637 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
638 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
639 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
640 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
641 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
642 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
643 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
644 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
645 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
646 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
647 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
648 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
649 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
650 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
651 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
652 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
653 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
654 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
655 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
656 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
657 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
658 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
659 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
660 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
661 'terp-check','terp-go-month','terp-go-year','terp-go-today','terp-document-new','terp-camera_test',
662 'terp-emblem-important','terp-gtk-media-pause','terp-gtk-stop','terp-gnome-cpu-frequency-applet+',
663 'terp-dialog-close','terp-gtk-jump-to-rtl','terp-gtk-jump-to-ltr','terp-accessories-archiver',
664 'terp-stock_align_left_24','terp-stock_effects-object-colorize','terp-go-home','terp-gtk-go-back-rtl',
665 'terp-gtk-go-back-ltr','terp-personal','terp-personal-','terp-personal+','terp-accessories-archiver-minus',
666 'terp-accessories-archiver+','terp-stock_symbol-selection','terp-call-start','terp-dolar',
667 'terp-face-plain','terp-folder-blue','terp-folder-green','terp-folder-orange','terp-folder-yellow',
668 'terp-gdu-smart-failing','terp-go-week','terp-gtk-select-all','terp-locked','terp-mail-forward',
669 'terp-mail-message-new','terp-mail-replied','terp-rating-rated','terp-stage','terp-stock_format-scientific',
670 'terp-dolar_ok!','terp-idea','terp-stock_format-default','terp-mail-','terp-mail_delete'
675 return [(x, x) for x in __icons_list ]
677 def detect_ip_addr():
678 """Try a very crude method to figure out a valid external
679 IP or hostname for the current machine. Don't rely on this
680 for binding to an interface, but it could be used as basis
681 for constructing a remote URL to the server.
683 def _detect_ip_addr():
684 from array import array
685 from struct import pack, unpack
694 if not fcntl: # not UNIX:
695 host = socket.gethostname()
696 ip_addr = socket.gethostbyname(host)
698 # get all interfaces:
700 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
701 names = array('B', '\0' * nbytes)
702 #print 'names: ', names
703 outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0]
704 namestr = names.tostring()
707 for i in range(0, outbytes, 40):
708 name = namestr[i:i+16].split('\0', 1)[0]
710 ip_addr = socket.inet_ntoa(namestr[i+20:i+24])
715 ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)])
717 for ifname in [iface for iface in ifaces if iface != 'lo']:
718 ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
721 return ip_addr or 'localhost'
724 ip_addr = _detect_ip_addr()
726 ip_addr = 'localhost'
729 # RATIONALE BEHIND TIMESTAMP CALCULATIONS AND TIMEZONE MANAGEMENT:
730 # The server side never does any timestamp calculation, always
731 # sends them in a naive (timezone agnostic) format supposed to be
732 # expressed within the server timezone, and expects the clients to
733 # provide timestamps in the server timezone as well.
734 # It stores all timestamps in the database in naive format as well,
735 # which also expresses the time in the server timezone.
736 # For this reason the server makes its timezone name available via the
737 # common/timezone_get() rpc method, which clients need to read
738 # to know the appropriate time offset to use when reading/writing
740 def get_win32_timezone():
741 """Attempt to return the "standard name" of the current timezone on a win32 system.
742 @return the standard name of the current win32 timezone, or False if it cannot be found.
745 if sys.platform == "win32":
748 hklm = _winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
749 current_tz_key = _winreg.OpenKey(hklm, r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation", 0,_winreg.KEY_ALL_ACCESS)
750 res = str(_winreg.QueryValueEx(current_tz_key,"StandardName")[0]) # [0] is value, [1] is type code
751 _winreg.CloseKey(current_tz_key)
752 _winreg.CloseKey(hklm)
757 def detect_server_timezone():
758 """Attempt to detect the timezone to use on the server side.
759 Defaults to UTC if no working timezone can be found.
760 @return the timezone identifier as expected by pytz.timezone.
765 _logger.warning("Python pytz module is not available. "
766 "Timezone will be set to UTC by default.")
769 # Option 1: the configuration option (did not exist before, so no backwards compatibility issue)
770 # Option 2: to be backwards compatible with 5.0 or earlier, the value from time.tzname[0], but only if it is known to pytz
771 # Option 3: the environment variable TZ
772 sources = [ (config['timezone'], 'OpenERP configuration'),
773 (time.tzname[0], 'time.tzname'),
774 (os.environ.get('TZ',False),'TZ environment variable'), ]
775 # Option 4: OS-specific: /etc/timezone on Unix
776 if os.path.exists("/etc/timezone"):
779 f = open("/etc/timezone")
780 tz_value = f.read(128).strip()
785 sources.append((tz_value,"/etc/timezone file"))
786 # Option 5: timezone info from registry on Win32
787 if sys.platform == "win32":
788 # Timezone info is stored in windows registry.
789 # However this is not likely to work very well as the standard name
790 # of timezones in windows is rarely something that is known to pytz.
791 # But that's ok, it is always possible to use a config option to set
793 sources.append((get_win32_timezone(),"Windows Registry"))
795 for (value,source) in sources:
798 tz = pytz.timezone(value)
799 _logger.info("Using timezone %s obtained from %s.", tz.zone, source)
801 except pytz.UnknownTimeZoneError:
802 _logger.warning("The timezone specified in %s (%s) is invalid, ignoring it.", source, value)
804 _logger.warning("No valid timezone could be detected, using default UTC "
805 "timezone. You can specify it explicitly with option 'timezone' in "
806 "the server configuration.")
809 def get_server_timezone():
813 DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
814 DEFAULT_SERVER_TIME_FORMAT = "%H:%M:%S"
815 DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % (
816 DEFAULT_SERVER_DATE_FORMAT,
817 DEFAULT_SERVER_TIME_FORMAT)
819 # Python's strftime supports only the format directives
820 # that are available on the platform's libc, so in order to
821 # be cross-platform we map to the directives required by
822 # the C standard (1989 version), always available on platforms
823 # with a C standard implementation.
824 DATETIME_FORMATS_MAP = {
826 '%D': '%m/%d/%Y', # modified %y->%Y
828 '%E': '', # special modifier
830 '%g': '%Y', # modified %y->%Y
836 '%O': '', # special modifier
840 '%s': '', #num of seconds since epoch
845 '%y': '%Y', # Even if %y works, it's ambiguous, so we should use %Y
846 '%+': '%Y-%m-%d %H:%M:%S',
848 # %Z is a special case that causes 2 problems at least:
849 # - the timezone names we use (in res_user.context_tz) come
850 # from pytz, but not all these names are recognized by
851 # strptime(), so we cannot convert in both directions
852 # when such a timezone is selected and %Z is in the format
853 # - %Z is replaced by an empty string in strftime() when
854 # there is not tzinfo in a datetime value (e.g when the user
855 # did not pick a context_tz). The resulting string does not
856 # parse back if the format requires %Z.
857 # As a consequence, we strip it completely from format strings.
858 # The user can always have a look at the context_tz in
859 # preferences to check the timezone.
883 # see comments above, and babel's format_datetime assumes an UTC timezone
884 # for naive datetime objects
889 def posix_to_ldml(fmt, locale):
890 """ Converts a posix/strftime pattern into an LDML date format pattern.
892 :param fmt: non-extended C89/C90 strftime pattern
893 :param locale: babel locale used for locale-specific conversions (e.g. %x and %X)
901 # LDML date format patterns uses letters, so letters must be quoted
902 if not pc and c.isalpha():
903 quoted.append(c if c != "'" else "''")
907 buf.append(''.join(quoted))
912 if c == '%': # escaped percent
914 elif c == 'x': # date format, short seems to match
915 buf.append(locale.date_formats['short'].pattern)
916 elif c == 'X': # time format, seems to include seconds. short does not
917 buf.append(locale.time_formats['medium'].pattern)
918 else: # look up format char in static mapping
919 buf.append(POSIX_TO_LDML[c])
926 # flush anything remaining in quoted buffer
929 buf.append(''.join(quoted))
934 def server_to_local_timestamp(src_tstamp_str, src_format, dst_format, dst_tz_name,
935 tz_offset=True, ignore_unparsable_time=True):
937 Convert a source timestamp string into a destination timestamp string, attempting to apply the
938 correct offset if both the server and local timezone are recognized, or no
939 offset at all if they aren't or if tz_offset is false (i.e. assuming they are both in the same TZ).
941 WARNING: This method is here to allow formatting dates correctly for inclusion in strings where
942 the client would not be able to format/offset it correctly. DO NOT use it for returning
943 date fields directly, these are supposed to be handled by the client!!
945 @param src_tstamp_str: the str value containing the timestamp in the server timezone.
946 @param src_format: the format to use when parsing the server timestamp.
947 @param dst_format: the format to use when formatting the resulting timestamp for the local/client timezone.
948 @param dst_tz_name: name of the destination timezone (such as the 'tz' value of the client context)
949 @param ignore_unparsable_time: if True, return False if src_tstamp_str cannot be parsed
950 using src_format or formatted using dst_format.
952 @return local/client formatted timestamp, expressed in the local/client timezone if possible
953 and if tz_offset is true, or src_tstamp_str if timezone offset could not be determined.
955 if not src_tstamp_str:
959 if src_format and dst_format:
960 # find out server timezone
961 server_tz = get_server_timezone()
963 # dt_value needs to be a datetime.datetime object (so no time.struct_time or mx.DateTime.DateTime here!)
964 dt_value = datetime.strptime(src_tstamp_str, src_format)
965 if tz_offset and dst_tz_name:
968 src_tz = pytz.timezone(server_tz)
969 dst_tz = pytz.timezone(dst_tz_name)
970 src_dt = src_tz.localize(dt_value, is_dst=True)
971 dt_value = src_dt.astimezone(dst_tz)
974 res = dt_value.strftime(dst_format)
976 # Normal ways to end up here are if strptime or strftime failed
977 if not ignore_unparsable_time:
982 def split_every(n, iterable, piece_maker=tuple):
983 """Splits an iterable into length-n pieces. The last piece will be shorter
984 if ``n`` does not evenly divide the iterable length.
985 @param ``piece_maker``: function to build the pieces
986 from the slices (tuple,list,...)
988 iterator = iter(iterable)
989 piece = piece_maker(islice(iterator, n))
992 piece = piece_maker(islice(iterator, n))
994 if __name__ == '__main__':
998 class upload_data_thread(threading.Thread):
999 def __init__(self, email, data, type):
1000 self.args = [('email',email),('type',type),('data',data)]
1001 super(upload_data_thread,self).__init__()
1005 args = urllib.urlencode(self.args)
1006 fp = urllib.urlopen('http://www.openerp.com/scripts/survey.php', args)
1012 def upload_data(email, data, type='SURVEY'):
1013 a = upload_data_thread(email, data, type)
1017 def get_and_group_by_field(cr, uid, obj, ids, field, context=None):
1018 """ Read the values of ``field´´ for the given ``ids´´ and group ids by value.
1020 :param string field: name of the field we want to read and group by
1021 :return: mapping of field values to the list of ids that have it
1025 for record in obj.read(cr, uid, ids, [field], context=context):
1027 res.setdefault(key[0] if isinstance(key, tuple) else key, []).append(record['id'])
1030 def get_and_group_by_company(cr, uid, obj, ids, context=None):
1031 return get_and_group_by_field(cr, uid, obj, ids, field='company_id', context=context)
1033 # port of python 2.6's attrgetter with support for dotted notation
1034 def resolve_attr(obj, attr):
1035 for name in attr.split("."):
1036 obj = getattr(obj, name)
1039 def attrgetter(*items):
1043 return resolve_attr(obj, attr)
1046 return tuple(resolve_attr(obj, attr) for attr in items)
1050 """A subclass of str that implements repr() without enclosing quotation marks
1051 or escaping, keeping the original string untouched. The name come from Lisp's unquote.
1052 One of the uses for this is to preserve or insert bare variable names within dicts during eval()
1053 of a dict's repr(). Use with care.
1055 Some examples (notice that there are never quotes surrounding
1056 the ``active_id`` name:
1058 >>> unquote('active_id')
1060 >>> d = {'test': unquote('active_id')}
1069 class UnquoteEvalContext(defaultdict):
1070 """Defaultdict-based evaluation context that returns
1071 an ``unquote`` string for any missing name used during
1073 Mostly useful for evaluating OpenERP domains/contexts that
1074 may refer to names that are unknown at the time of eval,
1075 so that when the context/domain is converted back to a string,
1076 the original names are preserved.
1078 **Warning**: using an ``UnquoteEvalContext`` as context for ``eval()`` or
1079 ``safe_eval()`` will shadow the builtins, which may cause other
1080 failures, depending on what is evaluated.
1082 Example (notice that ``section_id`` is preserved in the final
1085 >>> context_str = "{'default_user_id': uid, 'default_section_id': section_id}"
1086 >>> eval(context_str, UnquoteEvalContext(uid=1))
1087 {'default_user_id': 1, 'default_section_id': section_id}
1090 def __init__(self, *args, **kwargs):
1091 super(UnquoteEvalContext, self).__init__(None, *args, **kwargs)
1093 def __missing__(self, key):
1097 class mute_logger(object):
1098 """Temporary suppress the logging.
1099 Can be used as context manager or decorator.
1101 @mute_logger('openerp.plic.ploc')
1105 with mute_logger('openerp.foo.bar'):
1109 def __init__(self, *loggers):
1110 self.loggers = loggers
1112 def filter(self, record):
1115 def __enter__(self):
1116 for logger in self.loggers:
1117 assert isinstance(logger, basestring),\
1118 "A logger name must be a string, got %s" % type(logger)
1119 logging.getLogger(logger).addFilter(self)
1121 def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
1122 for logger in self.loggers:
1123 logging.getLogger(logger).removeFilter(self)
1125 def __call__(self, func):
1127 def deco(*args, **kwargs):
1129 return func(*args, **kwargs)
1133 class CountingStream(object):
1134 """ Stream wrapper counting the number of element it has yielded. Similar
1135 role to ``enumerate``, but for use when the iteration process of the stream
1136 isn't fully under caller control (the stream can be iterated from multiple
1137 points including within a library)
1139 ``start`` allows overriding the starting index (the index before the first
1142 On each iteration (call to :meth:`~.next`), increases its :attr:`~.index`
1145 .. attribute:: index
1147 ``int``, index of the last yielded element in the stream. If the stream
1148 has ended, will give an index 1-past the stream
1150 def __init__(self, stream, start=-1):
1151 self.stream = iter(stream)
1153 self.stopped = False
1157 if self.stopped: raise StopIteration()
1159 val = next(self.stream, _ph)
1162 raise StopIteration()
1165 def stripped_sys_argv(*strip_args):
1166 """Return sys.argv with some arguments stripped, suitable for reexecution or subprocesses"""
1167 strip_args = sorted(set(strip_args) | set(['-s', '--save', '-u', '--update', '-i', '--init']))
1168 assert all(config.parser.has_option(s) for s in strip_args)
1169 takes_value = dict((s, config.parser.get_option(s).takes_value()) for s in strip_args)
1171 longs, shorts = list(tuple(y) for _, y in groupby(strip_args, lambda x: x.startswith('--')))
1172 longs_eq = tuple(l + '=' for l in longs if takes_value[l])
1177 return args[i].startswith(shorts) \
1178 or args[i].startswith(longs_eq) or (args[i] in longs) \
1179 or (i >= 1 and (args[i - 1] in strip_args) and takes_value[args[i - 1]])
1181 return [x for i, x in enumerate(args) if not strip(args, i)]
1184 class ConstantMapping(Mapping):
1186 An immutable mapping returning the provided value for every single key.
1188 Useful for default value to methods
1190 __slots__ = ['_value']
1191 def __init__(self, val):
1196 defaultdict updates its length for each individually requested key, is
1203 same as len, defaultdict udpates its iterable keyset with each key
1204 requested, is there a point for this?
1208 def __getitem__(self, item):
1212 def dumpstacks(sig=None, frame=None):
1213 """ Signal handler: dump a stack trace for each existing thread."""
1216 def extract_stack(stack):
1217 for filename, lineno, name, line in traceback.extract_stack(stack):
1218 yield 'File: "%s", line %d, in %s' % (filename, lineno, name)
1220 yield " %s" % (line.strip(),)
1222 # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
1223 # modified for python 2.5 compatibility
1224 threads_info = dict([(th.ident, {'name': th.name, 'uid': getattr(th, 'uid', 'n/a')})
1225 for th in threading.enumerate()])
1226 for threadId, stack in sys._current_frames().items():
1227 thread_info = threads_info.get(threadId)
1228 code.append("\n# Thread: %s (id:%s) (uid:%s)" %
1229 (thread_info and thread_info['name'] or 'n/a',
1231 thread_info and thread_info['uid'] or 'n/a'))
1232 for line in extract_stack(stack):
1236 # code from http://stackoverflow.com/questions/12510648/in-gevent-how-can-i-dump-stack-traces-of-all-running-greenlets
1238 from greenlet import greenlet
1239 for ob in gc.get_objects():
1240 if not isinstance(ob, greenlet) or not ob:
1242 code.append("\n# Greenlet: %r" % (ob,))
1243 for line in extract_stack(ob.gr_frame):
1246 _logger.info("\n".join(code))
1248 class frozendict(dict):
1249 """ An implementation of an immutable dictionary. """
1250 def __delitem__(self, key):
1251 raise NotImplementedError("'__delitem__' not supported on frozendict")
1252 def __setitem__(self, key, val):
1253 raise NotImplementedError("'__setitem__' not supported on frozendict")
1255 raise NotImplementedError("'clear' not supported on frozendict")
1256 def pop(self, key, default=None):
1257 raise NotImplementedError("'pop' not supported on frozendict")
1259 raise NotImplementedError("'popitem' not supported on frozendict")
1260 def setdefault(self, key, default=None):
1261 raise NotImplementedError("'setdefault' not supported on frozendict")
1262 def update(self, *args, **kwargs):
1263 raise NotImplementedError("'update' not supported on frozendict")
1272 # Avoid DeprecationWarning while still remaining compatible with werkzeug pre-0.9
1273 if parse_version(getattr(werkzeug, '__version__', '0.0')) < parse_version('0.9.0'):
1274 def html_escape(text):
1275 return werkzeug.utils.escape(text, quote=True)
1277 def html_escape(text):
1278 return werkzeug.utils.escape(text)
1280 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: