[MERGE] merge with main server

bzr revid: rpr@tinyerp.com-20121008061045-lyu9nb756rvis02x
This commit is contained in:
Rajesh Prajapati (OpenERP)
2012-10-08 11:40:45 +05:30
24 changed files with 522 additions and 571 deletions
+1
View File
@@ -44,6 +44,7 @@ The kernel of OpenERP, needed for all installation.
'data/res.country.state.csv',
'ir/wizard/wizard_menu_view.xml',
'ir/ir.xml',
'ir/ir_translation_view.xml',
'ir/ir_filters.xml',
'ir/ir_config_parameter_view.xml',
'ir/workflow/workflow_view.xml',
-69
View File
@@ -1147,75 +1147,6 @@
<menuitem action="action_model_relation" id="ir_model_relation_menu" parent="base.next_id_9"
groups="base.group_no_one"/>
<!-- Translations -->
<record id="view_translation_search" model="ir.ui.view">
<field name="name">Translations</field>
<field name="model">ir.translation</field>
<field name="arch" type="xml">
<search string="Translations">
<filter icon="terp-gdu-smart-failing"
string="Untranslated"
domain="['|',('value', '=', False),('value','=','')]"/>
<field name="name" operator="="/>
<field name="lang"/>
<field name="src"/>
<field name="value"/>
</search>
</field>
</record>
<record id="view_translation_form" model="ir.ui.view">
<field name="name">Translations</field>
<field name="model">ir.translation</field>
<field name="arch" type="xml">
<form string="Translations" version="7.0">
<header>
<field name="state" widget="statusbar" nolabel="1"/>
</header>
<sheet>
<group>
<group>
<field name="name"/>
<field name="lang"/>
</group>
<group>
<field name="type"/>
<field name="res_id"/>
</group>
<group string="Source Term">
<field name="src" nolabel="1" height="400"/>
</group>
<group string="Translation">
<field name="value" nolabel="1" height="400"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_translation_tree" model="ir.ui.view">
<field name="name">Translations</field>
<field name="model">ir.translation</field>
<field name="arch" type="xml">
<tree string="Translations" editable="bottom">
<field name="src" readonly="True"/>
<field name="value"/>
<field name="name" readonly="True"/>
<field name="lang" readonly="True"/>
<field name="type" readonly="True"/>
</tree>
</field>
</record>
<record id="action_translation" model="ir.actions.act_window">
<field name="name">Translated Terms</field>
<field name="res_model">ir.translation</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_translation_tree"/>
</record>
<menuitem action="action_translation" id="menu_action_translation" parent="base.menu_translation_app" />
<!--
=============================================================
Menu Edition
+3 -1
View File
@@ -557,7 +557,9 @@ class ir_model_access(osv.osv):
model_name = model
# TransientModel records have no access rights, only an implicit access rule
if self.pool.get(model_name).is_transient():
if not self.pool.get(model_name):
_logger.error('Missing model %s' % (model_name, ))
elif self.pool.get(model_name).is_transient():
return True
# We check if a specific rule exists
+75 -44
View File
@@ -19,10 +19,12 @@
#
##############################################################################
from osv import fields, osv
import tools
import logging
import openerp.modules
from openerp.osv import fields, osv
_logger = logging.getLogger(__name__)
TRANSLATION_TYPE = [
@@ -57,7 +59,6 @@ class ir_translation_import_cursor(object):
the data.
@param parent an instance of ir.translation ORM model
"""
self._cr = cr
self._uid = uid
self._context = context
@@ -67,29 +68,23 @@ class ir_translation_import_cursor(object):
# Note that Postgres will NOT inherit the constraints or indexes
# of ir_translation, so this copy will be much faster.
cr.execute('''CREATE TEMP TABLE %s(
imd_model VARCHAR(64),
imd_module VARCHAR(64),
imd_name VARCHAR(128)
) INHERITS (%s) ''' % (self._table_name, self._parent_table))
def push(self, ddict):
def push(self, trans_dict):
"""Feed a translation, as a dictionary, into the cursor
"""
state = "translated" if (ddict['value'] and ddict['value'] != "") else "to_translate"
self._cr.execute("INSERT INTO " + self._table_name \
+ """(name, lang, res_id, src, type,
imd_model, imd_module, imd_name, value,state)
VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
(ddict['name'], ddict['lang'], ddict.get('res_id'), ddict['src'], ddict['type'],
ddict.get('imd_model'), ddict.get('imd_module'), ddict.get('imd_name'),
ddict['value'],state))
params = dict(trans_dict, state="translated" if trans_dict['value'] else "to_translate")
self._cr.execute("""INSERT INTO %s (name, lang, res_id, src, type, imd_model, module, imd_name, value, state, comments)
VALUES (%%(name)s, %%(lang)s, %%(res_id)s, %%(src)s, %%(type)s, %%(imd_model)s, %%(module)s,
%%(imd_name)s, %%(value)s, %%(state)s, %%(comments)s)""" % self._table_name,
params)
def finish(self):
""" Transfer the data from the temp table to ir.translation
"""
cr = self._cr
if self._debug:
cr.execute("SELECT count(*) FROM %s" % self._table_name)
@@ -101,22 +96,21 @@ class ir_translation_import_cursor(object):
SET res_id = imd.res_id
FROM ir_model_data AS imd
WHERE ti.res_id IS NULL
AND ti.imd_module IS NOT NULL AND ti.imd_name IS NOT NULL
AND ti.module IS NOT NULL AND ti.imd_name IS NOT NULL
AND ti.imd_module = imd.module AND ti.imd_name = imd.name
AND ti.module = imd.module AND ti.imd_name = imd.name
AND ti.imd_model = imd.model; """ % self._table_name)
if self._debug:
cr.execute("SELECT imd_module, imd_model, imd_name FROM %s " \
"WHERE res_id IS NULL AND imd_module IS NOT NULL" % self._table_name)
cr.execute("SELECT module, imd_model, imd_name FROM %s " \
"WHERE res_id IS NULL AND module IS NOT NULL" % self._table_name)
for row in cr.fetchall():
_logger.debug("ir.translation.cursor: missing res_id for %s. %s/%s ", *row)
cr.execute("DELETE FROM %s WHERE res_id IS NULL AND imd_module IS NOT NULL" % \
self._table_name)
# Records w/o res_id must _not_ be inserted into our db, because they are
# referencing non-existent data.
cr.execute("DELETE FROM %s WHERE res_id IS NULL AND module IS NOT NULL" % \
self._table_name)
find_expr = "irt.lang = ti.lang AND irt.type = ti.type " \
" AND irt.name = ti.name AND irt.src = ti.src " \
@@ -126,15 +120,14 @@ class ir_translation_import_cursor(object):
if self._overwrite:
cr.execute("""UPDATE ONLY %s AS irt
SET value = ti.value,
state = 'translated'
state = 'translated'
FROM %s AS ti
WHERE %s AND ti.value IS NOT NULL AND ti.value != ''
""" % (self._parent_table, self._table_name, find_expr))
# Step 3: insert new translations
cr.execute("""INSERT INTO %s(name, lang, res_id, src, type, value,state)
SELECT name, lang, res_id, src, type, value,state
cr.execute("""INSERT INTO %s(name, lang, res_id, src, type, value, module, state, comments)
SELECT name, lang, res_id, src, type, value, module, state, comments
FROM %s AS ti
WHERE NOT EXISTS(SELECT 1 FROM ONLY %s AS irt WHERE %s);
""" % (self._parent_table, self._table_name, self._parent_table, find_expr))
@@ -162,26 +155,37 @@ class ir_translation(osv.osv):
return [(d['code'], d['name']) for d in lang_data]
_columns = {
'name': fields.char('Field Name', size=128, required=True),
'res_id': fields.integer('Resource ID', select=True),
'lang': fields.selection(_get_language, string='Language', size=16),
'type': fields.selection(TRANSLATION_TYPE, string='Type', size=16, select=True),
'name': fields.char('Translated field', required=True),
'res_id': fields.integer('Record ID', select=True),
'lang': fields.selection(_get_language, string='Language'),
'type': fields.selection(TRANSLATION_TYPE, string='Type', select=True),
'src': fields.text('Source'),
'value': fields.text('Translation Value'),
'state':fields.selection([('to_translate','To Translate'),('inprogress','Translation in Progress'),('translated','Translated')])
'module': fields.char('Module', help="Module this term belongs to", select=True),
'state': fields.selection(
[('to_translate','To Translate'),
('inprogress','Translation in Progress'),
('translated','Translated')],
string="State",
help="Automatically set to let administators find new terms that might need to be translated"),
# aka gettext extracted-comments - we use them to flag openerp-web translation
# cfr: http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.html
'comments': fields.text('Translation comments', select=True),
}
_defaults = {
'state':'to_translate',
'state': 'to_translate',
}
_sql_constraints = [ ('lang_fkey_res_lang', 'FOREIGN KEY(lang) REFERENCES res_lang(code)',
_sql_constraints = [ ('lang_fkey_res_lang', 'FOREIGN KEY(lang) REFERENCES res_lang(code)',
'Language code of translation item must be among known languages' ), ]
def _auto_init(self, cr, context=None):
super(ir_translation, self)._auto_init(cr, context)
# FIXME: there is a size limit on btree indexed values so we can't index src column with normal btree.
# FIXME: there is a size limit on btree indexed values so we can't index src column with normal btree.
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_translation_ltns',))
if cr.fetchone():
#temporarily removed: cr.execute('CREATE INDEX ir_translation_ltns ON ir_translation (name, lang, type, src)')
@@ -207,7 +211,7 @@ class ir_translation(osv.osv):
if field == 'lang':
return
return super(ir_translation, self)._check_selection_field_value(cr, uid, field, value, context=context)
@tools.ormcache_multi(skiparg=3, multi=6)
def _get_ids(self, cr, uid, name, tt, lang, ids):
translations = dict.fromkeys(ids, False)
@@ -271,10 +275,10 @@ class ir_translation(osv.osv):
if isinstance(types, basestring):
types = (types,)
if source:
query = """SELECT value
FROM ir_translation
WHERE lang=%s
AND type in %s
query = """SELECT value
FROM ir_translation
WHERE lang=%s
AND type in %s
AND src=%s"""
params = (lang or '', types, tools.ustr(source))
if name:
@@ -308,9 +312,9 @@ class ir_translation(osv.osv):
if isinstance(ids, (int, long)):
ids = [ids]
if vals.get('src') or ('value' in vals and not(vals.get('value'))):
result = vals.update({'state':'to_translate'})
vals.update({'state':'to_translate'})
if vals.get('value'):
result = vals.update({'state':'translated'})
vals.update({'state':'translated'})
result = super(ir_translation, self).write(cursor, user, ids, vals, context=context)
for trans_obj in self.read(cursor, user, ids, ['name','type','res_id','src','lang'], context=context):
self._get_source.clear_cache(self, user, trans_obj['name'], trans_obj['type'], trans_obj['lang'], trans_obj['src'])
@@ -379,7 +383,34 @@ class ir_translation(osv.osv):
"""
return ir_translation_import_cursor(cr, uid, self, context=context)
ir_translation()
def load(self, cr, modules, langs, context=None):
context = dict(context or {}) # local copy
for module_name in modules:
modpath = openerp.modules.get_module_path(module_name)
if not modpath:
continue
for lang in langs:
lang_code = tools.get_iso_codes(lang)
base_lang_code = None
if '_' in lang_code:
base_lang_code = lang_code.split('_')[0]
# Step 1: for sub-languages, load base language first (e.g. es_CL.po is loaded over es.po)
if base_lang_code:
base_trans_file = openerp.modules.get_module_resource(module_name, 'i18n', base_lang_code + '.po')
if base_trans_file:
_logger.info('module %s: loading base translation file %s for language %s', module_name, base_lang_code, lang)
tools.trans_load(cr, base_trans_file, lang, verbose=False, module_name=module_name, context=context)
context['overwrite'] = True # make sure the requested translation will override the base terms later
# Step 2: then load the main translation file, possibly overriding the terms coming from the base language
trans_file = openerp.modules.get_module_resource(module_name, 'i18n', lang_code + '.po')
if trans_file:
_logger.info('module %s: loading translation file (%s) for language %s', module_name, lang_code, lang)
tools.trans_load(cr, trans_file, lang, verbose=False, module_name=module_name, context=context)
elif lang_code != 'en':
_logger.warning('module %s: no translation for language %s', module_name, lang_code)
return True
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,79 @@
<openerp>
<data>
<!-- Translations -->
<record id="view_translation_search" model="ir.ui.view">
<field name="name">Translations</field>
<field name="model">ir.translation</field>
<field name="arch" type="xml">
<search string="Translations">
<filter icon="terp-gdu-smart-failing"
string="Untranslated"
domain="['|',('value', '=', False),('value','=','')]"/>
<filter name="openerp-web"
string="Web-only translations"
domain="[('comments', 'like', 'openerp-web')]"/>
<field name="name" operator="="/>
<field name="lang"/>
<field name="src"/>
<field name="value"/>
</search>
</field>
</record>
<record id="view_translation_form" model="ir.ui.view">
<field name="name">Translations</field>
<field name="model">ir.translation</field>
<field name="arch" type="xml">
<form string="Translations" version="7.0">
<header>
<field name="state" widget="statusbar" nolabel="1"/>
</header>
<sheet>
<group>
<group>
<field name="name"/>
<field name="lang"/>
</group>
<group>
<field name="type"/>
<field name="res_id"/>
</group>
<group string="Source Term">
<field name="src" nolabel="1" height="400"/>
</group>
<group string="Translation">
<field name="value" nolabel="1" height="400"/>
</group>
<group string="Comments">
<field name="comments" nolabel="1" height="100"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_translation_tree" model="ir.ui.view">
<field name="name">Translations</field>
<field name="model">ir.translation</field>
<field name="arch" type="xml">
<tree string="Translations" editable="bottom">
<field name="src" readonly="True"/>
<field name="value"/>
<field name="name" readonly="True"/>
<field name="lang" readonly="True"/>
<field name="type" readonly="True"/>
</tree>
</field>
</record>
<record id="action_translation" model="ir.actions.act_window">
<field name="name">Translated Terms</field>
<field name="res_model">ir.translation</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_translation_tree"/>
</record>
<menuitem action="action_translation" id="menu_action_translation" parent="base.menu_translation_app" />
</data>
</openerp>
+1 -1
View File
@@ -265,7 +265,7 @@ class ir_ui_menu(osv.osv):
}
if menu.action and menu.action.type in ('ir.actions.act_window','ir.actions.client') and menu.action.res_model:
obj = self.pool.get(menu.action.res_model)
if obj._needaction:
if obj and obj._needaction:
if menu.action.type=='ir.actions.act_window':
dom = menu.action.domain and eval(menu.action.domain, {'uid': uid}) or []
else:
@@ -19,6 +19,5 @@
#
##############################################################################
import wizard_menu
import wizard_screen
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -47,25 +47,6 @@ class wizard_model_menu(osv.osv_memory):
'icon': 'STOCK_INDENT'
}, context)
return {'type':'ir.actions.act_window_close'}
wizard_model_menu()
class wizard_model_menu_line(osv.osv_memory):
_name = 'wizard.ir.model.menu.create.line'
_columns = {
'wizard_id': fields.many2one('wizard.ir.model.menu.create','Wizard'),
'sequence': fields.integer('Sequence'),
'view_type': fields.selection([
('tree','Tree'),
('form','Form'),
('graph','Graph'),
('calendar','Calendar'),
('gantt','Gantt')],'View Type',required=True),
'view_id': fields.many2one('ir.ui.view', 'View'),
}
_defaults = {
'view_type': lambda self,cr,uid,ctx: 'tree'
}
wizard_model_menu_line()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -1,54 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2010 OpenERP s.a. (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
import os
import random
import tools
from osv import fields,osv
# Simple base class for wizards that wish to use random images on the left
# side of the form.
class wizard_screen(osv.osv_memory):
_name = 'ir.wizard.screen'
def _get_image(self, cr, uid, context=None):
path = os.path.join('base','res','config_pixmaps','%d.png'%random.randrange(1,4))
image_file = file_data = tools.file_open(path,'rb')
try:
file_data = image_file.read()
return base64.encodestring(file_data)
finally:
image_file.close()
def _get_image_fn(self, cr, uid, ids, name, args, context=None):
image = self._get_image(cr, uid, context)
return dict.fromkeys(ids, image) # ok to use .fromkeys() as the image is same for all
_columns = {
'config_logo': fields.function(_get_image_fn, string='Image', type='binary'),
}
_defaults = {
'config_logo': _get_image
}
wizard_screen()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+5 -36
View File
@@ -627,45 +627,14 @@ class module(osv.osv):
self.write(cr, uid, [mod_browse.id], {'category_id': p_id})
def update_translations(self, cr, uid, ids, filter_lang=None, context=None):
if context is None:
context = {}
if not filter_lang:
pool = pooler.get_pool(cr.dbname)
lang_obj = pool.get('res.lang')
lang_ids = lang_obj.search(cr, uid, [('translatable', '=', True)])
filter_lang = [lang.code for lang in lang_obj.browse(cr, uid, lang_ids)]
res_lang = self.pool.get('res.lang')
lang_ids = res_lang.search(cr, uid, [('translatable', '=', True)])
filter_lang = [lang.code for lang in res_lang.browse(cr, uid, lang_ids)]
elif not isinstance(filter_lang, (list, tuple)):
filter_lang = [filter_lang]
for mod in self.browse(cr, uid, ids):
if mod.state != 'installed':
continue
modpath = modules.get_module_path(mod.name)
if not modpath:
# unable to find the module. we skip
continue
for lang in filter_lang:
iso_lang = tools.get_iso_codes(lang)
f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
context2 = context and context.copy() or {}
if f and '_' in iso_lang:
iso_lang2 = iso_lang.split('_')[0]
f2 = modules.get_module_resource(mod.name, 'i18n', iso_lang2 + '.po')
if f2:
_logger.info('module %s: loading base translation file %s for language %s', mod.name, iso_lang2, lang)
tools.trans_load(cr, f2, lang, verbose=False, context=context)
context2['overwrite'] = True
# Implementation notice: we must first search for the full name of
# the language derivative, like "en_UK", and then the generic,
# like "en".
if (not f) and '_' in iso_lang:
iso_lang = iso_lang.split('_')[0]
f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
if f:
_logger.info('module %s: loading translation file (%s) for language %s', mod.name, iso_lang, lang)
tools.trans_load(cr, f, lang, verbose=False, context=context2)
elif iso_lang != 'en':
_logger.warning('module %s: no translation for language %s', mod.name, iso_lang)
modules = [m.name for m in self.browse(cr, uid, ids) if m.state == 'installed']
self.pool.get('ir.translation').load(cr, modules, filter_lang, context=context)
def check(self, cr, uid, ids, context=None):
for mod in self.browse(cr, uid, ids, context=context):
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# OpenERP, Open Source Business Applications
# Copyright (c) 2004-2012 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
@@ -22,68 +22,58 @@
import tools
import base64
import cStringIO
import pooler
from osv import fields,osv
from tools.translate import _
from tools.misc import get_iso_codes
NEW_LANG_KEY = '__new__'
class base_language_export(osv.osv_memory):
_name = "base.language.export"
def _get_languages(self, cr, uid, context):
lang_obj=pooler.get_pool(cr.dbname).get('res.lang')
ids=lang_obj.search(cr, uid, ['&', ('active', '=', True), ('translatable', '=', True),])
langs=lang_obj.browse(cr, uid, ids)
return [(lang.code, lang.name) for lang in langs]
def act_cancel(self, cr, uid, ids, context=None):
#self.unlink(cr, uid, ids, context)
return {'type':'ir.actions.act_window_close' }
def act_destroy(self, *args):
return {'type':'ir.actions.act_window_close' }
lang_obj = self.pool.get('res.lang')
ids = lang_obj.search(cr, uid, [('translatable', '=', True)])
langs = lang_obj.browse(cr, uid, ids)
return [(NEW_LANG_KEY, _('New Language (Empty translation template)'))] + [(lang.code, lang.name) for lang in langs]
_columns = {
'name': fields.char('File Name', readonly=True),
'lang': fields.selection(_get_languages, 'Language', required=True),
'format': fields.selection([('csv','CSV File'),
('po','PO File'),
('tgz', 'TGZ Archive')], 'File Format', required=True),
'modules': fields.many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id', 'Modules To Export', domain=[('state','=','installed')]),
'data': fields.binary('File', readonly=True),
'state': fields.selection([('choose', 'choose'), # choose language
('get', 'get')]) # get the file
}
_defaults = {
'state': 'choose',
'name': 'lang.tar.gz',
'lang': NEW_LANG_KEY,
'format': 'csv',
}
def act_getfile(self, cr, uid, ids, context=None):
this = self.browse(cr, uid, ids)[0]
lang = this.lang if this.lang != NEW_LANG_KEY else False
mods = map(lambda m: m.name, this.modules) or ['all']
mods.sort()
buf=cStringIO.StringIO()
tools.trans_export(this.lang, mods, buf, this.format, cr)
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':
if not this.lang:
this.format = 'pot'
this.advice = _("Save this document to a %s file and edit it with a specific software or a text editor. The file encoding is UTF-8.") % ('.'+this.format,)
elif this.format == 'tgz':
ext = this.lang and '.po' or '.pot'
this.advice = _('Save this document to a .tgz file. This archive containt UTF-8 %s files and may be uploaded to launchpad.') % (ext,)
filename = _('new')
if not this.lang and len(mods) == 1:
buf = cStringIO.StringIO()
tools.trans_export(lang, mods, buf, this.format, cr)
filename = 'new'
if lang:
filename = get_iso_codes(lang)
elif len(mods) == 1:
filename = mods[0]
if this.lang:
filename = get_iso_codes(this.lang)
this.name = "%s.%s" % (filename, this.format)
out=base64.encodestring(buf.getvalue())
out = base64.encodestring(buf.getvalue())
buf.close()
return self.write(cr, uid, ids, {'state':'get', 'data':out, 'advice':this.advice, 'name':this.name}, context=context)
self.write(cr, uid, ids, {'state': 'get',
'data': out,
'name':this.name}, context=context)
return True
_name = "base.language.export"
_inherit = "ir.wizard.screen"
_columns = {
'name': fields.char('File Name', 16, readonly=True),
'lang': fields.selection(_get_languages, 'Language', help='To export a new language, do not select a language.'), # not required: unset = new language
'format': fields.selection( ( ('csv','CSV File'), ('po','PO File'), ('tgz', 'TGZ Archive')), '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('Advice', readonly=True),
'state': fields.selection( ( ('choose','choose'), # choose language
('get','get'), # get the file
) ),
}
_defaults = {
'state': lambda *a: 'choose',
'name': lambda *a: 'lang.tar.gz'
}
base_language_export()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -1,31 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="wizard_lang_export" model="ir.ui.view">
<field name="name">Export Translations</field>
<field name="model">base.language.export</field>
<field name="arch" type="xml">
<form string="Export Translations" version="7.0">
<group colspan="4" states="choose">
<separator colspan="4" string="Export Translation"/>
<field invisible="1" name="state"/>
<field name="name" invisible="1"/>
<group states="choose" string="Export Settings">
<field name="lang"/>
<field name="format" required="1"/>
<field name="modules" nolabel="1"/>
<field invisible="1" name="state"/>
</group>
<group colspan="4" states="get">
<separator string="Export done" colspan="4"/>
<field name="name" invisible="1" colspan="4"/>
<field name="data" nolabel="1" readonly="1" filename="name" colspan="4"/>
<field height="80" name="advice" nolabel="1" colspan="4"/>
<field name="format"/>
<field name="modules"/>
</group>
<div states="get">
<h2>Export Complete</h2>
<p>Here is the exported translation file: <field name="data" readonly="1" filename="name"/></p>
<p>This file was generated using the universal <strong>Unicode/UTF-8</strong> file encoding, please be sure to view and edit
using the same encoding.</p>
<p>The next step depends on the file format:
<ul>
<li>CSV format: you may edit it directly with your favorite spreadsheet software,
the rightmost column (value) contains the translations</li>
<li>PO(T) format: you should edit it with a PO editor such as
<a href="http://www.poedit.net/" target="_blank">POEdit</a>, or your preferred text editor</li>
<li>TGZ format: this is a compressed archive containing a PO file, directly suitable
for uploading to OpenERP's translation platform,
<a href="https://translations.launchpad.net/openobject-addons" target="_blank">Launchpad</a></li>
</ul>
</p>
<p>For more details about translating OpenERP in your language, please refer to the
<a href="http://doc.openerp.com/v6.1/contribute/07_improving_translations.html" target="_blank">documentation</a>.</p>
</div>
<footer states="choose">
<button name="act_getfile" string="_Export" type="object" class="oe_highlight"/> or
<button name="act_cancel" special="cancel" string="_Cancel" type="object" class="oe_link"/>
<button name="act_getfile" string="Export" type="object" class="oe_highlight"/> or
<button special="cancel" string="Cancel" type="object" class="oe_link"/>
</footer>
<footer states="get">
<button name="act_cancel" special="cancel" string="_Close" type="object"/>
<button special="cancel" string="Close" type="object"/>
</footer>
</form>
</field>
@@ -29,11 +29,9 @@ class base_language_import(osv.osv_memory):
_name = "base.language.import"
_description = "Language Import"
_inherit = "ir.wizard.screen"
_columns = {
'name': fields.char('Language Name',size=64 , required=True),
'code': fields.char('Code (eg:en__US)',size=5 , required=True),
'name': fields.char('Language Name', size=64 , required=True),
'code': fields.char('ISO Code', size=5, help="ISO Language and Country code, e.g. en_US", required=True),
'data': fields.binary('File', required=True),
'overwrite': fields.boolean('Overwrite Existing Terms',
help="If you enable this option, existing translations (including custom ones) "
@@ -41,31 +39,25 @@ class base_language_import(osv.osv_memory):
}
def import_lang(self, cr, uid, ids, context=None):
"""
Import Language
@param cr: the current row, from the database cursor.
@param uid: the current user’s ID for security checks.
@param ids: the ID or list of IDs
@param context: A standard dictionary
"""
if context is None:
context = {}
import_data = self.browse(cr, uid, ids)[0]
if import_data.overwrite:
this = self.browse(cr, uid, ids[0])
if this.overwrite:
context.update(overwrite=True)
fileobj = TemporaryFile('w+')
fileobj.write(base64.decodestring(import_data.data))
try:
fileobj.write(base64.decodestring(this.data))
# now we determine the file format
fileobj.seek(0)
first_line = fileobj.readline().strip().replace('"', '').replace(' ', '')
fileformat = first_line.endswith("type,name,res_id,src,value") and 'csv' or 'po'
fileobj.seek(0)
tools.trans_load_data(cr, fileobj, fileformat, this.code, lang_name=this.name, context=context)
finally:
fileobj.close()
return True
# now we determine the file format
fileobj.seek(0)
first_line = fileobj.readline().strip().replace('"', '').replace(' ', '')
fileformat = first_line.endswith("type,name,res_id,src,value") and 'csv' or 'po'
fileobj.seek(0)
tools.trans_load_data(cr, fileobj, fileformat, import_data.code, lang_name=import_data.name, context=context)
fileobj.close()
return {}
base_language_import()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -8,7 +8,7 @@
<field name="arch" type="xml">
<form string="Import Translation" version="7.0">
<group>
<field name="name"/>
<field name="name" placeholder="e.g. English"/>
<field name="code" string="Code" placeholder="e.g. en_US"/>
<field name="data"/>
<field name="overwrite"/>
@@ -27,9 +27,7 @@ class base_language_install(osv.osv_memory):
""" Install Language"""
_name = "base.language.install"
_inherit = "ir.wizard.screen"
_description = "Install Language"
_columns = {
'lang': fields.selection(tools.scan_languages(),'Language', required=True),
'overwrite': fields.boolean('Overwrite Existing Terms', help="If you check this box, your customized translations will be overwritten and replaced by the official ones."),
@@ -63,6 +61,5 @@ class base_language_install(osv.osv_memory):
'target': 'new',
'res_id': ids and ids[0] or False,
}
base_language_install()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -34,9 +34,7 @@ class base_module_import(osv.osv_memory):
""" Import Module """
_name = "base.module.import"
_inherit = "ir.wizard.screen"
_description = "Import Module"
_columns = {
'module_file': fields.binary('Module .ZIP file', required=True),
'state':fields.selection([('init','init'),('done','done')],
@@ -88,7 +86,5 @@ class base_module_import(osv.osv_memory):
'type': 'ir.actions.act_window',
}
base_module_import()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -25,7 +25,6 @@ class base_module_update(osv.osv_memory):
_name = "base.module.update"
_description = "Update Module"
_inherit = "ir.wizard.screen"
_columns = {
'update': fields.integer('Number of modules updated', readonly=True),
@@ -55,7 +54,4 @@ class base_module_update(osv.osv_memory):
}
return res
base_module_update()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -21,31 +21,28 @@
from osv import osv, fields
import tools
import pooler
import cStringIO
from tools.translate import _
class base_update_translations(osv.osv_memory):
def _get_languages(self, cr, uid, context):
lang_obj=pooler.get_pool(cr.dbname).get('res.lang')
ids=lang_obj.search(cr, uid, ['&', ('active', '=', True), ('translatable', '=', True),])
langs=lang_obj.browse(cr, uid, ids)
lang_obj = self.pool.get('res.lang')
ids = lang_obj.search(cr, uid, ['&', ('active', '=', True), ('translatable', '=', True),])
langs = lang_obj.browse(cr, uid, ids)
return [(lang.code, lang.name) for lang in langs]
def _get_lang_name(self, cr, uid, lang_code):
lang_obj=pooler.get_pool(cr.dbname).get('res.lang')
ids=lang_obj.search(cr, uid, [('code', '=', lang_code)])
lang_obj = self.pool.get('res.lang')
ids = lang_obj.search(cr, uid, [('code', '=', lang_code)])
if not ids:
raise osv.except_osv(_('Error!'), _('No language with code "%s" exists') % lang_code)
lang = lang_obj.browse(cr, uid, ids[0])
return lang.name
def act_cancel(self, cr, uid, ids, context=None):
return {'type': 'ir.actions.act_window_close'}
def act_update(self, cr, uid, ids, context=None):
this = self.browse(cr, uid, ids)[0]
lang_name = self._get_lang_name(cr, uid, this.lang)
buf=cStringIO.StringIO()
buf = cStringIO.StringIO()
tools.trans_export(this.lang, ['all'], buf, 'csv', cr)
tools.trans_load_data(cr, buf, 'csv', this.lang, lang_name=lang_name)
buf.close()
@@ -66,11 +63,8 @@ class base_update_translations(osv.osv_memory):
return res
_name = 'base.update.translations'
_inherit = "ir.wizard.screen"
_columns = {
'lang': fields.selection(_get_languages, 'Language', required=True),
}
base_update_translations()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -12,7 +12,7 @@
<footer>
<button name="act_update" string="Update" type="object" class="oe_highlight"/>
or
<button name="act_cancel" special="cancel" string="Cancel" type="object" class="oe_link"/>
<button special="cancel" string="Cancel" type="object" class="oe_link"/>
</footer>
</form>
</field>
-1
View File
@@ -37,7 +37,6 @@ class res_config_configurable(osv.osv_memory):
their view inherit from the related res_config_view_base view.
'''
_name = 'res.config'
_inherit = 'ir.wizard.screen'
def _next_action(self, cr, uid, context=None):
Todos = self.pool['ir.actions.todo']
+1 -1
View File
@@ -301,7 +301,7 @@ class res_partner(osv.osv, format_address):
def onchange_type(self, cr, uid, ids, is_company, context=None):
# get value as for an onchange on the image
value = tools.image_get_resized_images(self._get_default_image(cr, uid, is_company, context), return_big=True)
value = tools.image_get_resized_images(self._get_default_image(cr, uid, is_company, context), return_big=True, return_medium=False, return_small=False)
value['title'] = False
if is_company:
value['parent_id'] = False
+36 -34
View File
@@ -51,6 +51,7 @@ import pickle
import re
import simplejson
import time
import traceback
import types
from lxml import etree
@@ -371,8 +372,10 @@ class browse_record(object):
else:
return attr
else:
error_msg = "Field '%s' does not exist in object '%s'" % (name, self)
error_msg = "Field '%s' does not exist in object '%s'" % (name, self)
self.__logger.warning(error_msg)
if self.__logger.isEnabledFor(logging.DEBUG):
self.__logger.debug(''.join(traceback.format_stack()))
raise KeyError(error_msg)
# if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
@@ -709,7 +712,7 @@ class BaseModel(object):
def log(self, cr, uid, id, message, secondary=False, context=None):
return _logger.warning("log() is deprecated. Please use OpenChatter notification system instead of the res.log mechanism.")
def view_init(self, cr, uid, fields_list, context=None):
"""Override this method to do specific things when a view on the object is opened."""
pass
@@ -899,7 +902,7 @@ class BaseModel(object):
# If new class defines a constraint with
# same function name, we let it override
# the old one.
new[c2] = c
exist = True
break
@@ -1593,7 +1596,7 @@ class BaseModel(object):
def user_has_groups(self, cr, uid, groups, context=None):
"""Return true if the user is at least member of one of the groups
in groups_str. Typically used to resolve ``groups`` attribute
in view and model definitions.
in view and model definitions.
:param str groups: comma-separated list of fully-qualified group
external IDs, e.g.: ``base.group_user,base.group_system``
@@ -1636,7 +1639,7 @@ class BaseModel(object):
the field should be completely removed from the view, as it is
completely unavailable for non-members
:return: True if field should be included in the result of fields_view_get
:return: True if field should be included in the result of fields_view_get
"""
if node.tag == 'field' and node.get('name') in self._all_columns:
column = self._all_columns[node.get('name')].column
@@ -1911,7 +1914,7 @@ class BaseModel(object):
def _get_default_calendar_view(self, cr, user, context=None):
""" Generates a default calendar view by trying to infer
calendar fields from a number of pre-set attribute names
:param cr: database cursor
:param int user: user id
:param dict context: connection context
@@ -1990,7 +1993,7 @@ class BaseModel(object):
def raise_view_error(error_msg, child_view_id):
view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
error_msg = error_msg % {'parent_xml_id': view.xml_id}
error_msg = error_msg % {'parent_xml_id': view.xml_id}
raise AttributeError("View definition error for inherited view '%s' on model '%s': %s"
% (child_view.xml_id, self._name, error_msg))
@@ -2026,7 +2029,7 @@ class BaseModel(object):
if all(node.get(attr) == spec.get(attr) \
for attr in spec.attrib
if attr not in ('position','version')):
# Version spec should match parent's root element's version
# Version spec should match parent's root element's version
if spec.get('version') and spec.get('version') != source.get('version'):
return None
return node
@@ -2100,7 +2103,7 @@ class BaseModel(object):
raise_view_error("Mismatching view API version for element '%s': %r vs %r in parent view '%%(parent_xml_id)s'" % \
(tag, spec.get('version'), source.get('version')), inherit_id)
raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
return source
def apply_view_inheritance(cr, user, source, inherit_id):
@@ -2315,7 +2318,7 @@ class BaseModel(object):
or ``'='``.
:param int limit: optional max number of records to return
:rtype: list
:return: list of pairs ``(id,text_repr)`` for all matching records.
:return: list of pairs ``(id,text_repr)`` for all matching records.
"""
return self._name_search(cr, user, name, args, operator, context, limit)
@@ -2445,7 +2448,7 @@ class BaseModel(object):
# This is useful to implement kanban views for instance, where all columns
# should be displayed even if they don't contain any record.
# Grab the list of all groups that should be displayed, including all present groups
# Grab the list of all groups that should be displayed, including all present groups
present_group_ids = [x[groupby][0] for x in read_group_result if x[groupby]]
all_groups,folded = self._group_by_full[groupby](self, cr, uid, present_group_ids, domain,
read_group_order=read_group_order,
@@ -2821,28 +2824,28 @@ class BaseModel(object):
def _m2o_fix_foreign_key(self, cr, source_table, source_field, dest_model, ondelete):
# Find FK constraint(s) currently established for the m2o field,
# and see whether they are stale or not
# and see whether they are stale or not
cr.execute("""SELECT confdeltype as ondelete_rule, conname as constraint_name,
cl2.relname as foreign_table
FROM pg_constraint as con, pg_class as cl1, pg_class as cl2,
pg_attribute as att1, pg_attribute as att2
WHERE con.conrelid = cl1.oid
AND cl1.relname = %s
AND con.confrelid = cl2.oid
AND array_lower(con.conkey, 1) = 1
AND con.conkey[1] = att1.attnum
AND att1.attrelid = cl1.oid
AND att1.attname = %s
AND array_lower(con.confkey, 1) = 1
AND con.confkey[1] = att2.attnum
AND att2.attrelid = cl2.oid
AND att2.attname = %s
WHERE con.conrelid = cl1.oid
AND cl1.relname = %s
AND con.confrelid = cl2.oid
AND array_lower(con.conkey, 1) = 1
AND con.conkey[1] = att1.attnum
AND att1.attrelid = cl1.oid
AND att1.attname = %s
AND array_lower(con.confkey, 1) = 1
AND con.confkey[1] = att2.attnum
AND att2.attrelid = cl2.oid
AND att2.attname = %s
AND con.contype = 'f'""", (source_table, source_field, 'id'))
constraints = cr.dictfetchall()
if constraints:
if len(constraints) == 1:
# Is it the right constraint?
cons, = constraints
cons, = constraints
if cons['ondelete_rule'] != POSTGRES_CONFDELTYPES.get((ondelete or 'set null').upper(), 'a')\
or cons['foreign_table'] != dest_model._table:
_schema.debug("Table '%s': dropping obsolete FK constraint: '%s'",
@@ -2964,14 +2967,14 @@ class BaseModel(object):
('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
]
if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
if f_pg_type == 'varchar' and f._type == 'char' and ((f.size is None and f_pg_size) or f_pg_size < f.size):
cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, pg_varchar(f.size)))
cr.execute('UPDATE "%s" SET "%s"=temp_change_size::%s' % (self._table, k, pg_varchar(f.size)))
cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
cr.commit()
_schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
self._table, k, f_pg_size, f.size)
self._table, k, f_pg_size or 'unlimited', f.size or 'unlimited')
for c in casts:
if (f_pg_type==c[0]) and (f._type==c[1]):
if f_pg_type != f_obj_type:
@@ -3204,8 +3207,7 @@ class BaseModel(object):
# attlen is the number of bytes necessary to represent the type when
# the type has a fixed size. If the type has a varying size attlen is
# -1 and atttypmod is the size limit + 4, or -1 if there is no limit.
# Thus the query can return a negative size for a unlimited varchar.
cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size " \
cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN (CASE WHEN a.atttypmod=-1 THEN 0 ELSE a.atttypmod-4 END) ELSE a.attlen END as size " \
"FROM pg_class c,pg_attribute a,pg_type t " \
"WHERE c.relname=%s " \
"AND c.oid=a.attrelid " \
@@ -3215,7 +3217,7 @@ class BaseModel(object):
def _o2m_raise_on_missing_reference(self, cr, f):
# TODO this check should be a method on fields.one2many.
other = self.pool.get(f._obj)
if other:
# TODO the condition could use fields_get_keys().
@@ -3732,7 +3734,7 @@ class BaseModel(object):
the length of `ids`, and raise an appropriate exception if it does not.
"""
if cr.rowcount != len(ids):
# Attempt to distinguish record rule restriction vs deleted records,
# Attempt to distinguish record rule restriction vs deleted records,
# to provide a more specific error message
cr.execute('SELECT id FROM ' + self._table + ' WHERE id IN %s', (tuple(ids),))
if cr.rowcount != len(ids):
@@ -3787,13 +3789,13 @@ class BaseModel(object):
self._check_record_rules_result_count(cr, uid, sub_ids, operation, context=context)
def _workflow_trigger(self, cr, uid, ids, trigger, context=None):
"""Call given workflow trigger as a result of a CRUD operation"""
"""Call given workflow trigger as a result of a CRUD operation"""
wf_service = netsvc.LocalService("workflow")
for res_id in ids:
getattr(wf_service, trigger)(uid, self._name, res_id, cr)
def _workflow_signal(self, cr, uid, ids, signal, context=None):
"""Send given workflow signal"""
"""Send given workflow signal"""
wf_service = netsvc.LocalService("workflow")
for res_id in ids:
wf_service.trg_validate(uid, self._name, res_id, signal, cr)
@@ -3824,7 +3826,7 @@ class BaseModel(object):
self.check_access_rights(cr, uid, 'unlink')
ir_property = self.pool.get('ir.property')
# Check if the records are used as default properties.
domain = [('res_id', '=', False),
('value_reference', 'in', ['%s,%s' % (self._name, i) for i in ids]),
@@ -4952,7 +4954,7 @@ class BaseModel(object):
:return: map of ids to their fully qualified XML ID,
defaulting to an empty string when there's none
(to be usable as a function field),
(to be usable as a function field),
e.g.::
{ 'id': 'module.ext_id',
+152 -132
View File
@@ -33,6 +33,7 @@ import logging
import tarfile
import tempfile
import threading
from babel.messages import extract
from os.path import join
from datetime import datetime
@@ -47,6 +48,9 @@ from openerp import SUPERUSER_ID
_logger = logging.getLogger(__name__)
# used to notify web client that these translations should be loaded in the UI
WEB_TRANSLATION_COMMENT = "openerp-web"
_LOCALE2WIN32 = {
'af_ZA': 'Afrikaans_South Africa',
'sq_AL': 'Albanian_Albania',
@@ -262,7 +266,7 @@ class TinyPoFile(object):
self.lines_count = len(self.lines);
self.first = True
self.tnrs= []
self.extra_lines= []
return self
def _get_lines(self):
@@ -278,14 +282,14 @@ class TinyPoFile(object):
return (self.lines_count - len(self.lines))
def next(self):
type = name = res_id = source = trad = None
if self.tnrs:
type, name, res_id, source, trad = self.tnrs.pop(0)
trans_type = name = res_id = source = trad = None
if self.extra_lines:
trans_type, name, res_id, source, trad, comments = self.extra_lines.pop(0)
if not res_id:
res_id = '0'
else:
tmp_tnrs = []
comments = []
targets = []
line = None
fuzzy = False
while (not line):
@@ -295,15 +299,20 @@ class TinyPoFile(object):
while line.startswith('#'):
if line.startswith('#~ '):
break
if line.startswith('#:'):
if line.startswith('#.'):
line = line[2:].strip()
if not line.startswith('module:'):
comments.append(line)
elif line.startswith('#:'):
for lpart in line[2:].strip().split(' '):
trans_info = lpart.strip().split(':',2)
if trans_info and len(trans_info) == 2:
# looks like the translation type is missing, which is not
# looks like the translation trans_type is missing, which is not
# unexpected because it is not a GetText standard. Default: 'code'
trans_info[:0] = ['code']
if trans_info and len(trans_info) == 3:
tmp_tnrs.append(trans_info)
# this is a ref line holding the destination info (model, field, record)
targets.append(trans_info)
elif line.startswith('#,') and (line[2:].strip() == 'fuzzy'):
fuzzy = True
line = self.lines.pop(0).strip()
@@ -326,7 +335,7 @@ class TinyPoFile(object):
# if the source is "" and it's the first msgid, it's the special
# msgstr with the informations about the traduction and the
# traductor; we skip it
self.tnrs = []
self.extra_lines = []
while line:
line = self.lines.pop(0).strip()
return self.next()
@@ -343,10 +352,11 @@ class TinyPoFile(object):
trad += unquote(line)
line = self.lines.pop(0).strip()
if tmp_tnrs and not fuzzy:
type, name, res_id = tmp_tnrs.pop(0)
for t, n, r in tmp_tnrs:
self.tnrs.append((t, n, r, source, trad))
if targets and not fuzzy:
trans_type, name, res_id = targets.pop(0)
for t, n, r in targets:
if t == trans_type == 'code': continue
self.extra_lines.append((t, n, r, source, trad, comments))
self.first = False
@@ -355,7 +365,7 @@ class TinyPoFile(object):
self.warn('Missing "#:" formated comment at line %d for the following source:\n\t%s',
self.cur_line(), source[:30])
return self.next()
return type, name, res_id, source, trad
return trans_type, name, res_id, source, trad, '\n'.join(comments)
def write_infos(self, modules):
import openerp.release as release
@@ -384,11 +394,13 @@ class TinyPoFile(object):
}
)
def write(self, modules, tnrs, source, trad):
def write(self, modules, tnrs, source, trad, comments=None):
plurial = len(modules) > 1 and 's' or ''
self.buffer.write("#. module%s: %s\n" % (plurial, ', '.join(modules)))
if comments:
self.buffer.write(''.join(('#. %s\n' % c for c in comments)))
code = False
for typy, name, res_id in tnrs:
@@ -415,44 +427,43 @@ class TinyPoFile(object):
def trans_export(lang, modules, buffer, format, cr):
def _process(format, modules, rows, buffer, lang, newlang):
def _process(format, modules, rows, buffer, lang):
if format == 'csv':
writer=csv.writer(buffer, 'UNIX')
for row in rows:
writer.writerow(row)
writer = csv.writer(buffer, 'UNIX')
# write header first
writer.writerow(("module","type","name","res_id","src","value"))
for module, type, name, res_id, src, trad, comments in rows:
# Comments are ignored by the CSV writer
writer.writerow((module, type, name, res_id, src, trad))
elif format == 'po':
rows.pop(0)
writer = TinyPoFile(buffer)
writer.write_infos(modules)
# we now group the translations by source. That means one translation per source.
grouped_rows = {}
for module, type, name, res_id, src, trad in rows:
for module, type, name, res_id, src, trad, comments in rows:
row = grouped_rows.setdefault(src, {})
row.setdefault('modules', set()).add(module)
if ('translation' not in row) or (not row['translation']):
row['translation'] = trad
row.setdefault('tnrs', []).append((type, name, res_id))
row.setdefault('comments', set()).update(comments)
for src, row in grouped_rows.items():
writer.write(row['modules'], row['tnrs'], src, row['translation'])
writer.write(row['modules'], row['tnrs'], src, row['translation'], row['comments'])
elif format == 'tgz':
rows.pop(0)
rows_by_module = {}
for row in rows:
module = row[0]
# first row is the "header", as in csv, it will be popped
rows_by_module.setdefault(module, [['module', 'type', 'name', 'res_id', 'src', ''],])
rows_by_module[module].append(row)
rows_by_module.setdefault(module, []).append(row)
tmpdir = tempfile.mkdtemp()
for mod, modrows in rows_by_module.items():
tmpmoddir = join(tmpdir, mod, 'i18n')
os.makedirs(tmpmoddir)
pofilename = (newlang and mod or lang) + ".po" + (newlang and 't' or '')
pofilename = (lang if lang else mod) + ".po" + ('t' if not lang else '')
buf = file(join(tmpmoddir, pofilename), 'w')
_process('po', [mod], modrows, buf, lang, newlang)
_process('po', [mod], modrows, buf, lang)
buf.close()
tar = tarfile.open(fileobj=buffer, mode='w|gz')
@@ -463,16 +474,15 @@ def trans_export(lang, modules, buffer, format, cr):
raise Exception(_('Unrecognized extension: must be one of '
'.csv, .po, or .tgz (received .%s).' % format))
newlang = not bool(lang)
if newlang:
lang = 'en_US'
trans = trans_generate(lang, modules, cr)
if newlang and format!='csv':
for trx in trans:
trx[-1] = ''
modules = set([t[0] for t in trans[1:]])
_process(format, modules, trans, buffer, lang, newlang)
del trans
trans_lang = lang
if not trans_lang and format == 'csv':
# CSV files are meant for translators and they need a starting point,
# so we at least put the original term in the translation column
trans_lang = 'en_US'
translations = trans_generate(lang, modules, cr)
modules = set([t[0] for t in translations[1:]])
_process(format, modules, translations, buffer, lang)
del translations
def trans_parse_xsl(de):
res = []
@@ -535,6 +545,46 @@ def in_modules(object_name, modules):
module = module_dict.get(module, module)
return module in modules
def babel_extract_qweb(fileobj, keywords, comment_tags, options):
"""Babel message extractor for qweb template files.
:param fileobj: the file-like object the messages should be extracted from
:param keywords: a list of keywords (i.e. function names) that should
be recognized as translation functions
:param comment_tags: a list of translator tags to search for and
include in the results
:param options: a dictionary of additional options (optional)
:return: an iterator over ``(lineno, funcname, message, comments)``
tuples
:rtype: ``iterator``
"""
result = []
def handle_text(text, lineno):
text = (text or "").strip()
if len(text) > 1: # Avoid mono-char tokens like ':' ',' etc.
result.append((lineno, None, text, []))
# not using elementTree.iterparse because we need to skip sub-trees in case
# the ancestor element had a reason to be skipped
def iter_elements(current_element):
for el in current_element:
if isinstance(el, SKIPPED_ELEMENT_TYPES): continue
if "t-js" not in el.attrib and \
not ("t-jquery" in el.attrib and "t-operation" not in el.attrib) and \
not ("t-translation" in el.attrib and el.attrib["t-translation"].strip() == "off"):
handle_text(el.text, el.sourceline)
for att in ('title', 'alt', 'label', 'placeholder'):
if att in el.attrib:
handle_text(el.attrib[att], el.sourceline)
iter_elements(el)
handle_text(el.tail, el.sourceline)
tree = etree.parse(fileobj)
iter_elements(tree.getroot())
return result
def trans_generate(lang, modules, cr):
dbname = cr.dbname
@@ -566,8 +616,8 @@ def trans_generate(lang, modules, cr):
cr.execute(query, query_param)
_to_translate = []
def push_translation(module, type, name, id, source):
tuple = (module, source, name, id, type)
def push_translation(module, type, name, id, source, comments=None):
tuple = (module, source, name, id, type, comments or [])
if source and tuple not in _to_translate:
_to_translate.append(tuple)
@@ -717,7 +767,7 @@ def trans_generate(lang, modules, cr):
if not hasattr(msg, '__call__'):
push_translation(module, term_type, model, 0, encode(msg))
for (model_id, model, module) in cr.fetchall():
for (_, model, module) in cr.fetchall():
module = encode(module)
model = encode(model)
@@ -733,7 +783,6 @@ def trans_generate(lang, modules, cr):
for constraint in getattr(model_obj, '_sql_constraints', []):
push_constraint_msg(module, 'sql_constraint', model, constraint[2])
# parse source code for _() calls
def get_module_from_path(path, mod_paths=None):
if not mod_paths:
# First, construct a list of possible paths
@@ -771,92 +820,71 @@ def trans_generate(lang, modules, cr):
_logger.debug("Scanning modules at paths: ", path_list)
mod_paths = []
join_dquotes = re.compile(r'([^\\])"[\s\\]*"', re.DOTALL)
join_quotes = re.compile(r'([^\\])\'[\s\\]*\'', re.DOTALL)
re_dquotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*"(.+?)"[\s]*?\)', re.DOTALL)
re_quotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*\'(.+?)\'[\s]*?\)', re.DOTALL)
def export_code_terms_from_file(fname, path, root, terms_type):
def verified_module_filepaths(fname, path, root):
fabsolutepath = join(root, fname)
frelativepath = fabsolutepath[len(path):]
display_path = "addons%s" % frelativepath
module = get_module_from_path(fabsolutepath, mod_paths=mod_paths)
is_mod_installed = module in installed_modules
if (('all' in modules) or (module in modules)) and is_mod_installed:
_logger.debug("Scanning code of %s at module: %s", frelativepath, module)
src_file = misc.file_open(fabsolutepath, subdir='')
if (('all' in modules) or (module in modules)) and module in installed_modules:
return module, fabsolutepath, frelativepath, display_path
return None, None, None, None
def babel_extract_terms(fname, path, root, extract_method="python", trans_type='code',
extra_comments=None, extract_keywords={'_': None}):
module, fabsolutepath, _, display_path = verified_module_filepaths(fname, path, root)
extra_comments = extra_comments or []
if module:
src_file = open(fabsolutepath, 'r')
try:
code_string = src_file.read()
for lineno, message, comments in extract.extract(extract_method, src_file,
keywords=extract_keywords):
push_translation(module, trans_type, display_path, lineno,
encode(message), comments + extra_comments)
finally:
src_file.close()
if module in installed_modules:
frelativepath = str("addons" + frelativepath)
ite = re_dquotes.finditer(code_string)
code_offset = 0
code_line = 1
for i in ite:
src = i.group(1)
if src.startswith('""'):
assert src.endswith('""'), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30])
src = src[2:-2]
else:
src = join_dquotes.sub(r'\1', src)
# try to count the lines from the last pos to our place:
code_line += code_string[code_offset:i.start(1)].count('\n')
# now, since we did a binary read of a python source file, we
# have to expand pythonic escapes like the interpreter does.
src = src.decode('string_escape')
push_translation(module, terms_type, frelativepath, code_line, encode(src))
code_line += i.group(1).count('\n')
code_offset = i.end() # we have counted newlines up to the match end
ite = re_quotes.finditer(code_string)
code_offset = 0 #reset counters
code_line = 1
for i in ite:
src = i.group(1)
if src.startswith("''"):
assert src.endswith("''"), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30])
src = src[2:-2]
else:
src = join_quotes.sub(r'\1', src)
code_line += code_string[code_offset:i.start(1)].count('\n')
src = src.decode('string_escape')
push_translation(module, terms_type, frelativepath, code_line, encode(src))
code_line += i.group(1).count('\n')
code_offset = i.end() # we have counted newlines up to the match end
for path in path_list:
_logger.debug("Scanning files of modules at %s", path)
for root, dummy, files in osutil.walksymlinks(path):
for fname in itertools.chain(fnmatch.filter(files, '*.py')):
export_code_terms_from_file(fname, path, root, 'code')
for fname in itertools.chain(fnmatch.filter(files, '*.mako')):
export_code_terms_from_file(fname, path, root, 'report')
for fname in fnmatch.filter(files, '*.py'):
babel_extract_terms(fname, path, root)
for fname in fnmatch.filter(files, '*.mako'):
babel_extract_terms(fname, path, root, trans_type='report')
# Javascript source files in the static/src/js directory, rest is ignored (libs)
if fnmatch.fnmatch(root, '*/static/src/js*'):
for fname in fnmatch.filter(files, '*.js'):
babel_extract_terms(fname, path, root, 'javascript',
extra_comments=[WEB_TRANSLATION_COMMENT],
extract_keywords={'_t': None})
# QWeb template files
if fnmatch.fnmatch(root, '*/static/src/xml*'):
for fname in fnmatch.filter(files, '*.xml'):
babel_extract_terms(fname, path, root, 'openerp.tools.translate:babel_extract_qweb',
extra_comments=[WEB_TRANSLATION_COMMENT])
out = [["module","type","name","res_id","src","value"]] # header
out = []
_to_translate.sort()
# translate strings marked as to be translated
for module, source, name, id, type in _to_translate:
trans = trans_obj._get_source(cr, uid, name, type, lang, source)
out.append([module, type, name, id, source, encode(trans) or ''])
for module, source, name, id, type, comments in _to_translate:
trans = '' if not lang else trans_obj._get_source(cr, uid, name, type, lang, source)
out.append([module, type, name, id, source, encode(trans) or '', comments])
return out
def trans_load(cr, filename, lang, verbose=True, context=None):
def trans_load(cr, filename, lang, verbose=True, module_name=None, context=None):
try:
fileobj = misc.file_open(filename)
_logger.info("loading %s", filename)
fileformat = os.path.splitext(filename)[-1][1:].lower()
r = trans_load_data(cr, fileobj, fileformat, lang, verbose=verbose, context=context)
result = trans_load_data(cr, fileobj, fileformat, lang, verbose=verbose, module_name=module_name, context=context)
fileobj.close()
return r
return result
except IOError:
if verbose:
_logger.error("couldn't read translation file %s", filename)
return None
def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, context=None):
def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, module_name=None, context=None):
"""Populates the ir_translation table."""
if verbose:
_logger.info('loading translation file for language %s', lang)
@@ -868,8 +896,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
trans_obj = pool.get('ir.translation')
iso_lang = misc.get_iso_codes(lang)
try:
uid = 1
ids = lang_obj.search(cr, uid, [('code','=', lang)])
ids = lang_obj.search(cr, SUPERUSER_ID, [('code','=', lang)])
if not ids:
# lets create the language with locale information
@@ -886,14 +913,14 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
break
elif fileformat == 'po':
reader = TinyPoFile(fileobj)
f = ['type', 'name', 'res_id', 'src', 'value']
f = ['type', 'name', 'res_id', 'src', 'value', 'comments']
else:
_logger.error('Bad file format: %s', fileformat)
raise Exception(_('Bad file format'))
# read the rest of the file
line = 1
irt_cursor = trans_obj._get_import_cursor(cr, uid, context=context)
irt_cursor = trans_obj._get_import_cursor(cr, SUPERUSER_ID, context=context)
for row in reader:
line += 1
@@ -903,39 +930,32 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
# dictionary which holds values for this line of the csv file
# {'lang': ..., 'type': ..., 'name': ..., 'res_id': ...,
# 'src': ..., 'value': ...}
dic = {'lang': lang}
dic_module = False
for i in range(len(f)):
if f[i] in ('module',):
continue
dic[f[i]] = row[i]
# 'src': ..., 'value': ..., 'module':...}
dic = dict.fromkeys(('name', 'res_id', 'src', 'type', 'imd_model', 'imd_name', 'module', 'value', 'comments'))
dic['lang'] = lang
for i, field in enumerate(f):
dic[field] = row[i]
# This would skip terms that fail to specify a res_id
if not dic.get('res_id', False):
if not dic.get('res_id'):
continue
res_id = dic.pop('res_id')
if res_id and isinstance(res_id, (int, long)) \
or (isinstance(res_id, basestring) and res_id.isdigit()):
dic['res_id'] = int(res_id)
dic['module'] = module_name
else:
try:
tmodel = dic['name'].split(',')[0]
if '.' in res_id:
tmodule, tname = res_id.split('.', 1)
else:
tmodule = dic_module
tname = res_id
dic['imd_model'] = tmodel
dic['imd_module'] = tmodule
dic['imd_name'] = tname
dic['res_id'] = None
except Exception:
_logger.warning("Could not decode resource for %s, please fix the po file.",
dic['res_id'], exc_info=True)
dic['res_id'] = None
tmodel = dic['name'].split(',')[0]
if '.' in res_id:
tmodule, tname = res_id.split('.', 1)
else:
tmodule = False
tname = res_id
dic['imd_model'] = tmodel
dic['imd_name'] = tname
dic['module'] = tmodule
dic['res_id'] = None
irt_cursor.push(dic)
+78 -64
View File
@@ -313,13 +313,13 @@ class YamlInterpreter(object):
#context = self.get_context(record, self.eval_context)
#TOFIX: record.context like {'withoutemployee':True} should pass from self.eval_context. example: test_project.yml in project module
context = record.context
view_info = False
if view_id:
varg = view_id
if view_id is True: varg = False
view = model.fields_view_get(self.cr, SUPERUSER_ID, varg, 'form', context)
view_id = etree.fromstring(view['arch'].encode('utf-8'))
view_info = model.fields_view_get(self.cr, SUPERUSER_ID, varg, 'form', context)
record_dict = self._create_record(model, fields, view_id, default=default)
record_dict = self._create_record(model, fields, view_info, default=default)
_logger.debug("RECORD_DICT %s" % record_dict)
id = self.pool.get('ir.model.data')._update(self.cr, SUPERUSER_ID, record.model, \
self.module, record_dict, record.id, noupdate=self.isnoupdate(record), mode=self.mode, context=context)
@@ -327,16 +327,18 @@ class YamlInterpreter(object):
if config.get('import_partial'):
self.cr.commit()
def _create_record(self, model, fields, view=False, parent={}, default=True):
if view is not False:
defaults = default and model._add_missing_default_values(self.cr, SUPERUSER_ID, {}, context=self.context) or {}
fg = model.fields_get(self.cr, SUPERUSER_ID, context=self.context)
else:
defaults = {}
fg = {}
record_dict = {}
fields = fields or {}
def _create_record(self, model, fields, view_info=False, parent={}, default=True):
"""This function processes the !record tag in yalm files. It simulates the record creation through an xml
view (either specified on the !record tag or the default one for this object), including the calls to
on_change() functions.
:param model: model instance
:param fields: dictonary mapping the field names and their values
:param view_info: result of fields_view_get() called on the object
:param parent: dictionary containing the values already computed for the parent, in case of one2many fields
:param default: if True, the default values must be processed too or not
:return: dictionary mapping the field names and their values, ready to use when calling the create() function
:rtype: dict
"""
def process_val(key, val):
if fg[key]['type']=='many2one':
if type(val) in (tuple,list):
@@ -348,63 +350,75 @@ class YamlInterpreter(object):
val = map(lambda x: (0,0,x), val)
return val
# Process all on_change calls
nodes = (view is not False) and [view] or []
while nodes:
el = nodes.pop(0)
if el.tag=='field':
field_name = el.attrib['name']
assert field_name in fg, "The field '%s' is defined in the form view but not on the object '%s'!" % (field_name, model._name)
if field_name in fields:
view2 = None
# if the form view is not inline, we call fields_view_get
if (view is not False) and (fg[field_name]['type']=='one2many'):
view2 = view.find("field[@name='%s']/form"%(field_name,))
if not view2:
view2 = self.pool.get(fg[field_name]['relation']).fields_view_get(self.cr, SUPERUSER_ID, False, 'form', self.context)
view2 = etree.fromstring(view2['arch'].encode('utf-8'))
view = view_info and etree.fromstring(view_info['arch'].encode('utf-8')) or False
fields = fields or {}
if view is not False:
fg = view_info['fields']
# gather the default values on the object. (Can't use `fields´ as parameter instead of {} because we may
# have references like `base.main_company´ in the yaml file and it's not compatible with the function)
defaults = default and model._add_missing_default_values(self.cr, SUPERUSER_ID, {}, context=self.context) or {}
field_value = self._eval_field(model, field_name, fields[field_name], view2, parent=record_dict, default=default)
record_dict[field_name] = field_value
#if (field_name in defaults) and defaults[field_name] == field_value:
# print '*** You can remove these lines:', field_name, field_value
elif (field_name in defaults):
if (field_name not in record_dict):
record_dict[field_name] = process_val(field_name, defaults[field_name])
else:
continue
# copy the default values in record_dict, only if they are in the view (because that's what the client does)
# the other default values will be added later on by the create().
record_dict = dict([(key, val) for key, val in defaults.items() if key in fg])
if not el.attrib.get('on_change', False):
continue
match = re.match("([a-z_1-9A-Z]+)\((.*)\)", el.attrib['on_change'])
assert match, "Unable to parse the on_change '%s'!" % (el.attrib['on_change'], )
# Process all on_change calls
nodes = [view]
while nodes:
el = nodes.pop(0)
if el.tag=='field':
field_name = el.attrib['name']
assert field_name in fg, "The field '%s' is defined in the form view but not on the object '%s'!" % (field_name, model._name)
if field_name in fields:
one2many_form_view = None
if (view is not False) and (fg[field_name]['type']=='one2many'):
# for one2many fields, we want to eval them using the inline form view defined on the parent
one2many_form_view = view_info['fields'][field_name]['views'].get('form')
# if the form view is not defined inline, we call fields_view_get()
if not one2many_form_view:
one2many_form_view = self.pool.get(fg[field_name]['relation']).fields_view_get(self.cr, SUPERUSER_ID, False, 'form', self.context)
# creating the context
class parent2(object):
def __init__(self, d):
self.d = d
def __getattr__(self, name):
return self.d.get(name, False)
field_value = self._eval_field(model, field_name, fields[field_name], one2many_form_view or view_info, parent=record_dict, default=default)
record_dict[field_name] = field_value
#if (field_name in defaults) and defaults[field_name] == field_value:
# print '*** You can remove these lines:', field_name, field_value
ctx = record_dict.copy()
ctx['context'] = self.context
ctx['uid'] = 1
ctx['parent'] = parent2(parent)
for a in fg:
if a not in ctx:
ctx[a]=process_val(a, defaults.get(a, False))
#if field_name has a default value or a value is given in the yaml file, we must call its on_change()
elif field_name not in defaults:
continue
# Evaluation args
args = map(lambda x: eval(x, ctx), match.group(2).split(','))
result = getattr(model, match.group(1))(self.cr, SUPERUSER_ID, [], *args)
for key, val in (result or {}).get('value', {}).items():
if key not in fields:
assert key in fg, "The returning field '%s' from your on_change call '%s' does not exist on the object '%s'" % (key, match.group(1), model._name)
if not el.attrib.get('on_change', False):
continue
match = re.match("([a-z_1-9A-Z]+)\((.*)\)", el.attrib['on_change'])
assert match, "Unable to parse the on_change '%s'!" % (el.attrib['on_change'], )
# creating the context
class parent2(object):
def __init__(self, d):
self.d = d
def __getattr__(self, name):
return self.d.get(name, False)
ctx = record_dict.copy()
ctx['context'] = self.context
ctx['uid'] = SUPERUSER_ID
ctx['parent'] = parent2(parent)
for a in fg:
if a not in ctx:
ctx[a]=process_val(a, defaults.get(a, False))
# Evaluation args
args = map(lambda x: eval(x, ctx), match.group(2).split(','))
result = getattr(model, match.group(1))(self.cr, SUPERUSER_ID, [], *args)
for key, val in (result or {}).get('value', {}).items():
assert key in fg, "The returning field '%s' from your on_change call '%s' does not exist either on the object '%s', either in the view '%s' used for the creation" % (key, match.group(1), model._name, view_info['name'])
record_dict[key] = process_val(key, val)
#if (key in fields) and record_dict[key] == process_val(key, val):
# print '*** You can remove these lines:', key, val
else:
nodes = list(el) + nodes
else:
nodes = list(el) + nodes
else:
record_dict = {}
for field_name, expression in fields.items():
if field_name in record_dict:
@@ -440,7 +454,7 @@ class YamlInterpreter(object):
def process_eval(self, node):
return eval(node.expression, self.eval_context)
def _eval_field(self, model, field_name, expression, view=False, parent={}, default=True):
def _eval_field(self, model, field_name, expression, view_info=False, parent={}, default=True):
# TODO this should be refactored as something like model.get_field() in bin/osv
if field_name in model._columns:
column = model._columns[field_name]
@@ -461,7 +475,7 @@ class YamlInterpreter(object):
value = self.get_id(expression)
elif column._type == "one2many":
other_model = self.get_model(column._obj)
value = [(0, 0, self._create_record(other_model, fields, view, parent, default=default)) for fields in expression]
value = [(0, 0, self._create_record(other_model, fields, view_info, parent, default=default)) for fields in expression]
elif column._type == "many2many":
ids = [self.get_id(xml_id) for xml_id in expression]
value = [(6, 0, ids)]