From 3cad1b8a39ba18a6d44f17a7aef85e4f323bddd9 Mon Sep 17 00:00:00 2001 From: Manuel Raynaud Date: Wed, 11 Mar 2026 13:56:37 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(backend)=20add=20a=20migration=20for?= =?UTF-8?q?=20cleaning=20onboarding=20document=20accesses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We change the strategy on how the new users have access to the onboarding documents. We should remove all created accesses we don't want to have anymore. There is no need to add them in the link_trace table, they are already present in the favorites and user have already access to it. --- CHANGELOG.md | 2 + .../0031_clean_onboarding_accesses.py | 34 +++ ...grations_0031_clean_onboarding_accesses.py | 193 ++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 src/backend/core/migrations/0031_clean_onboarding_accesses.py create mode 100644 src/backend/core/tests/migrations/test_migrations_0031_clean_onboarding_accesses.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a577cada..d8d02531 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,11 @@ and this project adheres to ### Added - ✨(backend) add a is_first_connection flag to the User model #1938 + ### Changed - ♿(frontend) localize LaGaufre label fallback in Docs #1979 +- ✨(backend) add a migration cleaning on-boarding document accesses ### Fixed diff --git a/src/backend/core/migrations/0031_clean_onboarding_accesses.py b/src/backend/core/migrations/0031_clean_onboarding_accesses.py new file mode 100644 index 00000000..c85a5ef8 --- /dev/null +++ b/src/backend/core/migrations/0031_clean_onboarding_accesses.py @@ -0,0 +1,34 @@ +# Generated by Django 5.2.12 on 2026-03-11 17:16 + +from django.conf import settings +from django.db import migrations + +from core.models import PRIVILEGED_ROLES + + +def clean_onboarding_accesses(apps, schema_editor): + """clean accesses on on-boarding documents.""" + onboarding_document_ids = settings.USER_ONBOARDING_DOCUMENTS + if not onboarding_document_ids: + return + + onboarding_document_ids = set(settings.USER_ONBOARDING_DOCUMENTS) + + DocumentAccess = apps.get_model("core", "DocumentAccess") + + DocumentAccess.objects.filter(document_id__in=onboarding_document_ids).exclude( + role__in=PRIVILEGED_ROLES + ).delete() + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0030_user_is_first_connection"), + ] + + operations = [ + migrations.RunPython( + clean_onboarding_accesses, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/src/backend/core/tests/migrations/test_migrations_0031_clean_onboarding_accesses.py b/src/backend/core/tests/migrations/test_migrations_0031_clean_onboarding_accesses.py new file mode 100644 index 00000000..06ab59b2 --- /dev/null +++ b/src/backend/core/tests/migrations/test_migrations_0031_clean_onboarding_accesses.py @@ -0,0 +1,193 @@ +"""Module testing migration 0031_clean_onboarding_accesses.""" + +from django.contrib.auth.hashers import make_password + +import pytest + + +def create_user(OldUser, n): + """Create a user with a unique sub and email based on the given index.""" + return OldUser.objects.create( + email=f"user-{n}@example.com", + sub=f"user-{n}", + password=make_password("password"), + ) + + +@pytest.mark.django_db +def test_clean_onboarding_accesses(migrator, settings): + """Test migration 0031_clean_onboarding_accesses.""" + old_state = migrator.apply_initial_migration( + ("core", "0030_user_is_first_connection") + ) + + OldUser = old_state.apps.get_model("core", "User") + OldDocument = old_state.apps.get_model("core", "Document") + OldDocumentAccess = old_state.apps.get_model("core", "DocumentAccess") + + # Create onboarding documents + onboarding_doc_1 = OldDocument.objects.create( + title="Onboarding Doc 1", depth=1, path="0000001", link_reach="public" + ) + onboarding_doc_2 = OldDocument.objects.create( + title="Onboarding Doc 2", depth=1, path="0000002", link_reach="public" + ) + onboarding_documents = [onboarding_doc_1, onboarding_doc_2] + + settings.USER_ONBOARDING_DOCUMENTS = [str(doc.id) for doc in onboarding_documents] + + # Create other documents + non_onboarding_doc_1 = OldDocument.objects.create( + title="Non-Onboarding Doc 1", depth=1, path="0000003", link_reach="public" + ) + non_onboarding_doc_2 = OldDocument.objects.create( + title="Non-Onboarding Doc 2", depth=1, path="0000004", link_reach="public" + ) + non_onboarding_doc_3 = OldDocument.objects.create( + title="Non-Onboarding Doc 3", depth=1, path="0000005", link_reach="public" + ) + non_onboarding_documents = [ + non_onboarding_doc_1, + non_onboarding_doc_2, + non_onboarding_doc_3, + ] + + all_documents = onboarding_documents + non_onboarding_documents + + user_counter = 0 + + # For every document create privileged roles: owner and admin + for document in all_documents: + OldDocumentAccess.objects.create( + document=document, + user=create_user(OldUser, user_counter), + role="owner", + ) + user_counter += 1 + OldDocumentAccess.objects.create( + document=document, + user=create_user(OldUser, user_counter), + role="administrator", + ) + user_counter += 1 + + # For every document, create non-privileged roles + for document in all_documents: + for role in ["reader", "editor", "commenter"]: + for _ in range(10): + OldDocumentAccess.objects.create( + document=document, + user=create_user(OldUser, user_counter), + role=role, + ) + user_counter += 1 + + onboarding_ids = [doc.id for doc in onboarding_documents] + non_onboarding_ids = [doc.id for doc in non_onboarding_documents] + + # All documents should have 32 accesses each, so 160 accesses total + assert OldDocumentAccess.objects.count() == 160 + assert ( + OldDocumentAccess.objects.filter(document_id__in=onboarding_ids) + .exclude(role__in=["administrator", "owner"]) + .count() + == 60 + ) + assert ( + OldDocumentAccess.objects.filter( + document_id__in=onboarding_ids, role__in=["administrator", "owner"] + ).count() + == 4 + ) + assert ( + OldDocumentAccess.objects.filter(document_id__in=non_onboarding_ids) + .exclude(role__in=["administrator", "owner"]) + .count() + == 90 + ) + assert ( + OldDocumentAccess.objects.filter( + document_id__in=non_onboarding_ids, role__in=["administrator", "owner"] + ).count() + == 6 + ) + + # Apply the migration + new_state = migrator.apply_tested_migration( + ("core", "0031_clean_onboarding_accesses") + ) + + NewDocumentAccess = new_state.apps.get_model("core", "DocumentAccess") + + # 60 accesses should have been removed (30 non-privileged for each onboarding doc) + assert NewDocumentAccess.objects.count() == 100 + + # Non-privileged roles should have been deleted on the onboarding documents + assert ( + NewDocumentAccess.objects.filter(document_id__in=onboarding_ids) + .exclude(role__in=["administrator", "owner"]) + .count() + == 0 + ) + + # Privileged roles should have been kept + assert ( + NewDocumentAccess.objects.filter( + document_id__in=onboarding_ids, role__in=["administrator", "owner"] + ).count() + == 4 + ) + + # On other documents, all accesses should remain + assert ( + NewDocumentAccess.objects.filter(document_id__in=non_onboarding_ids) + .exclude(role__in=["administrator", "owner"]) + .count() + == 90 + ) + + # Privileged roles should have been kept + assert ( + NewDocumentAccess.objects.filter( + document_id__in=non_onboarding_ids, role__in=["administrator", "owner"] + ).count() + == 6 + ) + + +@pytest.mark.django_db +def test_clean_onboarding_accesses_no_setting(migrator, settings): + """Test migration 0031 does not delete any access when USER_ONBOARDING_DOCUMENTS is empty.""" + old_state = migrator.apply_initial_migration( + ("core", "0030_user_is_first_connection") + ) + + OldUser = old_state.apps.get_model("core", "User") + OldDocument = old_state.apps.get_model("core", "Document") + OldDocumentAccess = old_state.apps.get_model("core", "DocumentAccess") + + settings.USER_ONBOARDING_DOCUMENTS = [] + + doc_1 = OldDocument.objects.create(title="Doc 1", depth=1, path="0000001") + doc_2 = OldDocument.objects.create(title="Doc 2", depth=1, path="0000002") + + user_counter = 0 + for document in [doc_1, doc_2]: + for role in ["owner", "administrator", "reader", "editor", "commenter"]: + OldDocumentAccess.objects.create( + document=document, + user=create_user(OldUser, user_counter), + role=role, + ) + user_counter += 1 + + assert OldDocumentAccess.objects.count() == 10 + + new_state = migrator.apply_tested_migration( + ("core", "0031_clean_onboarding_accesses") + ) + + NewDocumentAccess = new_state.apps.get_model("core", "DocumentAccess") + + # No accesses should have been deleted + assert NewDocumentAccess.objects.count() == 10