changeset 3692e6e56e40 in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=3692e6e56e40
description:
Allow to update records when importing data
issue652
review273131003
diffstat:
CHANGELOG | 1 +
doc/ref/models/models.rst | 2 +-
trytond/model/modelstorage.py | 85 +++++++++++++++++++++++++++------------
trytond/tests/__init__.py | 24 +----------
trytond/tests/import_data.py | 47 ++++++++++++++++------
trytond/tests/test_importdata.py | 47 ++++++++++++++++++++++
6 files changed, 143 insertions(+), 63 deletions(-)
diffs (322 lines):
diff -r d660f293a3d3 -r 3692e6e56e40 CHANGELOG
--- a/CHANGELOG Thu Apr 04 19:49:05 2019 +0200
+++ b/CHANGELOG Thu Apr 04 20:00:25 2019 +0200
@@ -1,3 +1,4 @@
+* Allow to update record when importing data
* Set context on record instantiated by Field.__set__
* Set context when reading related fields
* Check read access on field in search order (issue8189)
diff -r d660f293a3d3 -r 3692e6e56e40 doc/ref/models/models.rst
--- a/doc/ref/models/models.rst Thu Apr 04 19:49:05 2019 +0200
+++ b/doc/ref/models/models.rst Thu Apr 04 20:00:25 2019 +0200
@@ -305,7 +305,7 @@
.. classmethod:: ModelStorage.import_data(fields_names, data)
- Create records for all values in ``datas``.
+ Create or update records for all values in ``data``.
The field names of values must be defined in ``fields_names``.
It returns the number of imported records.
diff -r d660f293a3d3 -r 3692e6e56e40 trytond/model/modelstorage.py
--- a/trytond/model/modelstorage.py Thu Apr 04 19:49:05 2019 +0200
+++ b/trytond/model/modelstorage.py Thu Apr 04 20:00:25 2019 +0200
@@ -7,7 +7,7 @@
from decimal import Decimal
from itertools import islice, chain
-from functools import reduce, wraps
+from functools import wraps
from operator import itemgetter
from collections import defaultdict
@@ -807,7 +807,16 @@
return '%s,%s' % (relation, str(res_ids[0]))
return res_ids and res_ids[0] or False
- def process_lines(data, prefix, fields_def, position=0):
+ def dispatch(create, write, row, Relation=cls):
+ id_ = row.pop('id', None)
+ if id_:
+ write.append([Relation(id_)])
+ write.append(row)
+ else:
+ create.append(row)
+ return id_
+
+ def process_lines(data, prefix, fields_def, position=0, klass=cls):
line = data[position]
row = {}
translate = {}
@@ -831,7 +840,12 @@
this_field_def = fields_def[field[-1]]
field_type = this_field_def['type']
res = None
- if field_type == 'boolean':
+ if field[-1] == 'id':
+ try:
+ res = int(value)
+ except ValueError:
+ res = get_many2one(klass.__name__, value)
+ elif field_type == 'boolean':
if value.lower() == 'true':
res = True
elif value.lower() == 'false':
@@ -878,15 +892,13 @@
# Import one2many fields
nbrmax = 1
for field in todo:
- newfd = pool.get(fields_def[field]['relation']
- ).fields_get()
- res = process_lines(data, prefix + [field], newfd,
- position)
- (newrow, max2, _) = res
+ Relation = pool.get(fields_def[field]['relation'])
+ newfd = Relation.fields_get()
+ newrow, max2, _ = process_lines(
+ data, prefix + [field], newfd, position, klass=Relation)
nbrmax = max(nbrmax, max2)
- reduce(lambda x, y: x and y, newrow)
- row[field] = (reduce(lambda x, y: x or y,
list(newrow.values())) and
- [('create', [newrow])]) or []
+ create, write = [], []
+ dispatch(create, write, newrow, Relation)
i = max2
while (position + i) < len(data):
test = True
@@ -897,13 +909,17 @@
break
if not test:
break
- (newrow, max2, _) = \
- process_lines(data, prefix + [field], newfd,
- position + i)
- if reduce(lambda x, y: x or y, list(newrow.values())):
- row[field].append(('create', [newrow]))
+ newrow, max2, _ = process_lines(
+ data, prefix + [field], newfd, position + i,
+ klass=Relation)
+ dispatch(create, write, newrow, Relation)
i += max2
nbrmax = max(nbrmax, i)
+ row[field] = []
+ if create:
+ row[field].append(('create', create))
+ if write:
+ row[field].append(('write',) + tuple(write))
if prefix_len == 0:
for i in range(max(nbrmax, 1)):
data.pop(0)
@@ -916,20 +932,35 @@
fields_names = [x.split('/') for x in fields_names]
fields_def = cls.fields_get()
- to_create, translations, languages = [], [], set()
+ to_create, to_create_translations = [], []
+ to_write, to_write_translations = [], []
+ languages = set()
while len(data):
- (res, _, translate) = \
+ (row, _, translate) = \
process_lines(data, [], fields_def)
- to_create.append(res)
- translations.append(translate)
+ if dispatch(to_create, to_write, row):
+ to_write_translations.append(translate)
+ else:
+ to_create_translations.append(translate)
languages.update(translate)
- new_records = cls.create(to_create)
- for language in languages:
- translated = [t.get(language, {}) for t in translations]
- with Transaction().set_context(language=language):
- cls.write(*chain(*filter(itemgetter(1),
- zip(([r] for r in new_records), translated))))
- return len(new_records)
+
+ def translate(records, translations):
+ for language in languages:
+ translated = [t.get(language, {}) for t in translations]
+ with Transaction().set_context(language=language):
+ cls.write(*chain(*filter(itemgetter(1),
+ zip(([r] for r in records), translated))))
+ count = 0
+ if to_create:
+ records = cls.create(to_create)
+ translate(records, to_create_translations)
+ count += len(records)
+ if to_write:
+ cls.write(*to_write)
+ records = sum(to_write[0:None:2], [])
+ translate(records, to_write_translations)
+ count += len(records)
+ return count
@classmethod
def check_xml_record(cls, records, values):
diff -r d660f293a3d3 -r 3692e6e56e40 trytond/tests/__init__.py
--- a/trytond/tests/__init__.py Thu Apr 04 19:49:05 2019 +0200
+++ b/trytond/tests/__init__.py Thu Apr 04 20:00:25 2019 +0200
@@ -10,7 +10,7 @@
from ..pool import Pool
from .modelview import *
from .mptt import *
-from .import_data import *
+from . import import_data
from .export_data import *
from .trigger import *
from . import access
@@ -56,27 +56,6 @@
ModelViewEmptyPage,
ModelViewCircularDepends,
MPTT,
- ImportDataBoolean,
- ImportDataInteger,
- ImportDataIntegerRequired,
- ImportDataFloat,
- ImportDataFloatRequired,
- ImportDataNumeric,
- ImportDataNumericRequired,
- ImportDataChar,
- ImportDataText,
- ImportDataDate,
- ImportDataDateTime,
- ImportDataSelection,
- ImportDataMany2OneTarget,
- ImportDataMany2One,
- ImportDataMany2ManyTarget,
- ImportDataMany2Many,
- ImportDataMany2ManyRelation,
- ImportDataOne2Many,
- ImportDataOne2ManyTarget,
- ImportDataReferenceSelection,
- ImportDataReference,
ExportDataTarget,
ExportData,
ExportDataTarget2,
@@ -94,6 +73,7 @@
TestWizard,
module='tests', type_='wizard')
+ import_data.register('tests')
access.register('tests')
model.register('tests')
modelstorage.register('tests')
diff -r d660f293a3d3 -r 3692e6e56e40 trytond/tests/import_data.py
--- a/trytond/tests/import_data.py Thu Apr 04 19:49:05 2019 +0200
+++ b/trytond/tests/import_data.py Thu Apr 04 20:00:25 2019 +0200
@@ -2,19 +2,7 @@
# this repository contains the full copyright notices and license terms.
"Test for import_data"
from trytond.model import ModelSQL, fields
-
-__all__ = [
- 'ImportDataBoolean', 'ImportDataInteger', 'ImportDataIntegerRequired',
- 'ImportDataFloat', 'ImportDataFloatRequired',
- 'ImportDataNumeric', 'ImportDataNumericRequired',
- 'ImportDataChar', 'ImportDataText',
- 'ImportDataDate', 'ImportDataDateTime', 'ImportDataSelection',
- 'ImportDataMany2OneTarget', 'ImportDataMany2One',
- 'ImportDataMany2ManyTarget', 'ImportDataMany2Many',
- 'ImportDataMany2ManyRelation', 'ImportDataOne2Many',
- 'ImportDataOne2ManyTarget', 'ImportDataReferenceSelection',
- 'ImportDataReference',
- ]
+from trytond.pool import Pool
class ImportDataBoolean(ModelSQL):
@@ -154,3 +142,36 @@
(None, ''),
('test.import_data.reference.selection', 'Test'),
])
+
+
+class ImportDataUpdate(ModelSQL):
+ "Import Data for Update"
+ __name__ = 'test.import_data.update'
+ name = fields.Char("Name")
+
+
+def register(module):
+ Pool.register(
+ ImportDataBoolean,
+ ImportDataInteger,
+ ImportDataIntegerRequired,
+ ImportDataFloat,
+ ImportDataFloatRequired,
+ ImportDataNumeric,
+ ImportDataNumericRequired,
+ ImportDataChar,
+ ImportDataText,
+ ImportDataDate,
+ ImportDataDateTime,
+ ImportDataSelection,
+ ImportDataMany2OneTarget,
+ ImportDataMany2One,
+ ImportDataMany2ManyTarget,
+ ImportDataMany2Many,
+ ImportDataMany2ManyRelation,
+ ImportDataOne2Many,
+ ImportDataOne2ManyTarget,
+ ImportDataReferenceSelection,
+ ImportDataReference,
+ ImportDataUpdate,
+ module=module, type_='model')
diff -r d660f293a3d3 -r 3692e6e56e40 trytond/tests/test_importdata.py
--- a/trytond/tests/test_importdata.py Thu Apr 04 19:49:05 2019 +0200
+++ b/trytond/tests/test_importdata.py Thu Apr 04 20:00:25 2019 +0200
@@ -493,6 +493,53 @@
[['test.import_data.reference.selection,test.foo']])
transaction.rollback()
+ @with_transaction()
+ def test_update_id(self):
+ "Test update with ID"
+ pool = Pool()
+ Char = pool.get('test.import_data.update')
+ record = Char(name="foo")
+ record.save()
+
+ count = Char.import_data(['id', 'name'], [[str(record.id), "bar"]])
+
+ record, = Char.search([])
+ self.assertEqual(count, 1)
+ self.assertEqual(record.name, "bar")
+
+ @with_transaction()
+ def test_update_rec_name(self):
+ "Test update with rec_name"
+ pool = Pool()
+ Char = pool.get('test.import_data.update')
+ record = Char(name="foo")
+ record.save()
+
+ count = Char.import_data(['id', 'name'], [[record.rec_name, "bar"]])
+
+ record, = Char.search([])
+ self.assertEqual(count, 1)
+ self.assertEqual(record.name, "bar")
+
+ @with_transaction()
+ def test_update_one2many(self):
+ "Test update one2many"
+ pool = Pool()
+ One2many = pool.get('test.import_data.one2many')
+ Target = pool.get('test.import_data.one2many.target')
+ record = One2many(name="test", one2many=[Target(name="foo")])
+ record.save()
+ target, = record.one2many
+
+ count = One2many.import_data(
+ ['id', 'one2many/id', 'one2many/name'],
+ [[record.id, target.id, "bar"],
+ ['', '', "baz"]])
+
+ self.assertEqual(count, 1)
+ self.assertEqual(len(record.one2many), 2)
+ self.assertEqual([t.name for t in record.one2many], ["bar", "baz"])
+
def suite():
return unittest.TestLoader().loadTestsFromTestCase(ImportDataTestCase)