summaryrefslogtreecommitdiff
path: root/bin/tools
diff options
context:
space:
mode:
authorVo Minh Thu <vmt@openerp.com>2011-02-07 13:57:23 +0100
committerVo Minh Thu <vmt@openerp.com>2011-02-07 13:57:23 +0100
commitf8572e5c602798f37c2a94d54ab5e3ac79ff0185 (patch)
tree35687977b932ac91bd31fece229ab78cc70926f5 /bin/tools
parent9c287abc143a7b187635c75751a29baeefa58c15 (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__.py36
-rw-r--r--bin/tools/amount_to_text.py194
-rw-r--r--bin/tools/amount_to_text_en.py124
-rw-r--r--bin/tools/config.py503
-rw-r--r--bin/tools/convert.py977
-rw-r--r--bin/tools/copy.py439
-rw-r--r--bin/tools/func.py116
-rw-r--r--bin/tools/graph.py784
-rw-r--r--bin/tools/import_email.py169
-rw-r--r--bin/tools/lru.py115
-rw-r--r--bin/tools/misc.py1508
-rw-r--r--bin/tools/osutil.py62
-rw-r--r--bin/tools/parse_version.py101
-rw-r--r--bin/tools/pdf_utils.py130
-rw-r--r--bin/tools/safe_eval.py283
-rw-r--r--bin/tools/sql.py28
-rw-r--r--bin/tools/test_reports.py309
-rw-r--r--bin/tools/threadinglocal.py269
-rw-r--r--bin/tools/translate.py1005
-rw-r--r--bin/tools/which.py153
-rw-r--r--bin/tools/win32.py50
-rw-r--r--bin/tools/yaml_import.py799
-rw-r--r--bin/tools/yaml_tag.py183
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('&','&amp;').replace('<','&lt;').replace('>','&gt;')
-
-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()
-