Merged with current trunk

bzr revid: jean-baptiste.aubort@camptocamp.com-20080723150218-vcnmpetwo1hpp9v5
This commit is contained in:
Aubort Jean-Baptiste 2008-07-23 17:02:18 +02:00
commit 2f93559d6e
19 changed files with 376 additions and 179 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -27,22 +27,26 @@
<field name="model">wizard.module.lang.export</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form col="4" string="Export language">
<form col="3" string="Export language">
<image name="gtk-dialog-info"/>
<group col="2" states="choose">
<group col="2" states="choose" fill="0" height="500">
<separator string="Export translation file" colspan="2"/>
<label align="0.0" string="Choose a language to export:"/>
<field name="lang" nolabel="1"/>
<button icon="gtk-cancel" name="act_cancel" special="cancel" string="Cancel" type="object"/>
<button icon="gtk-ok" name="act_getfile" string="Get file" type="object"/>
<field name="lang" width="300"/>
<field name="format"/>
<field name="modules" width="500" height="200"/>
<field name="state" invisible="1"/>
<newline/><label/>
</group>
<group col="1" states="get">
<separator string="Export done" colspan="1"/>
<field name="data" readonly="1"/>
<label string="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."/>
<button icon="gtk-close" name="act_destroy" special="cancel" string="Close" type="object"/>
<group col="1" states="get" fill="0">
<separator string="Export done"/>
<field name="data" readonly="1" nolabel="1"/>
<field name="advice" nolabel="1" height="80"/>
</group>
<group col="2" colspan="3" fill="0">
<button states="choose" icon="gtk-cancel" name="act_cancel" special="cancel" string="Cancel" type="object"/>
<button states="choose" icon="gtk-ok" name="act_getfile" string="Get file" type="object"/>
<button states="get" icon="gtk-close" name="act_destroy" special="cancel" string="Close" type="object"/>
</group>
<field name="state" readonly="1"/>
</form>
</field>
</record>

View File

@ -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', }

View File

@ -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 {}

View File

@ -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):

View File

@ -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)

View File

@ -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:

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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'),

View File

@ -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
#----------------------------------------------------------

View File

@ -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

View File

@ -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/