1313import logging
1414import re
1515import warnings
16- from collections import namedtuple
1716
1817import psycopg2
1918from psycopg2 import sql
@@ -41,7 +40,7 @@ def make_index_name(table_name, column_name):
4140from .const import ENVIRON
4241from .domains import _adapt_one_domain , _replace_path , _valid_path_to , adapt_domains
4342from .exceptions import SleepyDeveloperError
44- from .helpers import _dashboard_actions , _validate_model , table_of_model
43+ from .helpers import _dashboard_actions , _resolve_model_fields_path , _validate_model , table_of_model
4544from .inherit import for_each_inherit
4645from .misc import SelfPrintEvalContext , log_progress , version_gte
4746from .orm import env , invalidate
@@ -80,13 +79,7 @@ def make_index_name(table_name, column_name):
8079)
8180
8281
83- ResolvedExportsLine = namedtuple (
84- "ResolvedExportsLine" ,
85- "export_id export_name export_model line_id path_parts part_index field_name field_model field_id relation_model" ,
86- )
87-
88-
89- def get_resolved_ir_exports (cr , models = None , fields = None , only_missing = False ):
82+ def _get_resolved_ir_exports (cr , models = None , fields = None ):
9083 """
9184 Return a list of ir.exports.line records which models or fields match the given arguments.
9285
@@ -97,72 +90,56 @@ def get_resolved_ir_exports(cr, models=None, fields=None, only_missing=False):
9790
9891 :param list[str] models: a list of model names to match in exports
9992 :param list[(str, str)] fields: a list of (model, field) tuples to match in exports
100- :param bool only_missing: include only lines which contain missing models/fields
101- :return: the matched resolved exports lines
102- :rtype: list[ResolvedExportsLine]
93+ :return: the resolved field paths parts for each matched export line id
94+ :rtype: dict[int, list[FieldsPathPart]]
10395
10496 :meta private: exclude from online docs
10597 """
10698 assert bool (models ) ^ bool (fields ), "One of models or fields must be given, and not both."
107- extra_where = ""
108- query_params = {}
109- if models :
110- extra_where += " AND field_model = ANY(%(models)s)"
111- query_params ["models" ] = list (models )
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 = {}
112105 if fields :
113- extra_where += " AND (field_model, field_name) IN %(fields)s"
114- query_params ["fields" ] = tuple ((model , field ) for model , field in fields )
115- if only_missing :
116- extra_where += " AND field_id IS NULL"
117- # Resolve exports using a recursive CTE query
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 ]
118109 cr .execute (
119110 """
120- WITH RECURSIVE resolved_exports_fields AS (
121- -- non-recursive term
122- SELECT e.id AS export_id,
123- e.name AS export_name,
124- e.resource AS export_model,
125- el.id AS line_id,
126- string_to_array(el.name, '/') AS path_parts,
127- 1 AS part_index,
128- replace((string_to_array(el.name, '/'))[1], '.id', 'id') AS field_name,
129- e.resource AS field_model,
130- elf.id AS field_id,
131- elf.relation AS relation_model
132- FROM ir_exports_line el
133- JOIN ir_exports e
134- ON el.export_id = e.id
135- LEFT JOIN ir_model_fields elf
136- ON elf.model = e.resource AND elf.name = (string_to_array(el.name, '/'))[1]
137- LEFT JOIN ir_model em
138- ON em.model = e.resource
139-
140- UNION ALL
141-
142- -- recursive term
143- SELECT ref.export_id,
144- ref.export_name,
145- ref.export_model,
146- ref.line_id,
147- ref.path_parts,
148- ref.part_index + 1 AS part_index,
149- replace(ref.path_parts[ref.part_index + 1], '.id', 'id') AS field_name,
150- ref.relation_model AS field_model,
151- refmf.id AS field_id,
152- refmf.relation AS relation_model
153- FROM resolved_exports_fields ref
154- LEFT JOIN ir_model_fields refmf
155- ON refmf.model = ref.relation_model AND refmf.name = ref.path_parts[ref.part_index + 1]
156- WHERE cardinality(ref.path_parts) > ref.part_index AND ref.relation_model IS NOT NULL
157- )
158- SELECT *
159- FROM resolved_exports_fields
160- WHERE field_name != 'id' {extra_where}
161- ORDER BY export_id, line_id, part_index
162- """ .format (extra_where = extra_where ),
163- query_params ,
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 ,
164117 )
165- return [ResolvedExportsLine (** row ) for row in cr .dictfetchall ()]
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
166143
167144
168145def rename_ir_exports_fields (cr , models_fields_map ):
@@ -174,23 +151,24 @@ def rename_ir_exports_fields(cr, models_fields_map):
174151
175152 :meta private: exclude from online docs
176153 """
177- matching_exports = get_resolved_ir_exports (
154+ matching_exports = _get_resolved_ir_exports (
178155 cr ,
179156 fields = [(model , field ) for model , fields_map in models_fields_map .items () for field in fields_map ],
180157 )
181158 if not matching_exports :
182159 return
183160 _logger .debug ("Renaming %d export template lines with renamed fields" , len (matching_exports ))
184161 fixed_lines_paths = {}
185- for row in matching_exports :
186- assert row .field_model in models_fields_map
187- fields_map = models_fields_map [row .field_model ]
188- assert row .field_name in fields_map
189- assert row .path_parts [row .part_index - 1 ] == row .field_name
190- new_field_name = fields_map [row .field_name ]
191- fixed_path = list (row .path_parts )
192- fixed_path [row .part_index - 1 ] = new_field_name
193- fixed_lines_paths [row .line_id ] = fixed_path
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
194172 execute_values (
195173 cr ,
196174 """
@@ -214,12 +192,11 @@ def remove_ir_exports_lines(cr, models=None, fields=None):
214192
215193 :meta private: exclude from online docs
216194 """
217- matching_exports = get_resolved_ir_exports (cr , models = models , fields = fields )
195+ matching_exports = _get_resolved_ir_exports (cr , models = models , fields = fields )
218196 if not matching_exports :
219197 return
220- lines_ids = {row .line_id for row in matching_exports }
221- _logger .debug ("Deleting %d export template lines with removed models/fields" , len (lines_ids ))
222- cr .execute ("DELETE FROM ir_exports_line WHERE id IN %s" , [tuple (lines_ids )])
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 ())])
223200
224201
225202def ensure_m2o_func_field_data (cr , src_table , column , dst_table ):
0 commit comments