|
1 | 1 | # -*- coding: utf-8 -*-
|
2 | 2 | import logging
|
3 | 3 | import os
|
| 4 | +from collections import namedtuple |
4 | 5 |
|
5 | 6 | import lxml
|
6 | 7 |
|
@@ -214,3 +215,103 @@ def _get_theme_models():
|
214 | 215 | "theme.website.menu": "website.menu",
|
215 | 216 | "theme.ir.attachment": "ir.attachment",
|
216 | 217 | }
|
| 218 | + |
| 219 | + |
| 220 | +FieldsPathPart = namedtuple("FieldsPathPart", "field_model field_name relation_model") |
| 221 | +FieldsPathPart.__doc__ = """ |
| 222 | +Encapsulate information about a field within a fields path. |
| 223 | +
|
| 224 | +:param str field_model: model of the field |
| 225 | +:param str field_name: name of the field |
| 226 | +:param str relation_model: target model of the field, if relational, otherwise ``None`` |
| 227 | +""" |
| 228 | +for _f in FieldsPathPart._fields: |
| 229 | + getattr(FieldsPathPart, _f).__doc__ = None |
| 230 | + |
| 231 | + |
| 232 | +def resolve_model_fields_path(cr, model, path): |
| 233 | + """ |
| 234 | + Resolve model fields paths. |
| 235 | +
|
| 236 | + This function returns a list of :class:`~odoo.upgrade.util.helpers.FieldsPathPart` |
| 237 | + where each item describes a field in ``path`` (in the same order). The returned list |
| 238 | + could be shorter than the original ``path`` due to a missing field or model, or |
| 239 | + because there is a non-relational field in the path. The only non-relational field |
| 240 | + allowed in a fields path is the last one, in which case the returned list has the same |
| 241 | + length as the input ``path``. |
| 242 | +
|
| 243 | + .. example:: |
| 244 | +
|
| 245 | + .. code-block:: python |
| 246 | +
|
| 247 | + >>> util.resolve_model_fields_path(cr, "res.partner", "user_ids.partner_id.title".split(".")) |
| 248 | + [FieldsPathPart(field_model='res.partner', field_name='user_ids', relation_model='res.users'), |
| 249 | + FieldsPathPart(field_model='res.users', field_name='partner_id', relation_model='res.partner'), |
| 250 | + FieldsPathPart(field_model='res.partner', field_name='title', relation_model='res.partner.title')] |
| 251 | +
|
| 252 | + Last field is not relational: |
| 253 | +
|
| 254 | + .. code-block:: python |
| 255 | +
|
| 256 | + >>> resolve_model_fields_path(cr, "res.partner", "user_ids.active".split(".")) |
| 257 | + [FieldsPathPart(field_model='res.partner', field_name='user_ids', relation_model='res.users'), |
| 258 | + FieldsPathPart(field_model='res.users', field_name='active', relation_model=None)] |
| 259 | +
|
| 260 | + The path is wrong, it uses a non-relational field: |
| 261 | +
|
| 262 | + .. code-block:: python |
| 263 | +
|
| 264 | + >>> resolve_model_fields_path(cr, "res.partner", "user_ids.active.name".split(".")) |
| 265 | + [FieldsPathPart(field_model='res.partner', field_name='user_ids', relation_model='res.users'), |
| 266 | + FieldsPathPart(field_model='res.users', field_name='active', relation_model=None)] |
| 267 | +
|
| 268 | + The path is broken, it uses a non-existing field: |
| 269 | +
|
| 270 | + .. code-block:: python |
| 271 | +
|
| 272 | + >>> resolve_model_fields_path(cr, "res.partner", "user_ids.non_existing_id.active".split(".")) |
| 273 | + [FieldsPathPart(field_model='res.partner', field_name='user_ids', relation_model='res.users')] |
| 274 | +
|
| 275 | + :param str model: starting model of the fields path |
| 276 | + :param typing.Sequence[str] path: fields path |
| 277 | + :return: resolved fields path parts |
| 278 | + :rtype: list(:class:`~odoo.upgrade.util.helpers.FieldsPathPart`) |
| 279 | + """ |
| 280 | + path = list(path) |
| 281 | + cr.execute( |
| 282 | + """ |
| 283 | + WITH RECURSIVE resolved_fields_path AS ( |
| 284 | + -- non-recursive term |
| 285 | + SELECT imf.model AS field_model, |
| 286 | + imf.name AS field_name, |
| 287 | + imf.relation AS relation_model, |
| 288 | + p.path AS path, |
| 289 | + 1 AS part_index |
| 290 | + FROM (VALUES (%(model)s, %(path)s)) p(model, path) |
| 291 | + JOIN ir_model_fields imf |
| 292 | + ON imf.model = p.model |
| 293 | + AND imf.name = p.path[1] |
| 294 | +
|
| 295 | + UNION ALL |
| 296 | +
|
| 297 | + -- recursive term |
| 298 | + SELECT rimf.model AS field_model, |
| 299 | + rimf.name AS field_name, |
| 300 | + rimf.relation AS relation_model, |
| 301 | + rfp.path AS path, |
| 302 | + rfp.part_index + 1 AS part_index |
| 303 | + FROM resolved_fields_path rfp |
| 304 | + JOIN ir_model_fields rimf |
| 305 | + ON rimf.model = rfp.relation_model |
| 306 | + AND rimf.name = rfp.path[rfp.part_index + 1] |
| 307 | + WHERE cardinality(rfp.path) > rfp.part_index |
| 308 | + ) |
| 309 | + SELECT field_model, |
| 310 | + field_name, |
| 311 | + relation_model |
| 312 | + FROM resolved_fields_path |
| 313 | + ORDER BY part_index |
| 314 | + """, |
| 315 | + {"model": model, "path": list(path)}, |
| 316 | + ) |
| 317 | + return [FieldsPathPart(**row) for row in cr.dictfetchall()] |
0 commit comments