From 743d1ce8317b244a6df9ea28e3e1977017e505e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Sainl=C3=A9ger?= Date: Tue, 13 Jan 2026 12:38:47 +0100 Subject: [PATCH] [IMP] adds check view python scripts at db preparation step --- pre_migration_view_checking.py | 126 +++++++++++++++++++++++++++++++++ prepare_db.sh | 20 ++++++ 2 files changed, 146 insertions(+) create mode 100644 pre_migration_view_checking.py diff --git a/pre_migration_view_checking.py b/pre_migration_view_checking.py new file mode 100644 index 0000000..daa6616 --- /dev/null +++ b/pre_migration_view_checking.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +Pre-Migration Cleanup Script for Odoo +Run this BEFORE migrating to identify and clean up custom views. + +Usage: odoo shell -d dbname < pre_migration_cleanup.py +""" + +print("\n" + "="*80) +print("PRE-MIGRATION CLEANUP - VIEW ANALYSIS") +print("="*80 + "\n") + +# 1. Find all custom (COW) views +print("STEP 1: Identifying Custom/COW Views") +print("-"*80) + +all_views = env['ir.ui.view'].search(['|', ('active', '=', True), ('active', '=', False)]) +cow_views = all_views.filtered(lambda v: not v.model_data_id) + +print(f"Total views in database: {len(all_views)}") +print(f"Custom views (no module): {len(cow_views)}") +print(f"Module views: {len(all_views) - len(cow_views)}\n") + +if cow_views: + print("Custom views found:\n") + print(f"{'ID':<8} {'Active':<8} {'Key':<50} {'Name':<40}") + print("-"*120) + + for view in cow_views[:50]: # Show first 50 + active_str = "āœ“" if view.active else "āœ—" + key_str = view.key[:48] if view.key else "N/A" + name_str = view.name[:38] if view.name else "N/A" + print(f"{view.id:<8} {active_str:<8} {key_str:<50} {name_str:<40}") + + if len(cow_views) > 50: + print(f"\n... and {len(cow_views) - 50} more custom views") + +# 2. Find duplicate views +print("\n" + "="*80) +print("STEP 2: Finding Duplicate Views (Same Key)") +print("-"*80 + "\n") + +from collections import defaultdict + +keys = defaultdict(list) +for view in all_views.filtered(lambda v: v.key and v.active): + keys[view.key].append(view) + +duplicates = {k: v for k, v in keys.items() if len(v) > 1} + +print(f"Found {len(duplicates)} keys with duplicate views:\n") + +if duplicates: + for key, views in sorted(duplicates.items()): + print(f"\nKey: {key} ({len(views)} duplicates)") + for view in views: + module = view.model_data_id.module if view.model_data_id else "āš ļø Custom/DB" + print(f" ID {view.id:>6}: {module:<25} | {view.name}") + +# 3. Find views that might have xpath issues +print("\n" + "="*80) +print("STEP 3: Finding Views with XPath Expressions") +print("-"*80 + "\n") + +import re + +views_with_xpath = [] +xpath_pattern = r']+expr="([^"]+)"' + +for view in all_views.filtered(lambda v: v.active and v.inherit_id): + xpaths = re.findall(xpath_pattern, view.arch_db) + if xpaths: + views_with_xpath.append({ + 'view': view, + 'xpaths': xpaths, + 'is_custom': not bool(view.model_data_id) + }) + +print(f"Found {len(views_with_xpath)} views with xpath expressions") + +custom_xpath_views = [v for v in views_with_xpath if v['is_custom']] +print(f" - {len(custom_xpath_views)} are custom views (potential issue!)") +print(f" - {len(views_with_xpath) - len(custom_xpath_views)} are module views\n") + +if custom_xpath_views: + print("Custom views with xpaths (risk for migration issues):\n") + for item in custom_xpath_views: + view = item['view'] + print(f"ID {view.id}: {view.name}") + print(f" Key: {view.key}") + print(f" Inherits from: {view.inherit_id.key}") + print(f" XPath count: {len(item['xpaths'])}") + print(f" Sample xpaths: {item['xpaths'][:2]}") + print() + +# 4. Summary and recommendations +print("=" * 80) +print("SUMMARY AND RECOMMENDATIONS") +print("=" * 80 + "\n") + +print(f"šŸ“Š Statistics:") +print(f" • Total views: {len(all_views)}") +print(f" • Custom views: {len(cow_views)}") +print(f" • Duplicate view keys: {len(duplicates)}") +print(f" • Custom views with xpaths: {len(custom_xpath_views)}\n") + +print(f"\nšŸ“‹ RECOMMENDED ACTIONS BEFORE MIGRATION:\n") + +if custom_xpath_views: + print(f"1. Archive or delete {len(custom_xpath_views)} custom views with xpaths:") + print(f" • Review each one and determine if still needed") + print(f" • Archive unnecessary ones: env['ir.ui.view'].browse([ids]).write({{'active': False}})") + print(f" • Plan to recreate important ones as proper module views after migration\n") + +if duplicates: + print(f"2. Fix {len(duplicates)} duplicate view keys:") + print(f" • Manually review and delete obsolete duplicates, keeping the most appropriate one") + print(f" • Document the remaining appropriate ones as script post_migration_fix_duplicated_views.py will run AFTER the migration and delete all duplicates.\n") + +if cow_views: + print(f"3. Review {len(cow_views)} custom views:") + print(f" • Document which ones are important") + print(f" • Export their XML for reference") + print(f" • Consider converting to module views\n") + +print("=" * 80 + "\n") diff --git a/prepare_db.sh b/prepare_db.sh index 1236513..0e126a7 100755 --- a/prepare_db.sh +++ b/prepare_db.sh @@ -77,6 +77,26 @@ Do you accept to migrate the database with all these add-ons still installed? (Y echo "Y - Yes, let's go on with the upgrade." echo "N - No, stop the upgrade" read -n 1 -p "Your choice: " choice +case "$choice" in + [Yy] ) echo " +Let's go on!";; + [Nn] ) echo " +Upgrade cancelled!"; exit 1;; + * ) echo " +Please answer by Y or N.";; +esac + + +# Check the views +PYTHON_SCRIPT=pre_migration_view_checking.py +echo "Check views with script $PYTHON_SCRIPT ..." +exec_python_script_in_odoo_shell "$DB_NAME" "$DB_NAME" "$PYTHON_SCRIPT" || exit 1 + +echo " +Do you accept to migrate the database with the current views states? (Y/N/R)" +echo "Y - Yes, let's go on with the upgrade." +echo "N - No, stop the upgrade" +read -n 1 -p "Your choice: " choice case "$choice" in [Yy] ) echo " Upgrade confirmed!";;