[IMP] adds duplicated views cleaning at finalize db step
This commit is contained in:
@@ -11,6 +11,10 @@ EOF
|
|||||||
)
|
)
|
||||||
query_postgres_container "$FINALE_SQL" "$DB_NAME" || exit 1
|
query_postgres_container "$FINALE_SQL" "$DB_NAME" || exit 1
|
||||||
|
|
||||||
|
# Fix duplicated views
|
||||||
|
PYTHON_SCRIPT=post_migration_fix_duplicated_views.py
|
||||||
|
echo "Remove duplicated views with script $PYTHON_SCRIPT ..."
|
||||||
|
exec_python_script_in_odoo_shell "$DB_NAME" "$DB_NAME" "$PYTHON_SCRIPT" || exit 1
|
||||||
|
|
||||||
# Give back the right to user to access to the tables
|
# Give back the right to user to access to the tables
|
||||||
# docker exec -u 70 "$DB_CONTAINER_NAME" pgm chown "$FINALE_SERVICE_NAME" "$DB_NAME"
|
# docker exec -u 70 "$DB_CONTAINER_NAME" pgm chown "$FINALE_SERVICE_NAME" "$DB_NAME"
|
||||||
|
|||||||
192
post_migration_fix_duplicated_views.py
Normal file
192
post_migration_fix_duplicated_views.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Post-Migration Duplicate View Fixer
|
||||||
|
Run this AFTER migration to fix duplicate views automatically.
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("POST-MIGRATION DUPLICATE VIEW FIXER")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
# Find all duplicate views
|
||||||
|
all_views = env['ir.ui.view'].search(['|', ('active', '=', True), ('active', '=', False)])
|
||||||
|
keys = defaultdict(list)
|
||||||
|
|
||||||
|
for view in all_views:
|
||||||
|
if view.key:
|
||||||
|
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 not duplicates:
|
||||||
|
print("✓ No duplicate views found! Database is clean.")
|
||||||
|
print("=" * 80 + "\n")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# Process duplicates
|
||||||
|
views_to_delete = []
|
||||||
|
redirect_log = []
|
||||||
|
|
||||||
|
for key, views in sorted(duplicates.items()):
|
||||||
|
print(f"\nProcessing key: {key}")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
# Sort views: module views first, then by ID (older first)
|
||||||
|
sorted_views = sorted(views, key=lambda v: (
|
||||||
|
0 if v.model_data_id else 1, # Module views first
|
||||||
|
v.id # Older views first (lower ID = older)
|
||||||
|
))
|
||||||
|
|
||||||
|
# Keep the first view (should be module view or oldest)
|
||||||
|
keep = sorted_views[0]
|
||||||
|
to_delete = sorted_views[1:]
|
||||||
|
|
||||||
|
module_keep = keep.model_data_id.module if keep.model_data_id else "Custom/DB"
|
||||||
|
print(f"KEEP: ID {keep.id:>6} | Module: {module_keep:<20} | {keep.name}")
|
||||||
|
|
||||||
|
for view in to_delete:
|
||||||
|
module = view.model_data_id.module if view.model_data_id else "Custom/DB"
|
||||||
|
print(f"DELETE: ID {view.id:>6} | Module: {module:<20} | {view.name}")
|
||||||
|
|
||||||
|
# Find and redirect children
|
||||||
|
children = env['ir.ui.view'].search([('inherit_id', '=', view.id)])
|
||||||
|
if children:
|
||||||
|
print(f" → Redirecting {len(children)} children {children.ids} to view {keep.id}")
|
||||||
|
for child in children:
|
||||||
|
child_module = child.model_data_id.module if child.model_data_id else "Custom/DB"
|
||||||
|
redirect_log.append({
|
||||||
|
'child_id': child.id,
|
||||||
|
'child_name': child.name,
|
||||||
|
'child_module': child_module,
|
||||||
|
'from': view.id,
|
||||||
|
'to': keep.id
|
||||||
|
})
|
||||||
|
try:
|
||||||
|
children.write({'inherit_id': keep.id})
|
||||||
|
print(f" ✓ Redirected successfully")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ Redirect failed: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
views_to_delete.append(view)
|
||||||
|
|
||||||
|
# Summary before deletion
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("SUMMARY")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
|
||||||
|
print(f"Views to delete: {len(views_to_delete)}")
|
||||||
|
print(f"Child views to redirect: {len(redirect_log)}\n")
|
||||||
|
|
||||||
|
if redirect_log:
|
||||||
|
print("Redirections that will be performed:")
|
||||||
|
for item in redirect_log[:10]: # Show first 10
|
||||||
|
print(f" • View {item['child_id']} ({item['child_module']})")
|
||||||
|
print(f" '{item['child_name']}'")
|
||||||
|
print(f" Parent: {item['from']} → {item['to']}")
|
||||||
|
|
||||||
|
if len(redirect_log) > 10:
|
||||||
|
print(f" ... and {len(redirect_log) - 10} more redirections")
|
||||||
|
|
||||||
|
# Delete duplicate views
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("DELETING DUPLICATE VIEWS")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
|
||||||
|
deleted_count = 0
|
||||||
|
failed_deletes = []
|
||||||
|
|
||||||
|
# Sort views by ID descending (delete newer/child views first)
|
||||||
|
views_to_delete_sorted = sorted(views_to_delete, key=lambda v: v.id, reverse=True)
|
||||||
|
|
||||||
|
for view in views_to_delete_sorted:
|
||||||
|
try:
|
||||||
|
# Create savepoint to isolate each deletion
|
||||||
|
env.cr.execute('SAVEPOINT delete_view')
|
||||||
|
|
||||||
|
view_id = view.id
|
||||||
|
view_name = view.name
|
||||||
|
view_key = view.key
|
||||||
|
|
||||||
|
# Double-check it has no children
|
||||||
|
remaining_children = env['ir.ui.view'].search([('inherit_id', '=', view_id)])
|
||||||
|
if remaining_children:
|
||||||
|
print(f"⚠️ Skipping view {view_id}: Still has {len(remaining_children)} children")
|
||||||
|
failed_deletes.append({
|
||||||
|
'id': view_id,
|
||||||
|
'reason': f'Still has {len(remaining_children)} children'
|
||||||
|
})
|
||||||
|
env.cr.execute('ROLLBACK TO SAVEPOINT delete_view')
|
||||||
|
continue
|
||||||
|
|
||||||
|
view.unlink()
|
||||||
|
env.cr.execute('RELEASE SAVEPOINT delete_view')
|
||||||
|
print(f"✓ Deleted view {view_id}: {view_key}")
|
||||||
|
deleted_count += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
env.cr.execute('ROLLBACK TO SAVEPOINT delete_view')
|
||||||
|
print(f"✗ Failed to delete view {view.id}: {e}")
|
||||||
|
failed_deletes.append({
|
||||||
|
'id': view.id,
|
||||||
|
'name': view.name,
|
||||||
|
'reason': str(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Commit changes
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("COMMITTING CHANGES")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
env.cr.commit()
|
||||||
|
print("✓ All changes committed successfully!")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Commit failed: {e}")
|
||||||
|
print("Changes were NOT saved!")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# Final verification
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("FINAL VERIFICATION")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
|
||||||
|
# Re-check for duplicates
|
||||||
|
all_views_after = env['ir.ui.view'].search([('active', '=', True)])
|
||||||
|
keys_after = defaultdict(list)
|
||||||
|
|
||||||
|
for view in all_views_after:
|
||||||
|
if view.key:
|
||||||
|
keys_after[view.key].append(view)
|
||||||
|
|
||||||
|
duplicates_after = {k: v for k, v in keys_after.items() if len(v) > 1}
|
||||||
|
|
||||||
|
print(f"Results:")
|
||||||
|
print(f" • Successfully deleted: {deleted_count} views")
|
||||||
|
print(f" • Failed deletions: {len(failed_deletes)}")
|
||||||
|
print(f" • Child views redirected: {len(redirect_log)}")
|
||||||
|
print(f" • Remaining duplicates: {len(duplicates_after)}")
|
||||||
|
|
||||||
|
if failed_deletes:
|
||||||
|
print(f"\n⚠️ Failed deletions:")
|
||||||
|
for item in failed_deletes:
|
||||||
|
print(f" • View {item['id']}: {item['reason']}")
|
||||||
|
|
||||||
|
if duplicates_after:
|
||||||
|
print(f"\n⚠️ Still have {len(duplicates_after)} duplicate keys:")
|
||||||
|
for key, views in sorted(duplicates_after.items())[:5]:
|
||||||
|
print(f" • {key}: {len(views)} views")
|
||||||
|
for view in views:
|
||||||
|
module = view.model_data_id.module if view.model_data_id else "Custom/DB"
|
||||||
|
print(f" - ID {view.id} ({module})")
|
||||||
|
print(f"\n Run this script again to attempt another cleanup.")
|
||||||
|
else:
|
||||||
|
print(f"\n✓ All duplicates resolved!")
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("FIX COMPLETED!")
|
||||||
|
print("="*80)
|
||||||
Reference in New Issue
Block a user