update
This commit is contained in:
parent
659ee738ea
commit
dd595904a2
2
Makefile
2
Makefile
@ -87,7 +87,7 @@ cleanup_addons:
|
||||
install_modules:
|
||||
${PYTHON} odoo-bin --config=${CONFIG} -d ${DATABASE} -i ${MODULES} --xmlrpc-port=${PORT}
|
||||
upgrade_modules:
|
||||
${PYTHON} odoo-bin upgrade_code --addons-path ${UPGRADE_DIR} --from ${OLD_VER} --to ${NEW_VER}
|
||||
${PYTHON} odoo-bin upgrade_code --addons-path=${UPGRADE_DIR} --from ${OLD_VER} --to ${NEW_VER} --dry-run
|
||||
|
||||
##### Docker Deployment #########
|
||||
run_test_docker:
|
||||
|
172
odoo/upgrade_code/17.5-02-replace-attrs.py
Normal file
172
odoo/upgrade_code/17.5-02-replace-attrs.py
Normal file
@ -0,0 +1,172 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
from bs4 import BeautifulSoup as bs
|
||||
from ast import literal_eval
|
||||
import logging
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Constants
|
||||
NEW_ATTRS = {'required', 'invisible', 'readonly', 'column_invisible'}
|
||||
|
||||
def upgrade(file_manager):
|
||||
"""Upgrade XML files by converting 'attrs' and 'states' into new attributes."""
|
||||
# Filter files to only XML files
|
||||
files = [file for file in file_manager if file.path.suffix == '.xml']
|
||||
if not files:
|
||||
return
|
||||
|
||||
# Regex patterns with re.VERBOSE for readability
|
||||
percent_d_re = re.compile(r"""
|
||||
% # Start with percent
|
||||
\( # Opening parenthesis
|
||||
'? # Optional single quote
|
||||
"? # Optional double quote
|
||||
(?P<key>[\w\.\d_]+) # Capture key (word chars, dots, digits, underscores)
|
||||
'? # Optional single quote
|
||||
"? # Optional double quote
|
||||
\) # Closing parenthesis
|
||||
d # Ends with 'd'
|
||||
""", re.VERBOSE)
|
||||
|
||||
# Helper functions
|
||||
def normalize_domain(domain):
|
||||
if len(domain) == 1:
|
||||
return domain
|
||||
result = []
|
||||
expected = 1
|
||||
op_arity = {'!': 1, '&': 2, '|': 2}
|
||||
for token in domain:
|
||||
if expected == 0:
|
||||
result[0:0] = ['&']
|
||||
expected = 1
|
||||
if isinstance(token, (list, tuple)):
|
||||
expected -= 1
|
||||
token = tuple(token)
|
||||
else:
|
||||
expected += op_arity.get(token, 0) - 1
|
||||
result.append(token)
|
||||
return result
|
||||
|
||||
def stringify_leaf(leaf):
|
||||
operator = str(leaf[1])
|
||||
if operator == '=':
|
||||
operator = '=='
|
||||
elif 'like' in operator:
|
||||
operator = 'not in' if 'not' in operator else 'in'
|
||||
return f"{leaf[2] if isinstance(leaf[2], str) else str(leaf[2])} {operator} {leaf[0]}"
|
||||
right = str(leaf[2]) if isinstance(leaf[2], (list, tuple, set, int, float, bool)) or leaf[2] in ('True', 'False', '1', '0') else f"'{leaf[2]}'"
|
||||
return f"{leaf[0]} {operator} {right}"
|
||||
|
||||
def stringify_attr(stack):
|
||||
if stack in (True, False, 'True', 'False', 1, 0, '1', '0'):
|
||||
return str(stack)
|
||||
last_paren_idx = max(i for i, item in enumerate(stack[::-1]) if item not in ('|', '!'))
|
||||
stack = normalize_domain(stack)[::-1]
|
||||
result = []
|
||||
for idx, item in enumerate(stack):
|
||||
if item == '!':
|
||||
expr = result.pop()
|
||||
result.append(f"(not ({expr}))")
|
||||
elif item in ('&', '|'):
|
||||
try:
|
||||
left, right = result.pop(), result.pop()
|
||||
op = 'and' if item == '&' else 'or'
|
||||
form = f"({left} {op} {right})" if idx > last_paren_idx else f"{left} {op} {right}"
|
||||
result.append(form)
|
||||
except IndexError:
|
||||
result.append(f"{left} {op}")
|
||||
else:
|
||||
result.append(stringify_leaf(item))
|
||||
return result[0]
|
||||
|
||||
def get_new_attrs(attrs):
|
||||
try:
|
||||
# Use ast.literal_eval for safe evaluation of Python literals
|
||||
attrs_dict = literal_eval(attrs.strip())
|
||||
if not isinstance(attrs_dict, dict):
|
||||
logger.warning(f"Invalid attrs format, expected dict, got {attrs_dict}")
|
||||
return {}
|
||||
return {attr: stringify_attr(attrs_dict[attr]) for attr in NEW_ATTRS if attr in attrs_dict}
|
||||
except (ValueError, SyntaxError) as e:
|
||||
logger.error(f"Failed to parse attrs '{attrs}': {e}")
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error parsing attrs '{attrs}': {e}")
|
||||
return {}
|
||||
|
||||
# Process each file
|
||||
for fileno, file in enumerate(files, start=1):
|
||||
content = file.content
|
||||
|
||||
# Handle percent-d replacements
|
||||
percent_d_results = {}
|
||||
for i, match in enumerate(percent_d_re.findall(content), 1):
|
||||
placeholder = f"'REPLACEME{i}'"
|
||||
percent_d_results[i] = match[0] # Full match
|
||||
content = content.replace(match[0], placeholder)
|
||||
|
||||
# Parse XML with BeautifulSoup
|
||||
soup = bs(content, 'xml')
|
||||
tags_with_attrs = soup.select('[attrs]')
|
||||
attr_tags = soup.select('attribute[name="attrs"]')
|
||||
tags_with_states = soup.select('[states]')
|
||||
state_tags = soup.select('attribute[name="states"]')
|
||||
|
||||
if not (tags_with_attrs or attr_tags or tags_with_states or state_tags):
|
||||
continue
|
||||
|
||||
# Process tags with attrs
|
||||
for tag in tags_with_attrs:
|
||||
new_attrs = get_new_attrs(tag['attrs'])
|
||||
if new_attrs: # Only proceed if parsing succeeded
|
||||
del tag['attrs']
|
||||
for attr, value in new_attrs.items():
|
||||
tag[attr] = value
|
||||
else:
|
||||
logger.warning(f"Skipping tag in {file.path} due to invalid attrs: {tag.get('attrs')}")
|
||||
|
||||
# Process <attribute name="attrs">
|
||||
for attr_tag in attr_tags:
|
||||
new_attrs = get_new_attrs(attr_tag.text)
|
||||
if new_attrs:
|
||||
for attr, value in new_attrs.items():
|
||||
new_tag = soup.new_tag('attribute', name=attr)
|
||||
new_tag.string = value
|
||||
attr_tag.insert_after(new_tag)
|
||||
attr_tag.decompose()
|
||||
else:
|
||||
logger.warning(f"Skipping attribute tag in {file.path} due to invalid attrs: {attr_tag.text}")
|
||||
|
||||
# Process tags with states
|
||||
for tag in tags_with_states:
|
||||
base_invisible = tag.get('invisible', '')
|
||||
if base_invisible and not base_invisible.endswith(('or', 'and')):
|
||||
base_invisible += ' or '
|
||||
states = [f"'{s.strip()}'" for s in tag['states'].split(',')]
|
||||
tag['invisible'] = f"{base_invisible}state not in [{','.join(states)}]"
|
||||
del tag['states']
|
||||
|
||||
# Process <attribute name="states">
|
||||
for state_tag in state_tags:
|
||||
states = [f"'{s.strip()}'" for s in state_tag.text.split(',')]
|
||||
parent = state_tag.parent
|
||||
inv_tag = next((t for t in parent.findAll('attribute') if t['name'] == 'invisible'), None)
|
||||
if not inv_tag:
|
||||
inv_tag = soup.new_tag('attribute', name='invisible')
|
||||
current = inv_tag.text or ''
|
||||
new_text = f"state not in [{','.join(states)}]"
|
||||
inv_tag.string = f"{current} or {new_text}" if current else new_text
|
||||
state_tag.insert_after(inv_tag)
|
||||
state_tag.decompose()
|
||||
|
||||
# Write back with percent-d restored
|
||||
output = str(soup)
|
||||
for i, original in percent_d_results.items():
|
||||
output = output.replace(f"'REPLACEME{i}'", original)
|
||||
|
||||
file.content = output
|
||||
file_manager.print_progress(fileno, len(files))
|
@ -228,7 +228,7 @@ print('################## Run Debug ##################')
|
||||
print('################################################')
|
||||
|
||||
if nofilesfound:
|
||||
print('No XML Files with "attrs" or "states" found in dir " %s "' % root_dir)
|
||||
print('No XML Files with "attrs" or "states" found in dir "%s"' % root_dir)
|
||||
|
||||
print('Succeeded on files')
|
||||
for file in ok_files:
|
||||
|
Loading…
Reference in New Issue
Block a user