diff options
| author | Vo Minh Thu <vmt@openerp.com> | 2011-02-07 13:57:23 +0100 |
|---|---|---|
| committer | Vo Minh Thu <vmt@openerp.com> | 2011-02-07 13:57:23 +0100 |
| commit | f8572e5c602798f37c2a94d54ab5e3ac79ff0185 (patch) | |
| tree | 35687977b932ac91bd31fece229ab78cc70926f5 /bin/tools | |
| parent | 9c287abc143a7b187635c75751a29baeefa58c15 (diff) | |
[IMP] openerp python module.
- Some logging code moved from netsvc.py to loglevels.py
- Changed imports to use the new openerp module
- config and netsvc initialization calls move to openerp-server.py
- Moved openerp-server.py outside the old bin directory
- Some imports in tools moved inside the methods to break mutual-dependencies
bzr revid: vmt@openerp.com-20110207125723-ooee7d7ng5elmkso
Diffstat (limited to 'bin/tools')
| -rw-r--r-- | bin/tools/__init__.py | 36 | ||||
| -rw-r--r-- | bin/tools/amount_to_text.py | 194 | ||||
| -rw-r--r-- | bin/tools/amount_to_text_en.py | 124 | ||||
| -rw-r--r-- | bin/tools/config.py | 503 | ||||
| -rw-r--r-- | bin/tools/convert.py | 977 | ||||
| -rw-r--r-- | bin/tools/copy.py | 439 | ||||
| -rw-r--r-- | bin/tools/func.py | 116 | ||||
| -rw-r--r-- | bin/tools/graph.py | 784 | ||||
| -rw-r--r-- | bin/tools/import_email.py | 169 | ||||
| -rw-r--r-- | bin/tools/lru.py | 115 | ||||
| -rw-r--r-- | bin/tools/misc.py | 1508 | ||||
| -rw-r--r-- | bin/tools/osutil.py | 62 | ||||
| -rw-r--r-- | bin/tools/parse_version.py | 101 | ||||
| -rw-r--r-- | bin/tools/pdf_utils.py | 130 | ||||
| -rw-r--r-- | bin/tools/safe_eval.py | 283 | ||||
| -rw-r--r-- | bin/tools/sql.py | 28 | ||||
| -rw-r--r-- | bin/tools/test_reports.py | 309 | ||||
| -rw-r--r-- | bin/tools/threadinglocal.py | 269 | ||||
| -rw-r--r-- | bin/tools/translate.py | 1005 | ||||
| -rw-r--r-- | bin/tools/which.py | 153 | ||||
| -rw-r--r-- | bin/tools/win32.py | 50 | ||||
| -rw-r--r-- | bin/tools/yaml_import.py | 799 | ||||
| -rw-r--r-- | bin/tools/yaml_tag.py | 183 |
23 files changed, 0 insertions, 8337 deletions
diff --git a/bin/tools/__init__.py b/bin/tools/__init__.py deleted file mode 100644 index f1a0faf0f8f..00000000000 --- a/bin/tools/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -import copy -import win32 -from config import * -from misc import * -from convert import * -from translate import * -from graph import graph -from amount_to_text import * -from amount_to_text_en import * -from pdf_utils import * -from yaml_import import * -from sql import * - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/bin/tools/amount_to_text.py b/bin/tools/amount_to_text.py deleted file mode 100644 index 0b626af8a7a..00000000000 --- a/bin/tools/amount_to_text.py +++ /dev/null @@ -1,194 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -#------------------------------------------------------------- -# French -#------------------------------------------------------------- - - -to_19_fr = ( 'zéro', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', - 'sept', 'huit', 'neuf', 'dix', 'onze', 'douze', 'treize', - 'quatorze', 'quinze', 'seize', 'dix-sept', 'dix-huit', 'dix-neuf' ) -tens_fr = ( 'vingt', 'trente', 'quarante', 'Cinquante', 'Soixante', 'Soixante-dix', 'Quatre-vingts', 'Quatre-vingt Dix') -denom_fr = ( '', - 'Mille', 'Millions', 'Milliards', 'Billions', 'Quadrillions', - 'Quintillion', 'Sextillion', 'Septillion', 'Octillion', 'Nonillion', - 'Décillion', 'Undecillion', 'Duodecillion', 'Tredecillion', 'Quattuordecillion', - 'Sexdecillion', 'Septendecillion', 'Octodecillion', 'Icosillion', 'Vigintillion' ) - -# convert a value < 100 to French. -def _convert_nn_fr(val): - if val < 20: - return to_19_fr[val] - for (dcap, dval) in ((k, 20 + (10 * v)) for (v, k) in enumerate(tens_fr)): - if dval + 10 > val: - if val % 10: - return dcap + '-' + to_19_fr[val % 10] - return dcap - -# convert a value < 1000 to french, special cased because it is the level that kicks -# off the < 100 special case. The rest are more general. This also allows you to -# get strings in the form of 'forty-five hundred' if called directly. -def _convert_nnn_fr(val): - word = '' - (mod, rem) = (val % 100, val // 100) - if rem > 0: - word = to_19_fr[rem] + ' Cent' - if mod > 0: - word = word + ' ' - if mod > 0: - word = word + _convert_nn_fr(mod) - return word - -def french_number(val): - if val < 100: - return _convert_nn_fr(val) - if val < 1000: - return _convert_nnn_fr(val) - for (didx, dval) in ((v - 1, 1000 ** v) for v in range(len(denom_fr))): - if dval > val: - mod = 1000 ** didx - l = val // mod - r = val - (l * mod) - ret = _convert_nnn_fr(l) + ' ' + denom_fr[didx] - if r > 0: - ret = ret + ', ' + french_number(r) - return ret - -def amount_to_text_fr(number, currency): - number = '%.2f' % number - units_name = currency - list = str(number).split('.') - start_word = french_number(abs(int(list[0]))) - end_word = french_number(int(list[1])) - cents_number = int(list[1]) - cents_name = (cents_number > 1) and ' Cents' or ' Cent' - final_result = start_word +' '+units_name+' '+ end_word +' '+cents_name - return final_result - -#------------------------------------------------------------- -# Dutch -#------------------------------------------------------------- - -to_19_nl = ( 'Nul', 'Een', 'Twee', 'Drie', 'Vier', 'Vijf', 'Zes', - 'Zeven', 'Acht', 'Negen', 'Tien', 'Elf', 'Twaalf', 'Dertien', - 'Veertien', 'Vijftien', 'Zestien', 'Zeventien', 'Achttien', 'Negentien' ) -tens_nl = ( 'Twintig', 'Dertig', 'Veertig', 'Vijftig', 'Zestig', 'Zeventig', 'Tachtig', 'Negentig') -denom_nl = ( '', - 'Duizend', 'Miljoen', 'Miljard', 'Triljoen', 'Quadriljoen', - 'Quintillion', 'Sextiljoen', 'Septillion', 'Octillion', 'Nonillion', - 'Decillion', 'Undecillion', 'Duodecillion', 'Tredecillion', 'Quattuordecillion', - 'Sexdecillion', 'Septendecillion', 'Octodecillion', 'Novemdecillion', 'Vigintillion' ) - -# convert a value < 100 to Dutch. -def _convert_nn_nl(val): - if val < 20: - return to_19_nl[val] - for (dcap, dval) in ((k, 20 + (10 * v)) for (v, k) in enumerate(tens_nl)): - if dval + 10 > val: - if val % 10: - return dcap + '-' + to_19_nl[val % 10] - return dcap - -# convert a value < 1000 to Dutch, special cased because it is the level that kicks -# off the < 100 special case. The rest are more general. This also allows you to -# get strings in the form of 'forty-five hundred' if called directly. -def _convert_nnn_nl(val): - word = '' - (mod, rem) = (val % 100, val // 100) - if rem > 0: - word = to_19_nl[rem] + ' Honderd' - if mod > 0: - word = word + ' ' - if mod > 0: - word = word + _convert_nn_nl(mod) - return word - -def dutch_number(val): - if val < 100: - return _convert_nn_nl(val) - if val < 1000: - return _convert_nnn_nl(val) - for (didx, dval) in ((v - 1, 1000 ** v) for v in range(len(denom_nl))): - if dval > val: - mod = 1000 ** didx - l = val // mod - r = val - (l * mod) - ret = _convert_nnn_nl(l) + ' ' + denom_nl[didx] - if r > 0: - ret = ret + ', ' + dutch_number(r) - return ret - -def amount_to_text_nl(number, currency): - number = '%.2f' % number - units_name = currency - list = str(number).split('.') - start_word = dutch_number(int(list[0])) - end_word = dutch_number(int(list[1])) - cents_number = int(list[1]) - cents_name = (cents_number > 1) and 'cent' or 'cent' - final_result = start_word +' '+units_name+' '+ end_word +' '+cents_name - return final_result - -#------------------------------------------------------------- -# Generic functions -#------------------------------------------------------------- - -_translate_funcs = {'fr' : amount_to_text_fr, 'nl' : amount_to_text_nl} - -def add_amount_to_text_function(lang, func): - _translate_funcs[lang] = func - -#TODO: we should use the country AND language (ex: septante VS soixante dix) -#TODO: we should use en by default, but the translation func is yet to be implemented -def amount_to_text(nbr, lang='fr', currency='euro'): - """ - Converts an integer to its textual representation, using the language set in the context if any. - Example: - 1654: mille six cent cinquante-quatre. - """ -# if nbr > 1000000: -##TODO: use logger -# print "WARNING: number too large '%d', can't translate it!" % (nbr,) -# return str(nbr) - - if not _translate_funcs.has_key(lang): -#TODO: use logger - print "WARNING: no translation function found for lang: '%s'" % (lang,) -#TODO: (default should be en) same as above - lang = 'fr' - return _translate_funcs[lang](abs(nbr), currency) - -if __name__=='__main__': - from sys import argv - - lang = 'nl' - if len(argv) < 2: - for i in range(1,200): - print i, ">>", amount_to_text(i, lang) - for i in range(200,999999,139): - print i, ">>", amount_to_text(i, lang) - else: - print amount_to_text(int(argv[1]), lang) - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/bin/tools/amount_to_text_en.py b/bin/tools/amount_to_text_en.py deleted file mode 100644 index 66d749e5fb3..00000000000 --- a/bin/tools/amount_to_text_en.py +++ /dev/null @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -#------------------------------------------------------------- -#ENGLISH -#------------------------------------------------------------- -from tools.translate import _ - -to_19 = ( 'Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', - 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve', 'Thirteen', - 'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eighteen', 'Nineteen' ) -tens = ( 'Twenty', 'Thirty', 'Forty', 'Fifty', 'Sixty', 'Seventy', 'Eighty', 'Ninety') -denom = ( '', - 'Thousand', 'Million', 'Billion', 'Trillion', 'Quadrillion', - 'Quintillion', 'Sextillion', 'Septillion', 'Octillion', 'Nonillion', - 'Decillion', 'Undecillion', 'Duodecillion', 'Tredecillion', 'Quattuordecillion', - 'Sexdecillion', 'Septendecillion', 'Octodecillion', 'Novemdecillion', 'Vigintillion' ) - -# convert a value < 100 to English. -def _convert_nn(val): - if val < 20: - return to_19[val] - for (dcap, dval) in ((k, 20 + (10 * v)) for (v, k) in enumerate(tens)): - if dval + 10 > val: - if val % 10: - return dcap + '-' + to_19[val % 10] - return dcap - -# convert a value < 1000 to english, special cased because it is the level that kicks -# off the < 100 special case. The rest are more general. This also allows you to -# get strings in the form of 'forty-five hundred' if called directly. -def _convert_nnn(val): - word = '' - (mod, rem) = (val % 100, val // 100) - if rem > 0: - word = to_19[rem] + ' Hundred' - if mod > 0: - word = word + ' ' - if mod > 0: - word = word + _convert_nn(mod) - return word - -def english_number(val): - if val < 100: - return _convert_nn(val) - if val < 1000: - return _convert_nnn(val) - for (didx, dval) in ((v - 1, 1000 ** v) for v in range(len(denom))): - if dval > val: - mod = 1000 ** didx - l = val // mod - r = val - (l * mod) - ret = _convert_nnn(l) + ' ' + denom[didx] - if r > 0: - ret = ret + ', ' + english_number(r) - return ret - -def amount_to_text(number, currency): - number = '%.2f' % number - units_name = currency - list = str(number).split('.') - start_word = english_number(int(list[0])) - end_word = english_number(int(list[1])) - cents_number = int(list[1]) - cents_name = (cents_number > 1) and 'Cents' or 'Cent' - final_result = start_word +' '+units_name+' and ' + end_word +' '+cents_name - return final_result - - -#------------------------------------------------------------- -# Generic functions -#------------------------------------------------------------- - -_translate_funcs = {'en' : amount_to_text} - -#TODO: we should use the country AND language (ex: septante VS soixante dix) -#TODO: we should use en by default, but the translation func is yet to be implemented -def amount_to_text(nbr, lang='en', currency='euro'): - """ - Converts an integer to its textual representation, using the language set in the context if any. - Example: - 1654: thousands six cent cinquante-quatre. - """ - import netsvc -# if nbr > 10000000: -# netsvc.Logger().notifyChannel('translate', netsvc.LOG_WARNING, _("Number too large '%d', can not translate it")) -# return str(nbr) - - if not _translate_funcs.has_key(lang): - netsvc.Logger().notifyChannel('translate', netsvc.LOG_WARNING, _("no translation function found for lang: '%s'" % (lang,))) - #TODO: (default should be en) same as above - lang = 'en' - return _translate_funcs[lang](abs(nbr), currency) - -if __name__=='__main__': - from sys import argv - - lang = 'nl' - if len(argv) < 2: - for i in range(1,200): - print i, ">>", int_to_text(i, lang) - for i in range(200,999999,139): - print i, ">>", int_to_text(i, lang) - else: - print int_to_text(int(argv[1]), lang) - diff --git a/bin/tools/config.py b/bin/tools/config.py deleted file mode 100644 index bc9229e0b72..00000000000 --- a/bin/tools/config.py +++ /dev/null @@ -1,503 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -import ConfigParser -import optparse -import os -import sys -import netsvc -import logging -import release - -def check_ssl(): - try: - from OpenSSL import SSL - import socket - - return hasattr(socket, 'ssl') and hasattr(SSL, "Connection") - except: - return False - -class configmanager(object): - def __init__(self, fname=None): - self.options = { - 'email_from':False, - 'xmlrpc_interface': '', # this will bind the server to all interfaces - 'xmlrpc_port': 8069, - 'netrpc_interface': '', - 'netrpc_port': 8070, - 'xmlrpcs_interface': '', # this will bind the server to all interfaces - 'xmlrpcs_port': 8071, - 'db_host': False, - 'db_port': False, - 'db_name': False, - 'db_user': False, - 'db_password': False, - 'db_maxconn': 64, - 'reportgz': False, - 'netrpc': True, - 'xmlrpc': True, - 'xmlrpcs': True, - 'translate_in': None, - 'translate_out': None, - 'overwrite_existing_translations': False, - 'load_language': None, - 'language': None, - 'pg_path': None, - 'admin_passwd': 'admin', - 'csv_internal_sep': ',', - 'addons_path': None, - 'root_path': None, - 'debug_mode': False, - 'import_partial': "", - 'pidfile': None, - 'logfile': None, - 'logrotate': True, - 'smtp_server': 'localhost', - 'smtp_user': False, - 'smtp_port':25, - 'smtp_ssl':False, - 'smtp_password': False, - 'stop_after_init': False, # this will stop the server after initialization - 'syslog' : False, - 'log_level': logging.INFO, - 'assert_exit_level': logging.ERROR, # level above which a failed assert will be raised - 'cache_timeout': 100000, - 'login_message': False, - 'list_db' : True, - 'timezone' : False, # to override the default TZ - 'test_file' : False, - 'test_report_directory' : False, - 'test_disable' : False, - 'test_commit' : False, - 'static_http_enable': False, - 'static_http_document_root': None, - 'static_http_url_prefix': None, - 'secure_cert_file': 'server.cert', - 'secure_pkey_file': 'server.pkey', - 'publisher_warranty_url': 'http://services.openerp.com/publisher-warranty/', - } - - self.blacklist_for_save = set(["publisher_warranty_url", "load_language"]) - - self.misc = {} - self.config_file = fname - self.has_ssl = check_ssl() - - self._LOGLEVELS = dict([(getattr(netsvc, 'LOG_%s' % x), getattr(logging, x)) - for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'TEST', 'DEBUG', 'DEBUG_RPC', 'DEBUG_SQL', 'DEBUG_RPC_ANSWER','NOTSET')]) - - version = "%s %s" % (release.description, release.version) - self.parser = parser = optparse.OptionParser(version=version) - - parser.add_option("-c", "--config", dest="config", help="specify alternate config file") - parser.add_option("-s", "--save", action="store_true", dest="save", default=False, - help="save configuration to ~/.openerp_serverrc") - parser.add_option("--pidfile", dest="pidfile", help="file where the server pid will be stored") - - group = optparse.OptionGroup(parser, "XML-RPC Configuration") - group.add_option("--xmlrpc-interface", dest="xmlrpc_interface", help="specify the TCP IP address for the XML-RPC protocol") - group.add_option("--xmlrpc-port", dest="xmlrpc_port", help="specify the TCP port for the XML-RPC protocol", type="int") - group.add_option("--no-xmlrpc", dest="xmlrpc", action="store_false", help="disable the XML-RPC protocol") - parser.add_option_group(group) - - title = "XML-RPC Secure Configuration" - if not self.has_ssl: - title += " (disabled as ssl is unavailable)" - - group = optparse.OptionGroup(parser, title) - group.add_option("--xmlrpcs-interface", dest="xmlrpcs_interface", help="specify the TCP IP address for the XML-RPC Secure protocol") - group.add_option("--xmlrpcs-port", dest="xmlrpcs_port", help="specify the TCP port for the XML-RPC Secure protocol", type="int") - group.add_option("--no-xmlrpcs", dest="xmlrpcs", action="store_false", help="disable the XML-RPC Secure protocol") - group.add_option("--cert-file", dest="secure_cert_file", help="specify the certificate file for the SSL connection") - group.add_option("--pkey-file", dest="secure_pkey_file", help="specify the private key file for the SSL connection") - parser.add_option_group(group) - - # NET-RPC - group = optparse.OptionGroup(parser, "NET-RPC Configuration") - group.add_option("--netrpc-interface", dest="netrpc_interface", help="specify the TCP IP address for the NETRPC protocol") - group.add_option("--netrpc-port", dest="netrpc_port", help="specify the TCP port for the NETRPC protocol", type="int") - group.add_option("--no-netrpc", dest="netrpc", action="store_false", help="disable the NETRPC protocol") - parser.add_option_group(group) - - # Static HTTP - group = optparse.OptionGroup(parser, "Static HTTP service") - group.add_option("--static-http-enable", dest="static_http_enable", action="store_true", default=False, help="enable static HTTP service for serving plain HTML files") - group.add_option("--static-http-document-root", dest="static_http_document_root", help="specify the directory containing your static HTML files (e.g '/var/www/')") - group.add_option("--static-http-url-prefix", dest="static_http_url_prefix", help="specify the URL root prefix where you want web browsers to access your static HTML files (e.g '/')") - parser.add_option_group(group) - - parser.add_option("-i", "--init", dest="init", help="init a module (use \"all\" for all modules)") - parser.add_option("--without-demo", dest="without_demo", - help="load demo data for a module (use \"all\" for all modules)", default=False) - parser.add_option("-u", "--update", dest="update", - help="update a module (use \"all\" for all modules)") - parser.add_option("--cache-timeout", dest="cache_timeout", - help="set the timeout for the cache system", type="int") - parser.add_option("-t", "--timezone", dest="timezone", help="specify reference timezone for the server (e.g. Europe/Brussels") - - # stops the server from launching after initialization - parser.add_option("--stop-after-init", action="store_true", dest="stop_after_init", default=False, - help="stop the server after it initializes") - parser.add_option('--debug', dest='debug_mode', action='store_true', default=False, help='enable debug mode') - parser.add_option("--assert-exit-level", dest='assert_exit_level', type="choice", choices=self._LOGLEVELS.keys(), - help="specify the level at which a failed assertion will stop the server. Accepted values: %s" % (self._LOGLEVELS.keys(),)) - - # Testing Group - group = optparse.OptionGroup(parser, "Testing Configuration") - group.add_option("--test-file", dest="test_file", help="Launch a YML test file.") - group.add_option("--test-report-directory", dest="test_report_directory", help="If set, will save sample of all reports in this directory.") - group.add_option("--test-disable", action="store_true", dest="test_disable", - default=False, help="Disable loading test files.") - group.add_option("--test-commit", action="store_true", dest="test_commit", - default=False, help="Commit database changes performed by tests.") - parser.add_option_group(group) - - # Logging Group - group = optparse.OptionGroup(parser, "Logging Configuration") - group.add_option("--logfile", dest="logfile", help="file where the server log will be stored") - group.add_option("--no-logrotate", dest="logrotate", action="store_false", - help="do not rotate the logfile") - group.add_option("--syslog", action="store_true", dest="syslog", - default=False, help="Send the log to the syslog server") - group.add_option('--log-level', dest='log_level', type='choice', choices=self._LOGLEVELS.keys(), - help='specify the level of the logging. Accepted values: ' + str(self._LOGLEVELS.keys())) - parser.add_option_group(group) - - # SMTP Group - group = optparse.OptionGroup(parser, "SMTP Configuration") - group.add_option('--email-from', dest='email_from', help='specify the SMTP email address for sending email') - group.add_option('--smtp', dest='smtp_server', help='specify the SMTP server for sending email') - group.add_option('--smtp-port', dest='smtp_port', help='specify the SMTP port', type="int") - group.add_option('--smtp-ssl', dest='smtp_ssl', action='store_true', help='specify the SMTP server support SSL or not') - group.add_option('--smtp-user', dest='smtp_user', help='specify the SMTP username for sending email') - group.add_option('--smtp-password', dest='smtp_password', help='specify the SMTP password for sending email') - parser.add_option_group(group) - - group = optparse.OptionGroup(parser, "Database related options") - group.add_option("-d", "--database", dest="db_name", help="specify the database name") - group.add_option("-r", "--db_user", dest="db_user", help="specify the database user name") - group.add_option("-w", "--db_password", dest="db_password", help="specify the database password") - group.add_option("--pg_path", dest="pg_path", help="specify the pg executable path") - group.add_option("--db_host", dest="db_host", help="specify the database host") - group.add_option("--db_port", dest="db_port", help="specify the database port", type="int") - group.add_option("--db_maxconn", dest="db_maxconn", type='int', - help="specify the the maximum number of physical connections to posgresql") - group.add_option("-P", "--import-partial", dest="import_partial", - help="Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate importation states.", default=False) - parser.add_option_group(group) - - group = optparse.OptionGroup(parser, "Internationalisation options", - "Use these options to translate OpenERP to another language." - "See i18n section of the user manual. Option '-d' is mandatory." - "Option '-l' is mandatory in case of importation" - ) - - group.add_option('--load-language', dest="load_language", - help="specifies the languages for the translations you want to be loaded") - group.add_option('-l', "--language", dest="language", - help="specify the language of the translation file. Use it with --i18n-export or --i18n-import") - group.add_option("--i18n-export", dest="translate_out", - help="export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit") - group.add_option("--i18n-import", dest="translate_in", - help="import a CSV or a PO file with translations and exit. The '-l' option is required.") - group.add_option("--i18n-overwrite", dest="overwrite_existing_translations", action="store_true", default=False, - help="overwrites existing translation terms on importing a CSV or a PO file.") - group.add_option("--modules", dest="translate_modules", - help="specify modules to export. Use in combination with --i18n-export") - group.add_option("--addons-path", dest="addons_path", - help="specify an alternative addons path.", - action="callback", callback=self._check_addons_path, nargs=1, type="string") - parser.add_option_group(group) - - security = optparse.OptionGroup(parser, 'Security-related options') - security.add_option('--no-database-list', action="store_false", dest='list_db', help="disable the ability to return the list of databases") - parser.add_option_group(security) - - def parse_config(self): - opt = self.parser.parse_args()[0] - - def die(cond, msg): - if cond: - print msg - sys.exit(1) - - die(bool(opt.syslog) and bool(opt.logfile), - "the syslog and logfile options are exclusive") - - die(opt.translate_in and (not opt.language or not opt.db_name), - "the i18n-import option cannot be used without the language (-l) and the database (-d) options") - - die(opt.overwrite_existing_translations and (not opt.translate_in), - "the i18n-overwrite option cannot be used without the i18n-import option") - - die(opt.translate_out and (not opt.db_name), - "the i18n-export option cannot be used without the database (-d) option") - - # Check if the config file exists (-c used, but not -s) - die(not opt.save and opt.config and not os.path.exists(opt.config), - "The config file '%s' selected with -c/--config doesn't exist, "\ - "use -s/--save if you want to generate it"%(opt.config)) - - # place/search the config file on Win32 near the server installation - # (../etc from the server) - # if the server is run by an unprivileged user, he has to specify location of a config file where he has the rights to write, - # else he won't be able to save the configurations, or even to start the server... - if os.name == 'nt': - rcfilepath = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'openerp-server.conf') - else: - rcfilepath = os.path.expanduser('~/.openerp_serverrc') - - self.rcfile = os.path.abspath( - self.config_file or opt.config \ - or os.environ.get('OPENERP_SERVER') or rcfilepath) - self.load() - - - # Verify that we want to log or not, if not the output will go to stdout - if self.options['logfile'] in ('None', 'False'): - self.options['logfile'] = False - # the same for the pidfile - if self.options['pidfile'] in ('None', 'False'): - self.options['pidfile'] = False - - keys = ['xmlrpc_interface', 'xmlrpc_port', 'db_name', 'db_user', 'db_password', 'db_host', - 'db_port', 'logfile', 'pidfile', 'smtp_port', 'cache_timeout', - 'email_from', 'smtp_server', 'smtp_user', 'smtp_password', - 'netrpc_interface', 'netrpc_port', 'db_maxconn', 'import_partial', 'addons_path', - 'netrpc', 'xmlrpc', 'syslog', 'without_demo', 'timezone', - 'xmlrpcs_interface', 'xmlrpcs_port', 'xmlrpcs', - 'secure_cert_file', 'secure_pkey_file', - 'static_http_enable', 'static_http_document_root', 'static_http_url_prefix' - ] - - for arg in keys: - if getattr(opt, arg): - self.options[arg] = getattr(opt, arg) - - keys = [ - 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', - 'debug_mode', 'smtp_ssl', 'load_language', - 'stop_after_init', 'logrotate', 'without_demo', 'netrpc', 'xmlrpc', 'syslog', - 'list_db', 'xmlrpcs', - 'test_file', 'test_disable', 'test_commit', 'test_report_directory' - ] - - for arg in keys: - if getattr(opt, arg) is not None: - self.options[arg] = getattr(opt, arg) - - if opt.assert_exit_level: - self.options['assert_exit_level'] = self._LOGLEVELS[opt.assert_exit_level] - else: - self.options['assert_exit_level'] = self._LOGLEVELS.get(self.options['assert_exit_level']) or int(self.options['assert_exit_level']) - - if opt.log_level: - self.options['log_level'] = self._LOGLEVELS[opt.log_level] - else: - self.options['log_level'] = self._LOGLEVELS.get(self.options['log_level']) or int(self.options['log_level']) - - if not self.options['root_path'] or self.options['root_path']=='None': - self.options['root_path'] = os.path.abspath(os.path.dirname(sys.argv[0])) - if not self.options['addons_path'] or self.options['addons_path']=='None': - self.options['addons_path'] = os.path.join(self.options['root_path'], 'addons') - - self.options['init'] = opt.init and dict.fromkeys(opt.init.split(','), 1) or {} - self.options["demo"] = not opt.without_demo and self.options['init'] or {} - self.options['update'] = opt.update and dict.fromkeys(opt.update.split(','), 1) or {} - self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all'] - self.options['translate_modules'].sort() - - if self.options['timezone']: - # If an explicit TZ was provided in the config, make sure it is known - try: - import pytz - pytz.timezone(self.options['timezone']) - except pytz.UnknownTimeZoneError: - die(True, "The specified timezone (%s) is invalid" % self.options['timezone']) - except: - # If pytz is missing, don't check the provided TZ, it will be ignored anyway. - pass - - if opt.pg_path: - self.options['pg_path'] = opt.pg_path - - if self.options.get('language', False): - if len(self.options['language']) > 5: - raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE') - - if not self.options['db_user']: - try: - import getpass - self.options['db_user'] = getpass.getuser() - except: - self.options['db_user'] = None - - die(not self.options['db_user'], 'ERROR: No user specified for the connection to the database') - - if self.options['db_password']: - if sys.platform == 'win32' and not self.options['db_host']: - self.options['db_host'] = 'localhost' - #if self.options['db_host']: - # self._generate_pgpassfile() - - if opt.save: - self.save() - - def _generate_pgpassfile(self): - """ - Generate the pgpass file with the parameters from the command line (db_host, db_user, - db_password) - - Used because pg_dump and pg_restore can not accept the password on the command line. - """ - is_win32 = sys.platform == 'win32' - if is_win32: - filename = os.path.join(os.environ['APPDATA'], 'pgpass.conf') - else: - filename = os.path.join(os.environ['HOME'], '.pgpass') - - text_to_add = "%(db_host)s:*:*:%(db_user)s:%(db_password)s" % self.options - - if os.path.exists(filename): - content = [x.strip() for x in file(filename, 'r').readlines()] - if text_to_add in content: - return - - fp = file(filename, 'a+') - fp.write(text_to_add + "\n") - fp.close() - - if is_win32: - try: - import _winreg - except ImportError: - _winreg = None - x=_winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE) - y = _winreg.OpenKey(x, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", 0,_winreg.KEY_ALL_ACCESS) - _winreg.SetValueEx(y,"PGPASSFILE", 0, _winreg.REG_EXPAND_SZ, filename ) - _winreg.CloseKey(y) - _winreg.CloseKey(x) - else: - import stat - os.chmod(filename, stat.S_IRUSR + stat.S_IWUSR) - - def _check_addons_path(self, option, opt, value, parser): - res = os.path.abspath(os.path.expanduser(value)) - if not os.path.exists(res): - raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, value)) - - contains_addons = False - for f in os.listdir(res): - modpath = os.path.join(res, f) - if os.path.isdir(modpath) and \ - os.path.exists(os.path.join(modpath, '__init__.py')) and \ - (os.path.exists(os.path.join(modpath, '__openerp__.py')) or \ - os.path.exists(os.path.join(modpath, '__terp__.py'))): - - contains_addons = True - break - - if not contains_addons: - raise optparse.OptionValueError("option %s: The addons-path %r does not seem to a be a valid Addons Directory!" % (opt, value)) - - setattr(parser.values, option.dest, res) - - def load(self): - p = ConfigParser.ConfigParser() - try: - p.read([self.rcfile]) - for (name,value) in p.items('options'): - if value=='True' or value=='true': - value = True - if value=='False' or value=='false': - value = False - self.options[name] = value - #parse the other sections, as well - for sec in p.sections(): - if sec == 'options': - continue - if not self.misc.has_key(sec): - self.misc[sec]= {} - for (name, value) in p.items(sec): - if value=='True' or value=='true': - value = True - if value=='False' or value=='false': - value = False - self.misc[sec][name] = value - except IOError: - pass - except ConfigParser.NoSectionError: - pass - - def save(self): - p = ConfigParser.ConfigParser() - loglevelnames = dict(zip(self._LOGLEVELS.values(), self._LOGLEVELS.keys())) - p.add_section('options') - for opt in sorted(self.options.keys()): - if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'): - continue - if opt in self.blacklist_for_save: - continue - if opt in ('log_level', 'assert_exit_level'): - p.set('options', opt, loglevelnames.get(self.options[opt], self.options[opt])) - else: - p.set('options', opt, self.options[opt]) - - for sec in sorted(self.misc.keys()): - for opt in sorted(self.misc[sec].keys()): - p.set(sec,opt,self.misc[sec][opt]) - - # try to create the directories and write the file - try: - rc_exists = os.path.exists(self.rcfile) - if not rc_exists and not os.path.exists(os.path.dirname(self.rcfile)): - os.makedirs(os.path.dirname(self.rcfile)) - try: - p.write(file(self.rcfile, 'w')) - if not rc_exists: - os.chmod(self.rcfile, 0600) - except IOError: - sys.stderr.write("ERROR: couldn't write the config file\n") - - except OSError: - # what to do if impossible? - sys.stderr.write("ERROR: couldn't create the config directory\n") - - def get(self, key, default=None): - return self.options.get(key, default) - - def get_misc(self, sect, key, default=None): - return self.misc.get(sect,{}).get(key, default) - - def __setitem__(self, key, value): - self.options[key] = value - - def __getitem__(self, key): - return self.options[key] - -config = configmanager() - -# FIXME:following line should be called explicitly by the server -# when it starts, to allow doing 'import tools.config' from -# other python executables without parsing *their* args. -config.parse_config() - diff --git a/bin/tools/convert.py b/bin/tools/convert.py deleted file mode 100644 index db8d71843ba..00000000000 --- a/bin/tools/convert.py +++ /dev/null @@ -1,977 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -import cStringIO -import csv -import logging -import os.path -import pickle -import re - -# for eval context: -import time -import release -try: - import pytz -except: - logging.getLogger("init").warning('could not find pytz library, please install it') - class pytzclass(object): - all_timezones=[] - pytz=pytzclass() - - -from datetime import datetime, timedelta -from lxml import etree -import misc -import netsvc -import osv -import pooler -from config import config -from tools.translate import _ -from yaml_import import convert_yaml_import - -# List of etree._Element subclasses that we choose to ignore when parsing XML. -from tools import SKIPPED_ELEMENT_TYPES - -# Import of XML records requires the unsafe eval as well, -# almost everywhere, which is ok because it supposedly comes -# from trusted data, but at least we make it obvious now. -unsafe_eval = eval -from tools.safe_eval import safe_eval as eval - -class ConvertError(Exception): - def __init__(self, doc, orig_excpt): - self.d = doc - self.orig = orig_excpt - - def __str__(self): - return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d) - -def _ref(self, cr): - return lambda x: self.id_get(cr, x) - -def _obj(pool, cr, uid, model_str, context=None): - model = pool.get(model_str) - return lambda x: model.browse(cr, uid, x, context=context) - -def _get_idref(self, cr, uid, model_str, context, idref): - idref2 = dict(idref, - time=time, - DateTime=datetime, - timedelta=timedelta, - version=release.major_version, - ref=_ref(self, cr), - pytz=pytz) - if len(model_str): - idref2['obj'] = _obj(self.pool, cr, uid, model_str, context=context) - return idref2 - -def _fix_multiple_roots(node): - """ - Surround the children of the ``node`` element of an XML field with a - single root "data" element, to prevent having a document with multiple - roots once parsed separately. - - XML nodes should have one root only, but we'd like to support - direct multiple roots in our partial documents (like inherited view architectures). - As a convention we'll surround multiple root with a container "data" element, to be - ignored later when parsing. - """ - real_nodes = [x for x in node if not isinstance(x, SKIPPED_ELEMENT_TYPES)] - if len(real_nodes) > 1: - data_node = etree.Element("data") - for child in node: - data_node.append(child) - node.append(data_node) - -def _eval_xml(self, node, pool, cr, uid, idref, context=None): - if context is None: - context = {} - if node.tag in ('field','value'): - t = node.get('type','char') - f_model = node.get('model', '').encode('utf-8') - if node.get('search'): - f_search = node.get("search",'').encode('utf-8') - f_use = node.get("use",'id').encode('utf-8') - f_name = node.get("name",'').encode('utf-8') - idref2 = {} - if f_search: - idref2 = _get_idref(self, cr, uid, f_model, context, idref) - q = unsafe_eval(f_search, idref2) - ids = pool.get(f_model).search(cr, uid, q) - if f_use != 'id': - ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use])) - _cols = pool.get(f_model)._columns - if (f_name in _cols) and _cols[f_name]._type=='many2many': - return ids - f_val = False - if len(ids): - f_val = ids[0] - if isinstance(f_val, tuple): - f_val = f_val[0] - return f_val - a_eval = node.get('eval','') - idref2 = {} - if a_eval: - idref2 = _get_idref(self, cr, uid, f_model, context, idref) - try: - return unsafe_eval(a_eval, idref2) - except Exception: - logger = logging.getLogger('init') - logger.warning('could not eval(%s) for %s in %s' % (a_eval, node.get('name'), context), exc_info=True) - return "" - if t == 'xml': - def _process(s, idref): - m = re.findall('[^%]%\((.*?)\)[ds]', s) - for id in m: - if not id in idref: - idref[id]=self.id_get(cr, id) - return s % idref - _fix_multiple_roots(node) - return '<?xml version="1.0"?>\n'\ - +_process("".join([etree.tostring(n, encoding='utf-8') - for n in node]), - idref) - if t in ('char', 'int', 'float'): - d = node.text - if t == 'int': - d = d.strip() - if d == 'None': - return None - else: - return int(d.strip()) - elif t == 'float': - return float(d.strip()) - return d - elif t in ('list','tuple'): - res=[] - for n in node.findall('./value'): - res.append(_eval_xml(self,n,pool,cr,uid,idref)) - if t=='tuple': - return tuple(res) - return res - elif node.tag == "getitem": - for n in node: - res=_eval_xml(self,n,pool,cr,uid,idref) - if not res: - raise LookupError - elif node.get('type') in ("int", "list"): - return res[int(node.get('index'))] - else: - return res[node.get('index','').encode("utf8")] - elif node.tag == "function": - args = [] - a_eval = node.get('eval','') - if a_eval: - idref['ref'] = lambda x: self.id_get(cr, x) - args = unsafe_eval(a_eval, idref) - for n in node: - return_val = _eval_xml(self,n, pool, cr, uid, idref, context) - if return_val is not None: - args.append(return_val) - model = pool.get(node.get('model','')) - method = node.get('name','') - res = getattr(model, method)(cr, uid, *args) - return res - elif node.tag == "test": - return node.text - -escape_re = re.compile(r'(?<!\\)/') -def escape(x): - return x.replace('\\/', '/') - -class assertion_report(object): - def __init__(self): - self._report = {} - - def record_assertion(self, success, severity): - """ - Records the result of an assertion for the failed/success count - returns success - """ - if severity in self._report: - self._report[severity][success] += 1 - else: - self._report[severity] = {success:1, not success: 0} - return success - - def get_report(self): - return self._report - - def __str__(self): - res = '\nAssertions report:\nLevel\tsuccess\tfailed\n' - success = failed = 0 - for sev in self._report: - res += sev + '\t' + str(self._report[sev][True]) + '\t' + str(self._report[sev][False]) + '\n' - success += self._report[sev][True] - failed += self._report[sev][False] - res += 'total\t' + str(success) + '\t' + str(failed) + '\n' - res += 'end of report (' + str(success + failed) + ' assertion(s) checked)' - return res - -class xml_import(object): - __logger = logging.getLogger('tools.convert.xml_import') - @staticmethod - def nodeattr2bool(node, attr, default=False): - if not node.get(attr): - return default - val = node.get(attr).strip() - if not val: - return default - return val.lower() not in ('0', 'false', 'off') - - def isnoupdate(self, data_node=None): - return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False)) - - def get_context(self, data_node, node, eval_dict): - data_node_context = (len(data_node) and data_node.get('context','').encode('utf8')) - node_context = node.get("context",'').encode('utf8') - context = {} - for ctx in (data_node_context, node_context): - if ctx: - try: - ctx_res = unsafe_eval(ctx, eval_dict) - if isinstance(context, dict): - context.update(ctx_res) - else: - context = ctx_res - except NameError: - # Some contexts contain references that are only valid at runtime at - # client-side, so in that case we keep the original context string - # as it is. We also log it, just in case. - context = ctx - logging.getLogger("init").debug('Context value (%s) for element with id "%s" or its data node does not parse '\ - 'at server-side, keeping original string, in case it\'s meant for client side only', - ctx, node.get('id','n/a'), exc_info=True) - return context - - def get_uid(self, cr, uid, data_node, node): - node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid','')) - if node_uid: - return self.id_get(cr, node_uid) - return uid - - def _test_xml_id(self, xml_id): - id = xml_id - if '.' in xml_id: - module, id = xml_id.split('.', 1) - assert '.' not in id, """The ID reference "%s" must contain -maximum one dot. They are used to refer to other modules ID, in the -form: module.record_id""" % (xml_id,) - if module != self.module: - modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])]) - assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,) - - if len(id) > 64: - self.logger.error('id: %s is to long (max: 64)', id) - - def _tag_delete(self, cr, rec, data_node=None): - d_model = rec.get("model",'') - d_search = rec.get("search",'').encode('utf-8') - d_id = rec.get("id",'') - ids = [] - - if d_search: - idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={}) - ids = self.pool.get(d_model).search(cr, self.uid, unsafe_eval(d_search, idref)) - if d_id: - try: - ids.append(self.id_get(cr, d_id)) - except: - # d_id cannot be found. doesn't matter in this case - pass - if ids: - self.pool.get(d_model).unlink(cr, self.uid, ids) - self.pool.get('ir.model.data')._unlink(cr, self.uid, d_model, ids) - - def _remove_ir_values(self, cr, name, value, model): - ir_value_ids = self.pool.get('ir.values').search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)]) - if ir_value_ids: - self.pool.get('ir.values').unlink(cr, self.uid, ir_value_ids) - self.pool.get('ir.model.data')._unlink(cr, self.uid, 'ir.values', ir_value_ids) - - return True - - def _tag_report(self, cr, rec, data_node=None): - res = {} - for dest,f in (('name','string'),('model','model'),('report_name','name')): - res[dest] = rec.get(f,'').encode('utf8') - assert res[dest], "Attribute %s of report is empty !" % (f,) - for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')): - if rec.get(field): - res[dest] = rec.get(field).encode('utf8') - if rec.get('auto'): - res['auto'] = eval(rec.get('auto','False')) - if rec.get('sxw'): - sxw_content = misc.file_open(rec.get('sxw')).read() - res['report_sxw_content'] = sxw_content - if rec.get('header'): - res['header'] = eval(rec.get('header','False')) - if rec.get('report_type'): - res['report_type'] = rec.get('report_type') - - res['multi'] = rec.get('multi') and eval(rec.get('multi','False')) - - xml_id = rec.get('id','').encode('utf8') - self._test_xml_id(xml_id) - - if rec.get('groups'): - g_names = rec.get('groups','').split(',') - groups_value = [] - for group in g_names: - if group.startswith('-'): - group_id = self.id_get(cr, group[1:]) - groups_value.append((3, group_id)) - else: - group_id = self.id_get(cr, group) - groups_value.append((4, group_id)) - res['groups_id'] = groups_value - - id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.report.xml", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) - self.idref[xml_id] = int(id) - - if not rec.get('menu') or eval(rec.get('menu','False')): - keyword = str(rec.get('keyword', 'client_print_multi')) - value = 'ir.actions.report.xml,'+str(id) - replace = rec.get('replace', True) - self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, res['name'], [res['model']], value, replace=replace, isobject=True, xml_id=xml_id) - elif self.mode=='update' and eval(rec.get('menu','False'))==False: - # Special check for report having attribute menu=False on update - value = 'ir.actions.report.xml,'+str(id) - self._remove_ir_values(cr, res['name'], value, res['model']) - return False - - def _tag_function(self, cr, rec, data_node=None): - if self.isnoupdate(data_node) and self.mode != 'init': - return - context = self.get_context(data_node, rec, {'ref': _ref(self, cr)}) - uid = self.get_uid(cr, self.uid, data_node, rec) - _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context) - return - - def _tag_wizard(self, cr, rec, data_node=None): - string = rec.get("string",'').encode('utf8') - model = rec.get("model",'').encode('utf8') - name = rec.get("name",'').encode('utf8') - xml_id = rec.get('id','').encode('utf8') - self._test_xml_id(xml_id) - multi = rec.get('multi','') and eval(rec.get('multi','False')) - res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model} - - if rec.get('groups'): - g_names = rec.get('groups','').split(',') - groups_value = [] - for group in g_names: - if group.startswith('-'): - group_id = self.id_get(cr, group[1:]) - groups_value.append((3, group_id)) - else: - group_id = self.id_get(cr, group) - groups_value.append((4, group_id)) - res['groups_id'] = groups_value - - id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.wizard", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) - self.idref[xml_id] = int(id) - # ir_set - if (not rec.get('menu') or eval(rec.get('menu','False'))) and id: - keyword = str(rec.get('keyword','') or 'client_action_multi') - value = 'ir.actions.wizard,'+str(id) - replace = rec.get("replace",'') or True - self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id) - elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False): - # Special check for wizard having attribute menu=False on update - value = 'ir.actions.wizard,'+str(id) - self._remove_ir_values(cr, string, value, model) - - def _tag_url(self, cr, rec, data_node=None): - url = rec.get("string",'').encode('utf8') - target = rec.get("target",'').encode('utf8') - name = rec.get("name",'').encode('utf8') - xml_id = rec.get('id','').encode('utf8') - self._test_xml_id(xml_id) - - res = {'name': name, 'url': url, 'target':target} - - id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.url", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) - self.idref[xml_id] = int(id) - # ir_set - if (not rec.get('menu') or eval(rec.get('menu','False'))) and id: - keyword = str(rec.get('keyword','') or 'client_action_multi') - value = 'ir.actions.url,'+str(id) - replace = rec.get("replace",'') or True - self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, url, ["ir.actions.url"], value, replace=replace, isobject=True, xml_id=xml_id) - elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False): - # Special check for URL having attribute menu=False on update - value = 'ir.actions.url,'+str(id) - self._remove_ir_values(cr, url, value, "ir.actions.url") - - def _tag_act_window(self, cr, rec, data_node=None): - name = rec.get('name','').encode('utf-8') - xml_id = rec.get('id','').encode('utf8') - self._test_xml_id(xml_id) - type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window' - view_id = False - if rec.get('view_id'): - view_id = self.id_get(cr, rec.get('view_id','').encode('utf-8')) - domain = rec.get('domain','').encode('utf-8') or '[]' - res_model = rec.get('res_model','').encode('utf-8') - src_model = rec.get('src_model','').encode('utf-8') - view_type = rec.get('view_type','').encode('utf-8') or 'form' - view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form' - usage = rec.get('usage','').encode('utf-8') - limit = rec.get('limit','').encode('utf-8') - auto_refresh = rec.get('auto_refresh','').encode('utf-8') - uid = self.uid - active_id = str("active_id") # for further reference in client/bin/tools/__init__.py - def ref(str_id): - return self.id_get(cr, str_id) - - # Include all locals() in eval_context, for backwards compatibility - eval_context = { - 'name': name, - 'xml_id': xml_id, - 'type': type, - 'view_id': view_id, - 'domain': domain, - 'res_model': res_model, - 'src_model': src_model, - 'view_type': view_type, - 'view_mode': view_mode, - 'usage': usage, - 'limit': limit, - 'auto_refresh': auto_refresh, - 'uid' : uid, - 'active_id': active_id, - 'ref' : ref, - } - context = self.get_context(data_node, rec, eval_context) - - try: - domain = unsafe_eval(domain, eval_context) - except NameError: - # Some domains contain references that are only valid at runtime at - # client-side, so in that case we keep the original domain string - # as it is. We also log it, just in case. - logging.getLogger("init").debug('Domain value (%s) for element with id "%s" does not parse '\ - 'at server-side, keeping original string, in case it\'s meant for client side only', - domain, xml_id or 'n/a', exc_info=True) - res = { - 'name': name, - 'type': type, - 'view_id': view_id, - 'domain': domain, - 'context': context, - 'res_model': res_model, - 'src_model': src_model, - 'view_type': view_type, - 'view_mode': view_mode, - 'usage': usage, - 'limit': limit, - 'auto_refresh': auto_refresh, - } - - if rec.get('groups'): - g_names = rec.get('groups','').split(',') - groups_value = [] - for group in g_names: - if group.startswith('-'): - group_id = self.id_get(cr, group[1:]) - groups_value.append((3, group_id)) - else: - group_id = self.id_get(cr, group) - groups_value.append((4, group_id)) - res['groups_id'] = groups_value - - if rec.get('target'): - res['target'] = rec.get('target','') - if rec.get('multi'): - res['multi'] = rec.get('multi', False) - id = self.pool.get('ir.model.data')._update(cr, self.uid, 'ir.actions.act_window', self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) - self.idref[xml_id] = int(id) - - if src_model: - #keyword = 'client_action_relate' - keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate' - value = 'ir.actions.act_window,'+str(id) - replace = rec.get('replace','') or True - self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, xml_id, [src_model], value, replace=replace, isobject=True, xml_id=xml_id) - # TODO add remove ir.model.data - - def _tag_ir_set(self, cr, rec, data_node=None): - if self.mode != 'init': - return - res = {} - for field in rec.findall('./field'): - f_name = field.get("name",'').encode('utf-8') - f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref) - res[f_name] = f_val - self.pool.get('ir.model.data').ir_set(cr, self.uid, res['key'], res['key2'], res['name'], res['models'], res['value'], replace=res.get('replace',True), isobject=res.get('isobject', False), meta=res.get('meta',None)) - - def _tag_workflow(self, cr, rec, data_node=None): - if self.isnoupdate(data_node) and self.mode != 'init': - return - model = str(rec.get('model','')) - w_ref = rec.get('ref','') - if w_ref: - id = self.id_get(cr, w_ref) - else: - number_children = len(rec) - assert number_children > 0,\ - 'You must define a child node if you dont give a ref' - assert number_children == 1,\ - 'Only one child node is accepted (%d given)' % number_children - id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref) - - uid = self.get_uid(cr, self.uid, data_node, rec) - wf_service = netsvc.LocalService("workflow") - wf_service.trg_validate(uid, model, - id, - str(rec.get('action','')), cr) - - # - # Support two types of notation: - # name="Inventory Control/Sending Goods" - # or - # action="action_id" - # parent="parent_id" - # - def _tag_menuitem(self, cr, rec, data_node=None): - rec_id = rec.get("id",'').encode('ascii') - self._test_xml_id(rec_id) - m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8'))) - - values = {'parent_id': False} - if rec.get('parent', False) is False and len(m_l) > 1: - # No parent attribute specified and the menu name has several menu components, - # try to determine the ID of the parent according to menu path - pid = False - res = None - values['name'] = m_l[-1] - m_l = m_l[:-1] # last part is our name, not a parent - for idx, menu_elem in enumerate(m_l): - if pid: - cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem)) - else: - cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,)) - res = cr.fetchone() - if res: - pid = res[0] - else: - # the menuitem does't exist but we are in branch (not a leaf) - self.logger.warning('Warning no ID for submenu %s of menu %s !', menu_elem, str(m_l)) - pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem}) - values['parent_id'] = pid - else: - # The parent attribute was specified, if non-empty determine its ID, otherwise - # explicitly make a top-level menu - if rec.get('parent'): - menu_parent_id = self.id_get(cr, rec.get('parent','')) - else: - # we get here with <menuitem parent="">, explicit clear of parent, or - # if no parent attribute at all but menu name is not a menu path - menu_parent_id = False - values = {'parent_id': menu_parent_id} - if rec.get('name'): - values['name'] = rec.get('name') - try: - res = [ self.id_get(cr, rec.get('id','')) ] - except: - res = None - - if rec.get('action'): - a_action = rec.get('action','').encode('utf8') - a_type = rec.get('type','').encode('utf8') or 'act_window' - icons = { - "act_window": 'STOCK_NEW', - "report.xml": 'STOCK_PASTE', - "wizard": 'STOCK_EXECUTE', - "url": 'STOCK_JUMP_TO' - } - values['icon'] = icons.get(a_type,'STOCK_NEW') - if a_type=='act_window': - a_id = self.id_get(cr, a_action) - cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),)) - rrres = cr.fetchone() - assert rrres, "No window action defined for this id %s !\n" \ - "Verify that this is a window action or add a type argument." % (a_action,) - action_type,action_mode,action_name,view_id,target = rrres - if view_id: - cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),)) - action_mode, = cr.fetchone() - cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),)) - if cr.rowcount: - action_mode, = cr.fetchone() - if action_type=='tree': - values['icon'] = 'STOCK_INDENT' - elif action_mode and action_mode.startswith('tree'): - values['icon'] = 'STOCK_JUSTIFY_FILL' - elif action_mode and action_mode.startswith('graph'): - values['icon'] = 'terp-graph' - elif action_mode and action_mode.startswith('calendar'): - values['icon'] = 'terp-calendar' - if target=='new': - values['icon'] = 'STOCK_EXECUTE' - if not values.get('name', False): - values['name'] = action_name - elif a_type=='wizard': - a_id = self.id_get(cr, a_action) - cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),)) - resw = cr.fetchone() - if (not values.get('name', False)) and resw: - values['name'] = resw[0] - if rec.get('sequence'): - values['sequence'] = int(rec.get('sequence')) - if rec.get('icon'): - values['icon'] = str(rec.get('icon')) - if rec.get('web_icon'): - values['web_icon'] = "%s,%s" %(self.module, str(rec.get('web_icon'))) - if rec.get('web_icon_hover'): - values['web_icon_hover'] = "%s,%s" %(self.module, str(rec.get('web_icon_hover'))) - - if rec.get('groups'): - g_names = rec.get('groups','').split(',') - groups_value = [] - for group in g_names: - if group.startswith('-'): - group_id = self.id_get(cr, group[1:]) - groups_value.append((3, group_id)) - else: - group_id = self.id_get(cr, group) - groups_value.append((4, group_id)) - values['groups_id'] = groups_value - - xml_id = rec.get('id','').encode('utf8') - self._test_xml_id(xml_id) - pid = self.pool.get('ir.model.data')._update(cr, self.uid, 'ir.ui.menu', self.module, values, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode, res_id=res and res[0] or False) - - if rec_id and pid: - self.idref[rec_id] = int(pid) - - if rec.get('action') and pid: - a_action = rec.get('action').encode('utf8') - a_type = rec.get('type','').encode('utf8') or 'act_window' - a_id = self.id_get(cr, a_action) - action = "ir.actions.%s,%d" % (a_type, a_id) - self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', 'tree_but_open', 'Menuitem', [('ir.ui.menu', int(pid))], action, True, True, xml_id=rec_id) - return ('ir.ui.menu', pid) - - def _assert_equals(self, f1, f2, prec=4): - return not round(f1 - f2, prec) - - def _tag_assert(self, cr, rec, data_node=None): - if self.isnoupdate(data_node) and self.mode != 'init': - return - - rec_model = rec.get("model",'').encode('ascii') - model = self.pool.get(rec_model) - assert model, "The model %s does not exist !" % (rec_model,) - rec_id = rec.get("id",'').encode('ascii') - self._test_xml_id(rec_id) - rec_src = rec.get("search",'').encode('utf8') - rec_src_count = rec.get("count") - - severity = rec.get("severity",'').encode('ascii') or netsvc.LOG_ERROR - rec_string = rec.get("string",'').encode('utf8') or 'unknown' - - ids = None - eval_dict = {'ref': _ref(self, cr)} - context = self.get_context(data_node, rec, eval_dict) - uid = self.get_uid(cr, self.uid, data_node, rec) - if rec_id: - ids = [self.id_get(cr, rec_id)] - elif rec_src: - q = unsafe_eval(rec_src, eval_dict) - ids = self.pool.get(rec_model).search(cr, uid, q, context=context) - if rec_src_count: - count = int(rec_src_count) - if len(ids) != count: - self.assert_report.record_assertion(False, severity) - msg = 'assertion "%s" failed!\n' \ - ' Incorrect search count:\n' \ - ' expected count: %d\n' \ - ' obtained count: %d\n' \ - % (rec_string, count, len(ids)) - sevval = getattr(logging, severity.upper()) - self.logger.log(sevval, msg) - if sevval >= config['assert_exit_level']: - # TODO: define a dedicated exception - raise Exception('Severe assertion failure') - return - - assert ids is not None,\ - 'You must give either an id or a search criteria' - ref = _ref(self, cr) - for id in ids: - brrec = model.browse(cr, uid, id, context) - class d(dict): - def __getitem__(self2, key): - if key in brrec: - return brrec[key] - return dict.__getitem__(self2, key) - globals_dict = d() - globals_dict['floatEqual'] = self._assert_equals - globals_dict['ref'] = ref - globals_dict['_ref'] = ref - for test in rec.findall('./test'): - f_expr = test.get("expr",'').encode('utf-8') - expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True - expression_value = unsafe_eval(f_expr, globals_dict) - if expression_value != expected_value: # assertion failed - self.assert_report.record_assertion(False, severity) - msg = 'assertion "%s" failed!\n' \ - ' xmltag: %s\n' \ - ' expected value: %r\n' \ - ' obtained value: %r\n' \ - % (rec_string, etree.tostring(test), expected_value, expression_value) - sevval = getattr(logging, severity.upper()) - self.logger.log(sevval, msg) - if sevval >= config['assert_exit_level']: - # TODO: define a dedicated exception - raise Exception('Severe assertion failure') - return - else: # all tests were successful for this assertion tag (no break) - self.assert_report.record_assertion(True, severity) - - def _tag_record(self, cr, rec, data_node=None): - rec_model = rec.get("model").encode('ascii') - model = self.pool.get(rec_model) - assert model, "The model %s does not exist !" % (rec_model,) - rec_id = rec.get("id",'').encode('ascii') - rec_context = rec.get("context", None) - if rec_context: - rec_context = unsafe_eval(rec_context) - self._test_xml_id(rec_id) - if self.isnoupdate(data_node) and self.mode != 'init': - # check if the xml record has an id string - if rec_id: - if '.' in rec_id: - module,rec_id2 = rec_id.split('.') - else: - module = self.module - rec_id2 = rec_id - id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2) - # check if the resource already existed at the last update - if id: - # if it existed, we don't update the data, but we need to - # know the id of the existing record anyway - self.idref[rec_id] = int(id) - return None - else: - # if the resource didn't exist - if not self.nodeattr2bool(rec, 'forcecreate', True): - # we don't want to create it, so we skip it - return None - # else, we let the record to be created - - else: - # otherwise it is skipped - return None - res = {} - for field in rec.findall('./field'): -#TODO: most of this code is duplicated above (in _eval_xml)... - f_name = field.get("name",'').encode('utf-8') - f_ref = field.get("ref",'').encode('utf-8') - f_search = field.get("search",'').encode('utf-8') - f_model = field.get("model",'').encode('utf-8') - if not f_model and model._columns.get(f_name,False): - f_model = model._columns[f_name]._obj - f_use = field.get("use",'').encode('utf-8') or 'id' - f_val = False - - if f_search: - q = unsafe_eval(f_search, self.idref) - field = [] - assert f_model, 'Define an attribute model="..." in your .XML file !' - f_obj = self.pool.get(f_model) - # browse the objects searched - s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q)) - # column definitions of the "local" object - _cols = self.pool.get(rec_model)._columns - # if the current field is many2many - if (f_name in _cols) and _cols[f_name]._type=='many2many': - f_val = [(6, 0, map(lambda x: x[f_use], s))] - elif len(s): - # otherwise (we are probably in a many2one field), - # take the first element of the search - f_val = s[0][f_use] - elif f_ref: - if f_ref=="null": - f_val = False - else: - if f_name in model._columns \ - and model._columns[f_name]._type == 'reference': - val = self.model_id_get(cr, f_ref) - f_val = val[0] + ',' + str(val[1]) - else: - f_val = self.id_get(cr, f_ref) - else: - f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref) - if model._columns.has_key(f_name): - if isinstance(model._columns[f_name], osv.fields.integer): - f_val = int(f_val) - res[f_name] = f_val - - id = self.pool.get('ir.model.data')._update(cr, self.uid, rec_model, self.module, res, rec_id or False, not self.isnoupdate(data_node), noupdate=self.isnoupdate(data_node), mode=self.mode, context=rec_context ) - if rec_id: - self.idref[rec_id] = int(id) - if config.get('import_partial', False): - cr.commit() - return rec_model, id - - def id_get(self, cr, id_str): - if id_str in self.idref: - return self.idref[id_str] - res = self.model_id_get(cr, id_str) - if res and len(res)>1: res = res[1] - return res - - def model_id_get(self, cr, id_str): - model_data_obj = self.pool.get('ir.model.data') - mod = self.module - if '.' in id_str: - mod,id_str = id_str.split('.') - return model_data_obj.get_object_reference(cr, self.uid, mod, id_str) - - def parse(self, de): - if not de.tag in ['terp', 'openerp']: - self.logger.error("Mismatch xml format") - raise Exception( "Mismatch xml format: only terp or openerp as root tag" ) - - if de.tag == 'terp': - self.logger.warning("The tag <terp/> is deprecated, use <openerp/>") - - for n in de.findall('./data'): - for rec in n: - if rec.tag in self._tags: - try: - self._tags[rec.tag](self.cr, rec, n) - except: - self.__logger.error('Parse error in %s:%d: \n%s', - rec.getroottree().docinfo.URL, - rec.sourceline, - etree.tostring(rec).strip(), exc_info=True) - self.cr.rollback() - raise - return True - - def __init__(self, cr, module, idref, mode, report=None, noupdate=False): - - self.logger = logging.getLogger('init') - self.mode = mode - self.module = module - self.cr = cr - self.idref = idref - self.pool = pooler.get_pool(cr.dbname) - self.uid = 1 - if report is None: - report = assertion_report() - self.assert_report = report - self.noupdate = noupdate - self._tags = { - 'menuitem': self._tag_menuitem, - 'record': self._tag_record, - 'assert': self._tag_assert, - 'report': self._tag_report, - 'wizard': self._tag_wizard, - 'delete': self._tag_delete, - 'ir_set': self._tag_ir_set, - 'function': self._tag_function, - 'workflow': self._tag_workflow, - 'act_window': self._tag_act_window, - 'url': self._tag_url - } - -def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', - noupdate=False): - '''Import csv file : - quote: " - delimiter: , - encoding: utf-8''' - if not idref: - idref={} - logger = logging.getLogger('init') - model = ('.'.join(fname.split('.')[:-1]).split('-'))[0] - #remove folder path from model - head, model = os.path.split(model) - - pool = pooler.get_pool(cr.dbname) - - input = cStringIO.StringIO(csvcontent) #FIXME - reader = csv.reader(input, quotechar='"', delimiter=',') - fields = reader.next() - fname_partial = "" - if config.get('import_partial'): - fname_partial = module + '/'+ fname - if not os.path.isfile(config.get('import_partial')): - pickle.dump({}, file(config.get('import_partial'),'w+')) - else: - data = pickle.load(file(config.get('import_partial'))) - if fname_partial in data: - if not data[fname_partial]: - return - else: - for i in range(data[fname_partial]): - reader.next() - - if not (mode == 'init' or 'id' in fields): - logger.error("Import specification does not contain 'id' and we are in init mode, Cannot continue.") - return - - uid = 1 - datas = [] - for line in reader: - if (not line) or not reduce(lambda x,y: x or y, line) : - continue - try: - datas.append(map(lambda x: misc.ustr(x), line)) - except: - logger.error("Cannot import the line: %s", line) - result, rows, warning_msg, dummy = pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial) - if result < 0: - # Report failed import and abort module install - raise Exception(_('Module loading failed: file %s/%s could not be processed:\n %s') % (module, fname, warning_msg)) - if config.get('import_partial'): - data = pickle.load(file(config.get('import_partial'))) - data[fname_partial] = 0 - pickle.dump(data, file(config.get('import_partial'),'wb')) - cr.commit() - -# -# xml import/export -# -def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None): - doc = etree.parse(xmlfile) - relaxng = etree.RelaxNG( - etree.parse(os.path.join(config['root_path'],'import_xml.rng' ))) - try: - relaxng.assert_(doc) - except Exception: - logger = netsvc.Logger() - logger.notifyChannel('init', netsvc.LOG_ERROR, 'The XML file does not fit the required schema !') - logger.notifyChannel('init', netsvc.LOG_ERROR, misc.ustr(relaxng.error_log.last_error)) - raise - - if idref is None: - idref={} - obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate) - obj.parse(doc.getroot()) - return True - diff --git a/bin/tools/copy.py b/bin/tools/copy.py deleted file mode 100644 index badd4d51fc5..00000000000 --- a/bin/tools/copy.py +++ /dev/null @@ -1,439 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -""" -FROM Python 2.5 -Generic (shallow and deep) copying operations. - -Interface summary: - - import copy - - x = copy.copy(y) # make a shallow copy of y - x = copy.deepcopy(y) # make a deep copy of y - -For module specific errors, copy.Error is raised. - -The difference between shallow and deep copying is only relevant for -compound objects (objects that contain other objects, like lists or -class instances). - -- A shallow copy constructs a new compound object and then (to the - extent possible) inserts *the same objects* into it that the - original contains. - -- A deep copy constructs a new compound object and then, recursively, - inserts *copies* into it of the objects found in the original. - -Two problems often exist with deep copy operations that don't exist -with shallow copy operations: - - a) recursive objects (compound objects that, directly or indirectly, - contain a reference to themselves) may cause a recursive loop - - b) because deep copy copies *everything* it may copy too much, e.g. - administrative data structures that should be shared even between - copies - -Python's deep copy operation avoids these problems by: - - a) keeping a table of objects already copied during the current - copying pass - - b) letting user-defined classes override the copying operation or the - set of components copied - -This version does not copy types like module, class, function, method, -nor stack trace, stack frame, nor file, socket, window, nor array, nor -any similar types. - -Classes can use the same interfaces to control copying that they use -to control pickling: they can define methods called __getinitargs__(), -__getstate__() and __setstate__(). See the documentation for module -"pickle" for information on these methods. -""" - -import types -from copy_reg import dispatch_table - -class Error(Exception): - pass -error = Error # backward compatibility - -try: - from org.python.core import PyStringMap -except ImportError: - PyStringMap = None - -__all__ = ["Error", "copy", "deepcopy"] - -def copy(x): - """Shallow copy operation on arbitrary Python objects. - - See the module's __doc__ string for more info. - """ - - cls = type(x) - - copier = _copy_dispatch.get(cls) - if copier: - return copier(x) - - copier = getattr(cls, "__copy__", None) - if copier: - return copier(x) - - reductor = dispatch_table.get(cls) - if reductor: - rv = reductor(x) - else: - reductor = getattr(x, "__reduce_ex__", None) - if reductor: - rv = reductor(2) - else: - reductor = getattr(x, "__reduce__", None) - if reductor: - rv = reductor() - else: - raise Error("un(shallow)copyable object of type %s" % cls) - - return _reconstruct(x, rv, 0) - - -_copy_dispatch = d = {} - -def _copy_immutable(x): - return x -for t in (type(None), int, long, float, bool, str, tuple, - frozenset, type, xrange, types.ClassType, - types.BuiltinFunctionType, - types.FunctionType): - d[t] = _copy_immutable -for name in ("ComplexType", "UnicodeType", "CodeType"): - t = getattr(types, name, None) - if t is not None: - d[t] = _copy_immutable - -def _copy_with_constructor(x): - return type(x)(x) -for t in (list, dict, set): - d[t] = _copy_with_constructor - -def _copy_with_copy_method(x): - return x.copy() -if PyStringMap is not None: - d[PyStringMap] = _copy_with_copy_method - -def _copy_inst(x): - if hasattr(x, '__copy__'): - return x.__copy__() - if hasattr(x, '__getinitargs__'): - args = x.__getinitargs__() - y = x.__class__(*args) - else: - y = _EmptyClass() - y.__class__ = x.__class__ - if hasattr(x, '__getstate__'): - state = x.__getstate__() - else: - state = x.__dict__ - if hasattr(y, '__setstate__'): - y.__setstate__(state) - else: - y.__dict__.update(state) - return y -d[types.InstanceType] = _copy_inst - -del d - -def deepcopy(x, memo=None, _nil=None): - """Deep copy operation on arbitrary Python objects. - - See the module's __doc__ string for more info. - """ - - if memo is None: - memo = {} - if _nil is None: - _nil = {} - - d = id(x) - y = memo.get(d, _nil) - if y is not _nil: - return y - - cls = type(x) - - copier = _deepcopy_dispatch.get(cls) - if copier: - y = copier(x, memo) - else: - try: - issc = issubclass(cls, type) - except TypeError: # cls is not a class (old Boost; see SF #502085) - issc = 0 - if issc: - y = _deepcopy_atomic(x, memo) - else: - copier = getattr(x, "__deepcopy__", None) - if copier: - y = copier(memo) - else: - reductor = dispatch_table.get(cls) - if reductor: - rv = reductor(x) - else: - reductor = getattr(x, "__reduce_ex__", None) - if reductor: - rv = reductor(2) - else: - reductor = getattr(x, "__reduce__", None) - if reductor: - rv = reductor() - else: - raise Error( - "un(deep)copyable object of type %s" % cls) - y = _reconstruct(x, rv, 1, memo) - - memo[d] = y - _keep_alive(x, memo) # Make sure x lives at least as long as d - return y - -_deepcopy_dispatch = d = {} - -def _deepcopy_atomic(x, memo): - return x -d[type(None)] = _deepcopy_atomic -d[int] = _deepcopy_atomic -d[long] = _deepcopy_atomic -d[float] = _deepcopy_atomic -d[bool] = _deepcopy_atomic -try: - d[complex] = _deepcopy_atomic -except NameError: - pass -d[str] = _deepcopy_atomic -try: - d[unicode] = _deepcopy_atomic -except NameError: - pass -try: - d[types.CodeType] = _deepcopy_atomic -except AttributeError: - pass -d[type] = _deepcopy_atomic -d[xrange] = _deepcopy_atomic -d[types.ClassType] = _deepcopy_atomic -d[types.BuiltinFunctionType] = _deepcopy_atomic -d[types.FunctionType] = _deepcopy_atomic - -def _deepcopy_list(x, memo): - y = [] - memo[id(x)] = y - for a in x: - y.append(deepcopy(a, memo)) - return y -d[list] = _deepcopy_list - -def _deepcopy_tuple(x, memo): - y = [] - for a in x: - y.append(deepcopy(a, memo)) - d = id(x) - try: - return memo[d] - except KeyError: - pass - for i in range(len(x)): - if x[i] is not y[i]: - y = tuple(y) - break - else: - y = x - memo[d] = y - return y -d[tuple] = _deepcopy_tuple - -def _deepcopy_dict(x, memo): - y = {} - memo[id(x)] = y - for key, value in x.iteritems(): - y[deepcopy(key, memo)] = deepcopy(value, memo) - return y -d[dict] = _deepcopy_dict -if PyStringMap is not None: - d[PyStringMap] = _deepcopy_dict - -def _keep_alive(x, memo): - """Keeps a reference to the object x in the memo. - - Because we remember objects by their id, we have - to assure that possibly temporary objects are kept - alive by referencing them. - We store a reference at the id of the memo, which should - normally not be used unless someone tries to deepcopy - the memo itself... - """ - try: - memo[id(memo)].append(x) - except KeyError: - # aha, this is the first one :-) - memo[id(memo)]=[x] - -def _deepcopy_inst(x, memo): - if hasattr(x, '__deepcopy__'): - return x.__deepcopy__(memo) - if hasattr(x, '__getinitargs__'): - args = x.__getinitargs__() - args = deepcopy(args, memo) - y = x.__class__(*args) - else: - y = _EmptyClass() - y.__class__ = x.__class__ - memo[id(x)] = y - if hasattr(x, '__getstate__'): - state = x.__getstate__() - else: - state = x.__dict__ - state = deepcopy(state, memo) - if hasattr(y, '__setstate__'): - y.__setstate__(state) - else: - y.__dict__.update(state) - return y -d[types.InstanceType] = _deepcopy_inst - -def _reconstruct(x, info, deep, memo=None): - if isinstance(info, str): - return x - assert isinstance(info, tuple) - if memo is None: - memo = {} - n = len(info) - assert n in (2, 3, 4, 5) - callable, args = info[:2] - if n > 2: - state = info[2] - else: - state = {} - if n > 3: - listiter = info[3] - else: - listiter = None - if n > 4: - dictiter = info[4] - else: - dictiter = None - if deep: - args = deepcopy(args, memo) - y = callable(*args) - memo[id(x)] = y - if listiter is not None: - for item in listiter: - if deep: - item = deepcopy(item, memo) - y.append(item) - if dictiter is not None: - for key, value in dictiter: - if deep: - key = deepcopy(key, memo) - value = deepcopy(value, memo) - y[key] = value - if state: - if deep: - state = deepcopy(state, memo) - if hasattr(y, '__setstate__'): - y.__setstate__(state) - else: - if isinstance(state, tuple) and len(state) == 2: - state, slotstate = state - else: - slotstate = None - if state is not None: - y.__dict__.update(state) - if slotstate is not None: - for key, value in slotstate.iteritems(): - setattr(y, key, value) - return y - -del d - -del types - -# Helper for instance creation without calling __init__ -class _EmptyClass: - pass - -def _test(): - l = [None, 1, 2L, 3.14, 'xyzzy', (1, 2L), [3.14, 'abc'], - {'abc': 'ABC'}, (), [], {}] - l1 = copy(l) - print l1==l - l1 = map(copy, l) - print l1==l - l1 = deepcopy(l) - print l1==l - class C: - def __init__(self, arg=None): - self.a = 1 - self.arg = arg - if __name__ == '__main__': - import sys - file = sys.argv[0] - else: - file = __file__ - self.fp = open(file) - self.fp.close() - def __getstate__(self): - return {'a': self.a, 'arg': self.arg} - def __setstate__(self, state): - for key, value in state.iteritems(): - setattr(self, key, value) - def __deepcopy__(self, memo=None): - new = self.__class__(deepcopy(self.arg, memo)) - new.a = self.a - return new - c = C('argument sketch') - l.append(c) - l2 = copy(l) - print l == l2 - print l - print l2 - l2 = deepcopy(l) - print l == l2 - print l - print l2 - l.append({l[1]: l, 'xyz': l[2]}) - l3 = copy(l) - import repr - print map(repr.repr, l) - print map(repr.repr, l1) - print map(repr.repr, l2) - print map(repr.repr, l3) - l3 = deepcopy(l) - import repr - print map(repr.repr, l) - print map(repr.repr, l1) - print map(repr.repr, l2) - print map(repr.repr, l3) - -if __name__ == '__main__': - _test() diff --git a/bin/tools/func.py b/bin/tools/func.py deleted file mode 100644 index 4150b3e688b..00000000000 --- a/bin/tools/func.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -__all__ = ['partial', 'wraps', 'update_wrapper', 'synchronized'] - -try: - from functools import partial, wraps, update_wrapper -except ImportError: - # The functools module doesn't exist in python < 2.5 - # Code taken from python 2.5 - # http://svn.python.org/view/python/tags/r254/Lib/functools.py?view=markup - - def partial(fun, *args, **kwargs): - def _partial(*args2, **kwargs2): - return fun(*(args+args2), **dict(kwargs, **kwargs2)) - return _partial - - ### --- code from python 2.5 - - # update_wrapper() and wraps() are tools to help write - # wrapper functions that can handle naive introspection - - WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__') - WRAPPER_UPDATES = ('__dict__',) - def update_wrapper(wrapper, - wrapped, - assigned = WRAPPER_ASSIGNMENTS, - updated = WRAPPER_UPDATES): - """Update a wrapper function to look like the wrapped function - - wrapper is the function to be updated - wrapped is the original function - assigned is a tuple naming the attributes assigned directly - from the wrapped function to the wrapper function (defaults to - functools.WRAPPER_ASSIGNMENTS) - updated is a tuple naming the attributes off the wrapper that - are updated with the corresponding attribute from the wrapped - function (defaults to functools.WRAPPER_UPDATES) - """ - for attr in assigned: - setattr(wrapper, attr, getattr(wrapped, attr)) - for attr in updated: - getattr(wrapper, attr).update(getattr(wrapped, attr, {})) - # Return the wrapper so this can be used as a decorator via partial() - return wrapper - - def wraps(wrapped, - assigned = WRAPPER_ASSIGNMENTS, - updated = WRAPPER_UPDATES): - """Decorator factory to apply update_wrapper() to a wrapper function - - Returns a decorator that invokes update_wrapper() with the decorated - function as the wrapper argument and the arguments to wraps() as the - remaining arguments. Default arguments are as for update_wrapper(). - This is a convenience function to simplify applying partial() to - update_wrapper(). - """ - return partial(update_wrapper, wrapped=wrapped, - assigned=assigned, updated=updated) - - - -def synchronized(lock_attr='_lock'): - def decorator(func): - @wraps(func) - def wrapper(self, *args, **kwargs): - lock = getattr(self, lock_attr) - try: - lock.acquire() - return func(self, *args, **kwargs) - finally: - lock.release() - return wrapper - return decorator - - - -from inspect import getsourcefile - -def frame_codeinfo(fframe, back=0): - """ Return a (filename, line) pair for a previous frame . - @return (filename, lineno) where lineno is either int or string=='' - """ - - try: - if not fframe: - return ("<unknown>", '') - for i in range(back): - fframe = fframe.f_back - try: - fname = getsourcefile(fframe) - except TypeError: - fname = '<builtin>' - lineno = fframe.f_lineno or '' - return (fname, lineno) - except Exception: - return ("<unknown>", '') diff --git a/bin/tools/graph.py b/bin/tools/graph.py deleted file mode 100644 index 05a1b8302f0..00000000000 --- a/bin/tools/graph.py +++ /dev/null @@ -1,784 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -import operator -import math - -class graph(object): - def __init__(self, nodes, transitions, no_ancester=None): - """Initailize graph's object - - @param nodes: list of ids of nodes in the graph - @param transitions: list of edges in the graph in the form (source_node, destination_node) - @param no_ancester: list of nodes with no incoming edges - """ - - self.nodes = nodes or [] - self.edges = transitions or [] - self.no_ancester = no_ancester or {} - trans = {} - - for t in transitions: - trans.setdefault(t[0], []) - trans[t[0]].append(t[1]) - self.transitions = trans - self.result = {} - - - def init_rank(self): - """Computes rank of the nodes of the graph by finding initial feasible tree - """ - self.edge_wt = {} - for link in self.links: - self.edge_wt[link] = self.result[link[1]]['x'] - self.result[link[0]]['x'] - - tot_node = self.partial_order.__len__() - #do until all the nodes in the component are searched - while self.tight_tree()<tot_node: - list_node = [] - list_edge = [] - - for node in self.nodes: - if node not in self.reachable_nodes: - list_node.append(node) - - for edge in self.edge_wt: - if edge not in self.tree_edges: - list_edge.append(edge) - - slack = 100 - - for edge in list_edge: - if ((self.reachable_nodes.__contains__(edge[0]) and edge[1] not in self.reachable_nodes) or - (self.reachable_nodes.__contains__(edge[1]) and edge[0] not in self.reachable_nodes)): - if(slack>self.edge_wt[edge]-1): - slack = self.edge_wt[edge]-1 - new_edge = edge - - if new_edge[0] not in self.reachable_nodes: - delta = -(self.edge_wt[new_edge]-1) - else: - delta = self.edge_wt[new_edge]-1 - - for node in self.result: - if node in self.reachable_nodes: - self.result[node]['x'] += delta - - for edge in self.edge_wt: - self.edge_wt[edge] = self.result[edge[1]]['x'] - self.result[edge[0]]['x'] - - self.init_cutvalues() - - - def tight_tree(self): - self.reachable_nodes = [] - self.tree_edges = [] - self.reachable_node(self.start) - return self.reachable_nodes.__len__() - - - def reachable_node(self, node): - """Find the nodes of the graph which are only 1 rank apart from each other - """ - - if node not in self.reachable_nodes: - self.reachable_nodes.append(node) - for edge in self.edge_wt: - if edge[0]==node: - if self.edge_wt[edge]==1: - self.tree_edges.append(edge) - if edge[1] not in self.reachable_nodes: - self.reachable_nodes.append(edge[1]) - self.reachable_node(edge[1]) - - - def init_cutvalues(self): - """Initailize cut values of edges of the feasible tree. - Edges with negative cut-values are removed from the tree to optimize rank assignment - """ - self.cut_edges = {} - self.head_nodes = [] - i=0; - - for edge in self.tree_edges: - self.head_nodes = [] - rest_edges = [] - rest_edges += self.tree_edges - rest_edges.__delitem__(i) - self.head_component(self.start, rest_edges) - i+=1 - positive = 0 - negative = 0 - for source_node in self.transitions: - if source_node in self.head_nodes: - for dest_node in self.transitions[source_node]: - if dest_node not in self.head_nodes: - negative+=1 - else: - for dest_node in self.transitions[source_node]: - if dest_node in self.head_nodes: - positive+=1 - - self.cut_edges[edge] = positive - negative - - - def head_component(self, node, rest_edges): - """Find nodes which are reachable from the starting node, after removing an edge - """ - if node not in self.head_nodes: - self.head_nodes.append(node) - - for edge in rest_edges: - if edge[0]==node: - self.head_component(edge[1],rest_edges) - - - def process_ranking(self, node, level=0): - """Computes initial feasible ranking after making graph acyclic with depth-first search - """ - - if node not in self.result: - self.result[node] = {'y': None, 'x':level, 'mark':0} - else: - if level > self.result[node]['x']: - self.result[node]['x'] = level - - if self.result[node]['mark']==0: - self.result[node]['mark'] = 1 - for sec_end in self.transitions.get(node, []): - self.process_ranking(sec_end, level+1) - - - def make_acyclic(self, parent, node, level, tree): - """Computes Partial-order of the nodes with depth-first search - """ - - if node not in self.partial_order: - self.partial_order[node] = {'level':level, 'mark':0} - if parent: - tree.append((parent, node)) - - if self.partial_order[node]['mark']==0: - self.partial_order[node]['mark'] = 1 - for sec_end in self.transitions.get(node, []): - self.links.append((node, sec_end)) - self.make_acyclic(node, sec_end, level+1, tree) - - return tree - - - def rev_edges(self, tree): - """reverse the direction of the edges whose source-node-partail_order> destination-node-partail_order - to make the graph acyclic - """ - Is_Cyclic = False - i=0 - for link in self.links: - src = link[0] - des = link[1] - edge_len = self.partial_order[des]['level'] - self.partial_order[src]['level'] - if edge_len < 0: - self.links.__delitem__(i) - self.links.insert(i, (des, src)) - self.transitions[src].remove(des) - self.transitions.setdefault(des, []).append(src) - Is_Cyclic = True - elif math.fabs(edge_len) > 1: - Is_Cyclic = True - i += 1 - - return Is_Cyclic - - def exchange(self, e, f): - """Exchange edges to make feasible-tree optimized - @param edge: edge with negative cut-value - @param edge: new edge with minimum slack-value - """ - self.tree_edges.__delitem__(self.tree_edges.index(e)) - self.tree_edges.append(f) - self.init_cutvalues() - - - def enter_edge(self, edge): - """Finds a new_edge with minimum slack value to replace an edge with negative cut-value - - @param edge: edge with negative cut-value - """ - - self.head_nodes = [] - rest_edges = [] - rest_edges += self.tree_edges - rest_edges.__delitem__(rest_edges.index(edge)) - self.head_component(self.start, rest_edges) - - if self.head_nodes.__contains__(edge[1]): - l = [] - for node in self.result: - if not self.head_nodes.__contains__(node): - l.append(node) - self.head_nodes = l - - slack = 100 - new_edge = edge - for source_node in self.transitions: - if source_node in self.head_nodes: - for dest_node in self.transitions[source_node]: - if dest_node not in self.head_nodes: - if(slack>(self.edge_wt[edge]-1)): - slack = self.edge_wt[edge]-1 - new_edge = (source_node, dest_node) - - return new_edge - - - def leave_edge(self): - """Returns the edge with negative cut_value(if exists) - """ - if self.critical_edges: - for edge in self.critical_edges: - self.cut_edges[edge] = 0 - - for edge in self.cut_edges: - if self.cut_edges[edge]<0: - return edge - - return None - - - def finalize_rank(self, node, level): - self.result[node]['x'] = level - for destination in self.optimal_edges.get(node, []): - self.finalize_rank(destination, level+1) - - - def normalize(self): - """The ranks are normalized by setting the least rank to zero. - """ - - least_rank = min(map(lambda x: x['x'], self.result.values())) - - if(least_rank!=0): - for node in self.result: - self.result[node]['x']-=least_rank - - - def make_chain(self): - """Edges between nodes more than one rank apart are replaced by chains of unit - length edges between temporary nodes. - """ - - for edge in self.edge_wt: - if self.edge_wt[edge]>1: - self.transitions[edge[0]].remove(edge[1]) - start = self.result[edge[0]]['x'] - end = self.result[edge[1]]['x'] - - for rank in range(start+1, end): - if not self.result.get((rank, 'temp'), False): - self.result[(rank, 'temp')] = {'y': None, 'x': rank, 'mark': 0} - - for rank in range(start, end): - if start==rank: - self.transitions[edge[0]].append((rank+1, 'temp')) - elif rank==end-1: - self.transitions.setdefault((rank, 'temp'), []).append(edge[1]) - else: - self.transitions.setdefault((rank, 'temp'), []).append((rank+1, 'temp')) - - - def init_order(self, node, level): - """Initialize orders the nodes in each rank with depth-first search - """ - if not self.result[node]['y']: - self.result[node]['y'] = self.order[level] - self.order[level] = self.order[level]+1 - - for sec_end in self.transitions.get(node, []): - self.init_order(sec_end, self.result[sec_end]['x']) - - - def order_heuristic(self): - for i in range(12): - self.wmedian() - - - def wmedian(self): - """Applies median heuristic to find optimzed order of the nodes with in their ranks - """ - for level in self.levels: - - node_median = [] - nodes = self.levels[level] - for node in nodes: - node_median.append((node, self.median_value(node, level-1))) - - sort_list = sorted(node_median, key=operator.itemgetter(1)) - - new_list = [tuple[0] for tuple in sort_list] - - self.levels[level] = new_list - order = 0 - for node in nodes: - self.result[node]['y'] = order - order +=1 - - - def median_value(self, node, adj_rank): - """Returns median value of a vertex , defined as the median position of the adjacent vertices - - @param node: node to process - @param adj_rank: rank 1 less than the node's rank - """ - adj_nodes = self.adj_position(node, adj_rank) - l = len(adj_nodes) - m = l/2 - - if l==0: - return -1.0 - elif l%2 == 1: - return adj_nodes[m]#median of the middle element - elif l==2: - return (adj_nodes[0]+adj_nodes[1])/2 - else: - left = adj_nodes[m-1] - adj_nodes[0] - right = adj_nodes[l-1] - adj_nodes[m] - return ((adj_nodes[m-1]*right) + (adj_nodes[m]*left))/(left+right) - - - def adj_position(self, node, adj_rank): - """Returns list of the present positions of the nodes adjacent to node in the given adjacent rank. - - @param node: node to process - @param adj_rank: rank 1 less than the node's rank - """ - - pre_level_nodes = self.levels.get(adj_rank, []) - adj_nodes = [] - - if pre_level_nodes: - for src in pre_level_nodes: - if (self.transitions.get(src) and self.transitions[src].__contains__(node)): - adj_nodes.append(self.result[src]['y']) - - return adj_nodes - - - def preprocess_order(self): - levels = {} - - for r in self.partial_order: - l = self.result[r]['x'] - levels.setdefault(l,[]) - levels[l].append(r) - - self.levels = levels - - - def graph_order(self): - """Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component - """ - mid_pos = 0.0 - max_level = max(map(lambda x: len(x), self.levels.values())) - - for level in self.levels: - if level: - no = len(self.levels[level]) - factor = (max_level - no) * 0.10 - list = self.levels[level] - list.reverse() - - if no%2==0: - first_half = list[no/2:] - factor = -factor - else: - first_half = list[no/2+1:] - if max_level==1:#for the case when horizontal graph is there - self.result[list[no/2]]['y'] = mid_pos + (self.result[list[no/2]]['x']%2 * 0.5) - else: - self.result[list[no/2]]['y'] = mid_pos + factor - - last_half = list[:no/2] - - i=1 - for node in first_half: - self.result[node]['y'] = mid_pos - (i + factor) - i += 1 - - i=1 - for node in last_half: - self.result[node]['y'] = mid_pos + (i + factor) - i += 1 - else: - self.max_order += max_level+1 - mid_pos = self.result[self.start]['y'] - - - def tree_order(self, node, last=0): - mid_pos = self.result[node]['y'] - l = self.transitions.get(node, []) - l.reverse() - no = len(l) - - if no%2==0: - first_half = l[no/2:] - factor = 1 - else: - first_half = l[no/2+1:] - factor = 0 - - last_half = l[:no/2] - - i=1 - for child in first_half: - self.result[child]['y'] = mid_pos - (i - (factor * 0.5)) - i += 1 - - if self.transitions.get(child, False): - if last: - self.result[child]['y'] = last + len(self.transitions[child])/2 + 1 - last = self.tree_order(child, last) - - if no%2: - mid_node = l[no/2] - self.result[mid_node]['y'] = mid_pos - - if self.transitions.get((mid_node), False): - if last: - self.result[mid_node]['y'] = last + len(self.transitions[mid_node])/2 + 1 - last = self.tree_order(mid_node) - else: - if last: - self.result[mid_node]['y'] = last + 1 - self.result[node]['y'] = self.result[mid_node]['y'] - mid_pos = self.result[node]['y'] - - i=1 - last_child = None - for child in last_half: - self.result[child]['y'] = mid_pos + (i - (factor * 0.5)) - last_child = child - i += 1 - if self.transitions.get(child, False): - if last: - self.result[child]['y'] = last + len(self.transitions[child])/2 + 1 - last = self.tree_order(child, last) - - if last_child: - last = self.result[last_child]['y'] - - return last - - - def process_order(self): - """Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component - """ - - if self.Is_Cyclic: - max_level = max(map(lambda x: len(x), self.levels.values())) - - if max_level%2: - self.result[self.start]['y'] = (max_level+1)/2 + self.max_order + (self.max_order and 1) - else: - self.result[self.start]['y'] = (max_level)/2 + self.max_order + (self.max_order and 1) - - self.graph_order() - - else: - self.result[self.start]['y'] = 0 - self.tree_order(self.start, 0) - min_order = math.fabs(min(map(lambda x: x['y'], self.result.values()))) - - index = self.start_nodes.index(self.start) - same = False - - roots = [] - if index>0: - for start in self.start_nodes[:index]: - same = True - for edge in self.tree_list[start][1:]: - if self.tree_list[self.start].__contains__(edge): - continue - else: - same = False - break - if same: - roots.append(start) - - if roots: - min_order += self.max_order - else: - min_order += self.max_order + 1 - - for level in self.levels: - for node in self.levels[level]: - self.result[node]['y'] += min_order - - if roots: - roots.append(self.start) - one_level_el = self.tree_list[self.start][0][1] - base = self.result[one_level_el]['y']# * 2 / (index + 2) - - - no = len(roots) - first_half = roots[:no/2] - - if no%2==0: - last_half = roots[no/2:] - else: - last_half = roots[no/2+1:] - - factor = -math.floor(no/2) - for start in first_half: - self.result[start]['y'] = base + factor - factor += 1 - - if no%2: - self.result[roots[no/2]]['y'] = base + factor - factor +=1 - - for start in last_half: - self.result[start]['y'] = base + factor - factor += 1 - - self.max_order = max(map(lambda x: x['y'], self.result.values())) - - def find_starts(self): - """Finds other start nodes of the graph in the case when graph is disconneted - """ - rem_nodes = [] - for node in self.nodes: - if not self.partial_order.get(node): - rem_nodes.append(node) - cnt = 0 - while True: - if len(rem_nodes)==1: - self.start_nodes.append(rem_nodes[0]) - break - else: - count = 0 - new_start = rem_nodes[0] - largest_tree = [] - - for node in rem_nodes: - self.partial_order = {} - tree = self.make_acyclic(None, node, 0, []) - if len(tree)+1 > count: - count = len(tree) + 1 - new_start = node - largest_tree = tree - else: - if not largest_tree: - new_start = rem_nodes[0] - rem_nodes.remove(new_start) - - self.start_nodes.append(new_start) - - - for edge in largest_tree: - if rem_nodes.__contains__(edge[0]): - rem_nodes.remove(edge[0]) - if rem_nodes.__contains__(edge[1]): - rem_nodes.remove(edge[1]) - - if not rem_nodes: - break - - - def rank(self): - """Finds the optimized rank of the nodes using Network-simplex algorithm - - @param start: starting node of the component - """ - self.levels = {} - self.critical_edges = [] - self.partial_order = {} - self.links = [] - self.Is_Cyclic = False - - self.tree_list[self.start] = self.make_acyclic(None, self.start, 0, []) - self.Is_Cyclic = self.rev_edges(self.tree_list[self.start]) - self.process_ranking(self.start) - self.init_rank() - - #make cut values of all tree edges to 0 to optimize feasible tree - e = self.leave_edge() - - while e : - f = self.enter_edge(e) - if e==f: - self.critical_edges.append(e) - else: - self.exchange(e,f) - e = self.leave_edge() - - #finalize rank using optimum feasible tree -# self.optimal_edges = {} -# for edge in self.tree_edges: -# source = self.optimal_edges.setdefault(edge[0], []) -# source.append(edge[1]) - -# self.finalize_rank(self.start, 0) - - #normalization - self.normalize() - for edge in self.edge_wt: - self.edge_wt[edge] = self.result[edge[1]]['x'] - self.result[edge[0]]['x'] - - def order_in_rank(self): - """Finds optimized order of the nodes within their ranks using median heuristic - - @param start: starting node of the component - """ - - self.make_chain() - self.preprocess_order() - self.order = {} - max_rank = max(map(lambda x: x, self.levels.keys())) - - for i in range(max_rank+1): - self.order[i] = 0 - - self.init_order(self.start, self.result[self.start]['x']) - - for level in self.levels: - self.levels[level].sort(lambda x, y: cmp(self.result[x]['y'], self.result[y]['y'])) - - self.order_heuristic() - self.process_order() - - def process(self, starting_node): - """Process the graph to find ranks and order of the nodes - - @param starting_node: node from where to start the graph search - """ - - self.start_nodes = starting_node or [] - self.partial_order = {} - self.links = [] - self.tree_list = {} - - if self.nodes: - if self.start_nodes: - #add dummy edges to the nodes which does not have any incoming edges - tree = self.make_acyclic(None, self.start_nodes[0], 0, []) - - for node in self.no_ancester: - for sec_node in self.transitions.get(node, []): - if sec_node in self.partial_order.keys(): - self.transitions[self.start_nodes[0]].append(node) - break - - self.partial_order = {} - tree = self.make_acyclic(None, self.start_nodes[0], 0, []) - - - # if graph is disconnected or no start-node is given - #than to find starting_node for each component of the node - if len(self.nodes) > len(self.partial_order): - self.find_starts() - - self.max_order = 0 - #for each component of the graph find ranks and order of the nodes - for s in self.start_nodes: - self.start = s - self.rank() # First step:Netwoek simplex algorithm - self.order_in_rank() #Second step: ordering nodes within ranks - - - def __str__(self): - result = '' - for l in self.levels: - result += 'PosY: ' + str(l) + '\n' - for node in self.levels[l]: - result += '\tPosX: '+ str(self.result[node]['y']) + ' - Node:' + node + "\n" - return result - - - def scale(self, maxx, maxy, nwidth=0, nheight=0, margin=20): - """Computes actual co-ordiantes of the nodes - """ - - #for flat edges ie. source an destination nodes are on the same rank - for src in self.transitions: - for des in self.transitions[src]: - if (self.result[des]['x'] - self.result[src]['x'] == 0): - self.result[src]['x'] += 0.08 - self.result[des]['x'] -= 0.08 - - factorX = maxx + nheight - factorY = maxy + nwidth - - for node in self.result: - self.result[node]['y'] = (self.result[node]['y']) * factorX + margin - self.result[node]['x'] = (self.result[node]['x']) * factorY + margin - - - def result_get(self): - return self.result - -if __name__=='__main__': - starting_node = ['profile'] # put here nodes with flow_start=True - nodes = ['project','account','hr','base','product','mrp','test','profile'] - transitions = [ - ('profile','mrp'), - ('mrp','project'), - ('project','product'), - ('mrp','hr'), - ('mrp','test'), - ('project','account'), - ('project','hr'), - ('product','base'), - ('account','product'), - ('account','test'), - ('account','base'), - ('hr','base'), - ('test','base') - ] - - radius = 20 - g = graph(nodes, transitions) - g.process(starting_node) - g.scale(radius*3,radius*3, radius, radius) - - import Image - import ImageDraw - img = Image.new("RGB", (800, 600), "#ffffff") - draw = ImageDraw.Draw(img) - - result = g.result_get() - node_res = {} - for node in nodes: - node_res[node] = result[node] - - for name,node in node_res.items(): - - draw.arc( (int(node['y']-radius), int(node['x']-radius),int(node['y']+radius), int(node['x']+radius) ), 0, 360, (128,128,128)) - draw.text( (int(node['y']), int(node['x'])), name, (128,128,128)) - - - for t in transitions: - draw.line( (int(node_res[t[0]]['y']), int(node_res[t[0]]['x']),int(node_res[t[1]]['y']),int(node_res[t[1]]['x'])),(128,128,128) ) - img.save("graph.png", "PNG") - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/bin/tools/import_email.py b/bin/tools/import_email.py deleted file mode 100644 index b597dab5722..00000000000 --- a/bin/tools/import_email.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -import os, sys -import re -import smtplib -import email, mimetypes -from email.Header import decode_header -from email.MIMEText import MIMEText -import xmlrpclib - -warn_msg = """ -Bonjour, - -Le message avec le sujet "%s" n'a pu être archivé dans l'ERP. - -""".decode('utf-8') - -class EmailParser(object): - - def __init__(self, headers, dispatcher): - self.headers = headers - self.dispatcher = dispatcher - - def parse(self, msg): - dispatcher((self.headers, msg)) - -class CommandDispatcher(object): - - def __init__(self, receiver): - self.receiver = receiver - - def __call__(self, request): - return self.receiver(request) - -class RPCProxy(object): - - def __init__(self, uid, passwd, host='localhost', port=8069, path='object'): - self.rpc = xmlrpclib.ServerProxy('http://%s:%s/%s' % (host, port, path)) - self.user_id = uid - self.passwd = passwd - - def __call__(self, request): - return self.rpc.execute(self.user_id, self.passwd, *request) - -class ReceiverEmail2Event(object): - - email_re = re.compile(r""" - ([a-zA-Z][\w\.-]*[a-zA-Z0-9] # username part - @ # mandatory @ sign - [a-zA-Z0-9][\w\.-]* # domain must start with a letter - \. - [a-z]{2,3} # TLD - ) - """, re.VERBOSE) - - project_re = re.compile(r"^ *\[?(\d{4}\.?\d{0,3})\]?", re.UNICODE) - - - def __init__(self, rpc): - self.rpc = rpc - - def get_addresses(self, headers, msg): - hcontent = '' - for header in [h for h in headers if msg.has_key(h)]: - hcontent += msg[header] - return self.email_re.findall(hcontent) - - def get_partners(self, headers, msg): - alladdresses = self.get_addresses(headers, msg) - address_ids = self.rpc(('res.partner.address', 'search', [('email', 'in', alladdresses)])) - addresses = self.rpc(('res.partner.address', 'read', address_ids)) - return [x['partner_id'][0] for x in addresses] - - def __call__(self, request): - headers, msg = request - partners = self.get_partners(headers, msg) - subject = u'' - for string, charset in decode_header(msg['Subject']): - if charset: - subject += string.decode(charset) - else: - subject += unicode(string) - if partners: - self.save_mail(msg, subject, partners) - else: - warning = MIMEText((warn_msg % (subject,)).encode('utf-8'), 'plain', 'utf-8') - warning['Subject'] = 'Message de OpenERP' - warning['From'] = 'erp@steel-sa.com' - warning['To'] = msg['From'] - s = smtplib.SMTP() - s.connect() - s.sendmail('erp@steel-sa.com', self.email_re.findall(msg['From']), warning.as_string()) - s.close() - - if msg.is_multipart(): - for message in [m for m in msg.get_payload() if m.get_content_type() == 'message/rfc822']: - self((headers, message.get_payload()[0])) - - def save_mail(self, msg, subject, partners): - counter, description = 1, u'' - if msg.is_multipart(): - for part in msg.get_payload(): - stockdir = os.path.join('emails', msg['Message-Id'][1:-1]) - newdir = os.path.join('/tmp', stockdir) - filename = part.get_filename() - if not filename: - ext = mimetypes.guess_extension(part.get_type()) - if not ext: - ext = '.bin' - filename = 'part-%03d%s' % (counter, ext) - - if part.get_content_maintype() == 'multipart': - continue - elif part.get_content_maintype() == 'text': - if part.get_content_subtype() == 'plain': - description += part.get_payload(decode=1).decode(part.get_charsets()[0]) - description += u'\n\nVous trouverez les éventuels fichiers dans le répertoire: %s' % stockdir - continue - else: - description += u'\n\nCe message est en "%s", vous trouverez ce texte dans le répertoire: %s' % (part.get_content_type(), stockdir) - elif part.get_content_type() == 'message/rfc822': - continue - if not os.path.isdir(newdir): - os.mkdir(newdir) - - counter += 1 - fd = file(os.path.join(newdir, filename), 'w') - fd.write(part.get_payload(decode=1)) - fd.close() - else: - description = msg.get_payload(decode=1).decode(msg.get_charsets()[0]) - - project = self.project_re.search(subject) - if project: - project = project.groups()[0] - else: - project = '' - - for partner in partners: - self.rpc(('res.partner.event', 'create', {'name' : subject, 'partner_id' : partner, 'description' : description, 'project' : project})) - - -if __name__ == '__main__': - rpc_dispatcher = CommandDispatcher(RPCProxy(4, 'admin')) - dispatcher = CommandDispatcher(ReceiverEmail2Event(rpc_dispatcher)) - parser = EmailParser(['To', 'Cc', 'From'], dispatcher) - parser.parse(email.message_from_file(sys.stdin)) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/bin/tools/lru.py b/bin/tools/lru.py deleted file mode 100644 index 190fe5e4ff6..00000000000 --- a/bin/tools/lru.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -# taken from http://code.activestate.com/recipes/252524-length-limited-o1-lru-cache-implementation/ -import threading -from func import synchronized - -__all__ = ['LRU'] - -class LRUNode(object): - __slots__ = ['prev', 'next', 'me'] - def __init__(self, prev, me): - self.prev = prev - self.me = me - self.next = None - -class LRU: - """ - Implementation of a length-limited O(1) LRU queue. - Built for and used by PyPE: - http://pype.sourceforge.net - Copyright 2003 Josiah Carlson. - """ - def __init__(self, count, pairs=[]): - self._lock = threading.RLock() - self.count = max(count, 1) - self.d = {} - self.first = None - self.last = None - for key, value in pairs: - self[key] = value - - @synchronized() - def __contains__(self, obj): - return obj in self.d - - @synchronized() - def __getitem__(self, obj): - a = self.d[obj].me - self[a[0]] = a[1] - return a[1] - - @synchronized() - def __setitem__(self, obj, val): - if obj in self.d: - del self[obj] - nobj = LRUNode(self.last, (obj, val)) - if self.first is None: - self.first = nobj - if self.last: - self.last.next = nobj - self.last = nobj - self.d[obj] = nobj - if len(self.d) > self.count: - if self.first == self.last: - self.first = None - self.last = None - return - a = self.first - a.next.prev = None - self.first = a.next - a.next = None - del self.d[a.me[0]] - del a - - @synchronized() - def __delitem__(self, obj): - nobj = self.d[obj] - if nobj.prev: - nobj.prev.next = nobj.next - else: - self.first = nobj.next - if nobj.next: - nobj.next.prev = nobj.prev - else: - self.last = nobj.prev - del self.d[obj] - - @synchronized() - def __iter__(self): - cur = self.first - while cur != None: - cur2 = cur.next - yield cur.me[1] - cur = cur2 - - @synchronized() - def __len__(self): - return len(self.d) - - @synchronized() - def iteritems(self): - cur = self.first - while cur != None: - cur2 = cur.next - yield cur.me - cur = cur2 - - @synchronized() - def iterkeys(self): - return iter(self.d) - - @synchronized() - def itervalues(self): - for i,j in self.iteritems(): - yield j - - @synchronized() - def keys(self): - return self.d.keys() - - @synchronized() - def pop(self,key): - v=self[key] - del self[key] - return v - diff --git a/bin/tools/misc.py b/bin/tools/misc.py deleted file mode 100644 index 615893ef238..00000000000 --- a/bin/tools/misc.py +++ /dev/null @@ -1,1508 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -""" -Miscelleanous tools used by OpenERP. -""" - -import inspect -import subprocess -import logging -import os -import re -import smtplib -import socket -import sys -import threading -import time -import warnings -import zipfile -from datetime import datetime -from email.MIMEText import MIMEText -from email.MIMEBase import MIMEBase -from email.MIMEMultipart import MIMEMultipart -from email.Header import Header -from email.Utils import formatdate, COMMASPACE -from email import Encoders -from itertools import islice, izip -from lxml import etree -from which import which -if sys.version_info[:2] < (2, 4): - from threadinglocal import local -else: - from threading import local -try: - from html2text import html2text -except ImportError: - html2text = None - -import netsvc -from config import config -from lru import LRU - -_logger = logging.getLogger('tools') - -# List of etree._Element subclasses that we choose to ignore when parsing XML. -# We include the *Base ones just in case, currently they seem to be subclasses of the _* ones. -SKIPPED_ELEMENT_TYPES = (etree._Comment, etree._ProcessingInstruction, etree.CommentBase, etree.PIBase) - -# initialize a database with base/base.sql -def init_db(cr): - import addons - f = addons.get_module_resource('base', 'base.sql') - base_sql_file = file_open(f) - try: - cr.execute(base_sql_file.read()) - cr.commit() - finally: - base_sql_file.close() - - for i in addons.get_modules(): - mod_path = addons.get_module_path(i) - if not mod_path: - continue - - info = addons.load_information_from_description_file(i) - - if not info: - continue - categs = info.get('category', 'Uncategorized').split('/') - p_id = None - while categs: - if p_id is not None: - cr.execute('SELECT id \ - FROM ir_module_category \ - WHERE name=%s AND parent_id=%s', (categs[0], p_id)) - else: - cr.execute('SELECT id \ - FROM ir_module_category \ - WHERE name=%s AND parent_id IS NULL', (categs[0],)) - c_id = cr.fetchone() - if not c_id: - cr.execute('INSERT INTO ir_module_category \ - (name, parent_id) \ - VALUES (%s, %s) RETURNING id', (categs[0], p_id)) - c_id = cr.fetchone()[0] - else: - c_id = c_id[0] - p_id = c_id - categs = categs[1:] - - active = info.get('active', False) - installable = info.get('installable', True) - if installable: - if active: - state = 'to install' - else: - state = 'uninstalled' - else: - state = 'uninstallable' - cr.execute('INSERT INTO ir_module_module \ - (author, website, name, shortdesc, description, \ - category_id, state, certificate, web, license) \ - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id', ( - info.get('author', ''), - info.get('website', ''), i, info.get('name', False), - info.get('description', ''), p_id, state, info.get('certificate') or None, - info.get('web') or False, - info.get('license') or 'AGPL-3')) - id = cr.fetchone()[0] - cr.execute('INSERT INTO ir_model_data \ - (name,model,module, res_id, noupdate) VALUES (%s,%s,%s,%s,%s)', ( - 'module_meta_information', 'ir.module.module', i, id, True)) - dependencies = info.get('depends', []) - for d in dependencies: - cr.execute('INSERT INTO ir_module_module_dependency \ - (module_id,name) VALUES (%s, %s)', (id, d)) - cr.commit() - -def find_in_path(name): - try: - return which(name) - except IOError: - return None - -def find_pg_tool(name): - path = None - if config['pg_path'] and config['pg_path'] != 'None': - path = config['pg_path'] - try: - return which(name, path=path) - except IOError: - return None - -def exec_pg_command(name, *args): - prog = find_pg_tool(name) - if not prog: - raise Exception('Couldn\'t find %s' % name) - args2 = (prog,) + args - - return subprocess.call(args2) - -def exec_pg_command_pipe(name, *args): - prog = find_pg_tool(name) - if not prog: - raise Exception('Couldn\'t find %s' % name) - # on win32, passing close_fds=True is not compatible - # with redirecting std[in/err/out] - pop = subprocess.Popen((prog,) + args, bufsize= -1, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, - close_fds=(os.name=="posix")) - return (pop.stdin, pop.stdout) - -def exec_command_pipe(name, *args): - prog = find_in_path(name) - if not prog: - raise Exception('Couldn\'t find %s' % name) - # on win32, passing close_fds=True is not compatible - # with redirecting std[in/err/out] - pop = subprocess.Popen((prog,) + args, bufsize= -1, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, - close_fds=(os.name=="posix")) - return (pop.stdin, pop.stdout) - -#---------------------------------------------------------- -# File paths -#---------------------------------------------------------- -#file_path_root = os.getcwd() -#file_path_addons = os.path.join(file_path_root, 'addons') - -def file_open(name, mode="r", subdir='addons', pathinfo=False): - """Open a file from the OpenERP root, using a subdir folder. - - >>> file_open('hr/report/timesheer.xsl') - >>> file_open('addons/hr/report/timesheet.xsl') - >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True) - - @param name: name of the file - @param mode: file open mode - @param subdir: subdirectory - @param pathinfo: if True returns tupple (fileobject, filepath) - - @return: fileobject if pathinfo is False else (fileobject, filepath) - """ - import addons - adps = addons.ad_paths - rtp = os.path.normcase(os.path.abspath(config['root_path'])) - - if name.replace(os.path.sep, '/').startswith('addons/'): - subdir = 'addons' - name = name[7:] - - # First try to locate in addons_path - if subdir: - subdir2 = subdir - if subdir2.replace(os.path.sep, '/').startswith('addons/'): - subdir2 = subdir2[7:] - - subdir2 = (subdir2 != 'addons' or None) and subdir2 - - for adp in adps: - try: - if subdir2: - fn = os.path.join(adp, subdir2, name) - else: - fn = os.path.join(adp, name) - fn = os.path.normpath(fn) - fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo) - if pathinfo: - return fo, fn - return fo - except IOError: - pass - - if subdir: - name = os.path.join(rtp, subdir, name) - else: - name = os.path.join(rtp, name) - - name = os.path.normpath(name) - - # Check for a zipfile in the path - head = name - zipname = False - name2 = False - while True: - head, tail = os.path.split(head) - if not tail: - break - if zipname: - zipname = os.path.join(tail, zipname) - else: - zipname = tail - if zipfile.is_zipfile(head+'.zip'): - from cStringIO import StringIO - zfile = zipfile.ZipFile(head+'.zip') - try: - fo = StringIO() - fo.write(zfile.read(os.path.join( - os.path.basename(head), zipname).replace( - os.sep, '/'))) - fo.seek(0) - if pathinfo: - return fo, name - return fo - except Exception: - name2 = os.path.normpath(os.path.join(head + '.zip', zipname)) - pass - for i in (name2, name): - if i and os.path.isfile(i): - fo = file(i, mode) - if pathinfo: - return fo, i - return fo - if os.path.splitext(name)[1] == '.rml': - raise IOError, 'Report %s doesn\'t exist or deleted : ' %str(name) - raise IOError, 'File not found : %s' % name - - -#---------------------------------------------------------- -# iterables -#---------------------------------------------------------- -def flatten(list): - """Flatten a list of elements into a uniqu list - Author: Christophe Simonis (christophe@tinyerp.com) - - Examples: - >>> flatten(['a']) - ['a'] - >>> flatten('b') - ['b'] - >>> flatten( [] ) - [] - >>> flatten( [[], [[]]] ) - [] - >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] ) - ['a', 'b', 'c', 'd', 'e', 'f'] - >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]]) - >>> flatten(t) - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] - """ - - def isiterable(x): - return hasattr(x, "__iter__") - - r = [] - for e in list: - if isiterable(e): - map(r.append, flatten(e)) - else: - r.append(e) - return r - -def reverse_enumerate(l): - """Like enumerate but in the other sens - >>> a = ['a', 'b', 'c'] - >>> it = reverse_enumerate(a) - >>> it.next() - (2, 'c') - >>> it.next() - (1, 'b') - >>> it.next() - (0, 'a') - >>> it.next() - Traceback (most recent call last): - File "<stdin>", line 1, in <module> - StopIteration - """ - return izip(xrange(len(l)-1, -1, -1), reversed(l)) - -#---------------------------------------------------------- -# Emails -#---------------------------------------------------------- -email_re = re.compile(r""" - ([a-zA-Z][\w\.-]*[a-zA-Z0-9] # username part - @ # mandatory @ sign - [a-zA-Z0-9][\w\.-]* # domain must start with a letter ... Ged> why do we include a 0-9 then? - \. - [a-z]{2,3} # TLD - ) - """, re.VERBOSE) -res_re = re.compile(r"\[([0-9]+)\]", re.UNICODE) -command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE) -reference_re = re.compile("<.*-openobject-(\\d+)@(.*)>", re.UNICODE) - -priorities = { - '1': '1 (Highest)', - '2': '2 (High)', - '3': '3 (Normal)', - '4': '4 (Low)', - '5': '5 (Lowest)', - } - -def html2plaintext(html, body_id=None, encoding='utf-8'): - ## (c) Fry-IT, www.fry-it.com, 2007 - ## <peter@fry-it.com> - ## download here: http://www.peterbe.com/plog/html2plaintext - - - """ from an HTML text, convert the HTML to plain text. - If @body_id is provided then this is the tag where the - body (not necessarily <body>) starts. - """ - - html = ustr(html) - - from lxml.etree import tostring - try: - from lxml.html.soupparser import fromstring - kwargs = {} - except ImportError: - _logger.debug('tools.misc.html2plaintext: cannot use BeautifulSoup, fallback to lxml.etree.HTMLParser') - from lxml.etree import fromstring, HTMLParser - kwargs = dict(parser=HTMLParser()) - - tree = fromstring(html, **kwargs) - - if body_id is not None: - source = tree.xpath('//*[@id=%s]'%(body_id,)) - else: - source = tree.xpath('//body') - if len(source): - tree = source[0] - - url_index = [] - i = 0 - for link in tree.findall('.//a'): - url = link.get('href') - if url: - i += 1 - link.tag = 'span' - link.text = '%s [%s]' % (link.text, i) - url_index.append(url) - - html = ustr(tostring(tree, encoding=encoding)) - - html = html.replace('<strong>','*').replace('</strong>','*') - html = html.replace('<b>','*').replace('</b>','*') - html = html.replace('<h3>','*').replace('</h3>','*') - html = html.replace('<h2>','**').replace('</h2>','**') - html = html.replace('<h1>','**').replace('</h1>','**') - html = html.replace('<em>','/').replace('</em>','/') - html = html.replace('<tr>', '\n') - html = html.replace('</p>', '\n') - html = re.sub('<br\s*/?>', '\n', html) - html = re.sub('<.*?>', ' ', html) - html = html.replace(' ' * 2, ' ') - - # strip all lines - html = '\n'.join([x.strip() for x in html.splitlines()]) - html = html.replace('\n' * 2, '\n') - - for i, url in enumerate(url_index): - if i == 0: - html += '\n\n' - html += ustr('[%s] %s\n') % (i+1, url) - - return html - -def generate_tracking_message_id(openobject_id): - """Returns a string that can be used in the Message-ID RFC822 header field so we - can track the replies related to a given object thanks to the "In-Reply-To" or - "References" fields that Mail User Agents will set. - """ - return "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname()) - -def _email_send(smtp_from, smtp_to_list, message, openobject_id=None, ssl=False, debug=False): - """Low-level method to send directly a Message through the configured smtp server. - :param smtp_from: RFC-822 envelope FROM (not displayed to recipient) - :param smtp_to_list: RFC-822 envelope RCPT_TOs (not displayed to recipient) - :param message: an email.message.Message to send - :param debug: True if messages should be output to stderr before being sent, - and smtplib.SMTP put into debug mode. - :return: True if the mail was delivered successfully to the smtp, - else False (+ exception logged) - """ - class WriteToLogger(object): - def __init__(self): - self.logger = netsvc.Logger() - - def write(self, s): - self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s) - - if openobject_id: - message['Message-Id'] = generate_tracking_message_id(openobject_id) - - try: - smtp_server = config['smtp_server'] - - if smtp_server.startswith('maildir:/'): - from mailbox import Maildir - maildir_path = smtp_server[8:] - mdir = Maildir(maildir_path,factory=None, create = True) - mdir.add(message.as_string(True)) - return True - - oldstderr = smtplib.stderr - if not ssl: ssl = config.get('smtp_ssl', False) - s = smtplib.SMTP() - try: - # in case of debug, the messages are printed to stderr. - if debug: - smtplib.stderr = WriteToLogger() - - s.set_debuglevel(int(bool(debug))) # 0 or 1 - s.connect(smtp_server, config['smtp_port']) - if ssl: - s.ehlo() - s.starttls() - s.ehlo() - - if config['smtp_user'] or config['smtp_password']: - s.login(config['smtp_user'], config['smtp_password']) - - s.sendmail(smtp_from, smtp_to_list, message.as_string()) - finally: - try: - s.quit() - if debug: - smtplib.stderr = oldstderr - except Exception: - # ignored, just a consequence of the previous exception - pass - - except Exception: - _logger.error('could not deliver email', exc_info=True) - return False - - return True - - -def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False, - attach=None, openobject_id=False, ssl=False, debug=False, subtype='plain', x_headers=None, priority='3'): - - """Send an email. - - Arguments: - - `email_from`: A string used to fill the `From` header, if falsy, - config['email_from'] is used instead. Also used for - the `Reply-To` header if `reply_to` is not provided - - `email_to`: a sequence of addresses to send the mail to. - """ - if x_headers is None: - x_headers = {} - - - if not (email_from or config['email_from']): - raise ValueError("Sending an email requires either providing a sender " - "address or having configured one") - - if not email_from: email_from = config.get('email_from', False) - email_from = ustr(email_from).encode('utf-8') - - if not email_cc: email_cc = [] - if not email_bcc: email_bcc = [] - if not body: body = u'' - - email_body = ustr(body).encode('utf-8') - email_text = MIMEText(email_body or '',_subtype=subtype,_charset='utf-8') - - msg = MIMEMultipart() - - msg['Subject'] = Header(ustr(subject), 'utf-8') - msg['From'] = email_from - del msg['Reply-To'] - if reply_to: - msg['Reply-To'] = reply_to - else: - msg['Reply-To'] = msg['From'] - msg['To'] = COMMASPACE.join(email_to) - if email_cc: - msg['Cc'] = COMMASPACE.join(email_cc) - if email_bcc: - msg['Bcc'] = COMMASPACE.join(email_bcc) - msg['Date'] = formatdate(localtime=True) - - msg['X-Priority'] = priorities.get(priority, '3 (Normal)') - - # Add dynamic X Header - for key, value in x_headers.iteritems(): - msg['%s' % key] = str(value) - - if html2text and subtype == 'html': - text = html2text(email_body.decode('utf-8')).encode('utf-8') - alternative_part = MIMEMultipart(_subtype="alternative") - alternative_part.attach(MIMEText(text, _charset='utf-8', _subtype='plain')) - alternative_part.attach(email_text) - msg.attach(alternative_part) - else: - msg.attach(email_text) - - if attach: - for (fname,fcontent) in attach: - part = MIMEBase('application', "octet-stream") - part.set_payload( fcontent ) - Encoders.encode_base64(part) - part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,)) - msg.attach(part) - - return _email_send(email_from, flatten([email_to, email_cc, email_bcc]), msg, openobject_id=openobject_id, ssl=ssl, debug=debug) - -#---------------------------------------------------------- -# SMS -#---------------------------------------------------------- -# text must be latin-1 encoded -def sms_send(user, password, api_id, text, to): - import urllib - url = "http://api.urlsms.com/SendSMS.aspx" - #url = "http://196.7.150.220/http/sendmsg" - params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to}) - urllib.urlopen(url+"?"+params) - # FIXME: Use the logger if there is an error - return True - -#--------------------------------------------------------- -# Class that stores an updateable string (used in wizards) -#--------------------------------------------------------- -class UpdateableStr(local): - - def __init__(self, string=''): - self.string = string - - def __str__(self): - return str(self.string) - - def __repr__(self): - return str(self.string) - - def __nonzero__(self): - return bool(self.string) - - -class UpdateableDict(local): - '''Stores an updateable dict to use in wizards''' - - def __init__(self, dict=None): - if dict is None: - dict = {} - self.dict = dict - - def __str__(self): - return str(self.dict) - - def __repr__(self): - return str(self.dict) - - def clear(self): - return self.dict.clear() - - def keys(self): - return self.dict.keys() - - def __setitem__(self, i, y): - self.dict.__setitem__(i, y) - - def __getitem__(self, i): - return self.dict.__getitem__(i) - - def copy(self): - return self.dict.copy() - - def iteritems(self): - return self.dict.iteritems() - - def iterkeys(self): - return self.dict.iterkeys() - - def itervalues(self): - return self.dict.itervalues() - - def pop(self, k, d=None): - return self.dict.pop(k, d) - - def popitem(self): - return self.dict.popitem() - - def setdefault(self, k, d=None): - return self.dict.setdefault(k, d) - - def update(self, E, **F): - return self.dict.update(E, F) - - def values(self): - return self.dict.values() - - def get(self, k, d=None): - return self.dict.get(k, d) - - def has_key(self, k): - return self.dict.has_key(k) - - def items(self): - return self.dict.items() - - def __cmp__(self, y): - return self.dict.__cmp__(y) - - def __contains__(self, k): - return self.dict.__contains__(k) - - def __delitem__(self, y): - return self.dict.__delitem__(y) - - def __eq__(self, y): - return self.dict.__eq__(y) - - def __ge__(self, y): - return self.dict.__ge__(y) - - def __gt__(self, y): - return self.dict.__gt__(y) - - def __hash__(self): - return self.dict.__hash__() - - def __iter__(self): - return self.dict.__iter__() - - def __le__(self, y): - return self.dict.__le__(y) - - def __len__(self): - return self.dict.__len__() - - def __lt__(self, y): - return self.dict.__lt__(y) - - def __ne__(self, y): - return self.dict.__ne__(y) - - -# Don't use ! Use res.currency.round() -class currency(float): - - def __init__(self, value, accuracy=2, rounding=None): - if rounding is None: - rounding=10**-accuracy - self.rounding=rounding - self.accuracy=accuracy - - def __new__(cls, value, accuracy=2, rounding=None): - return float.__new__(cls, round(value, accuracy)) - - #def __str__(self): - # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy)) - # return str(display_value) - - -def is_hashable(h): - try: - hash(h) - return True - except TypeError: - return False - -class cache(object): - """ - Use it as a decorator of the function you plan to cache - Timeout: 0 = no timeout, otherwise in seconds - """ - - __caches = [] - - def __init__(self, timeout=None, skiparg=2, multi=None, size=8192): - assert skiparg >= 2 # at least self and cr - if timeout is None: - self.timeout = config['cache_timeout'] - else: - self.timeout = timeout - self.skiparg = skiparg - self.multi = multi - self.lasttime = time.time() - self.cache = LRU(size) # TODO take size from config - self.fun = None - cache.__caches.append(self) - - - def _generate_keys(self, dbname, kwargs2): - """ - Generate keys depending of the arguments and the self.mutli value - """ - - def to_tuple(d): - pairs = d.items() - pairs.sort(key=lambda (k,v): k) - for i, (k, v) in enumerate(pairs): - if isinstance(v, dict): - pairs[i] = (k, to_tuple(v)) - if isinstance(v, (list, set)): - pairs[i] = (k, tuple(v)) - elif not is_hashable(v): - pairs[i] = (k, repr(v)) - return tuple(pairs) - - if not self.multi: - key = (('dbname', dbname),) + to_tuple(kwargs2) - yield key, None - else: - multis = kwargs2[self.multi][:] - for id in multis: - kwargs2[self.multi] = (id,) - key = (('dbname', dbname),) + to_tuple(kwargs2) - yield key, id - - def _unify_args(self, *args, **kwargs): - # Update named arguments with positional argument values (without self and cr) - kwargs2 = self.fun_default_values.copy() - kwargs2.update(kwargs) - kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:]))) - return kwargs2 - - def clear(self, dbname, *args, **kwargs): - """clear the cache for database dbname - if *args and **kwargs are both empty, clear all the keys related to this database - """ - if not args and not kwargs: - keys_to_del = [key for key in self.cache.keys() if key[0][1] == dbname] - else: - kwargs2 = self._unify_args(*args, **kwargs) - keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache.keys()] - - for key in keys_to_del: - self.cache.pop(key) - - @classmethod - def clean_caches_for_db(cls, dbname): - for c in cls.__caches: - c.clear(dbname) - - def __call__(self, fn): - if self.fun is not None: - raise Exception("Can not use a cache instance on more than one function") - self.fun = fn - - argspec = inspect.getargspec(fn) - self.fun_arg_names = argspec[0][self.skiparg:] - self.fun_default_values = {} - if argspec[3]: - self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3])) - - def cached_result(self2, cr, *args, **kwargs): - if time.time()-int(self.timeout) > self.lasttime: - self.lasttime = time.time() - t = time.time()-int(self.timeout) - old_keys = [key for key in self.cache.keys() if self.cache[key][1] < t] - for key in old_keys: - self.cache.pop(key) - - kwargs2 = self._unify_args(*args, **kwargs) - - result = {} - notincache = {} - for key, id in self._generate_keys(cr.dbname, kwargs2): - if key in self.cache: - result[id] = self.cache[key][0] - else: - notincache[id] = key - - if notincache: - if self.multi: - kwargs2[self.multi] = notincache.keys() - - result2 = fn(self2, cr, *args[:self.skiparg-2], **kwargs2) - if not self.multi: - key = notincache[None] - self.cache[key] = (result2, time.time()) - result[None] = result2 - else: - for id in result2: - key = notincache[id] - self.cache[key] = (result2[id], time.time()) - result.update(result2) - - if not self.multi: - return result[None] - return result - - cached_result.clear_cache = self.clear - return cached_result - -def to_xml(s): - return s.replace('&','&').replace('<','<').replace('>','>') - -def get_encodings(hint_encoding='utf-8'): - fallbacks = { - 'latin1': 'latin9', - 'iso-8859-1': 'iso8859-15', - 'cp1252': '1252', - } - if hint_encoding: - yield hint_encoding - if hint_encoding.lower() in fallbacks: - yield fallbacks[hint_encoding.lower()] - - # some defaults (also taking care of pure ASCII) - for charset in ['utf8','latin1']: - if not (hint_encoding) or (charset.lower() != hint_encoding.lower()): - yield charset - - from locale import getpreferredencoding - prefenc = getpreferredencoding() - if prefenc and prefenc.lower() != 'utf-8': - yield prefenc - prefenc = fallbacks.get(prefenc.lower()) - if prefenc: - yield prefenc - - -def ustr(value, hint_encoding='utf-8'): - """This method is similar to the builtin `str` method, except - it will return unicode() string. - - @param value: the value to convert - @param hint_encoding: an optional encoding that was detected - upstream and should be tried first to - decode ``value``. - - @rtype: unicode - @return: unicode string - """ - if isinstance(value, Exception): - return exception_to_unicode(value) - - if isinstance(value, unicode): - return value - - if not isinstance(value, basestring): - try: - return unicode(value) - except Exception: - raise UnicodeError('unable to convert %r' % (value,)) - - for ln in get_encodings(hint_encoding): - try: - return unicode(value, ln) - except Exception: - pass - raise UnicodeError('unable to convert %r' % (value,)) - - -def exception_to_unicode(e): - if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'): - return ustr(e.message) - if hasattr(e, 'args'): - return "\n".join((ustr(a) for a in e.args)) - try: - return ustr(e) - except Exception: - return u"Unknown message" - - -# to be compatible with python 2.4 -import __builtin__ -if not hasattr(__builtin__, 'all'): - def all(iterable): - for element in iterable: - if not element: - return False - return True - - __builtin__.all = all - del all - -if not hasattr(__builtin__, 'any'): - def any(iterable): - for element in iterable: - if element: - return True - return False - - __builtin__.any = any - del any - -def get_iso_codes(lang): - if lang.find('_') != -1: - if lang.split('_')[0] == lang.split('_')[1].lower(): - lang = lang.split('_')[0] - return lang - -def get_languages(): - # The codes below are those from Launchpad's Rosetta, with the exception - # of some trivial codes where the Launchpad code is xx and we have xx_XX. - languages={ - 'ab_RU': u'Abkhazian / аҧсуа', - 'ar_AR': u'Arabic / الْعَرَبيّة', - 'bg_BG': u'Bulgarian / български език', - 'bs_BS': u'Bosnian / bosanski jezik', - 'ca_ES': u'Catalan / Català', - 'cs_CZ': u'Czech / Čeština', - 'da_DK': u'Danish / Dansk', - 'de_DE': u'German / Deutsch', - 'el_GR': u'Greek / Ελληνικά', - 'en_CA': u'English (CA)', - 'en_GB': u'English (UK)', - 'en_US': u'English (US)', - 'es_AR': u'Spanish (AR) / Español (AR)', - 'es_BO': u'Spanish (BO) / Español (BO)', - 'es_CL': u'Spanish (CL) / Español (CL)', - 'es_CO': u'Spanish (CO) / Español (CO)', - 'es_CR': u'Spanish (CR) / Español (CR)', - 'es_DO': u'Spanish (DO) / Español (DO)', - 'es_EC': u'Spanish (EC) / Español (EC)', - 'es_ES': u'Spanish / Español', - 'es_GT': u'Spanish (GT) / Español (GT)', - 'es_HN': u'Spanish (HN) / Español (HN)', - 'es_MX': u'Spanish (MX) / Español (MX)', - 'es_NI': u'Spanish (NI) / Español (NI)', - 'es_PA': u'Spanish (PA) / Español (PA)', - 'es_PE': u'Spanish (PE) / Español (PE)', - 'es_PR': u'Spanish (PR) / Español (PR)', - 'es_PY': u'Spanish (PY) / Español (PY)', - 'es_SV': u'Spanish (SV) / Español (SV)', - 'es_UY': u'Spanish (UY) / Español (UY)', - 'es_VE': u'Spanish (VE) / Español (VE)', - 'et_EE': u'Estonian / Eesti keel', - 'fa_IR': u'Persian / فارس', - 'fi_FI': u'Finnish / Suomi', - 'fr_BE': u'French (BE) / Français (BE)', - 'fr_CH': u'French (CH) / Français (CH)', - 'fr_FR': u'French / Français', - 'gl_ES': u'Galician / Galego', - 'gu_IN': u'Gujarati / ગુજરાતી', - 'he_IL': u'Hebrew / עִבְרִי', - 'hi_IN': u'Hindi / हिंदी', - 'hr_HR': u'Croatian / hrvatski jezik', - 'hu_HU': u'Hungarian / Magyar', - 'id_ID': u'Indonesian / Bahasa Indonesia', - 'it_IT': u'Italian / Italiano', - 'iu_CA': u'Inuktitut / ᐃᓄᒃᑎᑐᑦ', - 'ja_JP': u'Japanese / 日本語', - 'ko_KP': u'Korean (KP) / 한국어 (KP)', - 'ko_KR': u'Korean (KR) / 한국어 (KR)', - 'lt_LT': u'Lithuanian / Lietuvių kalba', - 'lv_LV': u'Latvian / latviešu valoda', - 'ml_IN': u'Malayalam / മലയാളം', - 'mn_MN': u'Mongolian / монгол', - 'nb_NO': u'Norwegian Bokmål / Norsk bokmål', - 'nl_NL': u'Dutch / Nederlands', - 'nl_BE': u'Flemish (BE) / Vlaams (BE)', - 'oc_FR': u'Occitan (FR, post 1500) / Occitan', - 'pl_PL': u'Polish / Język polski', - 'pt_BR': u'Portugese (BR) / Português (BR)', - 'pt_PT': u'Portugese / Português', - 'ro_RO': u'Romanian / română', - 'ru_RU': u'Russian / русский язык', - 'si_LK': u'Sinhalese / සිංහල', - 'sl_SI': u'Slovenian / slovenščina', - 'sk_SK': u'Slovak / Slovenský jazyk', - 'sq_AL': u'Albanian / Shqip', - 'sr_RS': u'Serbian (Cyrillic) / српски', - 'sr@latin': u'Serbian (Latin) / srpski', - 'sv_SE': u'Swedish / svenska', - 'te_IN': u'Telugu / తెలుగు', - 'tr_TR': u'Turkish / Türkçe', - 'vi_VN': u'Vietnamese / Tiếng Việt', - 'uk_UA': u'Ukrainian / українська', - 'ur_PK': u'Urdu / اردو', - 'zh_CN': u'Chinese (CN) / 简体中文', - 'zh_HK': u'Chinese (HK)', - 'zh_TW': u'Chinese (TW) / 正體字', - 'th_TH': u'Thai / ภาษาไทย', - 'tlh_TLH': u'Klingon', - } - return languages - -def scan_languages(): - # Now it will take all languages from get languages function without filter it with base module languages - lang_dict = get_languages() - ret = [(lang, lang_dict.get(lang, lang)) for lang in list(lang_dict)] - ret.sort(key=lambda k:k[1]) - return ret - - -def get_user_companies(cr, user): - def _get_company_children(cr, ids): - if not ids: - return [] - cr.execute('SELECT id FROM res_company WHERE parent_id IN %s', (tuple(ids),)) - res = [x[0] for x in cr.fetchall()] - res.extend(_get_company_children(cr, res)) - return res - cr.execute('SELECT company_id FROM res_users WHERE id=%s', (user,)) - user_comp = cr.fetchone()[0] - if not user_comp: - return [] - return [user_comp] + _get_company_children(cr, [user_comp]) - -def mod10r(number): - """ - Input number : account or invoice number - Output return: the same number completed with the recursive mod10 - key - """ - codec=[0,9,4,6,8,2,7,1,3,5] - report = 0 - result="" - for digit in number: - result += digit - if digit.isdigit(): - report = codec[ (int(digit) + report) % 10 ] - return result + str((10 - report) % 10) - - -def human_size(sz): - """ - Return the size in a human readable format - """ - if not sz: - return False - units = ('bytes', 'Kb', 'Mb', 'Gb') - if isinstance(sz,basestring): - sz=len(sz) - s, i = float(sz), 0 - while s >= 1024 and i < len(units)-1: - s = s / 1024 - i = i + 1 - return "%0.2f %s" % (s, units[i]) - -def logged(f): - from tools.func import wraps - - @wraps(f) - def wrapper(*args, **kwargs): - from pprint import pformat - - vector = ['Call -> function: %r' % f] - for i, arg in enumerate(args): - vector.append(' arg %02d: %s' % (i, pformat(arg))) - for key, value in kwargs.items(): - vector.append(' kwarg %10s: %s' % (key, pformat(value))) - - timeb4 = time.time() - res = f(*args, **kwargs) - - vector.append(' result: %s' % pformat(res)) - vector.append(' time delta: %s' % (time.time() - timeb4)) - netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector)) - return res - - return wrapper - -class profile(object): - def __init__(self, fname=None): - self.fname = fname - - def __call__(self, f): - from tools.func import wraps - - @wraps(f) - def wrapper(*args, **kwargs): - class profile_wrapper(object): - def __init__(self): - self.result = None - def __call__(self): - self.result = f(*args, **kwargs) - pw = profile_wrapper() - import cProfile - fname = self.fname or ("%s.cprof" % (f.func_name,)) - cProfile.runctx('pw()', globals(), locals(), filename=fname) - return pw.result - - return wrapper - -def debug(what): - """ - This method allow you to debug your code without print - Example: - >>> def func_foo(bar) - ... baz = bar - ... debug(baz) - ... qnx = (baz, bar) - ... debug(qnx) - ... - >>> func_foo(42) - - This will output on the logger: - - [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42 - [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42) - - To view the DEBUG lines in the logger you must start the server with the option - --log-level=debug - - """ - warnings.warn("The tools.debug() method is deprecated, please use logging.", - DeprecationWarning, stacklevel=2) - from inspect import stack - from pprint import pformat - st = stack()[1] - param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip() - while param.count(')') > param.count('('): param = param[:param.rfind(')')] - what = pformat(what) - if param != what: - what = "%s = %s" % (param, what) - logging.getLogger(st[3]).debug(what) - - -__icons_list = ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD', -'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER', -'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE', -'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO', -'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT', -'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE', -'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM', -'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK', -'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK', -'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC', -'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL', -'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD', -'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY', -'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND', -'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW', -'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES', -'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT', -'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED', -'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT', -'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK', -'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE', -'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100', -'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT', -'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase', -'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner', -'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph', -'terp-check','terp-go-month','terp-go-year','terp-go-today','terp-document-new','terp-camera_test', -'terp-emblem-important','terp-gtk-media-pause','terp-gtk-stop','terp-gnome-cpu-frequency-applet+', -'terp-dialog-close','terp-gtk-jump-to-rtl','terp-gtk-jump-to-ltr','terp-accessories-archiver', -'terp-stock_align_left_24','terp-stock_effects-object-colorize','terp-go-home','terp-gtk-go-back-rtl', -'terp-gtk-go-back-ltr','terp-personal','terp-personal-','terp-personal+','terp-accessories-archiver-minus', -'terp-accessories-archiver+','terp-stock_symbol-selection','terp-call-start','terp-dolar', -'terp-face-plain','terp-folder-blue','terp-folder-green','terp-folder-orange','terp-folder-yellow', -'terp-gdu-smart-failing','terp-go-week','terp-gtk-select-all','terp-locked','terp-mail-forward', -'terp-mail-message-new','terp-mail-replied','terp-rating-rated','terp-stage','terp-stock_format-scientific', -'terp-dolar_ok!','terp-idea','terp-stock_format-default','terp-mail-','terp-mail_delete' -] - -def icons(*a, **kw): - global __icons_list - return [(x, x) for x in __icons_list ] - -def extract_zip_file(zip_file, outdirectory): - zf = zipfile.ZipFile(zip_file, 'r') - out = outdirectory - for path in zf.namelist(): - tgt = os.path.join(out, path) - tgtdir = os.path.dirname(tgt) - if not os.path.exists(tgtdir): - os.makedirs(tgtdir) - - if not tgt.endswith(os.sep): - fp = open(tgt, 'wb') - fp.write(zf.read(path)) - fp.close() - zf.close() - -def detect_ip_addr(): - """Try a very crude method to figure out a valid external - IP or hostname for the current machine. Don't rely on this - for binding to an interface, but it could be used as basis - for constructing a remote URL to the server. - """ - def _detect_ip_addr(): - from array import array - from struct import pack, unpack - - try: - import fcntl - except ImportError: - fcntl = None - - ip_addr = None - - if not fcntl: # not UNIX: - host = socket.gethostname() - ip_addr = socket.gethostbyname(host) - else: # UNIX: - # get all interfaces: - nbytes = 128 * 32 - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - names = array('B', '\0' * nbytes) - #print 'names: ', names - outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0] - namestr = names.tostring() - - # try 64 bit kernel: - for i in range(0, outbytes, 40): - name = namestr[i:i+16].split('\0', 1)[0] - if name != 'lo': - ip_addr = socket.inet_ntoa(namestr[i+20:i+24]) - break - - # try 32 bit kernel: - if ip_addr is None: - ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)]) - - for ifname in [iface for iface in ifaces if iface != 'lo']: - ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24]) - break - - return ip_addr or 'localhost' - - try: - ip_addr = _detect_ip_addr() - except Exception: - ip_addr = 'localhost' - return ip_addr - -# RATIONALE BEHIND TIMESTAMP CALCULATIONS AND TIMEZONE MANAGEMENT: -# The server side never does any timestamp calculation, always -# sends them in a naive (timezone agnostic) format supposed to be -# expressed within the server timezone, and expects the clients to -# provide timestamps in the server timezone as well. -# It stores all timestamps in the database in naive format as well, -# which also expresses the time in the server timezone. -# For this reason the server makes its timezone name available via the -# common/timezone_get() rpc method, which clients need to read -# to know the appropriate time offset to use when reading/writing -# times. -def get_win32_timezone(): - """Attempt to return the "standard name" of the current timezone on a win32 system. - @return: the standard name of the current win32 timezone, or False if it cannot be found. - """ - res = False - if (sys.platform == "win32"): - try: - import _winreg - hklm = _winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE) - current_tz_key = _winreg.OpenKey(hklm, r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation", 0,_winreg.KEY_ALL_ACCESS) - res = str(_winreg.QueryValueEx(current_tz_key,"StandardName")[0]) # [0] is value, [1] is type code - _winreg.CloseKey(current_tz_key) - _winreg.CloseKey(hklm) - except Exception: - pass - return res - -def detect_server_timezone(): - """Attempt to detect the timezone to use on the server side. - Defaults to UTC if no working timezone can be found. - @return: the timezone identifier as expected by pytz.timezone. - """ - try: - import pytz - except Exception: - netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING, - "Python pytz module is not available. Timezone will be set to UTC by default.") - return 'UTC' - - # Option 1: the configuration option (did not exist before, so no backwards compatibility issue) - # 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 - # Option 3: the environment variable TZ - sources = [ (config['timezone'], 'OpenERP configuration'), - (time.tzname[0], 'time.tzname'), - (os.environ.get('TZ',False),'TZ environment variable'), ] - # Option 4: OS-specific: /etc/timezone on Unix - if (os.path.exists("/etc/timezone")): - tz_value = False - try: - f = open("/etc/timezone") - tz_value = f.read(128).strip() - except Exception: - pass - finally: - f.close() - sources.append((tz_value,"/etc/timezone file")) - # Option 5: timezone info from registry on Win32 - if (sys.platform == "win32"): - # Timezone info is stored in windows registry. - # However this is not likely to work very well as the standard name - # of timezones in windows is rarely something that is known to pytz. - # But that's ok, it is always possible to use a config option to set - # it explicitly. - sources.append((get_win32_timezone(),"Windows Registry")) - - for (value,source) in sources: - if value: - try: - tz = pytz.timezone(value) - netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_INFO, - "Using timezone %s obtained from %s." % (tz.zone,source)) - return value - except pytz.UnknownTimeZoneError: - netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING, - "The timezone specified in %s (%s) is invalid, ignoring it." % (source,value)) - - netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING, - "No valid timezone could be detected, using default UTC timezone. You can specify it explicitly with option 'timezone' in the server configuration.") - return 'UTC' - -def get_server_timezone(): - # timezone detection is safe in multithread, so lazy init is ok here - if (not config['timezone']): - config['timezone'] = detect_server_timezone() - return config['timezone'] - - -DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d" -DEFAULT_SERVER_TIME_FORMAT = "%H:%M:%S" -DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % ( - DEFAULT_SERVER_DATE_FORMAT, - DEFAULT_SERVER_TIME_FORMAT) - -# Python's strftime supports only the format directives -# that are available on the platform's libc, so in order to -# be cross-platform we map to the directives required by -# the C standard (1989 version), always available on platforms -# with a C standard implementation. -DATETIME_FORMATS_MAP = { - '%C': '', # century - '%D': '%m/%d/%Y', # modified %y->%Y - '%e': '%d', - '%E': '', # special modifier - '%F': '%Y-%m-%d', - '%g': '%Y', # modified %y->%Y - '%G': '%Y', - '%h': '%b', - '%k': '%H', - '%l': '%I', - '%n': '\n', - '%O': '', # special modifier - '%P': '%p', - '%R': '%H:%M', - '%r': '%I:%M:%S %p', - '%s': '', #num of seconds since epoch - '%T': '%H:%M:%S', - '%t': ' ', # tab - '%u': ' %w', - '%V': '%W', - '%y': '%Y', # Even if %y works, it's ambiguous, so we should use %Y - '%+': '%Y-%m-%d %H:%M:%S', - - # %Z is a special case that causes 2 problems at least: - # - the timezone names we use (in res_user.context_tz) come - # from pytz, but not all these names are recognized by - # strptime(), so we cannot convert in both directions - # when such a timezone is selected and %Z is in the format - # - %Z is replaced by an empty string in strftime() when - # there is not tzinfo in a datetime value (e.g when the user - # did not pick a context_tz). The resulting string does not - # parse back if the format requires %Z. - # As a consequence, we strip it completely from format strings. - # The user can always have a look at the context_tz in - # preferences to check the timezone. - '%z': '', - '%Z': '', -} - -def server_to_local_timestamp(src_tstamp_str, src_format, dst_format, dst_tz_name, - tz_offset=True, ignore_unparsable_time=True): - """ - Convert a source timestamp string into a destination timestamp string, attempting to apply the - correct offset if both the server and local timezone are recognized, or no - offset at all if they aren't or if tz_offset is false (i.e. assuming they are both in the same TZ). - - WARNING: This method is here to allow formatting dates correctly for inclusion in strings where - the client would not be able to format/offset it correctly. DO NOT use it for returning - date fields directly, these are supposed to be handled by the client!! - - @param src_tstamp_str: the str value containing the timestamp in the server timezone. - @param src_format: the format to use when parsing the server timestamp. - @param dst_format: the format to use when formatting the resulting timestamp for the local/client timezone. - @param dst_tz_name: name of the destination timezone (such as the 'tz' value of the client context) - @param ignore_unparsable_time: if True, return False if src_tstamp_str cannot be parsed - using src_format or formatted using dst_format. - - @return: local/client formatted timestamp, expressed in the local/client timezone if possible - and if tz_offset is true, or src_tstamp_str if timezone offset could not be determined. - """ - if not src_tstamp_str: - return False - - res = src_tstamp_str - if src_format and dst_format: - # find out server timezone - server_tz = get_server_timezone() - try: - # dt_value needs to be a datetime.datetime object (so no time.struct_time or mx.DateTime.DateTime here!) - dt_value = datetime.strptime(src_tstamp_str, src_format) - if tz_offset and dst_tz_name: - try: - import pytz - src_tz = pytz.timezone(server_tz) - dst_tz = pytz.timezone(dst_tz_name) - src_dt = src_tz.localize(dt_value, is_dst=True) - dt_value = src_dt.astimezone(dst_tz) - except Exception: - pass - res = dt_value.strftime(dst_format) - except Exception: - # Normal ways to end up here are if strptime or strftime failed - if not ignore_unparsable_time: - return False - return res - - -def split_every(n, iterable, piece_maker=tuple): - """Splits an iterable into length-n pieces. The last piece will be shorter - if ``n`` does not evenly divide the iterable length. - @param ``piece_maker``: function to build the pieces - from the slices (tuple,list,...) - """ - iterator = iter(iterable) - piece = piece_maker(islice(iterator, n)) - while piece: - yield piece - piece = piece_maker(islice(iterator, n)) - -if __name__ == '__main__': - import doctest - doctest.testmod() - -class upload_data_thread(threading.Thread): - def __init__(self, email, data, type): - self.args = [('email',email),('type',type),('data',data)] - super(upload_data_thread,self).__init__() - def run(self): - try: - import urllib - args = urllib.urlencode(self.args) - fp = urllib.urlopen('http://www.openerp.com/scripts/survey.php', args) - fp.read() - fp.close() - except Exception: - pass - -def upload_data(email, data, type='SURVEY'): - a = upload_data_thread(email, data, type) - a.start() - return True - - -# port of python 2.6's attrgetter with support for dotted notation -def resolve_attr(obj, attr): - for name in attr.split("."): - obj = getattr(obj, name) - return obj - -def attrgetter(*items): - if len(items) == 1: - attr = items[0] - def g(obj): - return resolve_attr(obj, attr) - else: - def g(obj): - return tuple(resolve_attr(obj, attr) for attr in items) - return g - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/bin/tools/osutil.py b/bin/tools/osutil.py deleted file mode 100644 index 51540ae28c7..00000000000 --- a/bin/tools/osutil.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -""" -Some functions related to the os and os.path module -""" - -import os -from os.path import join as opj - -def listdir(dir, recursive=False): - """Allow to recursively get the file listing""" - dir = os.path.normpath(dir) - if not recursive: - return os.listdir(dir) - - res = [] - for root, dirs, files in walksymlinks(dir): - root = root[len(dir)+1:] - res.extend([opj(root, f) for f in files]) - return res - -def walksymlinks(top, topdown=True, onerror=None): - """ - same as os.walk but follow symlinks - attention: all symlinks are walked before all normals directories - """ - for dirpath, dirnames, filenames in os.walk(top, topdown, onerror): - if topdown: - yield dirpath, dirnames, filenames - - symlinks = filter(lambda dirname: os.path.islink(os.path.join(dirpath, dirname)), dirnames) - for s in symlinks: - for x in walksymlinks(os.path.join(dirpath, s), topdown, onerror): - yield x - - if not topdown: - yield dirpath, dirnames, filenames - - - -if __name__ == '__main__': - from pprint import pprint as pp - pp(listdir('../report', True)) diff --git a/bin/tools/parse_version.py b/bin/tools/parse_version.py deleted file mode 100644 index c743a9bb239..00000000000 --- a/bin/tools/parse_version.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -## this functions are taken from the setuptools package (version 0.6c8) -## http://peak.telecommunity.com/DevCenter/PkgResources#parsing-utilities - -import re - -component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE) -replace = {'pre':'c', 'preview':'c','-':'final-','_':'final-','rc':'c','dev':'@'}.get - -def _parse_version_parts(s): - for part in component_re.split(s): - part = replace(part,part) - if not part or part=='.': - continue - if part[:1] in '0123456789': - yield part.zfill(8) # pad for numeric comparison - else: - yield '*'+part - - yield '*final' # ensure that alpha/beta/candidate are before final - -def parse_version(s): - """Convert a version string to a chronologically-sortable key - - This is a rough cross between distutils' StrictVersion and LooseVersion; - if you give it versions that would work with StrictVersion, then it behaves - the same; otherwise it acts like a slightly-smarter LooseVersion. It is - *possible* to create pathological version coding schemes that will fool - this parser, but they should be very rare in practice. - - The returned value will be a tuple of strings. Numeric portions of the - version are padded to 8 digits so they will compare numerically, but - without relying on how numbers compare relative to strings. Dots are - dropped, but dashes are retained. Trailing zeros between alpha segments - or dashes are suppressed, so that e.g. "2.4.0" is considered the same as - "2.4". Alphanumeric parts are lower-cased. - - The algorithm assumes that strings like "-" and any alpha string that - alphabetically follows "final" represents a "patch level". So, "2.4-1" - is assumed to be a branch or patch of "2.4", and therefore "2.4.1" is - considered newer than "2.4-1", whic in turn is newer than "2.4". - - Strings like "a", "b", "c", "alpha", "beta", "candidate" and so on (that - come before "final" alphabetically) are assumed to be pre-release versions, - so that the version "2.4" is considered newer than "2.4a1". - - Finally, to handle miscellaneous cases, the strings "pre", "preview", and - "rc" are treated as if they were "c", i.e. as though they were release - candidates, and therefore are not as new as a version string that does not - contain them. - """ - parts = [] - for part in _parse_version_parts((s or '0.1').lower()): - if part.startswith('*'): - if part<'*final': # remove '-' before a prerelease tag - while parts and parts[-1]=='*final-': parts.pop() - # remove trailing zeros from each series of numeric parts - while parts and parts[-1]=='00000000': - parts.pop() - parts.append(part) - return tuple(parts) - -if __name__ == '__main__': - - def cmp(a, b): - msg = '%s < %s == %s' % (a, b, a < b) - assert a < b, msg - return b - - def chk(lst, verbose=False): - pvs = [] - for v in lst: - pv = parse_version(v) - pvs.append(pv) - if verbose: - print v, pv - reduce(cmp, pvs) - - chk(('0', '4.2', '4.2.3.4', '5.0.0-alpha', '5.0.0-rc1', '5.0.0-rc1.1', '5.0.0_rc2', '5.0.0_rc3', '5.0.0'), False) - chk(('5.0.0-0_rc3', '5.0.0-1dev', '5.0.0-1'), False) - diff --git a/bin/tools/pdf_utils.py b/bin/tools/pdf_utils.py deleted file mode 100644 index 7d9bf3af700..00000000000 --- a/bin/tools/pdf_utils.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -""" Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE). - http://www.logilab.fr/ -- mailto:contact@logilab.fr - -manipulate pdf and fdf files. pdftk recommended. - -Notes regarding pdftk, pdf forms and fdf files (form definition file) -fields names can be extracted with: - pdftk orig.pdf generate_fdf output truc.fdf -to merge fdf and pdf: - pdftk orig.pdf fill_form test.fdf output result.pdf [flatten] -without flatten, one could further edit the resulting form. -with flatten, everything is turned into text. -""" - -from __future__ import with_statement - -import os -import tempfile - - -HEAD="""%FDF-1.2 -%\xE2\xE3\xCF\xD3 -1 0 obj -<< -/FDF -<< -/Fields [ -""" - -TAIL="""] ->> ->> -endobj -trailer - -<< -/Root 1 0 R ->> -%%EOF -""" - -def output_field(f): - return "\xfe\xff" + "".join( [ "\x00"+c for c in f ] ) - -def extract_keys(lines): - keys = [] - for line in lines: - if line.startswith('/V'): - pass #print 'value',line - elif line.startswith('/T'): - key = line[7:-2] - key = ''.join(key.split('\x00')) - keys.append( key ) - return keys - -def write_field(out, key, value): - out.write("<<\n") - if value: - out.write("/V (%s)\n" %value) - else: - out.write("/V /\n") - out.write("/T (%s)\n" % output_field(key) ) - out.write(">> \n") - -def write_fields(out, fields): - out.write(HEAD) - for key in fields: - value = fields[key] - write_field(out, key, value) -# write_field(out, key+"a", value) # pour copie-carbone sur autres pages - out.write(TAIL) - -def extract_keys_from_pdf(filename): - # what about using 'pdftk filename dump_data_fields' and parsing the output ? - tmp_file = tempfile.mkstemp(".fdf")[1] - try: - os.system('pdftk %s generate_fdf output \"%s\"' % (filename, tmp_file)) - with open(tmp_file, "r") as ofile: - lines = ofile.readlines() - finally: - try: - os.remove(tmp_file) - except Exception: - pass # nothing to do - return extract_keys(lines) - - -def fill_pdf(infile, outfile, fields): - tmp_file = tempfile.mkstemp(".fdf")[1] - try: - with open(tmp_file, "w") as ofile: - write_fields(ofile, fields) - os.system('pdftk %s fill_form \"%s\" output %s flatten' % (infile, tmp_file, outfile)) - finally: - try: - os.remove(tmp_file) - except Exception: - pass # nothing to do - -def testfill_pdf(infile, outfile): - keys = extract_keys_from_pdf(infile) - fields = [] - for key in keys: - fields.append( (key, key, '') ) - fill_pdf(infile, outfile, fields) - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/bin/tools/safe_eval.py b/bin/tools/safe_eval.py deleted file mode 100644 index 740b426f050..00000000000 --- a/bin/tools/safe_eval.py +++ /dev/null @@ -1,283 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# Copyright (C) 2004-2010 OpenERP s.a. (<http://www.openerp.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -""" -safe_eval module - methods intended to provide more restricted alternatives to - evaluate simple and/or untrusted code. - -Methods in this module are typically used as alternatives to eval() to parse -OpenERP domain strings, conditions and expressions, mostly based on locals -condition/math builtins. -""" - -# Module partially ripped from/inspired by several different sources: -# - http://code.activestate.com/recipes/286134/ -# - safe_eval in lp:~xrg/openobject-server/optimize-5.0 -# - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad -# - python 2.6's ast.literal_eval - -from opcode import HAVE_ARGUMENT, opmap, opname -from types import CodeType -import logging -import os - -__all__ = ['test_expr', 'literal_eval', 'safe_eval', 'const_eval', 'ext_eval' ] - -_ALLOWED_MODULES = ['_strptime'] - -_CONST_OPCODES = set(opmap[x] for x in [ - 'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP','POP_BLOCK','SETUP_LOOP', - 'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE', - 'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR'] if x in opmap) - -_EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [ - 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT', - 'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY', - 'BINARY_DIVIDE', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE', - 'BINARY_MODULO', 'BINARY_ADD', 'BINARY_SUBTRACT', 'BINARY_SUBSCR', - 'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR', - 'BINARY_OR'] if x in opmap)) - -_SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [ - 'STORE_MAP', 'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR', - 'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'DELETE_NAME', - 'JUMP_FORWARD', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_ABSOLUTE', - 'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3', - # New in Python 2.7 - http://bugs.python.org/issue4715 : - 'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE', - 'POP_JUMP_IF_TRUE' - ] if x in opmap)) - -_logger = logging.getLogger('safe_eval') - -def _get_opcodes(codeobj): - """_get_opcodes(codeobj) -> [opcodes] - - Extract the actual opcodes as a list from a code object - - >>> c = compile("[1 + 2, (1,2)]", "", "eval") - >>> _get_opcodes(c) - [100, 100, 23, 100, 100, 102, 103, 83] - """ - i = 0 - opcodes = [] - byte_codes = codeobj.co_code - while i < len(byte_codes): - code = ord(byte_codes[i]) - opcodes.append(code) - if code >= HAVE_ARGUMENT: - i += 3 - else: - i += 1 - return opcodes - -def test_expr(expr, allowed_codes, mode="eval"): - """test_expr(expression, allowed_codes[, mode]) -> code_object - - Test that the expression contains only the allowed opcodes. - If the expression is valid and contains only allowed codes, - return the compiled code object. - Otherwise raise a ValueError, a Syntax Error or TypeError accordingly. - """ - try: - if mode == 'eval': - # eval() does not like leading/trailing whitespace - expr = expr.strip() - code_obj = compile(expr, "", mode) - except (SyntaxError, TypeError): - _logger.debug('Invalid eval expression', exc_info=True) - raise - except Exception: - _logger.debug('Disallowed or invalid eval expression', exc_info=True) - raise ValueError("%s is not a valid expression" % expr) - for code in _get_opcodes(code_obj): - if code not in allowed_codes: - raise ValueError("opcode %s not allowed (%r)" % (opname[code], expr)) - return code_obj - - -def const_eval(expr): - """const_eval(expression) -> value - - Safe Python constant evaluation - - Evaluates a string that contains an expression describing - a Python constant. Strings that are not valid Python expressions - or that contain other code besides the constant raise ValueError. - - >>> const_eval("10") - 10 - >>> const_eval("[1,2, (3,4), {'foo':'bar'}]") - [1, 2, (3, 4), {'foo': 'bar'}] - >>> const_eval("1+2") - Traceback (most recent call last): - ... - ValueError: opcode BINARY_ADD not allowed - """ - c = test_expr(expr, _CONST_OPCODES) - return eval(c) - -def expr_eval(expr): - """expr_eval(expression) -> value - - Restricted Python expression evaluation - - Evaluates a string that contains an expression that only - uses Python constants. This can be used to e.g. evaluate - a numerical expression from an untrusted source. - - >>> expr_eval("1+2") - 3 - >>> expr_eval("[1,2]*2") - [1, 2, 1, 2] - >>> expr_eval("__import__('sys').modules") - Traceback (most recent call last): - ... - ValueError: opcode LOAD_NAME not allowed - """ - c = test_expr(expr, _EXPR_OPCODES) - return eval(c) - - -# Port of Python 2.6's ast.literal_eval for use under Python 2.5 -SAFE_CONSTANTS = {'None': None, 'True': True, 'False': False} - -try: - # first, try importing directly - from ast import literal_eval -except ImportError: - import _ast as ast - - def _convert(node): - if isinstance(node, ast.Str): - return node.s - elif isinstance(node, ast.Num): - return node.n - elif isinstance(node, ast.Tuple): - return tuple(map(_convert, node.elts)) - elif isinstance(node, ast.List): - return list(map(_convert, node.elts)) - elif isinstance(node, ast.Dict): - return dict((_convert(k), _convert(v)) for k, v - in zip(node.keys, node.values)) - elif isinstance(node, ast.Name): - if node.id in SAFE_CONSTANTS: - return SAFE_CONSTANTS[node.id] - raise ValueError('malformed or disallowed expression') - - def parse(expr, filename='<unknown>', mode='eval'): - """parse(source[, filename], mode]] -> code object - Parse an expression into an AST node. - Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST). - """ - return compile(expr, filename, mode, ast.PyCF_ONLY_AST) - - def literal_eval(node_or_string): - """literal_eval(expression) -> value - Safely evaluate an expression node or a string containing a Python - expression. The string or node provided may only consist of the - following Python literal structures: strings, numbers, tuples, - lists, dicts, booleans, and None. - - >>> literal_eval('[1,True,"spam"]') - [1, True, 'spam'] - - >>> literal_eval('1+3') - Traceback (most recent call last): - ... - ValueError: malformed or disallowed expression - """ - if isinstance(node_or_string, basestring): - node_or_string = parse(node_or_string) - if isinstance(node_or_string, ast.Expression): - node_or_string = node_or_string.body - return _convert(node_or_string) - -def _import(name, globals={}, locals={}, fromlist=[], level=-1): - if name in _ALLOWED_MODULES: - return __import__(name, globals, locals, level) - raise ImportError(name) - -def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=False): - """safe_eval(expression[, globals[, locals[, mode[, nocopy]]]]) -> result - - System-restricted Python expression evaluation - - Evaluates a string that contains an expression that mostly - uses Python constants, arithmetic expressions and the - objects directly provided in context. - - This can be used to e.g. evaluate - an OpenERP domain expression from an untrusted source. - - Throws TypeError, SyntaxError or ValueError (not allowed) accordingly. - - >>> safe_eval("__import__('sys').modules") - Traceback (most recent call last): - ... - ValueError: opcode LOAD_NAME not allowed - - """ - if isinstance(expr, CodeType): - raise ValueError("safe_eval does not allow direct evaluation of code objects.") - - if '__subclasses__' in expr: - raise ValueError('expression not allowed (__subclasses__)') - - if globals_dict is None: - globals_dict = {} - - # prevent altering the globals/locals from within the sandbox - # by taking a copy. - if not nocopy: - # isinstance() does not work below, we want *exactly* the dict class - if (globals_dict is not None and type(globals_dict) is not dict) \ - or (locals_dict is not None and type(locals_dict) is not dict): - logging.getLogger('safe_eval').warning('Looks like you are trying to pass a dynamic environment,"\ - "you should probably pass nocopy=True to safe_eval()') - - globals_dict = dict(globals_dict) - if locals_dict is not None: - locals_dict = dict(locals_dict) - - globals_dict.update( - __builtins__ = { - '__import__': _import, - 'True': True, - 'False': False, - 'None': None, - 'str': str, - 'globals': locals, - 'locals': locals, - 'bool': bool, - 'dict': dict, - 'list': list, - 'tuple': tuple, - 'map': map, - 'abs': abs, - 'reduce': reduce, - 'filter': filter, - 'round': round, - 'len': len, - 'set' : set - } - ) - return eval(test_expr(expr,_SAFE_OPCODES, mode=mode), globals_dict, locals_dict) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/tools/sql.py b/bin/tools/sql.py deleted file mode 100644 index 445eebb0bb8..00000000000 --- a/bin/tools/sql.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -def drop_view_if_exists(cr, viewname): - cr.execute("select count(1) from pg_class where relkind=%s and relname=%s", ('v', viewname,)) - if cr.fetchone()[0]: - cr.execute("DROP view %s" % (viewname,)) - cr.commit() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/tools/test_reports.py b/bin/tools/test_reports.py deleted file mode 100644 index 6e4b08ece2c..00000000000 --- a/bin/tools/test_reports.py +++ /dev/null @@ -1,309 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -""" Helper functions for reports testing. - - Please /do not/ import this file by default, but only explicitly call it - through the code of yaml tests. -""" - -import netsvc -import tools -import logging -import pooler -from tools.safe_eval import safe_eval -from subprocess import Popen, PIPE -import os -import tempfile - -def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None): - """ Try to render a report <rname> with contents of ids - - This function should also check for common pitfalls of reports. - """ - if our_module: - log = logging.getLogger('tests.%s' % our_module) - else: - log = logging.getLogger('tools.test_reports') - if data is None: - data = {} - if context is None: - context = {} - if rname.startswith('report.'): - rname_s = rname[7:] - else: - rname_s = rname - log.log(netsvc.logging.TEST, " - Trying %s.create(%r)", rname, ids) - res = netsvc.LocalService(rname).create(cr, uid, ids, data, context) - if not isinstance(res, tuple): - raise RuntimeError("Result of %s.create() should be a (data,format) tuple, now it is a %s" % \ - (rname, type(res))) - (res_data, res_format) = res - - if not res_data: - raise ValueError("Report %s produced an empty result!" % rname) - - if tools.config['test_report_directory']: - file(os.path.join(tools.config['test_report_directory'], rname+ '.'+res_format), 'wb+').write(res_data) - - log.debug("Have a %s report for %s, will examine it", res_format, rname) - if res_format == 'pdf': - if res_data[:5] != '%PDF-': - raise ValueError("Report %s produced a non-pdf header, %r" % (rname, res_data[:10])) - - res_text = False - try: - fd, rfname = tempfile.mkstemp(suffix=res_format) - os.write(fd, res_data) - os.close(fd) - - fp = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', rfname, '-'], shell=False, stdout=PIPE).stdout - res_text = tools.ustr(fp.read()) - os.unlink(rfname) - except Exception: - log.debug("Unable to parse PDF report: install pdftotext to perform automated tests.") - - if res_text is not False: - for line in res_text.split('\n'): - if ('[[' in line) or ('[ [' in line): - log.error("Report %s may have bad expression near: \"%s\".", rname, line[80:]) - # TODO more checks, what else can be a sign of a faulty report? - elif res_format == 'foobar': - # TODO - pass - else: - log.warning("Report %s produced a \"%s\" chunk, cannot examine it", rname, res_format) - return False - - log.log(netsvc.logging.TEST, " + Report %s produced correctly.", rname) - return True - -def try_report_action(cr, uid, action_id, active_model=None, active_ids=None, - wiz_data=None, wiz_buttons=None, - context=None, our_module=None): - """Take an ir.action.act_window and follow it until a report is produced - - :param action_id: the integer id of an action, or a reference to xml id - of the act_window (can search [our_module.]+xml_id - :param active_model, active_ids: call the action as if it had been launched - from that model+ids (tree/form view action) - :param wiz_data: a dictionary of values to use in the wizard, if needed. - They will override (or complete) the default values of the - wizard form. - :param wiz_buttons: a list of button names, or button icon strings, which - should be preferred to press during the wizard. - Eg. 'OK' or 'gtk-print' - :param our_module: the name of the calling module (string), like 'account' - """ - - if not our_module and isinstance(action_id, basestring): - if '.' in action_id: - our_module = action_id.split('.', 1)[0] - - if context is None: - context = {} - else: - context = context.copy() # keep it local - # TODO context fill-up - - pool = pooler.get_pool(cr.dbname) - if our_module: - log = logging.getLogger('tests.%s' % our_module) - else: - log = logging.getLogger('tools.test_reports') - - def log_test(msg, *args): - log.log(netsvc.logging.TEST, " - " + msg, *args) - - datas = {} - if active_model: - datas['model'] = active_model - if active_ids: - datas['ids'] = active_ids - - if not wiz_buttons: - wiz_buttons = [] - - if isinstance(action_id, basestring): - if '.' in action_id: - act_module, act_xmlid = action_id.split('.', 1) - else: - if not our_module: - raise ValueError('You cannot only specify action_id "%s" without a module name' % action_id) - act_module = our_module - act_xmlid = action_id - act_model, act_id = pool.get('ir.model.data').get_object_reference(cr, uid, act_module, act_xmlid) - else: - assert isinstance(action_id, (long, int)) - act_model = 'ir.action.act_window' # assume that - act_id = action_id - act_xmlid = '<%s>' % act_id - - def _exec_action(action, datas, context): - # taken from client/modules/action/main.py:84 _exec_action() - if isinstance(action, bool) or 'type' not in action: - return - # Updating the context : Adding the context of action in order to use it on Views called from buttons - if datas.get('id',False): - context.update( {'active_id': datas.get('id',False), 'active_ids': datas.get('ids',[]), 'active_model': datas.get('model',False)}) - context.update(safe_eval(action.get('context','{}'), context.copy())) - if action['type'] in ['ir.actions.act_window', 'ir.actions.submenu']: - for key in ('res_id', 'res_model', 'view_type', 'view_mode', - 'limit', 'auto_refresh', 'search_view', 'auto_search', 'search_view_id'): - datas[key] = action.get(key, datas.get(key, None)) - - view_id = False - if action.get('views', []): - if isinstance(action['views'],list): - view_id = action['views'][0][0] - datas['view_mode']= action['views'][0][1] - else: - if action.get('view_id', False): - view_id = action['view_id'][0] - elif action.get('view_id', False): - view_id = action['view_id'][0] - - assert datas['res_model'], "Cannot use the view without a model" - # Here, we have a view that we need to emulate - log_test("will emulate a %s view: %s#%s", - action['view_type'], datas['res_model'], view_id or '?') - - view_res = pool.get(datas['res_model']).fields_view_get(cr, uid, view_id, action['view_type'], context) - assert view_res and view_res.get('arch'), "Did not return any arch for the view" - view_data = {} - if view_res.get('fields',{}).keys(): - view_data = pool.get(datas['res_model']).default_get(cr, uid, view_res['fields'].keys(), context) - if datas.get('form'): - view_data.update(datas.get('form')) - if wiz_data: - view_data.update(wiz_data) - log.debug("View data is: %r", view_data) - - for fk, field in view_res.get('fields',{}).items(): - # Default fields returns list of int, while at create() - # we need to send a [(6,0,[int,..])] - if field['type'] in ('one2many', 'many2many') \ - and view_data.get(fk, False) \ - and isinstance(view_data[fk], list) \ - and not isinstance(view_data[fk][0], tuple) : - view_data[fk] = [(6, 0, view_data[fk])] - - action_name = action.get('name') - try: - from xml.dom import minidom - cancel_found = False - buttons = [] - dom_doc = minidom.parseString(view_res['arch']) - if not action_name: - action_name = dom_doc.documentElement.getAttribute('name') - - for button in dom_doc.getElementsByTagName('button'): - button_weight = 0 - if button.getAttribute('special') == 'cancel': - cancel_found = True - continue - if button.getAttribute('icon') == 'gtk-cancel': - cancel_found = True - continue - if button.getAttribute('default_focus') == '1': - button_weight += 20 - if button.getAttribute('string') in wiz_buttons: - button_weight += 30 - elif button.getAttribute('icon') in wiz_buttons: - button_weight += 10 - string = button.getAttribute('string') or '?%s' % len(buttons) - - buttons.append( { 'name': button.getAttribute('name'), - 'string': string, - 'type': button.getAttribute('type'), - 'weight': button_weight, - }) - except Exception, e: - log.warning("Cannot resolve the view arch and locate the buttons!", exc_info=True) - raise AssertionError(e.args[0]) - - if not datas['res_id']: - # it is probably an orm_memory object, we need to create - # an instance - datas['res_id'] = pool.get(datas['res_model']).create(cr, uid, view_data, context) - - if not buttons: - raise AssertionError("view form doesn't have any buttons to press!") - - buttons.sort(key=lambda b: b['weight']) - log.debug('Buttons are: %s', ', '.join([ '%s: %d' % (b['string'], b['weight']) for b in buttons])) - - res = None - while buttons and not res: - b = buttons.pop() - log_test("in the \"%s\" form, I will press the \"%s\" button.", action_name, b['string']) - if not b['type']: - log_test("the \"%s\" button has no type, cannot use it", b['string']) - continue - if b['type'] == 'object': - #there we are! press the button! - fn = getattr(pool.get(datas['res_model']), b['name']) - if not fn: - log.error("The %s model doesn't have a %s attribute!", datas['res_model'], b['name']) - continue - res = fn(cr, uid, [datas['res_id'],], context) - break - else: - log.warning("in the \"%s\" form, the \"%s\" button has unknown type %s", - action_name, b['string'], b['type']) - return res - - elif action['type']=='ir.actions.report.xml': - if 'window' in datas: - del datas['window'] - if not datas: - datas = action.get('datas',{}) - datas = datas.copy() - ids = datas.get('ids') - if 'ids' in datas: - del datas['ids'] - res = try_report(cr, uid, 'report.'+action['report_name'], ids, datas, context, our_module=our_module) - return res - else: - raise Exception("Cannot handle action of type %s" % act_model) - - log_test("will be using %s action %s #%d", act_model, act_xmlid, act_id) - action = pool.get(act_model).read(cr, uid, act_id, context=context) - assert action, "Could not read action %s[%s]" %(act_model, act_id) - loop = 0 - while action: - loop += 1 - # This part tries to emulate the loop of the Gtk client - if loop > 100: - log.error("Passed %d loops, giving up", loop) - raise Exception("Too many loops at action") - log_test("it is an %s action at loop #%d", action.get('type', 'unknown'), loop) - result = _exec_action(action, datas, context) - if not isinstance(result, dict): - break - datas = result.get('datas', {}) - if datas: - del result['datas'] - action = result - - return True - -#eof diff --git a/bin/tools/threadinglocal.py b/bin/tools/threadinglocal.py deleted file mode 100644 index a68181c10e1..00000000000 --- a/bin/tools/threadinglocal.py +++ /dev/null @@ -1,269 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (c) 2004-2005, CherryPy Team (team@cherrypy.org) -#All rights reserved. -# -#Redistribution and use in source and binary forms, with or without -#modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# * Neither the name of the CherryPy Team nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -#DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -#FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -#DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -#SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -#OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# This is a backport of Python-2.4's threading.local() implementation - -"""Thread-local objects - -(Note that this module provides a Python version of thread - threading.local class. Depending on the version of Python you're - using, there may be a faster one available. You should always import - the local class from threading.) - -Thread-local objects support the management of thread-local data. -If you have data that you want to be local to a thread, simply create -a thread-local object and use its attributes: - - >>> mydata = local() - >>> mydata.number = 42 - >>> mydata.number - 42 - -You can also access the local-object's dictionary: - - >>> mydata.__dict__ - {'number': 42} - >>> mydata.__dict__.setdefault('widgets', []) - [] - >>> mydata.widgets - [] - -What's important about thread-local objects is that their data are -local to a thread. If we access the data in a different thread: - - >>> log = [] - >>> def f(): - ... items = mydata.__dict__.items() - ... items.sort() - ... log.append(items) - ... mydata.number = 11 - ... log.append(mydata.number) - - >>> import threading - >>> thread = threading.Thread(target=f) - >>> thread.start() - >>> thread.join() - >>> log - [[], 11] - -we get different data. Furthermore, changes made in the other thread -don't affect data seen in this thread: - - >>> mydata.number - 42 - -Of course, values you get from a local object, including a __dict__ -attribute, are for whatever thread was current at the time the -attribute was read. For that reason, you generally don't want to save -these values across threads, as they apply only to the thread they -came from. - -You can create custom local objects by subclassing the local class: - - >>> class MyLocal(local): - ... number = 2 - ... initialized = False - ... def __init__(self, **kw): - ... if self.initialized: - ... raise SystemError('__init__ called too many times') - ... self.initialized = True - ... self.__dict__.update(kw) - ... def squared(self): - ... return self.number ** 2 - -This can be useful to support default values, methods and -initialization. Note that if you define an __init__ method, it will be -called each time the local object is used in a separate thread. This -is necessary to initialize each thread's dictionary. - -Now if we create a local object: - - >>> mydata = MyLocal(color='red') - -Now we have a default number: - - >>> mydata.number - 2 - -an initial color: - - >>> mydata.color - 'red' - >>> del mydata.color - -And a method that operates on the data: - - >>> mydata.squared() - 4 - -As before, we can access the data in a separate thread: - - >>> log = [] - >>> thread = threading.Thread(target=f) - >>> thread.start() - >>> thread.join() - >>> log - [[('color', 'red'), ('initialized', True)], 11] - -without affecting this thread's data: - - >>> mydata.number - 2 - >>> mydata.color - Traceback (most recent call last): - ... - AttributeError: 'MyLocal' object has no attribute 'color' - -Note that subclasses can define slots, but they are not thread -local. They are shared across threads: - - >>> class MyLocal(local): - ... __slots__ = 'number' - - >>> mydata = MyLocal() - >>> mydata.number = 42 - >>> mydata.color = 'red' - -So, the separate thread: - - >>> thread = threading.Thread(target=f) - >>> thread.start() - >>> thread.join() - -affects what we see: - - >>> mydata.number - 11 - ->>> del mydata -""" - -# Threading import is at end - -class _localbase(object): - __slots__ = '_local__key', '_local__args', '_local__lock' - - def __new__(cls, *args, **kw): - self = object.__new__(cls) - key = '_local__key', 'thread.local.' + str(id(self)) - object.__setattr__(self, '_local__key', key) - object.__setattr__(self, '_local__args', (args, kw)) - object.__setattr__(self, '_local__lock', RLock()) - - if args or kw and (cls.__init__ is object.__init__): - raise TypeError("Initialization arguments are not supported") - - # We need to create the thread dict in anticipation of - # __init__ being called, to make sure we don't call it - # again ourselves. - dict = object.__getattribute__(self, '__dict__') - currentThread().__dict__[key] = dict - - return self - -def _patch(self): - key = object.__getattribute__(self, '_local__key') - d = currentThread().__dict__.get(key) - if d is None: - d = {} - currentThread().__dict__[key] = d - object.__setattr__(self, '__dict__', d) - - # we have a new instance dict, so call out __init__ if we have - # one - cls = type(self) - if cls.__init__ is not object.__init__: - args, kw = object.__getattribute__(self, '_local__args') - cls.__init__(self, *args, **kw) - else: - object.__setattr__(self, '__dict__', d) - -class local(_localbase): - - def __getattribute__(self, name): - lock = object.__getattribute__(self, '_local__lock') - lock.acquire() - try: - _patch(self) - return object.__getattribute__(self, name) - finally: - lock.release() - - def __setattr__(self, name, value): - lock = object.__getattribute__(self, '_local__lock') - lock.acquire() - try: - _patch(self) - return object.__setattr__(self, name, value) - finally: - lock.release() - - def __delattr__(self, name): - lock = object.__getattribute__(self, '_local__lock') - lock.acquire() - try: - _patch(self) - return object.__delattr__(self, name) - finally: - lock.release() - - - def __del__(): - threading_enumerate = enumerate - __getattribute__ = object.__getattribute__ - - def __del__(self): - key = __getattribute__(self, '_local__key') - - try: - threads = list(threading_enumerate()) - except: - # if enumerate fails, as it seems to do during - # shutdown, we'll skip cleanup under the assumption - # that there is nothing to clean up - return - - for thread in threads: - try: - __dict__ = thread.__dict__ - except AttributeError: - # Thread is dying, rest in peace - continue - - if key in __dict__: - try: - del __dict__[key] - except KeyError: - pass # didn't have anything in this thread - - return __del__ - __del__ = __del__() - -from threading import currentThread, enumerate, RLock - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/bin/tools/translate.py b/bin/tools/translate.py deleted file mode 100644 index 510ae71ab1f..00000000000 --- a/bin/tools/translate.py +++ /dev/null @@ -1,1005 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -import codecs -import csv -import fnmatch -import inspect -import itertools -import locale -import os -import pooler -import re -import logging -import tarfile -import tempfile -import threading -from os.path import join - -from datetime import datetime -from lxml import etree - -import tools -import netsvc -from tools.misc import UpdateableStr -from tools.misc import SKIPPED_ELEMENT_TYPES - -_LOCALE2WIN32 = { - 'af_ZA': 'Afrikaans_South Africa', - 'sq_AL': 'Albanian_Albania', - 'ar_SA': 'Arabic_Saudi Arabia', - 'eu_ES': 'Basque_Spain', - 'be_BY': 'Belarusian_Belarus', - 'bs_BA': 'Serbian (Latin)', - 'bg_BG': 'Bulgarian_Bulgaria', - 'ca_ES': 'Catalan_Spain', - 'hr_HR': 'Croatian_Croatia', - 'zh_CN': 'Chinese_China', - 'zh_TW': 'Chinese_Taiwan', - 'cs_CZ': 'Czech_Czech Republic', - 'da_DK': 'Danish_Denmark', - 'nl_NL': 'Dutch_Netherlands', - 'et_EE': 'Estonian_Estonia', - 'fa_IR': 'Farsi_Iran', - 'ph_PH': 'Filipino_Philippines', - 'fi_FI': 'Finnish_Finland', - 'fr_FR': 'French_France', - 'fr_BE': 'French_France', - 'fr_CH': 'French_France', - 'fr_CA': 'French_France', - 'ga': 'Scottish Gaelic', - 'gl_ES': 'Galician_Spain', - 'ka_GE': 'Georgian_Georgia', - 'de_DE': 'German_Germany', - 'el_GR': 'Greek_Greece', - 'gu': 'Gujarati_India', - 'he_IL': 'Hebrew_Israel', - 'hi_IN': 'Hindi', - 'hu': 'Hungarian_Hungary', - 'is_IS': 'Icelandic_Iceland', - 'id_ID': 'Indonesian_indonesia', - 'it_IT': 'Italian_Italy', - 'ja_JP': 'Japanese_Japan', - 'kn_IN': 'Kannada', - 'km_KH': 'Khmer', - 'ko_KR': 'Korean_Korea', - 'lo_LA': 'Lao_Laos', - 'lt_LT': 'Lithuanian_Lithuania', - 'lat': 'Latvian_Latvia', - 'ml_IN': 'Malayalam_India', - 'id_ID': 'Indonesian_indonesia', - 'mi_NZ': 'Maori', - 'mn': 'Cyrillic_Mongolian', - 'no_NO': 'Norwegian_Norway', - 'nn_NO': 'Norwegian-Nynorsk_Norway', - 'pl': 'Polish_Poland', - 'pt_PT': 'Portuguese_Portugal', - 'pt_BR': 'Portuguese_Brazil', - 'ro_RO': 'Romanian_Romania', - 'ru_RU': 'Russian_Russia', - 'mi_NZ': 'Maori', - 'sr_CS': 'Serbian (Cyrillic)_Serbia and Montenegro', - 'sk_SK': 'Slovak_Slovakia', - 'sl_SI': 'Slovenian_Slovenia', - #should find more specific locales for spanish countries, - #but better than nothing - 'es_AR': 'Spanish_Spain', - 'es_BO': 'Spanish_Spain', - 'es_CL': 'Spanish_Spain', - 'es_CO': 'Spanish_Spain', - 'es_CR': 'Spanish_Spain', - 'es_DO': 'Spanish_Spain', - 'es_EC': 'Spanish_Spain', - 'es_ES': 'Spanish_Spain', - 'es_GT': 'Spanish_Spain', - 'es_HN': 'Spanish_Spain', - 'es_MX': 'Spanish_Spain', - 'es_NI': 'Spanish_Spain', - 'es_PA': 'Spanish_Spain', - 'es_PE': 'Spanish_Spain', - 'es_PR': 'Spanish_Spain', - 'es_PY': 'Spanish_Spain', - 'es_SV': 'Spanish_Spain', - 'es_UY': 'Spanish_Spain', - 'es_VE': 'Spanish_Spain', - 'sv_SE': 'Swedish_Sweden', - 'ta_IN': 'English_Australia', - 'th_TH': 'Thai_Thailand', - 'mi_NZ': 'Maori', - 'tr_TR': 'Turkish_Turkey', - 'uk_UA': 'Ukrainian_Ukraine', - 'vi_VN': 'Vietnamese_Viet Nam', - 'tlh_TLH': 'Klingon', - -} - - -class UNIX_LINE_TERMINATOR(csv.excel): - lineterminator = '\n' - -csv.register_dialect("UNIX", UNIX_LINE_TERMINATOR) - -# -# Warning: better use self.pool.get('ir.translation')._get_source if you can -# -def translate(cr, name, source_type, lang, source=None): - if source and name: - cr.execute('select value from ir_translation where lang=%s and type=%s and name=%s and src=%s', (lang, source_type, str(name), source)) - elif name: - cr.execute('select value from ir_translation where lang=%s and type=%s and name=%s', (lang, source_type, str(name))) - elif source: - cr.execute('select value from ir_translation where lang=%s and type=%s and src=%s', (lang, source_type, source)) - res_trans = cr.fetchone() - res = res_trans and res_trans[0] or False - return res - -logger = logging.getLogger('translate') - -class GettextAlias(object): - - def _get_db(self): - # find current DB based on thread/worker db name (see netsvc) - db_name = getattr(threading.currentThread(), 'dbname', None) - if db_name: - return pooler.get_db_only(db_name) - - def _get_cr(self, frame): - is_new_cr = False - cr = frame.f_locals.get('cr', frame.f_locals.get('cursor')) - if not cr: - s = frame.f_locals.get('self', {}) - cr = getattr(s, 'cr', None) - if not cr: - db = self._get_db() - if db: - cr = db.cursor() - is_new_cr = True - return cr, is_new_cr - - def _get_lang(self, frame): - lang = None - ctx = frame.f_locals.get('context') - if not ctx: - kwargs = frame.f_locals.get('kwargs') - if kwargs is None: - args = frame.f_locals.get('args') - if args and isinstance(args, (list, tuple)) \ - and isinstance(args[-1], dict): - ctx = args[-1] - elif isinstance(kwargs, dict): - ctx = kwargs.get('context') - if ctx: - lang = ctx.get('lang') - if not lang: - s = frame.f_locals.get('self', {}) - c = getattr(s, 'localcontext', None) - if c: - lang = c.get('lang') - return lang - - def __call__(self, source): - res = source - cr = None - is_new_cr = False - try: - frame = inspect.currentframe() - if frame is None: - return source - frame = frame.f_back - if not frame: - return source - lang = self._get_lang(frame) - if lang: - cr, is_new_cr = self._get_cr(frame) - if cr: - # Try to use ir.translation to benefit from global cache if possible - pool = pooler.get_pool(cr.dbname) - res = pool.get('ir.translation')._get_source(cr, 1, None, ('code','sql_constraint'), lang, source) - else: - logger.debug('no context cursor detected, skipping translation for "%r"', source) - else: - logger.debug('no translation language detected, skipping translation for "%r" ', source) - except Exception: - logger.debug('translation went wrong for "%r", skipped', source) - # if so, double-check the root/base translations filenames - finally: - if cr and is_new_cr: - cr.close() - return res - -_ = GettextAlias() - - -def quote(s): - """Returns quoted PO term string, with special PO characters escaped""" - assert r"\n" not in s, "Translation terms may not include escaped newlines ('\\n'), please use only literal newlines! (in '%s')" % s - return '"%s"' % s.replace('\\','\\\\') \ - .replace('"','\\"') \ - .replace('\n', '\\n"\n"') - -re_escaped_char = re.compile(r"(\\.)") -re_escaped_replacements = {'n': '\n', } - -def _sub_replacement(match_obj): - return re_escaped_replacements.get(match_obj.group(1)[1], match_obj.group(1)[1]) - -def unquote(str): - """Returns unquoted PO term string, with special PO characters unescaped""" - return re_escaped_char.sub(_sub_replacement, str[1:-1]) - -# class to handle po files -class TinyPoFile(object): - def __init__(self, buffer): - self.logger = logging.getLogger('i18n') - self.buffer = buffer - - def warn(self, msg, *args): - self.logger.warning(msg, *args) - - def __iter__(self): - self.buffer.seek(0) - self.lines = self._get_lines() - self.lines_count = len(self.lines); - - self.first = True - self.tnrs= [] - return self - - def _get_lines(self): - lines = self.buffer.readlines() - # remove the BOM (Byte Order Mark): - if len(lines): - lines[0] = unicode(lines[0], 'utf8').lstrip(unicode( codecs.BOM_UTF8, "utf8")) - - lines.append('') # ensure that the file ends with at least an empty line - return lines - - def cur_line(self): - return (self.lines_count - len(self.lines)) - - def next(self): - type = name = res_id = source = trad = None - - if self.tnrs: - type, name, res_id, source, trad = self.tnrs.pop(0) - if not res_id: - res_id = '0' - else: - tmp_tnrs = [] - line = None - fuzzy = False - while (not line): - if 0 == len(self.lines): - raise StopIteration() - line = self.lines.pop(0).strip() - while line.startswith('#'): - if line.startswith('#~ '): - break - if line.startswith('#:'): - if ' ' in line[2:].strip(): - for lpart in line[2:].strip().split(' '): - tmp_tnrs.append(lpart.strip().split(':',2)) - else: - tmp_tnrs.append( line[2:].strip().split(':',2) ) - elif line.startswith('#,') and (line[2:].strip() == 'fuzzy'): - fuzzy = True - line = self.lines.pop(0).strip() - while not line: - # allow empty lines between comments and msgid - line = self.lines.pop(0).strip() - if line.startswith('#~ '): - while line.startswith('#~ ') or not line.strip(): - if 0 == len(self.lines): - raise StopIteration() - line = self.lines.pop(0) - # This has been a deprecated entry, don't return anything - return self.next() - - if not line.startswith('msgid'): - raise Exception("malformed file: bad line: %s" % line) - source = unquote(line[6:]) - line = self.lines.pop(0).strip() - if not source and self.first: - # if the source is "" and it's the first msgid, it's the special - # msgstr with the informations about the traduction and the - # traductor; we skip it - self.tnrs = [] - while line: - line = self.lines.pop(0).strip() - return self.next() - - while not line.startswith('msgstr'): - if not line: - raise Exception('malformed file at %d'% self.cur_line()) - source += unquote(line) - line = self.lines.pop(0).strip() - - trad = unquote(line[7:]) - line = self.lines.pop(0).strip() - while line: - trad += unquote(line) - line = self.lines.pop(0).strip() - - if tmp_tnrs and not fuzzy: - type, name, res_id = tmp_tnrs.pop(0) - for t, n, r in tmp_tnrs: - self.tnrs.append((t, n, r, source, trad)) - - self.first = False - - if name is None: - if not fuzzy: - self.warn('Missing "#:" formated comment at line %d for the following source:\n\t%s', - self.cur_line(), source[:30]) - return self.next() - return type, name, res_id, source, trad - - def write_infos(self, modules): - import release - self.buffer.write("# Translation of %(project)s.\n" \ - "# This file contains the translation of the following modules:\n" \ - "%(modules)s" \ - "#\n" \ - "msgid \"\"\n" \ - "msgstr \"\"\n" \ - '''"Project-Id-Version: %(project)s %(version)s\\n"\n''' \ - '''"Report-Msgid-Bugs-To: %(bugmail)s\\n"\n''' \ - '''"POT-Creation-Date: %(now)s\\n"\n''' \ - '''"PO-Revision-Date: %(now)s\\n"\n''' \ - '''"Last-Translator: <>\\n"\n''' \ - '''"Language-Team: \\n"\n''' \ - '''"MIME-Version: 1.0\\n"\n''' \ - '''"Content-Type: text/plain; charset=UTF-8\\n"\n''' \ - '''"Content-Transfer-Encoding: \\n"\n''' \ - '''"Plural-Forms: \\n"\n''' \ - "\n" - - % { 'project': release.description, - 'version': release.version, - 'modules': reduce(lambda s, m: s + "#\t* %s\n" % m, modules, ""), - 'bugmail': release.support_email, - 'now': datetime.utcnow().strftime('%Y-%m-%d %H:%M')+"+0000", - } - ) - - def write(self, modules, tnrs, source, trad): - - plurial = len(modules) > 1 and 's' or '' - self.buffer.write("#. module%s: %s\n" % (plurial, ', '.join(modules))) - - - code = False - for typy, name, res_id in tnrs: - self.buffer.write("#: %s:%s:%s\n" % (typy, name, res_id)) - if typy == 'code': - code = True - - if code: - # only strings in python code are python formated - self.buffer.write("#, python-format\n") - - if not isinstance(trad, unicode): - trad = unicode(trad, 'utf8') - if not isinstance(source, unicode): - source = unicode(source, 'utf8') - - msg = "msgid %s\n" \ - "msgstr %s\n\n" \ - % (quote(source), quote(trad)) - self.buffer.write(msg.encode('utf8')) - - -# Methods to export the translation file - -def trans_export(lang, modules, buffer, format, cr): - - def _process(format, modules, rows, buffer, lang, newlang): - if format == 'csv': - writer=csv.writer(buffer, 'UNIX') - for row in rows: - writer.writerow(row) - elif format == 'po': - rows.pop(0) - writer = tools.TinyPoFile(buffer) - writer.write_infos(modules) - - # we now group the translations by source. That means one translation per source. - grouped_rows = {} - for module, type, name, res_id, src, trad in rows: - row = grouped_rows.setdefault(src, {}) - row.setdefault('modules', set()).add(module) - if ('translation' not in row) or (not row['translation']): - row['translation'] = trad - row.setdefault('tnrs', []).append((type, name, res_id)) - - for src, row in grouped_rows.items(): - writer.write(row['modules'], row['tnrs'], src, row['translation']) - - elif format == 'tgz': - rows.pop(0) - rows_by_module = {} - for row in rows: - module = row[0] - # first row is the "header", as in csv, it will be popped - rows_by_module.setdefault(module, [['module', 'type', 'name', 'res_id', 'src', ''],]) - rows_by_module[module].append(row) - - tmpdir = tempfile.mkdtemp() - for mod, modrows in rows_by_module.items(): - tmpmoddir = join(tmpdir, mod, 'i18n') - os.makedirs(tmpmoddir) - pofilename = (newlang and mod or lang) + ".po" + (newlang and 't' or '') - buf = file(join(tmpmoddir, pofilename), 'w') - _process('po', [mod], modrows, buf, lang, newlang) - buf.close() - - tar = tarfile.open(fileobj=buffer, mode='w|gz') - tar.add(tmpdir, '') - tar.close() - - else: - raise Exception(_('Bad file format')) - - newlang = not bool(lang) - if newlang: - lang = 'en_US' - trans = trans_generate(lang, modules, cr) - if newlang and format!='csv': - for trx in trans: - trx[-1] = '' - modules = set([t[0] for t in trans[1:]]) - _process(format, modules, trans, buffer, lang, newlang) - del trans - -def trans_parse_xsl(de): - res = [] - for n in de: - if n.get("t"): - for m in n: - if isinstance(m, SKIPPED_ELEMENT_TYPES) or not m.text: - continue - l = m.text.strip().replace('\n',' ') - if len(l): - res.append(l.encode("utf8")) - res.extend(trans_parse_xsl(n)) - return res - -def trans_parse_rml(de): - res = [] - for n in de: - for m in n: - if isinstance(m, SKIPPED_ELEMENT_TYPES) or not m.text: - continue - string_list = [s.replace('\n', ' ').strip() for s in re.split('\[\[.+?\]\]', m.text)] - for s in string_list: - if s: - res.append(s.encode("utf8")) - res.extend(trans_parse_rml(n)) - return res - -def trans_parse_view(de): - res = [] - if de.tag == 'attribute' and de.get("name") == 'string': - if de.text: - res.append(de.text.encode("utf8")) - if de.get("string"): - res.append(de.get('string').encode("utf8")) - if de.get("sum"): - res.append(de.get('sum').encode("utf8")) - if de.get("confirm"): - res.append(de.get('confirm').encode("utf8")) - for n in de: - res.extend(trans_parse_view(n)) - return res - -# tests whether an object is in a list of modules -def in_modules(object_name, modules): - if 'all' in modules: - return True - - module_dict = { - 'ir': 'base', - 'res': 'base', - 'workflow': 'base', - } - module = object_name.split('.')[0] - module = module_dict.get(module, module) - return module in modules - -def trans_generate(lang, modules, cr): - logger = logging.getLogger('i18n') - dbname = cr.dbname - - pool = pooler.get_pool(dbname) - trans_obj = pool.get('ir.translation') - model_data_obj = pool.get('ir.model.data') - uid = 1 - l = pool.obj_pool.items() - l.sort() - - query = 'SELECT name, model, res_id, module' \ - ' FROM ir_model_data' - - query_models = """SELECT m.id, m.model, imd.module - FROM ir_model AS m, ir_model_data AS imd - WHERE m.id = imd.res_id AND imd.model = 'ir.model' """ - - if 'all_installed' in modules: - query += ' WHERE module IN ( SELECT name FROM ir_module_module WHERE state = \'installed\') ' - query_models += " AND imd.module in ( SELECT name FROM ir_module_module WHERE state = 'installed') " - query_param = None - if 'all' not in modules: - query += ' WHERE module IN %s' - query_models += ' AND imd.module in %s' - query_param = (tuple(modules),) - query += ' ORDER BY module, model, name' - query_models += ' ORDER BY module, model' - - cr.execute(query, query_param) - - _to_translate = [] - def push_translation(module, type, name, id, source): - tuple = (module, source, name, id, type) - if source and tuple not in _to_translate: - _to_translate.append(tuple) - - def encode(s): - if isinstance(s, unicode): - return s.encode('utf8') - return s - - for (xml_name,model,res_id,module) in cr.fetchall(): - module = encode(module) - model = encode(model) - xml_name = "%s.%s" % (module, encode(xml_name)) - - if not pool.get(model): - logger.error("Unable to find object %r", model) - continue - - exists = pool.get(model).exists(cr, uid, res_id) - if not exists: - logger.warning("Unable to find object %r with id %d", model, res_id) - continue - obj = pool.get(model).browse(cr, uid, res_id) - - if model=='ir.ui.view': - d = etree.XML(encode(obj.arch)) - for t in trans_parse_view(d): - push_translation(module, 'view', encode(obj.model), 0, t) - elif model=='ir.actions.wizard': - service_name = 'wizard.'+encode(obj.wiz_name) - if netsvc.Service._services.get(service_name): - obj2 = netsvc.Service._services[service_name] - for state_name, state_def in obj2.states.iteritems(): - if 'result' in state_def: - result = state_def['result'] - if result['type'] != 'form': - continue - name = "%s,%s" % (encode(obj.wiz_name), state_name) - - def_params = { - 'string': ('wizard_field', lambda s: [encode(s)]), - 'selection': ('selection', lambda s: [encode(e[1]) for e in ((not callable(s)) and s or [])]), - 'help': ('help', lambda s: [encode(s)]), - } - - # export fields - if not result.has_key('fields'): - logger.warning("res has no fields: %r", result) - continue - for field_name, field_def in result['fields'].iteritems(): - res_name = name + ',' + field_name - - for fn in def_params: - if fn in field_def: - transtype, modifier = def_params[fn] - for val in modifier(field_def[fn]): - push_translation(module, transtype, res_name, 0, val) - - # export arch - arch = result['arch'] - if arch and not isinstance(arch, UpdateableStr): - d = etree.XML(arch) - for t in trans_parse_view(d): - push_translation(module, 'wizard_view', name, 0, t) - - # export button labels - for but_args in result['state']: - button_name = but_args[0] - button_label = but_args[1] - res_name = name + ',' + button_name - push_translation(module, 'wizard_button', res_name, 0, button_label) - - elif model=='ir.model.fields': - try: - field_name = encode(obj.name) - except AttributeError, exc: - logger.error("name error in %s: %s", xml_name, str(exc)) - continue - objmodel = pool.get(obj.model) - if not objmodel or not field_name in objmodel._columns: - continue - field_def = objmodel._columns[field_name] - - name = "%s,%s" % (encode(obj.model), field_name) - push_translation(module, 'field', name, 0, encode(field_def.string)) - - if field_def.help: - push_translation(module, 'help', name, 0, encode(field_def.help)) - - if field_def.translate: - ids = objmodel.search(cr, uid, []) - obj_values = objmodel.read(cr, uid, ids, [field_name]) - for obj_value in obj_values: - res_id = obj_value['id'] - if obj.name in ('ir.model', 'ir.ui.menu'): - res_id = 0 - model_data_ids = model_data_obj.search(cr, uid, [ - ('model', '=', model), - ('res_id', '=', res_id), - ]) - if not model_data_ids: - push_translation(module, 'model', name, 0, encode(obj_value[field_name])) - - if hasattr(field_def, 'selection') and isinstance(field_def.selection, (list, tuple)): - for dummy, val in field_def.selection: - push_translation(module, 'selection', name, 0, encode(val)) - - elif model=='ir.actions.report.xml': - name = encode(obj.report_name) - fname = "" - if obj.report_rml: - fname = obj.report_rml - parse_func = trans_parse_rml - report_type = "report" - elif obj.report_xsl: - fname = obj.report_xsl - parse_func = trans_parse_xsl - report_type = "xsl" - if fname and obj.report_type in ('pdf', 'xsl'): - try: - report_file = tools.file_open(fname) - try: - d = etree.parse(report_file) - for t in parse_func(d.iter()): - push_translation(module, report_type, name, 0, t) - finally: - report_file.close() - except (IOError, etree.XMLSyntaxError): - logger.exception("couldn't export translation for report %s %s %s", name, report_type, fname) - - for field_name,field_def in obj._table._columns.items(): - if field_def.translate: - name = model + "," + field_name - try: - trad = getattr(obj, field_name) or '' - except: - trad = '' - push_translation(module, 'model', name, xml_name, encode(trad)) - - # End of data for ir.model.data query results - - cr.execute(query_models, query_param) - - def push_constraint_msg(module, term_type, model, msg): - # Check presence of __call__ directly instead of using - # callable() because it will be deprecated as of Python 3.0 - if not hasattr(msg, '__call__'): - push_translation(module, term_type, model, 0, encode(msg)) - - for (model_id, model, module) in cr.fetchall(): - module = encode(module) - model = encode(model) - - model_obj = pool.get(model) - - if not model_obj: - logging.getLogger("i18n").error("Unable to find object %r", model) - continue - - for constraint in getattr(model_obj, '_constraints', []): - push_constraint_msg(module, 'constraint', model, constraint[1]) - - for constraint in getattr(model_obj, '_sql_constraints', []): - push_constraint_msg(module, 'sql_constraint', model, constraint[2]) - - # parse source code for _() calls - def get_module_from_path(path, mod_paths=None): - if not mod_paths: - # First, construct a list of possible paths - def_path = os.path.abspath(os.path.join(tools.config['root_path'], 'addons')) # default addons path (base) - ad_paths= map(lambda m: os.path.abspath(m.strip()),tools.config['addons_path'].split(',')) - mod_paths=[def_path] - for adp in ad_paths: - mod_paths.append(adp) - if not os.path.isabs(adp): - mod_paths.append(adp) - elif adp.startswith(def_path): - mod_paths.append(adp[len(def_path)+1:]) - for mp in mod_paths: - if path.startswith(mp) and (os.path.dirname(path) != mp): - path = path[len(mp)+1:] - return path.split(os.path.sep)[0] - return 'base' # files that are not in a module are considered as being in 'base' module - - modobj = pool.get('ir.module.module') - installed_modids = modobj.search(cr, uid, [('state', '=', 'installed')]) - installed_modules = map(lambda m: m['name'], modobj.read(cr, uid, installed_modids, ['name'])) - - root_path = os.path.join(tools.config['root_path'], 'addons') - - apaths = map(os.path.abspath, map(str.strip, tools.config['addons_path'].split(','))) - if root_path in apaths: - path_list = apaths - else : - path_list = [root_path,] + apaths - - # Also scan these non-addon paths - for bin_path in ['osv', 'report' ]: - path_list.append(os.path.join(tools.config['root_path'], bin_path)) - - logger.debug("Scanning modules at paths: ", path_list) - - mod_paths = [] - join_dquotes = re.compile(r'([^\\])"[\s\\]*"', re.DOTALL) - join_quotes = re.compile(r'([^\\])\'[\s\\]*\'', re.DOTALL) - re_dquotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*"(.+?)"[\s]*?\)', re.DOTALL) - re_quotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*\'(.+?)\'[\s]*?\)', re.DOTALL) - - def export_code_terms_from_file(fname, path, root, terms_type): - fabsolutepath = join(root, fname) - frelativepath = fabsolutepath[len(path):] - module = get_module_from_path(fabsolutepath, mod_paths=mod_paths) - is_mod_installed = module in installed_modules - if (('all' in modules) or (module in modules)) and is_mod_installed: - logger.debug("Scanning code of %s at module: %s", frelativepath, module) - src_file = tools.file_open(fabsolutepath, subdir='') - try: - code_string = src_file.read() - finally: - src_file.close() - if module in installed_modules: - frelativepath = str("addons" + frelativepath) - ite = re_dquotes.finditer(code_string) - code_offset = 0 - code_line = 1 - for i in ite: - src = i.group(1) - if src.startswith('""'): - assert src.endswith('""'), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30]) - src = src[2:-2] - else: - src = join_dquotes.sub(r'\1', src) - # try to count the lines from the last pos to our place: - code_line += code_string[code_offset:i.start(1)].count('\n') - # now, since we did a binary read of a python source file, we - # have to expand pythonic escapes like the interpreter does. - src = src.decode('string_escape') - push_translation(module, terms_type, frelativepath, code_line, encode(src)) - code_line += i.group(1).count('\n') - code_offset = i.end() # we have counted newlines up to the match end - - ite = re_quotes.finditer(code_string) - code_offset = 0 #reset counters - code_line = 1 - for i in ite: - src = i.group(1) - if src.startswith("''"): - assert src.endswith("''"), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30]) - src = src[2:-2] - else: - src = join_quotes.sub(r'\1', src) - code_line += code_string[code_offset:i.start(1)].count('\n') - src = src.decode('string_escape') - push_translation(module, terms_type, frelativepath, code_line, encode(src)) - code_line += i.group(1).count('\n') - code_offset = i.end() # we have counted newlines up to the match end - - for path in path_list: - logger.debug("Scanning files of modules at %s", path) - for root, dummy, files in tools.osutil.walksymlinks(path): - for fname in itertools.chain(fnmatch.filter(files, '*.py')): - export_code_terms_from_file(fname, path, root, 'code') - for fname in itertools.chain(fnmatch.filter(files, '*.mako')): - export_code_terms_from_file(fname, path, root, 'report') - - - out = [["module","type","name","res_id","src","value"]] # header - _to_translate.sort() - # translate strings marked as to be translated - for module, source, name, id, type in _to_translate: - trans = trans_obj._get_source(cr, uid, name, type, lang, source) - out.append([module, type, name, id, source, encode(trans) or '']) - - return out - -def trans_load(cr, filename, lang, verbose=True, context=None): - logger = logging.getLogger('i18n') - try: - fileobj = open(filename,'r') - logger.info("loading %s", filename) - fileformat = os.path.splitext(filename)[-1][1:].lower() - r = trans_load_data(cr, fileobj, fileformat, lang, verbose=verbose, context=context) - fileobj.close() - return r - except IOError: - if verbose: - logger.error("couldn't read translation file %s", filename) - return None - -def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, context=None): - """Populates the ir_translation table. Fixing the res_ids so that they point - correctly to ir_model_data is done in a separate step, using the - 'trans_update_res_ids' function below.""" - logger = logging.getLogger('i18n') - if verbose: - logger.info('loading translation file for language %s', lang) - if context is None: - context = {} - db_name = cr.dbname - pool = pooler.get_pool(db_name) - lang_obj = pool.get('res.lang') - trans_obj = pool.get('ir.translation') - model_data_obj = pool.get('ir.model.data') - iso_lang = tools.get_iso_codes(lang) - try: - uid = 1 - ids = lang_obj.search(cr, uid, [('code','=', lang)]) - - if not ids: - # lets create the language with locale information - lang_obj.load_lang(cr, 1, lang=lang, lang_name=lang_name) - - - # now, the serious things: we read the language file - fileobj.seek(0) - if fileformat == 'csv': - reader = csv.reader(fileobj, quotechar='"', delimiter=',') - # read the first line of the file (it contains columns titles) - for row in reader: - f = row - break - elif fileformat == 'po': - reader = TinyPoFile(fileobj) - f = ['type', 'name', 'res_id', 'src', 'value'] - else: - logger.error('Bad file format: %s', fileformat) - raise Exception(_('Bad file format')) - - # read the rest of the file - line = 1 - for row in reader: - line += 1 - # skip empty rows and rows where the translation field (=last fiefd) is empty - #if (not row) or (not row[-1]): - # continue - - # dictionary which holds values for this line of the csv file - # {'lang': ..., 'type': ..., 'name': ..., 'res_id': ..., - # 'src': ..., 'value': ...} - dic = {'lang': lang} - for i in range(len(f)): - if f[i] in ('module',): - continue - dic[f[i]] = row[i] - - try: - dic['res_id'] = dic['res_id'] and int(dic['res_id']) or 0 - dic['module'] = False - dic['xml_id'] = False - except: - split_id = dic['res_id'].split('.', 1) - dic['module'] = split_id[0] - dic['xml_id'] = split_id[1] - dic['res_id'] = False - - args = [ - ('lang', '=', lang), - ('type', '=', dic['type']), - ('name', '=', dic['name']), - ('src', '=', dic['src']), - ] - if dic['type'] == 'model': - if dic['res_id'] is False: - args.append(('module', '=', dic['module'])) - args.append(('xml_id', '=', dic['xml_id'])) - else: - args.append(('res_id', '=', dic['res_id'])) - ids = trans_obj.search(cr, uid, args) - if ids: - if context.get('overwrite') and dic['value']: - trans_obj.write(cr, uid, ids, {'value': dic['value']}) - else: - trans_obj.create(cr, uid, dic) - if verbose: - logger.info("translation file loaded succesfully") - except IOError: - filename = '[lang: %s][format: %s]' % (iso_lang or 'new', fileformat) - logger.exception("couldn't read translation file %s", filename) - -def trans_update_res_ids(cr): - cr.execute(""" - UPDATE ir_translation - SET res_id = COALESCE ((SELECT ir_model_data.res_id - FROM ir_model_data - WHERE ir_translation.module = ir_model_data.module - AND ir_translation.xml_id = ir_model_data.name), 0) - WHERE ir_translation.module is not null - AND ir_translation.xml_id is not null - AND ir_translation.res_id = 0; - """) - -def get_locales(lang=None): - if lang is None: - lang = locale.getdefaultlocale()[0] - - if os.name == 'nt': - lang = _LOCALE2WIN32.get(lang, lang) - - def process(enc): - ln = locale._build_localename((lang, enc)) - yield ln - nln = locale.normalize(ln) - if nln != ln: - yield nln - - for x in process('utf8'): yield x - - prefenc = locale.getpreferredencoding() - if prefenc: - for x in process(prefenc): yield x - - prefenc = { - 'latin1': 'latin9', - 'iso-8859-1': 'iso8859-15', - 'cp1252': '1252', - }.get(prefenc.lower()) - if prefenc: - for x in process(prefenc): yield x - - yield lang - - - -def resetlocale(): - # locale.resetlocale is bugged with some locales. - for ln in get_locales(): - try: - return locale.setlocale(locale.LC_ALL, ln) - except locale.Error: - continue - -def load_language(cr, lang): - """Loads a translation terms for a language. - Used mainly to automate language loading at db initialization. - - :param lang: language ISO code with optional _underscore_ and l10n flavor (ex: 'fr', 'fr_BE', but not 'fr-BE') - :type lang: str - """ - pool = pooler.get_pool(cr.dbname) - language_installer = pool.get('base.language.install') - uid = 1 - oid = language_installer.create(cr, uid, {'lang': lang}) - language_installer.lang_install(cr, uid, [oid], context=None) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/bin/tools/which.py b/bin/tools/which.py deleted file mode 100644 index 66b264bddec..00000000000 --- a/bin/tools/which.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python -""" Which - locate a command - - * adapted from Brian Curtin's http://bugs.python.org/file15381/shutil_which.patch - * see http://bugs.python.org/issue444582 - * uses ``PATHEXT`` on Windows - * searches current directory before ``PATH`` on Windows, - but not before an explicitly passed path - * accepts both string or iterable for an explicitly passed path, or pathext - * accepts an explicitly passed empty path, or pathext (either '' or []) - * does not search ``PATH`` for files that have a path specified in their name already - * moved defpath and defpathext lists initialization to module level, - instead of initializing them on each function call - * changed interface: which_files() returns generator, which() returns first match, - or raises IOError(errno.ENOENT) - - .. function:: which_files(file [, mode=os.F_OK | os.X_OK[, path=None[, pathext=None]]]) - - Return a generator which yields full paths in which the *file* name exists - in a directory that is part of the file name, or on *path*, - and has the given *mode*. - By default, *mode* matches an inclusive OR of os.F_OK and os.X_OK - - an existing executable file. - The *path* is, by default, the ``PATH`` variable on the platform, - or the string/iterable passed in as *path*. - In the event that a ``PATH`` variable is not found, :const:`os.defpath` is used. - On Windows, a current directory is searched before using the ``PATH`` variable, - but not before an explicitly passed *path*. - The *pathext* is only used on Windows to match files with given extensions appended as well. - It defaults to the ``PATHEXT`` variable, or the string/iterable passed in as *pathext*. - In the event that a ``PATHEXT`` variable is not found, - default value for Windows XP/Vista is used. - The command is always searched without extension first, - even when *pathext* is explicitly passed. - - .. function:: which(file [, mode=os.F_OK | os.X_OK[, path=None[, pathext=None]]]) - Return first match generated by which_files(file, mode, path, pathext), - or raise IOError(errno.ENOENT). - -""" -__docformat__ = 'restructuredtext en' -__all__ = 'which which_files pathsep defpath defpathext F_OK R_OK W_OK X_OK'.split() - -import sys -from os import access, defpath, pathsep, environ, F_OK, R_OK, W_OK, X_OK -from os.path import exists, dirname, split, join - -windows = sys.platform.startswith('win') - -defpath = environ.get('PATH', defpath).split(pathsep) - -if windows: - defpath.insert(0, '.') # can insert without checking, when duplicates are removed - # given the quite usual mess in PATH on Windows, let's rather remove duplicates - seen = set() - defpath = [dir for dir in defpath if dir.lower() not in seen and not seen.add(dir.lower())] - del seen - - defpathext = [''] + environ.get('PATHEXT', - '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC').lower().split(pathsep) -else: - defpathext = [''] - -def which_files(file, mode=F_OK | X_OK, path=None, pathext=None): - """ Locate a file in a path supplied as a part of the file name, - or the user's path, or a supplied path. - The function yields full paths (not necessarily absolute paths), - in which the given file name matches an existing file in a directory on the path. - - >>> def test_which(expected, *args, **argd): - ... result = list(which_files(*args, **argd)) - ... assert result == expected, 'which_files: %s != %s' % (result, expected) - ... - ... try: - ... result = [ which(*args, **argd) ] - ... except IOError: - ... result = [] - ... assert result[:1] == expected[:1], 'which: %s != %s' % (result[:1], expected[:1]) - - >>> if windows: cmd = environ['COMSPEC'] - >>> if windows: test_which([cmd], 'cmd') - >>> if windows: test_which([cmd], 'cmd.exe') - >>> if windows: test_which([cmd], 'cmd', path=dirname(cmd)) - >>> if windows: test_which([cmd], 'cmd', pathext='.exe') - >>> if windows: test_which([cmd], cmd) - >>> if windows: test_which([cmd], cmd, path='<nonexistent>') - >>> if windows: test_which([cmd], cmd, pathext='<nonexistent>') - >>> if windows: test_which([cmd], cmd[:-4]) - >>> if windows: test_which([cmd], cmd[:-4], path='<nonexistent>') - - >>> if windows: test_which([], 'cmd', path='<nonexistent>') - >>> if windows: test_which([], 'cmd', pathext='<nonexistent>') - >>> if windows: test_which([], '<nonexistent>/cmd') - >>> if windows: test_which([], cmd[:-4], pathext='<nonexistent>') - - >>> if not windows: sh = '/bin/sh' - >>> if not windows: test_which([sh], 'sh') - >>> if not windows: test_which([sh], 'sh', path=dirname(sh)) - >>> if not windows: test_which([sh], 'sh', pathext='<nonexistent>') - >>> if not windows: test_which([sh], sh) - >>> if not windows: test_which([sh], sh, path='<nonexistent>') - >>> if not windows: test_which([sh], sh, pathext='<nonexistent>') - - >>> if not windows: test_which([], 'sh', mode=W_OK) # not running as root, are you? - >>> if not windows: test_which([], 'sh', path='<nonexistent>') - >>> if not windows: test_which([], '<nonexistent>/sh') - """ - filepath, file = split(file) - - if filepath: - path = (filepath,) - elif path is None: - path = defpath - elif isinstance(path, str): - path = path.split(pathsep) - - if pathext is None: - pathext = defpathext - elif isinstance(pathext, str): - pathext = pathext.split(pathsep) - - if not '' in pathext: - pathext.insert(0, '') # always check command without extension, even for custom pathext - - for dir in path: - basepath = join(dir, file) - for ext in pathext: - fullpath = basepath + ext - if exists(fullpath) and access(fullpath, mode): - yield fullpath - -def which(file, mode=F_OK | X_OK, path=None, pathext=None): - """ Locate a file in a path supplied as a part of the file name, - or the user's path, or a supplied path. - The function returns full path (not necessarily absolute path), - in which the given file name matches an existing file in a directory on the path, - or raises IOError(errno.ENOENT). - - >>> # for doctest see which_files() - """ - try: - return iter(which_files(file, mode, path, pathext)).next() - except StopIteration: - try: - from errno import ENOENT - except ImportError: - ENOENT = 2 - raise IOError(ENOENT, '%s not found' % (mode & X_OK and 'command' or 'file'), file) - - -if __name__ == '__main__': - import doctest - doctest.testmod() diff --git a/bin/tools/win32.py b/bin/tools/win32.py deleted file mode 100644 index 89651417e6c..00000000000 --- a/bin/tools/win32.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## - -import locale -import time -import datetime - -if not hasattr(locale, 'D_FMT'): - locale.D_FMT = 1 - -if not hasattr(locale, 'T_FMT'): - locale.T_FMT = 2 - -if not hasattr(locale, 'nl_langinfo'): - def nl_langinfo(param): - if param == locale.D_FMT: - val = time.strptime('30/12/2004', '%d/%m/%Y') - dt = datetime.datetime(*val[:-2]) - format_date = dt.strftime('%x') - for x, y in [('30', '%d'),('12', '%m'),('2004','%Y'),('04', '%Y')]: - format_date = format_date.replace(x, y) - return format_date - if param == locale.T_FMT: - val = time.strptime('13:24:56', '%H:%M:%S') - dt = datetime.datetime(*val[:-2]) - format_time = dt.strftime('%X') - for x, y in [('13', '%H'),('24', '%M'),('56','%S')]: - format_time = format_time.replace(x, y) - return format_time - locale.nl_langinfo = nl_langinfo - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/tools/yaml_import.py b/bin/tools/yaml_import.py deleted file mode 100644 index 2b5c982ca7b..00000000000 --- a/bin/tools/yaml_import.py +++ /dev/null @@ -1,799 +0,0 @@ -# -*- encoding: utf-8 -*- -import types -import time # used to eval time.strftime expressions -from datetime import datetime, timedelta -import logging - -import pooler -import netsvc -import misc -from config import config -import yaml_tag -import yaml - -# YAML import needs both safe and unsafe eval, but let's -# default to /safe/. -unsafe_eval = eval -from tools.safe_eval import safe_eval as eval - -logger_channel = 'tests' - -class YamlImportException(Exception): - pass - -class YamlImportAbortion(Exception): - pass - -def _is_yaml_mapping(node, tag_constructor): - value = isinstance(node, types.DictionaryType) \ - and len(node.keys()) == 1 \ - and isinstance(node.keys()[0], tag_constructor) - return value - -def is_comment(node): - return isinstance(node, types.StringTypes) - -def is_assert(node): - return isinstance(node, yaml_tag.Assert) \ - or _is_yaml_mapping(node, yaml_tag.Assert) - -def is_record(node): - return _is_yaml_mapping(node, yaml_tag.Record) - -def is_python(node): - return _is_yaml_mapping(node, yaml_tag.Python) - -def is_menuitem(node): - return isinstance(node, yaml_tag.Menuitem) \ - or _is_yaml_mapping(node, yaml_tag.Menuitem) - -def is_function(node): - return isinstance(node, yaml_tag.Function) \ - or _is_yaml_mapping(node, yaml_tag.Function) - -def is_report(node): - return isinstance(node, yaml_tag.Report) - -def is_workflow(node): - return isinstance(node, yaml_tag.Workflow) - -def is_act_window(node): - return isinstance(node, yaml_tag.ActWindow) - -def is_delete(node): - return isinstance(node, yaml_tag.Delete) - -def is_context(node): - return isinstance(node, yaml_tag.Context) - -def is_url(node): - return isinstance(node, yaml_tag.Url) - -def is_eval(node): - return isinstance(node, yaml_tag.Eval) - -def is_ref(node): - return isinstance(node, yaml_tag.Ref) \ - or _is_yaml_mapping(node, yaml_tag.Ref) - -def is_ir_set(node): - return _is_yaml_mapping(node, yaml_tag.IrSet) - -def is_string(node): - return isinstance(node, basestring) - -class TestReport(object): - def __init__(self): - self._report = {} - - def record(self, success, severity): - """ - Records the result of an assertion for the failed/success count. - Returns success. - """ - if severity in self._report: - self._report[severity][success] += 1 - else: - self._report[severity] = {success: 1, not success: 0} - return success - - def __str__(self): - res = [] - res.append('\nAssertions report:\nLevel\tsuccess\tfailure') - success = failure = 0 - for severity in self._report: - res.append("%s\t%s\t%s" % (severity, self._report[severity][True], self._report[severity][False])) - success += self._report[severity][True] - failure += self._report[severity][False] - res.append("total\t%s\t%s" % (success, failure)) - res.append("end of report (%s assertion(s) checked)" % success + failure) - return "\n".join(res) - -class RecordDictWrapper(dict): - """ - Used to pass a record as locals in eval: - records do not strictly behave like dict, so we force them to. - """ - def __init__(self, record): - self.record = record - def __getitem__(self, key): - if key in self.record: - return self.record[key] - return dict.__getitem__(self, key) - -class YamlInterpreter(object): - def __init__(self, cr, module, id_map, mode, filename, noupdate=False): - self.cr = cr - self.module = module - self.id_map = id_map - self.mode = mode - self.filename = filename - self.assert_report = TestReport() - self.noupdate = noupdate - self.logger = logging.getLogger("%s.%s" % (logger_channel, self.module)) - self.pool = pooler.get_pool(cr.dbname) - self.uid = 1 - self.context = {} # opererp context - self.eval_context = {'ref': self._ref(), - '_ref': self._ref(), # added '_ref' so that record['ref'] is possible - 'time': time, - 'datetime': datetime, - 'timedelta': timedelta} - - def _ref(self): - return lambda xml_id: self.get_id(xml_id) - - def get_model(self, model_name): - model = self.pool.get(model_name) - assert model, "The model %s does not exist." % (model_name,) - return model - - def validate_xml_id(self, xml_id): - id = xml_id - if '.' in xml_id: - module, id = xml_id.split('.', 1) - assert '.' not in id, "The ID reference '%s' must contains maximum one dot.\n" \ - "It is used to refer to other modules ID, in the form: module.record_id" \ - % (xml_id,) - if module != self.module: - module_count = self.pool.get('ir.module.module').search_count(self.cr, self.uid, \ - ['&', ('name', '=', module), ('state', 'in', ['installed'])]) - assert module_count == 1, 'The ID "%s" refers to an uninstalled module.' % (xml_id,) - if len(id) > 64: # TODO where does 64 come from (DB is 128)? should be a constant or loaded form DB - self.logger.log(logging.ERROR, 'id: %s is to long (max: 64)', id) - - def get_id(self, xml_id): - if not xml_id: - raise YamlImportException("The xml_id should be a non empty string.") - if isinstance(xml_id, types.IntType): - id = xml_id - elif xml_id in self.id_map: - id = self.id_map[xml_id] - else: - if '.' in xml_id: - module, checked_xml_id = xml_id.split('.', 1) - else: - module = self.module - checked_xml_id = xml_id - _, id = self.pool.get('ir.model.data').get_object_reference(self.cr, self.uid, module, checked_xml_id) - self.id_map[xml_id] = id - return id - - def get_context(self, node, eval_dict): - context = self.context.copy() - if node.context: - context.update(eval(node.context, eval_dict)) - return context - - def isnoupdate(self, node): - return self.noupdate or node.noupdate or False - - def _get_first_result(self, results, default=False): - if len(results): - value = results[0] - if isinstance(value, types.TupleType): - value = value[0] - else: - value = default - return value - - def process_comment(self, node): - return node - - def _log_assert_failure(self, severity, msg, *args): - if isinstance(severity, types.StringTypes): - levelname = severity.strip().upper() - level = logging.getLevelName(levelname) - else: - level = severity - levelname = logging.getLevelName(level) - self.assert_report.record(False, levelname) - self.logger.log(level, msg, *args) - if level >= config['assert_exit_level']: - raise YamlImportAbortion('Severe assertion failure (%s), aborting.' % levelname) - return - - def _get_assertion_id(self, assertion): - if assertion.id: - ids = [self.get_id(assertion.id)] - elif assertion.search: - q = eval(assertion.search, self.eval_context) - ids = self.pool.get(assertion.model).search(self.cr, self.uid, q, context=assertion.context) - else: - raise YamlImportException('Nothing to assert: you must give either an id or a search criteria.') - return ids - - def process_assert(self, node): - if isinstance(node, dict): - assertion, expressions = node.items()[0] - else: - assertion, expressions = node, [] - - if self.isnoupdate(assertion) and self.mode != 'init': - self.logger.warn('This assertion was not evaluated ("%s").' % assertion.string) - return - model = self.get_model(assertion.model) - ids = self._get_assertion_id(assertion) - if assertion.count is not None and len(ids) != assertion.count: - msg = 'assertion "%s" failed!\n' \ - ' Incorrect search count:\n' \ - ' expected count: %d\n' \ - ' obtained count: %d\n' - args = (assertion.string, assertion.count, len(ids)) - self._log_assert_failure(assertion.severity, msg, *args) - else: - context = self.get_context(assertion, self.eval_context) - for id in ids: - record = model.browse(self.cr, self.uid, id, context) - for test in expressions: - try: - success = unsafe_eval(test, self.eval_context, RecordDictWrapper(record)) - except Exception, e: - self.logger.debug('Exception during evaluation of !assert block in yaml_file %s.', self.filename, exc_info=True) - raise YamlImportAbortion(e) - if not success: - msg = 'Assertion "%s" FAILED\ntest: %s\n' - args = (assertion.string, test) - for aop in ('==', '!=', '<>', 'in', 'not in', '>=', '<=', '>', '<'): - if aop in test: - left, right = test.split(aop,1) - lmsg = '' - rmsg = '' - try: - lmsg = unsafe_eval(left, self.eval_context, RecordDictWrapper(record)) - except Exception, e: - lmsg = '<exc>' - - try: - rmsg = unsafe_eval(right, self.eval_context, RecordDictWrapper(record)) - except Exception, e: - rmsg = '<exc>' - - msg += 'values: ! %s %s %s' - args += ( lmsg, aop, rmsg ) - break - - self._log_assert_failure(assertion.severity, msg, *args) - return - else: # all tests were successful for this assertion tag (no break) - self.assert_report.record(True, assertion.severity) - - def _coerce_bool(self, value, default=False): - if isinstance(value, types.BooleanType): - b = value - if isinstance(value, types.StringTypes): - b = value.strip().lower() not in ('0', 'false', 'off', 'no') - elif isinstance(value, types.IntType): - b = bool(value) - else: - b = default - return b - - def create_osv_memory_record(self, record, fields): - model = self.get_model(record.model) - record_dict = self._create_record(model, fields) - id_new=model.create(self.cr, self.uid, record_dict, context=self.context) - self.id_map[record.id] = int(id_new) - return record_dict - - def process_record(self, node): - import osv - record, fields = node.items()[0] - model = self.get_model(record.model) - if isinstance(model, osv.osv.osv_memory): - record_dict=self.create_osv_memory_record(record, fields) - else: - self.validate_xml_id(record.id) - if self.isnoupdate(record) and self.mode != 'init': - id = self.pool.get('ir.model.data')._update_dummy(self.cr, 1, record.model, self.module, record.id) - # check if the resource already existed at the last update - if id: - self.id_map[record] = int(id) - return None - else: - if not self._coerce_bool(record.forcecreate): - return None - - record_dict = self._create_record(model, fields) - self.logger.debug("RECORD_DICT %s" % record_dict) - #context = self.get_context(record, self.eval_context) - context = record.context #TOFIX: record.context like {'withoutemployee':True} should pass from self.eval_context. example: test_project.yml in project module - id = self.pool.get('ir.model.data')._update(self.cr, 1, record.model, \ - self.module, record_dict, record.id, noupdate=self.isnoupdate(record), mode=self.mode, context=context) - self.id_map[record.id] = int(id) - if config.get('import_partial'): - self.cr.commit() - - def _create_record(self, model, fields): - record_dict = {} - for field_name, expression in fields.items(): - field_value = self._eval_field(model, field_name, expression) - record_dict[field_name] = field_value - return record_dict - - def process_ref(self, node, column=None): - if node.search: - if node.model: - model_name = node.model - elif column: - model_name = column._obj - else: - raise YamlImportException('You need to give a model for the search, or a column to infer it.') - model = self.get_model(model_name) - q = eval(node.search, self.eval_context) - ids = model.search(self.cr, self.uid, q) - if node.use: - instances = model.browse(self.cr, self.uid, ids) - value = [inst[node.use] for inst in instances] - else: - value = ids - elif node.id: - value = self.get_id(node.id) - else: - value = None - return value - - def process_eval(self, node): - return eval(node.expression, self.eval_context) - - def _eval_field(self, model, field_name, expression): - # TODO this should be refactored as something like model.get_field() in bin/osv - if field_name in model._columns: - column = model._columns[field_name] - elif field_name in model._inherit_fields: - column = model._inherit_fields[field_name][2] - else: - raise KeyError("Object '%s' does not contain field '%s'" % (model, field_name)) - if is_ref(expression): - elements = self.process_ref(expression, column) - if column._type in ("many2many", "one2many"): - value = [(6, 0, elements)] - else: # many2one - value = self._get_first_result(elements) - elif column._type == "many2one": - value = self.get_id(expression) - elif column._type == "one2many": - other_model = self.get_model(column._obj) - value = [(0, 0, self._create_record(other_model, fields)) for fields in expression] - elif column._type == "many2many": - ids = [self.get_id(xml_id) for xml_id in expression] - value = [(6, 0, ids)] - elif column._type == "date" and is_string(expression): - # enforce ISO format for string date values, to be locale-agnostic during tests - time.strptime(expression, misc.DEFAULT_SERVER_DATE_FORMAT) - value = expression - elif column._type == "datetime" and is_string(expression): - # enforce ISO format for string datetime values, to be locale-agnostic during tests - time.strptime(expression, misc.DEFAULT_SERVER_DATETIME_FORMAT) - value = expression - else: # scalar field - if is_eval(expression): - value = self.process_eval(expression) - else: - value = expression - # raise YamlImportException('Unsupported column "%s" or value %s:%s' % (field_name, type(expression), expression)) - return value - - def process_context(self, node): - self.context = node.__dict__ - if node.uid: - self.uid = self.get_id(node.uid) - if node.noupdate: - self.noupdate = node.noupdate - - def process_python(self, node): - def log(msg, *args): - self.logger.log(logging.TEST, msg, *args) - python, statements = node.items()[0] - model = self.get_model(python.model) - statements = statements.replace("\r\n", "\n") - code_context = {'model': model, 'cr': self.cr, 'uid': self.uid, 'log': log, 'context': self.context} - code_context.update({'self': model}) # remove me when no !python block test uses 'self' anymore - try: - code_obj = compile(statements, self.filename, 'exec') - unsafe_eval(code_obj, {'ref': self.get_id}, code_context) - except AssertionError, e: - self._log_assert_failure(python.severity, 'AssertionError in Python code %s: %s', python.name, e) - return - except Exception, e: - self.logger.debug('Exception during evaluation of !python block in yaml_file %s.', self.filename, exc_info=True) - raise - else: - self.assert_report.record(True, python.severity) - - def process_workflow(self, node): - workflow, values = node.items()[0] - if self.isnoupdate(workflow) and self.mode != 'init': - return - if workflow.ref: - id = self.get_id(workflow.ref) - else: - if not values: - raise YamlImportException('You must define a child node if you do not give a ref.') - if not len(values) == 1: - raise YamlImportException('Only one child node is accepted (%d given).' % len(values)) - value = values[0] - if not 'model' in value and (not 'eval' in value or not 'search' in value): - raise YamlImportException('You must provide a "model" and an "eval" or "search" to evaluate.') - value_model = self.get_model(value['model']) - local_context = {'obj': lambda x: value_model.browse(self.cr, self.uid, x, context=self.context)} - local_context.update(self.id_map) - id = eval(value['eval'], self.eval_context, local_context) - - if workflow.uid is not None: - uid = workflow.uid - else: - uid = self.uid - self.cr.execute('select distinct signal from wkf_transition') - signals=[x['signal'] for x in self.cr.dictfetchall()] - if workflow.action not in signals: - raise YamlImportException('Incorrect action %s. No such action defined' % workflow.action) - wf_service = netsvc.LocalService("workflow") - wf_service.trg_validate(uid, workflow.model, id, workflow.action, self.cr) - - def _eval_params(self, model, params): - args = [] - for i, param in enumerate(params): - if isinstance(param, types.ListType): - value = self._eval_params(model, param) - elif is_ref(param): - value = self.process_ref(param) - elif is_eval(param): - value = self.process_eval(param) - elif isinstance(param, types.DictionaryType): # supports XML syntax - param_model = self.get_model(param.get('model', model)) - if 'search' in param: - q = eval(param['search'], self.eval_context) - ids = param_model.search(self.cr, self.uid, q) - value = self._get_first_result(ids) - elif 'eval' in param: - local_context = {'obj': lambda x: param_model.browse(self.cr, self.uid, x, self.context)} - local_context.update(self.id_map) - value = eval(param['eval'], self.eval_context, local_context) - else: - raise YamlImportException('You must provide either a !ref or at least a "eval" or a "search" to function parameter #%d.' % i) - else: - value = param # scalar value - args.append(value) - return args - - def process_function(self, node): - function, params = node.items()[0] - if self.isnoupdate(function) and self.mode != 'init': - return - model = self.get_model(function.model) - context = self.get_context(function, self.eval_context) - if function.eval: - args = self.process_eval(function.eval) - else: - args = self._eval_params(function.model, params) - method = function.name - getattr(model, method)(self.cr, self.uid, *args) - - def _set_group_values(self, node, values): - if node.groups: - group_names = node.groups.split(',') - groups_value = [] - for group in group_names: - if group.startswith('-'): - group_id = self.get_id(group[1:]) - groups_value.append((3, group_id)) - else: - group_id = self.get_id(group) - groups_value.append((4, group_id)) - values['groups_id'] = groups_value - - def process_menuitem(self, node): - self.validate_xml_id(node.id) - - if not node.parent: - parent_id = False - self.cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (node.name,)) - res = self.cr.fetchone() - values = {'parent_id': parent_id, 'name': node.name} - else: - parent_id = self.get_id(node.parent) - values = {'parent_id': parent_id} - if node.name: - values['name'] = node.name - try: - res = [ self.get_id(node.id) ] - except: # which exception ? - res = None - - if node.action: - action_type = node.type or 'act_window' - icons = { - "act_window": 'STOCK_NEW', - "report.xml": 'STOCK_PASTE', - "wizard": 'STOCK_EXECUTE', - "url": 'STOCK_JUMP_TO', - } - values['icon'] = icons.get(action_type, 'STOCK_NEW') - if action_type == 'act_window': - action_id = self.get_id(node.action) - self.cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (action_id,)) - ir_act_window_result = self.cr.fetchone() - assert ir_act_window_result, "No window action defined for this id %s !\n" \ - "Verify that this is a window action or add a type argument." % (node.action,) - action_type, action_mode, action_name, view_id, target = ir_act_window_result - if view_id: - self.cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (view_id,)) - # TODO guess why action_mode is ir_act_window.view_mode above and ir_ui_view.type here - action_mode = self.cr.fetchone() - self.cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (action_id,)) - if self.cr.rowcount: - action_mode = self.cr.fetchone() - if action_type == 'tree': - values['icon'] = 'STOCK_INDENT' - elif action_mode and action_mode.startswith('tree'): - values['icon'] = 'STOCK_JUSTIFY_FILL' - elif action_mode and action_mode.startswith('graph'): - values['icon'] = 'terp-graph' - elif action_mode and action_mode.startswith('calendar'): - values['icon'] = 'terp-calendar' - if target == 'new': - values['icon'] = 'STOCK_EXECUTE' - if not values.get('name', False): - values['name'] = action_name - elif action_type == 'wizard': - action_id = self.get_id(node.action) - self.cr.execute('select name from ir_act_wizard where id=%s', (action_id,)) - ir_act_wizard_result = self.cr.fetchone() - if (not values.get('name', False)) and ir_act_wizard_result: - values['name'] = ir_act_wizard_result[0] - else: - raise YamlImportException("Unsupported type '%s' in menuitem tag." % action_type) - if node.sequence: - values['sequence'] = node.sequence - if node.icon: - values['icon'] = node.icon - - self._set_group_values(node, values) - - pid = self.pool.get('ir.model.data')._update(self.cr, 1, \ - 'ir.ui.menu', self.module, values, node.id, mode=self.mode, \ - noupdate=self.isnoupdate(node), res_id=res and res[0] or False) - - if node.id and parent_id: - self.id_map[node.id] = int(parent_id) - - if node.action and pid: - action_type = node.type or 'act_window' - action_id = self.get_id(node.action) - action = "ir.actions.%s,%d" % (action_type, action_id) - self.pool.get('ir.model.data').ir_set(self.cr, 1, 'action', \ - 'tree_but_open', 'Menuitem', [('ir.ui.menu', int(parent_id))], action, True, True, xml_id=node.id) - - def process_act_window(self, node): - assert getattr(node, 'id'), "Attribute %s of act_window is empty !" % ('id',) - assert getattr(node, 'name'), "Attribute %s of act_window is empty !" % ('name',) - assert getattr(node, 'res_model'), "Attribute %s of act_window is empty !" % ('res_model',) - self.validate_xml_id(node.id) - view_id = False - if node.view: - view_id = self.get_id(node.view) - if not node.context: - node.context={} - context = eval(str(node.context), self.eval_context) - values = { - 'name': node.name, - 'type': node.type or 'ir.actions.act_window', - 'view_id': view_id, - 'domain': node.domain, - 'context': context, - 'res_model': node.res_model, - 'src_model': node.src_model, - 'view_type': node.view_type or 'form', - 'view_mode': node.view_mode or 'tree,form', - 'usage': node.usage, - 'limit': node.limit, - 'auto_refresh': node.auto_refresh, - 'multi': getattr(node, 'multi', False), - } - - self._set_group_values(node, values) - - if node.target: - values['target'] = node.target - id = self.pool.get('ir.model.data')._update(self.cr, 1, \ - 'ir.actions.act_window', self.module, values, node.id, mode=self.mode) - self.id_map[node.id] = int(id) - - if node.src_model: - keyword = 'client_action_relate' - value = 'ir.actions.act_window,%s' % id - replace = node.replace or True - self.pool.get('ir.model.data').ir_set(self.cr, 1, 'action', keyword, \ - node.id, [node.src_model], value, replace=replace, noupdate=self.isnoupdate(node), isobject=True, xml_id=node.id) - # TODO add remove ir.model.data - - def process_delete(self, node): - assert getattr(node, 'model'), "Attribute %s of delete tag is empty !" % ('model',) - if self.pool.get(node.model): - if len(node.search): - ids = self.pool.get(node.model).search(self.cr, self.uid, eval(node.search, self.eval_context)) - else: - ids = [self.get_id(node.id)] - if len(ids): - self.pool.get(node.model).unlink(self.cr, self.uid, ids) - self.pool.get('ir.model.data')._unlink(self.cr, 1, node.model, ids) - else: - self.logger.log(logging.TEST, "Record not deleted.") - - def process_url(self, node): - self.validate_xml_id(node.id) - - res = {'name': node.name, 'url': node.url, 'target': node.target} - - id = self.pool.get('ir.model.data')._update(self.cr, 1, \ - "ir.actions.url", self.module, res, node.id, mode=self.mode) - self.id_map[node.id] = int(id) - # ir_set - if (not node.menu or eval(node.menu)) and id: - keyword = node.keyword or 'client_action_multi' - value = 'ir.actions.url,%s' % id - replace = node.replace or True - self.pool.get('ir.model.data').ir_set(self.cr, 1, 'action', \ - keyword, node.url, ["ir.actions.url"], value, replace=replace, \ - noupdate=self.isnoupdate(node), isobject=True, xml_id=node.id) - - def process_ir_set(self, node): - if not self.mode == 'init': - return False - _, fields = node.items()[0] - res = {} - for fieldname, expression in fields.items(): - if is_eval(expression): - value = eval(expression.expression, self.eval_context) - else: - value = expression - res[fieldname] = value - self.pool.get('ir.model.data').ir_set(self.cr, 1, res['key'], res['key2'], \ - res['name'], res['models'], res['value'], replace=res.get('replace',True), \ - isobject=res.get('isobject', False), meta=res.get('meta',None)) - - def process_report(self, node): - values = {} - for dest, f in (('name','string'), ('model','model'), ('report_name','name')): - values[dest] = getattr(node, f) - assert values[dest], "Attribute %s of report is empty !" % (f,) - for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')): - if getattr(node, field): - values[dest] = getattr(node, field) - if node.auto: - values['auto'] = eval(node.auto) - if node.sxw: - sxw_file = misc.file_open(node.sxw) - try: - sxw_content = sxw_file.read() - values['report_sxw_content'] = sxw_content - finally: - sxw_file.close() - if node.header: - values['header'] = eval(node.header) - values['multi'] = node.multi and eval(node.multi) - xml_id = node.id - self.validate_xml_id(xml_id) - - self._set_group_values(node, values) - - id = self.pool.get('ir.model.data')._update(self.cr, 1, "ir.actions.report.xml", \ - self.module, values, xml_id, noupdate=self.isnoupdate(node), mode=self.mode) - self.id_map[xml_id] = int(id) - - if not node.menu or eval(node.menu): - keyword = node.keyword or 'client_print_multi' - value = 'ir.actions.report.xml,%s' % id - replace = node.replace or True - self.pool.get('ir.model.data').ir_set(self.cr, 1, 'action', \ - keyword, values['name'], [values['model']], value, replace=replace, isobject=True, xml_id=xml_id) - - def process_none(self): - """ - Empty node or commented node should not pass silently. - """ - self._log_assert_failure(logging.WARNING, "You have an empty block in your tests.") - - - def process(self, yaml_string): - """ - Processes a Yaml string. Custom tags are interpreted by 'process_' instance methods. - """ - yaml_tag.add_constructors() - - is_preceded_by_comment = False - for node in yaml.load(yaml_string): - is_preceded_by_comment = self._log(node, is_preceded_by_comment) - try: - self._process_node(node) - except YamlImportException, e: - self.logger.exception(e) - except Exception, e: - self.logger.exception(e) - raise - - def _process_node(self, node): - if is_comment(node): - self.process_comment(node) - elif is_assert(node): - self.process_assert(node) - elif is_record(node): - self.process_record(node) - elif is_python(node): - self.process_python(node) - elif is_menuitem(node): - self.process_menuitem(node) - elif is_delete(node): - self.process_delete(node) - elif is_url(node): - self.process_url(node) - elif is_context(node): - self.process_context(node) - elif is_ir_set(node): - self.process_ir_set(node) - elif is_act_window(node): - self.process_act_window(node) - elif is_report(node): - self.process_report(node) - elif is_workflow(node): - if isinstance(node, types.DictionaryType): - self.process_workflow(node) - else: - self.process_workflow({node: []}) - elif is_function(node): - if isinstance(node, types.DictionaryType): - self.process_function(node) - else: - self.process_function({node: []}) - elif node is None: - self.process_none() - else: - raise YamlImportException("Can not process YAML block: %s" % node) - - def _log(self, node, is_preceded_by_comment): - if is_comment(node): - is_preceded_by_comment = True - self.logger.log(logging.TEST, node) - elif not is_preceded_by_comment: - if isinstance(node, types.DictionaryType): - msg = "Creating %s\n with %s" - args = node.items()[0] - self.logger.log(logging.TEST, msg, *args) - else: - self.logger.log(logging.TEST, node) - else: - is_preceded_by_comment = False - return is_preceded_by_comment - -def yaml_import(cr, module, yamlfile, idref=None, mode='init', noupdate=False, report=None): - if idref is None: - idref = {} - yaml_string = yamlfile.read() - yaml_interpreter = YamlInterpreter(cr, module, idref, mode, filename=yamlfile.name, noupdate=noupdate) - yaml_interpreter.process(yaml_string) - -# keeps convention of convert.py -convert_yaml_import = yaml_import - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/tools/yaml_tag.py b/bin/tools/yaml_tag.py deleted file mode 100644 index 20aabf1c54c..00000000000 --- a/bin/tools/yaml_tag.py +++ /dev/null @@ -1,183 +0,0 @@ -import yaml -import logging - -class YamlTag(object): - """ - Superclass for constructors of custom tags defined in yaml file. - __str__ is overriden in subclass and used for serialization in module recorder. - """ - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - def __getitem__(self, key): - return getattr(self, key) - def __getattr__(self, attr): - return None - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, sorted(self.__dict__.items())) - -class Assert(YamlTag): - def __init__(self, model, id=None, severity=logging.WARNING, string="NONAME", **kwargs): - self.model = model - self.id = id - self.severity = severity - self.string = string - super(Assert, self).__init__(**kwargs) - -class Record(YamlTag): - def __init__(self, model, id, use='id', **kwargs): - self.model = model - self.id = id - super(Record, self).__init__(**kwargs) - def __str__(self): - return '!record {model: %s, id: %s}:' % (str(self.model,), str(self.id,)) - -class Python(YamlTag): - def __init__(self, model, severity=logging.WARNING, name="", **kwargs): - self.model= model - self.severity = severity - self.name = name - super(Python, self).__init__(**kwargs) - def __str__(self): - return '!python {model: %s}: |' % (str(self.model), ) - -class Menuitem(YamlTag): - def __init__(self, id, name, **kwargs): - self.id = id - self.name = name - super(Menuitem, self).__init__(**kwargs) - -class Workflow(YamlTag): - def __init__(self, model, action, ref=None, **kwargs): - self.model = model - self.action = action - self.ref = ref - super(Workflow, self).__init__(**kwargs) - def __str__(self): - return '!workflow {model: %s, action: %s, ref: %s}' % (str(self.model,), str(self.action,), str(self.ref,)) - -class ActWindow(YamlTag): - def __init__(self, **kwargs): - super(ActWindow, self).__init__(**kwargs) - -class Function(YamlTag): - def __init__(self, model, name, **kwargs): - self.model = model - self.name = name - super(Function, self).__init__(**kwargs) - -class Report(YamlTag): - def __init__(self, model, name, string, **kwargs): - self.model = model - self.name = name - self.string = string - super(Report, self).__init__(**kwargs) - -class Delete(YamlTag): - def __init__(self, **kwargs): - super(Delete, self).__init__(**kwargs) - -class Context(YamlTag): - def __init__(self, **kwargs): - super(Context, self).__init__(**kwargs) - -class Url(YamlTag): - def __init__(self, **kwargs): - super(Url, self).__init__(**kwargs) - -class Eval(YamlTag): - def __init__(self, expression): - self.expression = expression - super(Eval, self).__init__() - def __str__(self): - return '!eval %s' % str(self.expression) - -class Ref(YamlTag): - def __init__(self, expr="False", *args, **kwargs): - self.expr = expr - super(Ref, self).__init__(*args, **kwargs) - def __str__(self): - return 'ref(%s)' % repr(self.expr) - -class IrSet(YamlTag): - def __init__(self): - super(IrSet, self).__init__() - -def assert_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Assert(**kwargs) - -def record_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Record(**kwargs) - -def python_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Python(**kwargs) - -def menuitem_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Menuitem(**kwargs) - -def workflow_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Workflow(**kwargs) - -def act_window_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return ActWindow(**kwargs) - -def function_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Function(**kwargs) - -def report_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Report(**kwargs) - -def delete_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Delete(**kwargs) - -def context_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Context(**kwargs) - -def url_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Url(**kwargs) - -def eval_constructor(loader, node): - expression = loader.construct_scalar(node) - return Eval(expression) - -def ref_constructor(loader, tag_suffix, node): - if tag_suffix == "id": - kwargs = {"id": loader.construct_scalar(node)} - else: - kwargs = loader.construct_mapping(node) - return Ref(**kwargs) - -def ir_set_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return IrSet(**kwargs) - -# Registers constructors for custom tags. -# Constructors are actually defined globally: do not redefined them in another -# class/file/package. This means that module recorder need import this file. -def add_constructors(): - yaml.add_constructor(u"!assert", assert_constructor) - yaml.add_constructor(u"!record", record_constructor) - yaml.add_constructor(u"!python", python_constructor) - yaml.add_constructor(u"!menuitem", menuitem_constructor) - yaml.add_constructor(u"!workflow", workflow_constructor) - yaml.add_constructor(u"!act_window", act_window_constructor) - yaml.add_constructor(u"!function", function_constructor) - yaml.add_constructor(u"!report", report_constructor) - yaml.add_constructor(u"!context", context_constructor) - yaml.add_constructor(u"!delete", delete_constructor) - yaml.add_constructor(u"!url", url_constructor) - yaml.add_constructor(u"!eval", eval_constructor) - yaml.add_multi_constructor(u"!ref", ref_constructor) - yaml.add_constructor(u"!ir_set", ir_set_constructor) -add_constructors() - |
