From dba0a06fb15254423efc6594b6c843f2d36b4248 Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Mon, 28 Aug 2023 16:58:53 +0200 Subject: [PATCH 1/9] Implement m2m_to_o2m MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rémy Taymans Signed-off-by: Carmen Bianca BAKKER --- openupgradelib/openupgrade.py | 115 ++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/openupgradelib/openupgrade.py b/openupgradelib/openupgrade.py index ca040ef5..d49b3bf7 100644 --- a/openupgradelib/openupgrade.py +++ b/openupgradelib/openupgrade.py @@ -171,6 +171,8 @@ def do_raise(error): "get_legacy_name", "get_model2table", "m2o_to_x2m", + "m2m_to_o2m", + "m2m_to_o2m_dataloss_warn", "float_to_integer", "message", "check_values_selection_field", @@ -1935,6 +1937,119 @@ def m2o_to_m2m(cr, model, table, field, source_field): return m2o_to_x2m(cr, model, table, field, source_field) +def m2m_to_o2m( + env, + model, + field, + source_relation_table, + relation_source_field, + relation_comodel_field, +): + """Transform many2many relations into one2many (with possible data + loss). + + Use rename_tables() in your pre-migrate script to keep the many2many + relation table and give them as 'source_relation_table' argument. + And remove foreign keys constraints with remove_tables_fks(). + + A full example seems pertinent: hr.plan and hr.plan.activity.type used to + have an M2M relationship. Now, hr.plan has an O2M relationship to + hr.plan.activity.type. In pre-migrate, execute:: + + remove_tables_fks(env.cr, ["hr_plan_hr_plan_activity_type_rel"]) + rename_tables(env.cr, [("hr_plan_hr_plan_activity_type_rel", + get_legacy_name("hr_plan_hr_plan_activity_type_rel"))]) + + In post-migrate:: + + m2m_to_o2m_dataloss_warn(env.cr, + get_legacy_name("hr_plan_hr_plan_activity_type_rel"), "hr_plan_id", + "hr_plan_activity_type_id") + m2m_to_o2m(env, "hr.plan", "plan_activity_type_ids", + get_legacy_name("hr_plan_hr_plan_activity_type_rel"), + "hr_plan_id", "hr_plan_activity_type_id") + + :param model: The target registery model + :param field: The field that changes from m2m to o2m + :param source_relation_table: The (renamed) many2many relation table + :param relation_source_field: The column name of the 'model' id + in the relation table (the One part of One2Many) + :param relation_comodel_field: The column name of the comodel id in + the relation table (the Many part of One2Many) + + .. versionadded:: 16.0 + """ + columns = env[model]._fields.get(field) + target_table = env[columns.comodel_name]._table + target_field = columns.inverse_name + logged_query( + env.cr, + """ + UPDATE %(target_table)s AS target + SET %(target_field)s=source.%(relation_source_field)s + FROM %(source_relation_table)s AS source + WHERE source.%(relation_comodel_field)s=target.id + """, + { + "target_table": AsIs(target_table), + "target_field": AsIs(target_field), + "source_relation_table": AsIs(source_relation_table), + "relation_source_field": AsIs(relation_source_field), + "relation_comodel_field": AsIs(relation_comodel_field), + }, + ) + + +def m2m_to_o2m_dataloss_warn( + cr, source_relation_table, relation_source_field, relation_comodel_field +): + """Warn user about data loss when migrating data from many2many to + many2one. + + :param source_relation_table: The many2many relation table + of the model that will be on the 'one' side of the relation + :param relation_source_field: The name of the column containing the id of + the 'one' part of the new relation. + :param relation_comodel_field: The name of the column containing ids + of the 'many' part of the new relation. + + .. versionadded:: 16.0 + """ + result = False + logged_query( + cr, + """ + SELECT DISTINCT %(relation_comodel_field)s + FROM %(source_relation_table)s + WHERE %(relation_comodel_field)s IN ( + SELECT %(relation_comodel_field)s + FROM %(source_relation_table)s + GROUP BY %(relation_comodel_field)s + HAVING COUNT(*) > 1 + ) + """, + { + "source_relation_table": AsIs(source_relation_table), + "relation_comodel_field": AsIs(relation_comodel_field), + }, + ) + for res in cr.fetchall(): + _logger.error( + "%(relation_comodel_field)s record id %(id)s is linked to several" + " %(relation_source_field)s records. %(relation_comodel_field)s can" + " only be linked to one %(relation_source_field)s record. Fix these" + " data before migrating to avoid data loss.", + { + "id": res[0], + "relation_source_field": relation_source_field, + "relation_comodel_field": relation_comodel_field, + }, + ) + result = True + + return result + + def float_to_integer(cr, table, field): """ Change column type from float to integer. It will just From 1fe94d2218076853bc3f3440065b36e4be209103 Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Mon, 28 Aug 2023 17:16:43 +0200 Subject: [PATCH 2/9] fixup! Implement m2m_to_o2m Signed-off-by: Carmen Bianca BAKKER --- openupgradelib/openupgrade.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openupgradelib/openupgrade.py b/openupgradelib/openupgrade.py index d49b3bf7..4e599271 100644 --- a/openupgradelib/openupgrade.py +++ b/openupgradelib/openupgrade.py @@ -2004,7 +2004,7 @@ def m2m_to_o2m_dataloss_warn( cr, source_relation_table, relation_source_field, relation_comodel_field ): """Warn user about data loss when migrating data from many2many to - many2one. + one2many. :param source_relation_table: The many2many relation table of the model that will be on the 'one' side of the relation From af8bf42defce17d64579b7e31bd60bbef0a5c694 Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Tue, 29 Aug 2023 10:01:06 +0200 Subject: [PATCH 3/9] fixup! Implement m2m_to_o2m Signed-off-by: Carmen Bianca BAKKER --- openupgradelib/openupgrade.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/openupgradelib/openupgrade.py b/openupgradelib/openupgrade.py index 4e599271..ab8c44de 100644 --- a/openupgradelib/openupgrade.py +++ b/openupgradelib/openupgrade.py @@ -172,7 +172,6 @@ def do_raise(error): "get_model2table", "m2o_to_x2m", "m2m_to_o2m", - "m2m_to_o2m_dataloss_warn", "float_to_integer", "message", "check_values_selection_field", @@ -1944,6 +1943,7 @@ def m2m_to_o2m( source_relation_table, relation_source_field, relation_comodel_field, + warn_dataloss=True, ): """Transform many2many relations into one2many (with possible data loss). @@ -1952,8 +1952,8 @@ def m2m_to_o2m( relation table and give them as 'source_relation_table' argument. And remove foreign keys constraints with remove_tables_fks(). - A full example seems pertinent: hr.plan and hr.plan.activity.type used to - have an M2M relationship. Now, hr.plan has an O2M relationship to + A concrete example seems pertinent: hr.plan and hr.plan.activity.type used + to have an M2M relationship. Now, hr.plan has an O2M relationship to hr.plan.activity.type. In pre-migrate, execute:: remove_tables_fks(env.cr, ["hr_plan_hr_plan_activity_type_rel"]) @@ -1962,9 +1962,6 @@ def m2m_to_o2m( In post-migrate:: - m2m_to_o2m_dataloss_warn(env.cr, - get_legacy_name("hr_plan_hr_plan_activity_type_rel"), "hr_plan_id", - "hr_plan_activity_type_id") m2m_to_o2m(env, "hr.plan", "plan_activity_type_ids", get_legacy_name("hr_plan_hr_plan_activity_type_rel"), "hr_plan_id", "hr_plan_activity_type_id") @@ -1979,6 +1976,13 @@ def m2m_to_o2m( .. versionadded:: 16.0 """ + if warn_dataloss: + _m2m_to_o2m_dataloss_warn( + env.cr, + source_relation_table, + relation_source_field, + relation_comodel_field, + ) columns = env[model]._fields.get(field) target_table = env[columns.comodel_name]._table target_field = columns.inverse_name @@ -2000,7 +2004,7 @@ def m2m_to_o2m( ) -def m2m_to_o2m_dataloss_warn( +def _m2m_to_o2m_dataloss_warn( cr, source_relation_table, relation_source_field, relation_comodel_field ): """Warn user about data loss when migrating data from many2many to From 44cd4b345af59dca4f23501fe538cca7bd536e91 Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Tue, 29 Aug 2023 11:13:50 +0200 Subject: [PATCH 4/9] fixup! Implement m2m_to_o2m Signed-off-by: Carmen Bianca BAKKER --- openupgradelib/openupgrade.py | 52 ++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/openupgradelib/openupgrade.py b/openupgradelib/openupgrade.py index ab8c44de..80e59b74 100644 --- a/openupgradelib/openupgrade.py +++ b/openupgradelib/openupgrade.py @@ -9,6 +9,7 @@ import os import sys import uuid +from collections import defaultdict from datetime import datetime from functools import wraps @@ -1945,8 +1946,11 @@ def m2m_to_o2m( relation_comodel_field, warn_dataloss=True, ): - """Transform many2many relations into one2many (with possible data - loss). + """Transform many2many relations into one2many (with possible data loss). + The data loss occurs when two (or more) records are linked to one other + record in a way that is no longer possible in the new one2many relationship. + In that case, the one2many relationship stays active on the record with the + lowest id. Use rename_tables() in your pre-migrate script to keep the many2many relation table and give them as 'source_relation_table' argument. @@ -1966,6 +1970,10 @@ def m2m_to_o2m( get_legacy_name("hr_plan_hr_plan_activity_type_rel"), "hr_plan_id", "hr_plan_activity_type_id") + In the above example, if hr.plan A and hr.plan B both had a relation to + hr.plan.activity.type X, then that relationship is removed from one of the + hr.plans. The relationship stays active on the hr.plan with the lowest id. + :param model: The target registery model :param field: The field that changes from m2m to o2m :param source_relation_table: The (renamed) many2many relation table @@ -1991,7 +1999,11 @@ def m2m_to_o2m( """ UPDATE %(target_table)s AS target SET %(target_field)s=source.%(relation_source_field)s - FROM %(source_relation_table)s AS source + FROM ( + SELECT %(relation_comodel_field)s, MIN(%(relation_source_field)s) + FROM %(source_relation_table)s + GROUP BY %(relation_comodel_field)s + ) AS source WHERE source.%(relation_comodel_field)s=target.id """, { @@ -2023,7 +2035,7 @@ def _m2m_to_o2m_dataloss_warn( logged_query( cr, """ - SELECT DISTINCT %(relation_comodel_field)s + SELECT %(relation_comodel_field)s, %(relation_source_field)s FROM %(source_relation_table)s WHERE %(relation_comodel_field)s IN ( SELECT %(relation_comodel_field)s @@ -2034,23 +2046,31 @@ def _m2m_to_o2m_dataloss_warn( """, { "source_relation_table": AsIs(source_relation_table), + "relation_source_field": AsIs(relation_source_field), "relation_comodel_field": AsIs(relation_comodel_field), }, ) + # comodel_to_source is the m2x relationship from the perspective of the + # comodel, which should be m2o after migration. + comodel_to_source = defaultdict(list) for res in cr.fetchall(): - _logger.error( - "%(relation_comodel_field)s record id %(id)s is linked to several" - " %(relation_source_field)s records. %(relation_comodel_field)s can" - " only be linked to one %(relation_source_field)s record. Fix these" - " data before migrating to avoid data loss.", - { - "id": res[0], - "relation_source_field": relation_source_field, - "relation_comodel_field": relation_comodel_field, - }, - ) + comodel_to_source[res[0]].append(res[1]) + if comodel_to_source: result = True - + for comodel, source_records in comodel_to_source.items(): + logger.error( + "%(relation_comodel_field)s record id %(comodel_id)s is linked" + " to several %(relation_source_field)s records: %(source_ids)s." + " %(relation_comodel_field)s can only be linked to one" + " %(relation_source_field)s record. Fix these data before" + " migrating to avoid data loss.", + { + "comodel_id": comodel, + "source_ids": repr(source_records), + "relation_source_field": relation_source_field, + "relation_comodel_field": relation_comodel_field, + }, + ) return result From 11b36c461302427151df64202a82609bd395a869 Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Tue, 29 Aug 2023 11:37:17 +0200 Subject: [PATCH 5/9] fixup! Implement m2m_to_o2m Signed-off-by: Carmen Bianca BAKKER --- openupgradelib/openupgrade.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openupgradelib/openupgrade.py b/openupgradelib/openupgrade.py index 80e59b74..8f598466 100644 --- a/openupgradelib/openupgrade.py +++ b/openupgradelib/openupgrade.py @@ -2000,7 +2000,8 @@ def m2m_to_o2m( UPDATE %(target_table)s AS target SET %(target_field)s=source.%(relation_source_field)s FROM ( - SELECT %(relation_comodel_field)s, MIN(%(relation_source_field)s) + SELECT %(relation_comodel_field)s, + MIN(%(relation_source_field)s) AS %(relation_source_field)s FROM %(source_relation_table)s GROUP BY %(relation_comodel_field)s ) AS source @@ -2063,10 +2064,13 @@ def _m2m_to_o2m_dataloss_warn( " to several %(relation_source_field)s records: %(source_ids)s." " %(relation_comodel_field)s can only be linked to one" " %(relation_source_field)s record. Fix these data before" - " migrating to avoid data loss.", + " migrating to avoid data loss. If you do not, only" + " %(relation_source_field)s %(lowest_source_id)s will remain" + " linked.", { "comodel_id": comodel, "source_ids": repr(source_records), + "lowest_source_id": sorted(source_records)[0], "relation_source_field": relation_source_field, "relation_comodel_field": relation_comodel_field, }, From 873d46fadcb15d4b24f41efc280272d4b4fba72e Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Tue, 29 Aug 2023 15:24:39 +0200 Subject: [PATCH 6/9] fixup! Implement m2m_to_o2m Signed-off-by: Carmen Bianca BAKKER --- openupgradelib/openupgrade.py | 180 +++++++++++++++++++++------------- 1 file changed, 112 insertions(+), 68 deletions(-) diff --git a/openupgradelib/openupgrade.py b/openupgradelib/openupgrade.py index 8f598466..40cfef31 100644 --- a/openupgradelib/openupgrade.py +++ b/openupgradelib/openupgrade.py @@ -11,6 +11,7 @@ import uuid from collections import defaultdict from datetime import datetime +from enum import Enum, auto from functools import wraps try: @@ -172,6 +173,7 @@ def do_raise(error): "get_legacy_name", "get_model2table", "m2o_to_x2m", + "M2mToO2mStrategy", "m2m_to_o2m", "float_to_integer", "message", @@ -1937,28 +1939,41 @@ def m2o_to_m2m(cr, model, table, field, source_field): return m2o_to_x2m(cr, model, table, field, source_field) +class M2mToO2mStrategy(Enum): + """An Enum that flags the strategy for dealing with m2m-to-o2m migrations.""" + + #: Log data loss. + LOG = auto() + #: (Try to) prevent data loss by copying child records that would link to + #: only one parent record henceforth. + COPY = auto() + + def m2m_to_o2m( env, model, field, source_relation_table, - relation_source_field, - relation_comodel_field, - warn_dataloss=True, + relation_parent_field, + relation_child_field, + strategy: M2mToO2mStrategy, ): """Transform many2many relations into one2many (with possible data loss). - The data loss occurs when two (or more) records are linked to one other - record in a way that is no longer possible in the new one2many relationship. - In that case, the one2many relationship stays active on the record with the - lowest id. + The data loss occurs when two (or more) parent records are linked to one + other child record in a way that is no longer possible in the new one2many + relationship. In that case, the one2many relationship stays active on the + parent record with the lowest id. + + If the COPY strategy is used, the child records are instead copied and + assigned to the remaining parent records. Use rename_tables() in your pre-migrate script to keep the many2many relation table and give them as 'source_relation_table' argument. And remove foreign keys constraints with remove_tables_fks(). A concrete example seems pertinent: hr.plan and hr.plan.activity.type used - to have an M2M relationship. Now, hr.plan has an O2M relationship to - hr.plan.activity.type. In pre-migrate, execute:: + to have an M2M relationship. Now, hr.plan (parent) has an O2M relationship + to hr.plan.activity.type (child). In pre-migrate, execute:: remove_tables_fks(env.cr, ["hr_plan_hr_plan_activity_type_rel"]) rename_tables(env.cr, [("hr_plan_hr_plan_activity_type_rel", @@ -1968,29 +1983,49 @@ def m2m_to_o2m( m2m_to_o2m(env, "hr.plan", "plan_activity_type_ids", get_legacy_name("hr_plan_hr_plan_activity_type_rel"), - "hr_plan_id", "hr_plan_activity_type_id") + "hr_plan_id", "hr_plan_activity_type_id", M2mToO2mStrategy.LOG) In the above example, if hr.plan A and hr.plan B both had a relation to hr.plan.activity.type X, then that relationship is removed from one of the hr.plans. The relationship stays active on the hr.plan with the lowest id. + If the COPY strategy is used, a copy of hr.plan.activity.type X is made and + assigned to hr.plan B. :param model: The target registery model :param field: The field that changes from m2m to o2m :param source_relation_table: The (renamed) many2many relation table - :param relation_source_field: The column name of the 'model' id + :param relation_parent_field: The column name of the model id in the relation table (the One part of One2Many) - :param relation_comodel_field: The column name of the comodel id in + :param relation_child_field: The column name of the comodel id in the relation table (the Many part of One2Many) + :param strategy: The strategy of resolving the conversion. .. versionadded:: 16.0 """ - if warn_dataloss: - _m2m_to_o2m_dataloss_warn( - env.cr, - source_relation_table, - relation_source_field, - relation_comodel_field, - ) + child_to_parent = _get_child_to_parent_mapping( + env.cr, + source_relation_table, + relation_parent_field, + relation_child_field, + ) + if strategy == M2mToO2mStrategy.LOG: + for child, parents in child_to_parent.items(): + logger.error( + "%(relation_child_field)s record id %(child_id)s is linked" + " to several %(relation_parent_field)s records: %(parent_ids)s." + " %(relation_child_field)s can only be linked to one" + " %(relation_parent_field)s record. Fix these data before" + " migrating to avoid data loss. If you do not, only" + " %(relation_parent_field)s %(lowest_parent_id)s will remain" + " linked.", + { + "child_id": child, + "parent_ids": repr(parents), + "lowest_parent_id": sorted(parents)[0], + "relation_parent_field": relation_parent_field, + "relation_child_field": relation_child_field, + }, + ) columns = env[model]._fields.get(field) target_table = env[columns.comodel_name]._table target_field = columns.inverse_name @@ -1998,84 +2033,93 @@ def m2m_to_o2m( env.cr, """ UPDATE %(target_table)s AS target - SET %(target_field)s=source.%(relation_source_field)s + SET %(target_field)s=source.%(relation_parent_field)s FROM ( - SELECT %(relation_comodel_field)s, - MIN(%(relation_source_field)s) AS %(relation_source_field)s + SELECT %(relation_child_field)s, + MIN(%(relation_parent_field)s) AS %(relation_parent_field)s FROM %(source_relation_table)s - GROUP BY %(relation_comodel_field)s + GROUP BY %(relation_child_field)s ) AS source - WHERE source.%(relation_comodel_field)s=target.id + WHERE source.%(relation_child_field)s=target.id """, { "target_table": AsIs(target_table), "target_field": AsIs(target_field), "source_relation_table": AsIs(source_relation_table), - "relation_source_field": AsIs(relation_source_field), - "relation_comodel_field": AsIs(relation_comodel_field), + "relation_parent_field": AsIs(relation_parent_field), + "relation_child_field": AsIs(relation_child_field), }, ) + if strategy == M2mToO2mStrategy.COPY: + for child, parents in child_to_parent.items(): + # Remove the lowest, which should now have the target field + # populated. + parents.sort() + skip = parents.pop(0) + logger.warning( + "Retaining %(child_model)s(%(child_id)s,), having just assigned" + " its %(target_field)s field to" + " %(parent_model)s(%(parent_id)s,). Copies will be made of" + " this record to assign to other %(parent_model)s records.", + { + "child_model": columns.comodel_name, + "child_id": child, + "parent_model": model, + "parent_id": skip, + "target_field": target_field, + }, + ) + target_id = env[columns.comodel_name].browse(child) + for parent in parents: + parent_id = env[model].browse(parent) + logger.warning( + "Making a copy of %(child_model)s(%(target_id)s,) and" + " assigning %(parent_model)s(%(parent_id)s,) to its" + " %(target_field)s field.", + { + "child_model": columns.comodel_name, + "target_id": child, + "parent_model": model, + "parent_id": parent, + "target_field": target_field, + }, + ) + target_copy = target_id.copy() + setattr(target_copy, target_field, parent_id) -def _m2m_to_o2m_dataloss_warn( - cr, source_relation_table, relation_source_field, relation_comodel_field +def _get_child_to_parent_mapping( + cr, source_relation_table, relation_parent_field, relation_child_field ): - """Warn user about data loss when migrating data from many2many to - one2many. - - :param source_relation_table: The many2many relation table - of the model that will be on the 'one' side of the relation - :param relation_source_field: The name of the column containing the id of - the 'one' part of the new relation. - :param relation_comodel_field: The name of the column containing ids - of the 'many' part of the new relation. + """From a many2many table, get a child-to-parent mapping, where the child + has multiple parents (which will be impossible in the new o2m/m2o + relationship). The key is the child's id, and the value is a list of parent + ids. .. versionadded:: 16.0 """ - result = False logged_query( cr, """ - SELECT %(relation_comodel_field)s, %(relation_source_field)s + SELECT %(relation_child_field)s, %(relation_parent_field)s FROM %(source_relation_table)s - WHERE %(relation_comodel_field)s IN ( - SELECT %(relation_comodel_field)s + WHERE %(relation_child_field)s IN ( + SELECT %(relation_child_field)s FROM %(source_relation_table)s - GROUP BY %(relation_comodel_field)s + GROUP BY %(relation_child_field)s HAVING COUNT(*) > 1 ) """, { "source_relation_table": AsIs(source_relation_table), - "relation_source_field": AsIs(relation_source_field), - "relation_comodel_field": AsIs(relation_comodel_field), + "relation_parent_field": AsIs(relation_parent_field), + "relation_child_field": AsIs(relation_child_field), }, ) - # comodel_to_source is the m2x relationship from the perspective of the - # comodel, which should be m2o after migration. - comodel_to_source = defaultdict(list) + child_to_parent = defaultdict(list) for res in cr.fetchall(): - comodel_to_source[res[0]].append(res[1]) - if comodel_to_source: - result = True - for comodel, source_records in comodel_to_source.items(): - logger.error( - "%(relation_comodel_field)s record id %(comodel_id)s is linked" - " to several %(relation_source_field)s records: %(source_ids)s." - " %(relation_comodel_field)s can only be linked to one" - " %(relation_source_field)s record. Fix these data before" - " migrating to avoid data loss. If you do not, only" - " %(relation_source_field)s %(lowest_source_id)s will remain" - " linked.", - { - "comodel_id": comodel, - "source_ids": repr(source_records), - "lowest_source_id": sorted(source_records)[0], - "relation_source_field": relation_source_field, - "relation_comodel_field": relation_comodel_field, - }, - ) - return result + child_to_parent[res[0]].append(res[1]) + return child_to_parent def float_to_integer(cr, table, field): From 4258c5f991bf19e22116053568af80538b047bff Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Tue, 29 Aug 2023 15:34:47 +0200 Subject: [PATCH 7/9] fixup! Implement m2m_to_o2m Signed-off-by: Carmen Bianca BAKKER --- openupgradelib/openupgrade.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openupgradelib/openupgrade.py b/openupgradelib/openupgrade.py index 40cfef31..f2161204 100644 --- a/openupgradelib/openupgrade.py +++ b/openupgradelib/openupgrade.py @@ -1956,7 +1956,7 @@ def m2m_to_o2m( source_relation_table, relation_parent_field, relation_child_field, - strategy: M2mToO2mStrategy, + strategy, ): """Transform many2many relations into one2many (with possible data loss). The data loss occurs when two (or more) parent records are linked to one @@ -2071,7 +2071,6 @@ def m2m_to_o2m( ) target_id = env[columns.comodel_name].browse(child) for parent in parents: - parent_id = env[model].browse(parent) logger.warning( "Making a copy of %(child_model)s(%(target_id)s,) and" " assigning %(parent_model)s(%(parent_id)s,) to its" @@ -2084,6 +2083,7 @@ def m2m_to_o2m( "target_field": target_field, }, ) + parent_id = env[model].browse(parent) target_copy = target_id.copy() setattr(target_copy, target_field, parent_id) From f1d74121900a3855a86add9d4fff1e34e3e53f68 Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Tue, 29 Aug 2023 15:47:31 +0200 Subject: [PATCH 8/9] fixup! Implement m2m_to_o2m Signed-off-by: Carmen Bianca BAKKER --- openupgradelib/openupgrade.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openupgradelib/openupgrade.py b/openupgradelib/openupgrade.py index f2161204..225d57fa 100644 --- a/openupgradelib/openupgrade.py +++ b/openupgradelib/openupgrade.py @@ -1987,9 +1987,9 @@ def m2m_to_o2m( In the above example, if hr.plan A and hr.plan B both had a relation to hr.plan.activity.type X, then that relationship is removed from one of the - hr.plans. The relationship stays active on the hr.plan with the lowest id. - If the COPY strategy is used, a copy of hr.plan.activity.type X is made and - assigned to hr.plan B. + hr.plans. The relationship stays active on the hr.plan with the lowest id + (hr.plan A). If the COPY strategy is used, a copy of hr.plan.activity.type X + is made and assigned to hr.plan B. :param model: The target registery model :param field: The field that changes from m2m to o2m From 4717d394f3701ea9f372ce21037e1d695e09e258 Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Thu, 31 Aug 2023 09:46:24 +0200 Subject: [PATCH 9/9] fixup! Implement m2m_to_o2m Signed-off-by: Carmen Bianca BAKKER --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 99c25f8c..77cc0940 100755 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ "lxml", "cssselect", 'importlib_metadata; python_version<"3.8"', + "enum34; python_version < '3.4'", ], python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", license=openupgradelib.__license__,