[MERGE] merge with main server
bzr revid: rpr@tinyerp.com-20121008061045-lyu9nb756rvis02x
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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:
|
||||
@@ -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>
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user