-
-
Notifications
You must be signed in to change notification settings - Fork 185
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: back-fill submission unique identifier - TASK 1459 (#5434)
### 📣 Summary Added a long-running migration to backfill the `root_uuid` field for old submissions. ### 📖 Description Submissions now use a field called `root_uuid`, which serves as a unique identifier per project. For older submissions, this field is empty. This PR introduces a long-running migration to backfill the `root_uuid` field with the current `uuid` for existing submissions. To ensure uniqueness, the migration includes logic to detect and resolve conflicts. If a `uuid` is not unique within a project, the `clean_duplicated_submissions` management command is invoked to address the duplicates before assigning the `root_uuid`.
- Loading branch information
1 parent
e424952
commit 1e23d21
Showing
3 changed files
with
108 additions
and
1 deletion.
There are no files selected for viewing
71 changes: 71 additions & 0 deletions
71
kobo/apps/long_running_migrations/jobs/0005_back_fill_logger_instance_root_uuid.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# Generated on 2025-16-19 11:58 | ||
from django.conf import settings | ||
from django.core.management import call_command | ||
from django.db import IntegrityError | ||
from more_itertools import chunked | ||
from taggit.models import TaggedItem | ||
|
||
from kobo.apps.openrosa.apps.logger.models import XForm, Instance | ||
from kpi.utils.database import use_db | ||
|
||
|
||
def run(): | ||
""" | ||
Transfers all assets owned by members to their respective organizations. | ||
""" | ||
CHUNK_SIZE = 2000 | ||
|
||
with use_db(settings.OPENROSA_DB_ALIAS): | ||
xforms = XForm.objects.only('pk', 'id_string').exclude( | ||
tags__name__contains='kobo-root-uuid' | ||
).iterator() | ||
for xform_batch in chunked(xforms, CHUNK_SIZE): | ||
for xform in xform_batch: | ||
instances = Instance.objects.only( | ||
'pk', 'uuid', 'xml', 'root_uuid' | ||
).filter(root_uuid__isnull=True, xform_id=xform.pk).iterator() | ||
error = False | ||
for instance_batch in chunked(instances, CHUNK_SIZE): | ||
if not _process_instances_batch(xform, instance_batch): | ||
error = True | ||
break | ||
|
||
if not error: | ||
xform.tags.add('kobo-root-uuid-success') | ||
|
||
# Clean up tags while retaining failed entries for future manual review | ||
TaggedItem.objects.filter(tag__name='kobo-root-uuid-success').delete() | ||
|
||
|
||
def _process_instances_batch( | ||
xform: XForm, instance_batch: list[Instance], first_try=True | ||
) -> bool: | ||
for instance in instance_batch: | ||
try: | ||
instance._populate_root_uuid() # noqa | ||
except AssertionError as e: | ||
if 'root_uuid should not be empty' in str(e): | ||
# fallback on `uuid` to back-fill `root_uuid` | ||
instance.root_uuid = instance.uuid | ||
try: | ||
Instance.objects.bulk_update( | ||
instance_batch, fields=['root_uuid'] | ||
) | ||
except IntegrityError: | ||
if first_try: | ||
call_command( | ||
'clean_duplicated_submissions', | ||
xform=xform.id_string, | ||
) | ||
# Need to reload instance_batch to get new uuids | ||
instance_batch = Instance.objects.only( | ||
'pk', 'uuid', 'xml', 'root_uuid' | ||
).filter(pk__in=[instance.pk for instance in instance_batch]) | ||
return _process_instances_batch( | ||
xform, instance_batch, first_try=False | ||
) | ||
else: | ||
xform.tags.add('kobo-root-uuid-failed') | ||
return False | ||
else: | ||
return True |
30 changes: 30 additions & 0 deletions
30
kobo/apps/long_running_migrations/migrations/0005_back_fill_logger_instance_root_uuid.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Generated by Django 4.2.15 on 2025-01-16 11:50 | ||
|
||
from django.db import migrations | ||
|
||
|
||
def add_long_running_migration(apps, schema_editor): | ||
LongRunningMigration = apps.get_model( | ||
'long_running_migrations', 'LongRunningMigration' | ||
) # noqa | ||
LongRunningMigration.objects.create( | ||
name='0005_back_fill_logger_instance_root_uuid' | ||
) | ||
|
||
|
||
def noop(*args, **kwargs): | ||
pass | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
( | ||
'long_running_migrations', | ||
'0004_back_fill_asset_search_field_for_owner_label', | ||
), | ||
] | ||
|
||
operations = [ | ||
migrations.RunPython(add_long_running_migration, noop), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters