1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (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 General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
24 Miscelleanous tools used by OpenERP.
30 from config import config
36 if sys.version_info[:2] < (2, 4):
37 from threadinglocal import local
39 from threading import local
41 from itertools import izip
43 # initialize a database with base/base.sql
46 f = addons.get_module_resource('base', 'base.sql')
47 for line in file_open(f).read().split(';'):
48 if (len(line)>0) and (not line.isspace()):
52 for i in addons.get_modules():
53 terp_file = addons.get_module_resource(i, '__terp__.py')
54 mod_path = addons.get_module_path(i)
58 if os.path.isfile(terp_file) or os.path.isfile(mod_path+'.zip'):
59 info = eval(file_open(terp_file).read())
61 categs = info.get('category', 'Uncategorized').split('/')
65 cr.execute('select id \
66 from ir_module_category \
67 where name=%s and parent_id=%s', (categs[0], p_id))
69 cr.execute('select id \
70 from ir_module_category \
71 where name=%s and parent_id is NULL', (categs[0],))
74 cr.execute('select nextval(\'ir_module_category_id_seq\')')
75 c_id = cr.fetchone()[0]
76 cr.execute('insert into ir_module_category \
77 (id, name, parent_id) \
78 values (%s, %s, %s)', (c_id, categs[0], p_id))
84 active = info.get('active', False)
85 installable = info.get('installable', True)
92 state = 'uninstallable'
93 cr.execute('select nextval(\'ir_module_module_id_seq\')')
95 cr.execute('insert into ir_module_module \
96 (id, author, website, name, shortdesc, description, \
97 category_id, state, certificate) \
98 values (%s, %s, %s, %s, %s, %s, %s, %s, %s)', (
99 id, info.get('author', ''),
100 info.get('website', ''), i, info.get('name', False),
101 info.get('description', ''), p_id, state, info.get('certificate')))
102 cr.execute('insert into ir_model_data \
103 (name,model,module, res_id, noupdate) values (%s,%s,%s,%s,%s)', (
104 'module_meta_information', 'ir.module.module', i, id, True))
105 dependencies = info.get('depends', [])
106 for d in dependencies:
107 cr.execute('insert into ir_module_module_dependency \
108 (module_id,name) values (%s, %s)', (id, d))
111 def find_in_path(name):
116 path = [dir for dir in os.environ['PATH'].split(sep)
117 if os.path.isdir(dir)]
119 val = os.path.join(dir, name)
120 if os.path.isfile(val) or os.path.islink(val):
124 def find_pg_tool(name):
125 if config['pg_path'] and config['pg_path'] != 'None':
126 return os.path.join(config['pg_path'], name)
128 return find_in_path(name)
130 def exec_pg_command(name, *args):
131 prog = find_pg_tool(name)
133 raise Exception('Couldn\'t find %s' % name)
134 args2 = (os.path.basename(prog),) + args
135 return os.spawnv(os.P_WAIT, prog, args2)
137 def exec_pg_command_pipe(name, *args):
138 prog = find_pg_tool(name)
140 raise Exception('Couldn\'t find %s' % name)
142 cmd = '"' + prog + '" ' + ' '.join(args)
144 cmd = prog + ' ' + ' '.join(args)
145 return os.popen2(cmd, 'b')
147 def exec_command_pipe(name, *args):
148 prog = find_in_path(name)
150 raise Exception('Couldn\'t find %s' % name)
152 cmd = '"'+prog+'" '+' '.join(args)
154 cmd = prog+' '+' '.join(args)
155 return os.popen2(cmd, 'b')
157 #----------------------------------------------------------
159 #----------------------------------------------------------
160 #file_path_root = os.getcwd()
161 #file_path_addons = os.path.join(file_path_root, 'addons')
163 def file_open(name, mode="r", subdir='addons', pathinfo=False):
164 """Open a file from the OpenERP root, using a subdir folder.
166 >>> file_open('hr/report/timesheer.xsl')
167 >>> file_open('addons/hr/report/timesheet.xsl')
168 >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
170 @param name: name of the file
171 @param mode: file open mode
172 @param subdir: subdirectory
173 @param pathinfo: if True returns tupple (fileobject, filepath)
175 @return: fileobject if pathinfo is False else (fileobject, filepath)
178 adp = os.path.normcase(os.path.abspath(config['addons_path']))
179 rtp = os.path.normcase(os.path.abspath(config['root_path']))
181 if name.replace(os.path.sep, '/').startswith('addons/'):
185 # First try to locate in addons_path
188 if subdir2.replace(os.path.sep, '/').startswith('addons/'):
189 subdir2 = subdir2[7:]
191 subdir2 = (subdir2 != 'addons' or None) and subdir2
195 fn = os.path.join(adp, subdir2, name)
197 fn = os.path.join(adp, name)
198 fn = os.path.normpath(fn)
199 fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
207 name = os.path.join(rtp, subdir, name)
209 name = os.path.join(rtp, name)
211 name = os.path.normpath(name)
213 # Check for a zipfile in the path
218 head, tail = os.path.split(head)
222 zipname = os.path.join(tail, zipname)
225 if zipfile.is_zipfile(head+'.zip'):
226 from cStringIO import StringIO
227 zfile = zipfile.ZipFile(head+'.zip')
230 fo.write(zfile.read(os.path.join(
231 os.path.basename(head), zipname).replace(
238 name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
240 for i in (name2, name):
241 if i and os.path.isfile(i):
246 if os.path.splitext(name)[1] == '.rml':
247 raise IOError, 'Report %s doesn\'t exist or deleted : ' %str(name)
248 raise IOError, 'File not found : '+str(name)
251 #----------------------------------------------------------
253 #----------------------------------------------------------
255 """Flatten a list of elements into a uniqu list
256 Author: Christophe Simonis (christophe@tinyerp.com)
265 >>> flatten( [[], [[]]] )
267 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
268 ['a', 'b', 'c', 'd', 'e', 'f']
269 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
271 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
275 return hasattr(x, "__iter__")
280 map(r.append, flatten(e))
285 def reverse_enumerate(l):
286 """Like enumerate but in the other sens
287 >>> a = ['a', 'b', 'c']
288 >>> it = reverse_enumerate(a)
296 Traceback (most recent call last):
297 File "<stdin>", line 1, in <module>
300 return izip(xrange(len(l)-1, -1, -1), reversed(l))
302 #----------------------------------------------------------
304 #----------------------------------------------------------
305 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
306 attach=None, tinycrm=False, ssl=False, debug=False, subtype='plain', x_headers=None):
310 from email.MIMEText import MIMEText
311 from email.MIMEBase import MIMEBase
312 from email.MIMEMultipart import MIMEMultipart
313 from email.Header import Header
314 from email.Utils import formatdate, COMMASPACE
315 from email.Utils import formatdate, COMMASPACE
316 from email import Encoders
319 if x_headers is None:
323 ssl = config.get('smtp_ssl', False)
325 if not email_from and not config['email_from']:
326 raise Exception("No Email sender by default, see config file")
335 msg = MIMEText(body.encode('utf8') or '',_subtype=subtype,_charset='utf-8')
337 msg = MIMEText(body or '',_subtype=subtype,_charset='utf-8')
339 msg = MIMEMultipart()
341 msg['Subject'] = Header(ustr(subject), 'utf-8')
342 msg['From'] = email_from
345 msg['Reply-To'] = reply_to
347 msg['Reply-To'] = msg['From']
348 msg['To'] = COMMASPACE.join(email_to)
350 msg['Cc'] = COMMASPACE.join(email_cc)
352 msg['Bcc'] = COMMASPACE.join(email_bcc)
353 msg['Date'] = formatdate(localtime=True)
355 # Add OpenERP Server information
356 msg['X-Generated-By'] = 'OpenERP (http://www.openerp.com)'
357 msg['X-OpenERP-Server-Host'] = socket.gethostname()
358 msg['X-OpenERP-Server-Version'] = release.version
360 # Add dynamic X Header
361 for key, value in x_headers.items():
362 msg['X-OpenERP-%s' % key] = str(value)
365 msg['Message-Id'] = "<%s-tinycrm-%s@%s>" % (time.time(), tinycrm, socket.gethostname())
369 msg.attach(MIMEText(body.encode('utf8') or '',_subtype=subtype,_charset='utf-8'))
371 msg.attach(MIMEText(body or '', _charset='utf-8', _subtype=subtype) )
372 for (fname,fcontent) in attach:
373 part = MIMEBase('application', "octet-stream")
374 part.set_payload( fcontent )
375 Encoders.encode_base64(part)
376 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
379 class WriteToLogger(object):
381 self.logger = netsvc.Logger()
384 self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
387 oldstderr = smtplib.stderr
391 # in case of debug, the messages are printed to stderr.
393 smtplib.stderr = WriteToLogger()
395 s.set_debuglevel(int(bool(debug))) # 0 or 1
397 s.connect(config['smtp_server'], config['smtp_port'])
403 if config['smtp_user'] or config['smtp_password']:
404 s.login(config['smtp_user'], config['smtp_password'])
406 s.sendmail(email_from,
407 flatten([email_to, email_cc, email_bcc]),
414 smtplib.stderr = oldstderr
417 netsvc.Logger().notifyChannel('email_send', netsvc.LOG_ERROR, e)
422 #----------------------------------------------------------
424 #----------------------------------------------------------
425 # text must be latin-1 encoded
426 def sms_send(user, password, api_id, text, to):
428 url = "http://api.urlsms.com/SendSMS.aspx"
429 #url = "http://196.7.150.220/http/sendmsg"
430 params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
431 f = urllib.urlopen(url+"?"+params)
432 # FIXME: Use the logger if there is an error
435 #---------------------------------------------------------
436 # Class that stores an updateable string (used in wizards)
437 #---------------------------------------------------------
438 class UpdateableStr(local):
440 def __init__(self, string=''):
444 return str(self.string)
447 return str(self.string)
449 def __nonzero__(self):
450 return bool(self.string)
453 class UpdateableDict(local):
454 '''Stores an updateable dict to use in wizards'''
456 def __init__(self, dict=None):
462 return str(self.dict)
465 return str(self.dict)
468 return self.dict.clear()
471 return self.dict.keys()
473 def __setitem__(self, i, y):
474 self.dict.__setitem__(i, y)
476 def __getitem__(self, i):
477 return self.dict.__getitem__(i)
480 return self.dict.copy()
483 return self.dict.iteritems()
486 return self.dict.iterkeys()
488 def itervalues(self):
489 return self.dict.itervalues()
491 def pop(self, k, d=None):
492 return self.dict.pop(k, d)
495 return self.dict.popitem()
497 def setdefault(self, k, d=None):
498 return self.dict.setdefault(k, d)
500 def update(self, E, **F):
501 return self.dict.update(E, F)
504 return self.dict.values()
506 def get(self, k, d=None):
507 return self.dict.get(k, d)
509 def has_key(self, k):
510 return self.dict.has_key(k)
513 return self.dict.items()
515 def __cmp__(self, y):
516 return self.dict.__cmp__(y)
518 def __contains__(self, k):
519 return self.dict.__contains__(k)
521 def __delitem__(self, y):
522 return self.dict.__delitem__(y)
525 return self.dict.__eq__(y)
528 return self.dict.__ge__(y)
531 return self.dict.__gt__(y)
534 return self.dict.__hash__()
537 return self.dict.__iter__()
540 return self.dict.__le__(y)
543 return self.dict.__len__()
546 return self.dict.__lt__(y)
549 return self.dict.__ne__(y)
552 # Don't use ! Use res.currency.round()
553 class currency(float):
555 def __init__(self, value, accuracy=2, rounding=None):
557 rounding=10**-accuracy
558 self.rounding=rounding
559 self.accuracy=accuracy
561 def __new__(cls, value, accuracy=2, rounding=None):
562 return float.__new__(cls, round(value, accuracy))
565 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
566 # return str(display_value)
578 Use it as a decorator of the function you plan to cache
579 Timeout: 0 = no timeout, otherwise in seconds
584 def __init__(self, timeout=None, skiparg=2, multi=None):
585 assert skiparg >= 2 # at least self and cr
587 self.timeout = config['cache_timeout']
589 self.timeout = timeout
590 self.skiparg = skiparg
592 self.lasttime = time.time()
595 cache.__caches.append(self)
598 def _generate_keys(self, dbname, kwargs2):
600 Generate keys depending of the arguments and the self.mutli value
605 pairs.sort(key=lambda (k,v): k)
606 for i, (k, v) in enumerate(pairs):
607 if isinstance(v, dict):
608 pairs[i] = (k, to_tuple(v))
609 if isinstance(v, (list, set)):
610 pairs[i] = (k, tuple(v))
611 elif not is_hashable(v):
612 pairs[i] = (k, repr(v))
616 key = (('dbname', dbname),) + to_tuple(kwargs2)
619 multis = kwargs2[self.multi][:]
621 kwargs2[self.multi] = (id,)
622 key = (('dbname', dbname),) + to_tuple(kwargs2)
625 def _unify_args(self, *args, **kwargs):
626 # Update named arguments with positional argument values (without self and cr)
627 kwargs2 = self.fun_default_values.copy()
628 kwargs2.update(kwargs)
629 kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
632 def clear(self, dbname, *args, **kwargs):
633 """clear the cache for database dbname
634 if *args and **kwargs are both empty, clear all the keys related to this database
636 if not args and not kwargs:
637 keys_to_del = [key for key in self.cache.keys() if key[0][1] == dbname]
639 kwargs2 = self._unify_args(*args, **kwargs)
640 keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache.keys()]
642 for key in keys_to_del:
646 def clean_caches_for_db(cls, dbname):
647 for c in cls.__caches:
650 def __call__(self, fn):
651 if self.fun is not None:
652 raise Exception("Can not use a cache instance on more than one function")
655 argspec = inspect.getargspec(fn)
656 self.fun_arg_names = argspec[0][self.skiparg:]
657 self.fun_default_values = {}
659 self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
661 def cached_result(self2, cr, *args, **kwargs):
662 if time.time()-int(self.timeout) > self.lasttime:
663 self.lasttime = time.time()
664 t = time.time()-int(self.timeout)
665 old_keys = [key for key in self.cache.keys() if self.cache[key][1] < t]
669 kwargs2 = self._unify_args(*args, **kwargs)
673 for key, id in self._generate_keys(cr.dbname, kwargs2):
674 if key in self.cache:
675 result[id] = self.cache[key][0]
681 kwargs2[self.multi] = notincache.keys()
683 result2 = fn(self2, cr, *args[:self.skiparg-2], **kwargs2)
685 key = notincache[None]
686 self.cache[key] = (result2, time.time())
687 result[None] = result2
691 self.cache[key] = (result2[id], time.time())
692 result.update(result2)
698 cached_result.clear_cache = self.clear
702 return s.replace('&','&').replace('<','<').replace('>','>')
705 """This method is similar to the builtin `str` method, except
706 it will return Unicode string.
708 @param value: the value to convert
711 @return: unicode string
714 if isinstance(value, unicode):
717 if hasattr(value, '__unicode__'):
718 return unicode(value)
720 if not isinstance(value, str):
723 try: # first try utf-8
724 return unicode(value, 'utf-8')
728 try: # then extened iso-8858
729 return unicode(value, 'iso-8859-15')
733 # else use default system locale
734 from locale import getlocale
735 return unicode(value, getlocale()[1])
737 def exception_to_unicode(e):
738 if hasattr(e, 'message'):
739 return ustr(e.message)
740 if hasattr(e, 'args'):
741 return "\n".join((ustr(a) for a in e.args))
745 return u"Unknow message"
748 # to be compatible with python 2.4
750 if not hasattr(__builtin__, 'all'):
752 for element in iterable:
757 __builtin__.all = all
760 if not hasattr(__builtin__, 'any'):
762 for element in iterable:
767 __builtin__.any = any
774 'ar_AR': u'Arabic / الْعَرَبيّة',
775 'bg_BG': u'Bulgarian / български',
776 'bs_BS': u'Bosnian / bosanski jezik',
777 'ca_ES': u'Catalan / Català',
778 'cs_CZ': u'Czech / Čeština',
779 'da_DK': u'Danish / Dansk',
780 'de_DE': u'German / Deutsch',
781 'el_EL': u'Greek / Ελληνικά',
782 'en_CA': u'English (CA)',
783 'en_GB': u'English (UK)',
784 'en_US': u'English (US)',
785 'es_AR': u'Spanish (AR) / Español (AR)',
786 'es_ES': u'Spanish / Español',
787 'et_EE': u'Estonian / Eesti keel',
788 'fi_FI': u'Finland / Suomi',
789 'fr_BE': u'French (BE) / Français (BE)',
790 'fr_CH': u'French (CH) / Français (CH)',
791 'fr_FR': u'French / Français',
792 'hr_HR': u'Croatian / hrvatski jezik',
793 'hu_HU': u'Hungarian / Magyar',
794 'id_ID': u'Indonesian / Bahasa Indonesia',
795 'it_IT': u'Italian / Italiano',
796 'lt_LT': u'Lithuanian / Lietuvių kalba',
797 'nl_NL': u'Dutch / Nederlands',
798 'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
799 'pl_PL': u'Polish / Język polski',
800 'pt_BR': u'Portugese (BR) / português (BR)',
801 'pt_PT': u'Portugese / português',
802 'ro_RO': u'Romanian / limba română',
803 'ru_RU': u'Russian / русский язык',
804 'sl_SL': u'Slovenian / slovenščina',
805 'sq_AL': u'Albanian / Shqipëri',
806 'sv_SE': u'Swedish / svenska',
807 'tr_TR': u'Turkish / Türkçe',
808 'vi_VN': u'Vietnam / Cộng hòa xã hội chủ nghĩa Việt Nam',
809 'uk_UA': u'Ukrainian / украї́нська мо́ва',
810 'zh_CN': u'Chinese (CN) / 简体中文',
811 'zh_TW': u'Chinese (TW) / 正體字',
812 'th_TH': u'Thai / ภาษาไทย',
816 def scan_languages():
818 file_list = [os.path.splitext(os.path.basename(f))[0] for f in glob.glob(os.path.join(config['root_path'],'addons', 'base', 'i18n', '*.po'))]
819 lang_dict = get_languages()
820 ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
821 ret.sort(key=lambda k:k[1])
825 def get_user_companies(cr, user):
826 def _get_company_children(cr, ids):
829 cr.execute('SELECT id FROM res_company WHERE parent_id = any(array[%s])' %(','.join([str(x) for x in ids]),))
830 res=[x[0] for x in cr.fetchall()]
831 res.extend(_get_company_children(cr, res))
833 cr.execute('SELECT comp.id FROM res_company AS comp, res_users AS u WHERE u.id = %s AND comp.id = u.company_id' % (user,))
834 compids=[cr.fetchone()[0]]
835 compids.extend(_get_company_children(cr, compids))
840 Input number : account or invoice number
841 Output return: the same number completed with the recursive mod10
844 codec=[0,9,4,6,8,2,7,1,3,5]
850 report = codec[ (int(digit) + report) % 10 ]
851 return result + str((10 - report) % 10)
856 Return the size in a human readable format
860 units = ('bytes', 'Kb', 'Mb', 'Gb')
861 if isinstance(sz,basestring):
864 while s >= 1024 and i < len(units)-1:
867 return "%0.2f %s" % (s, units[i])
870 from tools.func import wraps
873 def wrapper(*args, **kwargs):
875 from pprint import pformat
877 vector = ['Call -> function: %r' % f]
878 for i, arg in enumerate(args):
879 vector.append(' arg %02d: %s' % (i, pformat(arg)))
880 for key, value in kwargs.items():
881 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
884 res = f(*args, **kwargs)
886 vector.append(' result: %s' % pformat(res))
887 vector.append(' time delta: %s' % (time.time() - timeb4))
888 netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
893 class profile(object):
894 def __init__(self, fname=None):
897 def __call__(self, f):
898 from tools.func import wraps
901 def wrapper(*args, **kwargs):
902 class profile_wrapper(object):
906 self.result = f(*args, **kwargs)
907 pw = profile_wrapper()
909 fname = self.fname or ("%s.cprof" % (f.func_name,))
910 cProfile.runctx('pw()', globals(), locals(), filename=fname)
917 This method allow you to debug your code without print
919 >>> def func_foo(bar)
927 This will output on the logger:
929 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
930 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
932 To view the DEBUG lines in the logger you must start the server with the option
937 from inspect import stack
939 from pprint import pformat
941 param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
942 while param.count(')') > param.count('('): param = param[:param.rfind(')')]
945 what = "%s = %s" % (param, what)
946 netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
949 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
950 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
951 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
952 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
953 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
954 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
955 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
956 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
957 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
958 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
959 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
960 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
961 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
962 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
963 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
964 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
965 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
966 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
967 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
968 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
969 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
970 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
971 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
972 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
973 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
974 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
977 def extract_zip_file(zip_file, outdirectory):
981 zf = zipfile.ZipFile(zip_file, 'r')
983 for path in zf.namelist():
984 tgt = os.path.join(out, path)
985 tgtdir = os.path.dirname(tgt)
986 if not os.path.exists(tgtdir):
989 if not tgt.endswith(os.sep):
991 fp.write(zf.read(path))
999 if __name__ == '__main__':
1005 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: