Allow to update records when importing data
issue652 review273131003
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user