|
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