[MERGE] merged latest trunk

bzr revid: odo@openerp.com-20110531103720-mas6phcnb22qj4p2
This commit is contained in:
Olivier Dony 2011-05-31 12:37:20 +02:00
commit 89e66c498d
62 changed files with 31005 additions and 2176 deletions

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2011-03-30 08:28+0000\n"
"PO-Revision-Date: 2011-04-28 13:32+0000\n"
"Last-Translator: Lorenzo Battistini - agilebg.com "
"<lorenzo.battistini@agilebg.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-04-19 04:44+0000\n"
"X-Launchpad-Export-Date: 2011-04-29 04:39+0000\n"
"X-Generator: Launchpad (build 12758)\n"
#. module: base
@ -6582,7 +6582,7 @@ msgstr "GPL-3 o versione successiva"
#. module: base
#: field:workflow.activity,action:0
msgid "Python Action"
msgstr "Puthon Attivo"
msgstr "Azione python"
#. module: base
#: selection:base.language.install,lang:0

9248
bin/addons/base/i18n/mk.po Normal file

File diff suppressed because it is too large Load Diff

10
debian/po/tr.po vendored
View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-server\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2009-08-24 22:41+0300\n"
"PO-Revision-Date: 2010-01-29 05:27+0000\n"
"Last-Translator: Fabien (Open ERP) <fp@tinyerp.com>\n"
"PO-Revision-Date: 2011-05-17 20:53+0000\n"
"Last-Translator: Ayhan KIZILTAN <Unknown>\n"
"Language-Team: Turkish <tr@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-04-19 04:50+0000\n"
"X-Generator: Launchpad (build 12758)\n"
"X-Launchpad-Export-Date: 2011-05-18 06:22+0000\n"
"X-Generator: Launchpad (build 12959)\n"
#. Type: string
#. Description
@ -38,4 +38,4 @@ msgstr ""
#. Description
#: ../openerp-server.templates:1001
msgid "Please choose that account's username."
msgstr "Lütfen bu hesabın kullanıcı adını seçiniz."
msgstr "Lütfen o hesabın kullanıcı adını seçiniz."

View File

