13
13
import logging
14
14
import re
15
15
import warnings
16
- from collections import namedtuple
17
16
18
17
import psycopg2
19
18
from psycopg2 import sql
@@ -41,7 +40,7 @@ def make_index_name(table_name, column_name):
41
40
from .const import ENVIRON
42
41
from .domains import _adapt_one_domain , _replace_path , _valid_path_to , adapt_domains
43
42
from .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
45
44
from .inherit import for_each_inherit
46
45
from .misc import SelfPrintEvalContext , log_progress , version_gte
47
46
from .orm import env , invalidate
@@ -80,13 +79,7 @@ def make_index_name(table_name, column_name):
80
79
)
81
80
82
81
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 ):
90
83
"""
91
84
Return a list of ir.exports.line records which models or fields match the given arguments.
92
85
@@ -97,72 +90,56 @@ def get_resolved_ir_exports(cr, models=None, fields=None, only_missing=False):
97
90
98
91
:param list[str] models: a list of model names to match in exports
99
92
: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]]
103
95
104
96
:meta private: exclude from online docs
105
97
"""
106
98
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 = {}
112
105
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 ]
118
109
cr .execute (
119
110
"""
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 ,
164
117
)
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
166
143
167
144
168
145
def rename_ir_exports_fields (cr , models_fields_map ):
@@ -174,23 +151,24 @@ def rename_ir_exports_fields(cr, models_fields_map):
174
151
175
152
:meta private: exclude from online docs
176
153
"""
177
- matching_exports = get_resolved_ir_exports (
154
+ matching_exports = _get_resolved_ir_exports (
178
155
cr ,
179
156
fields = [(model , field ) for model , fields_map in models_fields_map .items () for field in fields_map ],
180
157
)
181
158
if not matching_exports :
182
159
return
183
160
_logger .debug ("Renaming %d export template lines with renamed fields" , len (matching_exports ))
184
161
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
194
172
execute_values (
195
173
cr ,
196
174
"""
@@ -214,12 +192,11 @@ def remove_ir_exports_lines(cr, models=None, fields=None):
214
192
215
193
:meta private: exclude from online docs
216
194
"""
217
- matching_exports = get_resolved_ir_exports (cr , models = models , fields = fields )
195
+ matching_exports = _get_resolved_ir_exports (cr , models = models , fields = fields )
218
196
if not matching_exports :
219
197
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 ())])
223
200
224
201
225
202
def ensure_m2o_func_field_data (cr , src_table , column , dst_table ):
0 commit comments