Skip to content

Commit

Permalink
[IMP] update chained fields name in ir_exports_line and ir_filters
Browse files Browse the repository at this point in the history
  • Loading branch information
Ernesto Tejeda committed May 11, 2023
1 parent f45dcfe commit 10763f3
Show file tree
Hide file tree
Showing 2 changed files with 233 additions and 52 deletions.
284 changes: 232 additions & 52 deletions openupgradelib/openupgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Copyright Odoo Community Association (OCA)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

import ast
import inspect
import logging as _logging_module
import os
Expand All @@ -12,6 +13,11 @@
from datetime import datetime
from functools import wraps

if sys.version_info >= (3, 9):
from ast import unparse
else:
from astunparse import unparse

try:
from StringIO import StringIO
except ImportError:
Expand Down Expand Up @@ -147,6 +153,9 @@ def do_raise(error):
"copy_columns",
"copy_fields_multilang",
"remove_tables_fks",
"update_ir_exports_line",
"update_ir_filters",
"update_domain_str",
"rename_columns",
"rename_fields",
"rename_tables",
Expand Down Expand Up @@ -627,7 +636,188 @@ def rename_columns(cr, column_spec):
)


def rename_fields(env, field_spec, no_deep=False):
def _update_chained_field_name(
env, chained_field_name, first_model_name, model, new_field, old_field, separator
):
"""Rename a field name in a string. The string can represent
a chained fields name separated by a specific character.
Example: 'x_id/old_field/name'.
:param (Environment) env: Odoo environment.
:param (str) chained_field_name: String that may contain
the old field name.
:param (str) first_model_name: Model corresponding to the
first field of 'chained field name'.
:param (str) model: Model name of the field to rename.
:param (str) new_field: New field name.
:param (str) old_field: Old field name.
:param (str) separator: Separator of the 'chained field name'.
Example: in 'x_id/old_field/name' the separator is the character '/'.
:return: The updated 'chained field name' string if the field was
successfully renamed, False otherwise.
"""
current_model_name = first_model_name
field_name_list = chained_field_name.split(separator)
for index in range(len(field_name_list) - 1):
current_field_name = field_name_list[index]
if current_field_name == old_field and current_model_name == model:
field_name_list[index] = new_field
return separator.join(field_name_list)
env.cr.execute(
"""SELECT
im.model
FROM
ir_model_fields imf
INNER JOIN ir_model im ON im.model = imf.relation
WHERE
imf.name = %s AND imf.model = %s""",
(current_field_name, current_model_name),
)
res = env.cr.fetchone()
if not res:
return False
current_model_name = res[0]
if field_name_list[-1] == old_field and current_model_name == model:
field_name_list[-1] = new_field
return separator.join(field_name_list)
return False


def update_ir_exports_line(env, model, new_field, old_field):
"""Rename a field name in 'name' column in the 'ir_exports_line'
table. The field name in this column can be a chained
fields name separated by the character '/'.
:param (Environment) env: Odoo environment.
:param (str) model: Model name of the field to rename.
:param (str) new_field: New field name.
:param (str) old_field: Old field name.
"""
env.cr.execute(
"""SELECT
iel.id, iel.name, ie.resource
FROM
ir_exports_line iel
INNER JOIN ir_exports ie ON ie.id = iel.export_id""",
)
exports_lines = env.cr.fetchall()
for line_data in exports_lines:
line_id, chained_field_name, first_model_name = line_data
new_chained_field_name = _update_chained_field_name(
env,
chained_field_name=chained_field_name,
first_model_name=first_model_name,
model=model,
new_field=new_field,
old_field=old_field,
separator="/",
)
if new_chained_field_name:
logged_query(
env.cr,
"""UPDATE ir_exports_line
SET name = %s
WHERE id = %s
""",
(new_chained_field_name, line_id),
)


def update_domain_str(env, domain_str, first_model_name, model, new_field, old_field):
"""Rename a field that is part of a domain ('domain' column) in the
'ir_filters' table even if it is part of a dotted name.
Example:
If the domain string is "[('x_id.old_field.name', '=', 'value')]" and
'new_field' corresponds to 'model', then the domain string will be
updated to "[('x_id.new_field.name', '=', 'value')]".
:param (Environment) env: Odoo environment.
:param (str) domain_str: Domain string that may contain
the old field name.
:param (str) model: Model name of the field to rename.
:param (str) new_field: New field name.
:param (str) old_field: Old field name.
:return: The updated domain string if the field
was successfully renamed, False otherwise.
"""
domain_exp = ast.parse(domain_str, mode="eval")
domain_exp_list = domain_exp.body.elts
new_domain_exp_list = []
transformed = False
for elem in domain_exp_list:
new_elem = elem
if isinstance(elem, ast.Tuple) or isinstance(elem, ast.List):
field_name_node = elem.elts[0]
new_chained_field_name = _update_chained_field_name(
env,
chained_field_name=field_name_node.s,
first_model_name=first_model_name,
model=model,
new_field=new_field,
old_field=old_field,
separator=".",
)
if new_chained_field_name:
if sys.version_info < (3, 8):
new_field_name_node = ast.Str(s=new_chained_field_name)
else:
new_field_name_node = ast.Constant(
value=new_chained_field_name,
kind=field_name_node.kind,
)
new_elem = type(elem)(
elts=[new_field_name_node] + elem.elts[1:], ctx=elem.ctx
)
transformed = True
new_domain_exp_list.append(new_elem)
if transformed:
new_domain_expr = ast.Expression(
ast.List(elts=new_domain_exp_list, ctx=ast.Load())
)
return unparse(new_domain_expr).strip()
return False