@ -39,10 +39,11 @@ import signal
import sys
import threading
import traceback
import time
import openerp.release as release
__author__ = release.author
__version__ = release.version
import openerp
__author__ = openerp.release.author
__version__ = openerp.release.version
if os.name == 'posix':
import pwd
@ -53,80 +54,58 @@ if os.name == 'posix':
sys.exit(1)
#-----------------------------------------------------------------------
# import the tools module so that the commandline parameters are parsed
# parse the command line
#-----------------------------------------------------------------------
import openerp.tools as tools
tools.config.parse_config(sys.argv[1:])
openerp.tools.config.parse_config(sys.argv[1:])
config = openerp.tools.config
#----------------------------------------------------------
# get logger
#----------------------------------------------------------
import openerp.netsvc as netsvc
netsvc.init_logger()
openerp.netsvc.init_logger()
logger = logging.getLogger('server')
logger.info("OpenERP version - %s", release.version)
for name, value in [('addons_path', tools.config['addons_path']),
('database hostname', tools.config['db_host'] or 'localhost'),
('database port', tools.config['db_port'] or '5432'),
('database user', tools.config['db_user'])]:
logger.info("OpenERP version - %s", __version__)
for name, value in [('addons_path', config['addons_path']),
('database hostname', config['db_host'] or 'localhost'),
('database port', config['db_port'] or '5432'),
('database user', config['db_user'])]:
logger.info("%s - %s", name, value)
# Don't allow if the connection to PostgreSQL done by postgres user
if tools.config['db_user'] == 'postgres':
if config['db_user'] == 'postgres':
logger.error("Connecting to the database as 'postgres' user is forbidden, as it present major security issues. Shutting down.")
sys.exit(1)
import time
#----------------------------------------------------------
# init net service
#----------------------------------------------------------
logger.info('initialising distributed objects services')
#---------------------------------------------------------------
# connect to the database and initialize it with base if needed
#---------------------------------------------------------------
import openerp.pooler as pooler
#----------------------------------------------------------
# import basic modules
#----------------------------------------------------------
import openerp.osv as osv
import openerp.workflow as workflow
import openerp.report as report
import openerp.service as service
#----------------------------------------------------------
# import addons
#----------------------------------------------------------
import openerp.addons as addons
#----------------------------------------------------------
# Load and update databases if requested
#----------------------------------------------------------
import openerp.service.http_server as service_http_server
if not ( config["stop_after_init"] or \
config["translate_in"] or \
config["translate_out"] ):
openerp.osv.osv.start_object_proxy()
openerp.service.web_services.start_web_services()
http_server = openerp.service.http_server
netrpc_server = openerp.service.netrpc_server
http_server.init_servers()
http_server.init_xmlrpc()
http_server.init_static_http()
netrpc_server.init_servers()
if not ( tools.config["stop_after_init"] or \
tools.config["translate_in"] or \
tools.config["translate_out"] ):
service_http_server.init_servers()
service_http_server.init_xmlrpc()
service_http_server.init_static_http()
import openerp.service.netrpc_server as service_netrpc_server
service_netrpc_server.init_servers()
if tools.config['db_name']:
for dbname in tools.config['db_name'].split(','):
db,pool = pooler.get_db_and_pool(dbname, update_module=tools.config['init'] or tools.config['update'], pooljobs=False)
if config['db_name']:
for dbname in config['db_name'].split(','):
db, pool = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False)
cr = db.cursor()
if tools.config["test_file"]:
logger.info('loading test file %s', tools.config["test_file"])
tools.convert_yaml_import(cr, 'base', file(tools.config["test_file"]), {}, 'test', True)
if config["test_file"]:
logger.info('loading test file %s', config["test_file"])
openerp.tools.convert_yaml_import(cr, 'base', file(config["test_file"]), {}, 'test', True)
cr.rollback()
pool.get('ir.cron')._poolJobs(db.dbname)
@ -136,35 +115,33 @@ if tools.config['db_name']:
#----------------------------------------------------------
# translation stuff
#----------------------------------------------------------
if tools.config["translate_out"]:
import csv
if tools.config["language"]:
msg = "language %s" % (tools.config["language"],)
if config["translate_out"]:
if config["language"]:
msg = "language %s" % (config["language"],)
else:
msg = "new language"
logger.info('writing translation file for %s to %s', msg, tools.config["translate_out"])
logger.info('writing translation file for %s to %s', msg, config["translate_out"])
fileformat = os.path.splitext(tools.config["translate_out"])[-1][1:].lower()
buf = file(tools.config["translate_out"], "w")
dbname = tools.config['db_name']
cr = pooler.get_db(dbname).cursor()
tools.trans_export(tools.config["language"], tools.config["translate_modules"] or ["all"], buf, fileformat, cr)
fileformat = os.path.splitext(config["translate_out"])[-1][1:].lower()
buf = file(config["translate_out"], "w")
dbname = config['db_name']
cr = openerp.pooler.get_db(dbname).cursor()
openerp.tools.trans_export(config["language"], config["translate_modules"] or ["all"], buf, fileformat, cr)
cr.close()
buf.close()
logger.info('translation file written successfully')
sys.exit(0)
if tools.config["translate_in"]:
context = {'overwrite': tools.config["overwrite_existing_translations"]}
dbname = tools.config['db_name']
cr = pooler.get_db(dbname).cursor()
tools.trans_load(cr,
tools.config["translate_in"],
tools.config["language"],
if config["translate_in"]:
context = {'overwrite': config["overwrite_existing_translations"]}
dbname = config['db_name']
cr = openerp.pooler.get_db(dbname).cursor()
openerp.tools.trans_load(cr,
config["translate_in"],
config["language"],
context=context)
tools.trans_update_res_ids(cr)
openerp.tools.trans_update_res_ids(cr)
cr.commit()
cr.close()
sys.exit(0)
@ -172,9 +149,10 @@ if tools.config["translate_in"]:
#----------------------------------------------------------------------------------
# if we don't want the server to continue to run after initialization, we quit here
#----------------------------------------------------------------------------------
if tools.config["stop_after_init"]:
if config["stop_after_init"]:
sys.exit(0)
openerp.netsvc.start_agent()
#----------------------------------------------------------
# Launch Servers
@ -186,15 +164,16 @@ SIGNALS = dict(
[(getattr(signal, sign), sign) for sign in LST_SIGNALS]
)
netsvc.quit_signals_received = 0
quit_signals_received = 0
def handler(signum, frame):
"""
:param signum: the signal number
:param frame: the interrupted stack frame or None
"""
netsvc.quit_signals_received += 1
if netsvc.quit_signals_received > 1:
global quit_signals_received
quit_signals_received += 1
if quit_signals_received > 1:
sys.stderr.write("Forced shutdown.\n")
os._exit(0)
@ -219,10 +198,10 @@ if os.name == 'posix':
signal.signal(signal.SIGQUIT, dumpstacks)
def quit():
netsvc.Agent.quit()
netsvc.Server.quitAll()
if tools.config['pidfile']:
os.unlink(tools.config['pidfile'])
openerp.netsvc.Agent.quit()
openerp.netsvc.Server.quitAll()
if config['pidfile']:
os.unlink(config['pidfile'])
logger = logging.getLogger('shutdown')
logger.info("Initiating OpenERP Server shutdown")
logger.info("Hit CTRL-C again or send a second signal to immediately terminate the server...")
@ -240,17 +219,17 @@ def quit():
time.sleep(0.05)
sys.exit(0)
if tools.config['pidfile']:
fd = open(tools.config['pidfile'], 'w')
if config['pidfile']:
fd = open(config['pidfile'], 'w')
pidtext = "%d" % (os.getpid())
fd.write(pidtext)
fd.close()
netsvc.Server.startAll()
openerp.netsvc.Server.startAll()
logger.info('OpenERP server is running, waiting for connections...')
while netsvc.quit_signals_received == 0:
while quit_signals_received == 0:
time.sleep(60)
quit()

View File

@ -19,9 +19,14 @@
#
##############################################################################
""" OpenERP core library.
"""
import addons
import ir
import conf
import loglevels
import modules
import netsvc
import osv
import pooler

View File

@ -20,960 +20,16 @@
#
##############################################################################
import os, sys, imp
from os.path import join as opj
import itertools
import zipimport
""" Addons module.
import openerp.osv as osv
import openerp.tools as tools
import openerp.tools.osutil as osutil
from openerp.tools.safe_eval import safe_eval as eval
import openerp.pooler as pooler
from openerp.tools.translate import _
This module only serves to contain OpenERP addons. For the code to
manage those addons, see openerp.modules. This module conveniently
reexports some symbols from openerp.modules. Importing them from here
is deprecated.
import openerp.netsvc as netsvc
import zipfile
import openerp.release as release
import re
import base64
from zipfile import PyZipFile, ZIP_DEFLATED
from cStringIO import StringIO
import logging
logger = netsvc.Logger()
_ad = os.path.dirname(__file__) # default addons path (base)
ad_paths = []
# Modules already loaded
loaded = []
def initialize_sys_path():
global ad_paths
if ad_paths:
return
ad_paths = map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(','))
sys.path.insert(1, _ad)
ad_cnt=1
for adp in ad_paths:
if adp != _ad:
sys.path.insert(ad_cnt, adp)
ad_cnt+=1
ad_paths.append(_ad) # for get_module_path
class Graph(dict):
def addNode(self, name, deps):
max_depth, father = 0, None
for n in [Node(x, self) for x in deps]:
if n.depth >= max_depth:
father = n
max_depth = n.depth
if father:
father.addChild(name)
else:
Node(name, self)
def update_from_db(self, cr):
if not len(self):
return
# update the graph with values from the database (if exist)
## First, we set the default values for each package in graph
additional_data = dict.fromkeys(self.keys(), {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None})
## Then we get the values from the database
cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version'
' FROM ir_module_module'
' WHERE name IN %s',(tuple(additional_data),)
)
## and we update the default values with values from the database
additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()]))
for package in self.values():
for k, v in additional_data[package.name].items():
setattr(package, k, v)
def __iter__(self):
level = 0
done = set(self.keys())
while done:
level_modules = [(name, module) for name, module in self.items() if module.depth==level]
for name, module in level_modules:
done.remove(name)
yield module
level += 1
class Singleton(object):
def __new__(cls, name, graph):
if name in graph:
inst = graph[name]
else:
inst = object.__new__(cls)
inst.name = name
graph[name] = inst
return inst
class Node(Singleton):
def __init__(self, name, graph):
self.graph = graph
if not hasattr(self, 'children'):
self.children = []
if not hasattr(self, 'depth'):
self.depth = 0
def addChild(self, name):
node = Node(name, self.graph)
node.depth = self.depth + 1
if node not in self.children:
self.children.append(node)
for attr in ('init', 'update', 'demo'):
if hasattr(self, attr):
setattr(node, attr, True)
self.children.sort(lambda x, y: cmp(x.name, y.name))
def __setattr__(self, name, value):
super(Singleton, self).__setattr__(name, value)
if name in ('init', 'update', 'demo'):
tools.config[name][self.name] = 1
for child in self.children:
setattr(child, name, value)
if name == 'depth':
for child in self.children:
setattr(child, name, value + 1)
def __iter__(self):
return itertools.chain(iter(self.children), *map(iter, self.children))
def __str__(self):
return self._pprint()
def _pprint(self, depth=0):
s = '%s\n' % self.name
for c in self.children:
s += '%s`-> %s' % (' ' * depth, c._pprint(depth+1))
return s
def get_module_path(module, downloaded=False):
"""Return the path of the given module."""
initialize_sys_path()
for adp in ad_paths:
if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
return opj(adp, module)
if downloaded:
return opj(_ad, module)
logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,))
return False
def get_module_filetree(module, dir='.'):
path = get_module_path(module)
if not path:
return False
dir = os.path.normpath(dir)
if dir == '.':
dir = ''
if dir.startswith('..') or (dir and dir[0] == '/'):
raise Exception('Cannot access file outside the module')
if not os.path.isdir(path):
# zipmodule
zip = zipfile.ZipFile(path + ".zip")
files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
else:
files = osutil.listdir(path, True)
tree = {}
for f in files:
if not f.startswith(dir):
continue
if dir:
f = f[len(dir)+int(not dir.endswith('/')):]
lst = f.split(os.sep)
current = tree
while len(lst) != 1:
current = current.setdefault(lst.pop(0), {})
current[lst.pop(0)] = None
return tree
def zip_directory(directory, b64enc=True, src=True):
"""Compress a directory
@param directory: The directory to compress
@param base64enc: if True the function will encode the zip file with base64
@param src: Integrate the source files
@return: a string containing the zip file
"""
RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I)
def _zippy(archive, path, src=True):
path = os.path.abspath(path)
base = os.path.basename(path)
for f in osutil.listdir(path, True):
bf = os.path.basename(f)
if not RE_exclude.search(bf) and (src or bf in ('__openerp__.py', '__terp__.py') or not bf.endswith('.py')):
archive.write(os.path.join(path, f), os.path.join(base, f))
archname = StringIO()
archive = PyZipFile(archname, "w", ZIP_DEFLATED)
# for Python 2.5, ZipFile.write() still expects 8-bit strings (2.6 converts to utf-8)
directory = tools.ustr(directory).encode('utf-8')
archive.writepy(directory)
_zippy(archive, directory, src=src)
archive.close()
archive_data = archname.getvalue()
archname.close()
if b64enc:
return base64.encodestring(archive_data)
return archive_data
def get_module_as_zip(modulename, b64enc=True, src=True):
"""Generate a module as zip file with the source or not and can do a base64 encoding
@param modulename: The module name
@param b64enc: if True the function will encode the zip file with base64
@param src: Integrate the source files
@return: a stream to store in a file-like object
"""
ap = get_module_path(str(modulename))
if not ap:
raise Exception('Unable to find path for module %s' % modulename)
ap = ap.encode('utf8')
if os.path.isfile(ap + '.zip'):
val = file(ap + '.zip', 'rb').read()
if b64enc:
val = base64.encodestring(val)
else:
val = zip_directory(ap, b64enc, src)
return val
def get_module_resource(module, *args):
"""Return the full path of a resource of the given module.
@param module: the module
@param args: the resource path components
@return: absolute path to the resource
"""
a = get_module_path(module)
if not a: return False
resource_path = opj(a, *args)
if zipfile.is_zipfile( a +'.zip') :
zip = zipfile.ZipFile( a + ".zip")
files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
resource_path = '/'.join(args)
if resource_path in files:
return opj(a, resource_path)
elif os.path.exists(resource_path):
return resource_path
return False
def get_modules():
"""Returns the list of module names
"""
def listdir(dir):
def clean(name):
name = os.path.basename(name)
if name[-4:] == '.zip':
name = name[:-4]
return name
def is_really_module(name):
name = opj(dir, name)
return os.path.isdir(name) or zipfile.is_zipfile(name)
return map(clean, filter(is_really_module, os.listdir(dir)))
plist = []
initialize_sys_path()
for ad in ad_paths:
plist.extend(listdir(ad))
return list(set(plist))
def load_information_from_description_file(module):
"""
:param module: The name of the module (sale, purchase, ...)
"""
for filename in ['__openerp__.py', '__terp__.py']:
description_file = get_module_resource(module, filename)
if description_file :
desc_f = tools.file_open(description_file)
try:
return eval(desc_f.read())
finally:
desc_f.close()
#TODO: refactor the logger in this file to follow the logging guidelines
# for 6.0
logging.getLogger('addons').debug('The module %s does not contain a description file:'\
'__openerp__.py or __terp__.py (deprecated)', module)
return {}
def get_modules_with_version():
modules = get_modules()
res = {}
for module in modules:
try:
info = load_information_from_description_file(module)
res[module] = "%s.%s" % (release.major_version, info['version'])
except Exception, e:
continue
return res
def create_graph(cr, module_list, force=None):
graph = Graph()
upgrade_graph(graph, cr, module_list, force)
return graph
def upgrade_graph(graph, cr, module_list, force=None):
if force is None:
force = []
packages = []
len_graph = len(graph)
for module in module_list:
mod_path = get_module_path(module)
terp_file = get_module_resource(module, '__openerp__.py')
if not terp_file:
terp_file = get_module_resource(module, '__terp__.py')
if not mod_path or not terp_file:
logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not found, skipped' % (module))
continue
if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
terp_f = tools.file_open(terp_file)
try:
info = eval(terp_f.read())
except Exception:
logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: eval file %s' % (module, terp_file))
raise
finally:
terp_f.close()
if info.get('installable', True):
packages.append((module, info.get('depends', []), info))
else:
logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable, skipped' % (module))
dependencies = dict([(p, deps) for p, deps, data in packages])
current, later = set([p for p, dep, data in packages]), set()
while packages and current > later:
package, deps, data = packages[0]
# if all dependencies of 'package' are already in the graph, add 'package' in the graph
if reduce(lambda x, y: x and y in graph, deps, True):
if not package in current:
packages.pop(0)
continue
later.clear()
current.remove(package)
graph.addNode(package, deps)
node = Node(package, graph)
node.data = data
for kind in ('init', 'demo', 'update'):
if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
setattr(node, kind, True)
else:
later.add(package)
packages.append((package, deps, data))
packages.pop(0)
graph.update_from_db(cr)
for package in later:
unmet_deps = filter(lambda p: p not in graph, dependencies[package])
logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps)))
result = len(graph) - len_graph
if result != len(module_list):
logger.notifyChannel('init', netsvc.LOG_WARNING, 'Not all modules have loaded.')
return result
def init_module_objects(cr, module_name, obj_list):
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: creating or updating database tables' % module_name)
todo = []
for obj in obj_list:
try:
result = obj._auto_init(cr, {'module': module_name})
except Exception, e:
raise
if result:
todo += result
if hasattr(obj, 'init'):
obj.init(cr)
cr.commit()
todo.sort()
for t in todo:
t[1](cr, *t[2])
cr.commit()
def register_class(m):
"""
Register module named m, if not already registered
"""
def log(e):
mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
msg = "Couldn't load %smodule %s" % (mt, m)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
global loaded
if m in loaded:
return
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
mod_path = get_module_path(m)
initialize_sys_path()
try:
zip_mod_path = mod_path + '.zip'
if not os.path.isfile(zip_mod_path):
fm = imp.find_module(m, ad_paths)
try:
imp.load_module(m, *fm)
finally:
if fm[0]:
fm[0].close()
else:
zimp = zipimport.zipimporter(zip_mod_path)
zimp.load_module(m)
except Exception, e:
log(e)
raise
else:
loaded.append(m)
class MigrationManager(object):
"""
This class manage the migration of modules
Migrations files must be python files containing a "migrate(cr, installed_version)" function.
Theses files must respect a directory tree structure: A 'migrations' folder which containt a
folder by version. Version can be 'module' version or 'server.module' version (in this case,
the files will only be processed by this version of the server). Python file names must start
by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
Example:
<moduledir>
`-- migrations
|-- 1.0
| |-- pre-update_table_x.py
| |-- pre-update_table_y.py
| |-- post-clean-data.py
| `-- README.txt # not processed
|-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
| |-- pre-delete_table_z.py
| `-- post-clean-data.py
`-- foo.py # not processed
This similar structure is generated by the maintenance module with the migrations files get by
the maintenance contract
"""
def __init__(self, cr, graph):
self.cr = cr
self.graph = graph
self.migrations = {}
self._get_files()
def _get_files(self):
"""
import addons.base.maintenance.utils as maintenance_utils
maintenance_utils.update_migrations_files(self.cr)
#"""
for pkg in self.graph:
self.migrations[pkg.name] = {}
if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
continue
self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
def migrate_module(self, pkg, stage):
assert stage in ('pre', 'post')
stageformat = {'pre': '[>%s]',
'post': '[%s>]',
}
if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
return
def convert_version(version):
if version.startswith(release.major_version) and version != release.major_version:
return version # the version number already containt the server version
return "%s.%s" % (release.major_version, version)
def _get_migration_versions(pkg):
def __get_dir(tree):
return [d for d in tree if tree[d] is not None]
versions = list(set(
__get_dir(self.migrations[pkg.name]['module']) +
__get_dir(self.migrations[pkg.name]['maintenance'])
))
versions.sort(key=lambda k: parse_version(convert_version(k)))
return versions
def _get_migration_files(pkg, version, stage):
""" return a list of tuple (module, file)
"""
m = self.migrations[pkg.name]
lst = []
mapping = {'module': opj(pkg.name, 'migrations'),
'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
}
for x in mapping.keys():
if version in m[x]:
for f in m[x][version]:
if m[x][version][f] is not None:
continue
if not f.startswith(stage + '-'):
continue
lst.append(opj(mapping[x], version, f))
lst.sort()
return lst
def mergedict(a, b):
a = a.copy()
a.update(b)
return a
from openerp.tools.parse_version import parse_version
parsed_installed_version = parse_version(pkg.installed_version or '')
current_version = parse_version(convert_version(pkg.data.get('version', '0')))
versions = _get_migration_versions(pkg)
for version in versions:
if parsed_installed_version < parse_version(convert_version(version)) <= current_version:
strfmt = {'addon': pkg.name,
'stage': stage,
'version': stageformat[stage] % version,
}
for pyfile in _get_migration_files(pkg, version, stage):
name, ext = os.path.splitext(os.path.basename(pyfile))
if ext.lower() != '.py':
continue
mod = fp = fp2 = None
try:
fp = tools.file_open(pyfile)
# imp.load_source need a real file object, so we create
# one from the file-like object we get from file_open
fp2 = os.tmpfile()
fp2.write(fp.read())
fp2.seek(0)
try:
mod = imp.load_source(name, pyfile, fp2)
logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt))
mod.migrate(self.cr, pkg.installed_version)
except ImportError:
logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt))
raise
except AttributeError:
logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt)
except:
raise
finally:
if fp:
fp.close()
if fp2:
fp2.close()
if mod:
del mod
log = logging.getLogger('init')
def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, **kwargs):
"""Migrates+Updates or Installs all module nodes from ``graph``
:param graph: graph of module nodes to load
:param status: status dictionary for keeping track of progress
:param perform_checks: whether module descriptors should be checked for validity (prints warnings
for same cases, and even raise osv_except if certificate is invalid)
:param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
:return: list of modules that were installed or updated
"""
def process_sql_file(cr, fp):
queries = fp.read().split(';')
for query in queries:
new_query = ' '.join(query.split())
if new_query:
cr.execute(new_query)
def load_init_update_xml(cr, m, idref, mode, kind):
for filename in package.data.get('%s_xml' % kind, []):
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, filename))
_, ext = os.path.splitext(filename)
fp = tools.file_open(opj(m, filename))
try:
if ext == '.csv':
noupdate = (kind == 'init')
tools.convert_csv_import(cr, m, os.path.basename(filename), fp.read(), idref, mode=mode, noupdate=noupdate)
elif ext == '.sql':
process_sql_file(cr, fp)
elif ext == '.yml':
tools.convert_yaml_import(cr, m, fp, idref, mode=mode, **kwargs)
else:
tools.convert_xml_import(cr, m, fp, idref, mode=mode, **kwargs)
finally:
fp.close()
def load_demo_xml(cr, m, idref, mode):
for xml in package.data.get('demo_xml', []):
name, ext = os.path.splitext(xml)
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, xml))
fp = tools.file_open(opj(m, xml))
try:
if ext == '.csv':
tools.convert_csv_import(cr, m, os.path.basename(xml), fp.read(), idref, mode=mode, noupdate=True)
elif ext == '.yml':
tools.convert_yaml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
else:
tools.convert_xml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
finally:
fp.close()
def load_data(cr, module_name, id_map, mode):
_load_data(cr, module_name, id_map, mode, 'data')
def load_demo(cr, module_name, id_map, mode):
_load_data(cr, module_name, id_map, mode, 'demo')
def load_test(cr, module_name, id_map, mode):
cr.commit()
if not tools.config.options['test_disable']:
try:
_load_data(cr, module_name, id_map, mode, 'test')
except Exception, e:
logging.getLogger('test').exception('Tests failed to execute in module %s', module_name)
finally:
if tools.config.options['test_commit']:
cr.commit()
else:
cr.rollback()
def _load_data(cr, module_name, id_map, mode, kind):
for filename in package.data.get(kind, []):
noupdate = (kind == 'demo')
_, ext = os.path.splitext(filename)
log.info("module %s: loading %s", module_name, filename)
pathname = os.path.join(module_name, filename)
file = tools.file_open(pathname)
try:
if ext == '.sql':
process_sql_file(cr, file)
elif ext == '.csv':
noupdate = (kind == 'init')
tools.convert_csv_import(cr, module_name, pathname, file.read(), id_map, mode, noupdate)
elif ext == '.yml':
tools.convert_yaml_import(cr, module_name, file, id_map, mode, noupdate)
else:
tools.convert_xml_import(cr, module_name, file, id_map, mode, noupdate)
finally:
file.close()
# **kwargs is passed directly to convert_xml_import
if not status:
status = {}
status = status.copy()
processed_modules = []
statusi = 0
pool = pooler.get_pool(cr.dbname)
migrations = MigrationManager(cr, graph)
modobj = None
logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
for package in graph:
if skip_modules and package.name in skip_modules:
continue
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
migrations.migrate_module(package, 'pre')
register_class(package.name)
modules = pool.instanciate(package.name, cr)
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
init_module_objects(cr, package.name, modules)
cr.commit()
for package in graph:
status['progress'] = (float(statusi)+0.1) / len(graph)
m = package.name
mid = package.id
if skip_modules and m in skip_modules:
continue
if modobj is None:
modobj = pool.get('ir.module.module')
if modobj and perform_checks:
modobj.check(cr, 1, [mid])
idref = {}
status['progress'] = (float(statusi)+0.4) / len(graph)
mode = 'update'
if hasattr(package, 'init') or package.state == 'to install':
mode = 'init'
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
for kind in ('init', 'update'):
if package.state=='to upgrade':
# upgrading the module information
modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
load_init_update_xml(cr, m, idref, mode, kind)
load_data(cr, m, idref, mode)
if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
status['progress'] = (float(statusi)+0.75) / len(graph)
load_demo_xml(cr, m, idref, mode)
load_demo(cr, m, idref, mode)
cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
# launch tests only in demo mode, as most tests will depend
# on demo data. Other tests can be added into the regular
# 'data' section, but should probably not alter the data,
# as there is no rollback.
load_test(cr, m, idref, mode)
processed_modules.append(package.name)
migrations.migrate_module(package, 'post')
if modobj:
ver = release.major_version + '.' + package.data.get('version', '1.0')
# Set new modules and dependencies
modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
cr.commit()
# Update translations for all installed languages
modobj.update_translations(cr, 1, [mid], None)
cr.commit()
package.state = 'installed'
for kind in ('init', 'demo', 'update'):
if hasattr(package, kind):
delattr(package, kind)
statusi += 1
cr.commit()
return processed_modules
def _check_module_names(cr, module_names):
mod_names = set(module_names)
if 'base' in mod_names:
# ignore dummy 'all' module
if 'all' in mod_names:
mod_names.remove('all')
if mod_names:
cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
if cr.dictfetchone()['count'] != len(mod_names):
# find out what module name(s) are incorrect:
cr.execute("SELECT name FROM ir_module_module")
incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
def load_modules(db, force_demo=False, status=None, update_module=False):
initialize_sys_path()
# Backward compatibility: addons don't have to import openerp.xxx, they still can import xxx
for k, v in list(sys.modules.items()):
if k.startswith('openerp.') and sys.modules.get(k[8:]) is None:
sys.modules[k[8:]] = v
if not status:
status = {}
cr = db.cursor()
if cr:
cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
if len(cr.fetchall())==0:
logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
tools.init_db(cr)
tools.config["init"]["all"] = 1
tools.config['update']['all'] = 1
if not tools.config['without_demo']:
tools.config["demo"]['all'] = 1
force = []
if force_demo:
force.append('demo')
# This is a brand new pool, just created in pooler.get_db_and_pool()
pool = pooler.get_pool(cr.dbname)
try:
processed_modules = []
report = tools.assertion_report()
# NOTE: Try to also load the modules that have been marked as uninstallable previously...
STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
if 'base' in tools.config['update'] or 'all' in tools.config['update']:
cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
# STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
graph = create_graph(cr, ['base'], force)
if not graph:
logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report))
if tools.config['load_language']:
for lang in tools.config['load_language'].split(','):
tools.load_language(cr, lang)
# STEP 2: Mark other modules to be loaded/updated
if update_module:
modobj = pool.get('ir.module.module')
logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
if ('base' in tools.config['init']) or ('base' in tools.config['update']):
modobj.update_list(cr, 1)
_check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
mods = [k for k in tools.config['init'] if tools.config['init'][k]]
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
if ids:
modobj.button_install(cr, 1, ids)
mods = [k for k in tools.config['update'] if tools.config['update'][k]]
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
if ids:
modobj.button_upgrade(cr, 1, ids)
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
STATES_TO_LOAD += ['to install']
# STEP 3: Load marked modules (skipping base which was done in STEP 1)
loop_guardrail = 0
while True:
loop_guardrail += 1
if loop_guardrail > 100:
raise ValueError('Possible recursive module tree detected, aborting.')
cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
module_list = [name for (name,) in cr.fetchall() if name not in graph]
if not module_list:
break
new_modules_in_graph = upgrade_graph(graph, cr, module_list, force)
if new_modules_in_graph == 0:
# nothing to load
break
logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
# load custom models
cr.execute('select model from ir_model where state=%s', ('manual',))
for model in cr.dictfetchall():
pool.get('ir.model').instanciate(cr, 1, model['model'], {})
# STEP 4: Finish and cleanup
if processed_modules:
cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
for (model, name) in cr.fetchall():
model_obj = pool.get(model)
if model_obj and not isinstance(model_obj, osv.osv.osv_memory):
logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
# Temporary warning while we remove access rights on osv_memory objects, as they have
# been replaced by owner-only access rights
cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
for (model, name) in cr.fetchall():
model_obj = pool.get(model)
if isinstance(model_obj, osv.osv.osv_memory):
logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
cr.execute("SELECT model from ir_model")
for (model,) in cr.fetchall():
obj = pool.get(model)
if obj:
obj._check_removed_columns(cr, log=True)
else:
logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model)
# Cleanup orphan records
pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
if report.get_report():
logger.notifyChannel('init', netsvc.LOG_INFO, report)
for kind in ('init', 'demo', 'update'):
tools.config[kind] = {}
cr.commit()
if update_module:
cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
for mod_id, mod_name in cr.fetchall():
cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
for rmod, rid in cr.fetchall():
uid = 1
rmod_module= pool.get(rmod)
if rmod_module:
rmod_module.unlink(cr, uid, [rid])
else:
logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
cr.commit()
#
# TODO: remove menu without actions of children
#
while True:
cr.execute('''delete from
ir_ui_menu
where
(id not IN (select parent_id from ir_ui_menu where parent_id is not null))
and
(id not IN (select res_id from ir_values where model='ir.ui.menu'))
and
(id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
cr.commit()
if not cr.rowcount:
break
else:
logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
cr.commit()
finally:
cr.close()
"""
# get_module_path is used only by base_module_quality
from openerp.modules import get_module_resource, get_module_path
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -94,6 +94,8 @@
'test/bug_lp541545.xml',
'test/test_osv_expression.yml',
'test/test_ir_rule.yml', # <-- These tests modify/add/delete ir_rules.
'test/test_config_users.yml',
'test/test_ir_values.yml',
],
'installable': True,
'active': True,

View File

@ -45,6 +45,7 @@ CREATE TABLE ir_model_fields (
view_load boolean,
relate boolean default False,
relation_field varchar(128),
translate boolean default False,
primary key(id)
);

View File

@ -1618,29 +1618,44 @@
<field eval="time.strftime('%Y-01-01')" name="name"/>
</record>
<record id="CFA" model="res.currency">
<!-- There are in fact 2 CFA currencies:
- XAF for the Central African CFA franc
- XOF for the West African CFA franc
As they are interchangeable, we probably only need one.
XOF appears to have "CFA" as symbol, while "XAF" is "FCFA",
so let's pick XOF as the ISO Code, to have a code-symbol match.
We can keep CFA as XML ID to show that we only have one currency,
there is no possible conflict, as no other currency currently
has CFA as ISO Code.
See http://en.wikipedia.org/wiki/CFA_franc
http://en.wikipedia.org/wiki/ISO_4217
-->
<record id="XOF" model="res.currency">
<field name="name">XOF</field>
<field name="symbol">CFA</field>
<field name="rounding">1</field>
<field name="accuracy">4</field>
<field name="company_id" ref="main_company"/>
</record>
<record id="rateCFA" model="res.currency.rate">
<record id="rateXOF" model="res.currency.rate">
<field name="rate">655.957</field>
<field name="currency_id" ref="CFA"/>
<field name="currency_id" ref="XOF"/>
<field eval="time.strftime('%Y-01-01')" name="name"/>
</record>
<record id="XAF" model="res.currency">
<field name="name">XAF</field>
<field name="symbol">FCFA</field>
<field name="rounding">1</field>
<field name="accuracy">4</field>
<field name="company_id" ref="main_company"/>
</record>
<record id="rateXAF" model="res.currency.rate">
<field name="rate">655.957</field>
<field name="currency_id" ref="XAF"/>
<field eval="time.strftime('%Y-01-01')" name="name"/>
</record>
<record id="UGX" model="res.currency">
<field name="name">UGX</field>
<field name="symbol">USh</field>
<field name="rounding">1</field>
<field name="accuracy">4</field>
<field name="company_id" ref="main_company"/>
</record>
<record id="rateUGX" model="res.currency.rate">
<field name="rate">3401.91388</field>
<field name="currency_id" ref="UGX"/>
<field eval="time.strftime('%Y-01-01')" name="name"/>
</record>
</data>
</openerp>

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2010-06-06 04:19+0000\n"
"Last-Translator: Vladimír Burian <vladimir.burian@email.cz>\n"
"PO-Revision-Date: 2011-05-18 16:53+0000\n"
"Last-Translator: Jan B. Krejčí <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-01-12 04:46+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
"X-Launchpad-Export-Date: 2011-05-19 06:22+0000\n"
"X-Generator: Launchpad (build 12959)\n"
#. module: base
#: view:ir.filters:0
@ -23,22 +23,22 @@ msgstr ""
#: field:ir.rule,domain_force:0
#: field:res.partner.title,domain:0
msgid "Domain"
msgstr ""
msgstr "Doména"
#. module: base
#: model:res.country,name:base.sh
msgid "Saint Helena"
msgstr ""
msgstr "Svatá Helena"
#. module: base
#: view:ir.actions.report.xml:0
msgid "Other Configuration"
msgstr ""
msgstr "Ostatní nastavení"
#. module: base
#: selection:ir.property,type:0
msgid "DateTime"
msgstr ""
msgstr "Datum a čas"
#. module: base
#: code:addons/fields.py:534
@ -58,12 +58,12 @@ msgstr "Metadata"
#: field:ir.ui.view,arch:0
#: field:ir.ui.view.custom,arch:0
msgid "View Architecture"
msgstr "Architektura náhledu(View Architecture)"
msgstr "Architektura zobrazení"
#. module: base
#: field:base.language.import,code:0
msgid "Code (eg:en__US)"
msgstr "Kód (eg:en__US)"
msgstr "Kód (např. en_US)"
#. module: base
#: view:workflow:0
@ -73,12 +73,12 @@ msgstr "Kód (eg:en__US)"
#: field:workflow.transition,wkf_id:0
#: field:workflow.workitem,wkf_id:0
msgid "Workflow"
msgstr "Workflow(Workflow)"
msgstr "Pracovní postup"
#. module: base
#: view:partner.sms.send:0
msgid "SMS - Gateway: clickatell"
msgstr "Brána: clickatell(Gateway: clickatell)"
msgstr "SMS brána Clickatell"
#. module: base
#: selection:base.language.install,lang:0

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2011-01-19 14:42+0000\n"
"Last-Translator: Juan Pizarro <jpizarrom@gmail.com>\n"
"PO-Revision-Date: 2011-05-11 04:20+0000\n"
"Last-Translator: doingit.cl <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-01-20 04:50+0000\n"
"X-Generator: Launchpad (build 12177)\n"
"X-Launchpad-Export-Date: 2011-05-12 04:34+0000\n"
"X-Generator: Launchpad (build 12959)\n"
#. module: base
#: view:ir.filters:0
@ -73,7 +73,7 @@ msgstr "es_CL"
#: field:workflow.transition,wkf_id:0
#: field:workflow.workitem,wkf_id:0
msgid "Workflow"
msgstr "Flujo"
msgstr "Flujo de Trabajo"
#. module: base
#: view:partner.sms.send:0
@ -98,7 +98,7 @@ msgstr "Spanish (VE) / Español (VE)"
#. module: base
#: field:ir.actions.server,wkf_model_id:0
msgid "Workflow On"
msgstr "Flujo sobre"
msgstr "Flujo de trabajo activo"
#. module: base
#: field:ir.actions.act_window,display_menu_tip:0
@ -140,7 +140,7 @@ msgstr "Ventana destino"
#: code:addons/base/res/res_user.py:507
#, python-format
msgid "Warning!"
msgstr ""
msgstr "¡Advertencia!"
#. module: base
#: code:addons/base/ir/ir_model.py:304
@ -192,13 +192,13 @@ msgstr ""
#. module: base
#: field:ir.sequence,number_increment:0
msgid "Increment Number"
msgstr "Incremento del número"
msgstr "Incrementar número"
#. module: base
#: model:ir.actions.act_window,name:base.action_res_company_tree
#: model:ir.ui.menu,name:base.menu_action_res_company_tree
msgid "Company's Structure"
msgstr "Árbol de la compañía"
msgstr "Estructura de la compañia"
#. module: base
#: selection:base.language.install,lang:0
@ -247,7 +247,7 @@ msgstr "Tamaño máx."
#. module: base
#: field:res.partner.address,name:0
msgid "Contact Name"
msgstr "Nombre"
msgstr "Nombre de contacto"
#. module: base
#: code:addons/base/module/wizard/base_export_language.py:56
@ -267,7 +267,7 @@ msgstr "El nombre del idioma debe de ser único"
#. module: base
#: selection:res.request,state:0
msgid "active"
msgstr "activa"
msgstr "activo"
#. module: base
#: field:ir.actions.wizard,wiz_name:0
@ -283,7 +283,7 @@ msgstr ""
#. module: base
#: field:res.partner,credit_limit:0
msgid "Credit Limit"
msgstr "Crédito concedido"
msgstr "Límite de crédito"
#. module: base
#: field:ir.model.data,date_update:0
@ -303,7 +303,7 @@ msgstr "Objeto origen"
#. module: base
#: view:ir.actions.todo:0
msgid "Config Wizard Steps"
msgstr "Pasos de los asistentes de configuración"
msgstr "Configurar pasos del asistente"
#. module: base
#: model:ir.model,name:base.model_ir_ui_view_sc
@ -328,13 +328,13 @@ msgstr "Grupo"
#: field:ir.translation,name:0
#: field:res.partner.bank.type.field,name:0
msgid "Field Name"
msgstr "Nombre campo"
msgstr "Nombre del Campo"
#. module: base
#: wizard_view:server.action.create,init:0
#: wizard_field:server.action.create,init,type:0
msgid "Select Action Type"
msgstr "Seleccione tipo de acción"
msgstr "Seleccione el tipo de acción"
#. module: base
#: model:res.country,name:base.tv
@ -423,7 +423,7 @@ msgstr "Texto"
#. module: base
#: field:res.country,name:0
msgid "Country Name"
msgstr "Nombre de país"
msgstr "Nombre del País"
#. module: base
#: model:res.country,name:base.co
@ -495,7 +495,8 @@ msgstr ""
#. module: base
#: help:ir.actions.server,action_id:0
msgid "Select the Action Window, Report, Wizard to be executed."
msgstr "Seleccione la acción ventana, informe o asistente que se ejecutará."
msgstr ""
"Seleccione la ventana de acción, informe o asistente a ser ejecutado."
#. module: base
#: view:res.config.users:0
@ -517,13 +518,13 @@ msgstr "Descripción del modelo"
msgid ""
"Optional model name of the objects on which this action should be visible"
msgstr ""
"Nombre del modelo opcional de los objetos en los cuales esta acción debería "
"de ser visible."
"Nombre del modelo opcional de los objetos en los que esta acción debería ser "
"visible"
#. module: base
#: field:workflow.transition,trigger_expr_id:0
msgid "Trigger Expression"
msgstr "Expresión del disparo"
msgstr "Expresión del Disparador"
#. module: base
#: model:res.country,name:base.jo
@ -619,7 +620,7 @@ msgstr "Importación de idioma"
#. module: base
#: model:ir.model,name:base.model_res_config_users
msgid "res.config.users"
msgstr "res.config.usuarios"
msgstr "res.config.users"
#. module: base
#: selection:base.language.install,lang:0
@ -634,7 +635,7 @@ msgstr "Oportunidades"
#. module: base
#: model:ir.model,name:base.model_base_language_export
msgid "base.language.export"
msgstr "base.idioma.exportar"
msgstr "base.language.export"
#. module: base
#: model:res.country,name:base.pg
@ -698,7 +699,7 @@ msgid ""
"Groups are used to define access rights on objects and the visibility of "
"screens and menus"
msgstr ""
"Los grupos son utilizados para definir derechos de accesos sobre objteos y "
"Los grupos son utilizados para definir derechos de accesos sobre objetos y "
"para la visibilidad de pantallas y menús"
#. module: base
@ -721,7 +722,7 @@ msgstr "Omán"
#: model:ir.actions.act_window,name:base.action_payterm_form
#: model:ir.model,name:base.model_res_payterm
msgid "Payment term"
msgstr "Plazo de pago"
msgstr "Condiciones de pago"
#. module: base
#: model:res.country,name:base.nu
@ -758,7 +759,7 @@ msgstr "¡El método unlink (eliminar) no está implementado en este objeto!"
#: model:ir.actions.act_window,name:base.act_menu_create
#: view:wizard.ir.model.menu.create:0
msgid "Create Menu"
msgstr "Crear menú"
msgstr "Crear Menú"
#. module: base
#: model:res.country,name:base.in
@ -895,7 +896,7 @@ msgstr "Contratos"
#. module: base
#: selection:base.language.install,lang:0
msgid "Spanish (AR) / Español (AR)"
msgstr "Español (AR) / Español (AR)"
msgstr "Spanish (AR) / Español (AR)"
#. module: base
#: model:res.country,name:base.ug
@ -937,7 +938,7 @@ msgstr ""
#. module: base
#: selection:base.language.install,lang:0
msgid "Spanish (GT) / Español (GT)"
msgstr "Copy text \t Spanish (GT) / Español (GT)"
msgstr "Spanish (GT) / Español (GT)"
#. module: base
#: view:res.lang:0
@ -954,7 +955,7 @@ msgstr ""
#: field:ir.module.module,website:0
#: field:res.partner,website:0
msgid "Website"
msgstr "Sitio web"
msgstr "Sitio Web"
#. module: base
#: model:res.country,name:base.gs
@ -969,7 +970,7 @@ msgstr "URL de la acción"
#. module: base
#: field:base.module.import,module_name:0
msgid "Module Name"
msgstr "Nombre de módulo"
msgstr "Nombre del módulo"
#. module: base
#: model:res.country,name:base.mh
@ -1058,7 +1059,7 @@ msgstr "Versión"
#: field:ir.model.access,perm_read:0
#: view:ir.rule:0
msgid "Read Access"
msgstr "Permiso para leer"
msgstr "Permisos de lectura"
#. module: base
#: model:ir.model,name:base.model_ir_exports
@ -1126,7 +1127,7 @@ msgstr "Crear _Menú"
#. module: base
#: field:res.payterm,name:0
msgid "Payment Term (short name)"
msgstr "Plazo de pago (nombre abreviado)"
msgstr "Condiciones de pago"
#. module: base
#: model:ir.model,name:base.model_res_bank
@ -1138,7 +1139,7 @@ msgstr "Banco"
#. module: base
#: model:ir.model,name:base.model_ir_exports_line
msgid "ir.exports.line"
msgstr "ir.export.linea"
msgstr "ir.exports.line"
#. module: base
#: help:base.language.install,overwrite:0
@ -3743,6 +3744,8 @@ msgid ""
"For one2many fields, the field on the target model that implement the "
"opposite many2one relationship"
msgstr ""
"Para campos one2many, el campo modelo destino que implementa la relación "
"inversa many2one"
#. module: base
#: model:ir.model,name:base.model_ir_actions_act_window_view
@ -4056,7 +4059,7 @@ msgstr "ir.acciones.url"
#. module: base
#: model:res.widget,title:base.currency_converter_widget
msgid "Currency Converter"
msgstr ""
msgstr "Conversor de divisas"
#. module: base
#: code:addons/orm.py:156
@ -5217,7 +5220,7 @@ msgstr "Nigeria"
#: code:addons/base/ir/ir_model.py:250
#, python-format
msgid "For selection fields, the Selection Options must be given!"
msgstr ""
msgstr "¡Para los campos selección, debe indicar las opciones de selección!"
#. module: base
#: model:ir.actions.act_window,name:base.action_partner_sms_send
@ -7959,6 +7962,9 @@ msgid ""
"defining a list of (key, label) pairs. For example: "
"[('blue','Blue'),('yellow','Yellow')]"
msgstr ""
"Lista de opciones para un campo de selección, se especifica como una "
"expresión Python definiendo una lista de pares (clave, etiqueta). Por "
"ejemplo: [('blue','Blue'),('yellow','Yellow')]"
#. module: base
#: selection:base.language.export,state:0

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2011-03-22 22:41+0000\n"
"Last-Translator: Pierre Burnier <Unknown>\n"
"PO-Revision-Date: 2011-05-16 16:57+0000\n"
"Last-Translator: lolivier <olivier.lenoir@free.fr>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-03-23 06:23+0000\n"
"X-Generator: Launchpad (build 12559)\n"
"X-Launchpad-Export-Date: 2011-05-17 06:04+0000\n"
"X-Generator: Launchpad (build 12959)\n"
#. module: base
#: view:ir.filters:0
@ -8647,7 +8647,7 @@ msgstr "Danish / Dansk"
#. module: base
#: selection:ir.model.fields,select_level:0
msgid "Advanced Search (deprecated)"
msgstr ""
msgstr "Recherche avancée (obsolète)"
#. module: base
#: model:res.country,name:base.cx

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2011-03-31 14:18+0000\n"
"Last-Translator: Grzegorz Grzelak (OpenGLOBE.pl) <grzegorz@openglobe.pl>\n"
"PO-Revision-Date: 2011-05-14 20:16+0000\n"
"Last-Translator: Mariusz Wójtowicz <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-04-01 06:04+0000\n"
"X-Generator: Launchpad (build 12559)\n"
"X-Launchpad-Export-Date: 2011-05-15 05:37+0000\n"
"X-Generator: Launchpad (build 12959)\n"
#. module: base
#: view:ir.filters:0
@ -171,7 +171,7 @@ msgstr "Swaziland"
#: code:addons/orm.py:3653
#, python-format
msgid "created."
msgstr ""
msgstr "utworzona."
#. module: base
#: model:res.partner.category,name:base.res_partner_category_woodsuppliers0

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: pt_BR\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2011-04-23 20:42+0000\n"
"PO-Revision-Date: 2011-05-22 04:28+0000\n"
"Last-Translator: Emerson <Unknown>\n"
"Language-Team: <pt@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-04-24 05:52+0000\n"
"X-Generator: Launchpad (build 12758)\n"
"X-Launchpad-Export-Date: 2011-05-23 05:33+0000\n"
"X-Generator: Launchpad (build 12959)\n"
#. module: base
#: view:ir.filters:0
@ -153,6 +153,9 @@ msgid ""
"Properties of base fields cannot be altered in this manner! Please modify "
"them through Python code, preferably through a custom addon!"
msgstr ""
"As propriedades dos campos base não podem ser alterados desta maneira! Por "
"favor, faça a modificação através de código Python e, preferencialmente, via "
"um addon customizado."
#. module: base
#: code:addons/osv.py:133
@ -837,7 +840,7 @@ msgstr "Painel de Recursos Humanps"
#: code:addons/base/res/res_user.py:507
#, python-format
msgid "Setting empty passwords is not allowed for security reasons!"
msgstr ""
msgstr "Senhas vazias não são permitidas por questões de segurança!"
#. module: base
#: selection:ir.actions.server,state:0
@ -976,7 +979,7 @@ msgstr "Ilhas Marshall"
#: code:addons/base/ir/ir_model.py:328
#, python-format
msgid "Changing the model of a field is forbidden!"
msgstr ""
msgstr "Alteração de modelo de campo é proibido!"
#. module: base
#: model:res.country,name:base.ht
@ -2025,7 +2028,7 @@ msgstr "Coreâno (KR) / 한국어 (KR)"
#. module: base
#: help:ir.model.fields,model:0
msgid "The technical name of the model this field belongs to"
msgstr ""
msgstr "O nome técnico do modelo deste campo pertence a"
#. module: base
#: field:ir.actions.server,action_id:0
@ -3156,7 +3159,7 @@ msgstr "Workflows"
#. module: base
#: field:ir.translation,xml_id:0
msgid "XML Id"
msgstr ""
msgstr "Id XML"
#. module: base
#: model:ir.actions.act_window,name:base.action_config_user_form
@ -3773,7 +3776,7 @@ msgstr "Inglês (CA)"
#. module: base
#: model:ir.model,name:base.model_publisher_warranty_contract
msgid "publisher_warranty.contract"
msgstr ""
msgstr "publisher_warranty.contract"
#. module: base
#: model:res.country,name:base.et
@ -4200,7 +4203,7 @@ msgstr "Menus"
#. module: base
#: selection:base.language.install,lang:0
msgid "Serbian (Latin) / srpski"
msgstr ""
msgstr "Serbian (Latin) / srpski"
#. module: base
#: model:res.country,name:base.il
@ -4255,7 +4258,7 @@ msgstr "Subfluxo"
#. module: base
#: model:ir.model,name:base.model_res_config
msgid "res.config"
msgstr ""
msgstr "res.config"
#. module: base
#: field:workflow.transition,signal:0
@ -4306,7 +4309,7 @@ msgstr "Reino Unido"
#: view:res.config.users:0
#: view:res.config.view:0
msgid "res_config_contents"
msgstr ""
msgstr "res_config_contents"
#. module: base
#: help:res.partner.category,active:0
@ -4375,7 +4378,7 @@ msgstr ""
#. module: base
#: view:base.language.import:0
msgid "- module,type,name,res_id,src,value"
msgstr ""
msgstr "- módulo,tipo,nome,rec_id,src,valor"
#. module: base
#: selection:base.language.install,lang:0
@ -4578,7 +4581,7 @@ msgstr "Não implementado"
#. module: base
#: model:ir.model,name:base.model_res_widget_user
msgid "res.widget.user"
msgstr ""
msgstr "res.widget.user"
#. module: base
#: field:res.partner.category,complete_name:0
@ -5091,7 +5094,7 @@ msgstr "Campo de objeto"
#. module: base
#: selection:base.language.install,lang:0
msgid "Spanish (PE) / Español (PE)"
msgstr ""
msgstr "Spanish (PE) / Español (PE)"
#. module: base
#: selection:base.language.install,lang:0
@ -5142,7 +5145,7 @@ msgstr "Ligar eventos a ações"
#. module: base
#: model:ir.model,name:base.model_base_update_translations
msgid "base.update.translations"
msgstr ""
msgstr "base.update.translations"
#. module: base
#: field:ir.module.category,parent_id:0
@ -5453,7 +5456,7 @@ msgstr "Continuar"
#. module: base
#: selection:base.language.install,lang:0
msgid "Thai / ภาษาไทย"
msgstr ""
msgstr "Thai / ภาษาไทย"
#. module: base
#: code:addons/orm.py:158
@ -5536,7 +5539,7 @@ msgstr "Empresa Padrão"
#. module: base
#: selection:base.language.install,lang:0
msgid "Spanish (EC) / Español (EC)"
msgstr ""
msgstr "Spanish (EC) / Español (EC)"
#. module: base
#: help:ir.ui.view,xml_id:0
@ -5745,7 +5748,7 @@ msgstr ""
#. module: base
#: selection:base.language.install,lang:0
msgid "Flemish (BE) / Vlaams (BE)"
msgstr ""
msgstr "Flemish (BE) / Vlaams (BE)"
#. module: base
#: field:ir.cron,interval_number:0
@ -5810,7 +5813,7 @@ msgstr "Atalhos Customizados"
#. module: base
#: selection:base.language.install,lang:0
msgid "Vietnamese / Tiếng Việt"
msgstr ""
msgstr "Vietnamese / Tiếng Việt"
#. module: base
#: model:res.country,name:base.dz
@ -5825,7 +5828,7 @@ msgstr "Bélgica"
#. module: base
#: model:ir.model,name:base.model_osv_memory_autovacuum
msgid "osv_memory.autovacuum"
msgstr ""
msgstr "osv_memory.autovacuum"
#. module: base
#: field:base.language.export,lang:0
@ -5863,7 +5866,7 @@ msgstr "%H - Hora (Relógio 24 horas) [00,23]."
#. module: base
#: model:ir.model,name:base.model_res_widget
msgid "res.widget"
msgstr ""
msgstr "res.widget"
#. module: base
#: code:addons/base/ir/ir_model.py:258
@ -5928,7 +5931,7 @@ msgstr "Zona Neutra"
#. module: base
#: selection:base.language.install,lang:0
msgid "Hindi / हिंदी"
msgstr ""
msgstr "Hindi / हिंदी"
#. module: base
#: view:ir.model:0
@ -6133,7 +6136,7 @@ msgstr "Base"
#. module: base
#: selection:base.language.install,lang:0
msgid "Telugu / తెలుగు"
msgstr ""
msgstr "Telugu / తెలుగు"
#. module: base
#: model:res.country,name:base.lr
@ -6178,7 +6181,7 @@ msgstr "Código"
#. module: base
#: model:ir.model,name:base.model_res_config_installer
msgid "res.config.installer"
msgstr ""
msgstr "res.config.installer"
#. module: base
#: model:res.country,name:base.mc
@ -6222,7 +6225,7 @@ msgstr "Códigos seqüenciais"
#. module: base
#: selection:base.language.install,lang:0
msgid "Spanish (CO) / Español (CO)"
msgstr ""
msgstr "Spanish (CO) / Español (CO)"
#. module: base
#: view:base.module.configuration:0
@ -6257,7 +6260,7 @@ msgstr "França"
#. module: base
#: model:ir.model,name:base.model_res_log
msgid "res.log"
msgstr ""
msgstr "res.log"
#. module: base
#: help:ir.translation,module:0
@ -6347,7 +6350,7 @@ msgstr "res.request"
#. module: base
#: view:ir.model:0
msgid "In Memory"
msgstr ""
msgstr "Em Memória"
#. module: base
#: view:ir.actions.todo:0
@ -6374,6 +6377,7 @@ msgstr "Ltd"
msgid ""
"The group that a user must have to be authorized to validate this transition."
msgstr ""
"O grupo ao qual o usuário deve ter autorização para validar esta transição."
#. module: base
#: constraint:res.config.users:0
@ -6456,7 +6460,7 @@ msgstr "Visão de Busca"
#. module: base
#: sql_constraint:res.lang:0
msgid "The code of the language must be unique !"
msgstr ""
msgstr "O código do idioma deve ser único!"
#. module: base
#: model:ir.actions.act_window,name:base.action_attachment
@ -6573,6 +6577,7 @@ msgstr ""
#: view:base.language.export:0
msgid "To browse official translations, you can start with these links:"
msgstr ""
"Para consultar as traduções oficiais, você pode começar nestes links:"
#. module: base
#: code:addons/base/ir/ir_model.py:484
@ -6601,7 +6606,7 @@ msgstr "Versão instalada"
#. module: base
#: selection:base.language.install,lang:0
msgid "Mongolian / монгол"
msgstr ""
msgstr "Mongolian / монгол"
#. module: base
#: model:res.country,name:base.mr

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2011-04-27 12:17+0000\n"
"Last-Translator: Chertykov Denis <chertykov@gmail.com>\n"
"PO-Revision-Date: 2011-05-26 04:57+0000\n"
"Last-Translator: Pomazan Bogdan <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-04-28 05:48+0000\n"
"X-Generator: Launchpad (build 12758)\n"
"X-Launchpad-Export-Date: 2011-05-27 04:45+0000\n"
"X-Generator: Launchpad (build 12959)\n"
#. module: base
#: view:ir.filters:0
@ -23,7 +23,7 @@ msgstr ""
#: field:ir.rule,domain_force:0
#: field:res.partner.title,domain:0
msgid "Domain"
msgstr "Домен"
msgstr "Ограничение"
#. module: base
#: model:res.country,name:base.sh
@ -256,7 +256,7 @@ msgstr "Макс. размер"
#. module: base
#: field:res.partner.address,name:0
msgid "Contact Name"
msgstr "Название договора"
msgstr "Имя контакта"
#. module: base
#: code:addons/base/module/wizard/base_export_language.py:56
@ -613,7 +613,7 @@ msgstr "Королевство Камбоджа"
#: model:ir.ui.menu,name:base.menu_ir_sequence_form
#: model:ir.ui.menu,name:base.next_id_5
msgid "Sequences"
msgstr "Последовательности"
msgstr "Нумерация"
#. module: base
#: model:ir.model,name:base.model_base_language_import
@ -685,7 +685,7 @@ msgstr "Импорт / Экспорт"
msgid ""
"Optional domain filtering of the destination data, as a Python expression"
msgstr ""
"Дополнительная фильтрация данных назначения, в виде выражения на Python"
"Дополнительная фильтрация выходных данных, в виде выражения на Python"
#. module: base
#: model:ir.actions.act_window,name:base.action_view_base_module_upgrade
@ -1139,7 +1139,7 @@ msgstr "Банк"
#. module: base
#: model:ir.model,name:base.model_ir_exports_line
msgid "ir.exports.line"
msgstr "Строки экспорта"
msgstr "ir.exports.line"
#. module: base
#: help:base.language.install,overwrite:0
@ -1191,7 +1191,7 @@ msgstr ""
#: field:res.config.users,login:0
#: field:res.users,login:0
msgid "Login"
msgstr "Вход"
msgstr "Логин"
#. module: base
#: view:ir.actions.server:0
@ -2069,7 +2069,7 @@ msgstr "Куба"
#. module: base
#: model:ir.model,name:base.model_res_partner_event
msgid "res.partner.event"
msgstr "События партнера"
msgstr "res.partner.event"
#. module: base
#: model:res.widget,title:base.facebook_widget
@ -2572,7 +2572,7 @@ msgstr "RML (устаревший - используйте Отчет)"
#. module: base
#: view:ir.rule:0
msgid "Record rules"
msgstr "Правила записи"
msgstr "Ограничение доступа"
#. module: base
#: view:ir.property:0
@ -2995,7 +2995,7 @@ msgstr "Испанский (HN) / Español (HN)"
#. module: base
#: view:ir.sequence.type:0
msgid "Sequence Type"
msgstr "Тип последовательности"
msgstr "Тип нумерации"
#. module: base
#: view:ir.ui.view.custom:0
@ -3280,7 +3280,7 @@ msgstr "Договор уже зарегистрирован в системе."
#. module: base
#: help:ir.sequence,suffix:0
msgid "Suffix value of the record for the sequence"
msgstr "Суффикс записи для последовательности"
msgstr "Суффикс записи для нумерации"
#. module: base
#: selection:base.language.install,lang:0
@ -3528,10 +3528,11 @@ msgid ""
"Would your payment have been carried out after this mail was sent, please "
"consider the present one as void."
msgstr ""
"Уведомляем, что наступило время следующего платежа. Если платёж был "
"отправлен — предоставьте, пожалуйста, подробности этого платежа. Если платёж "
"будет и далее задерживаться, свяжитесь, пожалуйста, с нами. "
" Если платёж был отправлен до отправки этого письма — возможно, письмо "
"Уведомляем, что наступило время следующего платежа. \n"
"Если платёж был отправлен — предоставьте, пожалуйста, подробности этого "
"платежа. \n"
"Если платёж будет и далее задерживаться, свяжитесь, пожалуйста, с нами.\n"
"Если платёж был отправлен до отправки этого письма — возможно, письмо "
"отправлено по ошибке."
#. module: base
@ -4794,7 +4795,7 @@ msgstr "Бутан"
#. module: base
#: help:ir.sequence,number_next:0
msgid "Next number of this sequence"
msgstr "Следующее число в этой последовательности"
msgstr "Следующее число в этой нумерации"
#. module: base
#: model:res.partner.category,name:base.res_partner_category_11
@ -5170,7 +5171,7 @@ msgstr "Контакт"
#. module: base
#: model:ir.model,name:base.model_ir_ui_menu
msgid "ir.ui.menu"
msgstr "Меню"
msgstr "ir.ui.menu"
#. module: base
#: model:res.country,name:base.us
@ -5197,7 +5198,7 @@ msgstr "Отчет RML"
#. module: base
#: model:ir.model,name:base.model_ir_server_object_lines
msgid "ir.server.object.lines"
msgstr "События сервера"
msgstr "ir.server.object.lines"
#. module: base
#: code:addons/base/module/module.py:531
@ -5503,7 +5504,7 @@ msgstr "Обновление / Установка модуля"
#. module: base
#: model:ir.model,name:base.model_ir_actions_configuration_wizard
msgid "ir.actions.configuration.wizard"
msgstr "Диалог конфигурировая события"
msgstr "ir.actions.configuration.wizard"
#. module: base
#: view:res.lang:0
@ -5794,7 +5795,7 @@ msgstr "Дата создания"
#. module: base
#: model:ir.model,name:base.model_ir_actions_todo
msgid "ir.actions.todo"
msgstr "ТОДО"
msgstr "ir.actions.todo"
#. module: base
#: code:addons/base/res/res_config.py:94
@ -6227,7 +6228,7 @@ msgstr "Сбор средств"
#: model:ir.actions.act_window,name:base.ir_sequence_type
#: model:ir.ui.menu,name:base.menu_ir_sequence_type
msgid "Sequence Codes"
msgstr "Последовательность кодов"
msgstr "Коды нумераций"
#. module: base
#: selection:base.language.install,lang:0
@ -6421,7 +6422,7 @@ msgstr ""
#: model:ir.actions.act_window,name:base.action_rule
#: model:ir.ui.menu,name:base.menu_action_rule
msgid "Record Rules"
msgstr "Правила записи"
msgstr "Правила доступа"
#. module: base
#: field:res.config.users,name:0
@ -6749,7 +6750,7 @@ msgstr "Мартиника (заморский регион Франции)"
#. module: base
#: view:ir.sequence.type:0
msgid "Sequences Type"
msgstr "Тип последовательностей"
msgstr "Тип нумерации"
#. module: base
#: model:ir.actions.act_window,name:base.res_request-act
@ -6803,7 +6804,7 @@ msgid ""
"number of modules currently installed)..."
msgstr ""
"Пожалуйста подождите, эта операция может занять несколько минут (зависит от "
"количества устанавливаемых модулей)..."
"количества установленных модулей)..."
#. module: base
#: field:ir.ui.menu,child_id:0
@ -6990,7 +6991,7 @@ msgstr "Часовой пояс"
#: model:ir.model,name:base.model_ir_actions_report_xml
#: selection:ir.ui.menu,action:0
msgid "ir.actions.report.xml"
msgstr "События отчетов"
msgstr "ir.actions.report.xml"
#. module: base
#: model:res.partner.title,shortcut:base.res_partner_title_miss
@ -7309,7 +7310,7 @@ msgstr "Месяц: %(месяц)ы"
#: field:res.widget.user,sequence:0
#: field:wizard.ir.model.menu.create.line,sequence:0
msgid "Sequence"
msgstr "Последовательность"
msgstr "Нумерация"
#. module: base
#: model:res.country,name:base.tn
@ -7865,8 +7866,9 @@ msgid ""
msgstr ""
"Можно устанавливать новые модули для подключения новых функций, меню, "
"отчётов или данных в вашем экземпляре OpenERP. Для установки модулей, "
"нажмите на кнопку «Отметить для установки» в представлении в виде формы, "
"затем нажмите «Применить отмеченные обновления», чтобы обновить вашу систему."
"нажмите на кнопку «Запланировать установку» в представлении в виде формы, "
"затем нажмите «Выполнить запланированные обновления», чтобы обновить вашу "
"систему."
#. module: base
#: model:ir.ui.menu,name:base.menu_emails
@ -8055,7 +8057,7 @@ msgstr "Габон"
#. module: base
#: model:ir.model,name:base.model_ir_model_data
msgid "ir.model.data"
msgstr "Данные"
msgstr "ir.model.data"
#. module: base
#: view:ir.model:0
@ -8350,7 +8352,7 @@ msgstr "Адресная книга"
#. module: base
#: model:ir.model,name:base.model_ir_sequence_type
msgid "ir.sequence.type"
msgstr "Тип последовательности"
msgstr "ir.sequence.type"
#. module: base
#: selection:base.language.export,format:0
@ -8642,7 +8644,7 @@ msgstr "События клиента"
#. module: base
#: view:ir.module.module:0
msgid "Schedule for Installation"
msgstr "Расписание установки"
msgstr "Запланировать установку"
#. module: base
#: model:ir.model,name:base.model_partner_wizard_ean_check
@ -8689,7 +8691,7 @@ msgstr "Вануату"
#. module: base
#: view:res.company:0
msgid "Internal Header/Footer"
msgstr "Внутренние верхний / нижний колонтитулы"
msgstr "Внутренние верхний/нижний колонтитулы"
#. module: base
#: code:addons/base/module/wizard/base_export_language.py:59
@ -9488,7 +9490,7 @@ msgstr "Поле мастера"
#. module: base
#: help:ir.sequence,prefix:0
msgid "Prefix value of the record for the sequence"
msgstr "Префикс записи для последовательности"
msgstr "Префикс для нумерации"
#. module: base
#: model:res.country,name:base.sc
@ -9542,7 +9544,7 @@ msgstr "Объект ресурса"
#. module: base
#: help:ir.sequence,number_increment:0
msgid "The next number of the sequence will be incremented by this number"
msgstr "Следующее число последовательности будет увеличено на это число"
msgstr "Следующее число в нумерации будет увеличено на это число"
#. module: base
#: field:ir.cron,function:0

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2011-04-19 02:34+0000\n"
"Last-Translator: Nguyễn Thịnh <thinhnverp@gmail.com>\n"
"PO-Revision-Date: 2011-05-29 23:23+0000\n"
"Last-Translator: Phong Nguyen-Thanh <Unknown>\n"
"Language-Team: Vietnamese <vi@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-04-20 05:56+0000\n"
"X-Generator: Launchpad (build 12758)\n"
"X-Launchpad-Export-Date: 2011-05-31 04:47+0000\n"
"X-Generator: Launchpad (build 12959)\n"
#. module: base
#: view:ir.filters:0
@ -24,7 +24,7 @@ msgstr ""
#: field:ir.rule,domain_force:0
#: field:res.partner.title,domain:0
msgid "Domain"
msgstr "Vùng"
msgstr "Miền"
#. module: base
#: model:res.country,name:base.sh
@ -48,6 +48,8 @@ msgid ""
"The second argument of the many2many field %s must be a SQL table !You used "
"%s, which is not a valid SQL table name."
msgstr ""
"Đối số thứ hai trong quan hệ nhiều-nhiều %s phải là một bảng SQL ! Bạn đã "
"dùng %s, đó không phải là một tên bảng SQL hợp lệ."
#. module: base
#: view:ir.values:0
@ -104,7 +106,7 @@ msgstr "Quy trình công việc cho"
#. module: base
#: field:ir.actions.act_window,display_menu_tip:0
msgid "Display Menu Tips"
msgstr ""
msgstr "Hiện chỉ dẫn trình đơn"
#. module: base
#: view:ir.module.module:0
@ -118,6 +120,8 @@ msgid ""
"You can not write in this document (%s) ! Be sure your user belongs to one "
"of these groups: %s."
msgstr ""
"Bạn không thể viết trong tài liệu này (% s) ! Hãy chắc chắn người dùng của "
"bạn thuộc một trong các nhóm: %s."
#. module: base
#: help:ir.model.fields,domain:0
@ -126,6 +130,9 @@ msgid ""
"specified as a Python expression defining a list of triplets. For example: "
"[('color','=','red')]"
msgstr ""
"Miền tùy chọn dùng để giới hạn các giá trị có thể đối với các trường quan "
"hệ, được chỉ định bởi một biểu thức Python định nghĩa một danh sách của các "
"bộ ba. Ví dụ: [('màu'='đỏ')]"
#. module: base
#: field:res.partner,ref:0
@ -150,8 +157,9 @@ msgid ""
"Properties of base fields cannot be altered in this manner! Please modify "
"them through Python code, preferably through a custom addon!"
msgstr ""
"các thuộc tính cơ bản không thể thay đổi theo cách này ! hãy thay đổi chúng "
"thông qua Python mã, tốt hơn thông qua một addon tùy chỉnh!"
"Các thuộc tính của trường cơ bản không thể thay đổi theo cách này ! Vui lòng "
"thay đổi chúng thông qua mã Python, tốt nhất là thông qua một addon tùy "
"chỉnh!"
#. module: base
#: code:addons/osv.py:133
@ -227,7 +235,7 @@ msgstr "mới"
#. module: base
#: field:ir.actions.report.xml,multi:0
msgid "On multiple doc."
msgstr ""
msgstr "Trong nhiều tài liệu."
#. module: base
#: field:ir.module.category,module_nr:0
@ -237,7 +245,7 @@ msgstr "Số lượng mô-đun"
#. module: base
#: help:multi_company.default,company_dest_id:0
msgid "Company to store the current record"
msgstr ""
msgstr "Công ty lưu trữ bản ghi hiện tại"
#. module: base
#: field:res.partner.bank.type.field,size:0
@ -256,6 +264,8 @@ msgid ""
"Save this document to a %s file and edit it with a specific software or a "
"text editor. The file encoding is UTF-8."
msgstr ""
"Lưu tài liệu này thành tập tin %s và chỉnh sửa bằng một phần mềm phù hợp "
"hoặc trình soạnh thảo văn bản. Tập tin mã hóa dạng UTF-8"
#. module: base
#: sql_constraint:res.lang:0
@ -391,12 +401,14 @@ msgid ""
"If you check this, then the second time the user prints with same attachment "
"name, it returns the previous report."
msgstr ""
"Nếu bạn đánh dấu mục này, khi người sử dụng in lần thứ hai với cùng tên tập "
"tin đính kèm, hệ thống sẽ trả về báo cáo trước đó."
#. module: base
#: code:addons/orm.py:904
#, python-format
msgid "The read method is not implemented on this object !"
msgstr ""
msgstr "Phương thức đọc không được hiện thực trên đối tượng này !"
#. module: base
#: help:res.lang,iso_code:0
@ -412,7 +424,7 @@ msgstr "Hệ thống của bạn sẽ được cập nhật"
#: field:ir.actions.todo,note:0
#: selection:ir.property,type:0
msgid "Text"
msgstr "Chữ"
msgstr "Văn bản"
#. module: base
#: field:res.country,name:0
@ -433,7 +445,7 @@ msgstr "Lên kế hoạch nâng cấp"
#: code:addons/orm.py:838
#, python-format
msgid "Key/value '%s' not found in selection field '%s'"
msgstr ""
msgstr "Khóa/giá trị '%s' không tìm thấy trong trường được chọn '%s'"
#. module: base
#: help:res.country,code:0
@ -464,6 +476,7 @@ msgstr "Chưa được dịch"
msgid ""
"Context dictionary as Python expression, empty by default (Default: {})"
msgstr ""
"Từ điển bối cảnh là biểu thức Python, mặc định để trống (Mặc định: {})"
#. module: base
#: model:ir.actions.act_window,name:base.ir_action_wizard
@ -475,13 +488,13 @@ msgstr ""
#. module: base
#: model:res.partner.category,name:base.res_partner_category_miscellaneoussuppliers0
msgid "Miscellaneous Suppliers"
msgstr ""
msgstr "Các nhà cung cấp khác"
#. module: base
#: code:addons/base/ir/ir_model.py:255
#, python-format
msgid "Custom fields must have a name that starts with 'x_' !"
msgstr ""
msgstr "Các trường được tùy chỉnh phải có tên bắt đầu bằng 'x_' !"
#. module: base
#: help:ir.actions.server,action_id:0
@ -501,13 +514,13 @@ msgstr "Trích xuất hoàn tất"
#. module: base
#: view:ir.model:0
msgid "Model Description"
msgstr ""
msgstr "Mô tả mô hình"
#. module: base
#: help:ir.actions.act_window,src_model:0
msgid ""
"Optional model name of the objects on which this action should be visible"
msgstr ""
msgstr "Tùy chọn tên mô hình của đối tượng cho phép hiện hành động này"
#. module: base
#: field:workflow.transition,trigger_expr_id:0
@ -569,12 +582,12 @@ msgstr ""
#. module: base
#: field:res.partner,title:0
msgid "Partner Form"
msgstr ""
msgstr "Mẫu đối tác"
#. module: base
#: selection:base.language.install,lang:0
msgid "Swedish / svenska"
msgstr ""
msgstr "Tiếng Thụy điển"
#. module: base
#: model:res.country,name:base.rs
@ -633,6 +646,7 @@ msgstr "Pa-pu-a Niu Ghi-nê"
#: help:ir.actions.report.xml,report_type:0
msgid "Report Type, e.g. pdf, html, raw, sxw, odt, html2html, mako2html, ..."
msgstr ""
"Loại Báo cáo, ví dụ pdf, html, raw, sxw, odt, html2html, mako2html, ..."
#. module: base
#: model:res.partner.category,name:base.res_partner_category_4
@ -692,7 +706,7 @@ msgstr ""
#: field:res.partner,mobile:0
#: field:res.partner.address,mobile:0
msgid "Mobile"
msgstr "Di động"
msgstr "Số di động"
#. module: base
#: model:res.country,name:base.om
@ -734,7 +748,7 @@ msgstr ""
#: code:addons/orm.py:1043
#, python-format
msgid "The unlink method is not implemented on this object !"
msgstr ""
msgstr "Phương thức unlink không được hiện thực trên đối tượng này !"
#. module: base
#: model:ir.actions.act_window,name:base.act_menu_create
@ -777,7 +791,7 @@ msgstr "ir.config_parameter"
#. module: base
#: selection:base.language.export,format:0
msgid "TGZ Archive"
msgstr ""
msgstr "Lưu trữ TGZ"
#. module: base
#: view:res.lang:0
@ -1676,7 +1690,8 @@ msgstr ""
msgid ""
"The Object name must start with x_ and not contain any special character !"
msgstr ""
"The Object name must start with x_ and not contain any special character !"
"Tên đối tượng phải bắt đầu bằng x_ và không bao gồm bất kỳ ký tự đặc biệt "
"nào!"
#. module: base
#: field:ir.actions.configuration.wizard,note:0
@ -4447,7 +4462,7 @@ msgstr ""
#: view:res.partner:0
#: field:res.partner,user_id:0
msgid "Salesman"
msgstr "NV bán hàng"
msgstr "Nhân viên bán hàng"
#. module: base
#: field:res.partner,address:0
@ -5703,7 +5718,7 @@ msgstr "Tự chọn"
#. module: base
#: view:res.request:0
msgid "Current"
msgstr "Current"
msgstr "Hiện hành"
#. module: base
#: model:res.partner.category,name:base.res_partner_category_9
@ -6256,7 +6271,7 @@ msgstr ""
#: field:res.partner.address,city:0
#: field:res.partner.bank,city:0
msgid "City"
msgstr "Thành phố/Quận"
msgstr "TP thuộc Tỉnh/Quận/Huyện"
#. module: base
#: model:res.country,name:base.qa
@ -6810,7 +6825,7 @@ msgstr "Các mô-đun sau chưa được cài đặt hoặc không biết: %s"
#: view:res.users:0
#: field:res.widget.user,user_id:0
msgid "User"
msgstr "Người ng"
msgstr "Người sử dụng"
#. module: base
#: model:res.country,name:base.pr
@ -7021,7 +7036,7 @@ msgstr "Tháng: %(month)s"
#: field:res.widget.user,sequence:0
#: field:wizard.ir.model.menu.create.line,sequence:0
msgid "Sequence"
msgstr "Sequence"
msgstr "Trình tự"
#. module: base
#: model:res.country,name:base.tn
@ -7081,7 +7096,7 @@ msgstr ""
#: model:ir.actions.act_window,name:base.action_country_state
#: model:ir.ui.menu,name:base.menu_country_state_partner
msgid "Fed. States"
msgstr "Bang/Tỉnh/TP"
msgstr "Tiểu bang/TPTƯ/Tỉnh"
#. module: base
#: view:ir.model:0
@ -7912,17 +7927,17 @@ msgstr ""
#: field:res.currency,name:0
#: field:res.currency.rate,currency_id:0
msgid "Currency"
msgstr "Tiền tệ"
msgstr "Loại tiền tệ"
#. module: base
#: field:res.partner.canal,name:0
msgid "Channel Name"
msgstr ""
msgstr "Tên Kênh"
#. module: base
#: view:res.lang:0
msgid "5. %y, %Y ==> 08, 2008"
msgstr ""
msgstr "5. %y, %Y ==> 08, 2008"
#. module: base
#: model:res.partner.title,shortcut:base.res_partner_title_ltd
@ -7933,12 +7948,12 @@ msgstr ""
#: field:ir.values,res_id:0
#: field:res.log,res_id:0
msgid "Object ID"
msgstr ""
msgstr "Mã Đối tượng"
#. module: base
#: view:res.company:0
msgid "Landscape"
msgstr ""
msgstr "Khổ ngang"
#. module: base
#: model:ir.ui.menu,name:base.menu_administration
@ -8766,7 +8781,7 @@ msgstr ""
#: view:res.users:0
#: field:res.users,company_id:0
msgid "Company"
msgstr "Company"
msgstr "Công ty"
#. module: base
#: view:res.users:0
@ -9016,7 +9031,7 @@ msgstr "ir.actions.server"
#: field:res.config.users,progress:0
#: field:res.config.view,progress:0
msgid "Configuration Progress"
msgstr "Configuration Progress"
msgstr "Tiến trình cấu hình"
#. module: base
#: model:ir.actions.act_window,name:base.act_ir_actions_todo_form
@ -14811,3 +14826,6 @@ msgstr "Tiếng Nga"
#~ msgid "Corporation"
#~ msgstr "Công ty"
#~ msgid "Ltd."
#~ msgstr "TNHH"

View File

@ -1440,7 +1440,8 @@
<page string="Submenus">
<!-- Note: make sure you have 'ir.ui.menu.full_list'
in the context to see all submenus! -->
<field name="child_id" nolabel="1">
<field name="child_id" nolabel="1"
context="{'default_parent_id': active_id}">
<tree string="Menu">
<field name="sequence"/>
<field icon="icon" name="name" string="Menu"/>

View File

@ -121,7 +121,7 @@ class ir_attachment(osv.osv):
return self.pool.get('ir.actions.act_window').for_xml_id(
cr, uid, 'base', 'action_attachment', context=context)
def _name_get_resname(self, cr, uid, ids, object,method, context):
def _name_get_resname(self, cr, uid, ids, object, method, context):
data = {}
for attachment in self.browse(cr, uid, ids, context=context):
model_object = attachment.res_model
@ -129,9 +129,14 @@ class ir_attachment(osv.osv):
if model_object and res_id:
model_pool = self.pool.get(model_object)
res = model_pool.name_get(cr,uid,[res_id],context)
data[attachment.id] = (res and res[0][1]) or False
res_name = res and res[0][1] or False
if res_name:
field = self._columns.get('res_name',False)
if field and len(res_name) > field.size:
res_name = res_name[:field.size-3] + '...'
data[attachment.id] = res_name
else:
data[attachment.id] = False
data[attachment.id] = False
return data
_name = 'ir.attachment'

View File

@ -20,6 +20,7 @@
##############################################################################
import time
import logging
from datetime import datetime
from dateutil.relativedelta import relativedelta
import netsvc
@ -85,16 +86,22 @@ class ir_cron(osv.osv, netsvc.Agent):
(_check_args, 'Invalid arguments', ['args']),
]
def _callback(self, cr, uid, model, func, args):
def _handle_callback_exception(self, cr, uid, model, func, args, job_id, job_exception):
cr.rollback()
logger=logging.getLogger('cron')
logger.exception("Call of self.pool.get('%s').%s(cr, uid, *%r) failed in Job %s" % (model, func, args, job_id))
def _callback(self, cr, uid, model, func, args, job_id):
args = str2tuple(args)
m = self.pool.get(model)
if m and hasattr(m, func):
f = getattr(m, func)
try:
netsvc.log('cron', (cr.dbname,uid,'*',model,func)+tuple(args), channel=logging.DEBUG,
depth=(None if self._logger.isEnabledFor(logging.DEBUG_RPC_ANSWER) else 1), fn='object.execute')
f(cr, uid, *args)
except Exception, e:
cr.rollback()
self._logger.exception("Job call of self.pool.get('%s').%s(cr, uid, *%r) failed" % (model, func, args))
self._handle_callback_exception(cr, uid, model, func, args, job_id, e)
def _poolJobs(self, db_name, check=False):
@ -116,7 +123,7 @@ class ir_cron(osv.osv, netsvc.Agent):
if numbercall > 0:
numbercall -= 1
if not ok or job['doall']:
self._callback(cr, job['user_id'], job['model'], job['function'], job['args'])
self._callback(cr, job['user_id'], job['model'], job['function'], job['args'], job['id'])
if numbercall:
nextcall += _intervalTypes[job['interval_type']](job['interval_number'])
ok = True

View File

@ -24,7 +24,6 @@ import time
from operator import itemgetter
from osv import fields,osv
import ir
import netsvc
from osv.orm import except_orm, browse_record
import tools
@ -163,7 +162,7 @@ class ir_model(osv.osv):
pass
x_custom_model._name = model
x_custom_model._module = False
a = x_custom_model.createInstance(self.pool, '', cr)
a = x_custom_model.createInstance(self.pool, cr)
if (not a._columns) or ('x_name' in a._columns.keys()):
x_name = 'x_name'
else:
@ -754,7 +753,8 @@ class ir_model_data(osv.osv):
cr.execute('select * from ir_values where model=%s and key=%s and name=%s'+where,(model, key, name))
res = cr.fetchone()
if not res:
res = ir.ir_set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
res = ir_values_obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
elif xml_id:
cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
return True

View File

@ -3,6 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010-2011 OpenERP SA (<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
@ -23,7 +24,7 @@ import base64
import re
import tools
import addons
import openerp.modules
from osv import fields, osv
from tools.translate import _
@ -176,13 +177,13 @@ class ir_ui_menu(osv.osv):
def _action(self, cursor, user, ids, name, arg, context=None):
res = {}
values_obj = self.pool.get('ir.values')
value_ids = values_obj.search(cursor, user, [
ir_values_obj = self.pool.get('ir.values')
value_ids = ir_values_obj.search(cursor, user, [
('model', '=', self._name), ('key', '=', 'action'),
('key2', '=', 'tree_but_open'), ('res_id', 'in', ids)],
context=context)
values_action = {}
for value in values_obj.browse(cursor, user, value_ids, context=context):
for value in ir_values_obj.browse(cursor, user, value_ids, context=context):
values_action[value.res_id] = value.value
for menu_id in ids:
res[menu_id] = values_action.get(menu_id, False)
@ -194,16 +195,16 @@ class ir_ui_menu(osv.osv):
ctx = context.copy()
if self.CONCURRENCY_CHECK_FIELD in ctx:
del ctx[self.CONCURRENCY_CHECK_FIELD]
values_obj = self.pool.get('ir.values')
values_ids = values_obj.search(cursor, user, [
ir_values_obj = self.pool.get('ir.values')
values_ids = ir_values_obj.search(cursor, user, [
('model', '=', self._name), ('key', '=', 'action'),
('key2', '=', 'tree_but_open'), ('res_id', '=', menu_id)],
context=context)
if values_ids:
values_obj.write(cursor, user, values_ids, {'value': value},
ir_values_obj.write(cursor, user, values_ids, {'value': value},
context=ctx)
else:
values_obj.create(cursor, user, {
ir_values_obj.create(cursor, user, {
'name': 'Menuitem',
'model': self._name,
'value': value,
@ -225,8 +226,10 @@ class ir_ui_menu(osv.osv):
return {'type': {'icon_pict': 'picture'}, 'value': {'icon_pict': ('stock', (icon,'ICON_SIZE_MENU'))}}
def read_image(self, path):
if not path:
return False
path_info = path.split(',')
icon_path = addons.get_module_resource(path_info[0],path_info[1])
icon_path = openerp.modules.get_module_resource(path_info[0],path_info[1])
icon_image = False
if icon_path:
try:
@ -236,17 +239,14 @@ class ir_ui_menu(osv.osv):
icon_file.close()
return icon_image
def _get_image_icon(self, cr, uid, ids, name, args, context=None):
def _get_image_icon(self, cr, uid, ids, names, args, context=None):
res = {}
for menu in self.browse(cr, uid, ids, context=context):
res[menu.id] = {
'web_icon_data': False,
'web_icon_hover_data': False,
}
if menu.web_icon_hover:
res[menu.id]['web_icon_hover_data'] = self.read_image(menu.web_icon_hover)
if menu.web_icon:
res[menu.id]['web_icon_data'] = self.read_image(menu.web_icon)
res[menu.id] = r = {}
for fn in names:
fn_src = fn[:-5] # remove _data
r[fn] = self.read_image(menu[fn_src])
return res
_columns = {

View File

@ -144,7 +144,9 @@ class ir_values(osv.osv):
ids_res.append(self.create(cr, uid, vals))
return ids_res
def get(self, cr, uid, key, key2, models, meta=False, context={}, res_id_req=False, without_user=True, key2_req=True):
def get(self, cr, uid, key, key2, models, meta=False, context=None, res_id_req=False, without_user=True, key2_req=True):
if context is None:
context = {}
result = []
for m in models:
if isinstance(m, (list, tuple)):
@ -172,8 +174,15 @@ class ir_values(osv.osv):
else:
where.append('res_id=%s')
params.append(res_id)
where.append('(user_id=%s or (user_id IS NULL)) order by id')
order = 'id'
if key == 'default':
# Make sure we get first the values for specific users, then
# the global values. The map/filter below will retain the first
# value for any given name. The 'order by' will put the null
# values last; this may be postgres specific (it is the
# behavior in postgres at least since 8.2).
order = 'user_id'
where.append('(user_id=%s or (user_id IS NULL)) order by '+ order)
params.append(uid)
clause = ' and '.join(where)
cr.execute('select id,name,value,object,meta, key from ir_values where ' + clause, params)

View File

@ -30,7 +30,7 @@ import urllib
import zipfile
import zipimport
import addons
import openerp.modules as addons
import pooler
import release
import tools
@ -79,8 +79,7 @@ class module(osv.osv):
info = {}
try:
info = addons.load_information_from_description_file(name)
if 'version' in info:
info['version'] = release.major_version + '.' + info['version']
info['version'] = release.major_version + '.' + info['version']
except Exception:
cls.__logger.debug('Error when trying to fetch informations for '
'module %s', name, exc_info=True)
@ -479,7 +478,9 @@ class module(osv.osv):
categs = categs[1:]
self.write(cr, uid, [mod_browse.id], {'category_id': p_id})
def update_translations(self, cr, uid, ids, filter_lang=None, context={}):
def update_translations(self, cr, uid, ids, filter_lang=None, context=None):
if context is None:
context = {}
logger = logging.getLogger('i18n')
if not filter_lang:
pool = pooler.get_pool(cr.dbname)

View File

@ -21,7 +21,6 @@
import time
import netsvc
from osv import fields, osv
import ir
from tools.misc import currency
from tools.translate import _

View File

@ -21,6 +21,7 @@
import locale
import logging
import re
from osv import fields, osv
from locale import localeconv
@ -36,16 +37,25 @@ class lang(osv.osv):
_disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice
def install_lang(self, cr, uid, **args):
"""
This method is called from openerp/addons/base/base_data.xml to load
some language and set it as the default for every partners. The
language is set via tools.config by the RPC 'create' method on the
'db' object. This is a fragile solution and something else should be
found.
"""
lang = tools.config.get('lang')
if not lang:
return False
lang_ids = self.search(cr, uid, [('code','=', lang)])
values_obj = self.pool.get('ir.values')
ir_values_obj = self.pool.get('ir.values')
if not lang_ids:
lang_id = self.load_lang(cr, uid, lang)
default_value = values_obj.get(cr, uid, 'default', False, 'res.partner')
default_value = ir_values_obj.get(cr, uid, 'default', False, ['res.partner'])
if not default_value:
values_obj.set(cr, uid, 'default', False, 'lang', ['res.partner'], lang)
ir_values_obj.set(cr, uid, 'default', False, 'lang', ['res.partner'], lang)
return True
def load_lang(self, cr, uid, lang, lang_name=None):
@ -181,46 +191,6 @@ class lang(osv.osv):
trans_obj.unlink(cr, uid, trans_ids, context=context)
return super(lang, self).unlink(cr, uid, ids, context=context)
def _group(self, cr, uid, ids, s, monetary=False, grouping=False, thousands_sep=''):
grouping = eval(grouping)
if not grouping:
return (s, 0)
result = ""
seps = 0
spaces = ""
if s[-1] == ' ':
sp = s.find(' ')
spaces = s[sp:]
s = s[:sp]
while s and grouping:
# if grouping is -1, we are done
if grouping[0] == -1:
break
# 0: re-use last group ad infinitum
elif grouping[0] != 0:
#process last group
group = grouping[0]
grouping = grouping[1:]
if result:
result = s[-group:] + thousands_sep + result
seps += 1
else:
result = s[-group:]
s = s[:-group]
if s and s[-1] not in "0123456789":
# the leading string is only spaces and signs
return s + result + spaces, seps
if not result:
return s + spaces, seps
if s:
result = s + thousands_sep + result
seps += 1
return result + spaces, seps
def format(self, cr, uid, ids, percent, value, grouping=False, monetary=False):
""" Format() will return the language-specific output for float values"""
@ -228,6 +198,7 @@ class lang(osv.osv):
raise ValueError("format() must be given exactly one %char format specifier")
lang_grouping, thousands_sep, decimal_point = self._lang_data_get(cr, uid, ids[0], monetary)
eval_lang_grouping = eval(lang_grouping)
formatted = percent % value
# floats and decimal ints need special action!
@ -236,7 +207,7 @@ class lang(osv.osv):
parts = formatted.split('.')
if grouping:
parts[0], seps = self._group(cr,uid,ids,parts[0], monetary=monetary, grouping=lang_grouping, thousands_sep=thousands_sep)
parts[0], seps = intersperse(parts[0], eval_lang_grouping, thousands_sep)
formatted = decimal_point.join(parts)
while seps:
@ -246,7 +217,7 @@ class lang(osv.osv):
seps -= 1
elif percent[-1] in 'diu':
if grouping:
formatted = self._group(cr,uid,ids,formatted, monetary=monetary, grouping=lang_grouping, thousands_sep=thousands_sep)[0]
formatted = intersperse(formatted, eval_lang_grouping, thousands_sep)[0]
return formatted
@ -256,5 +227,139 @@ class lang(osv.osv):
lang()
def original_group(s, grouping, thousands_sep=''):
if not grouping:
return (s, 0)
result = ""
seps = 0
spaces = ""
if s[-1] == ' ':
sp = s.find(' ')
spaces = s[sp:]
s = s[:sp]
while s and grouping:
# if grouping is -1, we are done
if grouping[0] == -1:
break
# 0: re-use last group ad infinitum
elif grouping[0] != 0:
#process last group
group = grouping[0]
grouping = grouping[1:]
if result:
result = s[-group:] + thousands_sep + result
seps += 1
else:
result = s[-group:]
s = s[:-group]
if s and s[-1] not in "0123456789":
# the leading string is only spaces and signs
return s + result + spaces, seps
if not result:
return s + spaces, seps
if s:
result = s + thousands_sep + result
seps += 1
return result + spaces, seps
def split(l, counts):
"""
>>> split("hello world", [])
['hello world']
>>> split("hello world", [1])
['h', 'ello world']
>>> split("hello world", [2])
['he', 'llo world']
>>> split("hello world", [2,3])
['he', 'llo', ' world']
>>> split("hello world", [2,3,0])
['he', 'llo', ' wo', 'rld']
>>> split("hello world", [2,-1,3])
['he', 'llo world']
"""
res = []
saved_count = len(l) # count to use when encoutering a zero
for count in counts:
if count == -1:
break
if count == 0:
while l:
res.append(l[:saved_count])
l = l[saved_count:]
break
res.append(l[:count])
l = l[count:]
saved_count = count
if l:
res.append(l)
return res
intersperse_pat = re.compile('([^0-9]*)([^ ]*)(.*)')
def intersperse(string, counts, separator=''):
"""
See the asserts below for examples.
"""
left, rest, right = intersperse_pat.match(string).groups()
def reverse(s): return s[::-1]
splits = split(reverse(rest), counts)
res = separator.join(map(reverse, reverse(splits)))
return left + res + right, len(splits) > 0 and len(splits) -1 or 0
# TODO rewrite this with a unit test library
def _group_examples():
for g in [original_group, intersperse]:
# print "asserts on", g.func_name
assert g("", []) == ("", 0)
assert g("0", []) == ("0", 0)
assert g("012", []) == ("012", 0)
assert g("1", []) == ("1", 0)
assert g("12", []) == ("12", 0)
assert g("123", []) == ("123", 0)
assert g("1234", []) == ("1234", 0)
assert g("123456789", []) == ("123456789", 0)
assert g("&ab%#@1", []) == ("&ab%#@1", 0)
assert g("0", []) == ("0", 0)
assert g("0", [1]) == ("0", 0)
assert g("0", [2]) == ("0", 0)
assert g("0", [200]) == ("0", 0)
# breaks original_group:
if g.func_name == 'intersperse':
assert g("12345678", [0], '.') == ('12345678', 0)
assert g("", [1], '.') == ('', 0)
assert g("12345678", [1], '.') == ('1234567.8', 1)
assert g("12345678", [1], '.') == ('1234567.8', 1)
assert g("12345678", [2], '.') == ('123456.78', 1)
assert g("12345678", [2,1], '.') == ('12345.6.78', 2)
assert g("12345678", [2,0], '.') == ('12.34.56.78', 3)
assert g("12345678", [-1,2], '.') == ('12345678', 0)
assert g("12345678", [2,-1], '.') == ('123456.78', 1)
assert g("12345678", [2,0,1], '.') == ('12.34.56.78', 3)
assert g("12345678", [2,0,0], '.') == ('12.34.56.78', 3)
assert g("12345678", [2,0,-1], '.') == ('12.34.56.78', 3)
assert original_group("abc1234567xy", [2], '.') == ('abc1234567.xy', 1)
assert original_group("abc1234567xy8", [2], '.') == ('abc1234567xy8', 0) # difference here...
assert original_group("abc12", [3], '.') == ('abc12', 0)
assert original_group("abc12", [2], '.') == ('abc12', 0)
assert original_group("abc12", [1], '.') == ('abc1.2', 1)
assert intersperse("abc1234567xy", [2], '.') == ('abc1234567.xy', 1)
assert intersperse("abc1234567xy8", [2], '.') == ('abc1234567x.y8', 1) # ... w.r.t. here.
assert intersperse("abc12", [3], '.') == ('abc12', 0)
assert intersperse("abc12", [2], '.') == ('abc12', 0)
assert intersperse("abc12", [1], '.') == ('abc1.2', 1)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -127,6 +127,8 @@ class users(osv.osv):
def send_welcome_email(self, cr, uid, id, context=None):
logger= netsvc.Logger()
user = self.pool.get('res.users').read(cr, uid, id, context=context)
if not user.get('email'):
return False
if not tools.config.get('smtp_server'):
logger.notifyChannel('mails', netsvc.LOG_WARNING,
_('"smtp_server" needs to be set to send mails to users'))
@ -136,8 +138,6 @@ class users(osv.osv):
_('"email_from" needs to be set to send welcome mails '
'to users'))
return False
if not user.get('email'):
return False
return tools.email_send(email_from=None, email_to=[user['email']],
subject=self.get_welcome_mail_subject(
@ -206,6 +206,9 @@ class users(osv.osv):
raise osv.except_osv(_('Operation Canceled'), _('Please use the change password wizard (in User Preferences or User menu) to change your own password.'))
self.write(cr, uid, id, {'password': value})
def _get_password(self, cr, uid, ids, arg, karg, context=None):
return dict.fromkeys(ids, '')
_columns = {
'name': fields.char('User Name', size=64, required=True, select=True,
help="The new user's real name, used for searching"
@ -213,7 +216,7 @@ class users(osv.osv):
'login': fields.char('Login', size=64, required=True,
help="Used to log into the system"),
'password': fields.char('Password', size=64, invisible=True, help="Keep empty if you don't want the user to be able to connect on the system."),
'new_password': fields.function(lambda *a:'', method=True, type='char', size=64,
'new_password': fields.function(_get_password, method=True, type='char', size=64,
fnct_inv=_set_new_password,
string='Change password', help="Only specify a value if you want to change the user password. "
"This user will have to logout and login again!"),
@ -263,7 +266,6 @@ class users(osv.osv):
if 'password' in o and ( 'id' not in o or o['id'] != uid ):
o['password'] = '********'
return o
result = super(users, self).read(cr, uid, ids, fields, context, load)
canwrite = self.pool.get('ir.model.access').check(cr, uid, 'res.users', 'write', raise_exception=False)
if not canwrite:
@ -365,7 +367,7 @@ class users(osv.osv):
break
else:
if 'company_id' in values:
if not (values['company_id'] in self.read(cr, uid, uid, ['company_ids'], context=context)['company_ids']):
if not (values['company_id'] in self.read(cr, 1, uid, ['company_ids'], context=context)['company_ids']):
del values['company_id']
uid = 1 # safe fields only, so we write as super-user to bypass access rights
@ -535,15 +537,19 @@ class config_users(osv.osv_memory):
'email': base_data['email'],
'partner_id': partner_id,},
context)
user_data = dict(
base_data,
signature=self._generate_signature(
cr, base_data['name'], base_data['email'], context=context),
address_id=address,
)
# Change the read many2one values from (id,name) to id, and
# the one2many from ids to (6,0,ids).
base_data.update({'menu_id' : base_data.get('menu_id') and base_data['menu_id'][0],
'company_id' : base_data.get('company_id') and base_data['company_id'][0],
'action_id' : base_data.get('action_id') and base_data['action_id'][0],
'signature' : self._generate_signature(cr, base_data['name'], base_data['email'], context=context),
'address_id' : address,
'groups_id' : [(6,0, base_data.get('groups_id',[]))],
})
new_user = self.pool.get('res.users').create(
cr, uid, user_data, context)
cr, uid, base_data, context)
self.send_welcome_email(cr, uid, new_user, context=context)
def execute(self, cr, uid, ids, context=None):
'Do nothing on execution, just launch the next action/todo'
pass

View File

@ -0,0 +1,12 @@
import sys
import openerp
# TODO this loop will be exposed as open_openerp_namespace()
# once trunk-cleaning-vmt is merged.
for k, v in list(sys.modules.items()):
if k.startswith('openerp.') and sys.modules.get(k[8:]) is None:
sys.modules[k[8:]] = v
import openerp.addons.base.res.res_lang as res_lang
res_lang._group_examples()

View File

@ -0,0 +1,11 @@
-
I Create "test" user using configuration wizard.
-
!python {model: res.config.users}: |
defaults = {
'name' : 'test1',
'login' : 'test1',
'password' : 'test',
}
wizard_id = self.create(cr, uid, defaults)
self.action_add(cr, uid, [wizard_id], context)

View File

@ -0,0 +1,25 @@
-
Create some default value for some (non-existing) model, for all users.
-
!python {model: ir.values }: |
self.set(cr, uid, 'default', False, 'my_test_ir_value',['unexisting_model'], 'global value')
-
Retrieve it.
-
!python {model: ir.values }: |
# d is a list of triple (id, name, value)
d = self.get(cr, uid, 'default', False, ['unexisting_model'])
assert d[0][1] == u'my_test_ir_value', "Can't retrieve the created default value."
assert d[0][2] == 'global value', "Can't retrieve the created default value."
-
Do it again but for a specific user.
-
!python {model: ir.values }: |
self.set(cr, uid, 'default', False, 'my_test_ir_value',['unexisting_model'], 'specific value', preserve_user=True)
-
Retrieve it and check it is the one for the current user.
-
!python {model: ir.values }: |
d = self.get(cr, uid, 'default', False, ['unexisting_model'])
assert d[0][1] == u'my_test_ir_value', "Can't retrieve the created default value."
assert d[0][2] == 'specific value', "Can't retrieve the created default value."

35
openerp/conf/__init__.py Normal file
View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2011 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/>.
#
##############################################################################
""" Library-wide configuration variables.
For now, configuration code is in openerp.tools.config. It is in mainly
unprocessed form, e.g. addons_path is a string with commas-separated
paths. The aim is to have code related to configuration (command line
parsing, configuration file loading and saving, ...) in this module
and provide real Python variables, e.g. addons_paths is really a list
of paths.
"""
import deprecation
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2011 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/>.
#
##############################################################################
""" Regroup variables for deprecated features.
To keep the OpenERP server backward compatible with older modules, some
additional code is needed throughout the core library. This module keeps
track of those specific measures by providing variables that can be unset
by the user to check if her code is future proof.
"""
# If True, the Python modules inside the openerp namespace are made available
# without the 'openerp.' prefix. E.g. openerp.osv.osv and osv.osv refer to the
# same module.
# Introduced around 2011.02.
open_openerp_namespace = True
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2011 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
@ -15,12 +15,16 @@
# 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/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
""" Lower-level database access.
from ir import *
This module provides access to the underlying database without going
through the ORM. The goal is to gather sql_db.py and other various db
code.
"""
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010-2011 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
@ -15,26 +16,29 @@
# 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/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import pickle
import openerp.osv as osv
import openerp.pooler as pooler
""" Modules (also called addons) management.
def ir_set(cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None):
obj = pooler.get_pool(cr.dbname).get('ir.values')
return obj.set(cr, uid, key, key2, name, models, value, replace, isobject, meta)
"""
def ir_del(cr, uid, id):
obj = pooler.get_pool(cr.dbname).get('ir.values')
return obj.unlink(cr, uid, [id])
import openerp.modules.db
import openerp.modules.graph
import openerp.modules.loading
import openerp.modules.migration
import openerp.modules.module
# TODO temporarily expose those things
from openerp.modules.module import \
get_modules, get_modules_with_version, \
load_information_from_description_file, \
get_module_resource, zip_directory, \
get_module_path, initialize_sys_path, \
register_module_classes, init_module_models
from openerp.modules.loading import load_modules
def ir_get(cr, uid, key, key2, models, meta=False, context=None, res_id_req=False):
obj = pooler.get_pool(cr.dbname).get('ir.values')
res = obj.get(cr, uid, key, key2, models, meta=meta, context=context, res_id_req=res_id_req)
return res
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

121
openerp/modules/db.py Normal file
View File

@ -0,0 +1,121 @@
# -*- 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/>.
#
##############################################################################
import openerp.modules
def is_initialized(cr):
""" Check if a database has been initialized for the ORM.
The database can be initialized with the 'initialize' function below.
"""
cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
return len(cr.fetchall()) > 0
def initialize(cr):
""" Initialize a database with for the ORM.
This executes base/base.sql, creates the ir_module_categories (taken
from each module descriptor file), and creates the ir_module_module
and ir_model_data entries.
"""
f = openerp.modules.get_module_resource('base', 'base.sql')
base_sql_file = openerp.tools.misc.file_open(f)
try:
cr.execute(base_sql_file.read())
cr.commit()
finally:
base_sql_file.close()
for i in openerp.modules.get_modules():
mod_path = openerp.modules.get_module_path(i)
if not mod_path:
continue
# This will raise an exception if no/unreadable descriptor file.
info = openerp.modules.load_information_from_description_file(i)
if not info:
continue
categories = info['category'].split('/')
category_id = create_categories(cr, categories)
if info['installable']:
if info['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['author'],
info['website'], i, info['name'],
info['description'], category_id, state, info['certificate'],
info['web'],
info['license']))
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['depends']
for d in dependencies:
cr.execute('INSERT INTO ir_module_module_dependency \
(module_id,name) VALUES (%s, %s)', (id, d))
cr.commit()
def create_categories(cr, categories):
""" Create the ir_module_category entries for some categories.
categories is a list of strings forming a single category with its
parent categories, like ['Grand Parent', 'Parent', 'Child'].
Return the database id of the (last) category.
"""
p_id = None
while categories:
if p_id is not None:
cr.execute('SELECT id \
FROM ir_module_category \
WHERE name=%s AND parent_id=%s', (categories[0], p_id))
else:
cr.execute('SELECT id \
FROM ir_module_category \
WHERE name=%s AND parent_id IS NULL', (categories[0],))
c_id = cr.fetchone()
if not c_id:
cr.execute('INSERT INTO ir_module_category \
(name, parent_id) \
VALUES (%s, %s) RETURNING id', (categories[0], p_id))
c_id = cr.fetchone()[0]
else:
c_id = c_id[0]
p_id = c_id
categories = categories[1:]
return p_id
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

215
openerp/modules/graph.py Normal file
View File

@ -0,0 +1,215 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010-2011 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/>.
#
##############################################################################
""" Modules dependency graph. """
import os, sys, imp
from os.path import join as opj
import itertools
import zipimport
import openerp
import openerp.osv as osv
import openerp.tools as tools
import openerp.tools.osutil as osutil
from openerp.tools.safe_eval import safe_eval as eval
import openerp.pooler as pooler
from openerp.tools.translate import _
import openerp.netsvc as netsvc
import zipfile
import openerp.release as release
import re
import base64
from zipfile import PyZipFile, ZIP_DEFLATED
from cStringIO import StringIO
import logging
logger = netsvc.Logger()
class Graph(dict):
""" Modules dependency graph.
The graph is a mapping from module name to Nodes.
"""
def add_node(self, name, deps):
max_depth, father = 0, None
for n in [Node(x, self) for x in deps]:
if n.depth >= max_depth:
father = n
max_depth = n.depth
if father:
return father.add_child(name)
else:
return Node(name, self)
def update_from_db(self, cr):
if not len(self):
return
# update the graph with values from the database (if exist)
## First, we set the default values for each package in graph
additional_data = dict.fromkeys(self.keys(), {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None})
## Then we get the values from the database
cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version'
' FROM ir_module_module'
' WHERE name IN %s',(tuple(additional_data),)
)
## and we update the default values with values from the database
additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()]))
for package in self.values():
for k, v in additional_data[package.name].items():
setattr(package, k, v)
def add_module(self, cr, module, force=None):
self.add_modules(cr, [module], force)
def add_modules(self, cr, module_list, force=None):
if force is None:
force = []
packages = []
len_graph = len(self)
for module in module_list:
# This will raise an exception if no/unreadable descriptor file.
# NOTE The call to load_information_from_description_file is already
# done by db.initialize, so it is possible to not do it again here.
info = openerp.modules.module.load_information_from_description_file(module)
if info['installable']:
packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version
else:
logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable, skipped' % (module))
dependencies = dict([(p, info['depends']) for p, info in packages])
current, later = set([p for p, info in packages]), set()
while packages and current > later:
package, info = packages[0]
deps = info['depends']
# if all dependencies of 'package' are already in the graph, add 'package' in the graph
if reduce(lambda x, y: x and y in self, deps, True):
if not package in current:
packages.pop(0)
continue
later.clear()
current.remove(package)
node = self.add_node(package, deps)
node.data = info
for kind in ('init', 'demo', 'update'):
if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
setattr(node, kind, True)
else:
later.add(package)
packages.append((package, info))
packages.pop(0)
self.update_from_db(cr)
for package in later:
unmet_deps = filter(lambda p: p not in self, dependencies[package])
logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps)))
result = len(self) - len_graph
if result != len(module_list):
logger.notifyChannel('init', netsvc.LOG_WARNING, 'Not all modules have loaded.')
return result
def __iter__(self):
level = 0
done = set(self.keys())
while done:
level_modules = [(name, module) for name, module in self.items() if module.depth==level]
for name, module in level_modules:
done.remove(name)
yield module
level += 1
class Singleton(object):
def __new__(cls, name, graph):
if name in graph:
inst = graph[name]
else:
inst = object.__new__(cls)
inst.name = name
graph[name] = inst
return inst
class Node(Singleton):
""" One module in the modules dependency graph.
Node acts as a per-module singleton.
"""
def __init__(self, name, graph):
self.graph = graph
if not hasattr(self, 'children'):
self.children = []
if not hasattr(self, 'depth'):
self.depth = 0
def add_child(self, name):
node = Node(name, self.graph)
node.depth = self.depth + 1
if node not in self.children:
self.children.append(node)
for attr in ('init', 'update', 'demo'):
if hasattr(self, attr):
setattr(node, attr, True)
self.children.sort(lambda x, y: cmp(x.name, y.name))
return node
def __setattr__(self, name, value):
super(Singleton, self).__setattr__(name, value)
if name in ('init', 'update', 'demo'):
tools.config[name][self.name] = 1
for child in self.children:
setattr(child, name, value)
if name == 'depth':
for child in self.children:
setattr(child, name, value + 1)
def __iter__(self):
return itertools.chain(iter(self.children), *map(iter, self.children))
def __str__(self):
return self._pprint()
def _pprint(self, depth=0):
s = '%s\n' % self.name
for c in self.children:
s += '%s`-> %s' % (' ' * depth, c._pprint(depth+1))
return s
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

421
openerp/modules/loading.py Normal file
View File

@ -0,0 +1,421 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010-2011 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/>.
#
##############################################################################
""" Modules (also called addons) management.
"""
import os, sys, imp
from os.path import join as opj
import itertools
import zipimport
import openerp
import openerp.osv as osv
import openerp.tools as tools
import openerp.tools.osutil as osutil
from openerp.tools.safe_eval import safe_eval as eval
import openerp.pooler as pooler
from openerp.tools.translate import _
import openerp.netsvc as netsvc
import zipfile
import openerp.release as release
import re
import base64
from zipfile import PyZipFile, ZIP_DEFLATED
from cStringIO import StringIO
import logging
import openerp.modules.db
import openerp.modules.graph
import openerp.modules.migration
from openerp.modules.module import \
get_modules, get_modules_with_version, \
load_information_from_description_file, \
get_module_resource, zip_directory, \
get_module_path, initialize_sys_path, \
register_module_classes, init_module_models
logger = netsvc.Logger()
def open_openerp_namespace():
# See comment for open_openerp_namespace.
if openerp.conf.deprecation.open_openerp_namespace:
for k, v in list(sys.modules.items()):
if k.startswith('openerp.') and sys.modules.get(k[8:]) is None:
sys.modules[k[8:]] = v
def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
"""Migrates+Updates or Installs all module nodes from ``graph``
:param graph: graph of module nodes to load
:param status: status dictionary for keeping track of progress
:param perform_checks: whether module descriptors should be checked for validity (prints warnings
for same cases, and even raise osv_except if certificate is invalid)
:param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
:return: list of modules that were installed or updated
"""
def process_sql_file(cr, fp):
queries = fp.read().split(';')
for query in queries:
new_query = ' '.join(query.split())
if new_query:
cr.execute(new_query)
def load_init_xml(cr, m, idref, mode):
_load_data(cr, m, idref, mode, 'init_xml')
def load_update_xml(cr, m, idref, mode):
_load_data(cr, m, idref, mode, 'update_xml')
def load_demo_xml(cr, m, idref, mode):
_load_data(cr, m, idref, mode, 'demo_xml')
def load_data(cr, module_name, idref, mode):
_load_data(cr, module_name, idref, mode, 'data')
def load_demo(cr, module_name, idref, mode):
_load_data(cr, module_name, idref, mode, 'demo')
def load_test(cr, module_name, idref, mode):
cr.commit()
if not tools.config.options['test_disable']:
try:
_load_data(cr, module_name, idref, mode, 'test')
except Exception, e:
logging.getLogger('test').exception('Tests failed to execute in module %s', module_name)
finally:
if tools.config.options['test_commit']:
cr.commit()
else:
cr.rollback()
def _load_data(cr, module_name, idref, mode, kind):
"""
kind: data, demo, test, init_xml, update_xml, demo_xml.
noupdate is False, unless it is demo data or it is csv data in
init mode.
"""
for filename in package.data[kind]:
log = logging.getLogger('init')
log.info("module %s: loading %s", module_name, filename)
_, ext = os.path.splitext(filename)
pathname = os.path.join(module_name, filename)
fp = tools.file_open(pathname)
noupdate = False
if kind in ('demo', 'demo_xml'):
noupdate = True
try:
if ext == '.csv':
if kind in ('init', 'init_xml'):
noupdate = True
tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
elif ext == '.sql':
process_sql_file(cr, fp)
elif ext == '.yml':
tools.convert_yaml_import(cr, module_name, fp, idref, mode, noupdate)
else:
tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
finally:
fp.close()
if status is None:
status = {}
processed_modules = []
statusi = 0
pool = pooler.get_pool(cr.dbname)
migrations = openerp.modules.migration.MigrationManager(cr, graph)
logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
# register, instanciate and initialize models for each modules
for package in graph:
if skip_modules and package.name in skip_modules:
continue
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
migrations.migrate_module(package, 'pre')
register_module_classes(package.name)
models = pool.instanciate(package.name, cr)
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
init_module_models(cr, package.name, models)
cr.commit()
# load data for each modules
modobj = pool.get('ir.module.module')
for package in graph:
status['progress'] = (float(statusi)+0.1) / len(graph)
m = package.name
mid = package.id
if skip_modules and m in skip_modules:
continue
if perform_checks:
modobj.check(cr, 1, [mid])
idref = {}
status['progress'] = (float(statusi)+0.4) / len(graph)
mode = 'update'
if hasattr(package, 'init') or package.state == 'to install':
mode = 'init'
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
if package.state=='to upgrade':
# upgrading the module information
modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
load_init_xml(cr, m, idref, mode)
load_update_xml(cr, m, idref, mode)
load_data(cr, m, idref, mode)
if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
status['progress'] = (float(statusi)+0.75) / len(graph)
load_demo_xml(cr, m, idref, mode)
load_demo(cr, m, idref, mode)
cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
# launch tests only in demo mode, as most tests will depend
# on demo data. Other tests can be added into the regular
# 'data' section, but should probably not alter the data,
# as there is no rollback.
load_test(cr, m, idref, mode)
processed_modules.append(package.name)
migrations.migrate_module(package, 'post')
ver = release.major_version + '.' + package.data['version']
# Set new modules and dependencies
modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
cr.commit()
# Update translations for all installed languages
modobj.update_translations(cr, 1, [mid], None)
cr.commit()
package.state = 'installed'
for kind in ('init', 'demo', 'update'):
if hasattr(package, kind):
delattr(package, kind)
statusi += 1
cr.commit()
return processed_modules
def _check_module_names(cr, module_names):
mod_names = set(module_names)
if 'base' in mod_names:
# ignore dummy 'all' module
if 'all' in mod_names:
mod_names.remove('all')
if mod_names:
cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
if cr.dictfetchone()['count'] != len(mod_names):
# find out what module name(s) are incorrect:
cr.execute("SELECT name FROM ir_module_module")
incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
def load_modules(db, force_demo=False, status=None, update_module=False):
# TODO status['progress'] reporting is broken: used twice (and reset each
# time to zero) in load_module_graph, not fine-grained enough.
# It should be a method exposed by the pool.
initialize_sys_path()
open_openerp_namespace()
force = []
if force_demo:
force.append('demo')
cr = db.cursor()
try:
if not openerp.modules.db.is_initialized(cr):
logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
openerp.modules.db.initialize(cr)
tools.config["init"]["all"] = 1
tools.config['update']['all'] = 1
if not tools.config['without_demo']:
tools.config["demo"]['all'] = 1
# This is a brand new pool, just created in pooler.get_db_and_pool()
pool = pooler.get_pool(cr.dbname)
processed_modules = []
report = tools.assertion_report()
# NOTE: Try to also load the modules that have been marked as uninstallable previously...
STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
if 'base' in tools.config['update'] or 'all' in tools.config['update']:
cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
# STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
graph = openerp.modules.graph.Graph()
graph.add_module(cr, 'base', force)
if not graph:
logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report))
if tools.config['load_language']:
for lang in tools.config['load_language'].split(','):
tools.load_language(cr, lang)
# STEP 2: Mark other modules to be loaded/updated
if update_module:
modobj = pool.get('ir.module.module')
if ('base' in tools.config['init']) or ('base' in tools.config['update']):
logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
modobj.update_list(cr, 1)
_check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
mods = [k for k in tools.config['init'] if tools.config['init'][k]]
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
if ids:
modobj.button_install(cr, 1, ids)
mods = [k for k in tools.config['update'] if tools.config['update'][k]]
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
if ids:
modobj.button_upgrade(cr, 1, ids)
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
STATES_TO_LOAD += ['to install']
# STEP 3: Load marked modules (skipping base which was done in STEP 1)
loop_guardrail = 0
while True:
loop_guardrail += 1
if loop_guardrail > 100:
raise ValueError('Possible recursive module tree detected, aborting.')
cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
module_list = [name for (name,) in cr.fetchall() if name not in graph]
if not module_list:
break
new_modules_in_graph = graph.add_modules(cr, module_list, force)
if new_modules_in_graph == 0:
# nothing to load
break
logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
# load custom models
cr.execute('select model from ir_model where state=%s', ('manual',))
for model in cr.dictfetchall():
pool.get('ir.model').instanciate(cr, 1, model['model'], {})
# STEP 4: Finish and cleanup
if processed_modules:
cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
for (model, name) in cr.fetchall():
model_obj = pool.get(model)
if model_obj and not isinstance(model_obj, osv.osv.osv_memory):
logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
# Temporary warning while we remove access rights on osv_memory objects, as they have
# been replaced by owner-only access rights
cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
for (model, name) in cr.fetchall():
model_obj = pool.get(model)
if isinstance(model_obj, osv.osv.osv_memory):
logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
cr.execute("SELECT model from ir_model")
for (model,) in cr.fetchall():
obj = pool.get(model)
if obj:
obj._check_removed_columns(cr, log=True)
else:
logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model)
# Cleanup orphan records
pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
if report.get_report():
logger.notifyChannel('init', netsvc.LOG_INFO, report)
for kind in ('init', 'demo', 'update'):
tools.config[kind] = {}
cr.commit()
if update_module:
# Remove records referenced from ir_model_data for modules to be
# removed (and removed the references from ir_model_data).
cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
for mod_id, mod_name in cr.fetchall():
cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
for rmod, rid in cr.fetchall():
uid = 1
rmod_module= pool.get(rmod)
if rmod_module:
# TODO group by module so that we can delete multiple ids in a call
rmod_module.unlink(cr, uid, [rid])
else:
logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
cr.commit()
# Remove menu items that are not referenced by any of other
# (child) menu item, ir_values, or ir_model_data.
# This code could be a method of ir_ui_menu.
# TODO: remove menu without actions of children
while True:
cr.execute('''delete from
ir_ui_menu
where
(id not IN (select parent_id from ir_ui_menu where parent_id is not null))
and
(id not IN (select res_id from ir_values where model='ir.ui.menu'))
and
(id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
cr.commit()
if not cr.rowcount:
break
else:
logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
# Pretend that modules to be removed are actually uninstalled.
cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
cr.commit()
finally:
cr.close()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,204 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010-2011 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/>.
#
##############################################################################
""" Modules migration handling. """
import os, sys, imp
from os.path import join as opj
import itertools
import zipimport
import openerp
import openerp.osv as osv
import openerp.tools as tools
import openerp.tools.osutil as osutil
from openerp.tools.safe_eval import safe_eval as eval
import openerp.pooler as pooler
from openerp.tools.translate import _
import openerp.netsvc as netsvc
import zipfile
import openerp.release as release
import re
import base64
from zipfile import PyZipFile, ZIP_DEFLATED
from cStringIO import StringIO
import logging
import openerp.modules.db
import openerp.modules.graph
logger = netsvc.Logger()
class MigrationManager(object):
"""
This class manage the migration of modules
Migrations files must be python files containing a "migrate(cr, installed_version)" function.
Theses files must respect a directory tree structure: A 'migrations' folder which containt a
folder by version. Version can be 'module' version or 'server.module' version (in this case,
the files will only be processed by this version of the server). Python file names must start
by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
Example:
<moduledir>
`-- migrations
|-- 1.0
| |-- pre-update_table_x.py
| |-- pre-update_table_y.py
| |-- post-clean-data.py
| `-- README.txt # not processed
|-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
| |-- pre-delete_table_z.py
| `-- post-clean-data.py
`-- foo.py # not processed
This similar structure is generated by the maintenance module with the migrations files get by
the maintenance contract
"""
def __init__(self, cr, graph):
self.cr = cr
self.graph = graph
self.migrations = {}
self._get_files()
def _get_files(self):
"""
import addons.base.maintenance.utils as maintenance_utils
maintenance_utils.update_migrations_files(self.cr)
#"""
for pkg in self.graph:
self.migrations[pkg.name] = {}
if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
continue
get_module_filetree = openerp.modules.module.get_module_filetree
self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
def migrate_module(self, pkg, stage):
assert stage in ('pre', 'post')
stageformat = {'pre': '[>%s]',
'post': '[%s>]',
}
if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
return
def convert_version(version):
if version.startswith(release.major_version) and version != release.major_version:
return version # the version number already containt the server version
return "%s.%s" % (release.major_version, version)
def _get_migration_versions(pkg):
def __get_dir(tree):
return [d for d in tree if tree[d] is not None]
versions = list(set(
__get_dir(self.migrations[pkg.name]['module']) +
__get_dir(self.migrations[pkg.name]['maintenance'])
))
versions.sort(key=lambda k: parse_version(convert_version(k)))
return versions
def _get_migration_files(pkg, version, stage):
""" return a list of tuple (module, file)
"""
m = self.migrations[pkg.name]
lst = []
mapping = {'module': opj(pkg.name, 'migrations'),
'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
}
for x in mapping.keys():
if version in m[x]:
for f in m[x][version]:
if m[x][version][f] is not None:
continue
if not f.startswith(stage + '-'):
continue
lst.append(opj(mapping[x], version, f))
lst.sort()
return lst
def mergedict(a, b):
a = a.copy()
a.update(b)
return a
from openerp.tools.parse_version import parse_version
parsed_installed_version = parse_version(pkg.installed_version or '')
current_version = parse_version(convert_version(pkg.data['version']))
versions = _get_migration_versions(pkg)
for version in versions:
if parsed_installed_version < parse_version(convert_version(version)) <= current_version:
strfmt = {'addon': pkg.name,
'stage': stage,
'version': stageformat[stage] % version,
}
for pyfile in _get_migration_files(pkg, version, stage):
name, ext = os.path.splitext(os.path.basename(pyfile))
if ext.lower() != '.py':
continue
mod = fp = fp2 = None
try:
fp = tools.file_open(pyfile)
# imp.load_source need a real file object, so we create
# one from the file-like object we get from file_open
fp2 = os.tmpfile()
fp2.write(fp.read())
fp2.seek(0)
try:
mod = imp.load_source(name, pyfile, fp2)
logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt))
mod.migrate(self.cr, pkg.installed_version)
except ImportError:
logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt))
raise
except AttributeError:
logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt)
except:
raise
finally:
if fp:
fp.close()
if fp2:
fp2.close()
if mod:
del mod
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

