diff --git a/apis_ontology/migrations/0087_data_migration_fix_textchoices_value.py b/apis_ontology/migrations/0087_data_migration_fix_textchoices_value.py new file mode 100644 index 0000000..96a01af --- /dev/null +++ b/apis_ontology/migrations/0087_data_migration_fix_textchoices_value.py @@ -0,0 +1,113 @@ +""" +Data migration to update DB values of `Work` entity`temporal_frequency` field +following the discovery that one `TextChoices` value was not updated +from English to German in models.py. + +Reuses migration 0084's most pared down value conversion approach so far. +""" + +# Generated by Django 4.2.15 on 2024-09-19 08:07 + +import logging +from functools import partial + +from django.db import migrations + + +logger = logging.getLogger(__name__) + +# list of dicts containing old and new field (string) values +TEMPORAL_FREQUENCY_VALUES = [ # TemporalFrequency TextChoices in models + { + "old": "singulative", + "new": "singulativ", + }, +] + +# list of dicts linking fields with field value dicts and models they appear in +UPDATE_FIELDS = [ + { + "models": ["Work", "VersionWork"], + "fields": [ + { + "name": "temporal_frequency", + "values": TEMPORAL_FREQUENCY_VALUES, + }, + ], + }, +] + + +def update_field_values(apps, schema_editor, from_key="old", to_key="new"): + """ + Replace existing field values in the database with new values if + they match a given string. + + Assumes affected fields contain strings (CharField, TextField,...) + or a list of strings (PostgreSQL ArrayField with a string field for + base_field). + + :param from_key: identifier for current value in field value dict + :type from_key: str + :param to_key: identifier for replacement value in field value dict + :type to_key: str + """ + + for item in UPDATE_FIELDS: + for model in item["models"]: + model_class = apps.get_model("apis_ontology", model) + + for obj in model_class.objects.all(): + for field in item["fields"]: + field_name = field["name"] + field_values = field["values"] + + field_data = getattr(obj, field_name, None) + + if field_data: + converted_data = field_data + + if isinstance(field_data, str): + for c in field_values: + if field_data == c[from_key]: + converted_data = c[to_key] + + if isinstance(field_data, list): + converted_data = [] + for value in field_data: + for c in field_values: + if value == c[from_key]: + value = c[to_key] + converted_data.append(value) + + if converted_data != field_data: + setattr(obj, field_name, converted_data) + + # log value conversions + logger.info( + f"Converted: {','.join(field_data) if isinstance(field_data, list) else field_data} " + f"-> {','.join(converted_data) if isinstance(converted_data, list) else converted_data} " + f"({model}, ID: {getattr(obj, 'id', [])})" + ) + + if hasattr(obj, "skip_history_when_saving"): + obj.skip_history_when_saving = True + + obj.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("apis_ontology", "0086_alter_versionwork_temporal_frequency_and_more"), + ] + + operations = [ + migrations.RunPython( + code=update_field_values, + reverse_code=partial( + update_field_values, + from_key="new", + to_key="old", + ), + ), + ]