DataTalksClub / DataTalksClub/community-base
Curriculum nesting: submodules and typed unit elements (lesson, homework, event)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- Avg merge
- 9m
- Merged PRs (30d)
- 210
Description
Goal
Support a three-level curriculum — module, submodule, unit — where a submodule's elements can be lessons, homework, or an event, while simpler courses stay two-level (module, unit). Owner request, 2026-09-16:
we have module then submodules and homework or event could be one of the submodule elements
I want to have similar structure like in maven where we have these submodules for each module and units are within submodule (but simpler courses may consist just of modules)
This lands in community_base.curriculum so all three sites share one model. DataTalks.Club already shipped a related idea (CurriculumFlowItem), and the AI Shipping Labs buildcamp import had to flatten a genuinely three-level source into two levels, losing the section structure. Both point the same way.
Current state, all three sites
| Site | Hierarchy | Notes |
|---|---|---|
community_base.curriculum |
Cohort → Module → Unit | Module(cohort, slug, sort_order, overview), Unit(module, slug, sort_order, video_url, body, homework, is_preview, required_level, available_after_days); cb_module_cohort_slug_unique on (cohort, slug), cb_unit_module_slug_unique on (module, slug) |
| DataTalksClub/website | Cohort → Module → Unit, plus CurriculumFlowItem(cohort, position, module | project) |
Polymorphic cohort-level flow with an exactly-one-target check constraint |
| AI-Shipping-Labs/website | Course → Module → Unit | Pre-package; Unit.effective_required_level resolves unit → Course.default_unit_required_level → Course.required_level; homework is a text field on Unit, set by is_homework: true in unit frontmatter |
Design
1. Nesting: a self-referencing Module, not a new Submodule model
Add Module.parent (nullable self-FK, on_delete=CASCADE). A submodule is a module with a parent.
Why this over an explicit Submodule model:
- "Simpler courses may consist just of modules" falls out for free:
parentis null and nothing else changes. - A
Unitkeeps exactly one parent. An explicit middle table would giveUnittwo possible parents (moduleorsubmodule), forcing every query, ordering, access check and template to branch. That is the classic optional-middle-table smell and it would touch far more code than the feature is worth. - The migration is additive and null-defaulted, so every existing row on all three sites keeps its current behaviour with no data migration.
- Existing constraints still hold: unit slugs stay unique per module, and module slugs stay unique per cohort, which keeps URLs unambiguous.
Constraints to add:
- A depth cap. Two levels of module (
parent__parent__isnull=True) enforced inclean()and in the importer. Arbitrary depth is not requested and would make reading order, breadcrumbs and Studio editing materially harder. - A self-parent and cycle guard.
parentmust belong to the same cohort.
2. Heterogeneous elements: Unit.kind, not a polymorphic join table
Add Unit.kind with values lesson (default), homework, event, and a nullable Unit.event FK to events.Event used only when kind="event".
Why this over a ModuleItem(module, position, unit | homework | event) join table:
- One ordered leaf list per module means ordering,
UnitProgress, drip (available_after_days), access resolution and reading order all keep working unchanged. A polymorphic table would require every one of those to be rewritten and kept consistent. - AI Shipping Labs already encodes this implicitly:
is_homework: truein unit frontmatter routes the body intoUnit.homework.kindmakes the existing concept explicit rather than introducing a new one. - An event genuinely is a different object with dates, registration and Zoom, so it is referenced rather than copied. A unit with
kind="event"renders the event card and links to the event page.
Deliberate tension to record, not resolve here: DataTalks.Club's CurriculumFlowItem is polymorphic at cohort level (module or project). That stays as it is. Cohort-level flow and within-module elements are different problems at different levels; unifying them is out of scope and should not be attempted as part of this work.
Migration note: existing units with non-empty homework and empty body are candidates for kind="homework", but that inference is site-specific and belongs in each site's own data migration, not in the package.
3. What deliberately does not change
- No module-level access control.
Unit.effective_required_levelkeeps resolving unit → course default → course required level. Adding per-module gating on top of nesting would multiply the access matrix and is not requested. If it is ever added, the resolver must walk to the nearest ancestor module carrying a value. - No URL changes for units. Unit slugs stay unique per module and submodules are modules, so existing unit URLs keep working. This matters because the buildcamp is live and indexed.
UnitProgressstays per-unit.
4. New behaviour the package must provide
- A depth-first flattened reading order across the tree, used for previous/next navigation and percentage complete. It must be stable and defined once, not recomputed per template.
- Breadcrumbs that render module then submodule then unit.
- Importer support for nested module directories in both parsers: a module directory containing subdirectories that each carry their own
module.yamlbecomes a parent module with children. This is exactly the three-level shape the buildcamp source already has. - Studio editing for the tree, including reparenting and reordering.
- Serializer and API shape carrying the nesting.
Work split
community_base (this repository)
| Slice | Content |
|---|---|
| 1 | Module.parent with depth, cycle and same-cohort guards; ordering; reading-order helper |
| 2 | Unit.kind and Unit.event; rendering for each kind |
| 3 | Importer support for nested module directories, both parsers |
| 4 | Public pages: tree rendering, breadcrumbs, previous/next, progress |
| 5 | Studio tree editing and the API/serializer shape |
AI-Shipping-Labs/website
AI Shipping Labs cannot consume the package curriculum app yet: it is gated behind A5.1 and A5.2, which wait on the v0.6.0 release, which waits on C3.7 and C4.3, which wait on A3.2 and A4.1. That is weeks of work, and the buildcamp is live now with its structure flattened.
So AI Shipping Labs implements the same change locally in its content app, using identical field names and semantics (Module.parent, Unit.kind, Unit.event, same kind values, same depth cap). The A5.1 mapping document is already field-by-field, so this stays a one-to-one mapping rather than a rewrite. The duplication is deliberate and time-boxed.
Then: restore the buildcamp content repository to its original three-level shape, since the conversion flattened module/section/unit into module/unit only because the platform could not represent it.
DataTalksClub/website
Handled by a separate agent. The contract it must match is in the next section.
Shared contract — must be identical across all three sites
Any divergence here turns the later package adoption into a rewrite, so these are not local choices:
- Field names:
Module.parent,Unit.kind,Unit.event. kindvalues:lesson,homework,event.lessonis the default so existing rows are correct without a data migration.- Maximum nesting: two levels of module. A module may have a parent; that parent may not.
- A parent module belongs to the same cohort (or course, on AI Shipping Labs) as its children.
- Reading order is depth-first: a parent module's own direct units come before its child modules' units, each ordered by
sort_orderthen id. - Nesting carries no access semantics.
- Unit URLs are unchanged.
Open questions for the owner
- Can a parent module hold units directly, alongside child modules? The design above allows it and orders them first. Forbidding it would be simpler to render but less flexible.
- Should
kind="event"units appear in progress percentages, or count only lessons and homework? - On AI Shipping Labs, should existing units with homework text be migrated to
kind="homework", or shouldkindonly apply to newly synced content?
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the community_base.curriculum models and importer entry points, then review the Studio and API/serializer work split. The shared contract defines the required fields, depth cap, depth-first order, unchanged URLs, and nesting semantics; owner decisions on the three open questions are needed before implementation can be considered complete.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, python
- Domain
- backend, content
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100