379
openerp/modules/module.py Normal file
View File

@ -0,0 +1,379 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010-2011 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/>.
#
##############################################################################
import os, sys, imp
from os.path import join as opj
import itertools
import zipimport
import openerp
import openerp.osv as osv
import openerp.tools as tools
import openerp.tools.osutil as osutil
from openerp.tools.safe_eval import safe_eval as eval
import openerp.pooler as pooler
from openerp.tools.translate import _
import openerp.netsvc as netsvc
import zipfile
import openerp.release as release
import re
import base64
from zipfile import PyZipFile, ZIP_DEFLATED
from cStringIO import StringIO
import logging
import openerp.modules.db
import openerp.modules.graph
_ad = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons') # default addons path (base)
ad_paths = []
# Modules already loaded
loaded = []
logger = netsvc.Logger()
def initialize_sys_path():
global ad_paths
if ad_paths:
return
ad_paths = map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(','))
sys.path.insert(1, _ad)
ad_cnt=1
for adp in ad_paths:
if adp != _ad:
sys.path.insert(ad_cnt, adp)
ad_cnt+=1
ad_paths.append(_ad) # for get_module_path
def get_module_path(module, downloaded=False):
"""Return the path of the given module.
Search the addons paths and return the first path where the given
module is found. If downloaded is True, return the default addons
path if nothing else is found.
"""
initialize_sys_path()
for adp in ad_paths:
if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
return opj(adp, module)
if downloaded:
return opj(_ad, module)
logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,))
return False
def get_module_filetree(module, dir='.'):
path = get_module_path(module)
if not path:
return False
dir = os.path.normpath(dir)
if dir == '.':
dir = ''
if dir.startswith('..') or (dir and dir[0] == '/'):
raise Exception('Cannot access file outside the module')
if not os.path.isdir(path):
# zipmodule
zip = zipfile.ZipFile(path + ".zip")
files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
else:
files = osutil.listdir(path, True)
tree = {}
for f in files:
if not f.startswith(dir):
continue
if dir:
f = f[len(dir)+int(not dir.endswith('/')):]
lst = f.split(os.sep)
current = tree
while len(lst) != 1:
current = current.setdefault(lst.pop(0), {})
current[lst.pop(0)] = None
return tree
def zip_directory(directory, b64enc=True, src=True):
"""Compress a directory
@param directory: The directory to compress
@param base64enc: if True the function will encode the zip file with base64
@param src: Integrate the source files
@return: a string containing the zip file
"""
RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I)
def _zippy(archive, path, src=True):
path = os.path.abspath(path)
base = os.path.basename(path)
for f in osutil.listdir(path, True):
bf = os.path.basename(f)
if not RE_exclude.search(bf) and (src or bf in ('__openerp__.py', '__terp__.py') or not bf.endswith('.py')):
archive.write(os.path.join(path, f), os.path.join(base, f))
archname = StringIO()
archive = PyZipFile(archname, "w", ZIP_DEFLATED)
# for Python 2.5, ZipFile.write() still expects 8-bit strings (2.6 converts to utf-8)
directory = tools.ustr(directory).encode('utf-8')
archive.writepy(directory)
_zippy(archive, directory, src=src)
archive.close()
archive_data = archname.getvalue()
archname.close()
if b64enc:
return base64.encodestring(archive_data)
return archive_data
def get_module_as_zip(modulename, b64enc=True, src=True):
"""Generate a module as zip file with the source or not and can do a base64 encoding
@param modulename: The module name
@param b64enc: if True the function will encode the zip file with base64
@param src: Integrate the source files
@return: a stream to store in a file-like object
"""
ap = get_module_path(str(modulename))
if not ap:
raise Exception('Unable to find path for module %s' % modulename)
ap = ap.encode('utf8')
if os.path.isfile(ap + '.zip'):
val = file(ap + '.zip', 'rb').read()
if b64enc:
val = base64.encodestring(val)
else:
val = zip_directory(ap, b64enc, src)
return val
def get_module_resource(module, *args):
"""Return the full path of a resource of the given module.
@param module: the module
@param args: the resource path components
@return: absolute path to the resource
TODO name it get_resource_path
TODO make it available inside on osv object (self.get_resource_path)
"""
a = get_module_path(module)
if not a: return False
resource_path = opj(a, *args)
if zipfile.is_zipfile( a +'.zip') :
zip = zipfile.ZipFile( a + ".zip")
files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
resource_path = '/'.join(args)
if resource_path in files:
return opj(a, resource_path)
elif os.path.exists(resource_path):
return resource_path
return False
def load_information_from_description_file(module):
"""
:param module: The name of the module (sale, purchase, ...)
"""
terp_file = get_module_resource(module, '__openerp__.py')
if not terp_file:
terp_file = get_module_resource(module, '__terp__.py')
mod_path = get_module_path(module)
if terp_file:
info = {}
if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
terp_f = tools.file_open(terp_file)
try:
info = eval(terp_f.read())
except Exception:
logger.notifyChannel('modules', netsvc.LOG_ERROR,
'module %s: exception while evaluating file %s' %
(module, terp_file))
raise
finally:
terp_f.close()
# TODO the version should probably be mandatory
info.setdefault('version', '0')
info.setdefault('category', 'Uncategorized')
info.setdefault('depends', [])
info.setdefault('author', '')
info.setdefault('website', '')
info.setdefault('name', False)
info.setdefault('description', '')
info['certificate'] = info.get('certificate') or None
info['web'] = info.get('web') or False
info['license'] = info.get('license') or 'AGPL-3'
info.setdefault('installable', True)
info.setdefault('active', False)
for kind in ['data', 'demo', 'test',
'init_xml', 'update_xml', 'demo_xml']:
info.setdefault(kind, [])
return info
#TODO: refactor the logger in this file to follow the logging guidelines
# for 6.0
logging.getLogger('modules').debug('module %s: no descriptor file'
' found: __openerp__.py or __terp__.py (deprecated)', module)
return {}
def init_module_models(cr, module_name, obj_list):
""" Initialize a list of models.
Call _auto_init and init on each model to create or update the
database tables supporting the models.
TODO better explanation of _auto_init and init.
"""
logger.notifyChannel('init', netsvc.LOG_INFO,
'module %s: creating or updating database tables' % module_name)
# TODO _auto_init doesn't seem to return anything
# so this todo list would be useless.
todo = []
for obj in obj_list:
try:
# TODO the module in the context doesn't seem usefull:
# it is available (at least) in the class' _module attribute.
# (So module_name would be useless too.)
result = obj._auto_init(cr, {'module': module_name})
except Exception, e:
raise
if result:
todo += result
if hasattr(obj, 'init'):
obj.init(cr)
cr.commit()
todo.sort()
for t in todo:
t[1](cr, *t[2])
cr.commit()
def load_module(module_name):
""" Load a Python module found on the addons paths."""
fm = imp.find_module(module_name, ad_paths)
try:
imp.load_module(module_name, *fm)
finally:
if fm[0]:
fm[0].close()
def register_module_classes(m):
""" Register module named m, if not already registered.
This will load the module and register all of its models. (Actually, the
explicit constructor call of each of the models inside the module will
register them.)
"""
def log(e):
mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
msg = "Couldn't load %smodule %s" % (mt, m)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
global loaded
if m in loaded:
return
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
mod_path = get_module_path(m)
initialize_sys_path()
try:
zip_mod_path = mod_path + '.zip'
if not os.path.isfile(zip_mod_path):
load_module(m)
else:
zimp = zipimport.zipimporter(zip_mod_path)
zimp.load_module(m)
except Exception, e:
log(e)
raise
else:
loaded.append(m)
def get_modules():
"""Returns the list of module names
"""
def listdir(dir):
def clean(name):
name = os.path.basename(name)
if name[-4:] == '.zip':
name = name[:-4]
return name
def is_really_module(name):
name = opj(dir, name)
return os.path.isdir(name) or zipfile.is_zipfile(name)
return map(clean, filter(is_really_module, os.listdir(dir)))
plist = []
initialize_sys_path()
for ad in ad_paths:
plist.extend(listdir(ad))
return list(set(plist))
def get_modules_with_version():
modules = get_modules()
res = {}
for module in modules:
try:
info = load_information_from_description_file(module)
res[module] = "%s.%s" % (release.major_version, info['version'])
except Exception, e:
continue
return res
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

