From fc36345ca0730cf8cbd197d9177bc884764188f2 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 23 Jul 2012 17:03:06 +0200 Subject: [PATCH 01/36] [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/36] [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/36] [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/36] [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/36] [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/36] [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 73f3f24fb4564e9edb7bf4ddede1ccf46aaa94e7 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 30 Jul 2012 12:32:14 +0200 Subject: [PATCH 07/36] [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 08/36] [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 09/36] [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 10/36] [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 11/36] [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 12/36] [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 13/36] [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 f8822ae313e380f72f5aff23b7f61d2f73939764 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 30 Jul 2012 18:23:18 +0200 Subject: [PATCH 14/36] [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 15/36] [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 6bad3e84fb1093b3e9c1a3c99bea3cc19fd95e2e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 31 Jul 2012 12:39:08 +0200 Subject: [PATCH 16/36] [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 17/36] [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 18/36] [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 19/36] [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 20/36] [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 21/36] [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 22/36] [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 23/36] [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 6d6e66cfc1758444c635e3e4abd55ca16e3e9dbe Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 6 Aug 2012 15:02:20 +0200 Subject: [PATCH 24/36] [IMP] fixme comment bzr revid: xmo@openerp.com-20120806130220-6s525hr0jolzenv9 --- openerp/tests/test_export.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openerp/tests/test_export.py b/openerp/tests/test_export.py index e545fe24c61..b8e33b418a6 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/test_export.py @@ -577,6 +577,7 @@ class test_m2m(CreatorCase): self.assertEqual( self.export(self.commands, fields=['const', 'value']), [[ # FIXME: hardcoded comma, import uses config.csv_internal_sep + # resolution: remove configurable csv_internal_sep u'4', u','.join(self.names) ]]) From 346397530da50bcbb0bc8d3248212b7756a5ba13 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 6 Aug 2012 15:27:31 +0200 Subject: [PATCH 25/36] [FIX] weird dict.update calls bzr revid: xmo@openerp.com-20120806132731-ck4usrs99qlh1pq9 --- openerp/osv/orm.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 02a13a41dcb..31d68b64911 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2434,9 +2434,9 @@ class BaseModel(object): context=context) result_template = dict.fromkeys(aggregated_fields, False) - result_template.update({groupby + '_count':0}) + result_template[groupby + '_count'] = 0 if groupby_list and len(groupby_list) > 1: - result_template.update(__context={'group_by': groupby_list[1:]}) + result_template['__context'] = {'group_by': groupby_list[1:]} # Merge the left_side (current results as dicts) with the right_side (all # possible values as m2o pairs). Both lists are supposed to be using the @@ -2455,10 +2455,8 @@ class BaseModel(object): grouped_value = right_side[0] if not grouped_value in known_values: line = dict(result_template) - line.update({ - groupby: right_side, - '__domain': [(groupby,'=',grouped_value)] + domain, - }) + line[groupby] = right_side + line['__domain'] = [(groupby,'=',grouped_value)] + domain result.append(line) known_values[grouped_value] = line while read_group_result or all_groups: From 00d2d2fe05b7adc4e04d665f281eb696871cfa53 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 7 Aug 2012 16:37:44 +0200 Subject: [PATCH 26/36] [IMP] impex tests: moved test cases and models in their own test module. bzr revid: vmt@openerp.com-20120807143744-zsbwktjmy5d79jki --- openerp/tests/__init__.py | 4 +--- openerp/tests/addons/test_impex/__init__.py | 1 + openerp/tests/addons/test_impex/__openerp__.py | 15 +++++++++++++++ .../test_impex/models.py} | 7 ------- openerp/tests/addons/test_impex/tests/__init__.py | 13 +++++++++++++ .../{ => addons/test_impex/tests}/test_export.py | 4 +--- .../{ => addons/test_impex/tests}/test_import.py | 4 +--- 7 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 openerp/tests/addons/test_impex/__init__.py create mode 100644 openerp/tests/addons/test_impex/__openerp__.py rename openerp/tests/{export_models.py => addons/test_impex/models.py} (96%) create mode 100644 openerp/tests/addons/test_impex/tests/__init__.py rename openerp/tests/{ => addons/test_impex/tests}/test_export.py (99%) rename openerp/tests/{ => addons/test_impex/tests}/test_import.py (99%) diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index 4b8fa798aea..927a375df64 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_import + test_uninstall fast_suite = [ test_ir_sequence, @@ -18,8 +18,6 @@ fast_suite = [ checks = [ test_expression, test_orm, - test_export, - test_import, ] # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tests/addons/test_impex/__init__.py b/openerp/tests/addons/test_impex/__init__.py new file mode 100644 index 00000000000..bff786c0885 --- /dev/null +++ b/openerp/tests/addons/test_impex/__init__.py @@ -0,0 +1 @@ +import models diff --git a/openerp/tests/addons/test_impex/__openerp__.py b/openerp/tests/addons/test_impex/__openerp__.py new file mode 100644 index 00000000000..f3b5511dc30 --- /dev/null +++ b/openerp/tests/addons/test_impex/__openerp__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'test-import-export', + 'version': '0.1', + 'category': 'Tests', + 'description': """A module to test import/export.""", + 'author': 'OpenERP SA', + 'maintainer': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'depends': ['base'], + 'data': [], + 'installable': True, + 'auto_install': False, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tests/export_models.py b/openerp/tests/addons/test_impex/models.py similarity index 96% rename from openerp/tests/export_models.py rename to openerp/tests/addons/test_impex/models.py index 3bb5cf0c6d2..455ea6b22f4 100644 --- a/openerp/tests/export_models.py +++ b/openerp/tests/addons/test_impex/models.py @@ -34,7 +34,6 @@ models = [ for name, field in models: attrs = { '_name': 'export.%s' % name, - '_module': 'base', '_columns': { 'const': fields.integer(), 'value': field @@ -57,7 +56,6 @@ for name, field in models: class One2ManyChild(orm.Model): _name = 'export.one2many.child' - _module = 'base' # FIXME: orm.py:1161, fix to name_get on m2o field _rec_name = 'value' @@ -72,7 +70,6 @@ class One2ManyChild(orm.Model): class One2ManyMultiple(orm.Model): _name = 'export.one2many.multiple' - _module = 'base' _columns = { 'const': fields.integer(), @@ -83,7 +80,6 @@ class One2ManyMultiple(orm.Model): 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' @@ -97,16 +93,13 @@ class One2ManyChildMultiple(orm.Model): 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' diff --git a/openerp/tests/addons/test_impex/tests/__init__.py b/openerp/tests/addons/test_impex/tests/__init__.py new file mode 100644 index 00000000000..d6af53cba1b --- /dev/null +++ b/openerp/tests/addons/test_impex/tests/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +from . import test_export, test_import + +fast_suite = [ +] + +checks = [ + test_export, + test_import, +] + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tests/test_export.py b/openerp/tests/addons/test_impex/tests/test_export.py similarity index 99% rename from openerp/tests/test_export.py rename to openerp/tests/addons/test_impex/tests/test_export.py index b8e33b418a6..812a84b3a21 100644 --- a/openerp/tests/test_export.py +++ b/openerp/tests/addons/test_impex/tests/test_export.py @@ -3,13 +3,11 @@ import itertools import openerp.modules.registry import openerp -from . import common, export_models +from openerp.tests import common def setUpModule(): openerp.tools.config['update'] = {'base': 1} - openerp.modules.registry.RegistryManager.new( - common.DB, update_module=True) class CreatorCase(common.TransactionCase): model_name = False diff --git a/openerp/tests/test_import.py b/openerp/tests/addons/test_impex/tests/test_import.py similarity index 99% rename from openerp/tests/test_import.py rename to openerp/tests/addons/test_impex/tests/test_import.py index de2553427e0..9999a9c6e5d 100644 --- a/openerp/tests/test_import.py +++ b/openerp/tests/addons/test_impex/tests/test_import.py @@ -2,7 +2,7 @@ import openerp.modules.registry import openerp -from . import common, export_models +from openerp.tests import common def ok(n): """ Successful import of ``n`` records @@ -28,8 +28,6 @@ def values(seq, field='value'): 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 From b77da916e5386b5e291f3da6b54b4253ff9dac3e Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 7 Aug 2012 16:45:19 +0200 Subject: [PATCH 27/36] [FIX] impex tests: removed unnecessary code. bzr revid: vmt@openerp.com-20120807144519-r7tz3fh6ieslra1c --- openerp/tests/addons/test_impex/tests/test_export.py | 3 --- openerp/tests/addons/test_impex/tests/test_import.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/openerp/tests/addons/test_impex/tests/test_export.py b/openerp/tests/addons/test_impex/tests/test_export.py index 812a84b3a21..7e67ba581a1 100644 --- a/openerp/tests/addons/test_impex/tests/test_export.py +++ b/openerp/tests/addons/test_impex/tests/test_export.py @@ -6,9 +6,6 @@ import openerp from openerp.tests import common -def setUpModule(): - openerp.tools.config['update'] = {'base': 1} - class CreatorCase(common.TransactionCase): model_name = False diff --git a/openerp/tests/addons/test_impex/tests/test_import.py b/openerp/tests/addons/test_impex/tests/test_import.py index 9999a9c6e5d..ab12f0375b5 100644 --- a/openerp/tests/addons/test_impex/tests/test_import.py +++ b/openerp/tests/addons/test_impex/tests/test_import.py @@ -26,9 +26,6 @@ def error(row, message, record=None, **kwargs): def values(seq, field='value'): return [item[field] for item in seq] -def setupModule(): - openerp.tools.config['update'] = {'base': 1} - class ImporterCase(common.TransactionCase): model_name = False From 80d8c2c866df4940c9fce48c32e4399b1b1303ff Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Thu, 9 Aug 2012 11:57:33 +0200 Subject: [PATCH 28/36] [FIX] import_google: bad inheritance of the crm_meeting_form view bzr revid: stw@openerp.com-20120809095733-b4pjaww2muo5ncze --- addons/import_google/sync_google_calendar_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/import_google/sync_google_calendar_view.xml b/addons/import_google/sync_google_calendar_view.xml index 626d803d4df..c7f68de15a7 100644 --- a/addons/import_google/sync_google_calendar_view.xml +++ b/addons/import_google/sync_google_calendar_view.xml @@ -7,8 +7,8 @@ form - - + + [('user_id', 'in', [uid, False])] From 6aad16cf6b81aee5cf4563ef76a988d150dc9da1 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Thu, 9 Aug 2012 12:24:31 +0200 Subject: [PATCH 29/36] [FIX] account: show the product on the account.invoice.line bzr revid: stw@openerp.com-20120809102431-mxzx6c2iagwjp28f --- addons/account/account_invoice_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 501a50b96c2..03d127249fb 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -347,7 +347,7 @@ - + Date: Thu, 9 Aug 2012 12:50:09 +0200 Subject: [PATCH 30/36] [FIX] project*: Replace the labels with 'Partner' by Contact bzr revid: stw@openerp.com-20120809105009-0f7mw99dnavgs01s --- addons/project/project.py | 2 +- addons/project/project_demo.xml | 2 +- addons/project/project_view.xml | 4 ++-- addons/project/report/project_report.py | 2 +- addons/project/report/project_report_view.xml | 2 +- addons/project_issue/project_issue.py | 2 +- addons/project_issue/project_issue_view.xml | 2 +- addons/project_issue/report/project_issue_report.py | 2 +- addons/project_issue/report/project_issue_report_view.xml | 2 +- addons/project_timesheet/project_timesheet_view.xml | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/addons/project/project.py b/addons/project/project.py index 707df5607c6..6f3b23f4746 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -788,7 +788,7 @@ class task(base_stage, osv.osv): }), 'user_id': fields.many2one('res.users', 'Assigned to'), 'delegated_user_id': fields.related('child_ids', 'user_id', type='many2one', relation='res.users', string='Delegated To'), - 'partner_id': fields.many2one('res.partner', 'Partner'), + 'partner_id': fields.many2one('res.partner', 'Contact'), 'work_ids': fields.one2many('project.task.work', 'task_id', 'Work done'), 'manager_id': fields.related('project_id', 'analytic_account_id', 'user_id', type='many2one', relation='res.users', string='Project Manager'), 'company_id': fields.many2one('res.company', 'Company'), diff --git a/addons/project/project_demo.xml b/addons/project/project_demo.xml index cf3353a3750..fb4b37edb2b 100644 --- a/addons/project/project_demo.xml +++ b/addons/project/project_demo.xml @@ -71,7 +71,7 @@ - Partner's suggestion + Contact's suggestion Feature request diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 199dd5f712d..604b04fdb6c 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -171,10 +171,10 @@ - + - + diff --git a/addons/project/report/project_report.py b/addons/project/report/project_report.py index 36e025ab509..6c1f5443835 100644 --- a/addons/project/report/project_report.py +++ b/addons/project/report/project_report.py @@ -53,7 +53,7 @@ class report_project_task_user(osv.osv): 'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), ('10','October'), ('11','November'), ('12','December')], 'Month', readonly=True), 'state': fields.selection([('draft', 'Draft'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')],'Status', readonly=True), 'company_id': fields.many2one('res.company', 'Company', readonly=True, groups="base.group_multi_company"), - 'partner_id': fields.many2one('res.partner', 'Partner', readonly=True), + 'partner_id': fields.many2one('res.partner', 'Contact', readonly=True), } _order = 'name desc, project_id' diff --git a/addons/project/report/project_report_view.xml b/addons/project/report/project_report_view.xml index ab880536ced..9383a588e3a 100644 --- a/addons/project/report/project_report_view.xml +++ b/addons/project/report/project_report_view.xml @@ -80,7 +80,7 @@ - + diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index 935935e28ca..7b2afbeacf2 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -219,7 +219,7 @@ class project_issue(base_stage, osv.osv): 'section_id': fields.many2one('crm.case.section', 'Sales Team', \ select=True, help='Sales team to which Case belongs to.\ Define Responsible user and Email account for mail gateway.'), - 'partner_id': fields.many2one('res.partner', 'Partner', select=1), + 'partner_id': fields.many2one('res.partner', 'Contact', select=1), 'company_id': fields.many2one('res.company', 'Company'), 'description': fields.text('Description'), 'state': fields.related('stage_id', 'state', type="selection", store=True, diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index de52ec8d033..c01409b9466 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -210,7 +210,7 @@ - + diff --git a/addons/project_issue/report/project_issue_report.py b/addons/project_issue/report/project_issue_report.py index b43cbc51c50..bb95d5195c4 100644 --- a/addons/project_issue/report/project_issue_report.py +++ b/addons/project_issue/report/project_issue_report.py @@ -63,7 +63,7 @@ class project_issue_report(osv.osv): 'project_id':fields.many2one('project.project', 'Project',readonly=True), 'version_id': fields.many2one('project.issue.version', 'Version'), 'user_id' : fields.many2one('res.users', 'Assigned to',readonly=True), - 'partner_id': fields.many2one('res.partner','Partner',domain="[('object_id.model', '=', 'project.issue')]"), + 'partner_id': fields.many2one('res.partner','Contact',domain="[('object_id.model', '=', 'project.issue')]"), 'channel_id': fields.many2one('crm.case.channel', 'Channel',readonly=True), 'task_id': fields.many2one('project.task', 'Task',domain="[('object_id.model', '=', 'project.issue')]" ), 'email': fields.integer('# Emails', size=128, readonly=True), diff --git a/addons/project_issue/report/project_issue_report_view.xml b/addons/project_issue/report/project_issue_report_view.xml index c1ae69517c2..076e542b827 100644 --- a/addons/project_issue/report/project_issue_report_view.xml +++ b/addons/project_issue/report/project_issue_report_view.xml @@ -62,7 +62,7 @@ - + diff --git a/addons/project_timesheet/project_timesheet_view.xml b/addons/project_timesheet/project_timesheet_view.xml index 46e3ac43551..1c6ba1a05f4 100644 --- a/addons/project_timesheet/project_timesheet_view.xml +++ b/addons/project_timesheet/project_timesheet_view.xml @@ -36,7 +36,7 @@ - + From 5ac8f97de03d3109946989772aaf223c44191382 Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of openerp <> Date: Fri, 10 Aug 2012 04:58:41 +0000 Subject: [PATCH 31/36] Launchpad automatic translations update. bzr revid: launchpad_translations_on_behalf_of_openerp-20120810045841-5b2hoy7t0d4sotjr --- openerp/addons/base/i18n/nl.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/i18n/nl.po b/openerp/addons/base/i18n/nl.po index 818bd5c4602..45560f7e363 100644 --- a/openerp/addons/base/i18n/nl.po +++ b/openerp/addons/base/i18n/nl.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:44+0000\n" -"PO-Revision-Date: 2012-02-19 13:01+0000\n" +"PO-Revision-Date: 2012-08-09 06:15+0000\n" "Last-Translator: Erwin \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-07-19 04:38+0000\n" -"X-Generator: Launchpad (build 15637)\n" +"X-Launchpad-Export-Date: 2012-08-10 04:58+0000\n" +"X-Generator: Launchpad (build 15761)\n" #. module: base #: model:res.country,name:base.sh @@ -1443,7 +1443,7 @@ msgstr "" "U kunt de navolgende extra data toevoegen aan een verkooporder:\n" "* Aanvraagdatum\n" "* Bevestigde datum\n" -"* Effectieve datum\n" +"* Boek datum\n" #. module: base #: model:ir.module.module,shortdesc:base.module_account_sequence @@ -8110,7 +8110,7 @@ msgstr "Kenmerk" #. module: base #: model:ir.module.module,shortdesc:base.module_analytic msgid "Analytic Accounting" -msgstr "Kostenplaats boekhouding" +msgstr "Kostenplaatsen" #. module: base #: view:ir.actions.report.xml:0 From 7bd54c9c0b0d6c2333e270b2056fbbc87c608e6d Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 10 Aug 2012 11:45:22 +0200 Subject: [PATCH 32/36] [IMP] correction of some comments bzr revid: nicolas.vanhoren@openerp.com-20120810094522-4dpdw06ql3ex0ig3 --- addons/web_kanban/static/src/js/kanban.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index b4cffcba2d5..36394894167 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -503,9 +503,9 @@ instance.web_kanban.KanbanGroup = instance.web.OldWidget.extend({ } }, /** - * Handles a non-erroneous response from name_create + * Handles a newly created record * - * @param {(Id, String)} record name_get format for the newly created record + * @param {id} id of the newly created record */ quick_created: function (record) { var id = record, self = this; From d1d6203f4f9c546396f777401f0cdfea8a1af28c Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 10 Aug 2012 14:09:36 +0200 Subject: [PATCH 33/36] [FIX]: orm: reverted a change in a previous commit (where a try/except was removed). bzr revid: vmt@openerp.com-20120810120936-13lewpbjn7euveg0 --- openerp/osv/orm.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 2f850e23607..563c514726c 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4191,7 +4191,11 @@ 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)) - cr.execute("SELECT nextval('"+self._sequence+"')") + 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.')) id_new = cr.fetchone()[0] for table in tocreate: From cf57a87d41048f9031e99ecddc1c97f5f469f438 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Fri, 10 Aug 2012 15:45:17 +0200 Subject: [PATCH 34/36] [IMP] point of sale backend, useability improvements bzr revid: fp@tinyerp.com-20120810134517-tenqseu6otf0rdin --- addons/point_of_sale/point_of_sale.py | 34 +++- addons/point_of_sale/point_of_sale_data.xml | 7 +- addons/point_of_sale/point_of_sale_view.xml | 175 ++++++++++++++++++++ 3 files changed, 208 insertions(+), 8 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 6d95b561fce..aa152ad94d3 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -55,7 +55,7 @@ class pos_config(osv.osv): 'shop_id' : fields.many2one('sale.shop', 'Shop', required=True), 'journal_id' : fields.many2one('account.journal', 'Sale Journal', - required=True, domain=[('type', '=', 'sale')], + domain=[('type', '=', 'sale')], help="Accounting journal used to post sales entries."), 'iface_self_checkout' : fields.boolean('Self Checkout Mode', help="Check this if this point of sale should open by default in a self checkout mode. If unchecked, OpenERP uses the normal cashier mode by default."), @@ -301,11 +301,31 @@ class pos_session(osv.osv): def create(self, cr, uid, values, context=None): config_id = values.get('config_id', False) or False - - pos_config = None if config_id: - pos_config = self.pool.get('pos.config').browse(cr, uid, config_id, context=context) + # journal_id is not required on the pos_config because it does not + # exists at the installation. If nothing is configured at the + # installation we do the minimal configuration. Impossible to do in + # the .xml files as the CoA is not yet installed. + jobj = self.pool.get('pos.config') + pos_config = jobj.browse(cr, uid, config_id, context=context) + if not pos_config.journal_id: + jid = jobj.default_get(cr, uid, ['journal_id'], context=context)['journal_id'] + if jid: + jobj.write(cr, uid, [pos_config.id], {'journal_id': jid}, context=context) + else: + raise osv.except_osv( _('error!'), + _("Unable to open the session. You have to assign a sale journal to your point of sale.")) + # define some cash journal if no payment method exists + if not pos_config.journal_ids: + cashids = self.pool.get('account.journal').search(cr, uid, [('journal_user','=',True)], context=context) + if not cashids: + cashids = self.pool.get('account.journal').search(cr, uid, [('type','=','cash')], context=context) + self.pool.get('account.journal').write(cr, uid, cashids, {'journal_user': True}) + jobj.write(cr, uid, [pos_config.id], {'journal_ids': [(6,0, cashids)]}) + + + pos_config = jobj.browse(cr, uid, config_id, context=context) bank_statement_ids = [] for journal in pos_config.journal_ids: bank_values = { @@ -317,7 +337,8 @@ class pos_session(osv.osv): values.update({ 'name' : pos_config.sequence_id._next(), - 'statement_ids' : [(6, 0, bank_statement_ids)] + 'statement_ids' : [(6, 0, bank_statement_ids)], + 'config_id': config_id }) return super(pos_session, self).create(cr, uid, values, context=context) @@ -329,7 +350,7 @@ class pos_session(osv.osv): return True def wkf_action_open(self, cr, uid, ids, context=None): - # si pas de date start_at, je balance une date, sinon on utilise celle de l'utilisateur + # second browse because we need to refetch the data from the DB for cash_register_id for record in self.browse(cr, uid, ids, context=context): values = {} if not record.start_at: @@ -338,6 +359,7 @@ class pos_session(osv.osv): record.write(values, context=context) for st in record.statement_ids: st.button_open(context=context) + return self.open_frontend_cb(cr, uid, ids, context=context) def wkf_action_opening_control(self, cr, uid, ids, context=None): diff --git a/addons/point_of_sale/point_of_sale_data.xml b/addons/point_of_sale/point_of_sale_data.xml index 108dd98c750..92c04944792 100644 --- a/addons/point_of_sale/point_of_sale_data.xml +++ b/addons/point_of_sale/point_of_sale_data.xml @@ -12,12 +12,15 @@ automatic 100 - - Main + + + done + + diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index 28407d0bcae..15a1aa7e6bb 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -11,6 +11,181 @@ Others + + 1.00 + Unreferenced Products + + iVBORw0KGgoAAAANSUhEUgAAAFUAAABQCAYAAABoODnpAAAABGdBTUEAALGPC/xhBQAAAAFzUkdC +AkDAfcUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dE +AP4A/gD+6xjUggAAAAlwSFlzAAAXEgAAFxIBZ5/SUgAAAAl2cEFnAAAAVQAAAFAA8yp5GQAAJFFJ +REFUeNrdfXecVNXZ//fembnTy07f2dm+O9sXFtgC0kEBX9EQI0YRDWpiibFEjeaNJmqKRiXRBGI0 +eWMvYAMEEYWlKGULZXudrbO703sv9/7+WCHbUDC0/J7PZygzd855zvc85zzn+Z7nnCFwsYRhILr+ +DQTnlHD0va1Kud+ZJgr5c0RhfzY/GkzjR0PJVCwqpxJRMUnTPAAEACZBssIxNscfZVPOMMUfDlKC +/gBPaPTxxUaXSD5gyiiwC7+oi3m33QGwiYvStAtaK2d9HWJqNZFRd0CpdlsKlT57pcJrn6X0OfLl +fkeyLOCWSEJeDj8SJLixCDiJGFh0AgTDnCqDJkjQJIkYi4MIh4sQV8B4eeKoWyjzOcWKEbtE2eEQ +K+vtYsURq0zb2jdrvp1jszKxB8v//wGVfLkN9Pfzkf67txUpdlNVsntkeYrDNF/vMGVrXSNCud8F +fjQINp04pQwz5vvMFGUSU/ybARAnWQhyBXCJ5DDLtIEhhd44JE/5ciQp+bMhhf5I3/KbHJy2Jib2 +YOl/J6jKR7bCI1Wy8ntOGFIdg6syrL3fyzIbi9PtA/wkvxNUIn4KDOY/q2pSg042KspiwyVMwqAy +LWzUZrf2qTO3DipTP+zMKmmXuBwJ63Pf/+8ANfmh9+EWK1j5/Y3TM6x9t+QPta/KG+pI0TmHCF48 +es5BPJMGEgAibArDSTqmIyVvpCMlf0ufOuPV9rSSE1K/Mz68fvU5r/OcSNKvPoVr9gqidMvGvExL +zx3FA803FA80abRuC1gMDfoMGw+Mgk4TBBIECzRJgibIU8+RDA0WnQDJ0CAZZtx3vq2zSAAJgoRF +qkZrapG1Kb10c48m6+8NC25oVbYdYuxPX32JgPpSHyoOvgeHTJuUZe6+pbS/8WdlPceyUpzDIBn6 +tA09CSKDUSvy8cXwCKSMRyCL+ARiX4Ar9IQovifKpgJxFifCAAwJhmAnYjwqHhPyoiGJIBKUikNe +sTTo4coCHkIU8oEbj4D8FpAJjDq84aRkHM+a2deYXrqxR5v1qspjdRwuWQI88p85tf8IVN2DmzGc +XU6U13w0u3Cw5deVXTVLDMOdbCoRO61lnrQWL18Mi0xLD8t1drMsucshVjS6hdJmP0/cHeCJhv08 +kdMrkAR9Amk0IlUnQLIZJGIE12NhS4IeShzyCYThgEwYCejEIV+ONOgpVnrtJRq3OU/nGlJqXWZS +EvKB9XXHMqfRJcrioCs5N1FjqNrXmlr4ZG3JkoP6oXba9OcbLjyoRT95CR6BVFhgarutvLv2kYqu +Wp084JoSzJNWGaD4MClSaaM2e2hAmXbQkqTdbZOoaoeVaX32pSv9GB5mcHPK2SvzjhnQawjFZ9uF +Kdb+DKXXVql1m5em2QbmZFl69KmOQVIQCQLfAK5LKENtToWlLqfi2bbUglekAY+/+ZW7AOLsITrr +bwhfakTZ/m1wSFUpRYMtv53bdmBNyUAzxU7EJyl8snC3QIquZEOoIyWvtk+d8eGwPGVXT0ZBr9hi +jVn/eB3AO4f+spOBauNm+DTJ7Kz+1kydc/iKdGvfdflDbRWG4U6+LOiZElwCQJxkoyW1MPpV4fz3 +mtOKH1O7zIO1M5Yj+GjV+QNV9/NNKO86ggFVRsn03hMvLmquXpRu6z9t73t5IrSmFgWb0kuqezTZ +/+zXZO6d+/Eb3vdeegNYrjx3QJ6ucTuGsHbdWlTf9BNJuqV3UZbZeGvJQOOSwsFWoSTsP+2oGlSm +orp48YETmWX3F/Y1Ht9ddQ3MT19z7kFNvf8dfO/IhziSN2dOeXfdxiWNu6erfPZJihEA4iw2OpMN +8bqc8kOdKXkvGnW5u1RDA4HGZ34GpFHnHcxJ4omh5J4NsGv1gqzhrivyhjrum2msn5s33MnmJGKT +jIIEYBcpUF26pLk2p/ynN3z17oE/rXwQgy+uOXegZt/zOq6u+QAHC+YvqOo88vLSxt150qBnyiHk +EMlRY6gyHc+a8WK3NufVzIFWR/Wqe8CsS7vwYE6UTf1Y9PbL6EvPk+eMdP2orOf4fZVdNWlKn33K +tnj5YlSXLOk+nDfnzlVHPtzz8vK70fPXW/5zUDN/9hqm9TZgSKG/bE77wX9d3viFQRzyTTnku5Nz +EwcK5+9qTi1+4ti0m+qlnbsYz0s3XWwoJ4ng7k0Izl+Nss9enFFkanlyXuuBFYaRLtbEJSABwM8T +YXfpUuOh/MtuyzYb99fmlqN7423fWD7rGwG971WkWU2wyjSllV1H/nlFwxdFkpB3SkBpgkRNblXw +SM6s3/9kLnXgk7sW8Njtn/MKCgq4fD6f29bWxp02bRq3sLCQa7PZuPv27ePeeeedXI1Gww2FQtyn +nnqK+/bbb3MpiuLqdDpuRUUF99ixY1yZTMadPXs2d9GiRdyFCxdy582bx50/fz73nXfe4T7//PNc +gUDAraqq4j711FPcV199lVtYWMgtLCzkrlmzhrtt2zbuNddcwy0sLORu376dS1EUd/369Vyl6Svu +jnUVVIHYYetUGfY1QkKBpqdpXWY2ixk/oXHjUaQ4h+Rhild+IrOsRuOxjnDmr4WjfttpcWN/E6hK +txM2qUpf0VnzwpLGPdOmGvKneoehsbBlnygcj70wUrL6gThNX8ho9KyFIAiM2P34Qb6GaaK11GD2 +Ncyuz4RY0nwA/Fj4VDsZAOKQD4ubqouCXMGfjxhmr5UFXP3aJz+G+Terzg7UsltfxJBMI5zTfuip +xc3Vi6ZySmOFASCMBLCi7YD6+C6Fet/sStBjwshLWTgkASk3CHr11WhIz0DxjjcgmgCswu/Eoqa9 +83x8ye8O5c25K7lvwG8+TXnkVG+W3foCjv/fvSgcaPnx3LYv16Tb+scBOurhOYizqXGgMQAEsTBm +7v0A9Y8+jrZeFwg252JjdkbC0DSqSjOx5A+Po2HFTQhy+OPaRgNIdQxiXuuB6wsHW+46/vC9RNm6 +F88M1Iq1z0MS8mHWj/502Sxj/cMlA03UxHEcY3Gwu3QRtk6/HAGeaBKwvEQMsw5th6GmHj0mPwjW +N07dl4wwDIOZBXpk3HoDdhVehtgURlM02MIp7677ecUz6+fLgm6U3/z8t4NKs0j0K9OTCk2tj1V2 +HdGxv+Y9/y0EagyVCC5bjPDcMlTP+x68fMmkyik6jqoT1ZheVw+NrgTRxCU9xY4TIRmAb14V6ovn +gZ4wgbHoBCq6a7WFppbH+1XpCoacPNjHvVNx83O4ed8byDF3r63oql2q8LsmLTGM6UVonjkLSlEC +aQouvoy78EZuJVwi+bjCGAAkncCivuOoOlqLHTsOIxiJ/VfMsWAYpKq5OF5UjM6MkkkGkxRwo7Kz +ZmHOSPeP/vbaz1ExwVrHgUoAePmKO3KKB5ruzhvuYE+cR22CJHhvuQtzF5SCjscAAH5rH7YNNOPd +onmwS9WTTJ9gaExrPQz3r3+JfXtOABwewFz6VkvHoigpTUVw3d2wiBST5tfckS5WyUDTXbf9eGM+ +MaE9pzCoXPscal5/CFmWnh+V9RzP48aj4x6MESQ6FlyNFbdfD7mIB+brggiCQMQ9AlZRCqqXXodh +qXYysGBQ2FGL8j2fYqjHgyjYuLD8/9kLA0Am4OJ/bv0BuhevQpQY7xeoRAzTe49nZ1p6b6t54iGi +8ubnJoNKACj+yUt5BabWG1Idg+O8PQmgW1+A3DtuR16qAlMtQeUCEkSWDIdWrkFfkm7KYV482IKZ +Oz5AjrYQMYK65C2WZhjk6hUouPN2dGUUT7LWFKcJhabW1aVPbywca60kAFStfRb3b/8T0m391xcO +tmSx6cSpBwgAPjYPnqt/iCVLZp1WAYZhoBRRsJBuvJlXjj5t1iRgGQAFlm5c31qH/R8fwLDT913o +ygsuixaUwb/qRngpwbg2sWkaRQPNaenWvh8efvMXqLz52X+DyhAEHrvh97pss/HaFOfQpEV+d24Z +Zq35PuQi3rcqEPHakVKaAv9DT6I7JW9KYLMsPVD8+Sl88sqHiBL8S955SQUUKn/4PRjzxhsVDSDZ +NYwcs/H7eXf9M/UklUJWrv0jZAE39E7TYsNwRyH3awcEfG2lHD7Cy66JlpflntFYZRhAIebjxruu +Q8f169CoywMxATYGQJp9AHN2vINwpxXOEAPyEjfZmaVZMd/C5XbPBGulEnEYRjry9A7TUqXXhtlr +nwVJEAR2zf0hJ8VhWplqH2BPXEL1qDJC6suqDoh5nMSZKsAwDGQCLoRKoH3VDTiWVjxpW4IGkOq1 +oGrbGyhgyUEIFaDpb9tzvXgi5LLDwrKyD3rUWcGJSyy93cRKcZpW7ii/hksDIMEAhW0HM9JtA7Mn +MlBRFgcdyYa2svKiL3CakPabhM8mwROG8bpKh+OGCtBTAKvx2nBLZy2Gdh5CY9fQpTzHkpVzp1d3 +6gwtEfa/ifZRwsWLdFt/ZUnviSwCACkJeqH2WCr1jgEda4wHIwDYJCr0KNM/L01XDuM7bhLGIyEI ++CGIf/VrNExbCJpkTfKiioATxe/9DTuf/BMsPgYkedb9d0FAnZOrNfco0j61SlTjLIwEkGof1Kq8 +1iq53wny8w9+C7XXdpnWbWFNHPompT7YJ1TuIoDEdwWVYRhQbBLXXjMf1D33YG9uJeIszqQoRRb2 +Yfaud6Bo7sWAPQzy0uQLmD6h6vNBZZp3Yr6X2mMh1R7b3E8/epogNQ9/IFd7rGXi4MShz4ZJru9u +Cwoa8B2G/kThUWwoJAlYll+OA6ULEZ2CrJBEg5i790MU9NsgUmUjcenNsWR7WNxsUug7o6x/s28n +OVeV11qq+/kmJal2W9OVXls6lRjv9f08MSwyzVHm0/kunKP0IBIMdHIWPqYI7J62GCE2dxKw/GgQ +13bXQH2oHnsOtYxyspfOPEswv8nyWmSaeq9gPInEScSh8trSVV5bJikNeQxyn1M2MT3RKUqCU6w4 ++tFHvpNvnQudwNAJxHwmpN3zY5xYsQaBCUsUBgAvHsGsA1vQ8MhjaGi3gLyEONk3OjhwieTHXCL5 +pJROuc8plQS9+WxRyG+QhjyTVvUukTzkEUjbInzROVeMADB/lgFmQw62OZxYdmwPxGHfOKadS8cx +48gOxP0+dLPmIF3HBzGJhrzwQotE8Aik7S5Rkh/AOHCkQQ8lDvvySGEkkCUIB8bNpzQAt1Dm9vHE +JkWS+LwoRxIEtBISoVkF2LfoWrj40kkWy6ETqGo+gNw9u2G2JRC7BKgCrVIGL19scgtkzrEzPgNA +EAlAGA5kkvxISMeLRcZ9kSZJ+Hkih0codepU0vOjHUGAYRjo5VwcJQN4f9pCOESKKTnZ2V11yNz2 +MbweNkKxM45BzovoNTK4RHK3nydyJMjxKxRuLAJ+NKQjufGwij0hSyNOshHm8Fx2sTIklwrPu6Ih +xyDSrrwMxtsehFkymZMFQ6OivwHZH22C38VCKHbxVgXKJBGsCn0oRPEdcda/900ZAOxEHNxYWEFy +4nERix7f+zTJQoxN+SPqjJiQf/7TdBiGQa5eie//4g7sX3Y9BmXJUwDLYPpAE8p2fgTbQABR5uKs +Y0V8LpBTEY+yKX9iQiDDYhLgJGJCkmQSFIHxkRRNEIizWBFoc2gO+8IozzAMNFIKRJoYtdesRY9c +PyXDlT/QggX7PsUnb38GqztwwRkuFkkAKQRDk6zY2AxvACAYBiTDUKMU/AQHwIAAA4IBCwxxAdVm +GEAhYmMg4MNr2dNxC5eHrJHuSadVcka60Lv+CbwZCMBKhyAgL6CO4/+Y8nOSJsgoM2Z1zWA0r55N +J7gw97FiiQvtGAhEvA7Is2QIPPwEOlMLp7TYTFs/kjf8Ae4WM3xxFi7U9gzDMIAVBEnTnIm5VwxB +gCbIKBlncfwTvRiLToCKRUSU2cjxByNnV+s5UjxJxMVNd/wA/Wt/jOO6fBDEFJysaxjL9m+Fs64b +YUJwQXQLhKJAZz2bSsSErAlhdIJgIc5iB8gwh+uIs9gTtgni4EdDcpXXxnd4Ahcc1JPASgVciORA +x/euR3166ZScrMY1gpVHduLYW5+gy+Q87yGt3e2H0jHI40XDchb972DkZF5uhMN1skNc/kiEwwNC +vlMPkDQNUdgvlwY8crPdYy/J0V0UYAGAywLUWg5arrgSzAE+ZnTUnjoccRJYtd8B8q2/4uNYDIG8 +lElWfS7FZHFD7nPKhGG/cmKiSZTDRZjiD5MBrqgnwB0ff5MApEGPTBz26e1O31lVej6EpOPITBej +4bJ5OFq6AHFiCk425EHhe3+DY/s++GnBqS30cy1mmweSkFcnDXrkY30/ASBICRDgCntJP0/U5RVI +J02cSX6XQBr0FHBD/ouN6ehhtXgMORlSNFdVombGUkRJ9mTqMOzDVUd3If75V/DT5ydoYfxeSIOe +fIXfMYkU8QokUR9P1EF6BZIOp0jumbhsSQo4keR3lX1/wbfvoF4woePQqzgwLZyL/WVLEGZNkXUY +DmD50S8Q+ehT7K/r+NaThmcrt6RGIPc7Z8p9TmIiZk6R3OsVSDtIm0Tdb5Oo+qciXTVu80xizWEZ +cM51+07CMABJADJeBHm/fAjHlq5GcApOVhAPY2b1+6j9xWPY/WUTGOKcrbYZ4uk+scpjnTlxPy/G +YsMmVQ/aJcpecmT9dQ6bVH3cxxdP2nrVO0w5+aJgyaUC6kkhCWBhVQHmP/Mkai//IXwTcklPpnPO +OLAF/r//A/6IEJF44lw4MNog8hfqHaZ8akxa1MmzAVapusH0wA02cvm1v2KsEtVBq0xDT1Qs1TEo +ygjYrwBOHfe8ZIQAUDUtE6HZ07Bn3jXwTjCKUeowjkUdhyF45x1s2X7wXABLZATtl6faB6UTCWqr +VMNYJeqDV937CE26hTLYpOqaQUXqyNhcTAaA0mNDtmNgedOgI/lSA/VkY6RUBJz5M7Cj6n/gEiZN +og7ZdALTT1TD8vhjMPa4wUxwcGchTF2vTZXhGFyh8VjGDV0agEmpt9ikqsN2iXJUh+ac8t5+VfoR +r2B8b3MTMRhGOguP1bYsxSU2BZwUkiDBTfgw/UfXovPm+2CZipNlGExvO4zKg/tRvfsYAtHvFHon +Dn91YkHecOe0sfwzAcDPF6NflVHbnFZsBAGQDMngyiMfRocU+k9MitTExCGUZe0VWA7VLPJH4pfk +njEwGn0VZKiw+okH0HHLfRiagpMlwKC0+yioPz0Dry2B4FluI4RiCZ7v2PHVORajcNJWvlxPmxT6 +7VfVfxIGA5A1rz8Ch1gBk1y/p1NnaI9OIF4lsRC4u7ZRRxuNZzRqplp0n3yPYZgpX2Ofm/j9qf4/ +9rmT/6ZpBulqCdIWTMPOuVdhUJE65TAvNbVi1s6P8f5r2+Dwhs44rD3R2s8R7t+llUX8470+yUKX +ztA9JE/5wiLV4sibvxjtUIJhUPfSbSajNuejkSTdpF7O7jqG2ne3wB2Mfmvler0eWq0WAKBUKqHR +aJCZmQmSJCGVSqFSqZCTkwMejwcejweVSoWMjAwkJSWBxWJBrVZDp9MhJSXlVBlcLhcAwGazoVKp +oFarweGMLgEVCgXUajWEwtHFvlLIRtlVs1H3vbUY0GZPyXDlmtrAf+Zx1H5eD5ojxLe5C184hkPv +bEFWa82490kA5qRkdGuzt7a/9OO+k7w0CwBMjV9g39pnYZeoLNKgZ2WWxSgdm8TKT8TgNlvhKJ6F +/Fw9mpqa4HA4QJIkWlpaYLVaUV5eDolEgnvvvRcymQwDAwNITU3FsmXLYDAY0N/fDz6fj0WLFmHu +3LmwWq0IBoOoqqrC8uXLEYvFYDabUVRUhBUrVkCpVKK/vx96vR7xeBxWqxVKpRLr1q2DQqGA2WyG +SCRCWVkZ0tLSTulTVFyMgtwssGQC7I3wwB42QxVwTQIqye+E0m6FUZYBS8QHDkEjJSUFBoMBHR0d +GB4ehlAoRGVlJT7fexyB557GxBPjNEHiiKFquD6n4peZH+w3H3nrkVNgj/YgQaDx6Z+2tekLNw/J +9eOslQaQ29+Ctr//H4zDLpCnIYVJkoTb7cbbb7+N+vp6OJ1OsNlsbN68GQcPHkRfXx/4fD727t2L +nTt3oq2tDSwWC0ajEZs3b0ZDQwPC4TDC4TDeeust1NXVwWq1gvV1ChCbzUZHRwfef/997N+/H/v2 +7YPH48Gbb76JmpoabNu2DcFgEO9/8AE6m45BmipA7813oCO9eJKuDIBMxyBS//4M7Mf6EKQn5xaQ +BIGeEReaXnkNht6GcYCSAIblOrSmFm0+sepnjYkxaUqnsKt542FU/vx5pleT+c/jmWXGsREWAFBM +AjnVH+PTf30AXygGFkmCIIhTyWQkSYIkSXg8HlitVvj9fpAkiUgkgqGhIfh8PtA0DYZhMDw8DK/X +i3A4DDabDZvNBqfTiUAgADabDZ/PB4vFAp/PB4IgTr0YhsGuXbsQDAbB4/GQnZ2N+vp69Pf3gyRJ +ZGdnw2Kx4MiRI2AAFOZm4p6H1oF8/PdozZmJiTkhNIAU1whWfLUdtkOtiJCjnOxouwgEonFsf30L +sj5/H1x6PCMVY7FxPLOsr0ed+c+qzc/Rta8/dOqzcR49ZfoyvPTq/Y5Ns6/jqD3WJRqvjRzbO6Jo +CK7efjiLK3HVVUtRVFiIiooKrFy5EosXL0ZJSQmUSiWKioqQkZGBzZs3Y9q0acjPz8fMmTOxc+dO +iMVilJaWoqKiAt3d3eju7kZlZSXKyspAURS2b9+OOXPmoLCwEAaDAZs2bUJlZSXKy8uRm5uLw4cP +IysrC7NmzUJ1dTUIgoDb7cbq1atRXV0NPp+PpqYm3HTTTTAajSBB48qrr8AuLw1b3xB0HsukIEEU +9kNvNaGdSIJiWimy07RIz8hCa6cNwlc2INtsnGSlnToDvb9o4XMH3/nfj14pW4bhhs9PfT7ubGrt +Gw/hnlv+BKdY/lpNbuWVOtfwYlnAPS5zxNDXhGPvvw/7nEosmVOIwsLCcT3Y1tYGiqKQlJQEgUAA +v9+PeDwOjUaDpKQkhMNhMAwDg8EAtVoNr9cLt9sNiUQCrVYLNpsNj8cDFosFrVYLgUCA5ORk5OTk +AAAMBgNUKhUAwOfzoaysDH6/H3w+H16vFxKJBHl5eZDJZPD7/YjFYoiHgyBIJ2S/eRINf3kRJQ37 +waYT4zhZZcCFonc3Yks4jCufeAjuMAXutk+Q33Ni0lkyD1+C2tzKr4zJOf9Xcct61L3+4DgMJh/4 +JRik2/odralFv9U7h4oWN+3RkGO2sFlgUFK/G7VPPQPO7x83zZ+Z2zJ2XCUSCWzcuBH9/f1Ys2YN +9Ho93nvvPfzhD39AaWkpKisr8e6772L9+vXg8Xh49tlnYTQa8fzzz8PlcuHRRx+FQCDAhg0bYDQa +cfXVVyMzM/NU/fPnz8fDDz8Mv9+Pxx57DOXl5di2bRs2bdqEG2+8EbNnz0ZjYyPWrl2LGTNm4O67 +74bZbAafYuOG65fh1ThJHV4fnVHVVSMZeysFAyAp7EPpR//AVq8PRCyKypqdYE/YF6VJEvU55faW +1KLfZVj6rB6RBGckZbe+AOxmiGXX/uqRDWklsf0As2/Maz/AfEoJmIeqVvUv/+mGFQzDsBmGoU6+ +nnnmGSo5OZnq7u6mGIah9u/fT0mlUurDDz+kGIahrFYrlZeXRz3wwAMUwzAUTdPUjTfeSFVVVVF+ +v59iGIZ6+eWXKYVCQTU2NlJjy2YYhlq3bh01b948KhgMUgzDUP/6178ojUZDtba2UgzDUI2NjZRK +paJef/11imEYaseOHdRbb71FMQxD/bXWxFt8/RM/eaJ4keMLNsVM1bZdLIrZdZrP/pZaFF+x6peP +4xUTWXbr1Ad+T7v0nXHrC7CJVeI5HYdevrpuyw2pjqFJJ6n9PBH2lCzuOWyY87PDbz3yaco9b2Jo +481n1nMXSXJ/+i905c8mFxz64Ia5rV+un992QMOLhr+V2CABDCXpsLXimg+O5M3+sdzndB979f4p +nz1t6EktXY1kuzU6JE85FmdRs/SOwTRBNDTuGW48ijTHYBLBMIvZ+3ttXWmFLZnFC2ln3daLjd1p +xVm3FdlZs5jjN93bzJhMxgTJmqNzDknH5udOFAKAWyjD59OX1R7Lmnm33O8aDnN4sB779OxA9Rza +CkHVaqS4R9x96szGOIs9L9UxqJp4vJJKxJBmHxDzoqElLDpBWaWaE5lFC8LmWzcCO/9ysTGcUlx1 +W5Eiz8dV9dvbq0uXDALEiiyLkUtOEWITAAI8IfaULu2qza28U+W1NlplGrS8ctdpy/9GksR59BOw +59+M6f0nhpvSSltpkjVP7zAlTexVNp2A3jHEVfgcc2mSLLRJ1e3m3y+3yGPlCH35zsXGcJLInvwM +1qdX4cGr7ivIG+64a1pfwzS11zoppCEABLkC7C1ePHAo/7K7yo11+49mzULH33/yjeV/K/PkrN+G +wJU/xaMf/bHvwznXtsdZ7LkpzqGkiRZLgIHaayPTbP35VDy2TLC7PRHgCjoMhqrI8O0bgZ0bLjaW +wBYGZZQYCYIt/iS99ObZHYf/ennj5wuyrL2T8uEIAAGuEHuLF/cfLJj700NvPfrZ/St/jq6Xbv/W +as6IznPVbsWJ2/+CPf+4x/jsktuawxS/XOseUQmnmOBFkSAyrT1JSp/jcioeLXeJlY4IGJO+aGHc +8cQnwLtPXngwexhkRdIA9whX7bEtmmE8+vzi5up75nQe0simuGyHxOgcuqd0aefhvMvuPvTWo59l +3fMajBtvPaPqzpgjddVuwZuPfITFRz/rPVxwWY2PLy6W+xyp0tDkvAAWTUPrsbCyLMZsud91tSjo +LQnzBL6EfWSkWGGImTbsAN58+vyD2UWj2C4Cuq38FNfQ/FmdNU/Oa/vysYUte0syrX3siSmkwKiF +Dst1+Hz6spoaQ9UdK49uP3D92udgOsPb006WcVYieHI3Zjfth1muyygeaPr9vNYD1xWa2jisMRHK +xMKdoiR06PL97fr8Q/3K9E3DipQ9nYYyk8huS/h+t/w73fz4jTo+UY2gXE0a2mtSdI6hRem2/uvy +htvn5w21SxR+JwiGmVLXBMlCe0p+/EDh/I+b00oeTXGaeg7lzoHvmavOqv7v1poBBqVPbIBHLBMX +DLbdXdFV82B5d51KGvKe9lJCAPDxRBhQpsV7tNn9A8q0AxaZ9gubRFU/qMsZ9P1mSRh7gsDS75AE +sScILOZD/FQ1L2W4O0Xttc3QuC1LU+0DC7PNxqw0ez9b8vWIOt1ljx6+GPXZ5fba3MoX2vUFG6QB +l+fErKuAu7PPWp3/yERS738bg5pMsrL1q4XFg82/ruysmZdtMZJTXft5srLRKzVZcAukGElKjg/L +U8wWmbbNLlY0eISyZh9PZAzwRGafQOJxCZNCAVVGFAotDSmPgSNAwGsn+Y4BTpLPwZcEvVJRyK8V +h31Z0qCnSO5zTNe4LQUpzqHkZNcIRxZwgfP1CDqdPgmSBaMmi64xVB1sTit5qrZg9l69dSAx8MKZ +D/dzCioA4M0GLNzyMYY1enWWuef20r6GO2f0HkvVus1TDrOJANMEgRCHB69AArcwifYIpEEvX+IJ +cgWuMMXzRNlcX5xkRRiSjBM0zWHRCYqKR4W8WFgmDAeSxCGfTBZ0C2QBNykJesCLhcH6ut5vqpsh +CFikGhzPLBtqyJj2sjE555XUoR7L3utuBn1T/n8EyTmbzFS/3ApbWgFRVrezNMvSc1dJf+MPigZb +FCqvDSTDnNVF3zRGiYsEyUKCZIEmSDAECYKhRy/4pkcv+2bRiVOE8Jle9E0TBGxiJVpTi1xN6aUf +G7XZG45PW9KgHu6irX9chXMh5zznMPW+t+GRyjmG/tZZmda+W/OG2lfmDbdrtC4zuF+zQmebQDCR +/zzb7xIYPWtrlmnRqcuztqcU7OjVZL7ao8utEftd0b4zuL7zooJ6UnQPvAefWM7JNrUX6R2D12Za +e6/ONnfnp9kHKWnADc7Xy5nzce//xLl7UJka7dFkd/Vqsj4ZVKa+b9TnN4m9ztjQn394zlce5xXU +k0Ktr0E0PZ/I3vexWuccuizZOXxlqmPwshSHKUPrtvBkARd40TDYY67YZCb8/U2Kj21AgiAR5vDg +FspgTtJGTAp9v0mRenA4KXnniDzlq+7KZWauuZ+J/GL2eW3zBT0xI3hiL4IKBZnTflSr9lhKFV57 +ldLnmKX02nLlfodaFvCIxCEvmx8NgRuLgE3HQdKj8+hJOfmDNHGSPfqDNBQfPr447hHKAg6RwuoQ +K7rsEuVRu1h1xCZVN3TnTTML7M5E4LeXX7B2XrxD3w0xoJQN/tNf8XRD3RpZwJ0hCvtyROFAjiAS +TOdHQzoqHpWxEzERi06c+ukkmiAjoz+dxHWHKP5IkCvoD/CE3T6euNsjlPYO6XLMof+dF8aJOFB2 +cU5f/z+rXz1t3lG/LgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMi0wOC0xMFQxMzoyNjoxNSswMjow +MJsC4wUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTItMDgtMTBUMTM6MjY6MDkrMDI6MDDhVTFTAAAA +GXRFWHRTb2Z0d2FyZQBNaWNyb3NvZnQgT2ZmaWNlf+01cQAAAABJRU5ErkJggg== + + pos.order From f3d824424f9eb6d169b7b9598d95ad020c370003 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 10 Aug 2012 15:48:15 +0200 Subject: [PATCH 35/36] [FIX] problem with tipsy & quick create in m2o bzr revid: nicolas.vanhoren@openerp.com-20120810134815-qt91cbf1rndvxn0a --- addons/web/static/src/js/view_form.js | 37 +++++++++++++++++---------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 4d06cb2d767..508b1046305 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2784,8 +2784,8 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc } } }); - var tip_def = $.Deferred(); - var untip_def = $.Deferred(); + self.tip_def = $.Deferred(); + self.untip_def = $.Deferred(); var tip_delay = 200; var tip_duration = 15000; var anyoneLoosesFocus = function() { @@ -2807,24 +2807,25 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc } self.floating = false; } - if (used && self.get("value") === false) { - tip_def.reject(); - untip_def.reject(); - tip_def = $.Deferred(); - tip_def.then(function() { + if (used && self.get("value") === false && ! self.no_tipsy) { + self.tip_def.reject(); + self.untip_def.reject(); + self.tip_def = $.Deferred(); + self.tip_def.then(function() { self.$input.tipsy("show"); }); setTimeout(function() { - tip_def.resolve(); - untip_def.reject(); - untip_def = $.Deferred(); - untip_def.then(function() { + self.tip_def.resolve(); + self.untip_def.reject(); + self.untip_def = $.Deferred(); + self.untip_def.then(function() { self.$input.tipsy("hide"); }); - setTimeout(function() {untip_def.resolve();}, tip_duration); + setTimeout(function() {self.untip_def.resolve();}, tip_duration); }, tip_delay); } else { - tip_def.reject(); + self.no_tipsy = false; + self.tip_def.reject(); } }; this.$input.focusout(anyoneLoosesFocus); @@ -2953,7 +2954,15 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc }, focus: function () { this.$input.focus(); - } + }, + _quick_create: function() { + this.no_tipsy = true; + return instance.web.form.CompletionFieldMixin._quick_create.apply(this, arguments); + }, + _search_create_popup: function() { + this.no_tipsy = true; + return instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments); + }, }); /* From 01dc1b218b2165973914938ee37bcf1b855addf5 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Fri, 10 Aug 2012 15:55:48 +0200 Subject: [PATCH 36/36] [IMP] help menutips for PoS bzr revid: fp@tinyerp.com-20120810135548-w2cgv11ynu6l8n5l --- addons/point_of_sale/point_of_sale_view.xml | 26 ++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index 15a1aa7e6bb..bd11f5c070c 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -289,6 +289,15 @@ GXRFWHRTb2Z0d2FyZQBNaWNyb3NvZnQgT2ZmaWNlf+01cQAAAABJRU5ErkJggg== tree,form [] + +

+ Click to create a new order. +

+ Use this menu to browse your preceeding orders. To record new + orders, you should better use the menu Your Session for + the touchscreen interface. +

+
Sales @@ -341,6 +350,7 @@ GXRFWHRTb2Z0d2FyZQBNaWNyb3NvZnQgT2ZmaWNlf+01cQAAAABJRU5ErkJggg==
form tree,form,kanban + @@ -866,6 +876,18 @@ GXRFWHRTb2Z0d2FyZQBNaWNyb3NvZnQgT2ZmaWNlf+01cQAAAABJRU5ErkJggg== form tree,form + +

+ Click to define a new category. +

+ Categories are used to browse your products through the + touchscreen interface. +

+ If you put a photo on the category, the layout of the + touchscreen interface will automatically. We suggest not to put + a photo on categories for small (1024x768) screens. +

+
@@ -885,7 +907,9 @@ GXRFWHRTb2Z0d2FyZQBNaWNyb3NvZnQgT2ZmaWNlf+01cQAAAABJRU5ErkJggg==
Click to add a payment method.

Payment methods are defined by accounting journals having the - field Payment Method checked. + field PoS Payment Method checked. In order to be useable + from the touchscreen interface, you must set the payment method + on the Point of Sale configuration.