DataTalksClub / DataTalksClub/community-base

Curriculum nesting: submodules and typed unit elements (lesson, homework, event)

Open
#252 4 comments 0 reactions 0 assignees View on GitHub

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_levelCourse.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: parent is null and nothing else changes.
  • A Unit keeps exactly one parent. An explicit middle table would give Unit two possible parents (module or submodule), 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 in clean() 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.
  • parent must 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: true in unit frontmatter routes the body into Unit.homework. kind makes 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_level keeps 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.
  • UnitProgress stays 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.yaml becomes 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.
  • kind values: lesson, homework, event. lesson is 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_order then id.
  • Nesting carries no access semantics.
  • Unit URLs are unchanged.

Open questions for the owner

  1. 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.
  2. Should kind="event" units appear in progress percentages, or count only lessons and homework?
  3. On AI Shipping Labs, should existing units with homework text be migrated to kind="homework", or should kind only apply to newly synced content?

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.