110
openerp/modules/registry.py Normal file
View File

@ -0,0 +1,110 @@
# -*- 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/>.
#
##############################################################################
""" Models registries.
"""
import openerp.sql_db
class BoundRegistry(object):
""" Model registry/database connection pair."""
def __init__(self, db, registry):
self.db = db
self.registry = registry
class RegistryManager(object):
""" Model registries manager.
The manager is responsible for creation and deletion of bound model
registries (essentially database connection/model registry pairs).
"""
# TODO maybe should receive the addons paths
def __init__(self):
# Mapping between db name and bound model registry.
# Accessed through the methods below.
self.bound_registries = {}
def get(self, db_name, force_demo=False, status=None, update_module=False,
pooljobs=True):
""" Return a bound registry for a given database name."""
if db_name in self.bound_registries:
bound_registry = self.bound_registries[db_name]
else:
bound_registry = self.new(db_name, force_demo, status,
update_module, pooljobs)
return bound_registry
def new(self, db_name, force_demo=False, status=None,
update_module=False, pooljobs=True):
""" Create and return a new bound registry for a given database name.
The (possibly) previous bound registry for that database name is
discarded.
"""
import openerp.modules
import openerp.osv.osv as osv_osv
db = openerp.sql_db.db_connect(db_name)
pool = osv_osv.osv_pool()
# Initializing a registry will call general code which will in turn
# call registries.get (this object) to obtain the registry being
# initialized. Make it available in the bound_registries dictionary
# then remove it if an exception is raised.
self.delete(db_name)
bound_registry = BoundRegistry(db, pool)
self.bound_registries[db_name] = bound_registry
try:
# This should be a method on BoundRegistry
openerp.modules.load_modules(db, force_demo, status, update_module)
except Exception:
del self.bound_registries[db_name]
raise
cr = db.cursor()
try:
pool.do_parent_store(cr)
pool.get('ir.actions.report.xml').register_all(cr)
cr.commit()
finally:
cr.close()
if pooljobs:
pool.get('ir.cron').restart(db.dbname)
return bound_registry
def delete(self, db_name):
if db_name in self.bound_registries:
del self.bound_registries[db_name]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -213,6 +213,19 @@ def init_logger():
logger.addHandler(handler)
logger.setLevel(int(tools.config['log_level'] or '0'))
# A alternative logging scheme for automated runs of the
# server intended to test it.
def init_alternative_logger():
class H(logging.Handler):
def emit(self, record):
if record.levelno > 20:
print record.levelno, record.pathname, record.msg
handler = H()
logger = logging.getLogger()
logger.handlers = []
logger.addHandler(handler)
logger.setLevel(logging.ERROR)
class Agent(object):
"""Singleton that keeps track of cancellable tasks to run at a given
timestamp.
@ -277,12 +290,13 @@ class Agent(object):
time.sleep(1)
time.sleep(60)
agent_runner = threading.Thread(target=Agent.runner, name="netsvc.Agent.runner")
# the agent runner is a typical daemon thread, that will never quit and must be
# terminated when the main process exits - with no consequence (the processing
# threads it spawns are not marked daemon)
agent_runner.setDaemon(True)
agent_runner.start()
def start_agent():
agent_runner = threading.Thread(target=Agent.runner, name="netsvc.Agent.runner")
# the agent runner is a typical daemon thread, that will never quit and must be
# terminated when the main process exits - with no consequence (the processing
# threads it spawns are not marked daemon)
agent_runner.setDaemon(True)
agent_runner.start()
import traceback
@ -393,19 +407,22 @@ def replace_request_password(args):
args[2] = '*'
return args
class OpenERPDispatcher:
def log(self, title, msg, channel=logging.DEBUG_RPC, depth=None):
logger = logging.getLogger(title)
if logger.isEnabledFor(channel):
for line in pformat(msg, depth=depth).split('\n'):
logger.log(channel, line)
def log(title, msg, channel=logging.DEBUG_RPC, depth=None, fn=""):
logger = logging.getLogger(title)
if logger.isEnabledFor(channel):
indent=''
indent_after=' '*len(fn)
for line in (fn+pformat(msg, depth=depth)).split('\n'):
logger.log(channel, indent+line)
indent=indent_after
class OpenERPDispatcher:
def log(self, title, msg, channel=logging.DEBUG_RPC, depth=None, fn=""):
log(title, msg, channel=channel, depth=depth, fn=fn)
def dispatch(self, service_name, method, params):
try:
logger = logging.getLogger('result')
self.log('service', service_name)
self.log('method', method)
self.log('params', replace_request_password(params), depth=(None if logger.isEnabledFor(logging.DEBUG_RPC_ANSWER) else 1))
self.log('service', tuple(replace_request_password(params)), depth=(None if logger.isEnabledFor(logging.DEBUG_RPC_ANSWER) else 1), fn='%s.%s'%(service_name,method))
auth = getattr(self, 'auth_provider', None)
result = ExportService.getService(service_name).dispatch(method, auth, params)
self.log('result', result, channel=logging.DEBUG_RPC_ANSWER)

View File

@ -63,7 +63,13 @@ class _column(object):
_symbol_set = (_symbol_c, _symbol_f)
_symbol_get = None
def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete="set null", translate=False, select=False, **args):
def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete="set null", translate=False, select=False, manual=False, **args):
"""
The 'manual' keyword argument specifies if the field is a custom one.
It corresponds to the 'state' column in ir_model_fields.
"""
if domain is None:
domain = []
if context is None:
@ -84,6 +90,7 @@ class _column(object):
self.read = False
self.view_load = 0
self.select = select
self.manual = manual
self.selectable = True
self.group_operator = args.get('group_operator', False)
for a in args:

View File

@ -63,6 +63,39 @@ from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools import SKIPPED_ELEMENT_TYPES
regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
# Mapping between openerp module names and their osv classes.
module_class_list = {}
def check_object_name(name):
""" Check if the given name is a valid openerp object name.
The _name attribute in osv and osv_memory object is subject to
some restrictions. This function returns True or False whether
the given name is allowed or not.
TODO: this is an approximation. The goal in this approximation
is to disallow uppercase characters (in some places, we quote
table/column names and in other not, which leads to this kind
of errors:
psycopg2.ProgrammingError: relation "xxx" does not exist).
The same restriction should apply to both osv and osv_memory
objects for consistency.
"""
if regex_object_name.match(name) is None:
return False
return True
def raise_on_invalid_object_name(name):
if not check_object_name(name):
msg = "The _name attribute %s is not valid." % name
logger = netsvc.Logger()
logger.notifyChannel('orm', netsvc.LOG_ERROR, msg)
raise except_orm('ValueError', msg)
POSTGRES_CONFDELTYPES = {
'RESTRICT': 'r',
@ -384,6 +417,21 @@ def get_pg_type(f):
class orm_template(object):
""" Base class for OpenERP models.
OpenERP models are created by inheriting from this class (although
not directly; more specifically by inheriting from osv or
osv_memory). The constructor is called once, usually directly
after the class definition, e.g.:
class user(osv):
...
user()
The system will later instanciate the class once per database (on
which the class' module is installed).
"""
_name = None
_columns = {}
_constraints = []
@ -421,6 +469,18 @@ class orm_template(object):
raise NotImplementedError(_('The read_group method is not implemented on this object !'))
def _field_create(self, cr, context=None):
"""
Create/update entries in ir_model, ir_model_data, and ir_model_fields.
- create an entry in ir_model (if there is not already one),
- create an entry in ir_model_data (if there is not already one, and if
'module' is in the context),
- update ir_model_fields with the fields found in _columns
(TODO there is some redundancy as _columns is updated from
ir_model_fields in __init__).
"""
if context is None:
context = {}
cr.execute("SELECT id FROM ir_model WHERE model=%s", (self._name,))
@ -458,6 +518,7 @@ class orm_template(object):
'readonly': (f.readonly and 1) or 0,
'required': (f.required and 1) or 0,
'selectable': (f.selectable and 1) or 0,
'translate': (f.translate and 1) or 0,
'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
}
# When its a custom field,it does not contain f.select
@ -474,13 +535,13 @@ class orm_template(object):
vals['id'] = id
cr.execute("""INSERT INTO ir_model_fields (
id, model_id, model, name, field_description, ttype,
relation,view_load,state,select_level,relation_field
relation,view_load,state,select_level,relation_field, translate
) VALUES (
%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
)""", (
id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
vals['relation'], bool(vals['view_load']), 'base',
vals['select_level'], vals['relation_field']
vals['select_level'], vals['relation_field'], bool(vals['translate'])
))
if 'module' in context:
name1 = 'field_' + self._table + '_' + k
@ -497,20 +558,118 @@ class orm_template(object):
cr.commit()
cr.execute("""UPDATE ir_model_fields SET
model_id=%s, field_description=%s, ttype=%s, relation=%s,
view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s
view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s, translate=%s
WHERE
model=%s AND name=%s""", (
vals['model_id'], vals['field_description'], vals['ttype'],
vals['relation'], bool(vals['view_load']),
vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], vals['model'], vals['name']
vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], bool(vals['translate']), vals['model'], vals['name']
))
break
cr.commit()
def _auto_init(self, cr, context=None):
raise_on_invalid_object_name(self._name)
self._field_create(cr, context=context)
def __init__(self, cr):
#
# Goal: try to apply inheritance at the instanciation level and
# put objects in the pool var
#
@classmethod
def makeInstance(cls, pool, cr, attributes):
""" Instanciate a given model.
This class method instanciates the class of some model (i.e. a class
deriving from osv or osv_memory). The class might be the class passed
in argument or, if it inherits from another class, a class constructed
by combining the two classes.
The ``attributes`` argument specifies which parent class attributes
have to be combined.
TODO: the creation of the combined class is repeated at each call of
this method. This is probably unnecessary.
"""
parent_names = getattr(cls, '_inherit', None)
if parent_names:
if isinstance(parent_names, (str, unicode)):
name = cls._name or parent_names
parent_names = [parent_names]
else:
name = cls._name
if not name:
raise TypeError('_name is mandatory in case of multiple inheritance')
for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
parent_class = pool.get(parent_name).__class__
if not pool.get(parent_name):
raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n'
'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
nattr = {}
for s in attributes:
new = copy.copy(getattr(pool.get(parent_name), s))
if s == '_columns':
# Don't _inherit custom fields.
for c in new.keys():
if new[c].manual:
del new[c]
if hasattr(new, 'update'):
new.update(cls.__dict__.get(s, {}))
elif s=='_constraints':
for c in cls.__dict__.get(s, []):
exist = False
for c2 in range(len(new)):
#For _constraints, we should check field and methods as well
if new[c2][2]==c[2] and (new[c2][0] == c[0] \
or getattr(new[c2][0],'__name__', True) == \
getattr(c[0],'__name__', False)):
# If new class defines a constraint with
# same function name, we let it override
# the old one.
new[c2] = c
exist = True
break
if not exist:
new.append(c)
else:
new.extend(cls.__dict__.get(s, []))
nattr[s] = new
cls = type(name, (cls, parent_class), nattr)
obj = object.__new__(cls)
obj.__init__(pool, cr)
return obj
def __new__(cls):
""" Register this model.
This doesn't create an instance but simply register the model
as being part of the module where it is defined.
TODO make it possible to not even have to call the constructor
to be registered.
"""
# Set the module name (e.g. base, sale, accounting, ...) on the class.
module = cls.__module__.split('.')[0]
if not hasattr(cls, '_module'):
cls._module = module
# Remember which models to instanciate for this module.
module_class_list.setdefault(cls._module, []).append(cls)
# Since we don't return an instance here, the __init__
# method won't be called.
return None
def __init__(self, pool, cr):
""" Initialize a model and make it part of the given registry."""
pool.add(self._name, self)
self.pool = pool
if not self._name and not hasattr(self, '_inherit'):
name = type(self).__name__.split('.')[0]
msg = "The class %s has to have a _name attribute" % name
@ -736,7 +895,7 @@ class orm_template(object):
id = ir_model_data[0]['res_id']
else:
obj_model = self.pool.get(model_name)
ids = obj_model.name_search(cr, uid, id, operator='=')
ids = obj_model.name_search(cr, uid, id, operator='=', context=context)
if not ids:
raise ValueError('No record found for %s' % (id,))
id = ids[0][0]
@ -762,10 +921,12 @@ class orm_template(object):
done = {}
for i in range(len(fields)):
res = False
if i >= len(line):
raise Exception(_('Please check that all your lines have %d columns.'
'Stopped around line %d having %d columns.') % \
(len(fields), position+2, len(line)))
if not line[i]:
continue
if i >= len(line):
raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
field = fields[i]
if field[:len(prefix)] <> prefix:
@ -838,14 +999,15 @@ class orm_template(object):
res = line[i] and float(line[i]) or 0.0
elif fields_def[field[len(prefix)]]['type'] == 'selection':
for key, val in fields_def[field[len(prefix)]]['selection']:
if line[i] in [tools.ustr(key), tools.ustr(val)]:
if tools.ustr(line[i]) in [tools.ustr(key), tools.ustr(val)]:
res = key
break
if line[i] and not res:
logger.notifyChannel("import", netsvc.LOG_WARNING,
_("key '%s' not found in selection field '%s'") % \
(line[i], field[len(prefix)]))
warning += [_("Key/value '%s' not found in selection field '%s'") % (line[i], field[len(prefix)])]
(tools.ustr(line[i]), tools.ustr(field[len(prefix)])))
warning += [_("Key/value '%s' not found in selection field '%s'") % (tools.ustr(line[i]), tools.ustr(field[len(prefix)]))]
else:
res = line[i]
@ -1896,8 +2058,12 @@ class orm_memory(orm_template):
_max_hours = config.get('osv_memory_age_limit')
_check_time = 20
def __init__(self, cr):
super(orm_memory, self).__init__(cr)
@classmethod
def createInstance(cls, pool, cr):
return cls.makeInstance(pool, cr, ['_columns', '_defaults'])
def __init__(self, pool, cr):
super(orm_memory, self).__init__(pool, cr)
self.datas = {}
self.next_id = 0
self.check_id = 0
@ -2127,12 +2293,11 @@ class orm(orm_template):
:param cr: database cursor
:param uid: current user id
:param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]
:param fields: list of fields present in the list view specified on the object
:param groupby: list of fields on which to groupby the records
:type fields_list: list (example ['field_name_1', ...])
:param offset: optional number of records to skip
:param limit: optional max number of records to return
:param context: context arguments, like lang, time zone
:param list fields: list of fields present in the list view specified on the object
:param list groupby: fields by which the records will be grouped
:param int offset: optional number of records to skip
:param int limit: optional max number of records to return
:param dict context: context arguments, like lang, time zone
:param order: optional ``order by`` specification, for overriding the natural
sort ordering of the groups, see also :py:meth:`~osv.osv.osv.search`
(supported only for many2one fields currently)
@ -2364,6 +2529,22 @@ class orm(orm_template):
self._table, column['attname'])
def _auto_init(self, cr, context=None):
"""
Call _field_create and, unless _auto is False:
- create the corresponding table in database for the model,
- possibly add the parent columns in database,
- possibly add the columns 'create_uid', 'create_date', 'write_uid',
'write_date' in database if _log_access is True (the default),
- report on database columns no more existing in _columns,
- remove no more existing not null constraints,
- alter existing database columns to match _columns,
- create database tables to match _columns,
- add database indices to match _columns,
"""
raise_on_invalid_object_name(self._name)
if context is None:
context = {}
store_compute = False
@ -2769,8 +2950,22 @@ class orm(orm_template):
cr.commit()
return todo_end
def __init__(self, cr):
super(orm, self).__init__(cr)
@classmethod
def createInstance(cls, pool, cr):
return cls.makeInstance(pool, cr, ['_columns', '_defaults',
'_inherits', '_constraints', '_sql_constraints'])
def __init__(self, pool, cr):
"""
- copy the stored fields' functions in the osv_pool,
- update the _columns with the fields found in ir_model_fields,
- ensure there is a many2one for each _inherits'd parent,
- update the children's _columns,
- give a chance to each field to initialize itself.
"""
super(orm, self).__init__(pool, cr)
if not hasattr(self, '_log_access'):
# if not access is not specify, it is the same value as _auto
@ -2827,6 +3022,7 @@ class orm(orm_template):
'size': field['size'],
'ondelete': field['on_delete'],
'translate': (field['translate']),
'manual': True,
#'select': int(field['select_level'])
}
@ -2854,6 +3050,8 @@ class orm(orm_template):
for f in self._columns:
self._columns[f].restart()
__init__.__doc__ = orm_template.__init__.__doc__ + __init__.__doc__
#
# Update objects that uses this one to update their _inherits fields
#
@ -3051,6 +3249,9 @@ class orm(orm_template):
for key, val in todo.items():
if key:
res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
assert res2 is not None, \
'The function field "%s" on the "%s" model returned None\n' \
'(a dictionary was expected).' % (val[0], self._name)
for pos in val:
for record in res:
if isinstance(res2[record['id']], str): res2[record['id']] = eval(res2[record['id']]) #TOCHECK : why got string instend of dict in python2.6
@ -3230,7 +3431,7 @@ class orm(orm_template):
self.check_access_rule(cr, uid, ids, 'unlink', context=context)
pool_model_data = self.pool.get('ir.model.data')
pool_ir_values = self.pool.get('ir.values')
ir_values_obj = self.pool.get('ir.values')
for sub_ids in cr.split_for_in_conditions(ids):
cr.execute('delete from ' + self._table + ' ' \
'where id IN %s', (sub_ids,))
@ -3243,11 +3444,11 @@ class orm(orm_template):
pool_model_data.unlink(cr, uid, referenced_ids, context=context)
# For the same reason, removing the record relevant to ir_values
ir_value_ids = pool_ir_values.search(cr, uid,
ir_value_ids = ir_values_obj.search(cr, uid,
['|',('value','in',['%s,%s' % (self._name, sid) for sid in sub_ids]),'&',('res_id','in',list(sub_ids)),('model','=',self._name)],
context=context)
if ir_value_ids:
pool_ir_values.unlink(cr, uid, ir_value_ids, context=context)
ir_values_obj.unlink(cr, uid, ir_value_ids, context=context)
for order, object, store_ids, fields in result_store:
if object != self._name:
@ -3532,7 +3733,10 @@ class orm(orm_template):
#
def create(self, cr, user, vals, context=None):
"""
Create new record with specified value
Create a new record for the model.
The values for the new record are initialized using the ``vals``
argument, and if necessary the result of ``default_get()``.
:param cr: database cursor
:param user: current user id

View File

@ -23,18 +23,18 @@
# OSV: Objects Services
#
import sys
import inspect
import orm
import openerp.netsvc as netsvc
import openerp.pooler as pooler
import openerp.sql_db as sql_db
import copy
import logging
from psycopg2 import IntegrityError, errorcodes
from openerp.tools.func import wraps
from openerp.tools.translate import translate
module_list = []
module_class_list = {}
class_pool = {}
from openerp.osv.orm import module_class_list
class except_osv(Exception):
def __init__(self, name, value, exc_type='warning'):
@ -92,7 +92,7 @@ class object_proxy(netsvc.Service):
ids = args[3]
else:
ids = []
cr = pooler.get_db_only(dbname).cursor()
cr = sql_db.db_connect(db_name).cursor()
return src(obj, cr, uid, ids, context=(ctx or {}))
except Exception:
pass
@ -103,7 +103,7 @@ class object_proxy(netsvc.Service):
# be returned, it is the best we have.
try:
cr = pooler.get_db_only(dbname).cursor()
cr = sql_db.db_connect(db_name).cursor()
res = translate(cr, name=False, source_type=ttype,
lang=lang, source=src)
if res:
@ -117,7 +117,7 @@ class object_proxy(netsvc.Service):
return tr(src, 'code')
try:
if not pooler.get_pool(dbname)._ready:
if pooler.get_pool(dbname)._init:
raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.')
return f(self, dbname, *args, **kwargs)
except orm.except_orm, inst:
@ -202,163 +202,64 @@ class object_proxy(netsvc.Service):
cr.close()
return res
object_proxy()
class osv_pool(object):
""" Model registry for a particular database.
The registry is essentially a mapping between model names and model
instances. There is one registry instance per database.
"""
def __init__(self):
self._ready = False
self.obj_pool = {}
self.module_object_list = {}
self.created = []
self.obj_pool = {} # model name/model instance mapping
self._sql_error = {}
self._store_function = {}
self._init = True
self._init_parent = {}
self.logger = logging.getLogger("pool")
def init_set(self, cr, mode):
different = mode != self._init
if different:
if mode:
self._init_parent = {}
if not mode:
for o in self._init_parent:
self.get(o)._parent_store_compute(cr)
self._init = mode
self._ready = True
return different
def do_parent_store(self, cr):
for o in self._init_parent:
self.get(o)._parent_store_compute(cr)
self._init = False
def obj_list(self):
""" Return the list of model names in this registry."""
return self.obj_pool.keys()
# adds a new object instance to the object pool.
# if it already existed, the instance is replaced
def add(self, name, obj_inst):
if name in self.obj_pool:
del self.obj_pool[name]
self.obj_pool[name] = obj_inst
def add(self, model_name, model):
""" Add or replace a model in the registry."""
self.obj_pool[model_name] = model
module = str(obj_inst.__class__)[6:]
module = module[:len(module)-1]
module = module.split('.')[0][2:]
self.module_object_list.setdefault(module, []).append(obj_inst)
# Return None if object does not exist
def get(self, name):
obj = self.obj_pool.get(name, None)
return obj
""" Return a model for a given name or None if it doesn't exist."""
return self.obj_pool.get(name)
#TODO: pass a list of modules to load
def instanciate(self, module, cr):
""" Instanciate all the classes of a given module for a particular db."""
res = []
class_list = module_class_list.get(module, [])
for klass in class_list:
res.append(klass.createInstance(self, module, cr))
# Instanciate classes registered through their constructor and
# add them to the pool.
for klass in module_class_list.get(module, []):
res.append(klass.createInstance(self, cr))
return res
class osv_base(object):
def __init__(self, pool, cr):
pool.add(self._name, self)
self.pool = pool
super(osv_base, self).__init__(cr)
def __new__(cls):
module = str(cls)[6:]
module = module[:len(module)-1]
module = module.split('.')[0][2:]
if not hasattr(cls, '_module'):
cls._module = module
module_class_list.setdefault(cls._module, []).append(cls)
class_pool[cls._name] = cls
if module not in module_list:
module_list.append(cls._module)
return None
class osv_memory(orm.orm_memory):
""" Deprecated class. """
pass
class osv_memory(osv_base, orm.orm_memory):
#
# Goal: try to apply inheritancy at the instanciation level and
# put objects in the pool var
#
def createInstance(cls, pool, module, cr):
parent_names = getattr(cls, '_inherit', None)
if parent_names:
if isinstance(parent_names, (str, unicode)):
name = cls._name or parent_names
parent_names = [parent_names]
else:
name = cls._name
if not name:
raise TypeError('_name is mandatory in case of multiple inheritance')
for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
parent_class = pool.get(parent_name).__class__
assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
nattr = {}
for s in ('_columns', '_defaults'):
new = copy.copy(getattr(pool.get(parent_name), s))
if hasattr(new, 'update'):
new.update(cls.__dict__.get(s, {}))
else:
new.extend(cls.__dict__.get(s, []))
nattr[s] = new
cls = type(name, (cls, parent_class), nattr)
class osv(orm.orm):
""" Deprecated class. """
pass
obj = object.__new__(cls)
obj.__init__(pool, cr)
return obj
createInstance = classmethod(createInstance)
class osv(osv_base, orm.orm):
#
# Goal: try to apply inheritancy at the instanciation level and
# put objects in the pool var
#
def createInstance(cls, pool, module, cr):
parent_names = getattr(cls, '_inherit', None)
if parent_names:
if isinstance(parent_names, (str, unicode)):
name = cls._name or parent_names
parent_names = [parent_names]
else:
name = cls._name
if not name:
raise TypeError('_name is mandatory in case of multiple inheritance')
for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
parent_class = pool.get(parent_name).__class__
assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
nattr = {}
for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'):
new = copy.copy(getattr(pool.get(parent_name), s))
if hasattr(new, 'update'):
new.update(cls.__dict__.get(s, {}))
else:
if s=='_constraints':
for c in cls.__dict__.get(s, []):
exist = False
for c2 in range(len(new)):
#For _constraints, we should check field and methods as well
if new[c2][2]==c[2] and (new[c2][0] == c[0] \
or getattr(new[c2][0],'__name__', True) == \
getattr(c[0],'__name__', False)):
# If new class defines a constraint with
# same function name, we let it override
# the old one.
new[c2] = c
exist = True
break
if not exist:
new.append(c)
else:
new.extend(cls.__dict__.get(s, []))
nattr[s] = new
cls = type(name, (cls, parent_class), nattr)
obj = object.__new__(cls)
obj.__init__(pool, cr)
return obj
createInstance = classmethod(createInstance)
def start_object_proxy():
object_proxy()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -19,64 +19,50 @@
#
##############################################################################
pool_dic = {}
""" Functions kept for backward compatibility.
They are simple wrappers around a global RegistryManager methods.
"""
from openerp.modules.registry import RegistryManager
_Registries = None
def ensure_registries():
global _Registries
if _Registries is None:
_Registries = RegistryManager()
def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False, pooljobs=True):
if not status:
status={}
"""Create and return a database connection and a newly initialized registry."""
ensure_registries()
bound_registry = _Registries.get(db_name, force_demo, status, update_module, pooljobs)
return bound_registry.db, bound_registry.registry
db = get_db_only(db_name)
if db_name in pool_dic:
pool = pool_dic[db_name]
else:
import openerp.addons as addons
import openerp.osv.osv as osv_osv
pool = osv_osv.osv_pool()
pool_dic[db_name] = pool
try:
addons.load_modules(db, force_demo, status, update_module)
except Exception:
del pool_dic[db_name]
raise
cr = db.cursor()
try:
pool.init_set(cr, False)
pool.get('ir.actions.report.xml').register_all(cr)
cr.commit()
finally:
cr.close()
if pooljobs:
pool.get('ir.cron').restart(db.dbname)
return db, pool
def delete_pool(db_name):
"""Delete an existing registry."""
ensure_registries()
_Registries.delete(db_name)
def restart_pool(db_name, force_demo=False, status=None, update_module=False):
if db_name in pool_dic:
del pool_dic[db_name]
return get_db_and_pool(db_name, force_demo, status, update_module=update_module)
def get_db_only(db_name):
# ATTENTION:
# do not put this import outside this function
# sql_db must not be loaded before the logger is initialized.
# sql_db import psycopg2.tool which create a default logger if there is not.
# this resulting of having the logs outputed twice...
import openerp.sql_db as sql_db
db = sql_db.db_connect(db_name)
return db
"""Delete an existing registry and return a database connection and a newly initialized registry."""
ensure_registries()
bound_registry = _Registries.new(db_name, force_demo, status, update_module, True)
return bound_registry.db, bound_registry.registry
def get_db(db_name):
"""Return a database connection. The corresponding registry is initialized."""
return get_db_and_pool(db_name)[0]
def get_pool(db_name, force_demo=False, status=None, update_module=False):
pool = get_db_and_pool(db_name, force_demo, status, update_module)[1]
return pool
"""Return a model registry."""
return get_db_and_pool(db_name, force_demo, status, update_module)[1]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -27,7 +27,7 @@ import openerp.netsvc as netsvc
import openerp.pooler as pooler
import openerp.tools as tools
import openerp.addons as addons
import openerp.modules
import print_xml
import render
import urllib
@ -119,7 +119,7 @@ class report_rml(report_int):
pos_xml = i.end()
doc = print_xml.document(cr, uid, {}, {})
tmpl_path = addons.get_module_resource('base', 'report', 'corporate_defaults.xml')
tmpl_path = openerp.modules.get_module_resource('base', 'report', 'corporate_defaults.xml')
doc.parse(tmpl_path, [uid], 'res.users', context)
corporate_header = doc.xml_get()
doc.close()

