Notechondria
Version: 0.1.43 Build Date: 2026-04-19T14:00
What's Changed
Backend: split notes mega-app into courses + planner + notes
The notes app had accumulated 16 models spanning four unrelated
concerns (course catalogue, planner/heatmap/calendar, note storage,
and recycle-bin plumbing). This round carves out two new Django
apps with real tables so each domain owns its schema, admin, and
migrations cleanly.
Apps reorganized
New apps:
- backend/courses/ —
Course,CourseMedia,CourseSubscription,CourseOperationLog,CourseOperationTypeChoices. Plus thecourse_cover_pathandcourse_media_pathupload-path callables. - backend/planner/ —
PlannerEvent,HeatmapActivity,HeatmapActivityTypeChoices,CalendarFeed.
Still in notes:
Note,NoteVersion,NoteBlock,NoteAttachment,NoteIndex,NoteActivitySession,RecycleBinEntry,Tag,ValidationRecord.NoteBlockTypeChoices,RecycleBinItemTypeChoices.
Cross-app FKs are string-ref'd ("courses.Course",
"notes.Note") so the model files don't need circular imports.
Migrations (full physical table rename)
Six new migrations land in three apps, carefully ordered so state and schema stay in lockstep:
- courses/0001_initial.py
—
SeparateDatabaseAndState: state declares the four Course models withdb_table="notes_<model>", DB operations are empty. At this point the tables physically remain under the old names but Django considers them owned bycourses. - planner/0001_initial.py
— same pattern for PlannerEvent / HeatmapActivity / CalendarFeed,
all with
db_table="notes_<model>". - notes/0016_remove_moved_models_from_notes_state.py
— state-only:
DeleteModelfor all seven moved models and anAlterFieldthat re-pointsNote.course_idfromnotes.coursetocourses.course. No DB writes. - courses/0002_rename_tables.py
— physically renames
notes_course→courses_course(and the three sibling tables) viaAlterModelTable. - planner/0002_rename_tables.py
— physically renames
notes_plannerevent→planner_plannerevent(and the two sibling tables). - courses/0003_normalize_table_names.py
- planner/0003_normalize_table_names.py
— drop the explicit
db_tablemetadata so the state matches Django's default naming (same physical table name). Without these,makemigrations --dry-runreports a stale "rename table to (default)" every time.
- planner/0003_normalize_table_names.py
— drop the explicit
Rollback-safe: every step is reversible via the inverse operation
(AlterModelTable both ways, state-only deletes restore the old
state on rollback).
Historic-migration compatibility shim
notes/models.py re-exports course_cover_path and
course_media_path from courses.models so the pre-split
migrations (0005, 0006, 0007, 0009) that reference
notes.models.course_cover_path keep loading verbatim. No
rewriting of historic migration files.
Import updates
All production-code references to moved models switched to their new homes:
- backend/notes/api.py:
Course-family imports from
courses.models, planner imports fromplanner.models. - backend/notes/services.py: same split.
- backend/notes/tests.py: same split.
- backend/notes/admin.py: trimmed to
note-owned models only. Course and planner admin classes moved to
new apps'
admin.py. - backend/notes/management/commands/bootstrap_platform.py:
Course/CourseMedia imported from
courses.models. - backend/mcp/tools.py,
backend/mcp/tests.py: Course imported
from
courses.models, PlannerEvent fromplanner.models.
creators/api.py and scripts/validate_course_template.py needed
no changes — they only import from notes.services (still valid)
and don't touch moved models directly.
No API URL changes — the refactor is purely internal. Frontend clients see no difference.
Files Changed
New
docs/versions/0.1.43.md(this file).backend/courses/__init__.py,backend/courses/apps.py,backend/courses/models.py,backend/courses/admin.py,backend/courses/migrations/__init__.py,backend/courses/migrations/0001_initial.py,backend/courses/migrations/0002_rename_tables.py,backend/courses/migrations/0003_normalize_table_names.py.backend/planner/__init__.py,backend/planner/apps.py,backend/planner/models.py,backend/planner/admin.py,backend/planner/migrations/__init__.py,backend/planner/migrations/0001_initial.py,backend/planner/migrations/0002_rename_tables.py,backend/planner/migrations/0003_normalize_table_names.py.backend/notes/migrations/0016_remove_moved_models_from_notes_state.py.
Modified
VERSION: 0.1.42 → 0.1.43.backend/notechondria/settings.py: added'courses'and'planner'toINSTALLED_APPS, ordered so FKs resolve cleanly (courses before planner before notes).backend/notes/models.py: Course/CourseMedia/CourseSubscription/ CourseOperationLog/PlannerEvent/HeatmapActivity/CalendarFeed removed;Note.course_idre-pointed to"courses.Course"string ref;course_cover_path/course_media_pathre-exported fromcourses.modelsfor historic-migration compatibility.backend/notes/admin.py: trimmed to note-only admin classes.backend/notes/api.py,backend/notes/services.py,backend/notes/tests.py,backend/notes/management/commands/bootstrap_platform.py,backend/mcp/tools.py,backend/mcp/tests.py: imports updated to pull from new apps.
Verification
python manage.py check(settings_test) — 0 issues.python manage.py migrateon a fresh SQLite DB — all 43 migrations apply cleanly, including the six new ones in the correct interleaved order.python manage.py test notes creators mcp— all 118 tests pass.python manage.py makemigrations --dry-run— only a pre-existing unrelated drift remains (alter_noteattachment_id— AutoField → BigAutoField carried over from 0.1.37). No drift from this refactor.
Notes / follow-ups
- Migration order on production: the six migrations must apply
in one
migraterun so the rename + state-delete interleaves cleanly. Don't attempt a partial rollout of justcourseswithoutplanner— thenotes.0016state-delete depends on bothcourses.0001andplanner.0001. - NoteAttachment id drift (
AutoField→BigAutoField) still surfaces inmakemigrations --dry-run. Fold into a later round alongside any otheridtype cleanups. - Recycle bin + activity sessions still live in
notes. A future round could extract them into their ownactivityorrecycle_binapps, but neither has enough surface area right now to justify the split —RecycleBinEntryis 1 model,NoteActivitySessionis 1 model, both tightly coupled toNote. - Admin grouping: Django admin now shows three top-level sections (Notes, Courses, Planner) instead of the former single Notes section. This improves operator navigation and matches the user's feedback about "data structure not split cleanly by function."