def update_ir_filters(env, model, new_field, old_field):
"""Rename a field that is part of a domain ('domain' column) in the
'ir_filters' table even if it is part of a dotted name.
:param (Environment) env: Odoo environment.
:param (str) model: Model name of the field to rename.
:param (str) new_field: New field name.
:param (str) old_field: Old field name.
"""
env.cr.execute(
"""SELECT
if.id, if.domain, if.model_id
FROM
ir_filters if
WHERE
if.domain <> '[]'""",
)
ir_filters_data = env.cr.fetchall()
for data in ir_filters_data:
filter_id, domain_str, first_model_name = data
new_domain_str = update_domain_str(
env, domain_str, first_model_name, model, new_field, old_field
)
if new_domain_str:
logged_query(
env.cr,
"""UPDATE ir_filters
SET domain = %s
WHERE id = %s
""",
(new_domain_str, filter_id),
)


def rename_fields(env, field_spec, no_deep=False, dry=False):
"""Rename fields. Typically called in the pre script. WARNING: If using
this on base module, pass the argument ``no_deep`` with True value for
avoiding the using of the environment (which is not yet loaded).
Expand All @@ -654,34 +844,36 @@ def rename_fields(env, field_spec, no_deep=False):
:param no_deep: If True, avoids to perform any operation that involves
the environment. Not used for now.
"""
import wdb;wdb.set_trace()
cr = env.cr
for model, table, old_field, new_field in field_spec:
if column_exists(cr, table, old_field):
rename_columns(cr, {table: [(old_field, new_field)]})
# Rename corresponding field entry
cr.execute(
"""
UPDATE ir_model_fields
SET name = %s
WHERE name = %s
AND model = %s
""",
(new_field, old_field, model),
)
# Rename translations
if version_info[0] < 16:
if not dry:
if column_exists(cr, table, old_field):
rename_columns(cr, {table: [(old_field, new_field)]})
# Rename corresponding field entry
cr.execute(
"""
UPDATE ir_translation
UPDATE ir_model_fields
SET name = %s
WHERE name = %s
AND type = 'model'
AND model = %s
""",
(
"%s,%s" % (model, new_field),
"%s,%s" % (model, old_field),
),
(new_field, old_field, model),
)
# Rename translations
if version_info[0] < 16:
cr.execute(
"""
UPDATE ir_translation
SET name = %s
WHERE name = %s
AND type = 'model'
""",
(
"%s,%s" % (model, new_field),
"%s,%s" % (model, old_field),
),
)
# Rename possible attachments (if field is Binary with attachment=True)
if column_exists(cr, "ir_attachment", "res_field"):
cr.execute(
Expand All @@ -693,37 +885,17 @@ def rename_fields(env, field_spec, no_deep=False):
""",
(new_field, model, old_field),
)
# Rename appearances on export profiles
# TODO: Rename when the field is part of a submodel (ex. m2one.field)
cr.execute(
"""
UPDATE ir_exports_line iel
SET name = %s
FROM ir_exports ie
WHERE iel.name = %s
AND ie.id = iel.export_id
AND ie.resource = %s
""",
(new_field, old_field, model),
)
# Rename appearances on filters
# Example of replaced domain: [['field', '=', self], ...]
# TODO: Rename when the field is part of a submodel (ex. m2one.field)
cr.execute(
"""
UPDATE ir_filters
SET domain = regexp_replace(
domain, %(old_pattern)s, %(new_pattern)s, 'g'
)
WHERE model_id = %%s
AND domain ~ %(old_pattern)s
"""
% {
"old_pattern": r"""$$('|")%s('|")$$""" % old_field,
"new_pattern": r"$$\1%s\2$$" % new_field,
},
(model,),
)
# # Rename appearances on export profiles
# Covered the renaming when the field is part of a submodel
# Example: 'field_x.old_field.name' -> 'field_x.new_field.name'
update_ir_exports_line(env, model, new_field, old_field)

# # Rename appearances on filters
# Update field names inside domains, including dotted field names.
# Example:
# [('field_x.old_field.name', '=' name)] -> [('field_x.new_field.name', '=' name)]
update_ir_filters(env, model, new_field, old_field)

# Examples of replaced contexts:
# {'group_by': ['field', 'other_field'], 'other_key':value}
# {'group_by': ['date_field:month']}
Expand Down Expand Up @@ -1833,6 +2005,7 @@ def m2o_to_x2m(cr, model, table, field, source_field):
.. versionadded:: 8.0
"""
import wdb;wdb.set_trace()
try:
columns = model._fields
except AttributeError:
Expand Down Expand Up @@ -1896,6 +2069,13 @@ def m2o_to_x2m(cr, model, table, field, source_field):
"m2o_to_x2m: field %s of model %s is not a "
"many2many/one2many one" % (field, model._name)
)
if field != source_field:
rename_fields(
env=model.env,
field_spec=[(model._name, model._table, source_field, field)],
no_deep=False,
dry=True,
)


# Backwards compatibility
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pep8-naming
lxml<=4.3.4
psycopg2==2.7.3.1
setuptools<58.0
astunparse; python_version < '3.9'

0 comments on commit 10763f3

Please sign in to comment.