Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Convert dataset authors from old string format to new object format.

Old format: ["Author One", "Author Two"] (list of strings)
New format: [{"name": "Author One", "orcid_id": ""}, ...] (list of dicts)

This is a one-time data fix for datasets that were created or versioned
before the model-level auto-normalization was added to Dataset.save().
"""

from django.core.management.base import BaseCommand
from loguru import logger

from sds_gateway.api_methods.models import Dataset


class Command(BaseCommand):
help = (
"Convert dataset authors from old string format ['Name'] "
"to new object format [{'name': 'Name', 'orcid_id': ''}]"
)

def add_arguments(self, parser):
parser.add_argument(
"--dry-run",
action="store_true",
help="Only count affected datasets without modifying them",
)
parser.add_argument(
"--uuid",
type=str,
help="Migrate only a specific dataset by UUID",
)

def handle(self, *args, **options):
dry_run = options["dry_run"]
specific_uuid = options.get("uuid")

# Query datasets with non-empty authors
queryset = Dataset.objects.filter(authors__isnull=False).exclude(authors="")
if specific_uuid:
queryset = queryset.filter(uuid=specific_uuid)

total = queryset.count()
updated = 0
skipped = 0
errors = 0

self.stdout.write(f"Scanning {total} dataset(s) with authors...")

for dataset in queryset:
try:
# from_db already deserialized authors from JSON string to Python object
authors = dataset.authors

# Skip if not a list
if not isinstance(authors, list):
skipped += 1
continue

# Skip empty lists
if not authors:
skipped += 1
continue

# Skip if already in new format (list of dicts with 'name' key)
if isinstance(authors[0], dict):
skipped += 1
continue

# Convert old format: list of strings → list of dicts
if isinstance(authors[0], str):
new_authors = [
{"name": author, "orcid_id": ""}
for author in authors
if isinstance(author, str)
]

if not new_authors:
logger.warning(
f"Dataset {dataset.uuid}: no valid author strings found, "
"skipping",
)
skipped += 1
continue

if dry_run:
logger.info(
f"[DRY-RUN] Would convert dataset {dataset.uuid}: "
f"{len(authors)} author(s) in old format"
)
else:
dataset.authors = new_authors
dataset.save(update_fields=["authors"])
logger.info(
f"Converted dataset {dataset.uuid}: "
f"{len(authors)} author(s) → new format"
)
updated += 1
else:
# Unexpected format
logger.warning(
f"Dataset {dataset.uuid}: unexpected author type "
f"{type(authors[0]).__name__}, skipping"
)
skipped += 1

except Exception as e: # noqa: BLE001 # intentional: continue processing remaining datasets
logger.error(f"Error processing dataset {dataset.uuid}: {e}")
errors += 1

self.stdout.write(
f"Done: {updated} updated, {skipped} skipped, {errors} errors"
+ (" (dry run)" if dry_run else "")
)
22 changes: 22 additions & 0 deletions gateway/sds_gateway/api_methods/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,28 @@ def save(self, *args, **kwargs) -> None:
field_value = getattr(self, field)
if field_value:
if isinstance(field_value, list):
# --- Authors format normalization ---
# Prevent "authors still in old string format" warning by
# auto-converting old-style list-of-strings to list-of-dicts
# on save. Catches all write paths (form, versioning, API,
# direct ORM create) so old format cannot persist even when
# entering through code paths that bypass form validation
# (e.g. dataset versioning which copies raw field values
# via getattr + Dataset.objects.create).
#
# Old format: ["Author One", "Author Two"]
# New format: [{"name": "Author One", "orcid_id": ""}, ...]
if (
field == "authors"
and field_value
and isinstance(field_value[0], str)
):
field_value = [
{"name": author, "orcid_id": ""}
for author in field_value
if isinstance(author, str)
]
# --- End authors format normalization ---
Comment thread
lucaspar marked this conversation as resolved.
setattr(self, field, json.dumps(field_value))
elif isinstance(field_value, str):
# If it's already a string, it might be JSON - try to parse it
Expand Down