|
2 | 2 | import logging |
3 | 3 | import os |
4 | 4 | import re |
5 | | -from contextlib import contextmanager |
6 | 5 | from operator import itemgetter |
7 | 6 |
|
8 | 7 | import lxml |
|
12 | 11 | from odoo import release |
13 | 12 | from odoo.tools.convert import xml_import |
14 | 13 | from odoo.tools.misc import file_open |
15 | | - from odoo.tools.translate import xml_translate |
16 | 14 | except ImportError: |
17 | 15 | from openerp import release |
18 | 16 | from openerp.tools.convert import xml_import |
|
42 | 40 | table_exists, |
43 | 41 | target_of, |
44 | 42 | ) |
45 | | -from .report import add_to_migration_reports |
| 43 | +from .views.records import add_view, edit_view, remove_view # noqa: F401 |
46 | 44 |
|
47 | 45 | _logger = logging.getLogger(__name__) |
48 | 46 |
|
|
53 | 51 | basestring = unicode = str |
54 | 52 |
|
55 | 53 |
|
56 | | -def remove_view(cr, xml_id=None, view_id=None, silent=False, key=None): |
57 | | - """ |
58 | | - Recursively delete the given view and its inherited views, as long as they |
59 | | - are part of a module. Will crash as soon as a custom view exists anywhere |
60 | | - in the hierarchy. |
61 | | -
|
62 | | - Also handle multi-website COWed views. |
63 | | - """ |
64 | | - assert bool(xml_id) ^ bool(view_id) |
65 | | - if xml_id: |
66 | | - view_id = ref(cr, xml_id) |
67 | | - if view_id: |
68 | | - module, _, name = xml_id.partition(".") |
69 | | - cr.execute("SELECT model FROM ir_model_data WHERE module=%s AND name=%s", [module, name]) |
70 | | - |
71 | | - [model] = cr.fetchone() |
72 | | - if model != "ir.ui.view": |
73 | | - raise ValueError("%r should point to a 'ir.ui.view', not a %r" % (xml_id, model)) |
74 | | - else: |
75 | | - # search matching xmlid for logging or renaming of custom views |
76 | | - xml_id = "?" |
77 | | - if not key: |
78 | | - cr.execute("SELECT module, name FROM ir_model_data WHERE model='ir.ui.view' AND res_id=%s", [view_id]) |
79 | | - if cr.rowcount: |
80 | | - xml_id = "%s.%s" % cr.fetchone() |
81 | | - |
82 | | - # From given or determined xml_id, the views duplicated in a multi-website |
83 | | - # context are to be found and removed. |
84 | | - if xml_id != "?" and column_exists(cr, "ir_ui_view", "key"): |
85 | | - cr.execute("SELECT id FROM ir_ui_view WHERE key = %s AND id != %s", [xml_id, view_id]) |
86 | | - for [v_id] in cr.fetchall(): |
87 | | - remove_view(cr, view_id=v_id, silent=silent, key=xml_id) |
88 | | - |
89 | | - if not view_id: |
90 | | - return |
91 | | - |
92 | | - cr.execute( |
93 | | - """ |
94 | | - SELECT v.id, x.module || '.' || x.name, v.name |
95 | | - FROM ir_ui_view v LEFT JOIN |
96 | | - ir_model_data x ON (v.id = x.res_id AND x.model = 'ir.ui.view' AND x.module !~ '^_') |
97 | | - WHERE v.inherit_id = %s; |
98 | | - """, |
99 | | - [view_id], |
100 | | - ) |
101 | | - for child_id, child_xml_id, child_name in cr.fetchall(): |
102 | | - if child_xml_id: |
103 | | - if not silent: |
104 | | - _logger.info( |
105 | | - "remove deprecated built-in view %s (ID %s) as parent view %s (ID %s) is going to be removed", |
106 | | - child_xml_id, |
107 | | - child_id, |
108 | | - xml_id, |
109 | | - view_id, |
110 | | - ) |
111 | | - remove_view(cr, child_xml_id, silent=True) |
112 | | - else: |
113 | | - if not silent: |
114 | | - _logger.warning( |
115 | | - "deactivate deprecated custom view with ID %s as parent view %s (ID %s) is going to be removed", |
116 | | - child_id, |
117 | | - xml_id, |
118 | | - view_id, |
119 | | - ) |
120 | | - disable_view_query = """ |
121 | | - UPDATE ir_ui_view |
122 | | - SET name = (name || ' - old view, inherited from ' || %%s), |
123 | | - inherit_id = NULL |
124 | | - %s |
125 | | - WHERE id = %%s |
126 | | - """ |
127 | | - # In 8.0, disabling requires setting mode to 'primary' |
128 | | - extra_set_sql = "" |
129 | | - if column_exists(cr, "ir_ui_view", "mode"): |
130 | | - extra_set_sql = ", mode = 'primary' " |
131 | | - |
132 | | - # Column was not present in v7 and it's older version |
133 | | - if column_exists(cr, "ir_ui_view", "active"): |
134 | | - extra_set_sql += ", active = false " |
135 | | - |
136 | | - disable_view_query = disable_view_query % extra_set_sql |
137 | | - cr.execute(disable_view_query, (key or xml_id, child_id)) |
138 | | - add_to_migration_reports( |
139 | | - {"id": child_id, "name": child_name}, |
140 | | - "Disabled views", |
141 | | - ) |
142 | | - if not silent: |
143 | | - _logger.info("remove deprecated %s view %s (ID %s)", key and "COWed" or "built-in", key or xml_id, view_id) |
144 | | - |
145 | | - remove_records(cr, "ir.ui.view", [view_id]) |
146 | | - |
147 | | - |
148 | | -@contextmanager |
149 | | -def edit_view(cr, xmlid=None, view_id=None, skip_if_not_noupdate=True, active=True): |
150 | | - """Contextmanager that may yield etree arch of a view. |
151 | | - As it may not yield, you must use `skippable_cm` |
152 | | -
|
153 | | - with util.skippable_cm(), util.edit_view(cr, 'xml.id') as arch: |
154 | | - arch.attrib['string'] = 'My Form' |
155 | | -
|
156 | | - When view_id is passed to identify a view, view's arch will always yield to be edited because |
157 | | - we assume that xmlid for such view does not exist to check its noupdate flag. |
158 | | -
|
159 | | - If view's noupdate=false then the arch will not be yielded for edit unless skip_if_not_noupdate=False, |
160 | | - because when noupdate=False we assume it is a standard view that will be updated by the ORM later on anyways. |
161 | | -
|
162 | | - If view's noupdate=True, the view will be yielded for edit. |
163 | | -
|
164 | | - If the `active` argument is not None, the view will be (de)activated accordingly. |
165 | | -
|
166 | | - For more details, see discussion in: https://github.com/odoo/upgrade-specific/pull/4216 |
167 | | - """ |
168 | | - assert bool(xmlid) ^ bool(view_id), "You Must specify either xmlid or view_id" |
169 | | - noupdate = True |
170 | | - if xmlid: |
171 | | - if "." not in xmlid: |
172 | | - raise ValueError("Please use fully qualified name <module>.<name>") |
173 | | - |
174 | | - module, _, name = xmlid.partition(".") |
175 | | - cr.execute( |
176 | | - """ |
177 | | - SELECT res_id, noupdate |
178 | | - FROM ir_model_data |
179 | | - WHERE module = %s |
180 | | - AND name = %s |
181 | | - """, |
182 | | - [module, name], |
183 | | - ) |
184 | | - data = cr.fetchone() |
185 | | - if data: |
186 | | - view_id, noupdate = data |
187 | | - |
188 | | - if view_id and not (skip_if_not_noupdate and not noupdate): |
189 | | - arch_col = "arch_db" if column_exists(cr, "ir_ui_view", "arch_db") else "arch" |
190 | | - jsonb_column = column_type(cr, "ir_ui_view", arch_col) == "jsonb" |
191 | | - cr.execute( |
192 | | - """ |
193 | | - SELECT {arch} |
194 | | - FROM ir_ui_view |
195 | | - WHERE id=%s |
196 | | - """.format( |
197 | | - arch=arch_col, |
198 | | - ), |
199 | | - [view_id], |
200 | | - ) |
201 | | - [arch] = cr.fetchone() or [None] |
202 | | - if arch: |
203 | | - |
204 | | - def parse(arch): |
205 | | - arch = arch.encode("utf-8") if isinstance(arch, unicode) else arch |
206 | | - return lxml.etree.fromstring(arch.replace(b" \n", b"\n").strip()) |
207 | | - |
208 | | - if jsonb_column: |
209 | | - |
210 | | - def get_trans_terms(value): |
211 | | - terms = [] |
212 | | - xml_translate(terms.append, value) |
213 | | - return terms |
214 | | - |
215 | | - translation_terms = {lang: get_trans_terms(value) for lang, value in arch.items()} |
216 | | - arch_etree = parse(arch["en_US"]) |
217 | | - yield arch_etree |
218 | | - new_arch = lxml.etree.tostring(arch_etree, encoding="unicode") |
219 | | - terms_en = translation_terms["en_US"] |
220 | | - arch_column_value = Json( |
221 | | - { |
222 | | - lang: xml_translate(dict(zip(terms_en, terms)).get, new_arch) |
223 | | - for lang, terms in translation_terms.items() |
224 | | - } |
225 | | - ) |
226 | | - else: |
227 | | - arch_etree = parse(arch) |
228 | | - yield arch_etree |
229 | | - arch_column_value = lxml.etree.tostring(arch_etree, encoding="unicode") |
230 | | - |
231 | | - set_active = ", active={}".format(bool(active)) if active is not None else "" |
232 | | - cr.execute( |
233 | | - "UPDATE ir_ui_view SET {arch}=%s{set_active} WHERE id=%s".format(arch=arch_col, set_active=set_active), |
234 | | - [arch_column_value, view_id], |
235 | | - ) |
236 | | - |
237 | | - |
238 | | -def add_view(cr, name, model, view_type, arch_db, inherit_xml_id=None, priority=16): |
239 | | - inherit_id = None |
240 | | - if inherit_xml_id: |
241 | | - inherit_id = ref(cr, inherit_xml_id) |
242 | | - if not inherit_id: |
243 | | - raise ValueError( |
244 | | - "Unable to add view '%s' because its inherited view '%s' cannot be found!" % (name, inherit_xml_id) |
245 | | - ) |
246 | | - arch_col = "arch_db" if column_exists(cr, "ir_ui_view", "arch_db") else "arch" |
247 | | - jsonb_column = column_type(cr, "ir_ui_view", arch_col) == "jsonb" |
248 | | - arch_column_value = Json({"en_US": arch_db}) if jsonb_column else arch_db |
249 | | - cr.execute( |
250 | | - """ |
251 | | - INSERT INTO ir_ui_view(name, "type", model, inherit_id, mode, active, priority, %s) |
252 | | - VALUES(%%(name)s, %%(view_type)s, %%(model)s, %%(inherit_id)s, %%(mode)s, 't', %%(priority)s, %%(arch_db)s) |
253 | | - RETURNING id |
254 | | - """ |
255 | | - % arch_col, |
256 | | - { |
257 | | - "name": name, |
258 | | - "view_type": view_type, |
259 | | - "model": model, |
260 | | - "inherit_id": inherit_id, |
261 | | - "mode": "extension" if inherit_id else "primary", |
262 | | - "priority": priority, |
263 | | - "arch_db": arch_column_value, |
264 | | - }, |
265 | | - ) |
266 | | - return cr.fetchone()[0] |
267 | | - |
268 | | - |
269 | 54 | # fmt:off |
270 | 55 | if version_gte("saas~14.3"): |
271 | 56 | def remove_asset(cr, name): |
|
0 commit comments