"""Schema 2: a document holds symbols, not timelines, and no symbol is reserved. Three renames, each in the stored transit and nowhere else: clip//timeline/... -> clip//symbol/... a node leaf's :kind :symbol -> :kind :instance a feature leaf's :timeline key -> :symbol A leaf value is transit's map form, ["^ ", k1, v1, k2, v2, ...]. Only TOP-LEVEL pairs are rewritten, and only literal ones: transit caches a repeated keyword as "^N", and a rename that met a cache reference where it expected the keyword would be guessing. Every saved leaf at the time of writing had these as literals; if one does not, the migration stops rather than writing a document that decodes to something else. Renaming a cached keyword in place is safe because the cache is positional: the literal keeps its slot, so any later "^N" that referred to it now refers to the new name, which is what it meant. """ import re from django.db import migrations, models PATH = re.compile(r"^(clip/[^/]+/)timeline(/|$)") def _rename_pair(value, key, old, new, path): if not (isinstance(value, list) and value[:1] == ["^ "]): return value out = list(value) for i in range(1, len(out) - 1, 2): if out[i] != key: continue if old is None: out[i] = new elif out[i + 1] == old: out[i + 1] = new elif isinstance(out[i + 1], str) and out[i + 1].startswith("^") and out[i + 1] != "^ ": raise RuntimeError(f"leaf {path!r} has a cached {key} value; migrate it by hand") return out def forwards(apps, schema_editor): Leaf = apps.get_model("clips", "Leaf") Project = apps.get_model("clips", "Project") for leaf in Leaf.objects.all(): path = PATH.sub(r"\1symbol\2", leaf.path) value = leaf.value parts = path.split("/") if len(parts) == 6 and parts[2] == "symbol" and parts[4] == "node": value = _rename_pair(value, "~:kind", "~:symbol", "~:instance", leaf.path) if len(parts) == 4 and parts[2] == "feature": value = _rename_pair(value, "~:timeline", None, "~:symbol", leaf.path) if path != leaf.path or value != leaf.value: leaf.path = path leaf.value = value leaf.version += 1 leaf.save(update_fields=["path", "value", "version"]) Project.objects.update(schema_version=2) class Migration(migrations.Migration): dependencies = [ ("clips", "0006_project_schema_version"), ] operations = [ migrations.AlterField( model_name="project", name="schema_version", field=models.PositiveIntegerField(default=2), ), migrations.RunPython(forwards, migrations.RunPython.noop), ]