diff --git a/profiling/seed.py b/profiling/seed.py index cf167fa38..1bf68ff55 100644 --- a/profiling/seed.py +++ b/profiling/seed.py @@ -10,6 +10,7 @@ from django.contrib.auth.models import Group from django.contrib.auth.models import User from guardian.shortcuts import assign_perm +from documents.models import Document from documents.tests.factories import CorrespondentFactory from documents.tests.factories import DocumentFactory from documents.tests.factories import DocumentTypeFactory @@ -18,13 +19,17 @@ from documents.tests.factories import TagFactory if TYPE_CHECKING: from documents.models import Correspondent - from documents.models import Document from documents.models import DocumentType from documents.models import StoragePath from documents.models import Tag ScaleProfile = Literal["medium", "large"] +# Fraction of seeded documents given a real owner; the remainder stays +# owner=None, mirroring a real install's mix of owned and legacy/imported +# unowned documents. +_OWNED_FRACTION = 0.9 + class _ScaleCounts: __slots__ = ( @@ -140,7 +145,20 @@ def seed_permission_dataset( for user in users: user.groups.add(rng.choice(groups)) - documents = tuple(DocumentFactory.create_batch(counts.documents)) + documents = list(DocumentFactory.create_batch(counts.documents)) + + # Assign realistic ownership -- without this, every document stays + # owner=None and the "owned or unowned" visibility clause is trivially + # true for every row, so no guardian permission check ever needs to run + # (see the amendment note above this code block). + owned_documents = [] + for document in documents: + if rng.random() < _OWNED_FRACTION: + document.owner = rng.choice(users) + owned_documents.append(document) + Document.objects.bulk_update(owned_documents, ["owner"], batch_size=2_000) + documents = tuple(documents) + tags = tuple(TagFactory.create_batch(counts.tags)) correspondents = tuple(CorrespondentFactory.create_batch(counts.correspondents)) document_types = tuple(DocumentTypeFactory.create_batch(counts.document_types)) @@ -149,29 +167,26 @@ def seed_permission_dataset( n_user_perms = round(counts.documents * _USER_PERM_ROWS_PER_DOC) n_group_perms = round(counts.documents * _GROUP_PERM_ROWS_PER_DOC) - # guardian's assign_perm is a get_or_create on the (subject, object, - # permission) triple, so drawing (subject, document) pairs *with* - # replacement -- rng.choice/rng.choice in a loop -- collides far more - # than the raw draw count suggests once the subject pool is small - # relative to the number of draws (a birthday-paradox effect): e.g. at - # medium scale, 45,387 draws over only 5 groups x 20,000 documents = - # 100,000 slots yields ~36,500 *distinct* pairs, not ~45,387. Sampling - # distinct pairs up front guarantees the seeded row counts actually - # land on the real-install ratios the scale profiles are derived from. - for user_idx, doc_idx in _sample_distinct_pairs( + # Grant permissions only on OWNED documents -- a grant on an already- + # unowned document is a no-op for visibility (unowned documents are + # visible to everyone regardless), so restricting the pool here is what + # makes each assign_perm() call actually matter for someone, and is + # what exercises the guardian-permission-join code path this profiling + # effort exists to measure. + for user_idx, owned_idx in _sample_distinct_pairs( rng, len(users), - len(documents), + len(owned_documents), n_user_perms, ): - assign_perm("view_document", users[user_idx], documents[doc_idx]) - for group_idx, doc_idx in _sample_distinct_pairs( + assign_perm("view_document", users[user_idx], owned_documents[owned_idx]) + for group_idx, owned_idx in _sample_distinct_pairs( rng, len(groups), - len(documents), + len(owned_documents), n_group_perms, ): - assign_perm("view_document", groups[group_idx], documents[doc_idx]) + assign_perm("view_document", groups[group_idx], owned_documents[owned_idx]) return SeededData( users=users,