View File

@ -27,7 +27,7 @@ try:
import Image
except ImportError:
import logging
logging.getLogger('init').warning('Python Imaging not installed, you can use only .JPG pictures !')
logging.warning('Python Imaging not installed, you can use only .JPG pictures !')
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -451,7 +451,7 @@ class _rml_canvas(object):
res = utils._regex.findall(node.text)
for key in res:
newtext = eval(key, {}, self.localcontext)
node.text = newtext
node.text = newtext or ''
image_data = None
if node.text:
image_data = base64.decodestring(node.text)
@ -795,10 +795,10 @@ class _rml_flowable(object):
return False
else:
import base64
newtext = node.text
if self.localcontext:
newtext = utils._process_text(self, node.text or '')
node.text = newtext
image_data = base64.decodestring(node.text)
image_data = base64.decodestring(newtext)
if not image_data:
self._logger.debug("No inline image data")
return False

View File

@ -35,6 +35,7 @@ import zipfile
import common
from openerp.osv.fields import float as float_class, function as function_class
from openerp.osv.orm import browse_record
from openerp.tools.translate import _
DT_FORMAT = '%Y-%m-%d'
DHM_FORMAT = '%Y-%m-%d %H:%M:%S'
@ -424,7 +425,7 @@ class report_sxw(report_rml, preprocess.report):
elif report_type=='mako2html':
fnct = self.create_source_mako2html
else:
raise 'Unknown Report Type'
raise NotImplementedError(_('Unknown report type: %s') % report_type)
fnct_ret = fnct(cr, uid, ids, data, report_xml, context)
if not fnct_ret:
return (False,False)

