|
4 | 4 | import logging
|
5 | 5 | import os
|
6 | 6 | import re
|
7 |
| -from contextlib import contextmanager |
8 | 7 | from operator import itemgetter
|
9 | 8 |
|
10 | 9 | import lxml
|
|
14 | 13 | from odoo import release
|
15 | 14 | from odoo.tools.convert import xml_import
|
16 | 15 | from odoo.tools.misc import file_open
|
17 |
| - from odoo.tools.translate import xml_translate |
18 | 16 | except ImportError:
|
19 | 17 | from openerp import release
|
20 | 18 | from openerp.tools.convert import xml_import
|
|
44 | 42 | table_exists,
|
45 | 43 | target_of,
|
46 | 44 | )
|
47 |
| -from .report import add_to_migration_reports |
| 45 | +from .views.records import add_view, edit_view, remove_view # noqa: F401 |
48 | 46 |
|
49 | 47 | _logger = logging.getLogger(__name__)
|
50 | 48 |
|
|
55 | 53 | basestring = unicode = str
|
56 | 54 |
|
57 | 55 |
|
58 |
| -def remove_view(cr, xml_id=None, view_id=None, silent=False, key=None): |
59 |
| - """ |
60 |
| - Remove a view and all its descendants. |
61 |
| -
|
62 |
| - This function recursively deletes the given view and its inherited views, as long as |
63 |
| - they are part of a module. It will fail as soon as a custom view exists anywhere in |
64 |
| - the hierarchy. It also removes multi-website COWed views. |
65 |
| -
|
66 |
| - :param str xml_id: optional, the xml_id of the view to remove |
67 |
| - :param int view_id: optional, the ID of the view to remove |
68 |
| - :param bool silent: whether to show in the logs disabled custom views |
69 |
| - :param str or None key: key used to detect multi-website COWed views, if `None` then |
70 |
| - set to `xml_id` if provided, otherwise set to the xml_id |
71 |
| - referencing the view with ID `view_id` if any |
72 |
| -
|
73 |
| - .. warning:: |
74 |
| - Either `xml_id` or `view_id` must be set. Specifying both will raise an error. |
75 |
| - """ |
76 |
| - assert bool(xml_id) ^ bool(view_id) |
77 |
| - if xml_id: |
78 |
| - view_id = ref(cr, xml_id) |
79 |
| - if view_id: |
80 |
| - module, _, name = xml_id.partition(".") |
81 |
| - cr.execute("SELECT model FROM ir_model_data WHERE module=%s AND name=%s", [module, name]) |
82 |
| - |
83 |
| - [model] = cr.fetchone() |
84 |
| - if model != "ir.ui.view": |
85 |
| - raise ValueError("%r should point to a 'ir.ui.view', not a %r" % (xml_id, model)) |
86 |
| - else: |
87 |
| - # search matching xmlid for logging or renaming of custom views |
88 |
| - xml_id = "?" |
89 |
| - if not key: |
90 |
| - cr.execute("SELECT module, name FROM ir_model_data WHERE model='ir.ui.view' AND res_id=%s", [view_id]) |
91 |
| - if cr.rowcount: |
92 |
| - xml_id = "%s.%s" % cr.fetchone() |
93 |
| - |
94 |
| - # From given or determined xml_id, the views duplicated in a multi-website |
95 |
| - # context are to be found and removed. |
96 |
| - if xml_id != "?" and column_exists(cr, "ir_ui_view", "key"): |
97 |
| - cr.execute("SELECT id FROM ir_ui_view WHERE key = %s AND id != %s", [xml_id, view_id]) |
98 |
| - for [v_id] in cr.fetchall(): |
99 |
| - remove_view(cr, view_id=v_id, silent=silent, key=xml_id) |
100 |
| - |
101 |
| - if not view_id: |
102 |
| - return |
103 |
| - |
104 |
| - cr.execute( |
105 |
| - """ |
106 |
| - SELECT v.id, x.module || '.' || x.name, v.name |
107 |
| - FROM ir_ui_view v LEFT JOIN |
108 |
| - ir_model_data x ON (v.id = x.res_id AND x.model = 'ir.ui.view' AND x.module !~ '^_') |
109 |
| - WHERE v.inherit_id = %s; |
110 |
| - """, |
111 |
| - [view_id], |
112 |
| - ) |
113 |
| - for child_id, child_xml_id, child_name in cr.fetchall(): |
114 |
| - if child_xml_id: |
115 |
| - if not silent: |
116 |
| - _logger.info( |
117 |
| - "remove deprecated built-in view %s (ID %s) as parent view %s (ID %s) is going to be removed", |
118 |
| - child_xml_id, |
119 |
| - child_id, |
120 |
| - xml_id, |
121 |
| - view_id, |
122 |
| - ) |
123 |
| - remove_view(cr, child_xml_id, silent=True) |
124 |
| - else: |
125 |
| - if not silent: |
126 |
| - _logger.warning( |
127 |
| - "deactivate deprecated custom view with ID %s as parent view %s (ID %s) is going to be removed", |
128 |
| - child_id, |
129 |
| - xml_id, |
130 |
| - view_id, |
131 |
| - ) |
132 |
| - disable_view_query = """ |
133 |
| - UPDATE ir_ui_view |
134 |
| - SET name = (name || ' - old view, inherited from ' || %%s), |
135 |
| - inherit_id = NULL |
136 |
| - %s |
137 |
| - WHERE id = %%s |
138 |
| - """ |
139 |
| - # In 8.0, disabling requires setting mode to 'primary' |
140 |
| - extra_set_sql = "" |
141 |
| - if column_exists(cr, "ir_ui_view", "mode"): |
142 |
| - extra_set_sql = ", mode = 'primary' " |
143 |
| - |
144 |
| - # Column was not present in v7 and it's older version |
145 |
| - if column_exists(cr, "ir_ui_view", "active"): |
146 |
| - extra_set_sql += ", active = false " |
147 |
| - |
148 |
| - disable_view_query = disable_view_query % extra_set_sql |
149 |
| - cr.execute(disable_view_query, (key or xml_id, child_id)) |
150 |
| - add_to_migration_reports( |
151 |
| - {"id": child_id, "name": child_name}, |
152 |
| - "Disabled views", |
153 |
| - ) |
154 |
| - if not silent: |
155 |
| - _logger.info("remove deprecated %s view %s (ID %s)", key and "COWed" or "built-in", key or xml_id, view_id) |
156 |
| - |
157 |
| - remove_records(cr, "ir.ui.view", [view_id]) |
158 |
| - |
159 |
| - |
160 |
| -@contextmanager |
161 |
| -def edit_view(cr, xmlid=None, view_id=None, skip_if_not_noupdate=True, active=True): |
162 |
| - """ |
163 |
| - Context manager to edit a view's arch. |
164 |
| -
|
165 |
| - This function returns a context manager that may yield a parsed arch of a view as an |
166 |
| - `etree Element <https://lxml.de/tutorial.html#the-element-class>`_. Any changes done |
167 |
| - in the returned object will be written back to the database upon exit of the context |
168 |
| - manager, updating also the translated versions of the arch. Since the function may not |
169 |
| - yield, use :func:`~odoo.upgrade.util.misc.skippable_cm` to avoid errors. |
170 |
| -
|
171 |
| - .. code-block:: python |
172 |
| -
|
173 |
| - with util.skippable_cm(), util.edit_view(cr, "xml.id") as arch: |
174 |
| - arch.attrib["string"] = "My Form" |
175 |
| -
|
176 |
| - To select the target view to edit use either `xmlid` or `view_id`, not both. |
177 |
| -
|
178 |
| - When the view is identified by `view_id`, the arch is always yielded if the view |
179 |
| - exists, with disregard to any `noupdate` flag it may have associated. When `xmlid` is |
180 |
| - set, if the view `noupdate` flag is `True` then the arch will not be yielded *unless* |
181 |
| - `skip_if_not_noupdate` is set to `False`. If `noupdate` is `False`, the view will be |
182 |
| - yielded for edit. |
183 |
| -
|
184 |
| - If the `active` argument is not `None`, the `active` flag of the view will be set |
185 |
| - accordingly. |
186 |
| -
|
187 |
| - .. warning:: |
188 |
| - The default value of `active` is `True`, therefore views are always *activated* by |
189 |
| - default. To avoid inadvertently activating views, pass `None` as `active` parameter. |
190 |
| -
|
191 |
| - :param str xmlid: optional, xml_id of the view edit |
192 |
| - :param int view_id: optional, ID of the view to edit |
193 |
| - :param bool skip_if_not_noupdate: whether to force the edit of views requested via |
194 |
| - `xmlid` parameter even if they are flagged as |
195 |
| - `noupdate=True`, ignored if `view_id` is set |
196 |
| - :param bool or None active: active flag value to set, nothing is set when `None` |
197 |
| - :return: a context manager that yields the parsed arch, upon exit the context manager |
198 |
| - writes back the changes. |
199 |
| - """ |
200 |
| - assert bool(xmlid) ^ bool(view_id), "You Must specify either xmlid or view_id" |
201 |
| - noupdate = True |
202 |
| - if xmlid: |
203 |
| - if "." not in xmlid: |
204 |
| - raise ValueError("Please use fully qualified name <module>.<name>") |
205 |
| - |
206 |
| - module, _, name = xmlid.partition(".") |
207 |
| - cr.execute( |
208 |
| - """ |
209 |
| - SELECT res_id, noupdate |
210 |
| - FROM ir_model_data |
211 |
| - WHERE module = %s |
212 |
| - AND name = %s |
213 |
| - """, |
214 |
| - [module, name], |
215 |
| - ) |
216 |
| - data = cr.fetchone() |
217 |
| - if data: |
218 |
| - view_id, noupdate = data |
219 |
| - |
220 |
| - if view_id and not (skip_if_not_noupdate and not noupdate): |
221 |
| - arch_col = "arch_db" if column_exists(cr, "ir_ui_view", "arch_db") else "arch" |
222 |
| - jsonb_column = column_type(cr, "ir_ui_view", arch_col) == "jsonb" |
223 |
| - cr.execute( |
224 |
| - """ |
225 |
| - SELECT {arch} |
226 |
| - FROM ir_ui_view |
227 |
| - WHERE id=%s |
228 |
| - """.format( |
229 |
| - arch=arch_col, |
230 |
| - ), |
231 |
| - [view_id], |
232 |
| - ) |
233 |
| - [arch] = cr.fetchone() or [None] |
234 |
| - if arch: |
235 |
| - |
236 |
| - def parse(arch): |
237 |
| - arch = arch.encode("utf-8") if isinstance(arch, unicode) else arch |
238 |
| - return lxml.etree.fromstring(arch.replace(b" \n", b"\n").strip()) |
239 |
| - |
240 |
| - if jsonb_column: |
241 |
| - |
242 |
| - def get_trans_terms(value): |
243 |
| - terms = [] |
244 |
| - xml_translate(terms.append, value) |
245 |
| - return terms |
246 |
| - |
247 |
| - translation_terms = {lang: get_trans_terms(value) for lang, value in arch.items()} |
248 |
| - arch_etree = parse(arch["en_US"]) |
249 |
| - yield arch_etree |
250 |
| - new_arch = lxml.etree.tostring(arch_etree, encoding="unicode") |
251 |
| - terms_en = translation_terms["en_US"] |
252 |
| - arch_column_value = Json( |
253 |
| - { |
254 |
| - lang: xml_translate(dict(zip(terms_en, terms)).get, new_arch) |
255 |
| - for lang, terms in translation_terms.items() |
256 |
| - } |
257 |
| - ) |
258 |
| - else: |
259 |
| - arch_etree = parse(arch) |
260 |
| - yield arch_etree |
261 |
| - arch_column_value = lxml.etree.tostring(arch_etree, encoding="unicode") |
262 |
| - |
263 |
| - set_active = ", active={}".format(bool(active)) if active is not None else "" |
264 |
| - cr.execute( |
265 |
| - "UPDATE ir_ui_view SET {arch}=%s{set_active} WHERE id=%s".format(arch=arch_col, set_active=set_active), |
266 |
| - [arch_column_value, view_id], |
267 |
| - ) |
268 |
| - |
269 |
| - |
270 |
| -def add_view(cr, name, model, view_type, arch_db, inherit_xml_id=None, priority=16): |
271 |
| - inherit_id = None |
272 |
| - if inherit_xml_id: |
273 |
| - inherit_id = ref(cr, inherit_xml_id) |
274 |
| - if not inherit_id: |
275 |
| - raise ValueError( |
276 |
| - "Unable to add view '%s' because its inherited view '%s' cannot be found!" % (name, inherit_xml_id) |
277 |
| - ) |
278 |
| - arch_col = "arch_db" if column_exists(cr, "ir_ui_view", "arch_db") else "arch" |
279 |
| - jsonb_column = column_type(cr, "ir_ui_view", arch_col) == "jsonb" |
280 |
| - arch_column_value = Json({"en_US": arch_db}) if jsonb_column else arch_db |
281 |
| - cr.execute( |
282 |
| - """ |
283 |
| - INSERT INTO ir_ui_view(name, "type", model, inherit_id, mode, active, priority, %s) |
284 |
| - VALUES(%%(name)s, %%(view_type)s, %%(model)s, %%(inherit_id)s, %%(mode)s, 't', %%(priority)s, %%(arch_db)s) |
285 |
| - RETURNING id |
286 |
| - """ |
287 |
| - % arch_col, |
288 |
| - { |
289 |
| - "name": name, |
290 |
| - "view_type": view_type, |
291 |
| - "model": model, |
292 |
| - "inherit_id": inherit_id, |
293 |
| - "mode": "extension" if inherit_id else "primary", |
294 |
| - "priority": priority, |
295 |
| - "arch_db": arch_column_value, |
296 |
| - }, |
297 |
| - ) |
298 |
| - return cr.fetchone()[0] |
299 |
| - |
300 |
| - |
301 | 56 | # fmt:off
|
302 | 57 | if version_gte("saas~14.3"):
|
303 | 58 | def remove_asset(cr, name):
|
|
0 commit comments