From fc36345ca0730cf8cbd197d9177bc884764188f2 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 23 Jul 2012 17:03:06 +0200 Subject: [PATCH 01/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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/40] [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 7bd54c9c0b0d6c2333e270b2056fbbc87c608e6d Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 10 Aug 2012 11:45:22 +0200 Subject: [PATCH 28/40] [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 29/40] [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 29bd7ba2db8c6763fa6f9a0190617c9d323b4c12 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Fri, 10 Aug 2012 14:26:21 +0200 Subject: [PATCH 30/40] [IMP] account: invoice form view usability improvement (display 'Refund' in place of 'Invoice' when needed) bzr revid: qdp-launchpad@openerp.com-20120810122621-dbhx84hse3fylajw --- addons/account/account_invoice_view.xml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 501a50b96c2..d8ba5f5159d 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -163,8 +163,10 @@

-

@@ -309,9 +311,11 @@

-

From 766ee9487268b18dc718c3b4239538c9f64b7d3b Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Fri, 10 Aug 2012 14:35:25 +0200 Subject: [PATCH 31/40] [REF] account_voucher: remove useless and unused code (classes account.statement.from.invoice and account.voucher.unreconcile) bzr revid: qdp-launchpad@openerp.com-20120810123525-lhijvv2khh0xqw38 --- addons/account_voucher/__openerp__.py | 1 - addons/account_voucher/wizard/__init__.py | 3 +- .../wizard/account_statement_from_invoice.py | 73 +------------------ .../account_statement_from_invoice_view.xml | 28 ------- .../wizard/account_voucher_unreconcile.py | 62 ---------------- .../account_voucher_unreconcile_view.xml | 31 -------- 6 files changed, 2 insertions(+), 196 deletions(-) delete mode 100644 addons/account_voucher/wizard/account_voucher_unreconcile.py delete mode 100644 addons/account_voucher/wizard/account_voucher_unreconcile_view.xml diff --git a/addons/account_voucher/__openerp__.py b/addons/account_voucher/__openerp__.py index a7be61db2c9..16ea771d123 100644 --- a/addons/account_voucher/__openerp__.py +++ b/addons/account_voucher/__openerp__.py @@ -49,7 +49,6 @@ eInvoicing & Payments module manage all Voucher Entries such as "Reconciliation "account_voucher_sequence.xml", "account_voucher_workflow.xml", "account_voucher_report.xml", - "wizard/account_voucher_unreconcile_view.xml", "wizard/account_statement_from_invoice_view.xml", "account_voucher_view.xml", "voucher_payment_receipt_view.xml", diff --git a/addons/account_voucher/wizard/__init__.py b/addons/account_voucher/wizard/__init__.py index bfe32ba4b83..47b87b27162 100644 --- a/addons/account_voucher/wizard/__init__.py +++ b/addons/account_voucher/wizard/__init__.py @@ -19,7 +19,6 @@ # ############################################################################## -import account_voucher_unreconcile import account_statement_from_invoice -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account_voucher/wizard/account_statement_from_invoice.py b/addons/account_voucher/wizard/account_statement_from_invoice.py index 1b921e0ccf4..e72fee63af0 100644 --- a/addons/account_voucher/wizard/account_statement_from_invoice.py +++ b/addons/account_voucher/wizard/account_statement_from_invoice.py @@ -112,81 +112,10 @@ class account_statement_from_invoice_lines(osv.osv_memory): 'statement_id': statement_id, 'ref': line.ref, 'voucher_id': voucher_id, - 'date': time.strftime('%Y-%m-%d'), #time.strftime('%Y-%m-%d'), #line.date_maturity or, + 'date': time.strftime('%Y-%m-%d'), }, context=context) return {'type': 'ir.actions.act_window_close'} account_statement_from_invoice_lines() -class account_statement_from_invoice(osv.osv_memory): - """ - Generate Entries by Statement from Invoices - """ - _name = "account.statement.from.invoice" - _description = "Entries by Statement from Invoices" - _columns = { - 'date': fields.date('Date payment',required=True), - 'journal_ids': fields.many2many('account.journal', 'account_journal_relation', 'account_id', 'journal_id', 'Journal'), - 'line_ids': fields.many2many('account.move.line', 'account_move_line_relation', 'move_id', 'line_id', 'Invoices'), - } - _defaults = { - 'date': lambda *a: time.strftime('%Y-%m-%d'), - } - - def search_invoices(self, cr, uid, ids, context=None): - if context is None: - context = {} - line_obj = self.pool.get('account.move.line') - statement_obj = self.pool.get('account.bank.statement') - journal_obj = self.pool.get('account.journal') - mod_obj = self.pool.get('ir.model.data') - statement_id = 'statement_id' in context and context['statement_id'] - - data = self.read(cr, uid, ids, context=context)[0] - statement = statement_obj.browse(cr, uid, statement_id, context=context) - args_move_line = [] - repeated_move_line_ids = [] - # Creating a group that is unique for importing move lines(move lines, once imported into statement lines, should not appear again) - for st_line in statement.line_ids: - args_move_line = [] - args_move_line.append(('name', '=', st_line.name)) - args_move_line.append(('ref', '=', st_line.ref)) - if st_line.partner_id: - args_move_line.append(('partner_id', '=', st_line.partner_id.id)) - args_move_line.append(('account_id', '=', st_line.account_id.id)) - - move_line_id = line_obj.search(cr, uid, args_move_line, context=context) - if move_line_id: - repeated_move_line_ids += move_line_id - - journal_ids = data['journal_ids'] - if journal_ids == []: - journal_ids = journal_obj.search(cr, uid, [('type', 'in', ('sale', 'cash', 'purchase'))], context=context) - - args = [ - ('reconcile_id', '=', False), - ('journal_id', 'in', journal_ids), - ('account_id.reconcile', '=', True)] - - if repeated_move_line_ids: - args.append(('id', 'not in', repeated_move_line_ids)) - - line_ids = line_obj.search(cr, uid, args, - context=context) - - model_data_ids = mod_obj.search(cr, uid, [('model', '=', 'ir.ui.view'), ('name', '=', 'view_account_statement_from_invoice_lines')], context=context) - resource_id = mod_obj.read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id'] - return { - 'domain': "[('id','in', ["+','.join([str(x) for x in line_ids])+"])]", - 'name': _('Import Entries'), - 'context': context, - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'account.statement.from.invoice.lines', - 'views': [(resource_id,'form')], - 'type': 'ir.actions.act_window', - 'target': 'new', - } - -account_statement_from_invoice() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account_voucher/wizard/account_statement_from_invoice_view.xml b/addons/account_voucher/wizard/account_statement_from_invoice_view.xml index 33014f394a7..7af5155a6d3 100644 --- a/addons/account_voucher/wizard/account_statement_from_invoice_view.xml +++ b/addons/account_voucher/wizard/account_statement_from_invoice_view.xml @@ -1,34 +1,6 @@ - - account.statement.from.invoice.form - account.statement.from.invoice - form - -
- - - - -
-
-
-
-
- - - Import Invoices in Statement - account.statement.from.invoice - form - tree,form - - new - - account.statement.from.invoice.lines.form account.statement.from.invoice.lines diff --git a/addons/account_voucher/wizard/account_voucher_unreconcile.py b/addons/account_voucher/wizard/account_voucher_unreconcile.py deleted file mode 100644 index 42a9e1c5ec2..00000000000 --- a/addons/account_voucher/wizard/account_voucher_unreconcile.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from osv import osv -from osv import fields - -class account_voucher_unreconcile(osv.osv_memory): - _name = "account.voucher.unreconcile" - _description = "Account voucher unreconcile" - - _columns = { - 'remove':fields.boolean('Want to remove accounting entries too ?', required=False), - } - - _defaults = { - 'remove': True, - } - - def trans_unrec(self, cr, uid, ids, context=None): -# res = self.browse(cr, uid, ids[0]) - if context is None: - context = {} - voucher_pool = self.pool.get('account.voucher') - reconcile_pool = self.pool.get('account.move.reconcile') - if context.get('active_id'): - voucher = voucher_pool.browse(cr, uid, context.get('active_id'), context=context) - recs = [] - for line in voucher.move_ids: - if line.reconcile_id: - recs += [line.reconcile_id.id] - if line.reconcile_partial_id: - recs += [line.reconcile_partial_id.id] - #for rec in recs: - reconcile_pool.unlink(cr, uid, recs) -# if res.remove: - voucher_pool.cancel_voucher(cr, uid, [context.get('active_id')], context) -# wf_service = netsvc.LocalService("workflow") -# wf_service.trg_validate(uid, 'account.voucher', context.get('active_id'), 'cancel_voucher', cr) - - return {'type': 'ir.actions.act_window_close'} - -account_voucher_unreconcile() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/account_voucher/wizard/account_voucher_unreconcile_view.xml b/addons/account_voucher/wizard/account_voucher_unreconcile_view.xml deleted file mode 100644 index 04131b643eb..00000000000 --- a/addons/account_voucher/wizard/account_voucher_unreconcile_view.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - Account voucher unreconcile - account.voucher.unreconcile - form - -
- -
-
- - Unreconcile entries - account.voucher.unreconcile - form - form - - new - - -
-
From 6391df3989e088380ae5f580af11f60c16bcf535 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Fri, 10 Aug 2012 14:40:56 +0200 Subject: [PATCH 32/40] [FIX/IMP] account_voucher: changed/fixed/improved the way to handle refunds payments through vouchers. Now, the refund have to be encoded as a negative of the right type (if the amount is -200 in a customer payment, it means your paying a customer refund of 200) bzr revid: qdp-launchpad@openerp.com-20120810124056-9rpa4ugnxhdtqhhb --- addons/account_voucher/account_voucher.py | 20 ++++++------- addons/account_voucher/invoice.py | 2 +- .../voucher_payment_receipt_view.xml | 8 ++--- .../wizard/account_statement_from_invoice.py | 30 +++++++++++-------- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index ce0c64337b9..1287c7b3c18 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -46,11 +46,7 @@ class account_voucher(osv.osv): def _check_paid(self, cr, uid, ids, name, args, context=None): res = {} for voucher in self.browse(cr, uid, ids, context=context): - paid = False - for line in voucher.move_ids: - if (line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id: - paid = True - res[voucher.id] = paid + res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids]) return res def _get_type(self, cr, uid, context=None): @@ -186,15 +182,16 @@ class account_voucher(osv.osv): res['arch'] = etree.tostring(doc) return res - def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount): + def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type): debit = credit = 0.0 + sign = type == 'payment' and -1 or 1 for l in line_dr_ids: debit += l['amount'] for l in line_cr_ids: credit += l['amount'] - return abs(amount - abs(credit - debit)) + return amount - sign * (credit - debit) - def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, context=None): + def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None): context = context or {} if not line_dr_ids and not line_cr_ids: return {'value':{}} @@ -215,7 +212,7 @@ class account_voucher(osv.osv): if voucher_line.get('currency_id', company_currency) != company_currency: is_multi_currency = True break - return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount), 'is_multi_currency': is_multi_currency}} + return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}} def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None): if not ids: return {} @@ -223,12 +220,13 @@ class account_voucher(osv.osv): res = {} debit = credit = 0.0 for voucher in self.browse(cr, uid, ids, context=context): + sign = voucher.type == 'payment' and -1 or 1 for l in voucher.line_dr_ids: debit += l.amount for l in voucher.line_cr_ids: credit += l.amount currency = voucher.currency_id or voucher.company_id.currency_id - res[voucher.id] = currency_obj.round(cr, uid, currency, abs(voucher.amount - abs(credit - debit))) + res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit)) return res def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None): @@ -716,7 +714,7 @@ class account_voucher(osv.osv): default['value']['pre_line'] = 1 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0: default['value']['pre_line'] = 1 - default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price) + default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype) return default def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None): diff --git a/addons/account_voucher/invoice.py b/addons/account_voucher/invoice.py index 006a622e25f..14b725cbeca 100644 --- a/addons/account_voucher/invoice.py +++ b/addons/account_voucher/invoice.py @@ -40,7 +40,7 @@ class invoice(osv.osv): 'domain': '[]', 'context': { 'default_partner_id': inv.partner_id.id, - 'default_amount': inv.residual, + 'default_amount': inv.type in ('out_refund', 'in_refund') and -inv.residual or inv.residual, 'default_name':inv.name, 'close_after_process': True, 'invoice_type':inv.type, diff --git a/addons/account_voucher/voucher_payment_receipt_view.xml b/addons/account_voucher/voucher_payment_receipt_view.xml index 99ea3a2bbea..ef983145901 100644 --- a/addons/account_voucher/voucher_payment_receipt_view.xml +++ b/addons/account_voucher/voucher_payment_receipt_view.xml @@ -74,7 +74,7 @@ - + - + - + - + Date: Fri, 10 Aug 2012 15:45:17 +0200 Subject: [PATCH 33/40] [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 34/40] [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 35/40] [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.

From a9c0de532574aad5c1e9fb8db2f37891705858d6 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 10 Aug 2012 16:22:53 +0200 Subject: [PATCH 36/40] [FIX] small bug in some m2m bzr revid: nicolas.vanhoren@openerp.com-20120810142253-8ly58vsbqum6o7ng --- addons/web/static/src/js/view_form.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 508b1046305..0b1ed8fb67c 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -4327,11 +4327,12 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend }); }, on_click_element: function(ids) { + var self = this; this.selected_ids = ids || []; if(this.selected_ids.length > 0) { - this.$element.find(".oe_selectcreatepopup-search-select").removeAttr('disabled'); + self.$buttonpane.find(".oe_selectcreatepopup-search-select").removeAttr('disabled'); } else { - this.$element.find(".oe_selectcreatepopup-search-select").attr('disabled', "disabled"); + self.$buttonpane.find(".oe_selectcreatepopup-search-select").attr('disabled', "disabled"); } }, new_object: function() { From 600625f0ae4e04cbc8065154d59b9d9c3164091e Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Fri, 10 Aug 2012 16:37:07 +0200 Subject: [PATCH 37/40] [FIX] account_voucher: fixed yaml tests in order to reflect the new behavior of computed writeoff amount (does not use abs() anymore) bzr revid: qdp-launchpad@openerp.com-20120810143707-8vma2dld7mkolbh1 --- addons/account_voucher/test/case1_usd_usd.yml | 8 ++++---- addons/account_voucher/test/case_eur_usd.yml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/addons/account_voucher/test/case1_usd_usd.yml b/addons/account_voucher/test/case1_usd_usd.yml index d8ac932a055..a30c85ffb71 100644 --- a/addons/account_voucher/test/case1_usd_usd.yml +++ b/addons/account_voucher/test/case1_usd_usd.yml @@ -173,12 +173,12 @@ self.pool.get('account.voucher.line').write(cr, uid, [line_id], {'amount': amount}) assert (voucher_id.state=='draft'), "Voucher is not in draft state" - - I check that writeoff amount computed is 10.0 + I check that writeoff amount computed is -10.0 - !python {model: account.voucher}: | voucher = self.search(cr, uid, [('name', '=', 'First payment: Case 1 USD/USD'), ('partner_id', '=', ref('base.res_partner_seagate'))]) voucher_id = self.browse(cr, uid, voucher[0]) - assert (voucher_id.writeoff_amount == 10.0), "Writeoff amount is not 10.0" + assert (voucher_id.writeoff_amount == -10.0), "Writeoff amount is not -10.0" - I confirm the voucher - @@ -266,12 +266,12 @@ self.pool.get('account.voucher.line').write(cr, uid, [line_id], {'amount': amount}) assert (voucher_id.state=='draft'), "Voucher is not in draft state" - - I check that writeoff amount computed is 5.0 + I check that writeoff amount computed is -5.0 - !python {model: account.voucher}: | voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 1'), ('partner_id', '=', ref('base.res_partner_seagate'))]) voucher_id = self.browse(cr, uid, voucher[0]) - assert (voucher_id.writeoff_amount == 5.0), "Writeoff amount is not 5.0" + assert (voucher_id.writeoff_amount == -5.0), "Writeoff amount is not -5.0" - I confirm the voucher - diff --git a/addons/account_voucher/test/case_eur_usd.yml b/addons/account_voucher/test/case_eur_usd.yml index 72f0d22aead..53db4ffaf80 100644 --- a/addons/account_voucher/test/case_eur_usd.yml +++ b/addons/account_voucher/test/case_eur_usd.yml @@ -107,11 +107,11 @@ self.pool.get('account.voucher.line').write(cr, uid, [line_id], {'amount': amount}) assert (voucher.state=='draft'), "Voucher is not in draft state" - - I check that writeoff amount computed is 50.0 + I check that writeoff amount computed is -50.0 - !python {model: account.voucher}: | voucher = self.browse(cr, uid, ref('account_voucher_eur_usd_case')) - assert (voucher.writeoff_amount == 50.0), "Writeoff amount is not 50.0" + assert (voucher.writeoff_amount == -50.0), "Writeoff amount is not -50.0" - I confirm the voucher - @@ -167,4 +167,4 @@ move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)]) move_line = move_line_obj.browse(cr, uid, move_lines[0]) assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for Invoice" - \ No newline at end of file + From c710e12517e2ff3438823294e33eacf9565bd36b Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 10 Aug 2012 16:43:33 +0200 Subject: [PATCH 38/40] [IMP] auth_openid: forward port of patches made on 6.1 branch revid:chs@openerp.com-20120802102525-2uyr5za46swxs0n7 [FIX] auth_openid: Due to multiprocessus we must use a FileStore instead of a MemStore to store openid associations. [FIX] auth_openid: GoogleApps: avoid crash when handle not found in store. [IMP] auth_openid: flake8. revid:chs@openerp.com-20120403093926-4222u3cs3qc10xew [FIX] auth_openid: use correct api for login users revid:chs@openerp.com-20120403093627-easttnf2qoemeoli [FIX] auth_openid: allow login from direct http request bzr revid: chs@openerp.com-20120810144333-o8zejl4pmdldab6j --- addons/auth_openid/controllers/main.py | 63 ++++++++++++++------------ 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/addons/auth_openid/controllers/main.py b/addons/auth_openid/controllers/main.py index 0ee7f98a020..dc81730daf2 100644 --- a/addons/auth_openid/controllers/main.py +++ b/addons/auth_openid/controllers/main.py @@ -21,7 +21,7 @@ import logging import os -import sys +import tempfile import urllib import werkzeug.urls @@ -31,53 +31,51 @@ from openerp.modules.registry import RegistryManager try: import openerp.addons.web.common.http as openerpweb except ImportError: - import web.common.http as openerpweb + import web.common.http as openerpweb # noqa from openid import oidutil -from openid.store import memstore -#from openid.store import filestore +from openid.store import filestore from openid.consumer import consumer from openid.cryptutil import randomString from openid.extensions import ax, sreg from .. import utils - - _logger = logging.getLogger(__name__) oidutil.log = _logger.debug +_storedir = os.path.join(tempfile.gettempdir(), 'openerp-auth_openid-store') class GoogleAppsAwareConsumer(consumer.GenericConsumer): def complete(self, message, endpoint, return_to): if message.getOpenIDNamespace() == consumer.OPENID2_NS: - server_url = message.getArg(consumer.OPENID2_NS, 'op_endpoint', consumer.no_default) + server_url = message.getArg(consumer.OPENID2_NS, 'op_endpoint', '') if server_url.startswith('https://www.google.com/a/'): - # update fields - for attr in ['claimed_id', 'identity']: - value = message.getArg(consumer.OPENID2_NS, attr) - value = 'https://www.google.com/accounts/o8/user-xrds?uri=%s' % urllib.quote_plus(value) - message.setArg(consumer.OPENID2_NS, attr, value) - - # now, resign the message assoc_handle = message.getArg(consumer.OPENID_NS, 'assoc_handle') assoc = self.store.getAssociation(server_url, assoc_handle) - message.delArg(consumer.OPENID2_NS, 'sig') - message.delArg(consumer.OPENID2_NS, 'signed') - message = assoc.signMessage(message) + if assoc: + # update fields + for attr in ['claimed_id', 'identity']: + value = message.getArg(consumer.OPENID2_NS, attr, '') + value = 'https://www.google.com/accounts/o8/user-xrds?uri=%s' % urllib.quote_plus(value) + message.setArg(consumer.OPENID2_NS, attr, value) - return super(GoogleAppsAwareConsumer, self).complete(message, endpoint, return_to) + # now, resign the message + message.delArg(consumer.OPENID2_NS, 'sig') + message.delArg(consumer.OPENID2_NS, 'signed') + message = assoc.signMessage(message) + + return super(GoogleAppsAwareConsumer, self).complete(message, endpoint, return_to) class OpenIDController(openerpweb.Controller): _cp_path = '/auth_openid/login' - _store = memstore.MemoryStore() # TODO use a filestore + _store = filestore.FileOpenIDStore(_storedir) _REQUIRED_ATTRIBUTES = ['email'] _OPTIONAL_ATTRIBUTES = 'nickname fullname postcode country language timezone'.split() - def _add_extensions(self, request): """Add extensions to the request""" @@ -118,8 +116,20 @@ class OpenIDController(openerpweb.Controller): def _get_realm(self, req): return req.httprequest.host_url + @openerpweb.httprequest + def verify_direct(self, req, db, url): + result = self._verify(req, db, url) + if 'error' in result: + return werkzeug.exceptions.BadRequest(result['error']) + if result['action'] == 'redirect': + return werkzeug.utils.redirect(result['value']) + return result['value'] + @openerpweb.jsonrequest def verify(self, req, db, url): + return self._verify(req, db, url) + + def _verify(self, req, db, url): redirect_to = werkzeug.urls.Href(req.httprequest.host_url + 'auth_openid/login/process')(session_id=req.session_id) realm = self._get_realm(req) @@ -145,7 +155,6 @@ class OpenIDController(openerpweb.Controller): form_html = request.htmlMarkup(realm, redirect_to) return {'action': 'post', 'value': form_html, 'session_id': req.session_id} - @openerpweb.httprequest def process(self, req, **kw): session = getattr(req.session, 'openid_session', None) @@ -185,10 +194,8 @@ class OpenIDController(openerpweb.Controller): domain += ['|', ('openid_email', '=', False)] domain += [('openid_email', '=', openid_email)] - domain += [ - ('openid_url', '=', openid_url), - ('active', '=', True), - ] + domain += [('openid_url', '=', openid_url), ('active', '=', True)] + ids = Users.search(cr, 1, domain) assert len(ids) < 2 if ids: @@ -199,12 +206,11 @@ class OpenIDController(openerpweb.Controller): # TODO fill empty fields with the ones from sreg/ax cr.commit() - u = req.session.login(dbname, login, key) + req.session.authenticate(dbname, login, key, {}) if not user_id: session['message'] = 'This OpenID identifier is not associated to any active users' - elif info.status == consumer.SETUP_NEEDED: session['message'] = info.setup_url elif info.status == consumer.FAILURE and display_identifier: @@ -217,9 +223,8 @@ class OpenIDController(openerpweb.Controller): # information in a log. session['message'] = 'Verification failed.' - fragment = '#loginerror' if not user_id else '' - return werkzeug.utils.redirect('/'+fragment) + return werkzeug.utils.redirect('/' + fragment) @openerpweb.jsonrequest def status(self, req): From c2caa24bd6bc5a386e2a5463809860699c7736ca Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 10 Aug 2012 16:50:48 +0200 Subject: [PATCH 39/40] [FIX] small "or" in some x2x popup bzr revid: nicolas.vanhoren@openerp.com-20120810145048-ctnvl0w5r5h439f7 --- addons/web/static/src/js/view_form.js | 10 ++++------ addons/web/static/src/xml/base.xml | 24 +++++++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 0b1ed8fb67c..baf8c151b83 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -4165,7 +4165,10 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ this.view_form.appendTo(this.$element.find(".oe_popup_form")); this.view_form.on_loaded.add_last(function() { var multi_select = self.row_id === null && ! self.options.disable_multiple_selection; - self.$buttonpane.html(QWeb.render("AbstractFormPopup.buttons", {multi_select: multi_select})); + self.$buttonpane.html(QWeb.render("AbstractFormPopup.buttons", { + multi_select: multi_select, + readonly: self.row_id !== null && self.options.readonly, + })); var $snbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save-new"); $snbutton.click(function() { $.when(self.view_form.do_save()).then(function() { @@ -4186,11 +4189,6 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ $cbutton.click(function() { self.check_exit(); }); - if (self.row_id !== null && self.options.readonly) { - $snbutton.hide(); - $sbutton.hide(); - $cbutton.text(_t("Close")); - } self.view_form.do_show(); }); }, diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 58f77840e69..c3ba8f79c8e 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1230,14 +1230,24 @@ Cancel - - + + + + + + + + + or - - - - - or Discard + + + Discard + + + Close + + From 1f32ebdc7c8d32917b75554808f91c74274ef2a5 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 10 Aug 2012 17:12:04 +0200 Subject: [PATCH 40/40] [FIX] pos: Fix runbot breakage: use new XML id for unreference_product + move to pos_data file bzr revid: odo@openerp.com-20120810151204-t6c0wmiusf9un7gq --- addons/point_of_sale/point_of_sale_data.xml | 175 ++++++++++++++++++++ addons/point_of_sale/point_of_sale_view.xml | 175 -------------------- 2 files changed, 175 insertions(+), 175 deletions(-) diff --git a/addons/point_of_sale/point_of_sale_data.xml b/addons/point_of_sale/point_of_sale_data.xml index 92c04944792..dda0b78e952 100644 --- a/addons/point_of_sale/point_of_sale_data.xml +++ b/addons/point_of_sale/point_of_sale_data.xml @@ -32,6 +32,181 @@ If you install the PoS proxy, you will be able to interface OpenERP with retail materials; barcode scanners, printers, cash registers, weighing machine, credit card payments. + + + 1.00 + Unreferenced Products + + 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 + diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index bd11f5c070c..14eeda212cc 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -11,181 +11,6 @@ Others - - 1.00 - Unreferenced Products - - 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 - - pos.order