View File

@ -19,6 +19,8 @@
#
##############################################################################
import http_server
import netrpc_server
import web_services

View File

@ -28,13 +28,12 @@ import time
import sys
import platform
from openerp.tools.translate import _
import openerp.addons as addons
import openerp.ir
import openerp.netsvc as netsvc
import openerp.pooler as pooler
import openerp.release as release
import openerp.sql_db as sql_db
import openerp.tools as tools
import openerp.modules
import locale
import logging
from cStringIO import StringIO
@ -91,12 +90,7 @@ class db(netsvc.ExportService):
cr = None
try:
serv.actions[id]['progress'] = 0
cr = sql_db.db_connect(db_name).cursor()
tools.init_db(cr)
tools.config['lang'] = lang
cr.commit()
cr.close()
cr = None
pool = pooler.restart_pool(db_name, demo, serv.actions[id],
update_module=True)[1]
@ -139,7 +133,7 @@ class db(netsvc.ExportService):
def exp_get_progress(self, id):
if self.actions[id]['thread'].isAlive():
# return addons.init_progress[db_name]
# return openerp.modules.init_progress[db_name]
return (min(self.actions[id].get('progress', 0),0.95), [])
else:
clean = self.actions[id]['clean']
@ -154,6 +148,7 @@ class db(netsvc.ExportService):
def exp_drop(self, db_name):
sql_db.close_db(db_name)
openerp.netsvc.Agent.cancel(db_name)
logger = netsvc.Logger()
db = sql_db.db_connect('template1')
@ -256,6 +251,7 @@ class db(netsvc.ExportService):
def exp_rename(self, old_name, new_name):
sql_db.close_db(old_name)
openerp.netsvc.Agent.cancel(db_name)
logger = netsvc.Logger()
db = sql_db.db_connect('template1')
@ -346,7 +342,6 @@ class db(netsvc.ExportService):
l.notifyChannel('web-services', netsvc.LOG_ERROR, tb_s)
raise
return True
db()
class _ObjectService(netsvc.ExportService):
"A common base class for those who have fn(db, uid, password,...) "
@ -369,8 +364,6 @@ class common(_ObjectService):
def dispatch(self, method, auth, params):
logger = netsvc.Logger()
if method in [ 'ir_set','ir_del', 'ir_get' ]:
return self.common_dispatch(method,auth,params)
if method == 'login':
# At this old dispatcher, we do NOT update the auth proxy
res = security.login(params[0], params[1], params[2])
@ -401,22 +394,6 @@ class common(_ObjectService):
def new_dispatch(self,method,auth,params):
pass
def exp_ir_set(self, cr, uid, keys, args, name, value, replace=True, isobject=False):
res = ir.ir_set(cr,uid, keys, args, name, value, replace, isobject)
return res
def exp_ir_del(self, cr, uid, id):
res = ir.ir_del(cr,uid, id)
return res
def exp_ir_get(self, cr, uid, keys, args=None, meta=None, context=None):
if not args:
args=[]
if not context:
context={}
res = ir.ir_get(cr,uid, keys, args, meta, context)
return res
def exp_about(self, extended=False):
"""Return information about the OpenERP Server.
@ -447,7 +424,7 @@ GNU Public Licence.
if not rc.id:
raise tm.RemoteContractException('This contract does not exist or is not active')
return rc.get_available_updates(rc.id, addons.get_modules_with_version())
return rc.get_available_updates(rc.id, openerp.modules.get_modules_with_version())
except tm.RemoteContractException, e:
self.abortResponse(1, 'Migration Error', 'warning', str(e))
@ -465,7 +442,7 @@ GNU Public Licence.
l.notifyChannel('migration', netsvc.LOG_INFO, 'starting migration with contract %s' % (rc.name,))
zips = rc.retrieve_updates(rc.id, addons.get_modules_with_version())
zips = rc.retrieve_updates(rc.id, openerp.modules.get_modules_with_version())
from shutil import rmtree, copytree, copy
@ -477,7 +454,7 @@ GNU Public Licence.
for module in zips:
l.notifyChannel('migration', netsvc.LOG_INFO, 'upgrade module %s' % (module,))
mp = addons.get_module_path(module)
mp = openerp.modules.get_module_path(module)
if mp:
if os.path.isdir(mp):
copytree(mp, os.path.join(backup_directory, module))
@ -578,7 +555,6 @@ GNU Public Licence.
logger.warning("Counters of SQL will not be reliable unless DEBUG_SQL is set at the server's config.")
return sql_db.sql_counter
common()
class objects_proxy(netsvc.ExportService):
def __init__(self, name="object"):
@ -602,8 +578,6 @@ class objects_proxy(netsvc.ExportService):
def new_dispatch(self,method,auth,params):
pass
objects_proxy()
#
# Wizard ID: 1
@ -664,7 +638,6 @@ class wizard(netsvc.ExportService):
raise Exception, 'AccessDenied'
else:
raise Exception, 'WizardNotFound'
wizard()
#
# TODO: set a maximum report number per user to avoid DOS attacks
@ -778,7 +751,13 @@ class report_spool(netsvc.ExportService):
else:
raise Exception, 'ReportNotFound'
report_spool()
def start_web_services():
db()
common()
objects_proxy()
wizard()
report_spool()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -54,7 +54,6 @@ psycopg2.extensions.register_type(psycopg2.extensions.new_type((700, 701, 1700,)
import tools
from tools.func import wraps, frame_codeinfo
from netsvc import Agent
from datetime import datetime as mdt
from datetime import timedelta
import threading
@ -68,17 +67,22 @@ sql_counter = 0
class Cursor(object):
IN_MAX = 1000 # decent limit on size of IN queries - guideline = Oracle limit
__logger = logging.getLogger('db.cursor')
__logger = None
def check(f):
@wraps(f)
def wrapper(self, *args, **kwargs):
if self.__closed:
raise psycopg2.OperationalError('Unable to use the cursor after having closed it')
msg = 'Unable to use a closed cursor.'
if self.__closer:
msg += ' It was closed at %s, line %s' % self.__closer
raise psycopg2.OperationalError(msg)
return f(self, *args, **kwargs)
return wrapper
def __init__(self, pool, dbname, serialized=False):
if self.__class__.__logger is None:
self.__class__.__logger = logging.getLogger('db.cursor')
self.sql_from_log = {}
self.sql_into_log = {}
@ -100,6 +104,7 @@ class Cursor(object):
self.__caller = frame_codeinfo(currentframe(),2)
else:
self.__caller = False
self.__closer = False
def __del__(self):
if not self.__closed:
@ -198,6 +203,8 @@ class Cursor(object):
if not self._obj:
return
if self.sql_log:
self.__closer = frame_codeinfo(currentframe(),3)
self.print_log()
if not self._serialized:
@ -375,15 +382,18 @@ def dsn_are_equals(first, second):
return key(first) == key(second)
_Pool = ConnectionPool(int(tools.config['db_maxconn']))
_Pool = None
def db_connect(db_name):
global _Pool
if _Pool is None:
_Pool = ConnectionPool(int(tools.config['db_maxconn']))
currentThread().dbname = db_name
return Connection(_Pool, db_name)
def close_db(db_name):
""" You might want to call openerp.netsvc.Agent.cancel(db_name) along this function."""
_Pool.close_all(dsn(db_name))
Agent.cancel(db_name)
tools.cache.clean_caches_for_db(db_name)
ct = currentThread()
if hasattr(ct, 'dbname'):

View File

@ -32,74 +32,28 @@ 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):
# Options not exposed on the command line. Command line options will be added
# from optparse's parser.
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/',
'osv_memory_count_limit': None, # number of records in each osv_memory virtual table
'osv_memory_age_limit': 1, # hours
'reportgz': False,
'root_path': None,
}
self.blacklist_for_save = set(["publisher_warranty_url", "load_language", "root_path"])
# Not exposed in the configuration file.
self.blacklist_for_save = set(
['publisher_warranty_url', 'load_language', 'root_path',
'init', 'save', 'config', 'update'])
self.misc = {}
self.config_file = fname
@ -122,15 +76,18 @@ class configmanager(object):
group.add_option("--without-demo", dest="without_demo",
help="disable loading demo data for modules to be installed (comma-separated, use \"all\" for all modules). Requires -d and -i. Default is %default",
default=False)
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)
group.add_option("-P", "--import-partial", dest="import_partial", default='',
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.")
group.add_option("--pidfile", dest="pidfile", help="file where the server pid will be stored")
parser.add_option_group(group)
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")
group.add_option("--xmlrpc-interface", dest="xmlrpc_interface", default='',
help="Specify the TCP IP address for the XML-RPC protocol. The empty string binds to all interfaces.")
group.add_option("--xmlrpc-port", dest="xmlrpc_port", default=8069,
help="specify the TCP port for the XML-RPC protocol", type="int")
group.add_option("--no-xmlrpc", dest="xmlrpc", action="store_false", default=True,
help="disable the XML-RPC protocol")
parser.add_option_group(group)
title = "XML-RPC Secure Configuration"
@ -138,18 +95,26 @@ class configmanager(object):
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")
group.add_option("--xmlrpcs-interface", dest="xmlrpcs_interface", default='',
help="Specify the TCP IP address for the XML-RPC Secure protocol. The empty string binds to all interfaces.")
group.add_option("--xmlrpcs-port", dest="xmlrpcs_port", default=8071,
help="specify the TCP port for the XML-RPC Secure protocol", type="int")
group.add_option("--no-xmlrpcs", dest="xmlrpcs", action="store_false", default=True,
help="disable the XML-RPC Secure protocol")
group.add_option("--cert-file", dest="secure_cert_file", default='server.cert',
help="specify the certificate file for the SSL connection")
group.add_option("--pkey-file", dest="secure_pkey_file", default='server.pkey',
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")
group.add_option("--netrpc-interface", dest="netrpc_interface", default='',
help="specify the TCP IP address for the NETRPC protocol")
group.add_option("--netrpc-port", dest="netrpc_port", default=8070,
help="specify the TCP port for the NETRPC protocol", type="int")
group.add_option("--no-netrpc", dest="netrpc", action="store_false", default=True,
help="disable the NETRPC protocol")
parser.add_option_group(group)
# Static HTTP
@ -161,45 +126,60 @@ class configmanager(object):
# 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-file", dest="test_file", default=False,
help="Launch a YML test file.")
group.add_option("--test-report-directory", dest="test_report_directory", default=False,
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.")
group.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(),))
default='error',
help="specify the level at which a failed assertion will stop the server. Accepted values: %s" % (self._LOGLEVELS.keys(),))
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",
group.add_option("--no-logrotate", dest="logrotate", action="store_false", default=True,
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(),
default='info',
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')
group.add_option('--email-from', dest='email_from', default=False,
help='specify the SMTP email address for sending email')
group.add_option('--smtp', dest='smtp_server', default='localhost',
help='specify the SMTP server for sending email')
group.add_option('--smtp-port', dest='smtp_port', default=25,
help='specify the SMTP port', type="int")
group.add_option('--smtp-ssl', dest='smtp_ssl', action='store_true', default=False,
help='specify the SMTP server support SSL or not')
group.add_option('--smtp-user', dest='smtp_user', default=False,
help='specify the SMTP username for sending email')
group.add_option('--smtp-password', dest='smtp_password', default=False,
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("-d", "--database", dest="db_name", default=False,
help="specify the database name")
group.add_option("-r", "--db_user", dest="db_user", default=False,
help="specify the database user name")
group.add_option("-w", "--db_password", dest="db_password", default=False,
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',
group.add_option("--db_host", dest="db_host", default=False,
help="specify the database host")
group.add_option("--db_port", dest="db_port", default=False,
help="specify the database port", type="int")
group.add_option("--db_maxconn", dest="db_maxconn", type='int', default=64,
help="specify the the maximum number of physical connections to posgresql")
parser.add_option_group(group)
@ -211,32 +191,35 @@ class configmanager(object):
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",
default=None,
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.")
help="overwrites existing translation terms on updating a module or 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.",
help="specify additional addons paths (separated by commas).",
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")
security.add_option('--no-database-list', action="store_false", dest='list_db', default=True,
help="disable the ability to return the list of databases")
parser.add_option_group(security)
# Advanced options
group = optparse.OptionGroup(parser, "Advanced options")
group.add_option("--cache-timeout", dest="cache_timeout",
group.add_option("--cache-timeout", dest="cache_timeout", default=100000,
help="set the timeout for the cache system", type="int")
group.add_option('--debug', dest='debug_mode', action='store_true', default=False, help='enable debug mode')
group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", default=False,
help="stop the server after it initializes")
group.add_option("-t", "--timezone", dest="timezone", help="specify reference timezone for the server (e.g. Europe/Brussels")
help="stop the server after its initialization")
group.add_option("-t", "--timezone", dest="timezone", default=False,
help="specify reference timezone for the server (e.g. Europe/Brussels")
group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", default=False,
help="Force a limit on the maximum number of records kept in the virtual "
"osv_memory tables. The default is False, which means no count-based limit.",
@ -248,6 +231,14 @@ class configmanager(object):
type="float")
parser.add_option_group(group)
# Copy all optparse options into self.options.
for group in parser.option_groups:
for option in group.option_list:
if option.default == ('NO', 'DEFAULT'):
self.options[option.dest] = None
else:
self.options[option.dest] = option.default
self.parse_config()
def parse_config(self, args=[]):
@ -266,8 +257,8 @@ class configmanager(object):
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.overwrite_existing_translations and not (opt.translate_in or opt.update),
"the i18n-overwrite option cannot be used without the i18n-import option or without the update option")
die(opt.translate_out and (not opt.db_name),
"the i18n-export option cannot be used without the database (-d) option")
@ -350,7 +341,18 @@ class configmanager(object):
self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all']
self.options['translate_modules'].sort()
# TODO checking the type of the parameters should be done for every
# parameters, not just the timezone.
# The call to get_server_timezone() sets the timezone; this should
# probably done here.
if self.options['timezone']:
# Prevent the timezone to be True. (The config file parsing changes
# the string 'True' to the boolean value True. It would be probably
# be better to remove that conversion.)
die(not isinstance(self.options['timezone'], basestring),
"Invalid timezone value in configuration or environment: %r.\n"
"Please fix this in your configuration." %(self.options['timezone']))
# If an explicit TZ was provided in the config, make sure it is known
try:
import pytz
@ -424,26 +426,28 @@ class configmanager(object):
import stat
os.chmod(filename, stat.S_IRUSR + stat.S_IWUSR)
def _is_addons_path(self, path):
for f in os.listdir(path):
modpath = os.path.join(path, f)
if os.path.isdir(modpath):
def hasfile(filename):
return os.path.isfile(os.path.join(modpath, filename))
if hasfile('__init__.py') and (hasfile('__openerp__.py') or hasfile('__terp__.py')):
return True
return False
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))
ad_paths = []
for path in value.split(','):
path = path.strip()
res = os.path.abspath(os.path.expanduser(path))
if not os.path.isdir(res):
raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, path))
if not self._is_addons_path(res):
raise optparse.OptionValueError("option %s: The addons-path %r does not seem to a be a valid Addons Directory!" % (opt, path))
ad_paths.append(res)
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)
setattr(parser.values, option.dest, ",".join(ad_paths))
def load(self):
p = ConfigParser.ConfigParser()

View File

@ -304,9 +304,10 @@ form: module.record_id""" % (xml_id,)
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)])
ir_values_obj = self.pool.get('ir.values')
ir_value_ids = ir_values_obj.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)
ir_values_obj.unlink(cr, self.uid, ir_value_ids)
self.pool.get('ir.model.data')._unlink(cr, self.uid, 'ir.values', ir_value_ids)
return True

