Allow to update records when importing data

issue652
review273131003
This commit is contained in:
Cédric Krier
2019-04-04 20:00:25 +02:00
parent 016ebc8f4e
commit e9ee50982b
6 changed files with 143 additions and 63 deletions
+1
View File
@@ -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)
+1 -1
View File
@@ -305,7 +305,7 @@ Class methods:
.. 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.
+58 -27
View File
@@ -7,7 +7,7 @@ import csv
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 @@ class ModelStorage(Model):
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 @@ class ModelStorage(Model):
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 @@ class ModelStorage(Model):
# 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 @@ class ModelStorage(Model):
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 @@ class ModelStorage(Model):
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):
+2 -22
View File
@@ -10,7 +10,7 @@ except ImportError:
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 @@ def register():
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 @@ def register():
TestWizard,
module='tests', type_='wizard')
import_data.register('tests')
access.register('tests')
model.register('tests')
modelstorage.register('tests')
+34 -13
View File
@@ -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 @@ class ImportDataReference(ModelSQL):
(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')
+47
View File
@@ -493,6 +493,53 @@ class ImportDataTestCase(unittest.TestCase):
[['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)