1616
1717import psycopg2
1818from psycopg2 import sql
19- from psycopg2 .extras import Json , execute_values
19+ from psycopg2 .extras import Json
2020
2121try :
2222 from odoo import release
@@ -40,7 +40,13 @@ def make_index_name(table_name, column_name):
4040from .const import ENVIRON
4141from .domains import _adapt_one_domain , _replace_path , _valid_path_to , adapt_domains
4242from .exceptions import SleepyDeveloperError
43- from .helpers import _dashboard_actions , _validate_model , resolve_model_fields_path , table_of_model
43+ from .helpers import (
44+ _dashboard_actions ,
45+ _remove_export_lines ,
46+ _validate_model ,
47+ resolve_model_fields_path ,
48+ table_of_model ,
49+ )
4450from .inherit import for_each_inherit
4551from .misc import SelfPrintEvalContext , log_progress , version_gte
4652from .orm import env , invalidate
@@ -79,126 +85,6 @@ def make_index_name(table_name, column_name):
7985)
8086
8187
82- def _get_resolved_ir_exports (cr , models = None , fields = None ):
83- """
84- Return a list of ir.exports.line records which models or fields match the given arguments.
85-
86- Export lines can reference nested models through relationship field "paths"
87- (e.g. "partner_id/country_id/name"), therefore these needs to be resolved properly.
88-
89- Only one of ``models`` or ``fields`` arguments should be provided.
90-
91- :param list[str] models: a list of model names to match in exports
92- :param list[(str, str)] fields: a list of (model, field) tuples to match in exports
93- :return: the resolved field paths parts for each matched export line id
94- :rtype: dict[int, list[FieldsPathPart]]
95-
96- :meta private: exclude from online docs
97- """
98- assert bool (models ) ^ bool (fields ), "One of models or fields must be given, and not both."
99-
100- # Get the model fields paths for exports.
101- # When matching fields we can already broadly filter on field names (will be double-checked later).
102- # When matching models we can't exclude anything because we don't know intermediate models.
103- where = ""
104- params = {}
105- if fields :
106- fields = {(model , fields ) for model , fields in fields } # noqa: C416 # make sure set[tuple]
107- where = "WHERE el.name ~ ANY(%(field_names)s)"
108- params ["field_names" ] = [f [1 ] for f in fields ]
109- cr .execute (
110- """
111- SELECT el.id, e.resource AS model, string_to_array(el.name, '/') AS path
112- FROM ir_exports e
113- JOIN ir_exports_line el ON e.id = el.export_id
114- {where}
115- """ .format (where = where ),
116- params ,
117- )
118- paths_to_line_ids = {}
119- for line_id , model , path in cr .fetchall ():
120- paths_to_line_ids .setdefault ((model , tuple (path )), set ()).add (line_id )
121-
122- # Resolve intermediate models for all model fields paths, filter only matching paths parts
123- matching_paths_parts = {}
124- for model , path in paths_to_line_ids :
125- resolved_paths = resolve_model_fields_path (cr , model , path )
126- if fields :
127- matching_parts = [p for p in resolved_paths if (p .field_model , p .field_name ) in fields ]
128- else :
129- matching_parts = [p for p in resolved_paths if p .field_model in models ]
130- if not matching_parts :
131- continue
132- matching_paths_parts [(model , path )] = matching_parts
133-
134- # Return the matched parts for each export line id
135- result = {}
136- for (model , path ), matching_parts in matching_paths_parts .items ():
137- line_ids = paths_to_line_ids .get ((model , path ))
138- if not line_ids :
139- continue # wut?
140- for line_id in line_ids :
141- result .setdefault (line_id , []).extend (matching_parts )
142- return result
143-
144-
145- def rename_ir_exports_fields (cr , models_fields_map ):
146- """
147- Rename fields references in ir.exports.line records.
148-
149- :param dict[str, dict[str, str]] models_fields_map: a dict of models to the fields rename dict,
150- like: `{"model.name": {"old_field": "new_field", ...}, ...}`
151-
152- :meta private: exclude from online docs
153- """
154- matching_exports = _get_resolved_ir_exports (
155- cr ,
156- fields = [(model , field ) for model , fields_map in models_fields_map .items () for field in fields_map ],
157- )
158- if not matching_exports :
159- return
160- _logger .debug ("Renaming %d export template lines with renamed fields" , len (matching_exports ))
161- fixed_lines_paths = {}
162- for line_id , resolved_paths in matching_exports .items ():
163- for path_part in resolved_paths :
164- assert path_part .field_model in models_fields_map
165- fields_map = models_fields_map [path_part .field_model ]
166- assert path_part .field_name in fields_map
167- assert path_part .path [path_part .part_index - 1 ] == path_part .field_name
168- new_field_name = fields_map [path_part .field_name ]
169- fixed_path = fixed_lines_paths .get (line_id , list (path_part .path ))
170- fixed_path [path_part .part_index - 1 ] = new_field_name
171- fixed_lines_paths [line_id ] = fixed_path
172- execute_values (
173- cr ,
174- """
175- UPDATE ir_exports_line el
176- SET name = v.name
177- FROM (VALUES %s) AS v(id, name)
178- WHERE el.id = v.id
179- """ ,
180- [(k , "/" .join (v )) for k , v in fixed_lines_paths .items ()],
181- )
182-
183-
184- def remove_ir_exports_lines (cr , models = None , fields = None ):
185- """
186- Delete ir.exports.line records that reference models or fields that are/will be removed.
187-
188- Only one of ``models`` or ``fields`` arguments should be provided.
189-
190- :param list[str] models: a list of model names to match in exports
191- :param list[(str, str)] fields: a list of (model, field) tuples to match in exports
192-
193- :meta private: exclude from online docs
194- """
195- matching_exports = _get_resolved_ir_exports (cr , models = models , fields = fields )
196- if not matching_exports :
197- return
198- _logger .debug ("Deleting %d export template lines with removed models/fields" , len (matching_exports ))
199- cr .execute ("DELETE FROM ir_exports_line WHERE id IN %s" , [tuple (matching_exports .keys ())])
200-
201-
20288def ensure_m2o_func_field_data (cr , src_table , column , dst_table ):
20389 """
20490 Fix broken m2o relations.
@@ -323,7 +209,7 @@ def clean_context(context):
323209 )
324210
325211 # ir.exports.line
326- remove_ir_exports_lines (cr , fields = [( model , fieldname )] )
212+ _remove_export_lines (cr , model , fieldname )
327213
328214 def adapter (leaf , is_or , negated ):
329215 # replace by TRUE_LEAF, unless negated or in a OR operation but not negated
@@ -1195,7 +1081,31 @@ def _update_field_usage_multi(cr, models, old, new, domain_adapter=None, skip_in
11951081
11961082 # ir.exports.line
11971083 if only_models :
1198- rename_ir_exports_fields (cr , {model : {old : new } for model in only_models })
1084+ cr .execute (
1085+ """
1086+ SELECT el.id,
1087+ e.resource,
1088+ STRING_TO_ARRAY(el.name, '/')
1089+ FROM ir_exports_line el
1090+ JOIN ir_exports e
1091+ ON el.export_id = e.id
1092+ WHERE el.name ~ %s
1093+ """ ,
1094+ [r"\y{}\y" .format (old )],
1095+ )
1096+ fixed_lines_paths = {}
1097+ for line_id , line_model , line_path in cr .fetchall ():
1098+ new_path = [
1099+ new if x .field_name == old and x .field_model in only_models else x .field_name
1100+ for x in resolve_model_fields_path (cr , line_model , line_path )
1101+ ]
1102+ if len (new_path ) == len (line_path ) and new_path != line_path :
1103+ fixed_lines_paths [line_id ] = "/" .join (new_path )
1104+ if fixed_lines_paths :
1105+ cr .execute (
1106+ "UPDATE ir_exports_line SET name = (%s::jsonb)->>(id::text) WHERE id IN %s" ,
1107+ [Json (fixed_lines_paths ), tuple (fixed_lines_paths )],
1108+ )
11991109
12001110 # mail.alias
12011111 if column_exists (cr , "mail_alias" , "alias_defaults" ):
0 commit comments