View File

@ -69,76 +69,6 @@ _logger = logging.getLogger('tools')
# 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 openerp.addons as 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)
@ -159,7 +89,7 @@ def exec_pg_command(name, *args):
if not prog:
raise Exception('Couldn\'t find %s' % name)
args2 = (prog,) + args
return subprocess.call(args2)
def exec_pg_command_pipe(name, *args):
@ -204,8 +134,8 @@ def file_open(name, mode="r", subdir='addons', pathinfo=False):
@return: fileobject if pathinfo is False else (fileobject, filepath)
"""
import openerp.addons as addons
adps = addons.ad_paths
import openerp.modules as addons
adps = addons.module.ad_paths
rtp = os.path.normcase(os.path.abspath(config['root_path']))
if name.replace(os.path.sep, '/').startswith('addons/'):
@ -717,7 +647,28 @@ def is_hashable(h):
except TypeError:
return False
class cache(object):
class dummy_cache(object):
""" Cache decorator replacement to actually do no caching.
This can be useful to benchmark and/or track memory leak.
"""
def __init__(self, timeout=None, skiparg=2, multi=None, size=8192):
pass
def clear(self, dbname, *args, **kwargs):
pass
@classmethod
def clean_caches_for_db(cls, dbname):
pass
def __call__(self, fn):
fn.clear_cache = self.clear
return fn
class real_cache(object):
"""
Use it as a decorator of the function you plan to cache
Timeout: 0 = no timeout, otherwise in seconds
@ -842,6 +793,9 @@ class cache(object):
cached_result.clear_cache = self.clear
return cached_result
# TODO make it an option
cache = real_cache
def to_xml(s):
return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')

