76 lines
2.7 KiB
Python
76 lines
2.7 KiB
Python
|
|
"""Schema 2: a document holds symbols, not timelines, and no symbol is reserved.
|
||
|
|
|
||
|
|
Three renames, each in the stored transit and nowhere else:
|
||
|
|
|
||
|
|
clip/<cid>/timeline/... -> clip/<cid>/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),
|
||
|
|
]
|