diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py
index f1d74009ff5..cefd6859e6c 100644
--- a/bin/addons/__init__.py
+++ b/bin/addons/__init__.py
@@ -45,8 +45,8 @@ logger = netsvc.Logger()
opj = os.path.join
-_ad = opj(tools.config['root_path'], 'addons') # default addons path (base)
-ad = tools.config['addons_path'] # alternate addons path
+_ad = os.path.abspath(opj(tools.config['root_path'], 'addons')) # default addons path (base)
+ad = os.path.abspath(tools.config['addons_path']) # alternate addons path
sys.path.insert(1, _ad)
if ad != _ad:
@@ -131,10 +131,37 @@ class Node(Singleton):
s += '%s`-> %s' % (' ' * depth, c._pprint(depth+1))
return s
-def _get_module_path(module):
- if os.path.exists(opj(ad, module)):
+def get_module_path(module):
+ """Return the path of the given module.
+ """
+
+ if os.path.exists(opj(ad, module)) or os.path.exists(opj(ad, '%s.zip' % module)):
return opj(ad, module)
- return opj(_ad, module)
+
+ if os.path.exists(opj(_ad, module)) or os.path.exists(opj(_ad, '%s.zip' % module)):
+ return opj(_ad, module)
+
+ raise IOError, 'Module not found : %s' % module
+
+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
+ """
+ return opj(get_module_path(module), *args)
+
+def get_modules():
+ """Returns the list of module names
+ """
+
+ module_list = os.listdir(ad)
+ module_names = [os.path.basename(m) for m in module_list]
+ module_list += [m for m in os.listdir(_ad) if m not in module_names]
+
+ return module_list
def create_graph(module_list, force=None):
if not force:
@@ -145,9 +172,9 @@ def create_graph(module_list, force=None):
for module in module_list:
if module[-4:]=='.zip':
module = module[:-4]
- mod_path = _get_module_path(module)
- terp_file = opj(mod_path, '__terp__.py')
- if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
+ mod_path = get_module_path(module)
+ terp_file = get_module_resource(module, '__terp__.py')
+ if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path):
try:
info = eval(tools.file_open(terp_file).read())
except:
@@ -254,24 +281,20 @@ def load_module_graph(cr, graph, status=None, **kwargs):
cr.commit()
def register_classes():
- module_list = os.listdir(ad)
- module_names = [os.path.basename(m) for m in module_list]
- module_list += [m for m in os.listdir(_ad) if m not in module_names]
-
+ module_list = get_modules()
for package in create_graph(module_list):
m = package.name
logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:registering classes' % m)
sys.stdout.flush()
- mod_path = _get_module_path(m)
- if not os.path.isfile(mod_path + '.zip'):
+ mod_path = get_module_path(m)
+ if not os.path.isfile(mod_path+'.zip'):
# XXX must restrict to only addons paths
imp.load_module(m, *imp.find_module(m))
else:
import zipimport
- mod_path = mod_path + '.zip'
try:
- zimp = zipimport.zipimporter(mod_path)
+ zimp = zipimport.zipimporter(mod_path+'.zip')
zimp.load_module(m)
except zipimport.ZipImportError:
logger.notifyChannel('init', netsvc.LOG_ERROR, 'Couldn\'t find module %s' % m)
diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py
index a4a7ae71d1c..50168aac610 100644
--- a/bin/addons/base/ir/ir_actions.py
+++ b/bin/addons/base/ir/ir_actions.py
@@ -30,6 +30,8 @@
from osv import fields,osv
import tools
import time
+from tools.config import config
+import netsvc
class actions(osv.osv):
_name = 'ir.actions.actions'
@@ -306,6 +308,7 @@ class actions_server(osv.osv):
# False : Finnished correctly
# ACTION_ID : Action to launch
def run(self, cr, uid, ids, context={}):
+ logger = netsvc.Logger()
for action in self.browse(cr, uid, ids, context):
if action.state=='python':
localdict = {
@@ -316,11 +319,17 @@ class actions_server(osv.osv):
'cr': cr,
'uid': uid
}
- print action.code
exec action.code in localdict
- print localdict.keys()
if 'action' in localdict:
return localdict['action']
+ if action.state == 'email':
+ user = "%s@yahoo.co.in," % (config['smtp_user'])
+ subject = action.name
+ body = action.message
+ if tools.email_send_attach(user, action.address, subject, body) == True:
+ logger.notifyChannel('email', netsvc.LOG_INFO, 'Email successfully send to : %s' % (user))
+ else:
+ logger.notifyChannel('email', netsvc.LOG_ERROR, 'Failed to send email to : %s' % (user))
return False
actions_server()
diff --git a/bin/addons/base/ir/ir_rule.py b/bin/addons/base/ir/ir_rule.py
index 093c32981ab..6f76133341d 100644
--- a/bin/addons/base/ir/ir_rule.py
+++ b/bin/addons/base/ir/ir_rule.py
@@ -205,7 +205,7 @@ class ir_rule(osv.osv):
query_global, val_global = _query(clause_global, 'OR')
if query_global:
if query:
- query = '('+query+') AND '+query_global
+ query = '('+query+') OR '+query_global
val.extend(val_global)
else:
query = query_global
diff --git a/bin/addons/base/ir/ir_translation.py b/bin/addons/base/ir/ir_translation.py
index 211f9dbaf45..ac87f08d76a 100644
--- a/bin/addons/base/ir/ir_translation.py
+++ b/bin/addons/base/ir/ir_translation.py
@@ -43,6 +43,7 @@ TRANSLATION_TYPE = [
('xsl', 'XSL'),
('help', 'Help'),
('code', 'Code'),
+ ('constraint', 'Constraint'),
]
class ir_translation(osv.osv, Cacheable):
@@ -138,12 +139,10 @@ class ir_translation(osv.osv, Cacheable):
'and name=%s',
(lang, tt, str(name)))
res = cr.fetchone()
- if res:
- self.add((lang, tt, name, source), res[0])
- return res[0]
- else:
- self.add((lang, tt, name, source), False)
- return False
+
+ trad = res and res[0] or ''
+ self.add((lang, tt, name, source), trad)
+ return trad
def unlink(self, cursor, user, ids, context=None):
self.clear()
diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py
index 0ebb5d9ab79..1ad9a844ea0 100644
--- a/bin/addons/base/module/module.py
+++ b/bin/addons/base/module/module.py
@@ -38,9 +38,7 @@ import release
import zipimport
import wizard
-
-
-
+import addons
ver_regexp = re.compile("^(\\d+)((\\.\\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\\d*)*)(-r(\\d+))?$")
suffix_regexp = re.compile("^(alpha|beta|rc|pre|p)(\\d*)$")
@@ -199,7 +197,7 @@ class module(osv.osv):
def get_module_info(self, name):
try:
- f = tools.file_open(os.path.join(tools.config['addons_path'], name, '__terp__.py'))
+ f = tools.file_open(os.path.join(name, '__terp__.py'))
data = f.read()
info = eval(data)
if 'version' in info:
@@ -343,18 +341,17 @@ class module(osv.osv):
filepath = files[lang]
# if filepath does not contain :// we prepend the path of the module
if filepath.find('://') == -1:
- filepath = os.path.join(tools.config['addons_path'], module['name'], filepath)
+ filepath = addons.get_module_resource(module['name'], filepath)
tools.trans_load(filepath, lang)
return True
# update the list of available packages
def update_list(self, cr, uid, context={}):
robj = self.pool.get('ir.module.repository')
- adp = tools.config['addons_path']
res = [0, 0] # [update, add]
# iterate through installed modules and mark them as being so
- for name in os.listdir(adp):
+ for name in addons.get_modules():
mod_name = name
if name[-4:]=='.zip':
mod_name=name[:-4]
@@ -384,20 +381,19 @@ class module(osv.osv):
self._update_category(cr, uid, ids[0], terp.get('category',
'Uncategorized'))
continue
- terp_file = os.path.join(adp, name, '__terp__.py')
- mod_path = os.path.join(adp, name)
+ terp_file = addons.get_module_resource(name, '__terp__.py')
+ mod_path = addons.get_module_path(name)
if os.path.isdir(mod_path) or os.path.islink(mod_path) or zipfile.is_zipfile(mod_path):
terp = self.get_module_info(mod_name)
if not terp or not terp.get('installable', True):
continue
- if not os.path.isfile(os.path.join(adp, mod_name+'.zip')):
+ if not os.path.isfile(mod_path+'.zip'):
import imp
# XXX must restrict to only addons paths
imp.load_module(name, *imp.find_module(mod_name))
else:
import zipimport
- mod_path = os.path.join(adp, mod_name+'.zip')
- zimp = zipimport.zipimporter(mod_path)
+ zimp = zipimport.zipimporter(mod_path+'.zip')
zimp.load_module(mod_name)
id = self.create(cr, uid, {
'name': mod_name,
@@ -472,7 +468,6 @@ class module(osv.osv):
def download(self, cr, uid, ids, download=True, context=None):
res = []
- adp = tools.config['addons_path']
for mod in self.browse(cr, uid, ids, context=context):
if not mod.url:
continue
@@ -486,7 +481,7 @@ class module(osv.osv):
if not download:
continue
zipfile = urllib.urlopen(mod.url).read()
- fname = os.path.join(adp, mod.name+'.zip')
+ fname = addons.get_module_path(mod.name+'.zip')
try:
fp = file(fname, 'wb')
fp.write(zipfile)
diff --git a/bin/addons/base/module/module_wizard.xml b/bin/addons/base/module/module_wizard.xml
index 2c700b1e616..e6a445e3a86 100644
--- a/bin/addons/base/module/module_wizard.xml
+++ b/bin/addons/base/module/module_wizard.xml
@@ -27,22 +27,26 @@
wizard.module.lang.export
form
-
diff --git a/bin/addons/base/module/wizard/wizard_export_lang.py b/bin/addons/base/module/wizard/wizard_export_lang.py
index ed7edbb0c53..3f7fa8f99df 100644
--- a/bin/addons/base/module/wizard/wizard_export_lang.py
+++ b/bin/addons/base/module/wizard/wizard_export_lang.py
@@ -122,36 +122,31 @@ class wizard_export_lang(osv.osv_memory):
return {'type':'ir.actions.act_window_close' }
def act_getfile(self, cr, uid, ids, context=None):
- print "get filE"
this = self.browse(cr, uid, ids)[0]
- print this.lang
- # set the data
- file=tools.trans_generate(this.lang, 'all', dbname=cr.dbname)
+ mods = map(lambda m: m.name, this.modules)
+ mods.sort()
buf=StringIO.StringIO()
- writer=csv.writer(buf, 'UNIX')
- for row in file:
- writer.writerow(row)
- del file
+
+ tools.trans_export(this.lang, mods, buf, this.format, dbname=cr.dbname)
+
+ if this.format == 'csv':
+ this.advice = _("Save this document to a .CSV file and open it with your favourite spreadsheet software. The file encoding is UTF-8. You have to translate the latest column before reimporting it.")
+ elif this.format == 'po':
+ this.advice = _("Save this document to a .po file and edit it with a specific software or a text editor. The file encoding is UTF-8.")
+
out=base64.encodestring(buf.getvalue())
buf.close()
-
- self.write(cr, uid, ids, {'state':'get','data':out}, context=context)
-
- return {
- 'view_type': 'form',
- "view_mode": 'form',
- 'res_model': self._name,
- 'type': 'ir.actions.act_window',
- 'target':'new',
- }
+ return self.write(cr, uid, ids, {'state':'get', 'data':out, 'advice':this.advice}, context=context)
_name = "wizard.module.lang.export"
_columns = {
'lang': fields.selection(_get_languages, 'Language',required=True),
+ 'format': fields.selection( ( ('csv','CSV File'), ('po','PO File') ), 'File Format', required=True),
+ 'modules': fields.many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id', 'Modules', domain=[('state','=','installed')]),
'data': fields.binary('File', readonly=True),
+ 'advice': fields.text('', readonly=True),
'state': fields.selection( ( ('choose','choose'), # choose language
('get','get'), # get the file
- #('end','end'), # virtual state: unlink self
) ),
}
_defaults = { 'state': lambda *a: 'choose', }
diff --git a/bin/addons/base/module/wizard/wizard_import_lang.py b/bin/addons/base/module/wizard/wizard_import_lang.py
index a6516bb24df..9134e64433a 100644
--- a/bin/addons/base/module/wizard/wizard_import_lang.py
+++ b/bin/addons/base/module/wizard/wizard_import_lang.py
@@ -57,10 +57,16 @@ class wizard_import_lang(wizard.interface):
def _import_lang(self, cr, uid, data, context):
form=data['form']
- #buf=base64.decodestring(form['data']).split('\n')
fileobj = TemporaryFile('w+')
fileobj.write( base64.decodestring(form['data']) )
- tools.trans_load_data(cr.dbname, fileobj, form['code'], lang_name=form['name'])
+
+ # now we determine the file format
+ fileobj.seek(0)
+ first_line = fileobj.readline().strip()
+ fileformat = first_line.endswith("type,name,res_id,src,value") and 'csv' or 'po'
+ fileobj.seek(0)
+
+ tools.trans_load_data(cr.dbname, fileobj, fileformat, form['code'], lang_name=form['name'])
fileobj.close()
return {}
diff --git a/bin/osv/fields.py b/bin/osv/fields.py
index 32544bcb9c3..8f6ee185ad8 100644
--- a/bin/osv/fields.py
+++ b/bin/osv/fields.py
@@ -333,7 +333,6 @@ class one2many(_column):
for r in obj.pool.get(self._obj).read(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
if r[self._fields_id] in res:
res[r[self._fields_id]].append( r['id'] )
- print 'Ok', res
return res
def set_memory(self, cr, obj, id, field, values, user=None, context=None):
diff --git a/bin/osv/orm.py b/bin/osv/orm.py
index 0bf3fb22cab..3fdaddfa92c 100644
--- a/bin/osv/orm.py
+++ b/bin/osv/orm.py
@@ -625,14 +625,17 @@ class orm_template(object):
def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
raise _('The read method is not implemented on this object !')
- def _validate(self, cr, uid, ids):
+ def _validate(self, cr, uid, ids, context=None):
+ context = context or {}
+ lng = context.get('lang', False) or 'en_US'
+ trans = self.pool.get('ir.translation')
field_error = []
field_err_str = []
- for field in self._constraints:
- if not field[0](self, cr, uid, ids):
- if len(field)>1:
- field_error+=field[2]
- field_err_str.append(field[1])
+ for constraint in self._constraints:
+ fun, msg, fields = constraint
+ if not fun(self, cr, uid, ids):
+ field_error += fields
+ field_err_str.append( trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg )
if len(field_err_str):
cr.rollback()
raise except_orm('ValidateError', ('\n'.join(field_err_str), ','.join(field_error)))
@@ -1141,7 +1144,7 @@ class orm_memory(orm_template):
self.datas[id_new]['internal.date_access'] = time.time()
for field in upd_todo:
self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
- self._validate(cr, user, [id_new])
+ self._validate(cr, user, [id_new], context)
wf_service = netsvc.LocalService("workflow")
wf_service.trg_write(user, self._name, id_new, cr)
self.clear()
@@ -1167,7 +1170,7 @@ class orm_memory(orm_template):
self.datas[id_new]['internal.date_access'] = time.time()
for field in upd_todo:
self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
- self._validate(cr, user, [id_new])
+ self._validate(cr, user, [id_new], context)
wf_service = netsvc.LocalService("workflow")
wf_service.trg_create(user, self._name, id_new, cr)
self.clear()
@@ -1839,10 +1842,6 @@ class orm(orm_template):
_('You try to bypass an access rule (Document type: %s).') % \
self._description)
- cr.execute('delete from inherit ' \
- 'where (obj_type=%s and obj_id in ('+str_d+')) ' \
- 'or (inst_type=%s and inst_id in ('+str_d+'))',
- (self._name,)+tuple(sub_ids)+(self._name,)+tuple(sub_ids))
if d1:
cr.execute('delete from "'+self._table+'" ' \
'where id in ('+str_d+')'+d1, sub_ids+d2)
@@ -2010,7 +2009,7 @@ class orm(orm_template):
v[val]=vals[val]
self.pool.get(table).write(cr, user, nids, v, context)
- self._validate(cr, user, ids)
+ self._validate(cr, user, ids, context)
if context.has_key('read_delta'):
del context['read_delta']
@@ -2086,7 +2085,6 @@ class orm(orm_template):
upd0 += ','+self._inherits[table]
upd1 += ',%d'
upd2.append(id)
- cr.execute('insert into inherit (obj_type,obj_id,inst_type,inst_id) values (%s,%d,%s,%d)', (table,id,self._name,id_new))
for field in vals:
if self._columns[field]._classic_write:
@@ -2122,7 +2120,7 @@ class orm(orm_template):
for field in upd_todo:
self._columns[field].set(cr, self, id_new, field, vals[field], user, context)
- self._validate(cr, user, [id_new])
+ self._validate(cr, user, [id_new], context)
wf_service = netsvc.LocalService("workflow")
wf_service.trg_create(user, self._name, id_new, cr)
diff --git a/bin/osv/osv.py b/bin/osv/osv.py
index 49099aa7142..3b948aec5d3 100644
--- a/bin/osv/osv.py
+++ b/bin/osv/osv.py
@@ -68,33 +68,12 @@ class osv_pool(netsvc.Service):
self.exportMethod(self.execute_cr)
def execute_cr(self, cr, uid, obj, method, *args, **kw):
- #
- # TODO: check security level
- #
try:
object = pooler.get_pool(cr.dbname).get(obj)
if not object:
self.abortResponse(1, 'Object Error', 'warning',
'Object %s doesn\'t exist' % str(obj))
- if (not method in getattr(object,'_protected')) and len(args) \
- and args[0] and len(object._inherits):
- types = {obj: args[0]}
- cr.execute('select inst_type,inst_id,obj_id \
- from inherit \
- where obj_type=%s \
- and obj_id in ('+','.join(map(str,args[0]))+')', (obj,))
- for ty,id,id2 in cr.fetchall():
- if not ty in types:
- types[ty]=[]
- types[ty].append(id)
- types[obj].remove(id2)
- for t,ids in types.items():
- if len(ids):
- object_t = pooler.get_pool(cr.dbname).get(t)
- res = getattr(object_t,method)(cr, uid, ids, *args[1:], **kw)
- else:
- res = getattr(object,method)(cr, uid, *args, **kw)
- return res
+ return getattr(object,method)(cr, uid, *args, **kw)
except orm.except_orm, inst:
self.abortResponse(1, inst.name, 'warning', inst.value)
except except_osv, inst:
diff --git a/bin/report/interface.py b/bin/report/interface.py
index 5a5333cf462..63757ad6151 100644
--- a/bin/report/interface.py
+++ b/bin/report/interface.py
@@ -37,6 +37,7 @@ import netsvc
import pooler
import tools
+import addons
import print_xml
import render
import urllib
@@ -121,7 +122,7 @@ class report_rml(report_int):
pos_xml = i.end()
doc = print_xml.document(cr, uid, {}, {})
- tmpl_path = os.path.join(tools.config['root_path'], 'addons/custom/corporate_defaults.xml')
+ tmpl_path = addons.get_module_resource('custom', 'corporate_defaults.xml')
doc.parse(tmpl_path, [uid], 'res.users', context)
corporate_header = doc.xml_get()
doc.close()
@@ -146,16 +147,14 @@ class report_rml(report_int):
return xml
# load XSL (parse it to the XML level)
- styledoc = libxml2.parseDoc(tools.file_open(
- os.path.join(tools.config['root_path'], self.xsl)).read())
- xsl_path, tail = os.path.split(os.path.join(tools.config['root_path'],
- self.xsl))
+ styledoc = libxml2.parseDoc(tools.file_open(self.xsl).read())
+ xsl_path, tail = os.path.split(self.xsl)
for child in styledoc.children:
if child.name == 'import':
if child.hasProp('href'):
- file = child.prop('href')
- child.setProp('href', urllib.quote(str(
- os.path.normpath(os.path.join(xsl_path, file)))))
+ imp_file = child.prop('href')
+ _x, imp_file = tools.file_open(imp_file, subdir=xsl_path, pathinfo=True)
+ child.setProp('href', urllib.quote(str(imp_file)))
#TODO: get all the translation in one query. That means we have to:
# * build a list of items to translate,
diff --git a/bin/report/print_xml.py b/bin/report/print_xml.py
index 2370f5344db..75c700e3927 100644
--- a/bin/report/print_xml.py
+++ b/bin/report/print_xml.py
@@ -353,9 +353,7 @@ class document(object):
if not context:
context={}
# parses the xml template to memory
- self.dom = minidom.parseString(tools.file_open(
- os.path.join(tools.config['root_path'],
- filename)).read())
+ self.dom = minidom.parseString(tools.file_open(filename).read())
# create the xml data from the xml template
self.parse_tree(ids, model, context)
diff --git a/bin/tinyerp-server.py b/bin/tinyerp-server.py
index 8bdeec75bc6..c022188ff72 100755
--- a/bin/tinyerp-server.py
+++ b/bin/tinyerp-server.py
@@ -184,11 +184,12 @@ if tools.config["translate_out"]:
import csv
logger.notifyChannel("init", netsvc.LOG_INFO, 'writing translation file for language %s to %s' % (tools.config["language"], tools.config["translate_out"]))
- trans=tools.trans_generate(tools.config["language"], tools.config["translate_modules"])
- writer=csv.writer(file(tools.config["translate_out"], "w"), 'UNIX')
- for row in trans:
- writer.writerow(row)
- del trans
+
+ fileformat = os.path.splitext(tools.config["translate_out"])[-1][1:].lower()
+ buf = file(tools.config["translate_out"], "w")
+ tools.trans_export(tools.config["language"], tools.config["translate_modules"], buf, fileformat)
+ buf.close()
+
logger.notifyChannel("init", netsvc.LOG_INFO, 'translation file written succesfully')
sys.exit(0)
diff --git a/bin/tools/config.py b/bin/tools/config.py
index 1948d725b36..72f0f98091f 100644
--- a/bin/tools/config.py
+++ b/bin/tools/config.py
@@ -115,11 +115,11 @@ class configmanager(object):
group = optparse.OptionGroup(parser, "Internationalisation options",
"Use these options to translate Tiny ERP to another language."
- "See i18n section of the user manual. Option '-l' is mandatory.")
+ "See i18n section of the user manual. Options '-l' and '-d' are mandatory.")
group.add_option('-l', "--language", dest="language", help="specify the language of the translation file. Use it with --i18n-export and --i18n-import")
- group.add_option("--i18n-export", dest="translate_out", help="export all sentences to be translated to a CSV file and exit")
- group.add_option("--i18n-import", dest="translate_in", help="import a CSV file with translations and exit")
+ group.add_option("--i18n-export", dest="translate_out", help="export all sentences to be translated to a CSV or a PO file and exit")
+ group.add_option("--i18n-import", dest="translate_in", help="import a CSV or a PO file with translations and exit")
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.")
parser.add_option_group(group)
@@ -180,7 +180,9 @@ class configmanager(object):
update[i] = 1
self.options['update'] = update
- self.options['translate_modules'] = opt.translate_modules and opt.translate_modules.split(',') or ['all']
+ self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all']
+ self.options['translate_modules'].sort()
+
if opt.pg_path:
self.options['pg_path'] = opt.pg_path
diff --git a/bin/tools/graph.py b/bin/tools/graph.py
index c14a3e2488b..c271726b79c 100644
--- a/bin/tools/graph.py
+++ b/bin/tools/graph.py
@@ -290,11 +290,14 @@ class graph(object):
return self.result
if __name__=='__main__':
- starting_node = ['mrp'] # put here nodes with flow_start=True
- nodes = ['project','account','hr','base','product','mrp','test']
+ starting_node = ['profile'] # put here nodes with flow_start=True
+ nodes = ['project','account','hr','base','product','mrp','test','profile']
transitions = [
+ ('profile','mrp'),
('mrp','project'),
('project','product'),
+ ('mrp','hr'),
+ ('mrp','test'),
('project','account'),
('project','hr'),
('product','base'),
diff --git a/bin/tools/misc.py b/bin/tools/misc.py
index b8f9b61763f..f534d0e84da 100644
--- a/bin/tools/misc.py
+++ b/bin/tools/misc.py
@@ -50,25 +50,23 @@ else:
# initialize a database with base/base.sql
def init_db(cr):
- f = os.path.join(config['addons_path'], 'base/base.sql')
+ import addons
+ f = addons.get_module_resource('base', 'base.sql')
for line in file(f).read().split(';'):
if (len(line)>0) and (not line.isspace()):
cr.execute(line)
cr.commit()
- opj = os.path.join
- ad = config['addons_path']
-
- for i in os.listdir(ad):
- terp_file = opj(ad, i, '__terp__.py')
- mod_path = opj(ad, i)
+ for i in addons.get_modules():
+ terp_file = addons.get_module_resource(i, '__terp__.py')
+ mod_path = addons.get_module_path(i)
info = False
- if os.path.isfile(terp_file) and not os.path.isfile(opj(ad, i+'.zip')):
+ if os.path.isfile(terp_file) and not os.path.isfile(mod_path+'.zip'):
info = eval(file(terp_file).read())
- elif zipfile.is_zipfile(mod_path):
- zfile = zipfile.ZipFile(mod_path)
+ elif zipfile.is_zipfile(mod_path+'.zip'):
+ zfile = zipfile.ZipFile(mod_path+'.zip')
i = os.path.splitext(i)[0]
- info = eval(zfile.read(opj(i, '__terp__.py')))
+ info = eval(zfile.read(os.path.join(i, '__terp__.py')))
if info:
categs = info.get('category', 'Uncategorized').split('/')
p_id = None
@@ -170,12 +168,55 @@ def exec_command_pipe(name, *args):
#file_path_root = os.getcwd()
#file_path_addons = os.path.join(file_path_root, 'addons')
-def file_open(name, mode="r", subdir='addons'):
- """Open a file from the Tiny ERP root, using a subdir folder."""
+def file_open(name, mode="r", subdir='addons', pathinfo=False):
+ """Open a file from the Tiny ERP root, using a subdir folder.
+
+ >>> file_open('hr/report/timesheer.xsl')
+ >>> file_open('addons/hr/report/timesheet.xsl')
+ >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
+
+ @param name: name of the file
+ @param mode: file open mode
+ @param subdir: subdirectory
+ @param pathinfo: if True returns tupple (fileobject, filepath)
+
+ @return: fileobject if pathinfo is False else (fileobject, filepath)
+ """
+
+ adp = os.path.normcase(os.path.abspath(config['addons_path']))
+ rtp = os.path.normcase(os.path.abspath(config['root_path']))
+
+ if name.replace(os.path.sep, '/').startswith('addons/'):
+ subdir = 'addons'
+ name = name[7:]
+
+ # First try to locate in addons_path
if subdir:
- name = os.path.join(config['root_path'], subdir, name)
+ subdir2 = subdir
+ if subdir2.replace(os.path.sep, '/').startswith('addons/'):
+ subdir2 = subdir2[7:]
+
+ subdir2 = (subdir2 != 'addons' or None) and subdir2
+
+ try:
+ if subdir2:
+ fn = os.path.join(adp, subdir2, name)
+ else:
+ fn = os.path.join(adp, name)
+ fn = os.path.normpath(fn)
+ fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
+ if pathinfo:
+ return fo, fn
+ return fo
+ except IOError, e:
+ pass
+
+ if subdir:
+ name = os.path.join(rtp, subdir, name)
else:
- name = os.path.join(config['root_path'], name)
+ name = os.path.join(rtp, name)
+
+ name = os.path.normpath(name)
# Check for a zipfile in the path
head = name
@@ -193,15 +234,22 @@ def file_open(name, mode="r", subdir='addons'):
import StringIO
zfile = zipfile.ZipFile(head+'.zip')
try:
- return StringIO.StringIO(zfile.read(os.path.join(
+ fo = StringIO.StringIO(zfile.read(os.path.join(
os.path.basename(head), zipname).replace(
os.sep, '/')))
+
+ if pathinfo:
+ return fo, name
+ return fo
except:
name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
pass
for i in (name2, name):
if i and os.path.isfile(i):
- return file(i, mode)
+ fo = file(i, mode)
+ if pathinfo:
+ return fo, i
+ return fo
raise IOError, 'File not found : '+str(name)
@@ -287,7 +335,7 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
#----------------------------------------------------------
# Emails
#----------------------------------------------------------
-def email_send_attach(email_from, email_to, subject, body, email_cc=None, email_bcc=None, on_error=False, reply_to=False, attach=None, tinycrm=False):
+def email_send_attach(email_from, email_to, subject, body, email_cc=None, email_bcc=None, on_error=False, reply_to=False, attach=None, tinycrm=False, ssl=False, debug=False):
"""Send an email."""
if not email_cc:
email_cc=[]
@@ -327,6 +375,15 @@ def email_send_attach(email_from, email_to, subject, body, email_cc=None, email_
msg.attach(part)
try:
s = smtplib.SMTP()
+
+ if debug:
+ s.debuglevel = 5
+
+ if ssl:
+ s.ehlo()
+ s.starttls()
+ s.ehlo()
+
s.connect(config['smtp_server'])
if config['smtp_user'] or config['smtp_password']:
s.login(config['smtp_user'], config['smtp_password'])
@@ -335,6 +392,8 @@ def email_send_attach(email_from, email_to, subject, body, email_cc=None, email_
except Exception, e:
import logging
logging.getLogger().error(str(e))
+ return False
+
return True
#----------------------------------------------------------
diff --git a/bin/tools/translate.py b/bin/tools/translate.py
index e38c0f203b1..3c0530c3aa9 100644
--- a/bin/tools/translate.py
+++ b/bin/tools/translate.py
@@ -35,6 +35,7 @@ import ir
import netsvc
from tools.misc import UpdateableStr
import inspect
+import mx.DateTime as mxdt
class UNIX_LINE_TERMINATOR(csv.excel):
lineterminator = '\n'
@@ -67,8 +68,124 @@ class GettextAlias(object):
_ = GettextAlias()
+
+# class to handle po files
+class TinyPoFile(object):
+ def __init__(self, buffer):
+ self.buffer = buffer
+
+ def __iter__(self):
+ self.buffer.seek(0)
+ self.lines = self.buffer.readlines()
+ self.first = True
+ return self
+
+ def next(self):
+ def unquote(str):
+ return str[1:-1].replace("\\n", "\n") \
+ .replace('\\"', "\"")
+
+ type = name = res_id = source = trad = None
+
+ line = None
+ while not line:
+ if 0 == len(self.lines):
+ raise StopIteration()
+ line = self.lines.pop(0).strip()
+
+ while line.startswith('#'):
+ if line.startswith('#:'):
+ type, name, res_id = line[2:].strip().split(':')
+ line = self.lines.pop(0).strip()
+ if not line.startswith('msgid'):
+ raise Exception("malformed file")
+ source = line[7:-1]
+ line = self.lines.pop(0).strip()
+ if not source and self.first:
+ # if the source is "" and it's the first msgid, it's the special
+ # msgstr with the informations about the traduction and the
+ # traductor; we skip it
+ while line:
+ line = self.lines.pop(0).strip()
+ return next()
+
+ while not line.startswith('msgstr'):
+ if not line:
+ raise Exception('malformed file')
+ source += unquote(line)
+ line = self.lines.pop(0).strip()
+
+ trad = line[8:-1]
+ line = self.lines.pop(0).strip()
+ while line:
+ trad += unquote(line)
+ line = self.lines.pop(0).strip()
+
+ self.first = False
+ return type, name, res_id, source, trad
+
+ def write_infos(self, modules):
+ import release
+ self.buffer.write("# Translation of %(project)s.\n" \
+ "# This file containt the translation of the following modules:\n" \
+ "%(modules)s" \
+ "#\n" \
+ "msgid \"\"\n" \
+ "msgstr \"\"\n" \
+ "\"Project-Id-Version: %(project)s %(version)s\"\n" \
+ "\"Report-Msgid-Bugs-To: %(bugmail)s\"\n" \
+ "\"POT-Creation-Date: %(now)s\"\n" \
+ "\"PO-Revision-Date: %(now)s\"\n" \
+ "\"Last-Translator: <>\"\n" \
+ "\"Language-Team: \"\n" \
+ "\"MIME-Version: 1.0\"\n" \
+ "\"Content-Type: text/plain; charset=UTF-8\"\n" \
+ "\"Content-Transfer-Encoding: \"\n" \
+ "\"Plural-Forms: \"\n" \
+ "\n"
+
+ % { 'project': release.description,
+ 'version': release.version,
+ 'modules': reduce(lambda s, m: s + "#\t* %s\n" % m, modules, ""),
+ 'bugmail': 'support@tinyerp.com', #TODO: use variable from release
+ 'now': mxdt.ISO.strUTC(mxdt.ISO.DateTime.utc()),
+ }
+ )
+
+ def write(self, module, type, name, res_id, source, trad):
+ def quote(str):
+ return '"%s"' % str.replace('"','\\"') \
+ .replace('\n', '\\n"\n"')
+
+ self.buffer.write("#. module: %s\n" \
+ "#, python-format\n" \
+ "#: %s:%s:%s\n" \
+ "msgid %s\n" \
+ "msgstr %s\n\n" \
+ % (module, type, name, str(res_id), quote(source), quote(trad))
+ )
+
+
+
# Methods to export the translation file
+def trans_export(lang, modules, buffer, format, dbname=None):
+ trans = trans_generate(lang, modules, dbname)
+ if format == 'csv':
+ writer=csv.writer(buffer, 'UNIX')
+ for row in trans:
+ writer.writerow(row)
+ elif format == 'po':
+ trans.pop(0)
+ writer = tools.TinyPoFile(buffer)
+ writer.write_infos(modules)
+ for module, type, name, res_id, src, trad in trans:
+ writer.write(module, type, name, res_id, src, trad)
+ else:
+ raise Exception(_('Bad file format'))
+ del trans
+
+
def trans_parse_xsl(de):
res = []
for n in [i for i in de.childNodes if (i.nodeType == i.ELEMENT_NODE)]:
@@ -123,6 +240,9 @@ def trans_generate(lang, modules, dbname=None):
logger = netsvc.Logger()
if not dbname:
dbname=tools.config['db_name']
+ if not modules:
+ modules = ['all']
+
pool = pooler.get_pool(dbname)
trans_obj = pool.get('ir.translation')
model_data_obj = pool.get('ir.model.data')
@@ -239,11 +359,15 @@ def trans_generate(lang, modules, dbname=None):
if fname:
logger.notifyChannel("init", netsvc.LOG_WARNING, "couldn't export translation for report %s %s %s" % (name, report_type, fname))
-
+ for constraint in pool.get(model)._constraints:
+ msg = constraint[1]
+ push_translation(module, 'constraint', model, 0, msg.encode('utf8'))
+
for field_name,field_def in pool.get(model)._columns.items():
if field_def.translate:
name = model + "," + field_name
- push_translation(module, 'model', name, xml_name, getattr(obj, field_name))
+ trad = getattr(obj, field_name) or ''
+ push_translation(module, 'model', name, xml_name, trad)
# parse source code for _() calls
def get_module_from_path(path):
@@ -251,14 +375,16 @@ def trans_generate(lang, modules, dbname=None):
if path.startswith(relative_addons_path):
path = path[len(relative_addons_path)+1:]
return path.split(os.path.sep)[0]
- return None
-
- def parse_py_files(path):
- for root, dirs, files in os.walk(path):
- for fname in fnmatch.filter(files, '*.py'):
- fabsolutepath = join(root, fname)
- frelativepath = fabsolutepath[len(tools.config['root_path'])+1:]
- module = get_module_from_path(frelativepath)
+ return 'base' # files that are not in a module are considered as being in 'base' module
+
+ modobj = pool.get('ir.module.module')
+ for root, dirs, files in os.walk(tools.config['root_path']):
+ for fname in fnmatch.filter(files, '*.py'):
+ fabsolutepath = join(root, fname)
+ frelativepath = fabsolutepath[len(tools.config['root_path'])+1:]
+ module = get_module_from_path(frelativepath)
+ is_mod_installed = modobj.search(cr, uid, [('state', '=', 'installed'), ('name', '=', module)]) <> []
+ if (('all' in modules) or (module in modules)) and is_mod_installed:
code_string = tools.file_open(fabsolutepath, subdir='').read()
iter = re.finditer(
'[^a-zA-Z0-9_]_\([\s]*["\'](.+?)["\'][\s]*\)',
@@ -267,13 +393,6 @@ def trans_generate(lang, modules, dbname=None):
push_translation(module, 'code', frelativepath, 0, i.group(1).encode('utf8'))
- if 'all' in modules:
- parse_py_files(tools.config['root_path'])
- else:
- for m in modules:
- parse_py_files(join(tools.config['addons_path'], m))
-
-
out = [["module","type","name","res_id","src","value"]] # header
# translate strings marked as to be translated
for module, type, name, id, source in _to_translate:
@@ -287,13 +406,15 @@ def trans_load(db_name, filename, lang, strict=False):
logger = netsvc.Logger()
try:
fileobj = open(filename,'r')
+ fileformat = os.path.splitext(filename)[-1][1:].lower()
+ r = trans_load_data(db_name, fileobj, fileformat, lang, strict=False)
+ fileobj.close()
+ return r
except IOError:
logger.notifyChannel("init", netsvc.LOG_ERROR, "couldn't read file")
- r = trans_load_data(db_name, fileobj, lang, strict=False)
- fileobj.close()
- return r
+ return None
-def trans_load_data(db_name, fileobj, lang, strict=False, lang_name=None):
+def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name=None):
logger = netsvc.Logger()
logger.notifyChannel("init", netsvc.LOG_INFO,
'loading translation file for language %s' % (lang))
@@ -324,11 +445,18 @@ def trans_load_data(db_name, fileobj, lang, strict=False, lang_name=None):
ls = map(lambda x: (x['code'],x['name']), langs)
fileobj.seek(0)
- reader = csv.reader(fileobj, quotechar='"', delimiter=',')
- # read the first line of the file (it contains columns titles)
- for row in reader:
- f = row
- break
+
+ if fileformat == 'csv':
+ reader = csv.reader(fileobj, quotechar='"', delimiter=',')
+ # read the first line of the file (it contains columns titles)
+ for row in reader:
+ f = row
+ break
+ elif fileformat == 'po':
+ reader = TinyPoFile(fileobj)
+ f = ['type', 'name', 'res_id', 'src', 'value']
+ else:
+ raise Exception(_('Bad file format'))
# read the rest of the file
line = 1
@@ -336,8 +464,8 @@ def trans_load_data(db_name, fileobj, lang, strict=False, lang_name=None):
line += 1
#try:
# skip empty rows and rows where the translation field (=last fiefd) is empty
- if (not row) or (not row[:-1]):
- print "translate: skip %s" % repr(row)
+ if (not row) or (not row[-1]):
+ #print "translate: skip %s" % repr(row)
continue
# dictionary which holds values for this line of the csv file
diff --git a/setup.py b/setup.py
index 607fb28c5c5..2c8923f4f3b 100755
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
#!/usr/bin/env python
##############################################################################
#
@@ -26,7 +27,6 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
###############################################################################
-# -*- coding: utf-8 -*-
# setup from TinERP
# taken from straw http://www.nongnu.org/straw/index.html
# taken from gnomolicious http://www.nongnu.org/gnomolicious/