From 5df4aa8ccad22430cc1467ea0820c2bb16aaf726 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Thu, 31 May 2012 18:01:40 +0200 Subject: [PATCH 01/30] [IMP] uninstall: started test module, added new ir_model_constraint table. bzr revid: vmt@openerp.com-20120531160140-tutjw76tp0u5hipo --- openerp/addons/base/base.sql | 19 +++++ openerp/addons/base/ir/__init__.py | 1 + openerp/addons/base/ir/ir_model_constraint.py | 71 +++++++++++++++++++ openerp/osv/orm.py | 18 +++++ .../tests/addons/test_uninstall/__init__.py | 3 + .../addons/test_uninstall/__openerp__.py | 15 ++++ openerp/tests/addons/test_uninstall/models.py | 11 +++ t.py | 66 +++++++++++++++++ 8 files changed, 204 insertions(+) create mode 100644 openerp/addons/base/ir/ir_model_constraint.py create mode 100644 openerp/tests/addons/test_uninstall/__init__.py create mode 100644 openerp/tests/addons/test_uninstall/__openerp__.py create mode 100644 openerp/tests/addons/test_uninstall/models.py create mode 100644 t.py diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql index eb9966bddcd..a227007353c 100644 --- a/openerp/addons/base/base.sql +++ b/openerp/addons/base/base.sql @@ -347,6 +347,25 @@ CREATE TABLE ir_model_data ( res_id integer, primary key(id) ); +-- Records foreign keys and constraints +-- installed by a module (so they can be removed them when the module is +-- uninstalled).: +-- - for a foreign key: type 'f', +-- - for a constraint: type 'u'. +CREATE TABLE ir_model_constraint ( + id serial NOT NULL, + create_uid integer, + create_date timestamp without time zone, + write_date timestamp without time zone, + write_uid integer, + date_init timestamp without time zone, + 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, + type character varying(1) NOT NULL, + name character varying(128) NOT NULL +); + --------------------------------- -- Users --------------------------------- diff --git a/openerp/addons/base/ir/__init__.py b/openerp/addons/base/ir/__init__.py index a2a4b999ed8..f4605d1615f 100644 --- a/openerp/addons/base/ir/__init__.py +++ b/openerp/addons/base/ir/__init__.py @@ -20,6 +20,7 @@ ############################################################################## import ir_model +import ir_model_constraint import ir_sequence import ir_needaction import ir_ui_menu diff --git a/openerp/addons/base/ir/ir_model_constraint.py b/openerp/addons/base/ir/ir_model_constraint.py new file mode 100644 index 00000000000..790b598763a --- /dev/null +++ b/openerp/addons/base/ir/ir_model_constraint.py @@ -0,0 +1,71 @@ + +import openerp +from openerp.osv import fields +from openerp.osv.orm import Model + +class ir_model_constraint(Model): + """ + This model tracks PostgreSQL foreign keys and constraints used by OpenERP + models. + """ + _name = 'ir.model.constraint' + _columns = { + 'name': fields.char('Constraint', required=True, size=128, select=1, + help="PostgreSQL constraint or foreign key name."), + 'model': fields.many2one('ir.model', string='Model Name', + required=True, select=1), + 'module': fields.many2one('ir.module.module', string='Module Name', + required=True, select=1), + 'type': fields.char('Constraint Type', required=True, size=1, select=1, + help="Type of the constraint: `f` for a foreign key, " + "`u` for other constraints."), + 'date_update': fields.datetime('Update Date'), + 'date_init': fields.datetime('Initialization Date') + } + + _sql_constraints = [ + ('module_name_uniq', 'unique(name, module)', + 'Constraints with the same name are unique per module.'), + ] + + def _module_data_uninstall(self, cr, uid, ids, context=None): + """ + Delete PostgreSQL foreign keys and constraints tracked by this model. + """ + + if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"): + raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module'))) + + context = dict(context or {}) + + ids_set = set(ids) + ids.sort() + ids.reverse() + for data in self.browse(cr, uid, ids, context): + model = data.model + model_obj = self.pool.get(model) + name = tools.ustr(data.name) + typ = data.type + + # double-check we are really going to delete all the owners of this schema element + cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,)) + external_ids = [x[0] for x in cr.fetchall()] + if (set(external_ids)-ids_set): + # as installed modules have defined this element we must not delete it! + pass + + elif typ == 'f': + # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it) + cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid) + WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table)) + if cr.fetchone(): + cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) + _logger.info('Dropped FK CONSTRAINT %s@%s', name, model) + + elif typ == 'u': + # test if constraint exists + cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid) + WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table)) + if cr.fetchone(): + cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) + _logger.info('Dropped CONSTRAINT %s@%s', name, model) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index e99296a15e1..be55c1d305d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2744,6 +2744,23 @@ class BaseModel(object): _schema.debug("Table '%s': column '%s': dropped NOT NULL constraint", self._table, column['attname']) + def _save_constraint(self, cr, constraint_name, type): + assert type in ('f', 'u') + cr.execute(""" + SELECT 1 FROM ir_model_constraint, ir_module_module + WHERE ir_model_constraint.module=ir_module_module.id + AND ir_model_constraint.name=%s + AND ir_module_module.name=%s + """, (constraint_name, self._module)) + if not cr.rowcount: + cr.execute(""" + INSERT INTO ir_model_constraint + (name, date_init, date_update, module, model, type) + VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC', + (SELECT id FROM ir_module_module WHERE name=%s), + (SELECT id FROM ir_model WHERE model=%s), %s)""", + (constraint_name, self._module, self._name, type)) + # quick creation of ir.model.data entry to make uninstall of schema elements easier def _make_ext_id(self, cr, ext_id): cr.execute('SELECT 1 FROM ir_model_data WHERE name=%s AND module=%s', (ext_id, self._module)) @@ -3092,6 +3109,7 @@ class BaseModel(object): for t, k, r, d in self._foreign_keys: cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d)) self._make_ext_id(cr, "%s%s_%s_fkey" % (EXT_ID_PREFIX_FK, t, k)) + self._save_constraint(cr, "%s_%s_fkey" % (t, k), 'f') cr.commit() del self._foreign_keys diff --git a/openerp/tests/addons/test_uninstall/__init__.py b/openerp/tests/addons/test_uninstall/__init__.py new file mode 100644 index 00000000000..fe4487156b1 --- /dev/null +++ b/openerp/tests/addons/test_uninstall/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +import models +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tests/addons/test_uninstall/__openerp__.py b/openerp/tests/addons/test_uninstall/__openerp__.py new file mode 100644 index 00000000000..d8776897df0 --- /dev/null +++ b/openerp/tests/addons/test_uninstall/__openerp__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'test-uninstall', + 'version': '0.1', + 'category': 'Tests', + 'description': """A module to test the uninstall feature.""", + 'author': 'OpenERP SA', + 'maintainer': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'depends': ['base'], + 'data': [], + 'installable': True, + 'auto_install': False, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tests/addons/test_uninstall/models.py b/openerp/tests/addons/test_uninstall/models.py new file mode 100644 index 00000000000..645853cc2f8 --- /dev/null +++ b/openerp/tests/addons/test_uninstall/models.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +import openerp +from openerp.osv.orm import Model + +class test_uninstall_model(Model): + _name = 'test_uninstall.model' + + _columns = { + } + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/t.py b/t.py new file mode 100644 index 00000000000..f5497943a59 --- /dev/null +++ b/t.py @@ -0,0 +1,66 @@ +import openerp +from openerp.osv import fields +from openerp.osv.orm import Model +conf = openerp.tools.config + +if __name__ == '__main__': + openerp.netsvc.init_logger() + conf['addons_path'] = './openerp/tests/addons' + conf['init'] = {'test_uninstall': 1} + registry = openerp.modules.registry.RegistryManager.new('xx', update_module=True) + ir_model_constraint = registry.get('ir.model.constraint') + print ir_model_constraint + cr = registry.db.cursor() + ids = ir_model_constraint.search(cr, openerp.SUPERUSER_ID, [], {}) + print ir_model_constraint.browse(cr, openerp.SUPERUSER_ID, ids, {}) + cr.close() + +##################################################################### + +# Nice idea, but won't work without some more change to the framework (which +# expects everything on disk, maybe we can craft a zip file...). + +MY_MODULE = { + 'author': 'Jean Beauvoir', + 'website': 'http://www.youtube.com/watch?v=FeO5DfdZi7Y', + 'name': 'FEEL THE HEAT', + 'description': "Cobra's theme", + 'web': False, + 'license': 'WTFPL', + 'application': False, + 'icon': False, + 'sequence': 100, + 'depends': ['base'], +} + +def create_virtual_module(db_name, module_name, info): + registry = openerp.modules.registry.RegistryManager.get(db_name) + cr = registry.db.cursor() + + cr.execute("""SELECT 1 FROM ir_module_module WHERE name=%s""", (module_name,)) + if cr.fetchone(): return + + category_id = openerp.modules.db.create_categories(cr, ['Tests']) + cr.execute('INSERT INTO ir_module_module \ + (author, website, name, shortdesc, description, \ + category_id, auto_install, state, certificate, web, license, application, icon, sequence) \ + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id', ( + info['author'], + info['website'], module_name, info['name'], + info['description'], category_id, + True, 'to install', False, + info['web'], + info['license'], + info['application'], info['icon'], + info['sequence'])) + module_id = cr.fetchone()[0] + cr.execute('INSERT INTO ir_model_data \ + (name,model,module, res_id, noupdate) VALUES (%s,%s,%s,%s,%s)', ( + 'module_' + module_name, 'ir.module.module', 'base', module_id, True)) + dependencies = info['depends'] + for d in dependencies: + cr.execute('INSERT INTO ir_module_module_dependency \ + (module_id,name) VALUES (%s, %s)', (module_id, d)) + + cr.commit() + cr.close() From d790650bfb703ba30a7abde8d4d3d111034fbbb5 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Jun 2012 15:14:14 +0200 Subject: [PATCH 02/30] [IMP] uninstall: use dedicated table instead of overloading ir.model.data. bzr revid: vmt@openerp.com-20120601131414-880vmpctmjfxl84f --- openerp/addons/base/ir/ir_model.py | 26 +------ openerp/addons/base/ir/ir_model_constraint.py | 22 ++++-- openerp/addons/base/module/module.py | 3 + openerp/osv/orm.py | 5 +- openerp/tests/addons/test_uninstall/models.py | 7 ++ t.py | 78 +++++++++++++++++-- 6 files changed, 99 insertions(+), 42 deletions(-) diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index 421342cd625..174f52b1944 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -29,8 +29,7 @@ from openerp import netsvc, pooler, tools from openerp.tools.safe_eval import safe_eval as eval from openerp.tools import config from openerp.tools.translate import _ -from openerp.osv.orm import except_orm, browse_record, EXT_ID_PREFIX_FK, \ - EXT_ID_PREFIX_M2M_TABLE, EXT_ID_PREFIX_CONSTRAINT +from openerp.osv.orm import except_orm, browse_record, EXT_ID_PREFIX_M2M_TABLE _logger = logging.getLogger(__name__) @@ -857,8 +856,7 @@ class ir_model_data(osv.osv): model_obj = self.pool.get(model) name = tools.ustr(data.name) - if name.startswith(EXT_ID_PREFIX_FK) or name.startswith(EXT_ID_PREFIX_M2M_TABLE)\ - or name.startswith(EXT_ID_PREFIX_CONSTRAINT): + if name.startswith(EXT_ID_PREFIX_M2M_TABLE): # double-check we are really going to delete all the owners of this schema element cr.execute("""SELECT id from ir_model_data where name = %s and res_id IS NULL""", (data.name,)) external_ids = [x[0] for x in cr.fetchall()] @@ -866,16 +864,6 @@ class ir_model_data(osv.osv): # as installed modules have defined this element we must not delete it! continue - if name.startswith(EXT_ID_PREFIX_FK): - name = name[len(EXT_ID_PREFIX_FK):] - # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it) - cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid) - WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table)) - if cr.fetchone(): - cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) - _logger.info('Dropped FK CONSTRAINT %s@%s', name, model) - continue - if name.startswith(EXT_ID_PREFIX_M2M_TABLE): name = name[len(EXT_ID_PREFIX_M2M_TABLE):] cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,)) @@ -883,16 +871,6 @@ class ir_model_data(osv.osv): to_drop_table.append(name) continue - if name.startswith(EXT_ID_PREFIX_CONSTRAINT): - name = name[len(EXT_ID_PREFIX_CONSTRAINT):] - # test if constraint exists - cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid) - WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table)) - if cr.fetchone(): - cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) - _logger.info('Dropped CONSTRAINT %s@%s', name, model) - continue - pair_to_unlink = (model, res_id) if pair_to_unlink not in to_unlink: to_unlink.append(pair_to_unlink) diff --git a/openerp/addons/base/ir/ir_model_constraint.py b/openerp/addons/base/ir/ir_model_constraint.py index 790b598763a..620edb32363 100644 --- a/openerp/addons/base/ir/ir_model_constraint.py +++ b/openerp/addons/base/ir/ir_model_constraint.py @@ -1,8 +1,12 @@ +import logging import openerp +from openerp import SUPERUSER_ID from openerp.osv import fields from openerp.osv.orm import Model +_logger = logging.getLogger(__name__) + class ir_model_constraint(Model): """ This model tracks PostgreSQL foreign keys and constraints used by OpenERP @@ -33,7 +37,7 @@ class ir_model_constraint(Model): Delete PostgreSQL foreign keys and constraints tracked by this model. """ - if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"): + if uid != SUPERUSER_ID and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"): raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module'))) context = dict(context or {}) @@ -41,10 +45,11 @@ class ir_model_constraint(Model): ids_set = set(ids) ids.sort() ids.reverse() + to_unlink = [] for data in self.browse(cr, uid, ids, context): - model = data.model + model = data.model.name model_obj = self.pool.get(model) - name = tools.ustr(data.name) + name = openerp.tools.ustr(data.name) typ = data.type # double-check we are really going to delete all the owners of this schema element @@ -52,20 +57,25 @@ class ir_model_constraint(Model): external_ids = [x[0] for x in cr.fetchall()] if (set(external_ids)-ids_set): # as installed modules have defined this element we must not delete it! - pass + continue - elif typ == 'f': + if typ == 'f': # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it) cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid) WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table)) if cr.fetchone(): + print '>>> ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name) cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) _logger.info('Dropped FK CONSTRAINT %s@%s', name, model) - elif typ == 'u': + if typ == 'u': # test if constraint exists cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid) WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table)) if cr.fetchone(): + print '>>> ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name) cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) _logger.info('Dropped CONSTRAINT %s@%s', name, model) + + to_unlink.append(data.id) + self.unlink(cr, uid, to_unlink, context) diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index e04e4e4a02b..371bfb1b93c 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -364,7 +364,10 @@ class module(osv.osv): including the deletion of all database structures created by the module: tables, columns, constraints, etc.""" ir_model_data = self.pool.get('ir.model.data') + ir_model_constraint = self.pool.get('ir.model.constraint') modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)] + constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove)]) + ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context) data_ids = ir_model_data.search(cr, uid, [('module', 'in', modules_to_remove)]) ir_model_data._module_data_uninstall(cr, uid, data_ids, context) ir_model_data.unlink(cr, uid, data_ids, context) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index be55c1d305d..64c627849ac 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -71,9 +71,7 @@ _schema = logging.getLogger(__name__ + '.schema') from openerp.tools import SKIPPED_ELEMENT_TYPES # Prefixes for external IDs of schema elements -EXT_ID_PREFIX_FK = "_foreign_key_" EXT_ID_PREFIX_M2M_TABLE = "_m2m_rel_table_" -EXT_ID_PREFIX_CONSTRAINT = "_constraint_" regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I) regex_object_name = re.compile(r'^[a-z0-9_.]+$') @@ -3108,7 +3106,6 @@ class BaseModel(object): """ Create the foreign keys recorded by _auto_init. """ for t, k, r, d in self._foreign_keys: cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d)) - self._make_ext_id(cr, "%s%s_%s_fkey" % (EXT_ID_PREFIX_FK, t, k)) self._save_constraint(cr, "%s_%s_fkey" % (t, k), 'f') cr.commit() del self._foreign_keys @@ -3234,7 +3231,7 @@ class BaseModel(object): for (key, con, _) in self._sql_constraints: conname = '%s_%s' % (self._table, key) - self._make_ext_id(cr, EXT_ID_PREFIX_CONSTRAINT + conname) + self._save_constraint(cr, conname, 'u') cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,)) existing_constraints = cr.dictfetchall() sql_actions = { diff --git a/openerp/tests/addons/test_uninstall/models.py b/openerp/tests/addons/test_uninstall/models.py index 645853cc2f8..335aedc941a 100644 --- a/openerp/tests/addons/test_uninstall/models.py +++ b/openerp/tests/addons/test_uninstall/models.py @@ -1,11 +1,18 @@ # -*- coding: utf-8 -*- import openerp +from openerp.osv import fields from openerp.osv.orm import Model class test_uninstall_model(Model): _name = 'test_uninstall.model' _columns = { + 'name': fields.char('Name', size=64), + 'ref': fields.many2one('res.users', string='User'), } + _sql_constraints = [ + ('name_uniq', 'unique (name)', 'Each name must be unique.') + ] + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/t.py b/t.py index f5497943a59..7fc33f7d281 100644 --- a/t.py +++ b/t.py @@ -1,18 +1,80 @@ import openerp +from openerp import SUPERUSER_ID from openerp.osv import fields from openerp.osv.orm import Model conf = openerp.tools.config +# TODO Exception handling (especially on cursors). + +def get_registry(database_name): + registry = openerp.modules.registry.RegistryManager.get(database_name) + return registry + +def reload_registry(database_name): + openerp.modules.registry.RegistryManager.new( + database_name, update_module=True) + +def search_registry(database_name, model_name, domain): + registry = get_registry(database_name) + cr = registry.db.cursor() + model = registry.get(model_name) + record_ids = model.search(cr, SUPERUSER_ID, + domain, {}) + cr.close() + return record_ids + +def install_module(database_name, module_name): + registry = get_registry(database_name) + ir_module_module = registry.get('ir.module.module') + cr = registry.db.cursor() + module_ids = ir_module_module.search(cr, SUPERUSER_ID, + [('name', '=', module_name)], {}) + assert len(module_ids) == 1 + ir_module_module.button_install(cr, SUPERUSER_ID, module_ids, {}) + cr.commit() + cr.close() + reload_registry(database_name) + +def uninstall_module(database_name, module_name): + registry = get_registry(database_name) + ir_module_module = registry.get('ir.module.module') + cr = registry.db.cursor() + module_ids = ir_module_module.search(cr, SUPERUSER_ID, + [('name', '=', module_name)], {}) + assert len(module_ids) == 1 + ir_module_module.button_uninstall(cr, SUPERUSER_ID, module_ids, {}) + cr.commit() + cr.close() + reload_registry(database_name) + if __name__ == '__main__': openerp.netsvc.init_logger() - conf['addons_path'] = './openerp/tests/addons' - conf['init'] = {'test_uninstall': 1} - registry = openerp.modules.registry.RegistryManager.new('xx', update_module=True) + conf['addons_path'] = './openerp/tests/addons,../../addons/trunk,../../web/trunk/addons' + + install_module('xx', 'test_uninstall') + registry = get_registry('xx') + assert registry.get('test_uninstall.model') + + assert search_registry('xx', 'ir.model.data', + [('module', '=', 'test_uninstall')]) + + assert search_registry('xx', 'ir.model.fields', + [('model', '=', 'test_uninstall.model')]) + + uninstall_module('xx', 'test_uninstall') + registry = get_registry('xx') + assert not registry.get('test_uninstall.model') + + assert not search_registry('xx', 'ir.model.data', + [('module', '=', 'test_uninstall')]) + + assert not search_registry('xx', 'ir.model.fields', + [('model', '=', 'test_uninstall.model')]) + ir_model_constraint = registry.get('ir.model.constraint') - print ir_model_constraint cr = registry.db.cursor() - ids = ir_model_constraint.search(cr, openerp.SUPERUSER_ID, [], {}) - print ir_model_constraint.browse(cr, openerp.SUPERUSER_ID, ids, {}) + ids = ir_model_constraint.search(cr, SUPERUSER_ID, [], {}) + #print ir_model_constraint.browse(cr, SUPERUSER_ID, ids, {}) cr.close() ##################################################################### @@ -33,8 +95,8 @@ MY_MODULE = { 'depends': ['base'], } -def create_virtual_module(db_name, module_name, info): - registry = openerp.modules.registry.RegistryManager.get(db_name) +def create_virtual_module(database_name, module_name, info): + registry = get_registry(database_name) cr = registry.db.cursor() cr.execute("""SELECT 1 FROM ir_module_module WHERE name=%s""", (module_name,)) From eadd5e0a0a6aa8babfbedce91f28f9701c2812ea Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Jun 2012 17:34:49 +0200 Subject: [PATCH 03/30] [IMP] uninstall: use a dedicated table for many2many relationships instead of overlaoding ir.model.data. bzr revid: vmt@openerp.com-20120601153449-uj2sl7qjzz3s7xnj --- openerp/addons/base/base.sql | 20 ++++++++++++--- openerp/addons/base/ir/__init__.py | 1 + openerp/addons/base/ir/ir_model.py | 23 +---------------- openerp/addons/base/ir/ir_model_constraint.py | 2 -- openerp/addons/base/module/module.py | 3 +++ openerp/osv/orm.py | 25 +++++++++++-------- openerp/tests/addons/test_uninstall/models.py | 1 + 7 files changed, 38 insertions(+), 37 deletions(-) diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql index a227007353c..0a8f50c24bf 100644 --- a/openerp/addons/base/base.sql +++ b/openerp/addons/base/base.sql @@ -347,9 +347,8 @@ CREATE TABLE ir_model_data ( res_id integer, primary key(id) ); --- Records foreign keys and constraints --- installed by a module (so they can be removed them when the module is --- uninstalled).: +-- Records foreign keys and constraints installed by a module (so they can be +-- removed them when the module is uninstalled): -- - for a foreign key: type 'f', -- - for a constraint: type 'u'. CREATE TABLE ir_model_constraint ( @@ -366,6 +365,21 @@ CREATE TABLE ir_model_constraint ( name character varying(128) NOT NULL ); +-- Records relation tables (i.e. implementing many2many) installed by a module +-- (so they can be removed them when the module is uninstalled). +CREATE TABLE ir_model_relation ( + id serial NOT NULL, + create_uid integer, + create_date timestamp without time zone, + write_date timestamp without time zone, + write_uid integer, + date_init timestamp without time zone, + 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 +); + --------------------------------- -- Users --------------------------------- diff --git a/openerp/addons/base/ir/__init__.py b/openerp/addons/base/ir/__init__.py index f4605d1615f..bc4f3eecfbf 100644 --- a/openerp/addons/base/ir/__init__.py +++ b/openerp/addons/base/ir/__init__.py @@ -21,6 +21,7 @@ import ir_model import ir_model_constraint +import ir_model_relation import ir_sequence import ir_needaction import ir_ui_menu diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index 174f52b1944..231b19bb3f8 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -29,7 +29,7 @@ from openerp import netsvc, pooler, tools from openerp.tools.safe_eval import safe_eval as eval from openerp.tools import config from openerp.tools.translate import _ -from openerp.osv.orm import except_orm, browse_record, EXT_ID_PREFIX_M2M_TABLE +from openerp.osv.orm import except_orm, browse_record _logger = logging.getLogger(__name__) @@ -847,7 +847,6 @@ class ir_model_data(osv.osv): ids_set = set(ids) wkf_todo = [] to_unlink = [] - to_drop_table = [] ids.sort() ids.reverse() for data in self.browse(cr, uid, ids, context): @@ -856,21 +855,6 @@ class ir_model_data(osv.osv): model_obj = self.pool.get(model) name = tools.ustr(data.name) - if name.startswith(EXT_ID_PREFIX_M2M_TABLE): - # double-check we are really going to delete all the owners of this schema element - cr.execute("""SELECT id from ir_model_data where name = %s and res_id IS NULL""", (data.name,)) - external_ids = [x[0] for x in cr.fetchall()] - if (set(external_ids)-ids_set): - # as installed modules have defined this element we must not delete it! - continue - - if name.startswith(EXT_ID_PREFIX_M2M_TABLE): - name = name[len(EXT_ID_PREFIX_M2M_TABLE):] - cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,)) - if cr.fetchone() and not name in to_drop_table: - to_drop_table.append(name) - continue - pair_to_unlink = (model, res_id) if pair_to_unlink not in to_unlink: to_unlink.append(pair_to_unlink) @@ -890,11 +874,6 @@ class ir_model_data(osv.osv): except: _logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model) - # drop m2m relation tables - for table in to_drop_table: - cr.execute('DROP TABLE %s CASCADE'% (table),) - _logger.info('Dropped table %s', table) - def unlink_if_refcount(to_unlink): for model, res_id in to_unlink: external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)]) diff --git a/openerp/addons/base/ir/ir_model_constraint.py b/openerp/addons/base/ir/ir_model_constraint.py index 620edb32363..912284a8216 100644 --- a/openerp/addons/base/ir/ir_model_constraint.py +++ b/openerp/addons/base/ir/ir_model_constraint.py @@ -64,7 +64,6 @@ class ir_model_constraint(Model): cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid) WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table)) if cr.fetchone(): - print '>>> ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name) cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) _logger.info('Dropped FK CONSTRAINT %s@%s', name, model) @@ -73,7 +72,6 @@ class ir_model_constraint(Model): cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid) WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table)) if cr.fetchone(): - print '>>> ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name) cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) _logger.info('Dropped CONSTRAINT %s@%s', name, model) diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index 371bfb1b93c..20151946ed0 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -365,9 +365,12 @@ class module(osv.osv): tables, columns, constraints, etc.""" ir_model_data = self.pool.get('ir.model.data') ir_model_constraint = self.pool.get('ir.model.constraint') + ir_model_relation = self.pool.get('ir.model.relation') modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)] constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove)]) ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context) + relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove)]) + ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context) data_ids = ir_model_data.search(cr, uid, [('module', 'in', modules_to_remove)]) ir_model_data._module_data_uninstall(cr, uid, data_ids, context) ir_model_data.unlink(cr, uid, data_ids, context) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 64c627849ac..51485b83266 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -70,9 +70,6 @@ _schema = logging.getLogger(__name__ + '.schema') # List of etree._Element subclasses that we choose to ignore when parsing XML. from openerp.tools import SKIPPED_ELEMENT_TYPES -# Prefixes for external IDs of schema elements -EXT_ID_PREFIX_M2M_TABLE = "_m2m_rel_table_" - regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I) regex_object_name = re.compile(r'^[a-z0-9_.]+$') @@ -2743,6 +2740,7 @@ class BaseModel(object): self._table, column['attname']) def _save_constraint(self, cr, constraint_name, type): + """ TODO """ assert type in ('f', 'u') cr.execute(""" SELECT 1 FROM ir_model_constraint, ir_module_module @@ -2759,13 +2757,20 @@ class BaseModel(object): (SELECT id FROM ir_model WHERE model=%s), %s)""", (constraint_name, self._module, self._name, type)) - # quick creation of ir.model.data entry to make uninstall of schema elements easier - def _make_ext_id(self, cr, ext_id): - cr.execute('SELECT 1 FROM ir_model_data WHERE name=%s AND module=%s', (ext_id, self._module)) + def _save_relation_table(self, cr, relation_table): + """ TODO """ + cr.execute(""" + SELECT 1 FROM ir_model_relation, ir_module_module + WHERE ir_model_relation.module=ir_module_module.id + AND ir_model_relation.name=%s + AND ir_module_module.name=%s + """, (relation_table, self._module)) if not cr.rowcount: - cr.execute("""INSERT INTO ir_model_data (name,date_init,date_update,module,model) - VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC', %s, %s)""", - (ext_id, self._module, self._name)) + cr.execute("""INSERT INTO ir_model_relation (name, date_init, date_update, module, model) + VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC', + (SELECT id FROM ir_module_module WHERE name=%s), + (SELECT id FROM ir_model WHERE model=%s))""", + (relation_table, self._module, self._name)) # checked version: for direct m2o starting from `self` def _m2o_add_foreign_key_checked(self, source_field, dest_model, ondelete): @@ -3195,7 +3200,7 @@ class BaseModel(object): def _m2m_raise_or_create_relation(self, cr, f): m2m_tbl, col1, col2 = f._sql_names(self) - self._make_ext_id(cr, EXT_ID_PREFIX_M2M_TABLE + m2m_tbl) + self._save_relation_table(cr, m2m_tbl) cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (m2m_tbl,)) if not cr.dictfetchall(): if not self.pool.get(f._obj): diff --git a/openerp/tests/addons/test_uninstall/models.py b/openerp/tests/addons/test_uninstall/models.py index 335aedc941a..d7211dc2130 100644 --- a/openerp/tests/addons/test_uninstall/models.py +++ b/openerp/tests/addons/test_uninstall/models.py @@ -9,6 +9,7 @@ class test_uninstall_model(Model): _columns = { 'name': fields.char('Name', size=64), 'ref': fields.many2one('res.users', string='User'), + 'rel': fields.many2many('res.users', string='Users'), } _sql_constraints = [ From 9f46071958135b43b752246219503e30337749d0 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Jun 2012 17:45:44 +0200 Subject: [PATCH 04/30] [IMP] uninstall: added some docstrings. bzr revid: vmt@openerp.com-20120601154544-e9a9cox7fcyd411m --- openerp/osv/orm.py | 11 +++++++++-- openerp/tests/addons/test_uninstall/models.py | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 51485b83266..5fe926604d3 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2740,7 +2740,11 @@ class BaseModel(object): self._table, column['attname']) def _save_constraint(self, cr, constraint_name, type): - """ TODO """ + """ + Record the creation of a constraint for this model, to make it possible + to delete it later when the module is uninstalled. Type can be either + 'f' or 'u' depending on the constraing being a foreign key or not. + """ assert type in ('f', 'u') cr.execute(""" SELECT 1 FROM ir_model_constraint, ir_module_module @@ -2758,7 +2762,10 @@ class BaseModel(object): (constraint_name, self._module, self._name, type)) def _save_relation_table(self, cr, relation_table): - """ TODO """ + """ + Record the creation of a many2many for this model, to make it possible + to delete it later when the module is uninstalled. + """ cr.execute(""" SELECT 1 FROM ir_model_relation, ir_module_module WHERE ir_model_relation.module=ir_module_module.id diff --git a/openerp/tests/addons/test_uninstall/models.py b/openerp/tests/addons/test_uninstall/models.py index d7211dc2130..96a5800917d 100644 --- a/openerp/tests/addons/test_uninstall/models.py +++ b/openerp/tests/addons/test_uninstall/models.py @@ -4,6 +4,10 @@ from openerp.osv import fields from openerp.osv.orm import Model class test_uninstall_model(Model): + """ + This model uses different types of columns to make it possible to test + the uninstall feature of OpenERP. + """ _name = 'test_uninstall.model' _columns = { From 15a45794763e547b13c2eb97370557a8e4b36ff5 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Jun 2012 17:46:47 +0200 Subject: [PATCH 05/30] [FIX] uninstall: forgot to add a file to bzr. bzr revid: vmt@openerp.com-20120601154647-qhidwpw2bb3mlezw --- openerp/addons/base/ir/ir_model_relation.py | 65 +++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 openerp/addons/base/ir/ir_model_relation.py diff --git a/openerp/addons/base/ir/ir_model_relation.py b/openerp/addons/base/ir/ir_model_relation.py new file mode 100644 index 00000000000..2d147f5ee6e --- /dev/null +++ b/openerp/addons/base/ir/ir_model_relation.py @@ -0,0 +1,65 @@ +import logging + +import openerp +from openerp import SUPERUSER_ID +from openerp.osv import fields +from openerp.osv.orm import Model + +_logger = logging.getLogger(__name__) + +class ir_model_relation(Model): + """ + This model tracks PostgreSQL tables used to implement OpenERP many2many + relations. + """ + _name = 'ir.model.relation' + _columns = { + 'name': fields.char('Relation Name', required=True, size=128, select=1, + help="PostgreSQL table name implementing a many2many relation."), + 'model': fields.many2one('ir.model', string='Model Name', + required=True, select=1), + 'module': fields.many2one('ir.module.module', string='Module Name', + required=True, select=1), + 'date_update': fields.datetime('Update Date'), + 'date_init': fields.datetime('Initialization Date') + } + + def _module_data_uninstall(self, cr, uid, ids, context=None): + """ + Delete PostgreSQL many2many relations tracked by this model. + """ + + if uid != SUPERUSER_ID and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"): + raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module'))) + + ids_set = set(ids) + to_drop_table = [] + ids.sort() + ids.reverse() + to_unlink = [] + for data in self.browse(cr, uid, ids, context): + model = data.model + model_obj = self.pool.get(model) + name = openerp.tools.ustr(data.name) + + # double-check we are really going to delete all the owners of this schema element + cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,)) + external_ids = [x[0] for x in cr.fetchall()] + if (set(external_ids)-ids_set): + # as installed modules have defined this element we must not delete it! + continue + + cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,)) + if cr.fetchone() and not name in to_drop_table: + to_drop_table.append(name) + + to_unlink.append(data.id) + + self.unlink(cr, uid, to_unlink, context) + + # drop m2m relation tables + for table in to_drop_table: + cr.execute('DROP TABLE %s CASCADE'% (table),) + _logger.info('Dropped table %s', table) + + cr.commit() From 30dfa719977c832f0d39c67163a973eefd492f37 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Jun 2012 18:03:01 +0200 Subject: [PATCH 06/30] [IMP] uninstall: added views for ir_model_constraint (breaking a test by the way). bzr revid: vmt@openerp.com-20120601160301-b4xygkob9wuxllk9 --- openerp/addons/base/ir/ir.xml | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml index 837374d371b..a63b8477bbc 100644 --- a/openerp/addons/base/ir/ir.xml +++ b/openerp/addons/base/ir/ir.xml @@ -1128,6 +1128,36 @@ + + ir.model.constraint.form + ir.model.constraint + form + +
+ + + + + + + + + +
+ + + ir.model.constraint.list + ir.model.constraint + tree + + + + + + + + + Models @@ -1157,6 +1187,15 @@ + + Model Constraints + ir.model.constraint + form + + + + From 71211720a0825a8e13e4bbf14ee0a4c31899b3be Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Mon, 4 Jun 2012 10:22:06 +0200 Subject: [PATCH 07/30] [IMP] uninstall: added views for ir.model.relation. bzr revid: vmt@openerp.com-20120604082206-h6ua22byq3otx53p --- openerp/addons/base/ir/ir.xml | 40 ++++++++++++++++++- openerp/addons/base/ir/ir_model_constraint.py | 4 +- openerp/addons/base/ir/ir_model_relation.py | 4 +- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml index a63b8477bbc..b32ba9e8a5a 100644 --- a/openerp/addons/base/ir/ir.xml +++ b/openerp/addons/base/ir/ir.xml @@ -1159,6 +1159,35 @@ + + ir.model.relation.form + ir.model.relation + form + +
+ + + + + + + + +
+ + + ir.model.relation.list + ir.model.relation + tree + + + + + + + + + Models ir.model @@ -1193,7 +1222,16 @@ form - + + + ManyToMany Relations + ir.model.relation + form + + + diff --git a/openerp/addons/base/ir/ir_model_constraint.py b/openerp/addons/base/ir/ir_model_constraint.py index 912284a8216..05d47df462d 100644 --- a/openerp/addons/base/ir/ir_model_constraint.py +++ b/openerp/addons/base/ir/ir_model_constraint.py @@ -16,9 +16,9 @@ class ir_model_constraint(Model): _columns = { 'name': fields.char('Constraint', required=True, size=128, select=1, help="PostgreSQL constraint or foreign key name."), - 'model': fields.many2one('ir.model', string='Model Name', + 'model': fields.many2one('ir.model', string='Model', required=True, select=1), - 'module': fields.many2one('ir.module.module', string='Module Name', + 'module': fields.many2one('ir.module.module', string='Module', required=True, select=1), 'type': fields.char('Constraint Type', required=True, size=1, select=1, help="Type of the constraint: `f` for a foreign key, " diff --git a/openerp/addons/base/ir/ir_model_relation.py b/openerp/addons/base/ir/ir_model_relation.py index 2d147f5ee6e..eede6eae44f 100644 --- a/openerp/addons/base/ir/ir_model_relation.py +++ b/openerp/addons/base/ir/ir_model_relation.py @@ -16,9 +16,9 @@ class ir_model_relation(Model): _columns = { 'name': fields.char('Relation Name', required=True, size=128, select=1, help="PostgreSQL table name implementing a many2many relation."), - 'model': fields.many2one('ir.model', string='Model Name', + 'model': fields.many2one('ir.model', string='Model', required=True, select=1), - 'module': fields.many2one('ir.module.module', string='Module Name', + 'module': fields.many2one('ir.module.module', string='Module', required=True, select=1), 'date_update': fields.datetime('Update Date'), 'date_init': fields.datetime('Initialization Date') From fa4c2a40c0659b2aef895addbbc56ca900a09fa6 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Mon, 4 Jun 2012 12:44:04 +0200 Subject: [PATCH 08/30] [IMP] uninstall: moving tests to the openerp test framework (i.e. under openerp/tests/). bzr revid: vmt@openerp.com-20120604104404-zibru6794ma1996x --- openerp/tests/__init__.py | 1 + openerp/tests/test_uninstall.py | 81 +++++++++++++++++++++++++++++++++ t.py | 77 ++++++++++++++++--------------- 3 files changed, 123 insertions(+), 36 deletions(-) create mode 100644 openerp/tests/test_uninstall.py diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index 5fccb07a082..913c5c759c9 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -11,6 +11,7 @@ See the :ref:`test-framework` section in the :ref:`features` list. import test_expression import test_ir_sequence import test_orm +import test_uninstall fast_suite = [ test_ir_sequence, diff --git a/openerp/tests/test_uninstall.py b/openerp/tests/test_uninstall.py new file mode 100644 index 00000000000..4cc396e3d3c --- /dev/null +++ b/openerp/tests/test_uninstall.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Run with one of these commands: +# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \ +# OPENERP_DATABASE=yy PYTHONPATH=. python tests/test_ir_sequence.py +# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \ +# OPENERP_DATABASE=yy nosetests tests/test_ir_sequence.py +# > OPENERP_ADDONS_PATH='../../../addons/trunk' OPENERP_PORT=8069 \ +# OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence +# This assume an existing database. +import psycopg2 +import unittest2 + +import openerp +from openerp import SUPERUSER_ID +import common + +DB = common.DB +ADMIN_USER_ID = common.ADMIN_USER_ID + +def registry(model): + return openerp.modules.registry.RegistryManager.get(DB)[model] + +def cursor(): + return openerp.modules.registry.RegistryManager.get(DB).db.cursor() + +def get_module(module_name): + registry = openerp.modules.registry.RegistryManager.get(DB) + return registry.get(module_name) + +def reload_registry(): + openerp.modules.registry.RegistryManager.new( + DB, update_module=True) + +def search_registry(model_name, domain): + cr = cursor() + model = registry(model_name) + record_ids = model.search(cr, SUPERUSER_ID, domain, {}) + cr.close() + return record_ids + +def install_module(module_name): + ir_module_module = registry('ir.module.module') + cr = cursor() + module_ids = ir_module_module.search(cr, SUPERUSER_ID, + [('name', '=', module_name)], {}) + assert len(module_ids) == 1 + ir_module_module.button_install(cr, SUPERUSER_ID, module_ids, {}) + cr.commit() + cr.close() + reload_registry() + +def uninstall_module(module_name): + ir_module_module = registry('ir.module.module') + cr = cursor() + module_ids = ir_module_module.search(cr, SUPERUSER_ID, + [('name', '=', module_name)], {}) + assert len(module_ids) == 1 + ir_module_module.button_uninstall(cr, SUPERUSER_ID, module_ids, {}) + cr.commit() + cr.close() + reload_registry() + +class test_uninstall(unittest2.TestCase): + """ Test install/uninstall of a test module. """ + + def test_00_nothing(self): + """ Test the assumption the test module is not installed yet. """ + assert not get_module('test_uninstall.model') + + assert not search_registry('ir.model.data', + [('module', '=', 'test_uninstall')]) + + assert not search_registry('ir.model.fields', + [('model', '=', 'test_uninstall.model')]) + + +if __name__ == '__main__': + unittest2.main() + + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/t.py b/t.py index 7fc33f7d281..8529145d4fa 100644 --- a/t.py +++ b/t.py @@ -1,3 +1,5 @@ +import sys + import openerp from openerp import SUPERUSER_ID from openerp.osv import fields @@ -6,73 +8,77 @@ conf = openerp.tools.config # TODO Exception handling (especially on cursors). -def get_registry(database_name): - registry = openerp.modules.registry.RegistryManager.get(database_name) - return registry +DB = None -def reload_registry(database_name): +def registry(model_name): + return openerp.modules.registry.RegistryManager.get(DB)[model_name] + +def cursor(): + return openerp.modules.registry.RegistryManager.get(DB).db.cursor() + +def get_module(module_name): + registry = openerp.modules.registry.RegistryManager.get(DB) + return registry.get(module_name) + +def reload_registry(): openerp.modules.registry.RegistryManager.new( - database_name, update_module=True) + DB, update_module=True) -def search_registry(database_name, model_name, domain): - registry = get_registry(database_name) - cr = registry.db.cursor() - model = registry.get(model_name) - record_ids = model.search(cr, SUPERUSER_ID, - domain, {}) +def search_registry(model_name, domain): + cr = cursor() + model = registry(model_name) + record_ids = model.search(cr, SUPERUSER_ID, domain, {}) cr.close() return record_ids -def install_module(database_name, module_name): - registry = get_registry(database_name) - ir_module_module = registry.get('ir.module.module') - cr = registry.db.cursor() +def install_module(module_name): + ir_module_module = registry('ir.module.module') + cr = cursor() module_ids = ir_module_module.search(cr, SUPERUSER_ID, [('name', '=', module_name)], {}) assert len(module_ids) == 1 ir_module_module.button_install(cr, SUPERUSER_ID, module_ids, {}) cr.commit() cr.close() - reload_registry(database_name) + reload_registry() -def uninstall_module(database_name, module_name): - registry = get_registry(database_name) - ir_module_module = registry.get('ir.module.module') - cr = registry.db.cursor() +def uninstall_module(module_name): + ir_module_module = registry('ir.module.module') + cr = cursor() module_ids = ir_module_module.search(cr, SUPERUSER_ID, [('name', '=', module_name)], {}) assert len(module_ids) == 1 ir_module_module.button_uninstall(cr, SUPERUSER_ID, module_ids, {}) cr.commit() cr.close() - reload_registry(database_name) + reload_registry() if __name__ == '__main__': + assert len(sys.argv) == 2 + DB = sys.argv[1] openerp.netsvc.init_logger() conf['addons_path'] = './openerp/tests/addons,../../addons/trunk,../../web/trunk/addons' - install_module('xx', 'test_uninstall') - registry = get_registry('xx') - assert registry.get('test_uninstall.model') + install_module('test_uninstall') + assert get_module('test_uninstall.model') - assert search_registry('xx', 'ir.model.data', + assert search_registry('ir.model.data', [('module', '=', 'test_uninstall')]) - assert search_registry('xx', 'ir.model.fields', + assert search_registry('ir.model.fields', [('model', '=', 'test_uninstall.model')]) - uninstall_module('xx', 'test_uninstall') - registry = get_registry('xx') - assert not registry.get('test_uninstall.model') + uninstall_module('test_uninstall') + assert not get_module('test_uninstall.model') - assert not search_registry('xx', 'ir.model.data', + assert not search_registry('ir.model.data', [('module', '=', 'test_uninstall')]) - assert not search_registry('xx', 'ir.model.fields', + assert not search_registry('ir.model.fields', [('model', '=', 'test_uninstall.model')]) - ir_model_constraint = registry.get('ir.model.constraint') - cr = registry.db.cursor() + ir_model_constraint = registry('ir.model.constraint') + cr = cursor() ids = ir_model_constraint.search(cr, SUPERUSER_ID, [], {}) #print ir_model_constraint.browse(cr, SUPERUSER_ID, ids, {}) cr.close() @@ -95,9 +101,8 @@ MY_MODULE = { 'depends': ['base'], } -def create_virtual_module(database_name, module_name, info): - registry = get_registry(database_name) - cr = registry.db.cursor() +def create_virtual_module(module_name, info): + cr = cursor() cr.execute("""SELECT 1 FROM ir_module_module WHERE name=%s""", (module_name,)) if cr.fetchone(): return From 768aaa8854e8d71c732f6cd0ecde158ea48c6af9 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Mon, 4 Jun 2012 14:31:12 +0200 Subject: [PATCH 09/30] [IMP] uninstall: moved tests from t.py to openerp.tests. bzr revid: vmt@openerp.com-20120604123112-stegmdko2lakdv6f --- openerp/tests/test_uninstall.py | 22 +++++- t.py | 133 -------------------------------- 2 files changed, 19 insertions(+), 136 deletions(-) delete mode 100644 t.py diff --git a/openerp/tests/test_uninstall.py b/openerp/tests/test_uninstall.py index 4cc396e3d3c..514042188a9 100644 --- a/openerp/tests/test_uninstall.py +++ b/openerp/tests/test_uninstall.py @@ -61,10 +61,25 @@ def uninstall_module(module_name): reload_registry() class test_uninstall(unittest2.TestCase): - """ Test install/uninstall of a test module. """ + """ + Test the install/uninstall of a test module. The module is available in + `openerp.tests` which should be present in the addons-path. + """ - def test_00_nothing(self): - """ Test the assumption the test module is not installed yet. """ + def test_01_install(self): + """ Check a few things showing the module is installed. """ + install_module('test_uninstall') + assert get_module('test_uninstall.model') + + assert search_registry('ir.model.data', + [('module', '=', 'test_uninstall')]) + + assert search_registry('ir.model.fields', + [('model', '=', 'test_uninstall.model')]) + + def test_02_install(self): + """ Check a few things showing the module is uninstalled. """ + uninstall_module('test_uninstall') assert not get_module('test_uninstall.model') assert not search_registry('ir.model.data', @@ -74,6 +89,7 @@ class test_uninstall(unittest2.TestCase): [('model', '=', 'test_uninstall.model')]) + if __name__ == '__main__': unittest2.main() diff --git a/t.py b/t.py deleted file mode 100644 index 8529145d4fa..00000000000 --- a/t.py +++ /dev/null @@ -1,133 +0,0 @@ -import sys - -import openerp -from openerp import SUPERUSER_ID -from openerp.osv import fields -from openerp.osv.orm import Model -conf = openerp.tools.config - -# TODO Exception handling (especially on cursors). - -DB = None - -def registry(model_name): - return openerp.modules.registry.RegistryManager.get(DB)[model_name] - -def cursor(): - return openerp.modules.registry.RegistryManager.get(DB).db.cursor() - -def get_module(module_name): - registry = openerp.modules.registry.RegistryManager.get(DB) - return registry.get(module_name) - -def reload_registry(): - openerp.modules.registry.RegistryManager.new( - DB, update_module=True) - -def search_registry(model_name, domain): - cr = cursor() - model = registry(model_name) - record_ids = model.search(cr, SUPERUSER_ID, domain, {}) - cr.close() - return record_ids - -def install_module(module_name): - ir_module_module = registry('ir.module.module') - cr = cursor() - module_ids = ir_module_module.search(cr, SUPERUSER_ID, - [('name', '=', module_name)], {}) - assert len(module_ids) == 1 - ir_module_module.button_install(cr, SUPERUSER_ID, module_ids, {}) - cr.commit() - cr.close() - reload_registry() - -def uninstall_module(module_name): - ir_module_module = registry('ir.module.module') - cr = cursor() - module_ids = ir_module_module.search(cr, SUPERUSER_ID, - [('name', '=', module_name)], {}) - assert len(module_ids) == 1 - ir_module_module.button_uninstall(cr, SUPERUSER_ID, module_ids, {}) - cr.commit() - cr.close() - reload_registry() - -if __name__ == '__main__': - assert len(sys.argv) == 2 - DB = sys.argv[1] - openerp.netsvc.init_logger() - conf['addons_path'] = './openerp/tests/addons,../../addons/trunk,../../web/trunk/addons' - - install_module('test_uninstall') - assert get_module('test_uninstall.model') - - assert search_registry('ir.model.data', - [('module', '=', 'test_uninstall')]) - - assert search_registry('ir.model.fields', - [('model', '=', 'test_uninstall.model')]) - - uninstall_module('test_uninstall') - assert not get_module('test_uninstall.model') - - assert not search_registry('ir.model.data', - [('module', '=', 'test_uninstall')]) - - assert not search_registry('ir.model.fields', - [('model', '=', 'test_uninstall.model')]) - - ir_model_constraint = registry('ir.model.constraint') - cr = cursor() - ids = ir_model_constraint.search(cr, SUPERUSER_ID, [], {}) - #print ir_model_constraint.browse(cr, SUPERUSER_ID, ids, {}) - cr.close() - -##################################################################### - -# Nice idea, but won't work without some more change to the framework (which -# expects everything on disk, maybe we can craft a zip file...). - -MY_MODULE = { - 'author': 'Jean Beauvoir', - 'website': 'http://www.youtube.com/watch?v=FeO5DfdZi7Y', - 'name': 'FEEL THE HEAT', - 'description': "Cobra's theme", - 'web': False, - 'license': 'WTFPL', - 'application': False, - 'icon': False, - 'sequence': 100, - 'depends': ['base'], -} - -def create_virtual_module(module_name, info): - cr = cursor() - - cr.execute("""SELECT 1 FROM ir_module_module WHERE name=%s""", (module_name,)) - if cr.fetchone(): return - - category_id = openerp.modules.db.create_categories(cr, ['Tests']) - cr.execute('INSERT INTO ir_module_module \ - (author, website, name, shortdesc, description, \ - category_id, auto_install, state, certificate, web, license, application, icon, sequence) \ - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id', ( - info['author'], - info['website'], module_name, info['name'], - info['description'], category_id, - True, 'to install', False, - info['web'], - info['license'], - info['application'], info['icon'], - info['sequence'])) - module_id = cr.fetchone()[0] - cr.execute('INSERT INTO ir_model_data \ - (name,model,module, res_id, noupdate) VALUES (%s,%s,%s,%s,%s)', ( - 'module_' + module_name, 'ir.module.module', 'base', module_id, True)) - dependencies = info['depends'] - for d in dependencies: - cr.execute('INSERT INTO ir_module_module_dependency \ - (module_id,name) VALUES (%s, %s)', (module_id, d)) - - cr.commit() - cr.close() From 77ec840cb00d9910cd7751fd17dda8f0806849b2 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 5 Jun 2012 11:52:10 +0200 Subject: [PATCH 10/30] [FIX] ir_values: fix a test where some value was hard-coded. bzr revid: vmt@openerp.com-20120605095210-0t5bmlyxrtnoqvr0 --- openerp/addons/base/tests/test_ir_values.py | 9 ++++++--- openerp/modules/module.py | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/openerp/addons/base/tests/test_ir_values.py b/openerp/addons/base/tests/test_ir_values.py index b7ea0107776..b306e1f0929 100644 --- a/openerp/addons/base/tests/test_ir_values.py +++ b/openerp/addons/base/tests/test_ir_values.py @@ -54,7 +54,10 @@ class test_ir_values(common.TransactionCase): ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True) ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action 2', ['unexisting_model'], 'ir.actions.act_window,11', isobject=True) ir_values.set(self.cr, self.uid, 'action', 'client_action_multi', 'Side Wizard', ['unexisting_model'], 'ir.actions.act_window,12', isobject=True) - ir_values.set(self.cr, self.uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,45', isobject=True) + report_ids = self.registry('ir.actions.report.xml').search(self.cr, self.uid, [], {}) + reports = self.registry('ir.actions.report.xml').browse(self.cr, self.uid, report_ids, {}) + report_id = [report.id for report in reports if not report.groups_id][0] # assume at least one + ir_values.set(self.cr, self.uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,%s'%report_id, isobject=True) ir_values.set(self.cr, self.uid, 'action', 'client_action_relate', 'Related Stuff', ['unexisting_model'], 'ir.actions.act_window,14', isobject=True) # Replace one action binding to set a new name. @@ -86,7 +89,7 @@ class test_ir_values(common.TransactionCase): assert len(actions) == 1, "Mismatching number of bound actions" assert len(actions[0]) == 3, "Malformed action definition" assert actions[0][1] == 'Nice Report', 'Bound action does not match definition' - assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 45, 'Bound action does not match definition' + assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == report_id, 'Bound action does not match definition' actions = ir_values.get(self.cr, self.uid, 'action', 'client_action_relate', ['unexisting_model']) assert len(actions) == 1, "Mismatching number of bound actions" @@ -96,4 +99,4 @@ class test_ir_values(common.TransactionCase): if __name__ == '__main__': - unittest2.main() \ No newline at end of file + unittest2.main() diff --git a/openerp/modules/module.py b/openerp/modules/module.py index e226b69a6fc..265a23190e5 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -493,7 +493,7 @@ def get_test_modules(module, submodule, explode): # It seems the module has no `tests` sub-module, no problem. pass else: - print 'Can not `import %s`.' % module + _logger.exception('Can not `import %s`.', module) return [] # Discover available test sub-modules. @@ -554,6 +554,7 @@ def run_unit_tests(module_name): """ import unittest2 ms = get_test_modules(module_name, '__fast_suite__', explode=False) + # TODO: No need to try again if the above call failed because of e.g. a syntax error. ms.extend(get_test_modules(module_name, '__sanity_checks__', explode=False)) suite = unittest2.TestSuite() for m in ms: From 83d799559c49852019381d0549c930882c0b11e7 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Thu, 7 Jun 2012 14:41:56 +0200 Subject: [PATCH 11/30] [FIX] uninstall: use ir_model.model instead of ir_model.name (the former is the model name while the later is not...). bzr revid: vmt@openerp.com-20120607124156-p4xupmdjoi2vgunt --- openerp/addons/base/ir/ir_model_constraint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_model_constraint.py b/openerp/addons/base/ir/ir_model_constraint.py index 05d47df462d..4a69bc2f0a7 100644 --- a/openerp/addons/base/ir/ir_model_constraint.py +++ b/openerp/addons/base/ir/ir_model_constraint.py @@ -47,7 +47,7 @@ class ir_model_constraint(Model): ids.reverse() to_unlink = [] for data in self.browse(cr, uid, ids, context): - model = data.model.name + model = data.model.model model_obj = self.pool.get(model) name = openerp.tools.ustr(data.name) typ = data.type From 67af184b8cc6f7088ea2d4981eb537c4fa9cde15 Mon Sep 17 00:00:00 2001 From: "Vidhin Mehta (OpenERP)" Date: Tue, 12 Jun 2012 17:32:05 +0530 Subject: [PATCH 12/30] [IMP]translate placeholders. bzr revid: vme@tinyerp.com-20120612120205-2ehz22nlhxsq3245 --- openerp/osv/orm.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 7d6947a6255..f773dba4f0a 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1762,6 +1762,10 @@ class BaseModel(object): trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('help')) if trans: node.set('help', trans) + if node.get('placeholder'): + trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('placeholder')) + if trans: + node.set('placeholder', trans) for f in node: if children or (node.tag == 'field' and f.tag in ('filter','separator')): From 2b74ff54c601b9f1387bd02b20517707e005fccf Mon Sep 17 00:00:00 2001 From: ggh-openerp Date: Thu, 14 Jun 2012 12:58:21 +0530 Subject: [PATCH 13/30] [IMP]add translation to placeholder. bzr revid: ggh@tinyerp.com-20120614072821-r9ilqly88rkp3f1v --- addons/web/static/src/xml/base.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index b90addb0a2c..2a55ca0cd4f 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1391,7 +1391,7 @@

Save current filter

-

+

@@ -1711,7 +1711,7 @@ - + From 7fc06fc81f7ab9efa6084e8ed708cc7a694527ec Mon Sep 17 00:00:00 2001 From: "Vidhin Mehta (OpenERP)" Date: Fri, 15 Jun 2012 12:41:06 +0530 Subject: [PATCH 14/30] [IMP]add placeholder attribute for translation in qweb. bzr revid: vme@tinyerp.com-20120615071106-hagp49jwh9ntz5z4 --- addons/web/static/src/js/coresetup.js | 2 +- addons/web/static/src/xml/base.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index 4defde54a3c..77355dd163a 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -497,7 +497,7 @@ instance.web.qweb.preprocess_node = function() { break; case 1: // Element - var attr, attrs = ['label', 'title', 'alt']; + var attr, attrs = ['label', 'title', 'alt', 'placeholder']; while (attr = attrs.pop()) { if (this.attributes[attr]) { this.attributes[attr] = instance.web._t(this.attributes[attr]); diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 2a55ca0cd4f..6f2a297b3d7 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1391,7 +1391,7 @@

Save current filter

-

+

@@ -1711,7 +1711,7 @@ - + From fef0b4f52b0bd3803b2ae5764146bc7381e280ef Mon Sep 17 00:00:00 2001 From: "Vidhin Mehta (OpenERP)" Date: Fri, 15 Jun 2012 13:04:32 +0530 Subject: [PATCH 15/30] [REM]forgot to remove t-att. bzr revid: vme@tinyerp.com-20120615073432-hymeixv752fxtkji --- addons/web/static/src/xml/base.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 6f2a297b3d7..b90addb0a2c 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1391,7 +1391,7 @@

Save current filter

-

+

@@ -1711,7 +1711,7 @@ - + From e76a800f90c74d86e16a6265e49569fc5f3e7a69 Mon Sep 17 00:00:00 2001 From: "Vidhin Mehta (OpenERP)" Date: Fri, 15 Jun 2012 14:20:27 +0530 Subject: [PATCH 16/30] [IMP]add attr in npybabel.py. bzr revid: vme@tinyerp.com-20120615085027-z08zieqvr0v8br4j --- npybabel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npybabel.py b/npybabel.py index 93ff5bcb9dc..3b776857282 100755 --- a/npybabel.py +++ b/npybabel.py @@ -107,7 +107,7 @@ def extract_qweb(fileobj, keywords, comment_tags, options): 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'): + for att in ('title', 'alt', 'label','placeholder'): if att in el.attrib: handle_text(el.attrib[att], el.sourceline) iter_elements(el) From 5053c1a66e78ba3e4ce51e9cd41c2d4c39d5136e Mon Sep 17 00:00:00 2001 From: "Vidhin Mehta (OpenERP)" Date: Fri, 15 Jun 2012 14:22:38 +0530 Subject: [PATCH 17/30] bzr revid: vme@tinyerp.com-20120615085238-d0vm6jr5t23k5jim --- npybabel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npybabel.py b/npybabel.py index 3b776857282..14ac115e338 100755 --- a/npybabel.py +++ b/npybabel.py @@ -107,7 +107,7 @@ def extract_qweb(fileobj, keywords, comment_tags, options): 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'): + for att in ('title', 'alt', 'label', 'placeholder'): if att in el.attrib: handle_text(el.attrib[att], el.sourceline) iter_elements(el) From 575e234cd41a163b55c26e87c9326d4fd95567a6 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 19 Jun 2012 17:16:26 +0200 Subject: [PATCH 18/30] [FIX] res.partner.category: the relation table name was manually chosen, which breaks under _inherit, so we use the automatic one. bzr revid: vmt@openerp.com-20120619151626-2xho611icmq7e1ky --- openerp/addons/base/ir/ir_model.py | 13 +++++++++++-- openerp/addons/base/module/module.py | 7 +------ openerp/addons/base/res/res_partner.py | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index f7b4bd15936..a3d1c7f8d78 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -836,7 +836,7 @@ class ir_model_data(osv.osv): cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name)) return True - def _module_data_uninstall(self, cr, uid, ids, context=None): + def _module_data_uninstall(self, cr, uid, modules_to_remove, context=None): """Deletes all the records referenced by the ir.model.data entries ``ids`` along with their corresponding database backed (including dropping tables, columns, FKs, etc, as long as there is no other @@ -847,6 +847,8 @@ class ir_model_data(osv.osv): This step is performed as part of the full uninstallation of a module. """ + ids = self.search(cr, uid, [('module', 'in', modules_to_remove)]) + if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"): raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module'))) @@ -888,7 +890,7 @@ class ir_model_data(osv.osv): external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)]) if (set(external_ids)-ids_set): # if other modules have defined this record, we must not delete it - return + continue _logger.info('Deleting %s@%s', res_id, model) try: self.pool.get(model).unlink(cr, uid, [res_id], context=context) @@ -900,11 +902,18 @@ class ir_model_data(osv.osv): if model not in ('ir.model','ir.model.fields')) unlink_if_refcount((model, res_id) for model, res_id in to_unlink if model == 'ir.model.fields') + + ir_model_relation = self.pool.get('ir.model.relation') + relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove)]) + ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context) + unlink_if_refcount((model, res_id) for model, res_id in to_unlink if model == 'ir.model') cr.commit() + self.unlink(cr, uid, ids, context) + def _process_end(self, cr, uid, modules): """ Clear records removed from updated module data. This method is called at the end of the module loading process. diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index cff3c73d721..392850f7b77 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -379,15 +379,10 @@ class module(osv.osv): tables, columns, constraints, etc.""" ir_model_data = self.pool.get('ir.model.data') ir_model_constraint = self.pool.get('ir.model.constraint') - ir_model_relation = self.pool.get('ir.model.relation') modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)] constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove)]) ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context) - relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove)]) - ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context) - data_ids = ir_model_data.search(cr, uid, [('module', 'in', modules_to_remove)]) - ir_model_data._module_data_uninstall(cr, uid, data_ids, context) - ir_model_data.unlink(cr, uid, data_ids, context) + ir_model_data._module_data_uninstall(cr, uid, modules_to_remove, context) self.write(cr, uid, ids, {'state': 'uninstalled'}) return True diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 1dbad434ed6..745910b569b 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -87,7 +87,7 @@ class res_partner_category(osv.osv): 'active' : fields.boolean('Active', help="The active field allows you to hide the category without removing it."), 'parent_left' : fields.integer('Left parent', select=True), 'parent_right' : fields.integer('Right parent', select=True), - 'partner_ids': fields.many2many('res.partner', 'res_partner_category_rel', 'category_id', 'partner_id', 'Partners'), + 'partner_ids': fields.many2many('res.partner', id1='category_id', id2='partner_id', string='Partners'), } _constraints = [ (osv.osv._check_recursion, 'Error ! You can not create recursive categories.', ['parent_id']) @@ -142,7 +142,7 @@ class res_partner(osv.osv): 'website': fields.char('Website',size=64, help="Website of Partner or Company"), 'comment': fields.text('Notes'), 'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'), # should be removed in version 7, but kept until then for backward compatibility - 'category_id': fields.many2many('res.partner.category', 'res_partner_category_rel', 'partner_id', 'category_id', 'Tags'), + 'category_id': fields.many2many('res.partner.category', id1='partner_id', id2='category_id', string='Tags'), 'credit_limit': fields.float(string='Credit Limit'), 'ean13': fields.char('EAN13', size=13), 'active': fields.boolean('Active'), From 5edca8990309952f1a9dd0cbc98fa1c046e3e296 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 20 Jun 2012 12:07:05 +0200 Subject: [PATCH 19/30] [IMP] uninstall: typos bzr revid: vmt@openerp.com-20120620100705-p9o5kktmj8zkbshm --- openerp/addons/base/base.sql | 8 ++++---- openerp/tests/test_uninstall.py | 11 ++--------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql index 0a8f50c24bf..f2cb77709ed 100644 --- a/openerp/addons/base/base.sql +++ b/openerp/addons/base/base.sql @@ -348,9 +348,9 @@ CREATE TABLE ir_model_data ( ); -- Records foreign keys and constraints installed by a module (so they can be --- removed them when the module is uninstalled): --- - for a foreign key: type 'f', --- - for a constraint: type 'u'. +-- removed when the module is uninstalled): +-- - for a foreign key: type is 'f', +-- - for a constraint: type is 'u' (this is the convention PostgreSQL uses). CREATE TABLE ir_model_constraint ( id serial NOT NULL, create_uid integer, @@ -366,7 +366,7 @@ CREATE TABLE ir_model_constraint ( ); -- Records relation tables (i.e. implementing many2many) installed by a module --- (so they can be removed them when the module is uninstalled). +-- (so they can be removed when the module is uninstalled). CREATE TABLE ir_model_relation ( id serial NOT NULL, create_uid integer, diff --git a/openerp/tests/test_uninstall.py b/openerp/tests/test_uninstall.py index 514042188a9..1f74b660ce0 100644 --- a/openerp/tests/test_uninstall.py +++ b/openerp/tests/test_uninstall.py @@ -1,12 +1,5 @@ # -*- coding: utf-8 -*- -# Run with one of these commands: -# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \ -# OPENERP_DATABASE=yy PYTHONPATH=. python tests/test_ir_sequence.py -# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \ -# OPENERP_DATABASE=yy nosetests tests/test_ir_sequence.py -# > OPENERP_ADDONS_PATH='../../../addons/trunk' OPENERP_PORT=8069 \ -# OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence -# This assume an existing database. +# This assumes an existing but uninitialized database. import psycopg2 import unittest2 @@ -77,7 +70,7 @@ class test_uninstall(unittest2.TestCase): assert search_registry('ir.model.fields', [('model', '=', 'test_uninstall.model')]) - def test_02_install(self): + def test_02_uninstall(self): """ Check a few things showing the module is uninstalled. """ uninstall_module('test_uninstall') assert not get_module('test_uninstall.model') From abfa5612f5f3d12953285ca48ba232bb08d5af9b Mon Sep 17 00:00:00 2001 From: Jigar Amin - OpenERP Date: Mon, 25 Jun 2012 18:02:06 +0530 Subject: [PATCH 20/30] [USB] renamed e-mail to email *.py bzr revid: jam@tinyerp.com-20120625123206-2nwfjc9mpbuk4mn1 --- openerp/addons/base/ir/ir_mail_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_mail_server.py b/openerp/addons/base/ir/ir_mail_server.py index 04156411e48..971fd6596ce 100644 --- a/openerp/addons/base/ir/ir_mail_server.py +++ b/openerp/addons/base/ir/ir_mail_server.py @@ -396,7 +396,7 @@ class ir_mail_server(osv.osv): MailDeliveryException and logs root cause. """ smtp_from = message['Return-Path'] or message['From'] - assert smtp_from, "The Return-Path or From header is required for any outbound e-mail" + assert smtp_from, "The Return-Path or From header is required for any outbound email # The email's "Envelope From" (Return-Path), and all recipient addresses must only contain ASCII characters. from_rfc2822 = extract_rfc2822_addresses(smtp_from) From 0473afaca6ac81fa6b2a6aa0bd0e8f5391d2d7e8 Mon Sep 17 00:00:00 2001 From: Jigar Amin - OpenERP Date: Mon, 25 Jun 2012 18:27:00 +0530 Subject: [PATCH 21/30] [REN] e-mail to email bzr revid: jam@tinyerp.com-20120625125700-7jr8aeqp2kn9uggu --- openerp/addons/base/ir/ir_mail_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_mail_server.py b/openerp/addons/base/ir/ir_mail_server.py index 971fd6596ce..276bfd618e3 100644 --- a/openerp/addons/base/ir/ir_mail_server.py +++ b/openerp/addons/base/ir/ir_mail_server.py @@ -161,7 +161,7 @@ def encode_rfc2822_address_header(header_text): class ir_mail_server(osv.osv): - """Represents an SMTP server, able to send outgoing e-mails, with SSL and TLS capabilities.""" + """Represents an SMTP server, able to send outgoing emails, with SSL and TLS capabilities.""" _name = "ir.mail_server" _columns = { From c8f8bd9cea5e66c27439d1e4a6c872032d63b6fd Mon Sep 17 00:00:00 2001 From: Jigar Amin - OpenERP Date: Mon, 25 Jun 2012 18:32:01 +0530 Subject: [PATCH 22/30] [FIX] ren E-mail to Email bzr revid: jam@tinyerp.com-20120625130201-d9032tey5ddu1pqu --- openerp/addons/base/i18n/base.pot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/i18n/base.pot b/openerp/addons/base/i18n/base.pot index 83c982e76b9..19cd9c7efa5 100644 --- a/openerp/addons/base/i18n/base.pot +++ b/openerp/addons/base/i18n/base.pot @@ -9455,7 +9455,7 @@ msgstr "" #. module: base #: field:res.partner,email:0 -msgid "E-mail" +msgid "Email msgstr "" #. module: base From f5bc4540431023577ded90a60d8ca7228e048dc1 Mon Sep 17 00:00:00 2001 From: Jigar Amin - OpenERP Date: Mon, 25 Jun 2012 18:36:52 +0530 Subject: [PATCH 23/30] [FIX] renmae E-mail, E-Mail to Email bzr revid: jam@tinyerp.com-20120625130652-ini3czg6a8qd71n2 --- openerp/addons/base/i18n/base.pot | 4 ++-- openerp/addons/base/res/res_bank.py | 2 +- openerp/addons/base/res/res_partner.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/i18n/base.pot b/openerp/addons/base/i18n/base.pot index 19cd9c7efa5..646fbd800ff 100644 --- a/openerp/addons/base/i18n/base.pot +++ b/openerp/addons/base/i18n/base.pot @@ -467,7 +467,7 @@ msgstr "" #. module: base #: field:res.bank,email:0 #: field:res.partner.address,email:0 -msgid "E-Mail" +msgid "Email msgstr "" #. module: base @@ -7592,7 +7592,7 @@ msgstr "" #. module: base #: model:ir.module.module,shortdesc:base.module_email_template -msgid "E-Mail Templates" +msgid "Email Templates msgstr "" #. module: base diff --git a/openerp/addons/base/res/res_bank.py b/openerp/addons/base/res/res_bank.py index 48b5973cf64..c9010b64319 100644 --- a/openerp/addons/base/res/res_bank.py +++ b/openerp/addons/base/res/res_bank.py @@ -35,7 +35,7 @@ class Bank(osv.osv): 'state': fields.many2one("res.country.state", 'Fed. State', domain="[('country_id', '=', country)]"), 'country': fields.many2one('res.country', 'Country'), - 'email': fields.char('E-Mail', size=64), + 'email': fields.char('Email', size=64), 'phone': fields.char('Phone', size=64), 'fax': fields.char('Fax', size=64), 'active': fields.boolean('Active'), diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 14acbceb91c..625d6b7d3b5 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -163,7 +163,7 @@ class res_partner(osv.osv): 'state_id': fields.many2one("res.country.state", 'State', domain="[('country_id','=',country_id)]"), 'country_id': fields.many2one('res.country', 'Country'), 'country': fields.related('country_id', type='many2one', relation='res.country', string='Country'), # for backward compatibility - 'email': fields.char('E-Mail', size=240), + 'email': fields.char('Email', size=240), 'phone': fields.char('Phone', size=64), 'fax': fields.char('Fax', size=64), 'mobile': fields.char('Mobile', size=64), @@ -448,7 +448,7 @@ class res_partner_address(osv.osv): 'city': fields.char('City', size=128), 'state_id': fields.many2one("res.country.state", 'Fed. State', domain="[('country_id','=',country_id)]"), 'country_id': fields.many2one('res.country', 'Country'), - 'email': fields.char('E-Mail', size=240), + 'email': fields.char('Email', size=240), 'phone': fields.char('Phone', size=64), 'fax': fields.char('Fax', size=64), 'mobile': fields.char('Mobile', size=64), From 5b339753b140f68bc0f20665446a05e99eb88d25 Mon Sep 17 00:00:00 2001 From: Jigar Amin - OpenERP Date: Mon, 25 Jun 2012 18:41:58 +0530 Subject: [PATCH 24/30] [FIX] usa fix bzr revid: jam@tinyerp.com-20120625131158-qeggwyomms20zm1f --- openerp/addons/base/ir/ir_mail_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_mail_server.py b/openerp/addons/base/ir/ir_mail_server.py index 276bfd618e3..7b63430c1d8 100644 --- a/openerp/addons/base/ir/ir_mail_server.py +++ b/openerp/addons/base/ir/ir_mail_server.py @@ -396,7 +396,7 @@ class ir_mail_server(osv.osv): MailDeliveryException and logs root cause. """ smtp_from = message['Return-Path'] or message['From'] - assert smtp_from, "The Return-Path or From header is required for any outbound email + assert smtp_from, "The Return-Path or From header is required for any outbound email"" # The email's "Envelope From" (Return-Path), and all recipient addresses must only contain ASCII characters. from_rfc2822 = extract_rfc2822_addresses(smtp_from) From 8d00222f8b6b810283e94023e411be9cc387c848 Mon Sep 17 00:00:00 2001 From: Jigar Amin - OpenERP Date: Mon, 25 Jun 2012 19:14:14 +0530 Subject: [PATCH 25/30] [FIX] usa fix bzr revid: jam@tinyerp.com-20120625134414-jfztjo8by5kbgqg0 --- openerp/addons/base/ir/ir_mail_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_mail_server.py b/openerp/addons/base/ir/ir_mail_server.py index 7b63430c1d8..6099c68a0fb 100644 --- a/openerp/addons/base/ir/ir_mail_server.py +++ b/openerp/addons/base/ir/ir_mail_server.py @@ -396,7 +396,7 @@ class ir_mail_server(osv.osv): MailDeliveryException and logs root cause. """ smtp_from = message['Return-Path'] or message['From'] - assert smtp_from, "The Return-Path or From header is required for any outbound email"" + assert smtp_from, "The Return-Path or From header is required for any outbound email" # The email's "Envelope From" (Return-Path), and all recipient addresses must only contain ASCII characters. from_rfc2822 = extract_rfc2822_addresses(smtp_from) From f86ccd2f4e3a546fb601679cfaaa93c8f4720a43 Mon Sep 17 00:00:00 2001 From: Jigar Amin - OpenERP Date: Tue, 26 Jun 2012 11:54:28 +0530 Subject: [PATCH 26/30] [FIX] followup to follow-up bzr revid: jam@tinyerp.com-20120626062428-d42lj4sf4a4almja --- openerp/addons/base/i18n/base.pot | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openerp/addons/base/i18n/base.pot b/openerp/addons/base/i18n/base.pot index 646fbd800ff..b4d28052ba8 100644 --- a/openerp/addons/base/i18n/base.pot +++ b/openerp/addons/base/i18n/base.pot @@ -544,16 +544,16 @@ msgid "\n" "==========================================================================\n" "\n" "You can define your multiple levels of recall through the menu:\n" -" Accounting/Configuration/Miscellaneous/Follow-Ups\n" +" Accounting/Configuration/Miscellaneous/Follow-ups\n" "\n" "Once it is defined, you can automatically print recalls every day through simply clicking on the menu:\n" -" Accounting/Periodical Processing/Billing/Send followups\n" +" Accounting/Periodical Processing/Billing/Send follow-ups\n" "\n" "It will generate a PDF with all the letters according to the the\n" "different levels of recall defined. You can define different policies\n" "for different companies. You can also send mail to the customer.\n" "\n" -"Note that if you want to check the followup level for a given partner/account entry, you can do from in the menu:\n" +"Note that if you want to check the follow-up level for a given partner/account entry, you can do from in the menu:\n" " Accounting/Reporting/Generic Reporting/Partners/Follow-ups Sent\n" "\n" "" @@ -12688,7 +12688,7 @@ msgstr "" #. module: base #: model:ir.module.module,shortdesc:base.module_account_followup -msgid "Followup Management" +msgid "Follow-up Management" msgstr "" #. module: base From 6c0bdc167b1c6a5b2cbb36a045bfd1abc1cd121a Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of openerp <> Date: Wed, 27 Jun 2012 05:28:31 +0000 Subject: [PATCH 27/30] Launchpad automatic translations update. bzr revid: launchpad_translations_on_behalf_of_openerp-20120627052831-0lkrh7ucdhhpwet0 --- addons/product_visible_discount/i18n/sv.po | 62 +++++ addons/purchase/i18n/sv.po | 281 +++++++++++---------- addons/report_intrastat/i18n/sv.po | 10 +- addons/stock/i18n/ja.po | 135 +++++----- 4 files changed, 286 insertions(+), 202 deletions(-) create mode 100644 addons/product_visible_discount/i18n/sv.po diff --git a/addons/product_visible_discount/i18n/sv.po b/addons/product_visible_discount/i18n/sv.po new file mode 100644 index 00000000000..1b440cb2d4c --- /dev/null +++ b/addons/product_visible_discount/i18n/sv.po @@ -0,0 +1,62 @@ +# Swedish translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-02-08 00:37+0000\n" +"PO-Revision-Date: 2012-06-26 22:45+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Swedish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2012-06-27 05:28+0000\n" +"X-Generator: Launchpad (build 15482)\n" + +#. module: product_visible_discount +#: code:addons/product_visible_discount/product_visible_discount.py:153 +#, python-format +msgid "No Sale Pricelist Found!" +msgstr "Ingen försäljningsprislista funnen!" + +#. module: product_visible_discount +#: field:product.pricelist,visible_discount:0 +msgid "Visible Discount" +msgstr "Synlig rabatt" + +#. module: product_visible_discount +#: code:addons/product_visible_discount/product_visible_discount.py:145 +#, python-format +msgid "No Purchase Pricelist Found!" +msgstr "" + +#. module: product_visible_discount +#: model:ir.model,name:product_visible_discount.model_account_invoice_line +msgid "Invoice Line" +msgstr "Fakturarad" + +#. module: product_visible_discount +#: model:ir.model,name:product_visible_discount.model_product_pricelist +msgid "Pricelist" +msgstr "Prislista" + +#. module: product_visible_discount +#: code:addons/product_visible_discount/product_visible_discount.py:145 +#, python-format +msgid "You must first define a pricelist on the supplier form!" +msgstr "" + +#. module: product_visible_discount +#: model:ir.model,name:product_visible_discount.model_sale_order_line +msgid "Sales Order Line" +msgstr "Orderrad" + +#. module: product_visible_discount +#: code:addons/product_visible_discount/product_visible_discount.py:153 +#, python-format +msgid "You must first define a pricelist on the customer form!" +msgstr "" diff --git a/addons/purchase/i18n/sv.po b/addons/purchase/i18n/sv.po index 4792388c664..4277647110e 100644 --- a/addons/purchase/i18n/sv.po +++ b/addons/purchase/i18n/sv.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.14\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 01:37+0100\n" -"PO-Revision-Date: 2011-08-03 23:22+0000\n" -"Last-Translator: Stefan Lind \n" +"PO-Revision-Date: 2012-06-26 07:07+0000\n" +"Last-Translator: Anders Wallenquist \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-05-11 04:41+0000\n" -"X-Generator: Launchpad (build 15225)\n" +"X-Launchpad-Export-Date: 2012-06-27 05:28+0000\n" +"X-Generator: Launchpad (build 15482)\n" #. module: purchase #: model:process.transition,note:purchase.process_transition_confirmingpurchaseorder0 @@ -54,13 +54,13 @@ msgstr "Datum när detta dokument blev skapat" #: view:purchase.order:0 view:purchase.order.line:0 view:purchase.report:0 #: view:stock.picking:0 msgid "Group By..." -msgstr "" +msgstr "Gruppera på..." #. module: purchase #: field:purchase.order,create_uid:0 view:purchase.report:0 #: field:purchase.report,user_id:0 msgid "Responsible" -msgstr "" +msgstr "Ansvarig" #. module: purchase #: model:ir.actions.act_window,help:purchase.purchase_rfq @@ -86,7 +86,7 @@ msgstr "" #. module: purchase #: view:purchase.order:0 msgid "Approved purchase order" -msgstr "" +msgstr "Godkänd inköpsorder" #. module: purchase #: view:purchase.order:0 field:purchase.order,partner_id:0 @@ -99,12 +99,12 @@ msgstr "Supplier" #: model:ir.ui.menu,name:purchase.menu_product_pricelist_action2_purchase #: model:ir.ui.menu,name:purchase.menu_purchase_config_pricelist msgid "Pricelists" -msgstr "" +msgstr "Prislistor" #. module: purchase #: view:stock.picking:0 msgid "To Invoice" -msgstr "" +msgstr "Att fakturera" #. module: purchase #: view:purchase.order.line_invoice:0 @@ -128,7 +128,7 @@ msgstr "Leverantörsfakturor" #. module: purchase #: view:purchase.report:0 msgid "Purchase Orders Statistics" -msgstr "" +msgstr "Inköpsorderstatistik" #. module: purchase #: model:process.transition,name:purchase.process_transition_packinginvoice0 @@ -145,7 +145,7 @@ msgstr "No Pricelist !" #. module: purchase #: model:ir.model,name:purchase.model_purchase_config_wizard msgid "purchase.config.wizard" -msgstr "" +msgstr "purchase.config.wizard" #. module: purchase #: view:board.board:0 model:ir.actions.act_window,name:purchase.purchase_draft @@ -155,7 +155,7 @@ msgstr "Offertförfrågan" #. module: purchase #: selection:purchase.config.wizard,default_method:0 msgid "Based on Receptions" -msgstr "" +msgstr "Baserad på mottagning" #. module: purchase #: field:purchase.order,company_id:0 field:purchase.order.line,company_id:0 @@ -172,7 +172,7 @@ msgstr "" #: model:ir.actions.act_window,name:purchase.action_purchase_order_monthly_categ_graph #: view:purchase.report:0 msgid "Monthly Purchase by Category" -msgstr "" +msgstr "Månatlig försäljning per kategori" #. module: purchase #: view:purchase.order:0 @@ -187,7 +187,7 @@ msgstr "Invoice Exception" #. module: purchase #: model:product.pricelist,name:purchase.list0 msgid "Default Purchase Pricelist" -msgstr "Default Purchase Pricelist" +msgstr "Standardinköpslista" #. module: purchase #: help:purchase.order,dest_address_id:0 @@ -217,12 +217,12 @@ msgstr "Fax :" #. module: purchase #: view:purchase.order:0 msgid "To Approve" -msgstr "" +msgstr "Att godkänna" #. module: purchase #: view:res.partner:0 msgid "Purchase Properties" -msgstr "" +msgstr "Inköpsegenskaper" #. module: purchase #: model:ir.model,name:purchase.model_stock_partial_picking @@ -242,22 +242,22 @@ msgstr "Approve Purchase" #. module: purchase #: view:purchase.report:0 field:purchase.report,day:0 msgid "Day" -msgstr "" +msgstr "Dag" #. module: purchase #: selection:purchase.order,invoice_method:0 msgid "Based on generated draft invoice" -msgstr "" +msgstr "Baserad på genererad preleminär faktura" #. module: purchase #: view:purchase.report:0 msgid "Order of Day" -msgstr "" +msgstr "Dagens ordrar" #. module: purchase #: view:board.board:0 msgid "Monthly Purchases by Category" -msgstr "" +msgstr "Månatliga inköp per kategori" #. module: purchase #: model:ir.actions.act_window,name:purchase.action_purchase_line_product_tree @@ -267,7 +267,7 @@ msgstr "Inköp" #. module: purchase #: view:purchase.order:0 msgid "Purchase order which are in draft state" -msgstr "" +msgstr "Preleminära inköpsorder" #. module: purchase #: view:purchase.order:0 @@ -283,7 +283,7 @@ msgstr "Anteckningar" #. module: purchase #: selection:purchase.report,month:0 msgid "September" -msgstr "" +msgstr "september" #. module: purchase #: report:purchase.order:0 field:purchase.order,amount_tax:0 @@ -341,7 +341,7 @@ msgstr "Cancelled" #. module: purchase #: view:purchase.order:0 msgid "Convert to Purchase Order" -msgstr "" +msgstr "Konvertera till inköpsorder" #. module: purchase #: field:purchase.order,pricelist_id:0 field:purchase.report,pricelist_id:0 @@ -356,7 +356,7 @@ msgstr "Shipping Exception" #. module: purchase #: field:purchase.order.line,invoice_lines:0 msgid "Invoice Lines" -msgstr "" +msgstr "Fakturarader" #. module: purchase #: model:process.node,name:purchase.process_node_packinglist0 @@ -382,13 +382,14 @@ msgstr "Reference" #. module: purchase #: model:ir.model,name:purchase.model_stock_move msgid "Stock Move" -msgstr "" +msgstr "Lagertransaktion" #. module: purchase #: code:addons/purchase/purchase.py:419 #, python-format msgid "You must first cancel all invoices related to this purchase order." msgstr "" +"Du måste först avbryta all fakturering med anknytning till denna inköpsorder" #. module: purchase #: field:purchase.report,dest_address_id:0 @@ -404,7 +405,7 @@ msgstr "TVA :" #: code:addons/purchase/purchase.py:326 #, python-format msgid "Purchase order '%s' has been set in draft state." -msgstr "" +msgstr "Inköpsorder '%s' har åter fått status utkast." #. module: purchase #: field:purchase.order.line,account_analytic_id:0 @@ -414,7 +415,7 @@ msgstr "Analytic Account" #. module: purchase #: view:purchase.report:0 field:purchase.report,nbr:0 msgid "# of Lines" -msgstr "" +msgstr "# Rader" #. module: purchase #: code:addons/purchase/purchase.py:754 code:addons/purchase/purchase.py:769 @@ -422,7 +423,7 @@ msgstr "" #: code:addons/purchase/wizard/purchase_order_group.py:47 #, python-format msgid "Warning" -msgstr "" +msgstr "Varning" #. module: purchase #: field:purchase.order,validator:0 view:purchase.report:0 @@ -432,7 +433,7 @@ msgstr "Validerad av" #. module: purchase #: view:purchase.report:0 msgid "Order in last month" -msgstr "" +msgstr "Ordrar under senaste månaden" #. module: purchase #: code:addons/purchase/purchase.py:412 @@ -443,7 +444,7 @@ msgstr "" #. module: purchase #: selection:purchase.order.line,state:0 msgid "Draft" -msgstr "" +msgstr "Preliminär" #. module: purchase #: report:purchase.order:0 @@ -458,7 +459,7 @@ msgstr "Order Line" #. module: purchase #: help:purchase.order,shipped:0 msgid "It indicates that a picking has been done" -msgstr "" +msgstr "Anger att plockningen är klar." #. module: purchase #: view:purchase.order:0 @@ -479,7 +480,7 @@ msgstr "Confirmed" #. module: purchase #: view:purchase.report:0 field:purchase.report,price_average:0 msgid "Average Price" -msgstr "" +msgstr "Snittpris" #. module: purchase #: view:stock.picking:0 @@ -502,17 +503,17 @@ msgstr "Bekräfta" #: model:ir.ui.menu,name:purchase.menu_action_picking_tree4_picking_to_invoice #: selection:purchase.order,invoice_method:0 msgid "Based on receptions" -msgstr "" +msgstr "Baserad på mottagning" #. module: purchase #: constraint:res.company:0 msgid "Error! You can not create recursive companies." -msgstr "" +msgstr "Fel! Du kan inte skapa rekursiva bolagsstrukturer." #. module: purchase #: field:purchase.order,partner_ref:0 msgid "Supplier Reference" -msgstr "" +msgstr "Leverantörens referens" #. module: purchase #: model:process.transition,note:purchase.process_transition_productrecept0 @@ -563,7 +564,7 @@ msgstr "Skriv ut" #. module: purchase #: model:ir.actions.act_window,name:purchase.action_view_purchase_order_group msgid "Merge Purchase orders" -msgstr "" +msgstr "Sammanslagna inköpsordrar" #. module: purchase #: field:purchase.order,order_line:0 @@ -584,32 +585,32 @@ msgstr "Fax:" #. module: purchase #: view:purchase.report:0 field:purchase.report,price_total:0 msgid "Total Price" -msgstr "" +msgstr "Totalt belopp" #. module: purchase #: model:ir.actions.act_window,name:purchase.action_import_create_supplier_installer msgid "Create or Import Suppliers" -msgstr "" +msgstr "Skapa eller importera leverantörer" #. module: purchase #: view:stock.picking:0 msgid "Available" -msgstr "" +msgstr "Tillgänglig" #. module: purchase #: field:purchase.report,partner_address_id:0 msgid "Address Contact Name" -msgstr "" +msgstr "Kontaktadressnamn" #. module: purchase #: report:purchase.order:0 msgid "Shipping address :" -msgstr "Shipping address :" +msgstr "Leveransadress :" #. module: purchase #: help:purchase.order,invoice_ids:0 msgid "Invoices generated for a purchase order" -msgstr "" +msgstr "Fakturor skapade för en inköpsorder" #. module: purchase #: code:addons/purchase/purchase.py:285 code:addons/purchase/purchase.py:348 @@ -639,6 +640,8 @@ msgstr "" #, python-format msgid "There is no purchase journal defined for this company: \"%s\" (id:%d)" msgstr "" +"Det finns ingen inköpssjournal definierad för det här företaget: \"%s\" " +"(id:%d)" #. module: purchase #: model:process.transition,note:purchase.process_transition_invoicefrompackinglist0 @@ -664,7 +667,7 @@ msgstr "" #. module: purchase #: model:ir.ui.menu,name:purchase.menu_configuration_misc msgid "Miscellaneous" -msgstr "" +msgstr "Allmänt" #. module: purchase #: code:addons/purchase/purchase.py:769 @@ -692,7 +695,7 @@ msgstr "Create invoice" #: model:ir.ui.menu,name:purchase.menu_purchase_unit_measure_purchase #: model:ir.ui.menu,name:purchase.menu_purchase_uom_form_action msgid "Units of Measure" -msgstr "" +msgstr "Enhet" #. module: purchase #: field:purchase.order.line,move_dest_id:0 @@ -713,13 +716,13 @@ msgstr "Fiscal Position" #. module: purchase #: selection:purchase.report,month:0 msgid "July" -msgstr "" +msgstr "juli" #. module: purchase #: model:ir.ui.menu,name:purchase.menu_purchase_config_purchase #: view:res.company:0 msgid "Configuration" -msgstr "" +msgstr "Konfiguration" #. module: purchase #: view:purchase.order:0 @@ -756,22 +759,22 @@ msgstr "RFQ" #: code:addons/purchase/edi/purchase_order.py:139 #, python-format msgid "EDI Pricelist (%s)" -msgstr "" +msgstr "EDI-prislista (%s)" #. module: purchase #: selection:purchase.order,state:0 msgid "Waiting Approval" -msgstr "" +msgstr "Väntar på godkännande" #. module: purchase #: selection:purchase.report,month:0 msgid "January" -msgstr "" +msgstr "januari" #. module: purchase #: model:ir.actions.server,name:purchase.ir_actions_server_edi_purchase msgid "Auto-email confirmed purchase orders" -msgstr "" +msgstr "Automatisk e-post bekräftade inköpsorder" #. module: purchase #: model:process.transition,name:purchase.process_transition_approvingpurchaseorder0 @@ -783,18 +786,18 @@ msgstr "" #: field:purchase.order,date_order:0 field:purchase.order.line,date_order:0 #: field:purchase.report,date:0 view:stock.picking:0 msgid "Order Date" -msgstr "" +msgstr "Orderdatum" #. module: purchase #: constraint:stock.move:0 msgid "You must assign a production lot for this product" -msgstr "" +msgstr "Du måste tilldela ett produktionsbatch för denna produkt" #. module: purchase #: model:ir.model,name:purchase.model_res_partner #: field:purchase.order.line,partner_id:0 view:stock.picking:0 msgid "Partner" -msgstr "" +msgstr "Företag" #. module: purchase #: model:process.node,name:purchase.process_node_invoiceafterpacking0 @@ -810,7 +813,7 @@ msgstr "Qty" #. module: purchase #: view:purchase.report:0 msgid "Month-1" -msgstr "" +msgstr "Månad-1" #. module: purchase #: help:purchase.order,minimum_planned_date:0 @@ -824,17 +827,17 @@ msgstr "" #. module: purchase #: model:ir.model,name:purchase.model_purchase_order_group msgid "Purchase Order Merge" -msgstr "" +msgstr "Inköpsorderradsammanslagning" #. module: purchase #: view:purchase.report:0 msgid "Order in current month" -msgstr "" +msgstr "Ordrar i innevarande månad" #. module: purchase #: view:purchase.report:0 field:purchase.report,delay_pass:0 msgid "Days to Deliver" -msgstr "" +msgstr "Dagar för att leverera" #. module: purchase #: model:ir.ui.menu,name:purchase.menu_action_picking_tree_in_move @@ -845,22 +848,22 @@ msgstr "Ta emot Produkter" #. module: purchase #: model:ir.model,name:purchase.model_procurement_order msgid "Procurement" -msgstr "" +msgstr "Anskaffning" #. module: purchase #: view:purchase.order:0 field:purchase.order,invoice_ids:0 msgid "Invoices" -msgstr "" +msgstr "Fakturor" #. module: purchase #: selection:purchase.report,month:0 msgid "December" -msgstr "" +msgstr "december" #. module: purchase #: field:purchase.config.wizard,config_logo:0 msgid "Image" -msgstr "" +msgstr "Bild" #. module: purchase #: view:purchase.report:0 @@ -870,17 +873,17 @@ msgstr "" #. module: purchase #: view:purchase.order:0 msgid "Approved purchase orders" -msgstr "" +msgstr "Godkända inköpsordrar" #. module: purchase #: view:purchase.report:0 field:purchase.report,month:0 msgid "Month" -msgstr "" +msgstr "Månad" #. module: purchase #: model:email.template,subject:purchase.email_template_edi_purchase msgid "${object.company_id.name} Order (Ref ${object.name or 'n/a' })" -msgstr "" +msgstr "${object.company_id.name} Order (Ref ${object.name or 'n/a' })" #. module: purchase #: report:purchase.quotation:0 @@ -895,12 +898,12 @@ msgstr "Inköpsorder för godkännande" #. module: purchase #: view:purchase.order:0 msgid "Total Untaxed amount" -msgstr "" +msgstr "Totalt ex moms" #. module: purchase #: model:res.groups,name:purchase.group_purchase_user msgid "User" -msgstr "" +msgstr "Användare" #. module: purchase #: field:purchase.order,shipped:0 field:purchase.order,shipped_rate:0 @@ -922,18 +925,18 @@ msgstr "" #. module: purchase #: view:stock.picking:0 msgid "Is a Back Order" -msgstr "" +msgstr "Är en restorder" #. module: purchase #: model:process.node,note:purchase.process_node_invoiceafterpacking0 #: model:process.node,note:purchase.process_node_invoicecontrol0 msgid "To be reviewed by the accountant." -msgstr "" +msgstr "Skall granskas av bokföraren." #. module: purchase #: help:purchase.order,amount_total:0 msgid "The total amount" -msgstr "" +msgstr "Den totala summan" #. module: purchase #: report:purchase.order:0 @@ -943,18 +946,18 @@ msgstr "Taxes :" #. module: purchase #: field:purchase.order,invoiced_rate:0 field:purchase.order.line,invoiced:0 msgid "Invoiced" -msgstr "Invoiced" +msgstr "Fakturerad" #. module: purchase #: view:purchase.report:0 field:purchase.report,category_id:0 msgid "Category" -msgstr "" +msgstr "Kategori" #. module: purchase #: model:process.node,note:purchase.process_node_approvepurchaseorder0 #: model:process.node,note:purchase.process_node_confirmpurchaseorder0 msgid "State of the Purchase Order." -msgstr "" +msgstr "Status för inköpsordern." #. module: purchase #: field:purchase.order,dest_address_id:0 @@ -964,12 +967,12 @@ msgstr "Destination Address" #. module: purchase #: field:purchase.report,state:0 msgid "Order State" -msgstr "" +msgstr "Orderstatus" #. module: purchase #: model:ir.ui.menu,name:purchase.menu_product_category_config_purchase msgid "Product Categories" -msgstr "" +msgstr "Produktkategorier" #. module: purchase #: selection:purchase.config.wizard,default_method:0 @@ -979,12 +982,12 @@ msgstr "" #. module: purchase #: model:ir.actions.act_window,name:purchase.action_view_purchase_line_invoice msgid "Create invoices" -msgstr "" +msgstr "Skapa fakturor" #. module: purchase #: sql_constraint:res.company:0 msgid "The company name must be unique !" -msgstr "" +msgstr "Bolagsnamnet måste vara unikt !" #. module: purchase #: model:ir.model,name:purchase.model_purchase_order_line @@ -1005,7 +1008,7 @@ msgstr "" #. module: purchase #: help:purchase.order,amount_untaxed:0 msgid "The amount without tax" -msgstr "" +msgstr "Summa utan moms." #. module: purchase #: code:addons/purchase/purchase.py:754 @@ -1017,7 +1020,7 @@ msgstr "" #: code:addons/purchase/purchase.py:907 #, python-format msgid "PO: %s" -msgstr "" +msgstr "IO: %s" #. module: purchase #: model:process.transition,note:purchase.process_transition_purchaseinvoice0 @@ -1035,22 +1038,22 @@ msgstr "Untaxed Amount" #. module: purchase #: help:purchase.order,invoiced:0 msgid "It indicates that an invoice has been paid" -msgstr "" +msgstr "Anger att en faktura är betald." #. module: purchase #: model:process.node,note:purchase.process_node_packinginvoice0 msgid "Outgoing products to invoice" -msgstr "" +msgstr "Utlevererade produkter att fakturera" #. module: purchase #: selection:purchase.report,month:0 msgid "August" -msgstr "" +msgstr "augusti" #. module: purchase #: constraint:stock.move:0 msgid "You try to assign a lot which is not from the same product" -msgstr "" +msgstr "Du försöker tilldela ett parti som inte är från samma produkt." #. module: purchase #: help:purchase.order,date_order:0 @@ -1060,12 +1063,12 @@ msgstr "Date on which this document has been created." #. module: purchase #: view:res.partner:0 msgid "Sales & Purchases" -msgstr "" +msgstr "Försäljning och inköp" #. module: purchase #: selection:purchase.report,month:0 msgid "June" -msgstr "" +msgstr "juni" #. module: purchase #: model:process.transition,note:purchase.process_transition_invoicefrompurchase0 @@ -1079,17 +1082,17 @@ msgstr "" #: model:ir.actions.act_window,name:purchase.action_email_templates #: model:ir.ui.menu,name:purchase.menu_email_templates msgid "Email Templates" -msgstr "" +msgstr "E-postmallar" #. module: purchase #: model:ir.model,name:purchase.model_purchase_report msgid "Purchases Orders" -msgstr "" +msgstr "Inköpsorder" #. module: purchase #: view:purchase.order.line:0 msgid "Manual Invoices" -msgstr "" +msgstr "Manuella fakturor" #. module: purchase #: model:ir.ui.menu,name:purchase.menu_procurement_management_invoice @@ -1100,17 +1103,17 @@ msgstr "Invoice Control" #. module: purchase #: model:ir.ui.menu,name:purchase.menu_purchase_uom_categ_form_action msgid "UoM Categories" -msgstr "" +msgstr "Enhetskategorier" #. module: purchase #: selection:purchase.report,month:0 msgid "November" -msgstr "" +msgstr "november" #. module: purchase #: view:purchase.report:0 msgid "Extended Filters..." -msgstr "" +msgstr "Utökade filter..." #. module: purchase #: view:purchase.config.wizard:0 @@ -1130,21 +1133,24 @@ msgid "" "can import your existing partners by CSV spreadsheet from \"Import Data\" " "wizard" msgstr "" +"Skapa eller Importera leverantörer och deras kontakter manuellt från detta " +"formulär eller så kan du importera dina befintliga företag med CSV-filer " +"från \"Importera data\" guiden." #. module: purchase #: model:process.transition,name:purchase.process_transition_createpackinglist0 msgid "Pick list generated" -msgstr "" +msgstr "Plocklista skapad" #. module: purchase #: view:purchase.order:0 msgid "Exception" -msgstr "" +msgstr "Undantag" #. module: purchase #: selection:purchase.report,month:0 msgid "October" -msgstr "" +msgstr "oktober" #. module: purchase #: view:purchase.order:0 @@ -1154,17 +1160,17 @@ msgstr "Compute" #. module: purchase #: view:stock.picking:0 msgid "Incoming Shipments Available" -msgstr "" +msgstr "Inkommande leveranser tillgängliga." #. module: purchase #: model:ir.ui.menu,name:purchase.menu_purchase_partner_cat msgid "Address Book" -msgstr "" +msgstr "Adressbok" #. module: purchase #: model:ir.model,name:purchase.model_res_company msgid "Companies" -msgstr "" +msgstr "Bolag" #. module: purchase #: view:purchase.order:0 @@ -1194,12 +1200,12 @@ msgstr "" #. module: purchase #: model:ir.ui.menu,name:purchase.menu_purchase_deshboard msgid "Dashboard" -msgstr "" +msgstr "Infopanel" #. module: purchase #: sql_constraint:stock.picking:0 msgid "Reference must be unique per Company!" -msgstr "" +msgstr "Referensen måste vara unik per bolag!" #. module: purchase #: view:purchase.report:0 field:purchase.report,price_standard:0 @@ -1209,17 +1215,17 @@ msgstr "" #. module: purchase #: model:ir.ui.menu,name:purchase.menu_partner_categories_in_form msgid "Partner Categories" -msgstr "" +msgstr "Företagskategorier" #. module: purchase #: help:purchase.order,amount_tax:0 msgid "The tax amount" -msgstr "" +msgstr "Moms" #. module: purchase #: view:purchase.order:0 view:purchase.report:0 msgid "Quotations" -msgstr "" +msgstr "Offerter" #. module: purchase #: help:purchase.order,invoice_method:0 @@ -1233,7 +1239,7 @@ msgstr "" #. module: purchase #: model:ir.actions.act_window,name:purchase.action_supplier_address_form msgid "Addresses" -msgstr "" +msgstr "Adresser" #. module: purchase #: model:ir.actions.act_window,name:purchase.purchase_rfq @@ -1306,7 +1312,7 @@ msgstr "" #: view:purchase.order.line:0 field:purchase.order.line,state:0 #: view:purchase.report:0 view:stock.picking:0 msgid "State" -msgstr "" +msgstr "Tillstånd" #. module: purchase #: model:process.node,name:purchase.process_node_approvepurchaseorder0 @@ -1318,7 +1324,7 @@ msgstr "Approved" #. module: purchase #: view:purchase.order.line:0 msgid "General Information" -msgstr "" +msgstr "Allmän information" #. module: purchase #: view:purchase.order:0 @@ -1351,7 +1357,7 @@ msgstr "Faktura" #. module: purchase #: model:process.node,note:purchase.process_node_purchaseorder0 msgid "Confirmed purchase order to invoice" -msgstr "" +msgstr "Bekräftade inköpsorder att fakturera" #. module: purchase #: model:process.transition.action,name:purchase.process_transition_action_approvingcancelpurchaseorder0 @@ -1384,12 +1390,12 @@ msgstr "Inköpsordrar" #. module: purchase #: sql_constraint:purchase.order:0 msgid "Order Reference must be unique per Company!" -msgstr "" +msgstr "Orderreferensen måste vara unik per bolag!" #. module: purchase #: field:purchase.order,origin:0 msgid "Source Document" -msgstr "" +msgstr "Källdokument" #. module: purchase #: view:purchase.order.group:0 @@ -1404,7 +1410,7 @@ msgstr "" #. module: purchase #: model:ir.ui.menu,name:purchase.menu_action_picking_tree4 msgid "Incoming Shipments" -msgstr "" +msgstr "Inkommande leveranser" #. module: purchase #: model:ir.actions.act_window,name:purchase.action_purchase_order_by_user_all @@ -1425,7 +1431,7 @@ msgstr "Tél. :" #. module: purchase #: view:purchase.report:0 msgid "Order of Month" -msgstr "" +msgstr "Ordrar denna månad" #. module: purchase #: report:purchase.order:0 @@ -1484,7 +1490,7 @@ msgstr "" #. module: purchase #: field:purchase.order.line,date_planned:0 msgid "Scheduled Date" -msgstr "" +msgstr "Planerat datum" #. module: purchase #: field:purchase.order,product_id:0 view:purchase.order.line:0 @@ -1497,7 +1503,7 @@ msgstr "Product" #: model:process.transition,name:purchase.process_transition_confirmingpurchaseorder0 #: model:process.transition,name:purchase.process_transition_confirmingpurchaseorder1 msgid "Confirmation" -msgstr "" +msgstr "Bekräftan" #. module: purchase #: report:purchase.order:0 field:purchase.order.line,name:0 @@ -1508,7 +1514,7 @@ msgstr "Beskrivning" #. module: purchase #: view:purchase.report:0 msgid "Order of Year" -msgstr "" +msgstr "Ordrar i år" #. module: purchase #: report:purchase.quotation:0 @@ -1518,7 +1524,7 @@ msgstr "Expected Delivery address:" #. module: purchase #: view:stock.picking:0 msgid "Journal" -msgstr "" +msgstr "Journal" #. module: purchase #: model:ir.actions.act_window,name:purchase.action_stock_move_report_po @@ -1529,7 +1535,7 @@ msgstr "Mottagningsanalys" #. module: purchase #: field:res.company,po_lead:0 msgid "Purchase Lead Time" -msgstr "" +msgstr "Leverantörens ledtid" #. module: purchase #: model:ir.actions.act_window,help:purchase.action_supplier_address_form @@ -1547,7 +1553,7 @@ msgstr "Delivery" #. module: purchase #: view:purchase.order:0 msgid "Purchase orders which are in done state." -msgstr "" +msgstr "Klara inköpsordrar" #. module: purchase #: field:purchase.order.line,product_uom:0 @@ -1567,7 +1573,7 @@ msgstr "Väntar" #. module: purchase #: field:purchase.order,partner_address_id:0 msgid "Address" -msgstr "" +msgstr "Adress" #. module: purchase #: field:purchase.report,product_uom:0 @@ -1592,7 +1598,7 @@ msgstr "Untaxed amount" #. module: purchase #: view:stock.picking:0 msgid "Picking to Invoice" -msgstr "" +msgstr "Plockning att fakturera" #. module: purchase #: view:purchase.config.wizard:0 @@ -1611,13 +1617,13 @@ msgstr "" #. module: purchase #: selection:purchase.report,month:0 msgid "February" -msgstr "" +msgstr "februari" #. module: purchase #: model:ir.actions.act_window,name:purchase.action_purchase_order_report_all #: model:ir.ui.menu,name:purchase.menu_action_purchase_order_report_all msgid "Purchase Analysis" -msgstr "" +msgstr "Inköpsanalys" #. module: purchase #: report:purchase.order:0 @@ -1647,17 +1653,17 @@ msgstr "" #. module: purchase #: view:purchase.order:0 msgid "Purchase Control" -msgstr "Purchase Control" +msgstr "Inköpskontroll" #. module: purchase #: selection:purchase.report,month:0 msgid "March" -msgstr "" +msgstr "mars" #. module: purchase #: selection:purchase.report,month:0 msgid "April" -msgstr "" +msgstr "april" #. module: purchase #: view:purchase.order.group:0 @@ -1689,7 +1695,7 @@ msgstr "" #: model:product.pricelist.type,name:purchase.pricelist_type_purchase #: field:res.partner,property_product_pricelist_purchase:0 msgid "Purchase Pricelist" -msgstr "Purchase Pricelist" +msgstr "Inköpsprislista" #. module: purchase #: field:purchase.order,invoice_method:0 @@ -1699,7 +1705,7 @@ msgstr "Invoicing Control" #. module: purchase #: view:stock.picking:0 msgid "Back Orders" -msgstr "" +msgstr "Restordrar" #. module: purchase #: model:process.transition.action,name:purchase.process_transition_action_approvingpurchaseorder0 @@ -1709,7 +1715,7 @@ msgstr "Approve" #. module: purchase #: model:product.pricelist.version,name:purchase.ver0 msgid "Default Purchase Pricelist Version" -msgstr "Default Purchase Pricelist Version" +msgstr "Standardinköpsprislisteversion" #. module: purchase #: view:purchase.order.line:0 @@ -1732,7 +1738,7 @@ msgstr "" #: code:addons/purchase/purchase.py:426 #, python-format msgid "Purchase order '%s' is cancelled." -msgstr "" +msgstr "Inköpsorder '%s' är avbruten." #. module: purchase #: field:purchase.order,amount_total:0 @@ -1742,12 +1748,12 @@ msgstr "Total" #. module: purchase #: model:ir.ui.menu,name:purchase.menu_product_pricelist_action_purhase msgid "Pricelist Versions" -msgstr "" +msgstr "Prislisteversioner" #. module: purchase #: constraint:res.partner:0 msgid "Error ! You cannot create recursive associated members." -msgstr "" +msgstr "Fel ! Du kan inte ansluta medlemmar rekursivt" #. module: purchase #: code:addons/purchase/purchase.py:359 @@ -1839,32 +1845,32 @@ msgstr "" #. module: purchase #: selection:purchase.report,month:0 msgid "May" -msgstr "" +msgstr "maj" #. module: purchase #: model:res.groups,name:purchase.group_purchase_manager msgid "Manager" -msgstr "" +msgstr "Chef" #. module: purchase #: view:purchase.config.wizard:0 msgid "res_config_contents" -msgstr "" +msgstr "res_config_contents" #. module: purchase #: view:purchase.report:0 msgid "Order in current year" -msgstr "" +msgstr "Ordrar i innevarande år" #. module: purchase #: model:process.process,name:purchase.process_process_purchaseprocess0 msgid "Purchase" -msgstr "" +msgstr "Inköp" #. module: purchase #: view:purchase.report:0 field:purchase.report,name:0 msgid "Year" -msgstr "" +msgstr "År" #. module: purchase #: model:ir.actions.act_window,name:purchase.purchase_line_form_action2 @@ -1892,7 +1898,7 @@ msgstr "Välj en öppen säljorder" #. module: purchase #: view:purchase.report:0 msgid "Orders" -msgstr "" +msgstr "Ordrar" #. module: purchase #: help:purchase.order,name:0 @@ -1906,7 +1912,7 @@ msgstr "" #: model:ir.actions.act_window,name:purchase.open_board_purchase #: model:ir.ui.menu,name:purchase.menu_board_purchase msgid "Purchase Dashboard" -msgstr "" +msgstr "Inköpsorder infopanel" #~ msgid "" #~ "The Object name must start with x_ and not contain any special character !" @@ -2177,3 +2183,6 @@ msgstr "" #~ msgid "Products Categories" #~ msgstr "Produktkategorier" + +#~ msgid "Not invoiced" +#~ msgstr "Icke fakturerat" diff --git a/addons/report_intrastat/i18n/sv.po b/addons/report_intrastat/i18n/sv.po index 0df7182c8c0..9b7c0d9e131 100644 --- a/addons/report_intrastat/i18n/sv.po +++ b/addons/report_intrastat/i18n/sv.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.14\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:37+0000\n" -"PO-Revision-Date: 2012-06-21 15:07+0000\n" +"PO-Revision-Date: 2012-06-26 21:48+0000\n" "Last-Translator: Anders Wallenquist \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-06-22 04:44+0000\n" -"X-Generator: Launchpad (build 15461)\n" +"X-Launchpad-Export-Date: 2012-06-27 05:28+0000\n" +"X-Generator: Launchpad (build 15482)\n" #. module: report_intrastat #: report:account.invoice.intrastat:0 @@ -111,7 +111,7 @@ msgstr "Fakturadatum" #. module: report_intrastat #: selection:report.intrastat,month:0 msgid "June" -msgstr "Juni" +msgstr "juni" #. module: report_intrastat #: report:account.invoice.intrastat:0 @@ -194,7 +194,7 @@ msgstr "Land" #. module: report_intrastat #: selection:report.intrastat,month:0 msgid "September" -msgstr "September" +msgstr "september" #. module: report_intrastat #: report:account.invoice.intrastat:0 diff --git a/addons/stock/i18n/ja.po b/addons/stock/i18n/ja.po index d73bbf6d106..6b009d0dfd7 100644 --- a/addons/stock/i18n/ja.po +++ b/addons/stock/i18n/ja.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-02-08 01:37+0100\n" -"PO-Revision-Date: 2012-06-24 02:23+0000\n" +"PO-Revision-Date: 2012-06-27 01:22+0000\n" "Last-Translator: Akira Hiyama \n" "Language-Team: Japanese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-06-25 05:14+0000\n" +"X-Launchpad-Export-Date: 2012-06-27 05:28+0000\n" "X-Generator: Launchpad (build 15482)\n" #. module: stock @@ -886,7 +886,7 @@ msgstr "" #. module: stock #: view:report.stock.inventory:0 view:stock.move:0 view:stock.picking:0 msgid "Scrap" -msgstr "スクラップ" +msgstr "廃棄" #. module: stock #: field:stock.location,child_ids:0 @@ -1256,12 +1256,12 @@ msgstr "梱包の識別" #. module: stock #: report:stock.picking.list:0 msgid "Packing List:" -msgstr "" +msgstr "梱包リスト:" #. module: stock #: selection:stock.move,state:0 msgid "Waiting Another Move" -msgstr "" +msgstr "他の移動待ち" #. module: stock #: help:product.template,property_stock_production:0 @@ -1269,25 +1269,25 @@ msgid "" "For the current product, this stock location will be used, instead of the " "default one, as the source location for stock moves generated by production " "orders" -msgstr "" +msgstr "現在の製品のため、製造オーダーにより在庫移動は生成されるための元の場所として、デフォルトの代わりにこの在庫場所は使用されます。" #. module: stock #: view:product.product:0 msgid "Expected Stock Variations" -msgstr "" +msgstr "予想在庫変動" #. module: stock #: help:stock.move,price_unit:0 msgid "" "Technical field used to record the product cost set by the user during a " "picking confirmation (when average price costing method is used)" -msgstr "" +msgstr "集荷確認(平均価格コスト方式が使われる時)の間に、ユーザによる製品コストの設定を記録するために使用される技術的な項目" #. module: stock #: code:addons/stock/stock.py:1350 #, python-format msgid "is in draft state." -msgstr "" +msgstr "ドラフト状態です。" #. module: stock #: model:ir.actions.act_window,help:stock.action_tracking_form @@ -1299,12 +1299,12 @@ msgstr "これはあなたの全ての梱包リストです。梱包を選択す #. module: stock #: selection:stock.return.picking,invoice_state:0 msgid "To be refunded/invoiced" -msgstr "" +msgstr "返金 / 請求" #. module: stock #: model:stock.location,name:stock.stock_location_shop0 msgid "Shop 1" -msgstr "" +msgstr "店 1" #. module: stock #: view:stock.change.product.qty:0 view:stock.change.standard.price:0 @@ -1313,50 +1313,50 @@ msgstr "" #: view:stock.move:0 view:stock.partial.move:0 view:stock.partial.picking:0 #: view:stock.picking:0 view:stock.return.picking:0 view:stock.split.into:0 msgid "_Cancel" -msgstr "" +msgstr "キャンセル" #. module: stock #: view:stock.move:0 view:stock.picking:0 msgid "Ready" -msgstr "" +msgstr "準備完了" #. module: stock #: view:stock.picking:0 msgid "Calendar View" -msgstr "" +msgstr "カレンダービュー" #. module: stock #: view:stock.picking:0 msgid "Additional Info" -msgstr "" +msgstr "付加情報" #. module: stock #: field:stock.partial.move.line,move_id:0 #: field:stock.partial.picking.line,move_id:0 #: field:stock.return.picking.memory,move_id:0 msgid "Move" -msgstr "" +msgstr "移動" #. module: stock #: field:stock.location.product,from_date:0 msgid "From" -msgstr "" +msgstr "開始" #. module: stock #: view:stock.picking:0 msgid "Incoming Shipments already processed" -msgstr "" +msgstr "内部出荷は処理済です。" #. module: stock #: code:addons/stock/wizard/stock_return_picking.py:99 #, python-format msgid "You may only return pickings that are Confirmed, Available or Done!" -msgstr "" +msgstr "確認済、利用可能、完了の集荷のみ返却できます。" #. module: stock #: view:stock.picking:0 field:stock.picking,invoice_state:0 msgid "Invoice Control" -msgstr "" +msgstr "請求書制御" #. module: stock #: model:ir.model,name:stock.model_stock_production_lot_revision @@ -1366,49 +1366,49 @@ msgstr "製造ロットのリビジョン" #. module: stock #: view:stock.picking:0 msgid "Internal Picking List" -msgstr "" +msgstr "内部集荷リスト" #. module: stock #: selection:report.stock.inventory,state:0 #: selection:report.stock.move,state:0 view:stock.picking:0 msgid "Waiting" -msgstr "" +msgstr "待機中" #. module: stock #: view:stock.move:0 view:stock.picking:0 msgid "Split" -msgstr "" +msgstr "分割" #. module: stock #: view:stock.picking:0 msgid "Search Stock Picking" -msgstr "" +msgstr "在庫集荷の検索" #. module: stock #: code:addons/stock/product.py:92 #, python-format msgid "Company is not specified in Location" -msgstr "" +msgstr "会社が場所に指定されていません。" #. module: stock #: view:report.stock.move:0 msgid "Type" -msgstr "" +msgstr "タイプ" #. module: stock #: view:stock.picking:0 msgid "Available Pickings" -msgstr "" +msgstr "利用可能集荷" #. module: stock #: model:stock.location,name:stock.stock_location_5 msgid "Generic IT Suppliers" -msgstr "" +msgstr "一般的なIT仕入先" #. module: stock #: view:stock.move:0 msgid "Stock to be receive" -msgstr "" +msgstr "受領する在庫" #. module: stock #: help:stock.location,valuation_out_account_id:0 @@ -1419,61 +1419,63 @@ msgid "" "the generic Stock Output Account set on the product. This has no effect for " "internal locations." msgstr "" +"リアルタイム在庫評価のために使用されます。仮想の場所(非内部タイプ)に設定されている場合、このアカウントは製品にセットされる一般的な在庫出力アカウントの代" +"わりに、この場所から内部の場所に移動される製品の価値を保持するために使用されます。" #. module: stock #: report:stock.picking.list:0 msgid "Picking List:" -msgstr "" +msgstr "集荷リスト:" #. module: stock #: field:stock.inventory,date:0 field:stock.move,create_date:0 #: field:stock.production.lot,date:0 field:stock.tracking,date:0 msgid "Creation Date" -msgstr "" +msgstr "作成日" #. module: stock #: field:report.stock.lines.date,id:0 msgid "Inventory Line Id" -msgstr "" +msgstr "在庫行ID" #. module: stock #: help:stock.location,address_id:0 msgid "Address of customer or supplier." -msgstr "" +msgstr "顧客または仕入先住所" #. module: stock #: view:report.stock.move:0 field:report.stock.move,picking_id:0 msgid "Packing" -msgstr "" +msgstr "梱包" #. module: stock #: selection:report.stock.inventory,location_type:0 #: field:res.partner,property_stock_customer:0 #: selection:stock.location,usage:0 msgid "Customer Location" -msgstr "" +msgstr "顧客の場所" #. module: stock #: code:addons/stock/stock.py:2312 #, python-format msgid "You can only delete draft moves." -msgstr "" +msgstr "ドラフト移動のみ削除ができます。" #. module: stock #: model:ir.model,name:stock.model_stock_inventory_line_split_lines msgid "Inventory Split lines" -msgstr "" +msgstr "在庫分割行" #. module: stock #: model:ir.actions.report.xml,name:stock.report_location_overview #: report:lot.stock.overview:0 msgid "Location Inventory Overview" -msgstr "在庫の場所の概観" +msgstr "在庫場所の概要" #. module: stock #: view:stock.inventory:0 msgid "General Informations" -msgstr "" +msgstr "一般情報" #. module: stock #: view:report.stock.inventory:0 @@ -1489,34 +1491,34 @@ msgstr "下流追跡能力" #: code:addons/stock/wizard/stock_invoice_onshipping.py:112 #, python-format msgid "No Invoices were created" -msgstr "" +msgstr "請求書は作成されませんでした。" #. module: stock #: field:stock.location,posy:0 msgid "Shelves (Y)" -msgstr "" +msgstr "棚(Y)" #. module: stock #: help:stock.incoterms,active:0 msgid "" "By unchecking the active field, you may hide an INCOTERM without deleting it." -msgstr "" +msgstr "アクティブな項目のチェックを外すことで、インコタームズを削除することなく非表示にできます。" #. module: stock #: view:stock.move:0 view:stock.picking:0 field:stock.picking,date:0 msgid "Order Date" -msgstr "" +msgstr "オーダー日" #. module: stock #: code:addons/stock/wizard/stock_change_product_qty.py:88 #, python-format msgid "INV: %s" -msgstr "" +msgstr "請求書:%s" #. module: stock #: view:stock.location:0 field:stock.location,location_id:0 msgid "Parent Location" -msgstr "" +msgstr "親の場所" #. module: stock #: help:stock.picking,state:0 @@ -1529,34 +1531,40 @@ msgid "" "* Done: has been processed, can't be modified or cancelled anymore\n" "* Cancelled: has been cancelled, can't be confirmed anymore" msgstr "" +"・ ドラフト:まだ確認されておらず、そして確認されるまでスケジュールされません。\n" +"・ 確認済:製品が利用可能になるのを待っています。\n" +"・ 利用可能:製品が確保され、単純に確認を待っています。\n" +"・ 待機中:自動的に利用可能になる前に(例えばオーダー作成フロー)、処理のために他の移動を待っています。\n" +"・ 完了:処理されたもの。もはや変更もキャンセルもできません。\n" +"・ キャンセル済:キャンセルされたのもの。もはや確認できません。" #. module: stock #: help:stock.location,company_id:0 msgid "Let this field empty if this location is shared between all companies" -msgstr "" +msgstr "この場所が全ての会社間で共有される場合は、この項目を空にしましょう。" #. module: stock #: code:addons/stock/stock.py:2337 #, python-format msgid "Please provide a positive quantity to scrap!" -msgstr "" +msgstr "廃棄するには正の数量を与えて下さい。" #. module: stock #: field:stock.location,chained_delay:0 msgid "Chaining Lead Time" -msgstr "" +msgstr "連鎖的なリードタイム" #. module: stock #: help:product.product,track_outgoing:0 msgid "" "Forces to specify a Production Lot for all moves containing this product and " "going to a Customer Location" -msgstr "" +msgstr "この製品を含む全ての移動と顧客の場所に行くのための製造ロットを強制的に定義します。" #. module: stock #: model:ir.model,name:stock.model_stock_invoice_onshipping msgid "Stock Invoice Onshipping" -msgstr "" +msgstr "出荷される在庫の請求書" #. module: stock #: help:stock.move,state:0 @@ -1568,43 +1576,48 @@ msgid "" " When the picking is done the state is 'Done'. \n" "The state is 'Waiting' if the move is waiting for another one." msgstr "" +"在庫移動が作成された時にはドラフト状態です。\n" +" その後、スケジューラが製品を持つける事ができなかった場合は利用不可にセットされます。\n" +" 製品が確保されると、利用可能にセットされます。\n" +" 集荷が完了すると、状態は完了になります。\n" +"移動が他の移動のために待ち状態になると、状態は待機中となります。" #. module: stock #: selection:report.stock.inventory,location_type:0 #: field:res.partner,property_stock_supplier:0 #: selection:stock.location,usage:0 msgid "Supplier Location" -msgstr "" +msgstr "仕入先の場所" #. module: stock #: code:addons/stock/stock.py:2358 #, python-format msgid "were scrapped" -msgstr "" +msgstr "廃棄されました。" #. module: stock #: help:stock.partial.move.line,currency:0 #: help:stock.partial.picking.line,currency:0 msgid "Currency in which Unit cost is expressed" -msgstr "" +msgstr "単価が表現される通貨" #. module: stock #: selection:report.stock.inventory,month:0 #: selection:report.stock.move,month:0 msgid "September" -msgstr "" +msgstr "9月" #. module: stock #: help:stock.picking,backorder_id:0 msgid "" "If this picking was split this field links to the picking that contains the " "other part that has been processed already." -msgstr "" +msgstr "集荷が分割される場合、この項目は既に処理された他の部分を含む集荷にリンクします。" #. module: stock #: model:ir.model,name:stock.model_report_stock_inventory msgid "Stock Statistics" -msgstr "" +msgstr "在庫の統計値" #. module: stock #: view:report.stock.move:0 @@ -1781,7 +1794,7 @@ msgstr "" #: model:ir.model,name:stock.model_stock_move_scrap view:stock.move:0 #: view:stock.move.scrap:0 view:stock.picking:0 msgid "Scrap Products" -msgstr "" +msgstr "廃棄製品" #. module: stock #: code:addons/stock/stock.py:1157 @@ -2167,7 +2180,7 @@ msgstr "" #: model:stock.location,name:stock.stock_location_scrapped #: field:stock.move,scrapped:0 msgid "Scrapped" -msgstr "" +msgstr "廃棄済" #. module: stock #: view:stock.inventory:0 @@ -2488,7 +2501,7 @@ msgstr "" #. module: stock #: model:ir.actions.act_window,name:stock.move_scrap view:stock.move.scrap:0 msgid "Scrap Move" -msgstr "" +msgstr "廃棄移動" #. module: stock #: model:ir.actions.act_window,name:stock.action_reception_picking_move @@ -3330,7 +3343,7 @@ msgstr "" #: help:stock.location,scrap_location:0 msgid "" "Check this box to allow using this location to put scrapped/damaged goods." -msgstr "" +msgstr "廃棄 / 破損品を置くためにこの場所を使用する場合は、このボックスをチェックします。" #. module: stock #: model:ir.actions.act_window,name:stock.act_relate_picking @@ -3938,7 +3951,7 @@ msgstr "" #. module: stock #: field:stock.location,scrap_location:0 view:stock.move.scrap:0 msgid "Scrap Location" -msgstr "" +msgstr "廃棄場所" #. module: stock #: selection:report.stock.inventory,month:0 From 3b8573f7ce273948826cffbffd7f2cfa91665e3c Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 27 Jun 2012 11:25:35 +0200 Subject: [PATCH 28/30] [FIX] analytic_contract_expense_project: removed empty certificate entry from descriptor file (which cause the unique constraint to be impossible to add). bzr revid: vmt@openerp.com-20120627092535-va6fuhc3o65te8u9 --- addons/analytic_contract_expense_project/__openerp__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/analytic_contract_expense_project/__openerp__.py b/addons/analytic_contract_expense_project/__openerp__.py index 514f0bfbbf5..41e0bbd084a 100644 --- a/addons/analytic_contract_expense_project/__openerp__.py +++ b/addons/analytic_contract_expense_project/__openerp__.py @@ -41,7 +41,6 @@ This module is for modifying project view to show some data related to the hr_ex ], 'installable': True, 'auto_install': True, - 'certificate': '', } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From ed7b1d1b568e871a4e5591df3e5f301900909788 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 27 Jun 2012 11:41:06 +0200 Subject: [PATCH 29/30] [ADD] Added oe_link css class bzr revid: fme@openerp.com-20120627094106-sg0nk89obcjtlzpl --- addons/web/static/src/css/base.css | 32 +++++++++++++++++++++++++++++ addons/web/static/src/css/base.sass | 19 +++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 72d89921cfd..20d7e46dd11 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -309,6 +309,38 @@ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); border: none; } +.openerp .oe_button.oe_link { + border: none; + padding: 0; + margin: 0; + background: none; + -moz-border-radius: none; + -webkit-border-radius: none; + border-radius: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} +.openerp .oe_button.oe_link img { + display: none; +} +.openerp .oe_button.oe_link span { + border: none; + padding: 0; + margin: 0; + background: none; + -moz-border-radius: none; + -webkit-border-radius: none; + border-radius: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; + color: #8a89ba; + font-weight: bold; +} +.openerp .oe_button.oe_link span:hover { + text-decoration: underline; +} .openerp.oe_tooltip { font-size: 12px; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index cae493ea2e5..f42b527a500 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -27,6 +27,14 @@ $colour4: #8a89ba font-weight: normal font-style: normal +@mixin reset() + border: none + padding: 0 + margin: 0 + background: none + @include radius(none) + @include box-shadow(none) + @mixin vertical-gradient($startColor: #555, $endColor: #333) background-color: $startColor background-image: -webkit-gradient(linear, left top, left bottom, from($startColor), to($endColor)) /* Saf4+, Chrome */ @@ -261,6 +269,17 @@ $colour4: #8a89ba @include radius(3px) @include box-shadow(0 1px 3px rgba(0, 0, 0, 0.3)) border: none + .oe_button.oe_link + @include reset() + img + display: none + span + @include reset() + color: #8A89BA + font-weight: bold + &:hover + text-decoration: underline + //.oe_edit_only  // }}} // Tooltips {{{ From b22523e08242edb8df5d1d39f6a76121833956d4 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 27 Jun 2012 11:41:42 +0200 Subject: [PATCH 30/30] [IMP] opportunity2phonecall wizard, use oe_link on cancel button bzr revid: fme@openerp.com-20120627094142-nut1pqe4vzevvu4z --- addons/crm/wizard/crm_opportunity_to_phonecall_view.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/crm/wizard/crm_opportunity_to_phonecall_view.xml b/addons/crm/wizard/crm_opportunity_to_phonecall_view.xml index ad1a763fa46..de4219fb42d 100644 --- a/addons/crm/wizard/crm_opportunity_to_phonecall_view.xml +++ b/addons/crm/wizard/crm_opportunity_to_phonecall_view.xml @@ -13,7 +13,8 @@