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)

Reply via email to