Skip to content

Sub-modules phase A: additive DB models and constraints #397

Description

@alexeygrigorev

Part of the shared-curriculum sub-modules umbrella (#396), phase A of 4. Full design: _docs/planning/shared-curriculum-submodules-design.md (sections "Refined design" and "Constraints and what the renderers must know").

Goal

Additive-only database schema for shared-curriculum sub-modules and per-cohort homework anchoring. No behavior change for any existing content — every module currently has zero sub-modules and must keep rendering exactly as today.

Scope

  1. New model courses.models.shared_curriculum.SharedSubModule: required FK to SharedModule (related_name="sub_modules"), position (int), slug, title, summary (optional), source provenance fields mirroring SharedModule's own (source_content_id etc.). No overview_markdown, no independent published/retired_at — a sub-module is a grouping, not an independently-publishable unit. Unique constraints: (module, position), (module, slug), (module, source_content_id).
  2. SharedLesson gains a nullable sub_module FK. clean() validates sub_module.module_id == module_id when set.
  3. CohortSharedModule gains a nullable sub_module FK (the homework anchor selector) and:
    • UniqueConstraint(fields=("cohort", "shared_module"), condition=Q(sub_module__isnull=True))
    • UniqueConstraint(fields=("cohort", "sub_module"), condition=Q(sub_module__isnull=False))
    • UniqueConstraint(fields=("terminal_homework",), condition=Q(terminal_homework__isnull=False))
    • clean(): sub_module.module_id == shared_module_id when sub_module is set.
    • Update the model's docstring: a placement is one cohort's homework anchor on one node (module or one of its sub-modules) of a shared module — not merely "optional."
  4. Migrations: purely additive (new table, new nullable columns, new constraints on existing tables — verify the three new CohortSharedModule constraints don't reject any existing row; they shouldn't, since every existing row has sub_module IS NULL).
  5. Django admin: register SharedSubModule with reasonable list/search config, consistent with how SharedModule is already registered.

Non-goals

  • No importer/parser changes (phase B).
  • No rendering changes (phase C).
  • No zoomcamp-ops/module.yaml/cohort.yaml schema changes (phase D).
  • No real sub-module data — this phase ships with zero SharedSubModule rows in every environment.

Dependencies

Acceptance criteria

  • make django-check / make migrations-check pass.
  • make test (full suite) passes with zero behavior change to any existing rendered page (verify by diffing rendered HTML for a couple of real shared-curriculum pages before/after, e.g. an llm-zoomcamp module page).
  • New model tests: SharedSubModule uniqueness constraints, SharedLesson.clean() cross-module rejection, CohortSharedModule.clean() cross-module rejection, all three new CohortSharedModule constraints (module-level dup rejected, sub-module-level dup rejected, a homework anchored twice rejected).
  • manage.py migrate applies cleanly end to end on a fresh database and on the existing local dev database; migration reverses cleanly.
  • Django admin renders SharedSubModule without error.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Important follow-upcoursesArea: coursesenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions