diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql index e91e4516b1a..1187f027bde 100644 --- a/openerp/addons/base/base.sql +++ b/openerp/addons/base/base.sql @@ -25,25 +25,25 @@ create table ir_values CREATE TABLE ir_model ( id serial, - model varchar(64) DEFAULT ''::varchar NOT NULL, - name varchar(64), - state varchar(16), + model varchar DEFAULT ''::varchar NOT NULL, + name varchar, + state varchar, info text, primary key(id) ); CREATE TABLE ir_model_fields ( id serial, - model varchar(64) DEFAULT ''::varchar NOT NULL, + model varchar DEFAULT ''::varchar NOT NULL, model_id int references ir_model on delete cascade, - name varchar(64) DEFAULT ''::varchar NOT NULL, - relation varchar(64), - select_level varchar(4), - field_description varchar(256), - ttype varchar(64), - state varchar(64) default 'base', + name varchar DEFAULT ''::varchar NOT NULL, + relation varchar, + select_level varchar, + field_description varchar, + ttype varchar, + state varchar default 'base', relate boolean default False, - relation_field varchar(128), + relation_field varchar, translate boolean default False, primary key(id) ); @@ -350,11 +350,11 @@ CREATE TABLE ir_model_data ( write_date timestamp without time zone, write_uid integer, noupdate boolean, - name character varying(128) NOT NULL, + name varchar NOT NULL, date_init timestamp without time zone, date_update timestamp without time zone, - module character varying(64) NOT NULL, - model character varying(64) NOT NULL, + module varchar NOT NULL, + model varchar NOT NULL, res_id integer, primary key(id) ); @@ -373,7 +373,7 @@ CREATE TABLE ir_model_constraint ( module integer NOT NULL references ir_module_module on delete restrict, model integer NOT NULL references ir_model on delete restrict, type character varying(1) NOT NULL, - name character varying(128) NOT NULL + name varchar NOT NULL ); -- Records relation tables (i.e. implementing many2many) installed by a module @@ -388,7 +388,7 @@ CREATE TABLE ir_model_relation ( date_update timestamp without time zone, module integer NOT NULL references ir_module_module on delete restrict, model integer NOT NULL references ir_model on delete restrict, - name character varying(128) NOT NULL + name varchar NOT NULL ); --------------------------------- diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index d8198f7270a..65cc448b1d1 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2011 OpenERP S.A. +# Copyright (C) 2004-2013 OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -801,7 +801,7 @@ class ir_actions_server(osv.osv): raise osv.except_osv(_('Error'), _("Please specify an action to launch!")) return self.pool[action.action_id.type].read(cr, uid, action.action_id.id, context=context) - def run_action_code(self, cr, uid, action, eval_context=None, context=None): + def run_action_code_multi(self, cr, uid, action, eval_context=None, context=None): eval(action.code.strip(), eval_context, mode="exec", nocopy=True) # nocopy allows to return 'action' if 'action' in eval_context: return eval_context['action'] @@ -933,38 +933,50 @@ class ir_actions_server(osv.osv): context = {} res = False user = self.pool.get('res.users').browse(cr, uid, uid) - active_ids = context.get('active_ids', [context.get('active_id', None)]) + active_ids = context.get('active_ids', [context.get('active_id')]) for action in self.browse(cr, uid, ids, context): - obj = None obj_pool = self.pool[action.model_id.model] - for active_id in active_ids: - if context.get('active_model') == action.model_id.model and active_id: - obj = obj_pool.browse(cr, uid, context['active_id'], context=context) - # run context dedicated to a particular active_id - run_context = dict(context, active_ids=[active_id], active_id=active_id) - # evaluation context for python strings to evaluate - eval_context = { - 'self': obj_pool, - 'object': obj, - 'obj': obj, - 'pool': self.pool, - 'time': time, - 'cr': cr, - 'context': dict(run_context), # copy context to prevent side-effects of eval - 'uid': uid, - 'user': user - } + obj = None + if context.get('active_model') == action.model_id.model and context.get('active_id'): + obj = obj_pool.browse(cr, uid, context['active_id'], context=context) - # evaluate the condition, with the specific case that a void (aka False) condition is considered as True - condition = action.condition - if action.condition is False: - condition = True + # evaluation context for python strings to evaluate + eval_context = { + 'self': obj_pool, + 'object': obj, + 'obj': obj, + 'pool': self.pool, + 'time': time, + 'cr': cr, + 'uid': uid, + 'user': user, + } + condition = action.condition + if condition is False: + # Void (aka False) conditions are considered as True + condition = True + if hasattr(self, 'run_action_%s_multi' % action.state): + # set active_ids in context only needed if one active_id + run_context = dict(context, active_ids=active_ids) + eval_context["context"] = run_context expr = eval(str(condition), eval_context) if not expr: continue - # call the method related to the action: run_action_ - if hasattr(self, 'run_action_%s' % action.state): - res = getattr(self, 'run_action_%s' % action.state)(cr, uid, action, eval_context=eval_context, context=run_context) + # call the multi method + func = getattr(self, 'run_action_%s_multi' % action.state) + res = func(cr, uid, action, eval_context=eval_context, context=run_context) + + elif hasattr(self, 'run_action_%s' % action.state): + func = getattr(self, 'run_action_%s' % action.state) + for active_id in active_ids: + # run context dedicated to a particular active_id + run_context = dict(context, active_ids=[active_id], active_id=active_id) + eval_context["context"] = run_context + expr = eval(str(condition), eval_context) + if not expr: + continue + # call the single method related to the action: run_action_ + res = func(cr, uid, action, eval_context=eval_context, context=run_context) return res diff --git a/openerp/addons/base/ir/ir_actions.xml b/openerp/addons/base/ir/ir_actions.xml index 843dc5df35b..7e6c1f9be4b 100644 --- a/openerp/addons/base/ir/ir_actions.xml +++ b/openerp/addons/base/ir/ir_actions.xml @@ -476,7 +476,8 @@ - + + diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 6b7b4cecc4b..dbfd1a82ced 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -198,7 +198,7 @@ class ir_attachment(osv.osv): continue res_ids.setdefault(rmod,set()).add(rid) if values: - if values.get('res_model') and 'res_id' in values: + if values.get('res_model') and values.get('res_id'): res_ids.setdefault(values['res_model'],set()).add(values['res_id']) ima = self.pool.get('ir.model.access') diff --git a/openerp/addons/base/ir/ir_fields.py b/openerp/addons/base/ir/ir_fields.py index 09622dadd17..e1522123dcf 100644 --- a/openerp/addons/base/ir/ir_fields.py +++ b/openerp/addons/base/ir/ir_fields.py @@ -253,7 +253,7 @@ class ir_fields_converter(orm.Model): if not isinstance(selection, (tuple, list)): # FIXME: Don't pass context to avoid translations? # Or just copy context & remove lang? - selection = selection(model, cr, uid) + selection = selection(model, cr, uid, context=None) for item, label in selection: labels = self._get_translations( cr, uid, ('selection', 'model', 'code'), label, context=context) diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index 14b854f0f1a..61fdb8ab005 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -96,8 +96,8 @@ class ir_model(osv.osv): return res _columns = { - 'name': fields.char('Model Description', size=64, translate=True, required=True), - 'model': fields.char('Model', size=64, required=True, select=1), + 'name': fields.char('Model Description', translate=True, required=True), + 'model': fields.char('Model', required=True, select=1), 'info': fields.text('Information'), 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True), 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Type',readonly=True), @@ -105,7 +105,7 @@ class ir_model(osv.osv): 'osv_memory': fields.function(_is_osv_memory, string='Transient Model', type='boolean', fnct_search=_search_osv_memory, help="This field specifies whether the model is transient or not (i.e. if records are automatically deleted from the database or not)"), - 'modules': fields.function(_in_modules, type='char', size=128, string='In Modules', help='List of modules in which the object is defined or inherited'), + 'modules': fields.function(_in_modules, type='char', string='In Modules', help='List of modules in which the object is defined or inherited'), 'view_ids': fields.function(_view_ids, type='one2many', obj='ir.ui.view', string='Views'), } @@ -223,19 +223,19 @@ class ir_model_fields(osv.osv): _rec_name = 'field_description' _columns = { - 'name': fields.char('Name', required=True, size=64, select=1), - 'complete_name': fields.char('Complete Name', size=64, select=1), - 'model': fields.char('Object Name', size=64, required=True, select=1, + 'name': fields.char('Name', required=True, select=1), + 'complete_name': fields.char('Complete Name', select=1), + 'model': fields.char('Object Name', required=True, select=1, help="The technical name of the model this field belongs to"), - 'relation': fields.char('Object Relation', size=64, + 'relation': fields.char('Object Relation', help="For relationship fields, the technical name of the target model"), - 'relation_field': fields.char('Relation Field', size=64, + 'relation_field': fields.char('Relation Field', help="For one2many fields, the field on the target model that implement the opposite many2one relationship"), 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade', help="The model this field belongs to"), - 'field_description': fields.char('Field Label', required=True, size=256), - 'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True), - 'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, " + 'field_description': fields.char('Field Label', required=True), + 'ttype': fields.selection(_get_fields_type, 'Field Type', required=True), + 'selection': fields.char('Selection Options', help="List of options for a selection field, " "specified as a Python expression defining a list of (key, label) pairs. " "For example: [('blue','Blue'),('yellow','Yellow')]"), 'required': fields.boolean('Required'), @@ -245,12 +245,12 @@ class ir_model_fields(osv.osv): 'size': fields.integer('Size'), 'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1), 'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On Delete', help='On delete property for many2one fields'), - 'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, " + 'domain': fields.char('Domain', help="The optional domain to restrict possible values for relationship fields, " "specified as a Python expression defining a list of triplets. " "For example: [('color','=','red')]"), 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'), 'selectable': fields.boolean('Selectable'), - 'modules': fields.function(_in_modules, type='char', size=128, string='In Modules', help='List of modules in which the field is defined'), + 'modules': fields.function(_in_modules, type='char', string='In Modules', help='List of modules in which the field is defined'), 'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]", ondelete='cascade', help="If set, this field will be stored in the sparse " "structure of the serialization field, instead " @@ -483,7 +483,7 @@ class ir_model_constraint(Model): """ _name = 'ir.model.constraint' _columns = { - 'name': fields.char('Constraint', required=True, size=128, select=1, + 'name': fields.char('Constraint', required=True, select=1, help="PostgreSQL constraint or foreign key name."), 'model': fields.many2one('ir.model', string='Model', required=True, select=1), @@ -552,7 +552,7 @@ class ir_model_relation(Model): """ _name = 'ir.model.relation' _columns = { - 'name': fields.char('Relation Name', required=True, size=128, select=1, + 'name': fields.char('Relation Name', required=True, select=1, help="PostgreSQL table name implementing a many2many relation."), 'model': fields.many2one('ir.model', string='Model', required=True, select=1), @@ -601,7 +601,7 @@ class ir_model_relation(Model): class ir_model_access(osv.osv): _name = 'ir.model.access' _columns = { - 'name': fields.char('Name', size=64, required=True, select=True), + 'name': fields.char('Name', required=True, select=True), 'active': fields.boolean('Active', help='If you uncheck the active field, it will disable the ACL without deleting it (if you delete a native ACL, it will be re-created when you reload the module.'), 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'), 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True), @@ -815,13 +815,13 @@ class ir_model_data(osv.osv): return result _columns = { - 'name': fields.char('External Identifier', required=True, size=128, select=1, + 'name': fields.char('External Identifier', required=True, select=1, help="External Key/Identifier that can be used for " "data integration with third-party systems"), 'complete_name': fields.function(_complete_name_get, type='char', string='Complete ID'), 'display_name': fields.function(_display_name_get, type='char', string='Record Name'), - 'model': fields.char('Model Name', required=True, size=64, select=1), - 'module': fields.char('Module', required=True, size=64, select=1), + 'model': fields.char('Model Name', required=True, select=1), + 'module': fields.char('Module', required=True, select=1), 'res_id': fields.integer('Record ID', select=1, help="ID of the target record in the database"), 'noupdate': fields.boolean('Non Updatable'), @@ -1128,7 +1128,7 @@ class ir_model_data(osv.osv): return True to_unlink = [] cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data - WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s""", + WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s ORDER BY id DESC""", (tuple(modules), False)) for (id, name, model, res_id, module) in cr.fetchall(): if (module,name) not in self.loads: diff --git a/openerp/addons/base/ir/ir_translation.py b/openerp/addons/base/ir/ir_translation.py index 6cbd90c3072..2ab93e2b11b 100644 --- a/openerp/addons/base/ir/ir_translation.py +++ b/openerp/addons/base/ir/ir_translation.py @@ -182,7 +182,7 @@ class ir_translation(osv.osv): if context is None: context = {} record = self.browse(cr, uid, id, context=context) - if value and record.type == 'model': + if record.type == 'model': model_name, field = record.name.split(',') model = self.pool.get(model_name) #We need to take the context without the language information, because we want to write on the diff --git a/openerp/addons/base/res/res_country_data.xml b/openerp/addons/base/res/res_country_data.xml index 55818cb5c39..13c1025cf6b 100644 --- a/openerp/addons/base/res/res_country_data.xml +++ b/openerp/addons/base/res/res_country_data.xml @@ -1212,7 +1212,7 @@ Venezuela ve - + Virgin Islands (British) diff --git a/openerp/addons/base/res/res_currency_data.xml b/openerp/addons/base/res/res_currency_data.xml index 9265c897f8f..3bf2c1b4473 100644 --- a/openerp/addons/base/res/res_currency_data.xml +++ b/openerp/addons/base/res/res_currency_data.xml @@ -1540,7 +1540,7 @@ - VUB + VEB Bs 0.01 4 diff --git a/openerp/addons/base/res/res_partner_view.xml b/openerp/addons/base/res/res_partner_view.xml index 943aedf49f7..822c6a36fd2 100644 --- a/openerp/addons/base/res/res_partner_view.xml +++ b/openerp/addons/base/res/res_partner_view.xml @@ -329,7 +329,7 @@ + filter_domain="['|','|',('display_name','ilike',self),('ref','=',self),('email','ilike',self)]"/> diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 160ac6a2059..1acb55513c2 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -169,7 +169,8 @@ class res_users(osv.osv): } def on_change_login(self, cr, uid, ids, login, context=None): - return {'value': {'email': login}} + v = {'email': login} if tools.single_email_re.match(login) else {} + return {'value': v} def onchange_state(self, cr, uid, ids, state_id, context=None): partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)] diff --git a/openerp/addons/base/tests/test_base.py b/openerp/addons/base/tests/test_base.py index 345973526c5..1ca66f57943 100644 --- a/openerp/addons/base/tests/test_base.py +++ b/openerp/addons/base/tests/test_base.py @@ -304,5 +304,47 @@ class test_partner_recursion(common.TransactionCase): cr, uid, p1, p2, p3 = self.cr, self.uid, self.p1, self.p2, self.p3 self.assertTrue(self.res_partner.write(cr, uid, [p1,p2,p3], {'phone': '123456'})) +class test_translation(common.TransactionCase): + + def setUp(self): + super(test_translation, self).setUp() + self.res_category = self.registry('res.partner.category') + self.ir_translation = self.registry('ir.translation') + cr, uid = self.cr, self.uid + self.registry('ir.translation').load(cr, ['base'], ['fr_FR']) + self.cat_id = self.res_category.create(cr, uid, {'name': 'Customers'}) + self.ir_translation.create(cr, uid, {'name': 'res.partner.category,name', 'module':'base', + 'value': 'Clients', 'res_id': self.cat_id, 'lang':'fr_FR', 'state':'translated', 'type': 'model'}) + + def test_101_create_translated_record(self): + cr, uid = self.cr, self.uid + + no_context_cat = self.res_category.browse(cr, uid, self.cat_id) + self.assertEqual(no_context_cat.name, 'Customers', "Error in basic name_get") + + fr_context_cat = self.res_category.browse(cr, uid, self.cat_id, context={'lang':'fr_FR'}) + self.assertEqual(fr_context_cat.name, 'Clients', "Translation not found") + + def test_102_duplicate_record(self): + cr, uid = self.cr, self.uid + self.new_cat_id = self.res_category.copy(cr, uid, self.cat_id, context={'lang':'fr_FR'}) + + no_context_cat = self.res_category.browse(cr, uid, self.new_cat_id) + self.assertEqual(no_context_cat.name, 'Customers', "Duplication did not set untranslated value") + + fr_context_cat = self.res_category.browse(cr, uid, self.new_cat_id, context={'lang':'fr_FR'}) + self.assertEqual(fr_context_cat.name, 'Clients', "Did not found translation for initial value") + + def test_103_duplicate_record_fr(self): + cr, uid = self.cr, self.uid + self.new_fr_cat_id = self.res_category.copy(cr, uid, self.cat_id, default={'name': 'Clients (copie)'}, context={'lang':'fr_FR'}) + + no_context_cat = self.res_category.browse(cr, uid, self.new_fr_cat_id) + self.assertEqual(no_context_cat.name, 'Customers', "Duplication erased original untranslated value") + + fr_context_cat = self.res_category.browse(cr, uid, self.new_fr_cat_id, context={'lang':'fr_FR'}) + self.assertEqual(fr_context_cat.name, 'Clients (copie)', "Did not used default value for translated value") + + if __name__ == '__main__': unittest2.main() diff --git a/openerp/addons/base/tests/test_views.py b/openerp/addons/base/tests/test_views.py index 6203e4f001e..5d127580bd5 100644 --- a/openerp/addons/base/tests/test_views.py +++ b/openerp/addons/base/tests/test_views.py @@ -40,6 +40,19 @@ class test_views(common.TransactionCase): """, }) + def test_20_remove_unexisting_attribute(self): + Views = self.registry('ir.ui.view') + Views.create(self.cr, self.uid, { + 'name': 'Test View', + 'model': 'ir.ui.view', + 'inherit_id': self.browse_ref('base.view_view_tree').id, + 'arch': """ + + + + """, + }) + def _insert_view(self, **kw): """Insert view into database via a query to passtrough validation""" kw.pop('id', None) diff --git a/openerp/cli/server.py b/openerp/cli/server.py index 97737a38c3f..cf4d3855a84 100644 --- a/openerp/cli/server.py +++ b/openerp/cli/server.py @@ -94,11 +94,11 @@ def preload_registry(dbname): """ Preload a registry, and start the cron.""" try: update_module = True if openerp.tools.config['init'] or openerp.tools.config['update'] else False - openerp.modules.registry.RegistryManager.new(dbname, update_module=update_module) + registry = openerp.modules.registry.RegistryManager.new(dbname, update_module=update_module) except Exception: _logger.exception('Failed to initialize database `%s`.', dbname) return False - return True + return registry._assertion_report.failures == 0 def run_test_file(dbname, test_file): """ Preload a registry, possibly run a test file, and start the cron.""" diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 983de2d532c..e9a6da4c397 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -342,7 +342,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if processed_modules: cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""") for (model, name) in cr.fetchall(): - if model in registry and not registry[model].is_transient(): + if model in registry and not registry[model].is_transient() and isinstance(registry[model], openerp.osv.orm.AbstractModel): _logger.warning('The model %s has no access rules, consider adding one. E.g. access_%s,access_%s,model_%s,,1,1,1,1', model, model.replace('.', '_'), model.replace('.', '_'), model.replace('.', '_')) diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py index d58f4b3186b..5929924b118 100644 --- a/openerp/modules/registry.py +++ b/openerp/modules/registry.py @@ -199,15 +199,16 @@ class RegistryManager(object): @classmethod def get(cls, db_name, force_demo=False, status=None, update_module=False): """ Return a registry for a given database name.""" - try: - return cls.registries[db_name] - except KeyError: - return cls.new(db_name, force_demo, status, - update_module) - finally: - # set db tracker - cleaned up at the WSGI - # dispatching phase in openerp.service.wsgi_server.application - threading.current_thread().dbname = db_name + with cls.registries_lock: + try: + return cls.registries[db_name] + except KeyError: + return cls.new(db_name, force_demo, status, + update_module) + finally: + # set db tracker - cleaned up at the WSGI + # dispatching phase in openerp.service.wsgi_server.application + threading.current_thread().dbname = db_name @classmethod def new(cls, db_name, force_demo=False, status=None, diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 57a68338193..4ae57881273 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -1147,7 +1147,7 @@ class function(_column): # make the result a tuple if it is not already one if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'): obj_model = obj.pool[obj._columns[field].relation] - dict_names = dict(obj_model.name_get(cr, uid, [value], context)) + dict_names = dict(obj_model.name_get(cr, SUPERUSER_ID, [value], context)) result = (value, dict_names[value]) if field_type == 'binary': diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index cf96c8d27bd..d0504300431 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -399,7 +399,15 @@ class browse_record(object): ids = filter(lambda id: name not in self._data[id], self._data.keys()) # read the results field_names = map(lambda x: x[0], fields_to_fetch) - field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write") + try: + field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write") + except (openerp.exceptions.AccessError, except_orm): + if len(ids) == 1: + raise + # prefetching attempt failed, perhaps we're violating ACL restrictions involuntarily + _logger.info('Prefetching attempt for fields %s on %s failed for ids %s, re-trying just for id %s', field_names, self._model._name, ids, self._id) + ids = [self._id] + field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write") # TODO: improve this, very slow for reports if self._fields_process: @@ -2153,8 +2161,8 @@ class BaseModel(object): attribute = (child.get('name'), child.text and child.text.encode('utf8') or None) if attribute[1]: node.set(attribute[0], attribute[1]) - else: - del(node.attrib[attribute[0]]) + elif attribute[0] in node.attrib: + del node.attrib[attribute[0]] else: sib = node.getnext() for child in spec: @@ -4997,10 +5005,7 @@ class BaseModel(object): else: default['state'] = self._defaults['state'] - context_wo_lang = context.copy() - if 'lang' in context: - del context_wo_lang['lang'] - data = self.read(cr, uid, [id,], context=context_wo_lang) + data = self.read(cr, uid, [id,], context=context) if data: data = data[0] else: @@ -5060,36 +5065,50 @@ class BaseModel(object): # TODO it seems fields_get can be replaced by _all_columns (no need for translation) fields = self.fields_get(cr, uid, context=context) - translation_records = [] for field_name, field_def in fields.items(): + # removing the lang to compare untranslated values + context_wo_lang = dict(context, lang=None) + old_record, new_record = self.browse(cr, uid, [old_id, new_id], context=context_wo_lang) # we must recursively copy the translations for o2o and o2m if field_def['type'] == 'one2many': target_obj = self.pool[field_def['relation']] - old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context) # here we rely on the order of the ids to match the translations # as foreseen in copy_data() - old_children = sorted(old_record[field_name]) - new_children = sorted(new_record[field_name]) + old_children = sorted(r.id for r in old_record[field_name]) + new_children = sorted(r.id for r in new_record[field_name]) for (old_child, new_child) in zip(old_children, new_children): target_obj.copy_translations(cr, uid, old_child, new_child, context=context) # and for translatable fields we keep them for copy elif field_def.get('translate'): - trans_name = '' if field_name in self._columns: trans_name = self._name + "," + field_name + target_id = new_id + source_id = old_id elif field_name in self._inherit_fields: trans_name = self._inherit_fields[field_name][0] + "," + field_name - if trans_name: - trans_ids = trans_obj.search(cr, uid, [ - ('name', '=', trans_name), - ('res_id', '=', old_id) - ]) - translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context)) + # get the id of the parent record to set the translation + inherit_field_name = self._inherit_fields[field_name][1] + target_id = new_record[inherit_field_name].id + source_id = old_record[inherit_field_name].id + else: + continue - for record in translation_records: - del record['id'] - record['res_id'] = new_id - trans_obj.create(cr, uid, record, context=context) + trans_ids = trans_obj.search(cr, uid, [ + ('name', '=', trans_name), + ('res_id', '=', source_id) + ]) + user_lang = context.get('lang') + for record in trans_obj.read(cr, uid, trans_ids, context=context): + del record['id'] + # remove source to avoid triggering _set_src + del record['source'] + record.update({'res_id': target_id}) + if user_lang and user_lang == record['lang']: + # 'source' to force the call to _set_src + # 'value' needed if value is changed in copy(), want to see the new_value + record['source'] = old_record[field_name] + record['value'] = new_record[field_name] + trans_obj.create(cr, uid, record, context=context) def copy(self, cr, uid, id, default=None, context=None): diff --git a/openerp/report/render/rml2pdf/trml2pdf.py b/openerp/report/render/rml2pdf/trml2pdf.py index 2052b128ad9..b075b0f53ea 100644 --- a/openerp/report/render/rml2pdf/trml2pdf.py +++ b/openerp/report/render/rml2pdf/trml2pdf.py @@ -242,7 +242,7 @@ class _rml_styles(object,): if sname in self.styles_obj: style = self.styles_obj[sname] else: - _logger.warning('Warning: style not found, %s - setting default!\n' % (node.get('style'),) ) + _logger.debug('Warning: style not found, %s - setting default!', node.get('style')) if not style: style = self.default_style['Normal'] para_update = self._para_style_update(node) @@ -631,6 +631,18 @@ class _rml_Illustration(platypus.flowables.Flowable): drw = _rml_draw(self.localcontext ,self.node,self.styles, images=self.self2.images, path=self.self2.path, title=self.self2.title) drw.render(self.canv, None) +# Workaround for issue #15: https://bitbucket.org/rptlab/reportlab/issue/15/infinite-pages-produced-when-splitting +original_pto_split = platypus.flowables.PTOContainer.split +def split(self, availWidth, availHeight): + res = original_pto_split(self, availWidth, availHeight) + if len(res) > 2 and len(self._content) > 0: + header = self._content[0]._ptoinfo.header + trailer = self._content[0]._ptoinfo.trailer + if isinstance(res[-2], platypus.flowables.UseUpSpace) and len(header + trailer) == len(res[:-2]): + return [] + return res +platypus.flowables.PTOContainer.split = split + class _rml_flowable(object): def __init__(self, doc, localcontext, images=None, path='.', title=None, canvas=None): if images is None: @@ -1012,11 +1024,16 @@ class _rml_template(object): # Reset Page Number with new story tag fis.append(PageReset()) story_cnt += 1 - if self.localcontext and self.localcontext.get('internal_header',False): - self.doc_tmpl.afterFlowable(fis) - self.doc_tmpl.build(fis,canvasmaker=NumberedCanvas) - else: - self.doc_tmpl.build(fis) + try: + if self.localcontext and self.localcontext.get('internal_header',False): + self.doc_tmpl.afterFlowable(fis) + self.doc_tmpl.build(fis,canvasmaker=NumberedCanvas) + else: + self.doc_tmpl.build(fis) + except platypus.doctemplate.LayoutError, e: + e.name = 'Print Error' + e.value = 'The document you are trying to print contains a table row that does not fit on one page. Please try to split it in smaller rows or contact your administrator.' + raise def parseNode(rml, localcontext=None, fout=None, images=None, path='.', title=None): node = etree.XML(rml) diff --git a/openerp/tests/test_acl.py b/openerp/tests/test_acl.py index e80390cd5a2..44ec677ff6f 100644 --- a/openerp/tests/test_acl.py +++ b/openerp/tests/test_acl.py @@ -95,9 +95,10 @@ class TestACL(common.TransactionCase): part = P.browse(self.cr, self.demo_uid, pid) # accessing fields must no raise exceptions... part.name - # ... except they are restricted + # ... except if they are restricted with self.assertRaises(openerp.osv.orm.except_orm) as cm: - part.email + with mute_logger('openerp.osv.orm'): + part.email self.assertEqual(cm.exception.args[0], 'Access Denied') finally: diff --git a/openerp/tools/mail.py b/openerp/tools/mail.py index ff73a3e0c72..97af2745cd8 100644 --- a/openerp/tools/mail.py +++ b/openerp/tools/mail.py @@ -87,7 +87,9 @@ def html_sanitize(src, silent=True): # some corner cases make the parser crash (such as in test_mail) cleaner = clean.Cleaner(**kwargs) cleaned = cleaner.clean_html(src) - except etree.ParserError: + except etree.ParserError, e: + if 'empty' in str(e): + return "" if not silent: raise logger.warning('ParserError obtained when sanitizing %r', src, exc_info=True) diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index 65e7c38e536..ea5bf6a185b 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -470,6 +470,7 @@ ALL_LANGUAGES = { 'ja_JP': u'Japanese / 日本語', 'ko_KP': u'Korean (KP) / 한국어 (KP)', 'ko_KR': u'Korean (KR) / 한국어 (KR)', + 'lo_LA': u'Lao / ພາສາລາວ', 'lt_LT': u'Lithuanian / Lietuvių kalba', 'lv_LV': u'Latvian / latviešu valoda', 'ml_IN': u'Malayalam / മലയാളം', diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py index aba9db25f13..a418cb9f841 100644 --- a/openerp/tools/yaml_import.py +++ b/openerp/tools/yaml_import.py @@ -448,9 +448,9 @@ class YamlInterpreter(object): 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 + if key not in fields: + # do not shadow values explicitly set in yaml. + record_dict[key] = process_val(key, val) else: nodes = list(el) + nodes else: diff --git a/openerpcommand/common.py b/openerpcommand/common.py index f306d7f4b2d..9209f8e1eb6 100644 --- a/openerpcommand/common.py +++ b/openerpcommand/common.py @@ -40,8 +40,7 @@ def get_addons_from_paths(paths, exclude): module_names = [] for p in paths: if os.path.exists(p): - names = list(set(os.listdir(p))) - names = filter(lambda a: not (a.startswith('.') or a in exclude), names) + names = [n for n in os.listdir(p) if os.path.isfile(os.path.join(p, n, '__openerp__.py')) and not n.startswith('.') and n not in exclude] names = filter(lambda a: os.path.isdir(os.path.join(p, a)), names) names = filter(lambda a: os.path.exists(os.path.join(p, a, '__openerp__.py')), names) module_names.extend(names)