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.formaccount.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 @@
automatic100
-
-
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.
+
+ 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