From fc36345ca0730cf8cbd197d9177bc884764188f2 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 23 Jul 2012 17:03:06 +0200 Subject: [PATCH 01/73] [ADD] test cases for export of simple fields bzr revid: xmo@openerp.com-20120723150306-6zgtr9ho0d3ud78u --- openerp/modules/__init__.py | 6 +- openerp/osv/orm.py | 6 +- openerp/tests/__init__.py | 11 +- openerp/tests/common.py | 5 +- openerp/tests/test_export.py | 265 +++++++++++++++++++++++++++++++++++ 5 files changed, 276 insertions(+), 17 deletions(-) create mode 100644 openerp/tests/test_export.py diff --git a/openerp/modules/__init__.py b/openerp/modules/__init__.py index f28c397bb23..56265e353a2 100644 --- a/openerp/modules/__init__.py +++ b/openerp/modules/__init__.py @@ -24,11 +24,7 @@ """ -import openerp.modules.db -import openerp.modules.graph -import openerp.modules.loading -import openerp.modules.migration -import openerp.modules.module +from . import db, graph, loading, migration, module, registry # TODO temporarily expose those things from openerp.modules.module import \ diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 78487e7641c..02a13a41dcb 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4179,11 +4179,7 @@ class BaseModel(object): # Try-except added to filter the creation of those records whose filds are readonly. # Example : any dashboard which has all the fields readonly.(due to Views(database views)) - try: - cr.execute("SELECT nextval('"+self._sequence+"')") - except: - raise except_orm(_('UserError'), - _('You cannot perform this operation. New Record Creation is not allowed for this object as this object is for reporting purpose.')) + cr.execute("SELECT nextval('"+self._sequence+"')") id_new = cr.fetchone()[0] for table in tocreate: diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index 913c5c759c9..7b30adece79 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -8,18 +8,17 @@ Tests can be explicitely added to the `fast_suite` or `checks` lists or not. See the :ref:`test-framework` section in the :ref:`features` list. """ -import test_expression -import test_ir_sequence -import test_orm -import test_uninstall +from . import test_expression, test_ir_sequence, test_orm,\ + test_uninstall, test_export fast_suite = [ test_ir_sequence, - ] +] checks = [ test_expression, test_orm, - ] + test_export, +] # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tests/common.py b/openerp/tests/common.py index ef5b0586136..b1af4338312 100644 --- a/openerp/tests/common.py +++ b/openerp/tests/common.py @@ -39,13 +39,16 @@ class TransactionCase(unittest2.TestCase): """ def setUp(self): - self.cr = openerp.modules.registry.RegistryManager.get(DB).db.cursor() + self.cr = self.cursor() self.uid = openerp.SUPERUSER_ID def tearDown(self): self.cr.rollback() self.cr.close() + def cursor(self): + return openerp.modules.registry.RegistryManager.get(DB).db.cursor() + def registry(self, model): return openerp.modules.registry.RegistryManager.get(DB)[model] diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py new file mode 100644 index 00000000000..b1b51a608dc --- /dev/null +++ b/openerp/tests/test_export.py @@ -0,0 +1,265 @@ +# -*- coding: utf-8 -*- +import psycopg2 + +import openerp.modules.registry +import openerp +from openerp.osv import orm, fields + +from . import common + +models = [ + ('boolean', fields.boolean()), + ('integer', fields.integer()), + ('float', fields.float()), + ('decimal', fields.float(digits=(16, 3))), + ('string.bounded', fields.char('unknown', size=16)), + ('string', fields.char('unknown', size=None)), + ('date', fields.date()), + ('datetime', fields.datetime()), + ('text', fields.text()), + ('selection', fields.selection([(1, "Foo"), (2, "Bar"), (3, "Qux")])), + # TODO: m2o, o2m, m2m + # TODO: function? + # TODO: related? + # TODO: reference? +] +for name, field in models: + attrs = { + '_name': 'export.%s' % name, + '_module': 'base', + '_columns': { + 'value': field + } + } + NewModel = type( + 'Export%s' % ''.join(section.capitalize() for section in name.split('.')), + (orm.Model,), + attrs) + +def setUpModule(): + openerp.tools.config['update'] = dict(base=1) + openerp.modules.registry.RegistryManager.new( + common.DB, update_module=True) + +class CreatorCase(common.TransactionCase): + model_name = False + + def __init__(self, *args, **kwargs): + super(CreatorCase, self).__init__(*args, **kwargs) + self.model = None + + def setUp(self): + super(CreatorCase, self).setUp() + self.model = self.registry(self.model_name) + def make(self, value): + id = self.model.create(self.cr, openerp.SUPERUSER_ID, {'value': value}) + return self.model.browse(self.cr, openerp.SUPERUSER_ID, [id])[0] + def export(self, value, context=None): + record = self.make(value) + return self.model._BaseModel__export_row( + self.cr, openerp.SUPERUSER_ID, record, [["value"]], context=context) + +class test_boolean_field(CreatorCase): + model_name = 'export.boolean' + + def test_true(self): + self.assertEqual( + self.export(True), + [[u'True']]) + def test_false(self): + """ ``False`` value to boolean fields is unique in being exported as a + (unicode) string, not a boolean + """ + self.assertEqual( + self.export(False), + [[u'False']]) + +class test_integer_field(CreatorCase): + model_name = 'export.integer' + + def test_empty(self): + self.assertEqual(self.model.search(self.cr, openerp.SUPERUSER_ID, []), [], + "Test model should have no records") + def test_0(self): + self.assertEqual( + self.export(0), + [[False]]) + + def test_basic_value(self): + self.assertEqual( + self.export(42), + [[u'42']]) + + def test_negative(self): + self.assertEqual( + self.export(-32), + [[u'-32']]) + + def test_huge(self): + self.assertEqual( + self.export(2**31-1), + [[unicode(2**31-1)]]) + +class test_float_field(CreatorCase): + model_name = 'export.float' + + def test_0(self): + self.assertEqual( + self.export(0.0), + [[False]]) + + def test_epsilon(self): + self.assertEqual( + self.export(0.000000000027), + [[u'2.7e-11']]) + + def test_negative(self): + self.assertEqual( + self.export(-2.42), + [[u'-2.42']]) + + def test_positive(self): + self.assertEqual( + self.export(47.36), + [[u'47.36']]) + + def test_big(self): + self.assertEqual( + self.export(87654321.4678), + [[u'87654321.4678']]) + +class test_decimal_field(CreatorCase): + model_name = 'export.decimal' + + def test_0(self): + self.assertEqual( + self.export(0.0), + [[False]]) + + def test_epsilon(self): + """ epsilon gets sliced to 0 due to precision + """ + self.assertEqual( + self.export(0.000000000027), + [[False]]) + + def test_negative(self): + self.assertEqual( + self.export(-2.42), + [[u'-2.42']]) + + def test_positive(self): + self.assertEqual( + self.export(47.36), + [[u'47.36']]) + + def test_big(self): + self.assertEqual( + self.export(87654321.4678), [[u'87654321.468']]) + +class test_string_field(CreatorCase): + model_name = 'export.string.bounded' + + def test_empty(self): + self.assertEqual( + self.export(""), + [[False]]) + def test_within_bounds(self): + self.assertEqual( + self.export("foobar"), + [[u"foobar"]]) + def test_out_of_bounds(self): + self.assertEqual( + self.export("C for Sinking, " + "Java for Drinking, " + "Smalltalk for Thinking. " + "...and Power to the Penguin!"), + [[u"C for Sinking, J"]]) + +class test_unbound_string_field(CreatorCase): + model_name = 'export.string' + + def test_empty(self): + self.assertEqual( + self.export(""), + [[False]]) + def test_small(self): + self.assertEqual( + self.export("foobar"), + [[u"foobar"]]) + def test_big(self): + self.assertEqual( + self.export("We flew down weekly to meet with IBM, but they " + "thought the way to measure software was the amount " + "of code we wrote, when really the better the " + "software, the fewer lines of code."), + [[u"We flew down weekly to meet with IBM, but they thought the " + u"way to measure software was the amount of code we wrote, " + u"when really the better the software, the fewer lines of " + u"code."]]) + +class test_text(CreatorCase): + model_name = 'export.text' + + def test_empty(self): + self.assertEqual( + self.export(""), + [[False]]) + def test_small(self): + self.assertEqual( + self.export("foobar"), + [[u"foobar"]]) + def test_big(self): + self.assertEqual( + self.export("So, `bind' is `let' and monadic programming is" + " equivalent to programming in the A-normal form. That" + " is indeed all there is to monads"), + [[u"So, `bind' is `let' and monadic programming is equivalent to" + u" programming in the A-normal form. That is indeed all there" + u" is to monads"]]) + +class test_date(CreatorCase): + model_name = 'export.date' + + def test_empty(self): + self.assertEqual( + self.export(False), + [[False]]) + def test_basic(self): + self.assertEqual( + self.export('2011-11-07'), + [[u'2011-11-07']]) + +class test_datetime(CreatorCase): + model_name = 'export.datetime' + + def test_empty(self): + self.assertEqual( + self.export(False), + [[False]]) + def test_basic(self): + self.assertEqual( + self.export('2011-11-07 21:05:48'), + [[u'2011-11-07 21:05:48']]) + def test_tz(self): + """ Export ignores the timezone and always exports to UTC + """ + self.assertEqual( + self.export('2011-11-07 21:05:48', {'tz': 'Pacific/Norfolk'}), + [[u'2011-11-07 21:05:48']]) + +class test_selection(CreatorCase): + model_name = 'export.selection' + + def test_empty(self): + self.assertEqual( + self.export(False), + [[False]]) + + def test_value(self): + """ selections export the *label* for their value + """ + self.assertEqual( + self.export(2), + [[u"Bar"]]) + From feb9efed4dd58dc3bbb20b58ee49b269d726f4e3 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 23 Jul 2012 17:38:26 +0200 Subject: [PATCH 02/73] [IMP] prepare export test cases for relational fields bzr revid: xmo@openerp.com-20120723153826-hu7cwhj8uv6w0xu7 --- openerp/tests/test_export.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index b1b51a608dc..401a90fd635 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -54,10 +54,12 @@ class CreatorCase(common.TransactionCase): def make(self, value): id = self.model.create(self.cr, openerp.SUPERUSER_ID, {'value': value}) return self.model.browse(self.cr, openerp.SUPERUSER_ID, [id])[0] - def export(self, value, context=None): + def export(self, value, fields=('value',), context=None): record = self.make(value) return self.model._BaseModel__export_row( - self.cr, openerp.SUPERUSER_ID, record, [["value"]], context=context) + self.cr, openerp.SUPERUSER_ID, record, + [f.split('/') for f in fields], + context=context) class test_boolean_field(CreatorCase): model_name = 'export.boolean' @@ -245,7 +247,7 @@ class test_datetime(CreatorCase): """ Export ignores the timezone and always exports to UTC """ self.assertEqual( - self.export('2011-11-07 21:05:48', {'tz': 'Pacific/Norfolk'}), + self.export('2011-11-07 21:05:48', context={'tz': 'Pacific/Norfolk'}), [[u'2011-11-07 21:05:48']]) class test_selection(CreatorCase): From 5850df630c87ab2474fec8827af64c99d184e122 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 23 Jul 2012 18:00:41 +0200 Subject: [PATCH 03/73] [ADD] m2o field export tests bzr revid: xmo@openerp.com-20120723160041-o6w24ia2kwnqu19z --- openerp/tests/test_export.py | 41 +++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index 401a90fd635..61c71510f42 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -18,6 +18,8 @@ models = [ ('datetime', fields.datetime()), ('text', fields.text()), ('selection', fields.selection([(1, "Foo"), (2, "Bar"), (3, "Qux")])), + # just relate to an integer + ('many2one', fields.many2one('export.integer')), # TODO: m2o, o2m, m2m # TODO: function? # TODO: related? @@ -29,7 +31,11 @@ for name, field in models: '_module': 'base', '_columns': { 'value': field - } + }, + 'name_get': (lambda self, cr, uid, ids, context=None: + [(record.id, "%s:%s" % (self._name, record.value)) + for record in self.browse(cr, uid, ids, context=context)]) + } NewModel = type( 'Export%s' % ''.join(section.capitalize() for section in name.split('.')), @@ -265,3 +271,36 @@ class test_selection(CreatorCase): self.export(2), [[u"Bar"]]) +class test_m2o(CreatorCase): + model_name = 'export.many2one' + + def test_empty(self): + self.assertEqual( + self.export(False), + [[False]]) + def test_basic(self): + """ Exported value is the name_get of the related object + """ + integer_id = self.registry('export.integer').create( + self.cr, openerp.SUPERUSER_ID, {'value': 42}) + name = dict(self.registry('export.integer').name_get( + self.cr, openerp.SUPERUSER_ID,[integer_id]))[integer_id] + self.assertEqual( + self.export(integer_id), + [[name]]) + def test_path(self): + """ Can recursively export fields of m2o via path + """ + integer_id = self.registry('export.integer').create( + self.cr, openerp.SUPERUSER_ID, {'value': 42}) + self.assertEqual( + self.export(integer_id, fields=['value/.id', 'value/value']), + [[unicode(integer_id), u'42']]) + def test_external_id(self): + integer_id = self.registry('export.integer').create( + self.cr, openerp.SUPERUSER_ID, {'value': 42}) + # __export__.$class.$id + external_id = u'__export__.export_many2one_%d' % integer_id + self.assertEqual( + self.export(integer_id, fields=['value/id']), + [[external_id]]) From 02109d24e244037483d86f33e0532a0b455deca6 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 24 Jul 2012 12:27:28 +0200 Subject: [PATCH 04/73] [ADD] tests for o2m field export bzr revid: xmo@openerp.com-20120724102728-g7noucvda19k7k5q --- openerp/tests/test_export.py | 119 ++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index 61c71510f42..c46ef53837e 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -20,7 +20,8 @@ models = [ ('selection', fields.selection([(1, "Foo"), (2, "Bar"), (3, "Qux")])), # just relate to an integer ('many2one', fields.many2one('export.integer')), - # TODO: m2o, o2m, m2m + ('one2many', fields.one2many('export.one2many.child', 'parent_id')), + # TODO: m2m # TODO: function? # TODO: related? # TODO: reference? @@ -30,18 +31,35 @@ for name, field in models: '_name': 'export.%s' % name, '_module': 'base', '_columns': { + 'const': fields.integer(), 'value': field }, + '_defaults': {'const': 4}, 'name_get': (lambda self, cr, uid, ids, context=None: [(record.id, "%s:%s" % (self._name, record.value)) for record in self.browse(cr, uid, ids, context=context)]) - } NewModel = type( 'Export%s' % ''.join(section.capitalize() for section in name.split('.')), (orm.Model,), attrs) +class One2ManyChild(orm.Model): + _name = 'export.one2many.child' + _module = 'base' + # FIXME: orm.py:1161, fix to name_get on m2o field + _rec_name = 'value' + + _columns = { + 'parent_id': fields.many2one('export.one2many'), + 'str': fields.char('unknown', size=None), + 'value': fields.integer() + } + def name_get(self, cr, uid, ids, context=None): + return [(record.id, "%s:%s" % (self._name, record.value)) + for record in self.browse(cr, uid, ids, context=context)] + + def setUpModule(): openerp.tools.config['update'] = dict(base=1) openerp.modules.registry.RegistryManager.new( @@ -251,6 +269,8 @@ class test_datetime(CreatorCase): [[u'2011-11-07 21:05:48']]) def test_tz(self): """ Export ignores the timezone and always exports to UTC + + .. note:: on the other hand, export uses user lang for name_get """ self.assertEqual( self.export('2011-11-07 21:05:48', context={'tz': 'Pacific/Norfolk'}), @@ -304,3 +324,98 @@ class test_m2o(CreatorCase): self.assertEqual( self.export(integer_id, fields=['value/id']), [[external_id]]) + +class test_o2m(CreatorCase): + model_name = 'export.one2many' + commands = [ + (0, False, {'value': 4, 'str': 'record1'}), + (0, False, {'value': 42, 'str': 'record2'}), + (0, False, {'value': 36, 'str': 'record3'}), + (0, False, {'value': 4, 'str': 'record4'}), + (0, False, {'value': 13, 'str': 'record5'}), + ] + names = [ + u'export.one2many.child:%d' % d['value'] + for c, _, d in commands + ] + + def test_empty(self): + self.assertEqual( + self.export(False), + [[False]]) + + def test_single(self): + self.assertEqual( + self.export([(0, False, {'value': 42})]), + # name_get result + [[u'export.one2many.child:42']]) + + def test_single_subfield(self): + self.assertEqual( + self.export([(0, False, {'value': 42})], + fields=['value', 'value/value']), + [[u'export.one2many.child:42', u'42']]) + + def test_integrate_one_in_parent(self): + self.assertEqual( + self.export([(0, False, {'value': 42})], + fields=['const', 'value/value']), + [[u'4', u'42']]) + + def test_multiple_records(self): + self.assertEqual( + self.export(self.commands, fields=['const', 'value/value']), + [ + [u'4', u'4'], + [u'', u'42'], + [u'', u'36'], + [u'', u'4'], + [u'', u'13'], + ]) + + def test_multiple_records_name(self): + self.assertEqual( + self.export(self.commands, fields=['const', 'value']), + [[ + u'4', u','.join(self.names) + ]]) + + def test_multiple_records_with_name_before(self): + self.assertEqual( + self.export(self.commands, fields=['const', 'value', 'value/value']), + [[ # exports sub-fields of very first o2m + u'4', u','.join(self.names), u'4' + ]]) + + def test_multiple_records_with_name_before(self): + self.assertEqual( + self.export(self.commands, fields=['const', 'value/value', 'value']), + [ # completely ignores name_get request + [u'4', u'4', ''], + [u'', u'42', ''], + [u'', u'36', ''], + [u'', u'4', ''], + [u'', u'13', ''], + ]) + + def test_multiple_subfields_neighbour(self): + self.assertEqual( + self.export(self.commands, fields=['const', 'value/str','value/value']), + [ + [u'4', u'record1', u'4'], + ['', u'record2', u'42'], + ['', u'record3', u'36'], + ['', u'record4', u'4'], + ['', u'record5', u'13'], + ]) + + def test_multiple_subfields_separated(self): + self.assertEqual( + self.export(self.commands, fields=['value/str', 'const', 'value/value']), + [ + [u'record1', u'4', u'4'], + [u'record2', '', u'42'], + [u'record3', '', u'36'], + [u'record4', '', u'4'], + [u'record5', '', u'13'], + ]) From f3c315cf10e2fb766586e2c8c9e03e7b228c800b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 24 Jul 2012 12:57:56 +0200 Subject: [PATCH 05/73] [ADD] some m2m tests bzr revid: xmo@openerp.com-20120724105756-swxo1l4tahqjopzd --- openerp/tests/test_export.py | 80 +++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index c46ef53837e..030466dc674 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -21,7 +21,7 @@ models = [ # just relate to an integer ('many2one', fields.many2one('export.integer')), ('one2many', fields.one2many('export.one2many.child', 'parent_id')), - # TODO: m2m + ('many2many', fields.many2many('export.many2many.other')) # TODO: function? # TODO: related? # TODO: reference? @@ -59,6 +59,19 @@ class One2ManyChild(orm.Model): return [(record.id, "%s:%s" % (self._name, record.value)) for record in self.browse(cr, uid, ids, context=context)] +class Many2ManyChild(orm.Model): + _name = 'export.many2many.other' + _module = 'base' + # FIXME: orm.py:1161, fix to name_get on m2o field + _rec_name = 'value' + + _columns = { + 'str': fields.char('unknown', size=None), + 'value': fields.integer() + } + def name_get(self, cr, uid, ids, context=None): + return [(record.id, "%s:%s" % (self._name, record.value)) + for record in self.browse(cr, uid, ids, context=context)] def setUpModule(): openerp.tools.config['update'] = dict(base=1) @@ -392,10 +405,10 @@ class test_o2m(CreatorCase): self.export(self.commands, fields=['const', 'value/value', 'value']), [ # completely ignores name_get request [u'4', u'4', ''], - [u'', u'42', ''], - [u'', u'36', ''], - [u'', u'4', ''], - [u'', u'13', ''], + ['', u'42', ''], + ['', u'36', ''], + ['', u'4', ''], + ['', u'13', ''], ]) def test_multiple_subfields_neighbour(self): @@ -419,3 +432,60 @@ class test_o2m(CreatorCase): [u'record4', '', u'4'], [u'record5', '', u'13'], ]) + +class test_m2m(CreatorCase): + model_name = 'export.many2many' + commands = [ + (0, False, {'value': 4, 'str': 'record000'}), + (0, False, {'value': 42, 'str': 'record001'}), + (0, False, {'value': 36, 'str': 'record010'}), + (0, False, {'value': 4, 'str': 'record011'}), + (0, False, {'value': 13, 'str': 'record100'}), + ] + names = [ + u'export.many2many.other:%d' % d['value'] + for c, _, d in commands + ] + + def test_empty(self): + self.assertEqual( + self.export(False), + [[False]]) + + def test_single(self): + self.assertEqual( + self.export([(0, False, {'value': 42})]), + # name_get result + [[u'export.many2many.other:42']]) + + def test_single_subfield(self): + self.assertEqual( + self.export([(0, False, {'value': 42})], + fields=['value', 'value/value']), + [[u'export.many2many.other:42', u'42']]) + + def test_integrate_one_in_parent(self): + self.assertEqual( + self.export([(0, False, {'value': 42})], + fields=['const', 'value/value']), + [[u'4', u'42']]) + + def test_multiple_records(self): + self.assertEqual( + self.export(self.commands, fields=['const', 'value/value']), + [ + [u'4', u'4'], + [u'', u'42'], + [u'', u'36'], + [u'', u'4'], + [u'', u'13'], + ]) + + def test_multiple_records_name(self): + self.assertEqual( + self.export(self.commands, fields=['const', 'value']), + [[ + u'4', u','.join(self.names) + ]]) + + # essentially same as o2m, so boring From 53c82f4086e2fd2a029106fbbcc95beaad53c250 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 12:08:04 +0200 Subject: [PATCH 06/73] [TEST] export of multiple o2m fields in the same object bzr revid: xmo@openerp.com-20120726100804-ggsjwco93k9d5bqw --- openerp/tests/export_models.py | 102 ++++++++++++++++++++ openerp/tests/test_export.py | 167 +++++++++++++++++++-------------- 2 files changed, 200 insertions(+), 69 deletions(-) create mode 100644 openerp/tests/export_models.py diff --git a/openerp/tests/export_models.py b/openerp/tests/export_models.py new file mode 100644 index 00000000000..d54c73ac4ad --- /dev/null +++ b/openerp/tests/export_models.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +from openerp.osv import orm, fields + +models = [ + ('boolean', fields.boolean()), + ('integer', fields.integer()), + ('float', fields.float()), + ('decimal', fields.float(digits=(16, 3))), + ('string.bounded', fields.char('unknown', size=16)), + ('string', fields.char('unknown', size=None)), + ('date', fields.date()), + ('datetime', fields.datetime()), + ('text', fields.text()), + ('selection', fields.selection([(1, "Foo"), (2, "Bar"), (3, "Qux")])), + # just relate to an integer + ('many2one', fields.many2one('export.integer')), + ('one2many', fields.one2many('export.one2many.child', 'parent_id')), + ('many2many', fields.many2many('export.many2many.other')) + # TODO: function? + # TODO: related? + # TODO: reference? +] +for name, field in models: + attrs = { + '_name': 'export.%s' % name, + '_module': 'base', + '_columns': { + 'const': fields.integer(), + 'value': field + }, + '_defaults': {'const': 4}, + 'name_get': (lambda self, cr, uid, ids, context=None: + [(record.id, "%s:%s" % (self._name, record.value)) + for record in self.browse(cr, uid, ids, context=context)]) + } + NewModel = type( + 'Export%s' % ''.join(section.capitalize() for section in name.split('.')), + (orm.Model,), + attrs) + +class One2ManyChild(orm.Model): + _name = 'export.one2many.child' + _module = 'base' + # FIXME: orm.py:1161, fix to name_get on m2o field + _rec_name = 'value' + + _columns = { + 'parent_id': fields.many2one('export.one2many'), + 'str': fields.char('unknown', size=None), + 'value': fields.integer() + } + def name_get(self, cr, uid, ids, context=None): + return [(record.id, "%s:%s" % (self._name, record.value)) + for record in self.browse(cr, uid, ids, context=context)] + +class One2ManyMultiple(orm.Model): + _name = 'export.one2many.multiple' + _module = 'base' + + _columns = { + 'const': fields.integer(), + 'child1': fields.one2many('export.one2many.child.1', 'parent_id'), + 'child2': fields.one2many('export.one2many.child.2', 'parent_id'), + } + _defaults = { 'const': 36 } + +class One2ManyChildMultiple(orm.Model): + _name = 'export.one2many.multiple.child' + _module = 'base' + # FIXME: orm.py:1161, fix to name_get on m2o field + _rec_name = 'value' + + _columns = { + 'parent_id': fields.many2one('export.one2many.multiple'), + 'str': fields.char('unknown', size=None), + 'value': fields.integer() + } + def name_get(self, cr, uid, ids, context=None): + return [(record.id, "%s:%s" % (self._name, record.value)) + for record in self.browse(cr, uid, ids, context=context)] +class One2ManyChild1(orm.Model): + _name = 'export.one2many.child.1' + _module = 'base' + _inherit = 'export.one2many.multiple.child' +class One2ManyChild2(orm.Model): + _name = 'export.one2many.child.2' + _module = 'base' + _inherit = 'export.one2many.multiple.child' + +class Many2ManyChild(orm.Model): + _name = 'export.many2many.other' + _module = 'base' + # FIXME: orm.py:1161, fix to name_get on m2o field + _rec_name = 'value' + + _columns = { + 'str': fields.char('unknown', size=None), + 'value': fields.integer() + } + def name_get(self, cr, uid, ids, context=None): + return [(record.id, "%s:%s" % (self._name, record.value)) + for record in self.browse(cr, uid, ids, context=context)] diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index 030466dc674..ea21cd36a8a 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -1,77 +1,10 @@ # -*- coding: utf-8 -*- -import psycopg2 - +import itertools import openerp.modules.registry import openerp -from openerp.osv import orm, fields -from . import common +from . import common, export_models -models = [ - ('boolean', fields.boolean()), - ('integer', fields.integer()), - ('float', fields.float()), - ('decimal', fields.float(digits=(16, 3))), - ('string.bounded', fields.char('unknown', size=16)), - ('string', fields.char('unknown', size=None)), - ('date', fields.date()), - ('datetime', fields.datetime()), - ('text', fields.text()), - ('selection', fields.selection([(1, "Foo"), (2, "Bar"), (3, "Qux")])), - # just relate to an integer - ('many2one', fields.many2one('export.integer')), - ('one2many', fields.one2many('export.one2many.child', 'parent_id')), - ('many2many', fields.many2many('export.many2many.other')) - # TODO: function? - # TODO: related? - # TODO: reference? -] -for name, field in models: - attrs = { - '_name': 'export.%s' % name, - '_module': 'base', - '_columns': { - 'const': fields.integer(), - 'value': field - }, - '_defaults': {'const': 4}, - 'name_get': (lambda self, cr, uid, ids, context=None: - [(record.id, "%s:%s" % (self._name, record.value)) - for record in self.browse(cr, uid, ids, context=context)]) - } - NewModel = type( - 'Export%s' % ''.join(section.capitalize() for section in name.split('.')), - (orm.Model,), - attrs) - -class One2ManyChild(orm.Model): - _name = 'export.one2many.child' - _module = 'base' - # FIXME: orm.py:1161, fix to name_get on m2o field - _rec_name = 'value' - - _columns = { - 'parent_id': fields.many2one('export.one2many'), - 'str': fields.char('unknown', size=None), - 'value': fields.integer() - } - def name_get(self, cr, uid, ids, context=None): - return [(record.id, "%s:%s" % (self._name, record.value)) - for record in self.browse(cr, uid, ids, context=context)] - -class Many2ManyChild(orm.Model): - _name = 'export.many2many.other' - _module = 'base' - # FIXME: orm.py:1161, fix to name_get on m2o field - _rec_name = 'value' - - _columns = { - 'str': fields.char('unknown', size=None), - 'value': fields.integer() - } - def name_get(self, cr, uid, ids, context=None): - return [(record.id, "%s:%s" % (self._name, record.value)) - for record in self.browse(cr, uid, ids, context=context)] def setUpModule(): openerp.tools.config['update'] = dict(base=1) @@ -433,6 +366,102 @@ class test_o2m(CreatorCase): [u'record5', '', u'13'], ]) +# todo: test with multiple o2m fields and exporting all +class test_o2m_multiple(CreatorCase): + model_name = 'export.one2many.multiple' + + def make(self, value=None, **values): + if value is not None: values['value'] = value + id = self.model.create(self.cr, openerp.SUPERUSER_ID, values) + return self.model.browse(self.cr, openerp.SUPERUSER_ID, [id])[0] + def export(self, value=None, fields=('child1', 'child2',), context=None, **values): + record = self.make(value, **values) + return self.model._BaseModel__export_row( + self.cr, openerp.SUPERUSER_ID, record, + [f.split('/') for f in fields], + context=context) + + def test_empty(self): + self.assertEqual( + self.export(child1=False, child2=False), + [[False, False]]) + + def test_single_per_side(self): + self.assertEqual( + self.export(child1=False, child2=[(0, False, {'value': 42})]), + [[False, u'export.one2many.child.2:42']]) + + self.assertEqual( + self.export(child1=[(0, False, {'value': 43})], child2=False), + [[u'export.one2many.child.1:43', False]]) + + self.assertEqual( + self.export(child1=[(0, False, {'value': 43})], + child2=[(0, False, {'value': 42})]), + [[u'export.one2many.child.1:43', u'export.one2many.child.2:42']]) + + def test_single_integrate_subfield(self): + fields = ['const', 'child1/value', 'child2/value'] + self.assertEqual( + self.export(child1=False, child2=[(0, False, {'value': 42})], + fields=fields), + [[u'36', False, u'42']]) + + self.assertEqual( + self.export(child1=[(0, False, {'value': 43})], child2=False, + fields=fields), + [[u'36', u'43', False]]) + + self.assertEqual( + self.export(child1=[(0, False, {'value': 43})], + child2=[(0, False, {'value': 42})], + fields=fields), + [[u'36', u'43', u'42']]) + + def test_multiple(self): + """ With two "concurrent" o2ms, exports the first line combined, then + exports the rows for the first o2m, then the rows for the second o2m. + """ + fields = ['const', 'child1/value', 'child2/value'] + child1 = [(0, False, {'value': v, 'str': 'record%.02d' % index}) + for index, v in zip(itertools.count(), [4, 42, 36, 4, 13])] + child2 = [(0, False, {'value': v, 'str': 'record%.02d' % index}) + for index, v in zip(itertools.count(10), [8, 12, 8, 55, 33, 13])] + + self.assertEqual( + self.export(child1=child1, child2=False, fields=fields), + [ + [u'36', u'4', False], + ['', u'42', ''], + ['', u'36', ''], + ['', u'4', ''], + ['', u'13', ''], + ]) + self.assertEqual( + self.export(child1=False, child2=child2, fields=fields), + [ + [u'36', False, u'8'], + ['', '', u'12'], + ['', '', u'8'], + ['', '', u'55'], + ['', '', u'33'], + ['', '', u'13'], + ]) + self.assertEqual( + self.export(child1=child1, child2=child2, fields=fields), + [ + [u'36', u'4', u'8'], + ['', u'42', ''], + ['', u'36', ''], + ['', u'4', ''], + ['', u'13', ''], + ['', '', u'12'], + ['', '', u'8'], + ['', '', u'55'], + ['', '', u'33'], + ['', '', u'13'], + ]) + class test_m2m(CreatorCase): model_name = 'export.many2many' commands = [ From ad6742b6b873507b577231323392b70dea96512c Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Thu, 26 Jul 2012 13:55:50 +0200 Subject: [PATCH 07/73] [FIX] kanban group dropdown bzr revid: al@openerp.com-20120726115550-bu4jp1163bb4ov6n --- addons/web_kanban/static/src/css/kanban.css | 2 +- addons/web_kanban/static/src/css/kanban.sass | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web_kanban/static/src/css/kanban.css b/addons/web_kanban/static/src/css/kanban.css index a6f2cc7441b..5830cb33e0f 100644 --- a/addons/web_kanban/static/src/css/kanban.css +++ b/addons/web_kanban/static/src/css/kanban.css @@ -50,7 +50,7 @@ .openerp .oe_kanban_view .oe_kanban_group_title { font-size: 16px; font-weight: bold; - min-height: 30px; + min-height: 32px; color: #333333; text-shadow: 0 1px 0 white; } diff --git a/addons/web_kanban/static/src/css/kanban.sass b/addons/web_kanban/static/src/css/kanban.sass index 7175b398cbe..44b3251c8fb 100644 --- a/addons/web_kanban/static/src/css/kanban.sass +++ b/addons/web_kanban/static/src/css/kanban.sass @@ -73,7 +73,7 @@ .oe_kanban_group_title font-size: 16px font-weight: bold - min-height: 30px + min-height: 32px color: #333333 text-shadow: 0 1px 0 white > span From 6e48ab42041c589c9c63cfa7ba289df80652c0f1 Mon Sep 17 00:00:00 2001 From: Minh Tran Date: Thu, 26 Jul 2012 14:20:06 +0200 Subject: [PATCH 08/73] Resizing of the About dialog to match the golden ratio bzr revid: mit@openerp.com-20120726122006-5y3tm5r2fwk0m233 --- addons/web/static/src/js/chrome.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index b5c69f5c82f..4a76d7ded2f 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -864,7 +864,7 @@ instance.web.UserMenu = instance.web.Widget.extend({ window.location.href, 'debug'); }); instance.web.dialog($help, {autoOpen: true, - modal: true, width: 580, height: 290, resizable: false, title: _t("About")}); + modal: true, width: 507, height: 290, resizable: false, title: _t("About")}); }); }, }); From 5f2fe0169be2b0cb5bde166aa3db750dcc74bcb9 Mon Sep 17 00:00:00 2001 From: Minh Tran Date: Thu, 26 Jul 2012 18:11:06 +0200 Subject: [PATCH 09/73] Rationalization of tag css classes bzr revid: mit@openerp.com-20120726161106-5a9uu0wjuwausmgh --- addons/web/static/src/css/base.css | 50 +++++++++++++-------------- addons/web/static/src/css/base.sass | 53 +++++++++++++++-------------- addons/web/static/src/xml/base.xml | 4 +-- 3 files changed, 54 insertions(+), 53 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index e929ec6c16b..9b6cd67b51c 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1,4 +1,4 @@ -@charset "utf-8"; +@charset "UTF-8"; @font-face { font-family: "mnmliconsRegular"; src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot"); @@ -551,17 +551,19 @@ -webkit-animation: bounce 0.4s linear; } .openerp .oe_tag { - border-radius: 2px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; - border: 1px solid #9daccc; - background: #e2e6f0; - color: black !important; - padding: 0px 3px 0px 3px; + border: 1px solid #afafb6; + font-size: 11px; + padding: 2px 4px; margin: 0 2px 2px 0; - height: 16px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background: #f0f0fa; + color: #4c4c4c; +} +.openerp .oe_tag_dark { + background: #8786b7; + color: #eeeeee; } .openerp .oe_tags .text-wrap { width: 100% !important; @@ -1183,16 +1185,9 @@ } .openerp .oe_secondary_submenu .oe_menu_counter { float: right; - background: #8a89ba; - color: #eeeeee; - font-size: 10px; - border: 1px solid lightgray; - padding: 2px 4px; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); margin: 0px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; + padding: 1px 4px; } .openerp .oe_secondary_submenu .oe_active { background: #8a89ba; @@ -1532,25 +1527,27 @@ display: inline-block; line-height: 17px; } -.openerp .oe_searchview .oe_searchview_facets .oe_searchview_input, .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet { +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet { height: 18px; margin: 1px 0; font-size: 11px; } -.openerp .oe_searchview .oe_searchview_facets .oe_searchview_input:focus, .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { outline: none; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_input { padding: 0 0 0 6px; + font-size: 12px; + height: 16px; + margin-top: 3px; +} +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_input:focus { + outline: none; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet { position: relative; cursor: pointer; - border: 1px solid #afafb6; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - background: #8786b7; + padding: 0; -webkit-font-smoothing: auto; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { @@ -1579,6 +1576,7 @@ .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value { border-left: 1px solid #afafb6; text-shadow: 0 1px 1px white; + color: #4c4c4c; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value:last-child { padding-right: 16px; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index c9e00c65a8a..dd4f9baee66 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -2,8 +2,10 @@ // Variables {{{ $section-title-color: #8786b7 -$facets-border: #afafb6 -$facets-border-selected: #a6a6fe +$tag-bg-light: #f0f0fa +$tag-bg-dark: #8786b7 +$tag-border: #afafb6 +$tag-border-selected: #a6a6fe $hover-background: #f0f0fa $colour4: #8a89ba $sheet-max-width: 860px @@ -427,15 +429,17 @@ $sheet-max-width: 860px // }}} // Tags (for many2many tags, among others) {{{ .oe_tag - border-radius: 2px - @include box-sizing(border) - border: 1px solid #9DACCC - background: #E2E6F0 - color: black !important - padding: 0px 3px 0px 3px + border: 1px solid $tag-border + font-size: 11px + padding: 2px 4px margin: 0 2px 2px 0 - height: 16px - // font: 11px "lucida grande", tahoma, verdana, arial, sans-serif + @include radius(3px) + background: $tag-bg-light + color: #4C4C4C + .oe_tag_dark + background: $tag-bg-dark + color: #eee + .oe_tags .text-wrap width: 100% !important @@ -906,14 +910,9 @@ $sheet-max-width: 860px @include box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.2)) .oe_menu_counter float: right - background: #8a89ba - color: #eee - font-size: 10px - border: 1px solid lightgray - padding: 2px 4px text-shadow: 0 1px 1px rgba(0,0,0,0.2) margin: 0px - @include radius(4px) + padding: 1px 4px .oe_active background: $colour4 border-top: 1px solid lightGray @@ -1125,8 +1124,8 @@ $sheet-max-width: 860px font-size: 12px line-height: 18px &.oe_focused - border-color: $facets-border-selected - @include box-shadow(0 1px 2px $facets-border-selected inset) + border-color: $tag-border-selected + @include box-shadow(0 1px 2px $tag-border-selected inset) .oe_searchview_clear cursor: pointer position: absolute @@ -1177,7 +1176,7 @@ $sheet-max-width: 860px vertical-align: top display: inline-block line-height: 17px - .oe_searchview_input, .oe_searchview_facet + .oe_searchview_facet height: 18px margin: 1px 0 font-size: 11px @@ -1185,17 +1184,20 @@ $sheet-max-width: 860px outline: none .oe_searchview_input padding: 0 0 0 6px + font-size: 12px + height: 16px + margin-top: 3px + &:focus + outline: none .oe_searchview_facet position: relative cursor: pointer - border: 1px solid $facets-border - @include radius(3px) - background: #8786b7 + padding: 0 -webkit-font-smoothing: auto // spacing for opera, FF &:focus - border-color: $facets-border-selected - @include box-shadow(0 0 3px 1px $facets-border-selected) + border-color: $tag-border-selected + @include box-shadow(0 0 3px 1px $tag-border-selected) .oe_facet_values background: #f0f0fa @include radius(0 3px 3px 0) @@ -1208,8 +1210,9 @@ $sheet-max-width: 860px .oe_facet_category.oe_i font-size: 16px .oe_facet_value - border-left: 1px solid $facets-border + border-left: 1px solid $tag-border text-shadow: 0 1px 1px white + color: #4C4C4C &:last-child padding-right: 16px .oe_facet_remove diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 55353f0a748..c6b8b20c9bd 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -310,7 +310,7 @@ t-att-data-action-id="menu.action ? menu.action.split(',')[1] : ''"> -
+
@@ -1269,7 +1269,7 @@ contenteditable="true"/>
x From 73f3f24fb4564e9edb7bf4ddede1ccf46aaa94e7 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 30 Jul 2012 12:32:14 +0200 Subject: [PATCH 10/73] [ADD] tests for selection[function] and function fields export bzr revid: xmo@openerp.com-20120730103214-t4s9g0z3sl1x05b3 --- openerp/tests/export_models.py | 19 +++++++++++++++---- openerp/tests/test_export.py | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/openerp/tests/export_models.py b/openerp/tests/export_models.py index d54c73ac4ad..2f85ab473e8 100644 --- a/openerp/tests/export_models.py +++ b/openerp/tests/export_models.py @@ -1,6 +1,16 @@ # -*- coding: utf-8 -*- from openerp.osv import orm, fields +def selection_fn(obj, cr, uid, context=None): + return list(enumerate(["Corge", "Grault", "Wheee", "Moog"])) + +def function_fn(model, cr, uid, ids, field_name, arg, context): + return dict((id, 3) for id in ids) +def function_fn_write(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context): + """ just so CreatorCase.export can be used + """ + pass + models = [ ('boolean', fields.boolean()), ('integer', fields.integer()), @@ -12,13 +22,14 @@ models = [ ('datetime', fields.datetime()), ('text', fields.text()), ('selection', fields.selection([(1, "Foo"), (2, "Bar"), (3, "Qux")])), + ('selection.function', fields.selection(selection_fn)), # just relate to an integer ('many2one', fields.many2one('export.integer')), ('one2many', fields.one2many('export.one2many.child', 'parent_id')), - ('many2many', fields.many2many('export.many2many.other')) - # TODO: function? - # TODO: related? - # TODO: reference? + ('many2many', fields.many2many('export.many2many.other')), + ('function', fields.function(function_fn, fnct_inv=function_fn_write, type="integer")), + # related: specialization of fields.function, should work the same way + # TODO: reference ] for name, field in models: attrs = { diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index ea21cd36a8a..ee4405db75d 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -7,7 +7,7 @@ from . import common, export_models def setUpModule(): - openerp.tools.config['update'] = dict(base=1) + openerp.tools.config['update'] = {'base': 1} openerp.modules.registry.RegistryManager.new( common.DB, update_module=True) @@ -237,6 +237,28 @@ class test_selection(CreatorCase): self.export(2), [[u"Bar"]]) +class test_selection_function(CreatorCase): + model_name = 'export.selection.function' + + def test_empty(self): + self.assertEqual( + self.export(False), + [[False]]) + + def test_value(self): + """ selection functions export the *value* itself + """ + self.assertEqual( + self.export(1), + [[u'1']]) + self.assertEqual( + self.export(3), + [[u'3']]) + # fucking hell + self.assertEqual( + self.export(0), + [[False]]) + class test_m2o(CreatorCase): model_name = 'export.many2one' @@ -518,3 +540,13 @@ class test_m2m(CreatorCase): ]]) # essentially same as o2m, so boring + +class test_function(CreatorCase): + model_name = 'export.function' + + def test_value(self): + """ Exports value normally returned by accessing the function field + """ + self.assertEqual( + self.export(42), + [[u'3']]) From 64910eaebad74019b4d7e0c58621a8487f2c007e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 30 Jul 2012 13:11:05 +0200 Subject: [PATCH 11/73] [ADD] boolean field import tests bzr revid: xmo@openerp.com-20120730111105-k6q8xop0ru71l0dj --- openerp/tests/__init__.py | 3 +- openerp/tests/test_import.py | 95 ++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 openerp/tests/test_import.py diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index 7b30adece79..4b8fa798aea 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -9,7 +9,7 @@ See the :ref:`test-framework` section in the :ref:`features` list. """ from . import test_expression, test_ir_sequence, test_orm,\ - test_uninstall, test_export + test_uninstall, test_export, test_import fast_suite = [ test_ir_sequence, @@ -19,6 +19,7 @@ checks = [ test_expression, test_orm, test_export, + test_import, ] # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py new file mode 100644 index 00000000000..4c5af087b2c --- /dev/null +++ b/openerp/tests/test_import.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +import openerp.modules.registry +import openerp + +from . import common, export_models + +def ok(n): + """ Successful import of ``n`` records + + :param int n: number of records which should have been imported + """ + return n, 0, 0, 0 + +def values(seq): + return [item['value'] for item in seq] + +def setupModule(): + openerp.tools.config['update'] = {'base': 1} + openerp.modules.registry.RegistryManager.new( + common.DB, update_module=True) + +class ImporterCase(common.TransactionCase): + model_name = False + + def __init__(self, *args, **kwargs): + super(ImporterCase, self).__init__(*args, **kwargs) + self.model = None + + def setUp(self): + super(ImporterCase, self).setUp() + self.model = self.registry(self.model_name) + + def import_(self, fields, rows, context=None): + return self.model.import_data( + self.cr, openerp.SUPERUSER_ID, fields, rows, context=context) + def read(self, fields=('value',), domain=(), context=None): + return self.model.read( + self.cr, openerp.SUPERUSER_ID, + self.model.search(self.cr, openerp.SUPERUSER_ID, domain, context=context), + fields=fields, context=context) + +class test_boolean_field(ImporterCase): + model_name = 'export.boolean' + + def test_empty(self): + self.assertEqual( + self.import_(['value'], []), + ok(0)) + + def test_exported(self): + self.assertEqual( + self.import_(['value'], [ + ['False'], + ['True'], + ]), + ok(2)) + records = self.read() + self.assertItemsEqual([ + False, + True, + ], values(records)) + + def test_falses(self): + self.assertEqual( + self.import_(['value'], [ + [u'0'], + [u'off'], + [u'false'], + [u'FALSE'], + [u'OFF'], + ]), + ok(5)) + self.assertItemsEqual([ + False, + False, + False, + False, + False, + ], + values(self.read())) + + def test_trues(self): + self.assertEqual( + self.import_(['value'], [ + ['no'], + ['None'], + ['nil'], + ['()'], + ['f'], + ['#f'], + ]), + ok(6)) + self.assertItemsEqual( + [True] * 6, + values(self.read())) From 59193b0c0fdcffb05e1a25a01f083bbf5b8d924d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 30 Jul 2012 13:36:33 +0200 Subject: [PATCH 12/73] [ADD] empty record/field case to booleans bzr revid: xmo@openerp.com-20120730113633-tauioxm941k0iaqn --- openerp/tests/test_import.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index 4c5af087b2c..e29cc8a3ad9 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -55,7 +55,7 @@ class test_boolean_field(ImporterCase): ]), ok(2)) records = self.read() - self.assertItemsEqual([ + self.assertEqual([ False, True, ], values(records)) @@ -68,9 +68,11 @@ class test_boolean_field(ImporterCase): [u'false'], [u'FALSE'], [u'OFF'], + [u''], ]), - ok(5)) - self.assertItemsEqual([ + ok(6)) + self.assertEqual([ + False, False, False, False, @@ -90,6 +92,7 @@ class test_boolean_field(ImporterCase): ['#f'], ]), ok(6)) - self.assertItemsEqual( + self.assertEqual( [True] * 6, values(self.read())) + From 6af9bb41598dfdc03721fe228fb4c5f972335361 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 30 Jul 2012 14:22:43 +0200 Subject: [PATCH 13/73] [TEST] integers import bzr revid: xmo@openerp.com-20120730122243-wjpalm6ovwuk6k5c --- openerp/tests/test_import.py | 88 +++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index e29cc8a3ad9..c38517a6f07 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -11,6 +11,18 @@ def ok(n): """ return n, 0, 0, 0 +def error(row, message, record=None, **kwargs): + """ Failed import of the record ``record`` at line ``row``, with the error + message ``message`` + + :param str message: + :param dict record: + """ + return ( + -1, dict(record or {}, **kwargs), + "Line %d : %s\n" % (row, message), + '') + def values(seq): return [item['value'] for item in seq] @@ -90,9 +102,81 @@ class test_boolean_field(ImporterCase): ['()'], ['f'], ['#f'], + # Problem: OpenOffice (and probably excel) output localized booleans + ['VRAI'], ]), - ok(6)) + ok(7)) self.assertEqual( - [True] * 6, + [True] * 7, values(self.read())) +class test_integer_field(ImporterCase): + model_name = 'export.integer' + + def test_none(self): + self.assertEqual( + self.import_(['value'], []), + ok(0)) + + def test_empty(self): + self.assertEqual( + self.import_(['value'], [['']]), + ok(1)) + self.assertEqual( + [False], + values(self.read())) + + def test_zero(self): + self.assertEqual( + self.import_(['value'], [['0']]), + ok(1)) + self.assertEqual( + self.import_(['value'], [['-0']]), + ok(1)) + self.assertEqual([False, False], values(self.read())) + + def test_positives(self): + self.assertEqual( + self.import_(['value'], [ + ['1'], + ['42'], + [str(2**31-1)], + ['12345678'] + ]), + ok(4)) + self.assertEqual([ + 1, 42, 2**31-1, 12345678 + ], values(self.read())) + + def test_negatives(self): + self.assertEqual( + self.import_(['value'], [ + ['-1'], + ['-42'], + [str(-(2**31 - 1))], + [str(-(2**31))], + ['-12345678'] + ]), + ok(5)) + self.assertEqual([ + -1, -42, -(2**31 - 1), -(2**31), -12345678 + ], values(self.read())) + + def test_out_of_range(self): + self.assertEqual( + self.import_(['value'], [[str(2**31)]]), + error(1, "integer out of range", value=2**31)) + # auto-rollbacks if error is in process_liness, but not during + # ir.model.data write. Can differentiate because former ends lines + # error lines with "!" + self.cr.rollback() + self.assertEqual( + self.import_(['value'], [[str(-2**32)]]), + error(1, "integer out of range", value=-2**32)) + + + def test_nonsense(self): + # dafuq? why does that one raise an error? + self.assertRaises( + ValueError, + self.import_, ['value'], [['zorglub']]) From 1cf264cfd4ae468653f5aade0786f522a459bf58 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 30 Jul 2012 16:28:51 +0200 Subject: [PATCH 14/73] [TEST] char and text fields bzr revid: xmo@openerp.com-20120730142851-6854x99l4sxmoc0s --- openerp/tests/test_import.py | 125 +++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index c38517a6f07..7fef60876e0 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -180,3 +180,128 @@ class test_integer_field(ImporterCase): self.assertRaises( ValueError, self.import_, ['value'], [['zorglub']]) + +class test_float_field(ImporterCase): + model_name = 'export.float' + def test_none(self): + self.assertEqual( + self.import_(['value'], []), + ok(0)) + + def test_empty(self): + self.assertEqual( + self.import_(['value'], [['']]), + ok(1)) + self.assertEqual( + [False], + values(self.read())) + + def test_zero(self): + self.assertEqual( + self.import_(['value'], [['0']]), + ok(1)) + self.assertEqual( + self.import_(['value'], [['-0']]), + ok(1)) + self.assertEqual([False, False], values(self.read())) + + def test_positives(self): + self.assertEqual( + self.import_(['value'], [ + ['1'], + ['42'], + [str(2**31-1)], + ['12345678'], + [str(2**33)], + ['0.000001'], + ]), + ok(6)) + self.assertEqual([ + 1, 42, 2**31-1, 12345678, 2.0**33, .000001 + ], values(self.read())) + + def test_negatives(self): + self.assertEqual( + self.import_(['value'], [ + ['-1'], + ['-42'], + [str(-2**31 + 1)], + [str(-2**31)], + ['-12345678'], + [str(-2**33)], + ['-0.000001'], + ]), + ok(7)) + self.assertEqual([ + -1, -42, -(2**31 - 1), -(2**31), -12345678, -2.0**33, -.000001 + ], values(self.read())) + + def test_nonsense(self): + self.assertRaises( + ValueError, + self.import_, ['value'], [['foobar']]) + +class test_string_field(ImporterCase): + model_name = 'export.string.bounded' + + def test_empty(self): + self.assertEqual( + self.import_(['value'], [['']]), + ok(1)) + self.assertEqual([False], values(self.read())) + + def test_imported(self): + self.assertEqual( + self.import_(['value'], [ + [u'foobar'], + [u'foobarbaz'], + [u'Með suð í eyrum við spilum endalaust'], + [u"People 'get' types. They use them all the time. Telling " + u"someone he can't pound a nail with a banana doesn't much " + u"surprise him."] + ]), + ok(4)) + self.assertEqual([ + u"foobar", + u"foobarbaz", + u"Með suð í eyrum ", + u"People 'get' typ", + ], values(self.read())) + +class test_unbound_string_field(ImporterCase): + model_name = 'export.string' + + def test_imported(self): + self.assertEqual( + self.import_(['value'], [ + [u'í dag viðrar vel til loftárása'], + # ackbar.jpg + [u"If they ask you about fun, you tell them – fun is a filthy" + u" parasite"] + ]), + ok(2)) + self.assertEqual([ + u"í dag viðrar vel til loftárása", + u"If they ask you about fun, you tell them – fun is a filthy parasite" + ], values(self.read())) + +class test_text(ImporterCase): + model_name = 'export.text' + + def test_empty(self): + self.assertEqual( + self.import_(['value'], [['']]), + ok(1)) + self.assertEqual([False], values(self.read())) + + def test_imported(self): + s = (u"Breiðskífa er notað um útgefna hljómplötu sem inniheldur " + u"stúdíóupptökur frá einum flytjanda. Breiðskífur eru oftast " + u"milli 25-80 mínútur og er lengd þeirra oft miðuð við 33⅓ " + u"snúninga 12 tommu vínylplötur (sem geta verið allt að 30 mín " + u"hvor hlið).\n\nBreiðskífur eru stundum tvöfaldar og eru þær þá" + u" gefnar út á tveimur geisladiskum eða tveimur vínylplötum.") + self.assertEqual( + self.import_(['value'], [[s]]), + ok(1)) + self.assertEqual([s], values(self.read())) From 5b5148b925e5750036257c33426faa41c5f61486 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 30 Jul 2012 17:28:44 +0200 Subject: [PATCH 15/73] [TEST] selection fields import bzr revid: xmo@openerp.com-20120730152844-xob3ezlw0qpb1kxc --- openerp/tests/test_export.py | 2 ++ openerp/tests/test_import.py | 49 +++++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index ee4405db75d..01d0fbc7726 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -237,6 +237,8 @@ class test_selection(CreatorCase): self.export(2), [[u"Bar"]]) + # TODO: localized export! + class test_selection_function(CreatorCase): model_name = 'export.selection.function' diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index 7fef60876e0..9c817236cf2 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -20,7 +20,7 @@ def error(row, message, record=None, **kwargs): """ return ( -1, dict(record or {}, **kwargs), - "Line %d : %s\n" % (row, message), + "Line %d : %s" % (row, message), '') def values(seq): @@ -165,14 +165,14 @@ class test_integer_field(ImporterCase): def test_out_of_range(self): self.assertEqual( self.import_(['value'], [[str(2**31)]]), - error(1, "integer out of range", value=2**31)) + error(1, "integer out of range\n", value=2**31)) # auto-rollbacks if error is in process_liness, but not during # ir.model.data write. Can differentiate because former ends lines # error lines with "!" self.cr.rollback() self.assertEqual( self.import_(['value'], [[str(-2**32)]]), - error(1, "integer out of range", value=-2**32)) + error(1, "integer out of range\n", value=-2**32)) def test_nonsense(self): @@ -305,3 +305,46 @@ class test_text(ImporterCase): self.import_(['value'], [[s]]), ok(1)) self.assertEqual([s], values(self.read())) + +class test_selection(ImporterCase): + model_name = 'export.selection' + + def test_imported(self): + self.assertEqual( + self.import_(['value'], [ + ['Qux'], + ['Bar'], + ['Foo'], + [2], + ]), + ok(4)) + self.assertEqual([3, 2, 1, 2], values(self.read())) + + def test_invalid(self): + self.assertEqual( + self.import_(['value'], [['Baz']]), + error(1, "Key/value 'Baz' not found in selection field 'value'", + # what the fuck? + value=False)) + +class test_selection_function(ImporterCase): + model_name = 'export.selection.function' + + def test_imported(self): + """ By what bloody magic does that thing work? + + => import uses fields_get, so translates import label (may or may not + be good news) *and* serializes the selection function to reverse + it: import does not actually know that the selection field uses a + function + """ + # TODO: localized import + self.assertEqual( + self.import_(['value'], [ + [3], + ["Grault"], + ]), + ok(2)) + self.assertEqual( + ['3', '1'], + values(self.read())) From 496177a9b1a8fbd517bf8faf453b8ac04a1e996c Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 30 Jul 2012 17:30:17 +0200 Subject: [PATCH 16/73] [TEST] incorrect value for selection field import bzr revid: xmo@openerp.com-20120730153017-iywveudrq5h0ykef --- openerp/tests/test_import.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index 9c817236cf2..c87f65c4c6e 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -326,6 +326,11 @@ class test_selection(ImporterCase): error(1, "Key/value 'Baz' not found in selection field 'value'", # what the fuck? value=False)) + self.cr.rollback() + self.assertEqual( + self.import_(['value'], [[42]]), + error(1, "Key/value '42' not found in selection field 'value'", + value=False)) class test_selection_function(ImporterCase): model_name = 'export.selection.function' From 84488630075d41930a8d2a715eebb62e69471b2f Mon Sep 17 00:00:00 2001 From: Minh Tran Date: Mon, 30 Jul 2012 17:47:50 +0200 Subject: [PATCH 17/73] Restyling of jquery-ui buttons in dialog box + various style fixes bzr revid: mit@openerp.com-20120730154750-x40vpq9y9f492zvn --- addons/web/static/src/css/base.css | 27 ++++++++++++++++++--------- addons/web/static/src/css/base.sass | 22 +++++++++++++++------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 9b6cd67b51c..703683d4d96 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -148,7 +148,7 @@ .openerp h4 { margin: 4px 0; } -.openerp a.button:link, .openerp a.button:visited, .openerp button, .openerp input[type='submit'] { +.openerp a.button:link, .openerp a.button:visited, .openerp button, .openerp input[type='submit'], .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button { display: inline-block; border: 1px solid #ababab; color: #404040; @@ -173,7 +173,7 @@ -webkit-font-smoothing: antialiased; outline: none; } -.openerp a.button:hover, .openerp button:hover, .openerp input[type='submit']:hover { +.openerp a.button:hover, .openerp button:hover, .openerp input[type='submit']:hover, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-hover { background-color: #f6f6f6; background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3); @@ -182,9 +182,11 @@ background-image: -o-linear-gradient(top, #f6f6f6, #e3e3e3); background-image: linear-gradient(to bottom, #f6f6f6, #e3e3e3); cursor: pointer; + background-position: 0; } -.openerp a.button:focus, .openerp button:focus, .openerp input[type='submit']:focus { +.openerp a.button:focus, .openerp button:focus, .openerp input[type='submit']:focus, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-focus { border: 1px solid #80bfff; + background-position: 0; background-color: #f6f6f6; background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3); @@ -196,7 +198,7 @@ -webkit-box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; } -.openerp a.button:active, .openerp a.button.active, .openerp button:active, .openerp button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active { +.openerp a.button:active, .openerp a.button.active, .openerp button:active, .openerp button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-active { background-color: #e3e3e3; background-image: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6)); background-image: -webkit-linear-gradient(top, #e3e3e3, #f6f6f6); @@ -230,11 +232,6 @@ .openerp .ui-menu .ui-menu-item a.ui-state-active { background: #f0f0fa; } -.openerp div.ui-widget-overlay { - background: black; - filter: alpha(opacity=30); - opacity: 0.3; -} .openerp.ui-dialog { display: none; padding: 6px; @@ -294,6 +291,9 @@ .openerp.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: left; } +.openerp.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button { + margin-right: 4px; +} .openerp.ui-dialog .ui-dialog-titlebar-close { padding: 0; } @@ -329,6 +329,9 @@ .openerp.ui-dialog .oe_about a:hover { text-decoration: underline; } +.openerp.ui-dialog .oe_about a:focus { + outline: none; +} .openerp.ui-dialog .oe_about .oe_logo { margin-left: -6px; } @@ -2539,3 +2542,9 @@ .kitten-mode-activated > * { opacity: 0.7; } + +div.ui-widget-overlay { + background: black; + filter: alpha(opacity=30); + opacity: 0.3; +} diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index dd4f9baee66..2a2d816241f 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -184,7 +184,7 @@ $sheet-max-width: 860px margin: 4px 0 // }}} // Button style {{{ - a.button:link, a.button:visited, button, input[type='submit'] + a.button:link, a.button:visited, button, input[type='submit'], .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button display: inline-block border: 1px solid #ababab color: #404040 @@ -199,16 +199,18 @@ $sheet-max-width: 860px -webkit-font-smoothing: antialiased outline: none - a.button:hover, button:hover, input[type='submit']:hover + a.button:hover, button:hover, input[type='submit']:hover, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-hover @include vertical-gradient(#f6f6f6, #e3e3e3) cursor: pointer + background-position: 0 - a.button:focus, button:focus, input[type='submit']:focus + a.button:focus, button:focus, input[type='submit']:focus, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-focus border: 1px solid #80bfff + background-position: 0 @include vertical-gradient(#f6f6f6, #e3e3e3) @include box-shadow((0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, .8) inset)) - a.button:active, a.button.active, button:active, button.active, input[type='submit']:active, input[type='submit'].active + a.button:active, a.button.active, button:active, button.active, input[type='submit']:active, input[type='submit'].active, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-active @include vertical-gradient(#e3e3e3, #f6f6f6) @include box-shadow(none) @@ -231,9 +233,7 @@ $sheet-max-width: 860px width: auto a.ui-state-active background: #f0f0fa - div.ui-widget-overlay - background: black - @include opacity(0.3) + // Modal box &.ui-dialog display: none @@ -270,6 +270,8 @@ $sheet-max-width: 860px margin: 0 .ui-dialog-buttonset float: left + .ui-button + margin-right: 4px .ui-dialog-titlebar-close padding: 0 .ui-icon-closethick @@ -296,6 +298,8 @@ $sheet-max-width: 860px color: #8A89BA &:hover text-decoration: underline + &:focus + outline: none .oe_logo margin-left: -6px .oe_bottom @@ -1993,6 +1997,10 @@ $sheet-max-width: 860px // }}} +// The jQuery-ui overlay is outside the .openerp div, please don't add indentation !!! +div.ui-widget-overlay + background: black + @include opacity(0.3) // au BufWritePost,FileWritePost *.sass :!sass --style expanded --line-numbers > "%:p:r.css" // vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker: From f8822ae313e380f72f5aff23b7f61d2f73939764 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 30 Jul 2012 18:23:18 +0200 Subject: [PATCH 18/73] [TEST] m2o import bzr revid: xmo@openerp.com-20120730162318-qwxncgvizrifgxyh --- openerp/tests/export_models.py | 8 ++- openerp/tests/test_import.py | 106 +++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/openerp/tests/export_models.py b/openerp/tests/export_models.py index 2f85ab473e8..277814808b4 100644 --- a/openerp/tests/export_models.py +++ b/openerp/tests/export_models.py @@ -42,7 +42,13 @@ for name, field in models: '_defaults': {'const': 4}, 'name_get': (lambda self, cr, uid, ids, context=None: [(record.id, "%s:%s" % (self._name, record.value)) - for record in self.browse(cr, uid, ids, context=context)]) + for record in self.browse(cr, uid, ids, context=context)]), + 'name_search': (lambda self, cr, uid, name, operator, context=None: + self.name_get(cr, uid, + self.search(cr, uid, [['value', operator, int(name.split(':')[1])]]) + , context=context) + if isinstance(name, basestring) and name.split(':')[0] == self._name + else []) } NewModel = type( 'Export%s' % ''.join(section.capitalize() for section in name.split('.')), diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index c87f65c4c6e..90e14375540 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -50,6 +50,12 @@ class ImporterCase(common.TransactionCase): self.cr, openerp.SUPERUSER_ID, self.model.search(self.cr, openerp.SUPERUSER_ID, domain, context=context), fields=fields, context=context) + def browse(self, domain=(), context=None): + return self.model.browse( + self.cr, openerp.SUPERUSER_ID, + self.model.search(self.cr, openerp.SUPERUSER_ID, domain, context=context), + context=context) + class test_boolean_field(ImporterCase): model_name = 'export.boolean' @@ -353,3 +359,103 @@ class test_selection_function(ImporterCase): self.assertEqual( ['3', '1'], values(self.read())) + +class test_o2m(ImporterCase): + model_name = 'export.many2one' + + def test_by_name(self): + # create integer objects + integer_id1 = self.registry('export.integer').create( + self.cr, openerp.SUPERUSER_ID, {'value': 42}) + integer_id2 = self.registry('export.integer').create( + self.cr, openerp.SUPERUSER_ID, {'value': 36}) + # get its name + name1 = dict(self.registry('export.integer').name_get( + self.cr, openerp.SUPERUSER_ID,[integer_id1]))[integer_id1] + name2 = dict(self.registry('export.integer').name_get( + self.cr, openerp.SUPERUSER_ID,[integer_id2]))[integer_id2] + + self.assertEqual( + self.import_(['value'], [ + # import by name_get + [name1], + [name1], + [name2], + ]), + ok(3)) + # correct ids assigned to corresponding records + self.assertEqual([ + (integer_id1, name1), + (integer_id1, name1), + (integer_id2, name2),], + values(self.read())) + + # TODO: test import by xid + + def test_by_id(self): + integer_id = self.registry('export.integer').create( + self.cr, openerp.SUPERUSER_ID, {'value': 42}) + self.assertEqual( + self.import_(['value/.id'], [[integer_id]]), + ok(1)) + b = self.browse() + self.assertEqual(42, b[0].value.value) + + def test_by_names(self): + integer_id1 = self.registry('export.integer').create( + self.cr, openerp.SUPERUSER_ID, {'value': 42}) + integer_id2 = self.registry('export.integer').create( + self.cr, openerp.SUPERUSER_ID, {'value': 42}) + name1 = dict(self.registry('export.integer').name_get( + self.cr, openerp.SUPERUSER_ID,[integer_id1]))[integer_id1] + name2 = dict(self.registry('export.integer').name_get( + self.cr, openerp.SUPERUSER_ID,[integer_id2]))[integer_id2] + # names should be the same + self.assertEqual(name1, name2) + + self.assertEqual( + self.import_(['value'], [[name2]]), + ok(1)) + # FIXME: is it really normal import does not care for name_search collisions? + self.assertEqual([ + (integer_id1, name1) + ], values(self.read())) + + def test_fail_by_implicit_id(self): + """ Can't implicitly import records by id + """ + # create integer objects + integer_id1 = self.registry('export.integer').create( + self.cr, openerp.SUPERUSER_ID, {'value': 42}) + integer_id2 = self.registry('export.integer').create( + self.cr, openerp.SUPERUSER_ID, {'value': 36}) + + self.assertRaises( + ValueError, # Because name_search all the things. Fallback schmallback + self.import_, ['value'], [ + # import by id, without specifying it + [integer_id1], + [integer_id2], + [integer_id1], + ]) + + def test_sub_field(self): + """ Does not implicitly create the record, does not warn that you can't + import m2o subfields (at all)... + """ + self.assertRaises( + ValueError, # No record found for 42, name_searches the bloody thing + self.import_, ['value/value'], [['42']]) + + def test_fail_noids(self): + self.assertRaises( + ValueError, + self.import_, ['value'], [['nameisnoexist:3']]) + self.cr.rollback() + self.assertRaises( + ValueError, + self.import_, ['value/id'], [['noxidhere']]), + self.cr.rollback() + self.assertRaises( + Exception, # FIXME: Why can't you be a ValueError like everybody else? + self.import_, ['value/.id'], [[66]]) From a3542213bc80ab9247e70a4f1353da6e36dd8266 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 30 Jul 2012 18:53:30 +0200 Subject: [PATCH 19/73] [TEST] ids handling on main record bzr revid: xmo@openerp.com-20120730165330-lm4l1yzfjduajb1p --- openerp/tests/test_import.py | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index 90e14375540..2305928a12e 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -56,6 +56,38 @@ class ImporterCase(common.TransactionCase): self.model.search(self.cr, openerp.SUPERUSER_ID, domain, context=context), context=context) +class test_ids_stuff(ImporterCase): + model_name = 'export.integer' + + def test_create_with_id(self): + self.assertRaises( + Exception, # dammit + self.import_, ['.id', 'value'], [['42', '36']]) + def test_create_with_xid(self): + self.assertEqual( + self.import_(['id', 'value'], [['somexmlid', '42']]), + ok(1)) + # TODO: get xid back, check that it is correct? + + def test_update_with_id(self): + id = self.model.create(self.cr, openerp.SUPERUSER_ID, {'value': 36}) + self.assertEqual( + 36, + self.model.browse(self.cr, openerp.SUPERUSER_ID, id).value) + + self.assertEqual( + self.import_(['.id', 'value'], [[str(id), '42']]), + ok(1)) + self.assertEqual( + [42], # updated value to imported + values(self.read())) + + def test_update_with_xid(self): + self.import_(['id', 'value'], [['somexmlid', '36']]) + self.assertEqual([36], values(self.read())) + + self.import_(['id', 'value'], [['somexmlid', '1234567']]) + self.assertEqual([1234567], values(self.read())) class test_boolean_field(ImporterCase): model_name = 'export.boolean' @@ -459,3 +491,8 @@ class test_o2m(ImporterCase): self.assertRaises( Exception, # FIXME: Why can't you be a ValueError like everybody else? self.import_, ['value/.id'], [[66]]) +# TODO: M2M +# TODO: O2M + +# function, related, reference: written to db as-is... +# => function uses @type for value coercion/conversion From 0fa88efde693fe8e0f3efd384b17d1012935159e Mon Sep 17 00:00:00 2001 From: Minh Tran Date: Mon, 30 Jul 2012 19:18:44 +0200 Subject: [PATCH 20/73] fixed display issue in kanban nocontent view bzr revid: mit@openerp.com-20120730171844-4cjadauklpe8if2e --- addons/web_kanban/static/src/js/kanban.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index cd12953adcd..568e6890522 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -161,6 +161,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ }, do_search: function(domain, context, group_by) { var self = this; + this.$element.find('.oe_view_nocontent').parent().addClass('oe_kanban_view').find('.oe_kanban_groups').show(); this.$element.find('.oe_view_nocontent').remove(); this.search_domain = domain; this.search_context = context; @@ -335,7 +336,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ $('
') .append($('', { src: '/web/static/src/img/view_empty_arrow.png' })) .append($('
').html(this.options.action.help)) - ); + ).removeClass('oe_kanban_view').find('.oe_kanban_groups').hide(); } }); From 6bad3e84fb1093b3e9c1a3c99bea3cc19fd95e2e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 31 Jul 2012 12:39:08 +0200 Subject: [PATCH 21/73] [TEST] m2o import link by xid bzr revid: xmo@openerp.com-20120731103908-p1apmrx3ifnodz0f --- openerp/tests/test_import.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index 2305928a12e..0b0d8fe4d0d 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -392,7 +392,7 @@ class test_selection_function(ImporterCase): ['3', '1'], values(self.read())) -class test_o2m(ImporterCase): +class test_m2o(ImporterCase): model_name = 'export.many2one' def test_by_name(self): @@ -422,7 +422,21 @@ class test_o2m(ImporterCase): (integer_id2, name2),], values(self.read())) - # TODO: test import by xid + def test_by_xid(self): + integer_id = self.registry('export.integer').create( + self.cr, openerp.SUPERUSER_ID, {'value': 42}) + self.registry('ir.model.data').create(self.cr, openerp.SUPERUSER_ID, { + 'name': 'export-integer-value', + 'model': 'export.integer', + 'res_id': integer_id, + 'module': 'test-export' + }) + + self.assertEqual( + self.import_(['value/id'], [['test-export.export-integer-value']]), + ok(1)) + b = self.browse() + self.assertEqual(42, b[0].value.value) def test_by_id(self): integer_id = self.registry('export.integer').create( @@ -491,7 +505,9 @@ class test_o2m(ImporterCase): self.assertRaises( Exception, # FIXME: Why can't you be a ValueError like everybody else? self.import_, ['value/.id'], [[66]]) + # TODO: M2M + # TODO: O2M # function, related, reference: written to db as-is... From ac647909bf97d6caf2ddc74b4daad6cea7752645 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 31 Jul 2012 15:54:49 +0200 Subject: [PATCH 22/73] [TEST] m2m import bzr revid: xmo@openerp.com-20120731135449-i07ex1zswepen3wt --- openerp/tests/export_models.py | 6 ++ openerp/tests/test_export.py | 2 +- openerp/tests/test_import.py | 135 ++++++++++++++++++++++++++++++--- 3 files changed, 132 insertions(+), 11 deletions(-) diff --git a/openerp/tests/export_models.py b/openerp/tests/export_models.py index 277814808b4..3bb5cf0c6d2 100644 --- a/openerp/tests/export_models.py +++ b/openerp/tests/export_models.py @@ -117,3 +117,9 @@ class Many2ManyChild(orm.Model): def name_get(self, cr, uid, ids, context=None): return [(record.id, "%s:%s" % (self._name, record.value)) for record in self.browse(cr, uid, ids, context=context)] + def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100): + return (self.name_get(cr, user, + self.search(cr, user, [['value', operator, int(name.split(':')[1])]]) + , context=context) + if isinstance(name, basestring) and name.split(':')[0] == self._name + else []) diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index 01d0fbc7726..d693de01993 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -537,7 +537,7 @@ class test_m2m(CreatorCase): def test_multiple_records_name(self): self.assertEqual( self.export(self.commands, fields=['const', 'value']), - [[ + [[ # FIXME: hardcoded comma, import uses config.csv_internal_sep u'4', u','.join(self.names) ]]) diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index 0b0d8fe4d0d..b275db520c8 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -56,6 +56,30 @@ class ImporterCase(common.TransactionCase): self.model.search(self.cr, openerp.SUPERUSER_ID, domain, context=context), context=context) + def xid(self, record): + ModelData = self.registry('ir.model.data') + + ids = ModelData.search( + self.cr, openerp.SUPERUSER_ID, + [('model', '=', record._table_name), ('res_id', '=', record.id)]) + if ids: + d = ModelData.read( + self.cr, openerp.SUPERUSER_ID, ids, ['name', 'module'])[0] + if d['module']: + return '%s.%s' % (d['module'], d['name']) + return d['name'] + + name = dict(record.name_get())[record.id] + # fix dotted name_get results, otherwise xid lookups blow up + name = name.replace('.', '-') + ModelData.create(self.cr, openerp.SUPERUSER_ID, { + 'name': name, + 'model': record._table_name, + 'res_id': record.id, + 'module': '__test__' + }) + return '__test__.' + name + class test_ids_stuff(ImporterCase): model_name = 'export.integer' @@ -67,7 +91,9 @@ class test_ids_stuff(ImporterCase): self.assertEqual( self.import_(['id', 'value'], [['somexmlid', '42']]), ok(1)) - # TODO: get xid back, check that it is correct? + self.assertEqual( + 'somexmlid', + self.xid(self.browse()[0])) def test_update_with_id(self): id = self.model.create(self.cr, openerp.SUPERUSER_ID, {'value': 36}) @@ -423,17 +449,14 @@ class test_m2o(ImporterCase): values(self.read())) def test_by_xid(self): - integer_id = self.registry('export.integer').create( + ExportInteger = self.registry('export.integer') + integer_id = ExportInteger.create( self.cr, openerp.SUPERUSER_ID, {'value': 42}) - self.registry('ir.model.data').create(self.cr, openerp.SUPERUSER_ID, { - 'name': 'export-integer-value', - 'model': 'export.integer', - 'res_id': integer_id, - 'module': 'test-export' - }) + xid = self.xid(ExportInteger.browse( + self.cr, openerp.SUPERUSER_ID, [integer_id])[0]) self.assertEqual( - self.import_(['value/id'], [['test-export.export-integer-value']]), + self.import_(['value/id'], [[xid]]), ok(1)) b = self.browse() self.assertEqual(42, b[0].value.value) @@ -506,7 +529,99 @@ class test_m2o(ImporterCase): Exception, # FIXME: Why can't you be a ValueError like everybody else? self.import_, ['value/.id'], [[66]]) -# TODO: M2M +class test_m2m(ImporterCase): + model_name = 'export.many2many' + + # apparently, one and only thing which works is a + # csv_internal_sep-separated list of ids, xids, or names (depending if + # m2m/.id, m2m/id or m2m[/anythingelse] + def test_ids(self): + id1 = self.registry('export.many2many.other').create( + self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'}) + id2 = self.registry('export.many2many.other').create( + self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'}) + id3 = self.registry('export.many2many.other').create( + self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'}) + id4 = self.registry('export.many2many.other').create( + self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'}) + id5 = self.registry('export.many2many.other').create( + self.cr, openerp.SUPERUSER_ID, {'value': 99, 'str': 'record4'}) + + self.assertEqual( + self.import_(['value/.id'], [ + ['%d,%d' % (id1, id2)], + ['%d,%d,%d' % (id1, id3, id4)], + ['%d,%d,%d' % (id1, id2, id3)], + ['%d' % id5] + ]), + ok(4)) + ids = lambda records: [record.id for record in records] + + b = self.browse() + self.assertEqual(ids(b[0].value), [id1, id2]) + self.assertEqual(values(b[0].value), [3, 44]) + + self.assertEqual(ids(b[2].value), [id1, id2, id3]) + self.assertEqual(values(b[2].value), [3, 44, 84]) + + def test_noids(self): + try: + self.import_(['value/.id'], [['42']]) + self.fail("Should have raised an exception") + except Exception, e: + self.assertIs(type(e), Exception, + "test should be fixed on exception subclass") + + def test_xids(self): + M2O_o = self.registry('export.many2many.other') + id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'}) + id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'}) + id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'}) + id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'}) + records = M2O_o.browse(self.cr, openerp.SUPERUSER_ID, [id1, id2, id3, id4]) + + self.assertEqual( + self.import_(['value/id'], [ + ['%s,%s' % (self.xid(records[0]), self.xid(records[1]))], + ['%s' % self.xid(records[3])], + ['%s,%s' % (self.xid(records[2]), self.xid(records[1]))], + ]), + ok(3)) + + b = self.browse() + self.assertEqual(values(b[0].value), [3, 44]) + self.assertEqual(values(b[2].value), [44, 84]) + def test_noxids(self): + self.assertRaises( + ValueError, + self.import_, ['value/id'], [['noxidforthat']]) + + def test_names(self): + M2O_o = self.registry('export.many2many.other') + id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'}) + id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'}) + id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'}) + id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'}) + records = M2O_o.browse(self.cr, openerp.SUPERUSER_ID, [id1, id2, id3, id4]) + + name = lambda record: dict(record.name_get())[record.id] + + self.assertEqual( + self.import_(['value'], [ + ['%s,%s' % (name(records[1]), name(records[2]))], + ['%s,%s,%s' % (name(records[0]), name(records[1]), name(records[2]))], + ['%s,%s' % (name(records[0]), name(records[3]))], + ]), + ok(3)) + + b = self.browse() + self.assertEqual(values(b[1].value), [3, 44, 84]) + self.assertEqual(values(b[2].value), [3, 9]) + + def test_nonames(self): + self.assertRaises( + ValueError, + self.import_, ['value'], [['wherethem2mhavenonames']]) # TODO: O2M From 29e6a77fef10ebe06017bb55b492f8b004a0f80e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 31 Jul 2012 16:46:06 +0200 Subject: [PATCH 23/73] [TEST] localized import/export of selection fields bzr revid: xmo@openerp.com-20120731144606-ceax7lk3rfi7zumk --- openerp/tests/test_export.py | 29 +++++++++++++- openerp/tests/test_import.py | 74 +++++++++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index d693de01993..d60e5a09dcb 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -224,6 +224,11 @@ class test_datetime(CreatorCase): class test_selection(CreatorCase): model_name = 'export.selection' + translations_fr = [ + ("Qux", "toto"), + ("Bar", "titi"), + ("Foo", "tete"), + ] def test_empty(self): self.assertEqual( @@ -237,7 +242,28 @@ class test_selection(CreatorCase): self.export(2), [[u"Bar"]]) - # TODO: localized export! + def test_localized_export(self): + self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, { + 'name': u'Français', + 'code': 'fr_FR', + 'translatable': True, + 'date_format': '%d.%m.%Y', + 'decimal_point': ',', + 'thousand_sep': ' ', + }) + Translations = self.registry('ir.translation') + for source, value in self.translations_fr: + Translations.create(self.cr, openerp.SUPERUSER_ID, { + 'name': 'export.selection,value', + 'lang': 'fr_FR', + 'type': 'selection', + 'src': source, + 'value': value + }) + # FIXME: can't import an exported selection field label if lang != en_US + self.assertEqual( + self.export(2, context={'lang': 'fr_FR'}), + [[u'Bar']]) class test_selection_function(CreatorCase): model_name = 'export.selection.function' @@ -390,7 +416,6 @@ class test_o2m(CreatorCase): [u'record5', '', u'13'], ]) -# todo: test with multiple o2m fields and exporting all class test_o2m_multiple(CreatorCase): model_name = 'export.one2many.multiple' diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index b275db520c8..7422ffd1a7a 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -372,6 +372,11 @@ class test_text(ImporterCase): class test_selection(ImporterCase): model_name = 'export.selection' + translations_fr = [ + ("Qux", "toto"), + ("Bar", "titi"), + ("Foo", "tete"), + ] def test_imported(self): self.assertEqual( @@ -384,6 +389,38 @@ class test_selection(ImporterCase): ok(4)) self.assertEqual([3, 2, 1, 2], values(self.read())) + def test_imported_translated(self): + self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, { + 'name': u'Français', + 'code': 'fr_FR', + 'translatable': True, + 'date_format': '%d.%m.%Y', + 'decimal_point': ',', + 'thousand_sep': ' ', + }) + Translations = self.registry('ir.translation') + for source, value in self.translations_fr: + Translations.create(self.cr, openerp.SUPERUSER_ID, { + 'name': 'export.selection,value', + 'lang': 'fr_FR', + 'type': 'selection', + 'src': source, + 'value': value + }) + + self.assertEqual( + self.import_(['value'], [ + ['toto'], + ['tete'], + ['titi'], + ], context={'lang': 'fr_FR'}), + ok(3)) + self.assertEqual([3, 1, 2], values(self.read())) + self.assertEqual( + self.import_(['value'], [['Foo']], context={'lang': 'fr_FR'}), + error(1, "Key/value 'Foo' not found in selection field 'value'", + value=False)) + def test_invalid(self): self.assertEqual( self.import_(['value'], [['Baz']]), @@ -398,6 +435,12 @@ class test_selection(ImporterCase): class test_selection_function(ImporterCase): model_name = 'export.selection.function' + translations_fr = [ + ("Corge", "toto"), + ("Grault", "titi"), + ("Whee", "tete"), + ("Moog", "tutu"), + ] def test_imported(self): """ By what bloody magic does that thing work? @@ -407,7 +450,6 @@ class test_selection_function(ImporterCase): it: import does not actually know that the selection field uses a function """ - # TODO: localized import self.assertEqual( self.import_(['value'], [ [3], @@ -418,6 +460,36 @@ class test_selection_function(ImporterCase): ['3', '1'], values(self.read())) + def test_translated(self): + self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, { + 'name': u'Français', + 'code': 'fr_FR', + 'translatable': True, + 'date_format': '%d.%m.%Y', + 'decimal_point': ',', + 'thousand_sep': ' ', + }) + Translations = self.registry('ir.translation') + for source, value in self.translations_fr: + Translations.create(self.cr, openerp.SUPERUSER_ID, { + 'name': 'export.selection,value', + 'lang': 'fr_FR', + 'type': 'selection', + 'src': source, + 'value': value + }) + # FIXME: Fucking hell + self.assertEqual( + self.import_(['value'], [ + ['toto'], + ['tete'], + ], context={'lang': 'fr_FR'}), + error(1, "Key/value 'toto' not found in selection field 'value'", + value=False)) + self.assertEqual( + self.import_(['value'], [['Wheee']], context={'lang': 'fr_FR'}), + ok(1)) + class test_m2o(ImporterCase): model_name = 'export.many2one' From e1c94f32212ed3d74b3aa84cc805fe614562e17a Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 31 Jul 2012 17:27:47 +0200 Subject: [PATCH 24/73] [TEST] importing m2m fields to an existing object with m2m values bzr revid: xmo@openerp.com-20120731152747-beg72fx2150upvay --- openerp/tests/test_import.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index 7422ffd1a7a..c2534caa929 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -695,7 +695,28 @@ class test_m2m(ImporterCase): ValueError, self.import_, ['value'], [['wherethem2mhavenonames']]) -# TODO: O2M + def test_import_to_existing(self): + M2O_o = self.registry('export.many2many.other') + id1 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 3, 'str': 'record0'}) + id2 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 44, 'str': 'record1'}) + id3 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 84, 'str': 'record2'}) + id4 = M2O_o.create(self.cr, openerp.SUPERUSER_ID, {'value': 9, 'str': 'record3'}) + + xid = 'myxid' + self.assertEqual( + self.import_(['id', 'value/.id'], [[xid, '%d,%d' % (id1, id2)]]), + ok(1)) + self.assertEqual( + self.import_(['id', 'value/.id'], [[xid, '%d,%d' % (id3, id4)]]), + ok(1)) + + b = self.browse() + self.assertEqual(len(b), 1) + # TODO: replacement of existing m2m values is correct? + self.assertEqual(values(b[0].value), [84, 9]) + +class test_o2m(ImporterCase): + model_name = 'export.one2many' # function, related, reference: written to db as-is... # => function uses @type for value coercion/conversion From c4a60df691a0822258489105b71018ff6b3d73c6 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 31 Jul 2012 18:36:31 +0200 Subject: [PATCH 25/73] [TEST] o2m db-id export and import bzr revid: xmo@openerp.com-20120731163631-n97nxph9gm2f6kdp --- openerp/tests/test_export.py | 17 +++++- openerp/tests/test_import.py | 113 ++++++++++++++++++++++++++++++++++- 2 files changed, 127 insertions(+), 3 deletions(-) diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index d60e5a09dcb..ed47b580365 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -376,6 +376,21 @@ class test_o2m(CreatorCase): u'4', u','.join(self.names) ]]) + def test_multiple_records_id(self): + export = self.export(self.commands, fields=['const', 'value/.id']) + O2M_c = self.registry('export.one2many.child') + ids = O2M_c.browse(self.cr, openerp.SUPERUSER_ID, + O2M_c.search(self.cr, openerp.SUPERUSER_ID, [])) + self.assertEqual( + export, + [ + ['4', str(ids[0].id)], + ['', str(ids[1].id)], + ['', str(ids[2].id)], + ['', str(ids[3].id)], + ['', str(ids[4].id)], + ]) + def test_multiple_records_with_name_before(self): self.assertEqual( self.export(self.commands, fields=['const', 'value', 'value/value']), @@ -383,7 +398,7 @@ class test_o2m(CreatorCase): u'4', u','.join(self.names), u'4' ]]) - def test_multiple_records_with_name_before(self): + def test_multiple_records_with_name_after(self): self.assertEqual( self.export(self.commands, fields=['const', 'value/value', 'value']), [ # completely ignores name_get request diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index c2534caa929..c23d74d080a 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -23,8 +23,8 @@ def error(row, message, record=None, **kwargs): "Line %d : %s" % (row, message), '') -def values(seq): - return [item['value'] for item in seq] +def values(seq, field='value'): + return [item[field] for item in seq] def setupModule(): openerp.tools.config['update'] = {'base': 1} @@ -718,5 +718,114 @@ class test_m2m(ImporterCase): class test_o2m(ImporterCase): model_name = 'export.one2many' + def test_single(self): + self.assertEqual( + self.import_(['const', 'value/value'], [ + ['5', '63'] + ]), + ok(1)) + + (b,) = self.browse() + self.assertEqual(b.const, 5) + self.assertEqual(values(b.value), [63]) + + def test_multicore(self): + self.assertEqual( + self.import_(['const', 'value/value'], [ + ['5', '63'], + ['6', '64'], + ]), + ok(2)) + + b1, b2 = self.browse() + self.assertEqual(b1.const, 5) + self.assertEqual(values(b1.value), [63]) + self.assertEqual(b2.const, 6) + self.assertEqual(values(b2.value), [64]) + + def test_multisub(self): + self.assertEqual( + self.import_(['const', 'value/value'], [ + ['5', '63'], + ['', '64'], + ['', '65'], + ['', '66'], + ]), + ok(4)) + + (b,) = self.browse() + self.assertEqual(values(b.value), [63, 64, 65, 66]) + + def test_multi_subfields(self): + self.assertEqual( + self.import_(['value/str', 'const', 'value/value'], [ + ['this', '5', '63'], + ['is', '', '64'], + ['the', '', '65'], + ['rhythm', '', '66'], + ]), + ok(4)) + + (b,) = self.browse() + self.assertEqual(values(b.value), [63, 64, 65, 66]) + self.assertEqual( + values(b.value, 'str'), + 'this is the rhythm'.split()) + + def test_link(self): + id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, { + 'str': 'Bf', 'value': 109 + }) + id2 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, { + 'str': 'Me', 'value': 262 + }) + + self.assertEqual( + self.import_(['const', 'value/.id'], [ + ['42', str(id1)], + ['', str(id2)], + ]), + ok(2)) + + # No record values alongside id => o2m resolution skipped altogether, + # creates 2 records + b, b1 = self.browse() + self.assertEqual(b.const, 42) + self.assertEqual(values(b.value), []) + self.assertEqual(b1.const, 4) + self.assertEqual(values(b1.value), []) + + def test_link_2(self): + O2M_c = self.registry('export.one2many.child') + id1 = O2M_c.create(self.cr, openerp.SUPERUSER_ID, { + 'str': 'Bf', 'value': 109 + }) + id2 = O2M_c.create(self.cr, openerp.SUPERUSER_ID, { + 'str': 'Me', 'value': 262 + }) + + self.assertEqual( + self.import_(['const', 'value/.id', 'value/value'], [ + ['42', str(id1), '1'], + ['', str(id2), '2'], + ]), + ok(2)) + + (b,) = self.browse() + # if an id (db or xid) is provided, expectations that objects are + # *already* linked and emits UPDATE (1, id, {}). + # Noid => CREATE (0, ?, {}) + # TODO: xid ignored aside from getting corresponding db id? + self.assertEqual(b.const, 42) + self.assertEqual(values(b.value), []) + + # FIXME: updates somebody else's records? + self.assertEqual( + O2M_c.read(self.cr, openerp.SUPERUSER_ID, id1), + {'id': id1, 'str': 'Bf', 'value': 1, 'parent_id': False}) + self.assertEqual( + O2M_c.read(self.cr, openerp.SUPERUSER_ID, id2), + {'id': id2, 'str': 'Me', 'value': 2, 'parent_id': False}) + # function, related, reference: written to db as-is... # => function uses @type for value coercion/conversion From 3f1ab77e2858659388426b5148b8c91c71c6bc3a Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 1 Aug 2012 09:08:03 +0200 Subject: [PATCH 26/73] [TEST] o2m importing of multiple fields bzr revid: xmo@openerp.com-20120801070803-4sgvemuiifrhe61s --- openerp/tests/test_import.py | 66 +++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index c23d74d080a..08c52dd902c 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -772,6 +772,8 @@ class test_o2m(ImporterCase): values(b.value, 'str'), 'this is the rhythm'.split()) + # TODO: failing inline LINK_TO + def test_link(self): id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, { 'str': 'Bf', 'value': 109 @@ -788,7 +790,8 @@ class test_o2m(ImporterCase): ok(2)) # No record values alongside id => o2m resolution skipped altogether, - # creates 2 records + # creates 2 records => remove/don't import columns sideshow columns, + # get completely different semantics b, b1 = self.browse() self.assertEqual(b.const, 42) self.assertEqual(values(b.value), []) @@ -827,5 +830,66 @@ class test_o2m(ImporterCase): O2M_c.read(self.cr, openerp.SUPERUSER_ID, id2), {'id': id2, 'str': 'Me', 'value': 2, 'parent_id': False}) + # TODO: name_get? + +class test_o2m_multiple(ImporterCase): + model_name = 'export.one2many.multiple' + + def test_multi_mixed(self): + self.assertEqual( + self.import_(['const', 'child1/value', 'child2/value'], [ + ['5', '11', '21'], + ['', '12', '22'], + ['', '13', '23'], + ['', '14', ''], + ]), + ok(4)) + # Oh yeah, that's the stuff + (b, b1, b2) = self.browse() + self.assertEqual(values(b.child1), [11]) + self.assertEqual(values(b.child2), [21]) + + self.assertEqual(values(b1.child1), [12]) + self.assertEqual(values(b1.child2), [22]) + + self.assertEqual(values(b2.child1), [13, 14]) + self.assertEqual(values(b2.child2), [23]) + + def test_multi(self): + self.assertEqual( + self.import_(['const', 'child1/value', 'child2/value'], [ + ['5', '11', '21'], + ['', '12', ''], + ['', '13', ''], + ['', '14', ''], + ['', '', '22'], + ['', '', '23'], + ]), + ok(6)) + # What the actual fuck? + (b, b1) = self.browse() + self.assertEqual(values(b.child1), [11, 12, 13, 14]) + self.assertEqual(values(b.child2), [21]) + self.assertEqual(values(b1.child2), [22, 23]) + + def test_multi_fullsplit(self): + self.assertEqual( + self.import_(['const', 'child1/value', 'child2/value'], [ + ['5', '11', ''], + ['', '12', ''], + ['', '13', ''], + ['', '14', ''], + ['', '', '21'], + ['', '', '22'], + ['', '', '23'], + ]), + ok(7)) + # oh wow + (b, b1) = self.browse() + self.assertEqual(b.const, 5) + self.assertEqual(values(b.child1), [11, 12, 13, 14]) + self.assertEqual(b1.const, 36) + self.assertEqual(values(b1.child2), [21, 22, 23]) + # function, related, reference: written to db as-is... # => function uses @type for value coercion/conversion From e3dc73880280fca68d43eb4352c8d56c3e47debc Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 1 Aug 2012 10:21:08 +0200 Subject: [PATCH 27/73] [TEST] failure of o2m relinking via inline id (as exported) bzr revid: xmo@openerp.com-20120801082108-vkind3ocwsqgd0bv --- openerp/tests/test_import.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index 08c52dd902c..87e7ae9c350 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -772,7 +772,24 @@ class test_o2m(ImporterCase): values(b.value, 'str'), 'this is the rhythm'.split()) - # TODO: failing inline LINK_TO + def test_link_inline(self): + id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, { + 'str': 'Bf', 'value': 109 + }) + id2 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, { + 'str': 'Me', 'value': 262 + }) + + try: + self.import_(['const', 'value/.id'], [ + ['42', '%d,%d' % (id1, id2)] + ]) + except ValueError, e: + # should be Exception(Database ID doesn't exist: export.one2many.child : $id1,$id2) + self.assertIs(type(e), ValueError) + self.assertEqual( + e.args[0], + "invalid literal for int() with base 10: '%d,%d'" % (id1, id2)) def test_link(self): id1 = self.registry('export.one2many.child').create(self.cr, openerp.SUPERUSER_ID, { @@ -830,8 +847,6 @@ class test_o2m(ImporterCase): O2M_c.read(self.cr, openerp.SUPERUSER_ID, id2), {'id': id2, 'str': 'Me', 'value': 2, 'parent_id': False}) - # TODO: name_get? - class test_o2m_multiple(ImporterCase): model_name = 'export.one2many.multiple' From 471ef96edf890014df766a0ee956fe190f4e5480 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 1 Aug 2012 11:44:34 +0200 Subject: [PATCH 28/73] [FIX] some notes and comments in import/export tests bzr revid: xmo@openerp.com-20120801094434-t23wa2343utnevh6 --- openerp/tests/test_export.py | 5 ++--- openerp/tests/test_import.py | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index ed47b580365..e545fe24c61 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -218,6 +218,7 @@ class test_datetime(CreatorCase): .. note:: on the other hand, export uses user lang for name_get """ + # NOTE: ignores user timezone, always exports to UTC self.assertEqual( self.export('2011-11-07 21:05:48', context={'tz': 'Pacific/Norfolk'}), [[u'2011-11-07 21:05:48']]) @@ -260,7 +261,6 @@ class test_selection(CreatorCase): 'src': source, 'value': value }) - # FIXME: can't import an exported selection field label if lang != en_US self.assertEqual( self.export(2, context={'lang': 'fr_FR'}), [[u'Bar']]) @@ -274,8 +274,7 @@ class test_selection_function(CreatorCase): [[False]]) def test_value(self): - """ selection functions export the *value* itself - """ + # FIXME: selection functions export the *value* itself self.assertEqual( self.export(1), [[u'1']]) diff --git a/openerp/tests/test_import.py b/openerp/tests/test_import.py index 87e7ae9c350..de2553427e0 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/test_import.py @@ -240,7 +240,7 @@ class test_integer_field(ImporterCase): def test_nonsense(self): - # dafuq? why does that one raise an error? + # FIXME: shit error reporting, exceptions half the time, messages the other half self.assertRaises( ValueError, self.import_, ['value'], [['zorglub']]) @@ -384,7 +384,7 @@ class test_selection(ImporterCase): ['Qux'], ['Bar'], ['Foo'], - [2], + ['2'], ]), ok(4)) self.assertEqual([3, 2, 1, 2], values(self.read())) @@ -408,6 +408,8 @@ class test_selection(ImporterCase): 'value': value }) + # FIXME: can't import an exported selection field label if lang != en_US + # (see test_export.test_selection.test_localized_export) self.assertEqual( self.import_(['value'], [ ['toto'], @@ -443,16 +445,14 @@ class test_selection_function(ImporterCase): ] def test_imported(self): - """ By what bloody magic does that thing work? - - => import uses fields_get, so translates import label (may or may not - be good news) *and* serializes the selection function to reverse - it: import does not actually know that the selection field uses a - function + """ import uses fields_get, so translates import label (may or may not + be good news) *and* serializes the selection function to reverse it: + import does not actually know that the selection field uses a function """ + # NOTE: conflict between a value and a label => ? self.assertEqual( self.import_(['value'], [ - [3], + ['3'], ["Grault"], ]), ok(2)) @@ -461,6 +461,8 @@ class test_selection_function(ImporterCase): values(self.read())) def test_translated(self): + """ Expects output of selection function returns translated labels + """ self.registry('res.lang').create(self.cr, openerp.SUPERUSER_ID, { 'name': u'Français', 'code': 'fr_FR', @@ -478,7 +480,6 @@ class test_selection_function(ImporterCase): 'src': source, 'value': value }) - # FIXME: Fucking hell self.assertEqual( self.import_(['value'], [ ['toto'], From 3364a85ba5904ed4bdd8fe9c61574e794c0ce9a7 Mon Sep 17 00:00:00 2001 From: Minh Tran Date: Wed, 1 Aug 2012 16:28:05 +0200 Subject: [PATCH 29/73] fixed padding in kanban tags and searchview tags bzr revid: mit@openerp.com-20120801142805-5drbpx6t3ks7p86s --- addons/web/static/src/css/base.css | 1 + addons/web/static/src/css/base.sass | 1 + addons/web_kanban/static/src/css/kanban.css | 5 ++++- addons/web_kanban/static/src/css/kanban.sass | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 703683d4d96..a15d8ca4b22 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1592,6 +1592,7 @@ line-height: 8px; width: 12px; height: 12px; + padding-top: 1px; text-align: center; font-weight: bold; cursor: pointer; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 2a2d816241f..8723d9d6f2e 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1227,6 +1227,7 @@ $sheet-max-width: 860px line-height: 8px width: 12px height: 12px + padding-top: 1px text-align: center font-weight: bold cursor: pointer diff --git a/addons/web_kanban/static/src/css/kanban.css b/addons/web_kanban/static/src/css/kanban.css index 5830cb33e0f..246475a2b7f 100644 --- a/addons/web_kanban/static/src/css/kanban.css +++ b/addons/web_kanban/static/src/css/kanban.css @@ -1,4 +1,3 @@ -@charset "utf-8"; .openerp .oe_kanban_view { background: url(/web/static/src/img/form_sheetbg.png); height: inherit; @@ -169,6 +168,10 @@ .openerp .oe_kanban_view .oe_kanban_details h4 { margin: 0 0 4px 0; } +.openerp .oe_kanban_view .oe_kanban_details .oe_tag { + display: inline-block; + margin: 0 0 2px 0; +} .openerp .oe_kanban_view .oe_kanban_record { position: relative; display: block; diff --git a/addons/web_kanban/static/src/css/kanban.sass b/addons/web_kanban/static/src/css/kanban.sass index 44b3251c8fb..ae387244024 100644 --- a/addons/web_kanban/static/src/css/kanban.sass +++ b/addons/web_kanban/static/src/css/kanban.sass @@ -176,6 +176,9 @@ color: #4c4c4c h4 margin: 0 0 4px 0 + .oe_tag + display: inline-block + margin: 0 0 2px 0 .oe_kanban_record position: relative display: block From ad585bf761e99e8037d2ff50ddc303ad0d4a8086 Mon Sep 17 00:00:00 2001 From: Minh Tran Date: Fri, 3 Aug 2012 17:37:58 +0200 Subject: [PATCH 30/73] Restyling of 'edit avatar' button bzr revid: mit@openerp.com-20120803153758-03jg3xuwyrh2e198 --- addons/web/static/src/css/base.css | 29 ++++++++----------- addons/web/static/src/css/base.sass | 43 +++++++++++++---------------- addons/web/static/src/xml/base.xml | 7 +---- 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 1aa6f8aa54a..41ba0ce4cc3 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -224,6 +224,9 @@ font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif; font-size: 13px; } +.openerp .ui-widget-content a { + color: #8a89ba; +} .openerp .ui-menu .ui-menu-item { margin: 0 8px 0 0; padding: 0 0 0 12px; @@ -487,9 +490,6 @@ .openerp .oe_button_box button { margin: 4px; } -.openerp .oe_avatar { - margin: 0 6px 15px 0; -} .openerp .oe_avatar > img { height: 90px; max-width: 100px; @@ -2199,20 +2199,14 @@ .openerp .oe_form_editable .oe_form .oe_form_field_datetime { width: 11.5em !important; } -.openerp .oe_hidden_input_file { - overflow: hidden; - position: relative; - display: inline-block; - width: 45px; - height: 30px; -} .openerp .oe_hidden_input_file input.oe_form_binary_file { z-index: 0; line-height: 0; font-size: 12px; position: absolute; top: 1px; - right: 10px; + left: 0; + right: 0; opacity: 0; filter: alpha(opacity=0); -ms-filter: "alpha(opacity=0)"; @@ -2228,15 +2222,16 @@ } .openerp .oe_form .oe_form_field_image .oe_form_field_image_controls { position: absolute; - white-space: nowrap; top: 1px; - padding: 3px 0 0 0; - margin: 0 1px; + padding: 4px; + width: 100%; display: none; - width: 99%; text-align: center; - background: url(/web/static/src/img/form_sheetbg.png); - border-bottom: 1px dotted black; + color: #eeeeee; + background: rgba(37, 37, 37, 0.9); + -moz-border-radius: 3px 3px 0 0; + -webkit-border-radius: 3px 3px 0 0; + border-radius: 3px 3px 0 0; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -ms-box-sizing: border-box; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 0eb4472164f..a2b4231b130 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -7,7 +7,7 @@ $tag-bg-dark: #8786b7 $tag-border: #afafb6 $tag-border-selected: #a6a6fe $hover-background: #f0f0fa -$colour4: #8a89ba +$link-color: #8a89ba $sheet-max-width: 860px // }}} // Mixins {{{ @@ -226,6 +226,8 @@ $sheet-max-width: 860px .ui-widget font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif font-size: 13px + .ui-widget-content a + color: $link-color .ui-menu .ui-menu-item margin: 0 8px 0 0 @@ -295,7 +297,7 @@ $sheet-max-width: 860px background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=) @include radius(0 0 2px 2px) a - color: #8A89BA + color: $link-color &:hover text-decoration: underline &:focus @@ -397,7 +399,6 @@ $sheet-max-width: 860px button margin: 4px .oe_avatar - margin: 0 6px 15px 0 > img height: 90px max-width: 100px @@ -412,7 +413,7 @@ $sheet-max-width: 860px display: none span @include reset() - color: #8A89BA + color: $link-color font-weight: bold &:hover text-decoration: underline @@ -886,7 +887,7 @@ $sheet-max-width: 860px .oe_secondary_menu_section font-weight: bold margin-left: 8px - color: $colour4 + color: $link-color .oe_secondary_submenu padding: 2px 0 8px 0 margin: 0 @@ -904,7 +905,7 @@ $sheet-max-width: 860px top: 1px right: 1px font-size: 10px - background: $colour4 + background: $link-color color: white padding: 2px 4px margin: 1px 6px 0 0 @@ -918,7 +919,7 @@ $sheet-max-width: 860px margin: 0px padding: 1px 4px .oe_active - background: $colour4 + background: $link-color border-top: 1px solid lightGray border-bottom: 1px solid lightGray text-shadow: 0 1px 1px rgba(0,0,0,0.2) @@ -927,12 +928,12 @@ $sheet-max-width: 860px color: white .oe_menu_label background: #eee - color: $colour4 + color: $link-color text-shadow: 0 1px 1px white @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.2)) .oe_menu_counter background: #eee - color: #8a89ba + color: $link-color .oe_menu_toggler:before width: 0 height: 0 @@ -959,7 +960,7 @@ $sheet-max-width: 860px .oe_application width: 100% a - color: $colour4 + color: $link-color &:hover text-decoration: underline > div @@ -993,7 +994,7 @@ $sheet-max-width: 860px margin: 0 float: left a - color: $colour4 + color: $link-color .oe_button_group display: inline-block border: 1px solid #ababab @@ -1706,19 +1707,14 @@ $sheet-max-width: 860px * http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image */ .oe_hidden_input_file - overflow: hidden - position: relative - display: inline-block - width: 45px - height: 30px input.oe_form_binary_file z-index: 0 line-height: 0 font-size: 12px position: absolute - // Should be adjusted for all browsers top: 1px - right: 10px + left: 0 + right: 0 opacity: 0 filter: alpha(opacity = 0) -ms-filter: "alpha(opacity=0)" @@ -1733,15 +1729,14 @@ $sheet-max-width: 860px vertical-align: top .oe_form_field_image_controls position: absolute - white-space: nowrap top: 1px - padding: 3px 0 0 0 - margin: 0 1px + padding: 4px + width: 100% display: none - width: 99% text-align: center - background: url(/web/static/src/img/form_sheetbg.png) - border-bottom: 1px dotted black + color: #eee + background: rgba(37,37,37,0.9) + @include radius(3px 3px 0 0) @include box-sizing(border) &:hover .oe_form_field_image_controls display: block diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 014868eac90..37da66eb082 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1042,13 +1042,8 @@
- + Edit - - - + +
diff --git a/addons/account_analytic_default/account_analytic_default_view.xml b/addons/account_analytic_default/account_analytic_default_view.xml index 3e2ba0fe5f3..0bc0cd0c3bb 100644 --- a/addons/account_analytic_default/account_analytic_default_view.xml +++ b/addons/account_analytic_default/account_analytic_default_view.xml @@ -31,7 +31,7 @@ - + diff --git a/addons/account_asset/account_asset_view.xml b/addons/account_asset/account_asset_view.xml index cdc772ad65d..50c0443b02a 100644 --- a/addons/account_asset/account_asset_view.xml +++ b/addons/account_asset/account_asset_view.xml @@ -254,7 +254,7 @@ - + diff --git a/addons/account_coda/account_coda_view.xml b/addons/account_coda/account_coda_view.xml index 07f397c663f..b4b8e7796c2 100644 --- a/addons/account_coda/account_coda_view.xml +++ b/addons/account_coda/account_coda_view.xml @@ -249,7 +249,7 @@ - + diff --git a/addons/account_payment/account_payment_view.xml b/addons/account_payment/account_payment_view.xml index 5395bcafb17..dd9a78b261a 100644 --- a/addons/account_payment/account_payment_view.xml +++ b/addons/account_payment/account_payment_view.xml @@ -120,7 +120,7 @@
- + diff --git a/addons/analytic_user_function/analytic_user_function_view.xml b/addons/analytic_user_function/analytic_user_function_view.xml index 9c6e89d1bda..036fe7c29ff 100644 --- a/addons/analytic_user_function/analytic_user_function_view.xml +++ b/addons/analytic_user_function/analytic_user_function_view.xml @@ -90,7 +90,7 @@ - + @@ -114,7 +114,7 @@ - + diff --git a/addons/audittrail/audittrail_view.xml b/addons/audittrail/audittrail_view.xml index 65eca09a9a1..55c97760544 100644 --- a/addons/audittrail/audittrail_view.xml +++ b/addons/audittrail/audittrail_view.xml @@ -30,7 +30,7 @@ - + @@ -97,7 +97,7 @@ - + diff --git a/addons/base_calendar/base_calendar_view.xml b/addons/base_calendar/base_calendar_view.xml index d48795173ed..151d64f6370 100644 --- a/addons/base_calendar/base_calendar_view.xml +++ b/addons/base_calendar/base_calendar_view.xml @@ -34,7 +34,7 @@ - + @@ -222,7 +222,7 @@ - + diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml index d3371ad602a..96a13c4bcc6 100644 --- a/addons/base_calendar/crm_meeting_view.xml +++ b/addons/base_calendar/crm_meeting_view.xml @@ -99,7 +99,7 @@ - + @@ -232,7 +232,7 @@ - + @@ -277,7 +277,7 @@ - + diff --git a/addons/caldav/caldav_view.xml b/addons/caldav/caldav_view.xml index a45a2231738..eb89469ca19 100644 --- a/addons/caldav/caldav_view.xml +++ b/addons/caldav/caldav_view.xml @@ -10,7 +10,7 @@
- + @@ -84,7 +84,7 @@ - + diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index dd2ef7bd9e8..cf2466c4f13 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -166,15 +166,15 @@
- - + +
- + @@ -496,7 +496,7 @@ - + @@ -526,10 +526,10 @@
- +
- +
@@ -537,7 +537,7 @@