View File

@ -27,6 +27,7 @@ import itertools
import locale
import os
import openerp.pooler as pooler
import openerp.sql_db as sql_db
import re
import logging
import tarfile
@ -160,7 +161,7 @@ class GettextAlias(object):
# 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)
return sql_db.db_connect(db_name)
def _get_cr(self, frame):
is_new_cr = False

View File

@ -114,7 +114,7 @@ class RecordDictWrapper(dict):
records do not strictly behave like dict, so we force them to.
"""
def __init__(self, record):
self.record = record
self.record = record
def __getitem__(self, key):
if key in self.record:
return self.record[key]
@ -174,10 +174,16 @@ class YamlInterpreter(object):
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
try:
_, id = self.pool.get('ir.model.data').get_object_reference(self.cr, self.uid, module, checked_xml_id)
self.id_map[xml_id] = id
except ValueError, e:
raise ValueError("""%s not found when processing %s.
This Yaml file appears to depend on missing data. This often happens for
tests that belong to a module's test suite and depend on each other.""" % (checked_xml_id, self.filename))
return id
def get_context(self, node, eval_dict):
context = self.context.copy()
if node.context:
@ -186,7 +192,7 @@ class YamlInterpreter(object):
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]
@ -195,7 +201,7 @@ class YamlInterpreter(object):
else:
value = default
return value
def process_comment(self, node):
return node
@ -419,7 +425,7 @@ class YamlInterpreter(object):
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':
@ -438,7 +444,7 @@ class YamlInterpreter(object):
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:
@ -450,7 +456,7 @@ class YamlInterpreter(object):
import openerp.netsvc as netsvc
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):
@ -476,7 +482,7 @@ class YamlInterpreter(object):
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':
@ -489,7 +495,7 @@ class YamlInterpreter(object):
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(',')
@ -570,7 +576,7 @@ class YamlInterpreter(object):
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)
@ -640,7 +646,7 @@ class YamlInterpreter(object):
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)
@ -657,7 +663,7 @@ class YamlInterpreter(object):
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
@ -708,13 +714,13 @@ class YamlInterpreter(object):
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):
"""
@ -732,7 +738,7 @@ class YamlInterpreter(object):
except Exception, e:
self.logger.exception(e)
raise
def _process_node(self, node):
if is_comment(node):
self.process_comment(node)
@ -770,7 +776,7 @@ class YamlInterpreter(object):
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
@ -786,7 +792,7 @@ class YamlInterpreter(object):
is_preceded_by_comment = False
return is_preceded_by_comment
def yaml_import(cr, module, yamlfile, idref=None, mode='init', noupdate=False, report=None):
def yaml_import(cr, module, yamlfile, idref=None, mode='init', noupdate=False):
if idref is None:
idref = {}
yaml_string = yamlfile.read()

View File

@ -25,7 +25,6 @@ from openerp.tools.misc import UpdateableStr, UpdateableDict
from openerp.tools.translate import translate
from lxml import etree
import openerp.ir as ir
import openerp.pooler as pooler
from openerp.osv.osv import except_osv
@ -90,7 +89,8 @@ class interface(netsvc.Service):
arch = arch.string
# fetch user-set defaut values for the field... shouldn't we pass it the uid?
defaults = ir.ir_get(cr, uid, 'default', False, [('wizard.'+self.wiz_name, False)])
ir_values_obj = pooler.get_pool(cr.dbname).get('ir.values')
defaults = ir_values_obj.get(cr, uid, 'default', False, [('wizard.'+self.wiz_name, False)])
default_values = dict([(x[1], x[2]) for x in defaults])
for val in fields.keys():
if 'default' in fields[val]:

View File

@ -27,6 +27,7 @@ import optparse
import sys
import glob
# TODO use the same function provided in openerp.modules
def load_information_from_description_file(module):
"""
:param module: The name of the module (sale, purchase, ...)