Skip to main content

learning app

Built-in learning management — author courses, deliver content, assess via quizzes, issue certificates.

Models (14)

  • Course — top-level training unit; FK to org; scope (national / regional / chapter); passing_score_percent, allow_retakes, max_attempts, optional compliance_requirement FK
  • CourseModule — ordered section of a course
  • Lesson — content block in a module; content_type (rich_text / video / document / image / external_link); tracks PDF conversion status for uploaded PowerPoints
  • Assessment — a quiz attached to either a CourseModule or the Course itself (course-level = final assessment); passing_score_percent, max_attempts, shuffle_questions, time_limit_minutes
  • Question — a question inside an Assessment; question_type (multiple_choice / true_false / multi_select / short_answer / free_form)
  • QuestionOption — an answer choice belonging to a Question
  • CourseAssignment — a course assigned to a target (individual membership / chapter / region / organization / status / role); due_date, is_required
  • AssignmentRule — auto-assigns a course when a membership status transitions to one of trigger_on_statuses; due_days_after_trigger
  • Enrollment — joins a Membership to a Course; status (not_started / in_progress / completed / failed / expired), completed_at, final_score_percent, deadline-reminder dedup flags
  • LessonProgress — append-only event log for lesson views/completions
  • QuizAttempt — a member's attempt at an Assessment; attempt_number, score_percent, passed, is_graded
  • QuizAnswer — a single answer a member gave within a QuizAttempt; links to QuestionOption(s) or free-text, points_earned, grader_feedback
  • Certificate — issued on a passing Enrollment; certificate_number, pdf_key, verification_token
  • OrgLearningDefaults — per-org auto-assignment defaults (auto-assign national courses on publish, default due days), one row per org, created on demand

There is no separate CourseCompletion or AssessmentResult model, and no MasteredSkill model — none of the three appear anywhere in this app's git history. Completion is tracked directly on Enrollment (status="completed", completed_at, final_score_percent) plus the resulting Certificate; assessment submissions are QuizAttempt + QuizAnswer. There's also no skills-on-completion feature — MemberSkill (in the members app) is a self-reported/LinkedIn-sourced list with no link back to learning.

Key endpoints

Mounted at /api/learning/ (see backend/greekmanage/urls.py). Most models are exposed via DefaultRouter-registered viewsets; only the non-CRUD routes are listed here.

URLPurpose
GET/POST /api/learning/courses/List/create courses (scoped to what the user can see/author)
GET/POST /api/learning/my-courses/The current user's enrollments (EnrollmentViewSet)
GET/POST /api/learning/attempts/Start/list quiz attempts (QuizAttemptViewSet)
GET/POST /api/learning/answers/Submit/grade quiz answers (QuizAnswerGradeViewSet)
GET /api/learning/my-certificates/The current user's issued certificates
GET /api/learning/verify/<verification_token>/Public certificate verification page (no auth)
GET /api/learning/dashboard/org/<org_id>/ .../region/<region_id>/ .../chapter/<chapter_id>/ .../course/<course_id>/Completion dashboards by scope
GET /api/learning/reports/completion.xlsxCompletion report export
POST /api/learning/import/completions/Bulk-import external completions
GET/PATCH /api/learning/org-defaults/OrgLearningDefaults
GET /api/learning/meta/Static metadata for authoring UI

Permissions

Defined in backend/apps/learning/permissions.py, all subclass TierPermission:

  • CanAuthorCourses — national/org admins or regional admins; chapter authoring is deferred (not yet built)
  • CanGradeAssessments — national/regional admins or chapter officers/presidents with authority over the submitter's chapter
  • CanViewDashboard — same admin/officer roles, gates dashboard access
  • IsCourseAuthor — the course's created_by or a higher-scope admin (used for edit/delete)
  • IsEnrollmentOwner — only the member who owns the underlying Enrollment can act on their own QuizAttempt

Background tasks

Celery tasks in backend/apps/learning/tasks.py:

  • learning.expand_assignment — expands a CourseAssignment targeting a chapter/region/org/status/role into per-membership Enrollment rows
  • learning.auto_assign_on_status_change — runs on Membership status change; matches against the AssignmentRule set
  • learning.generate_certificate — renders the certificate PDF, uploads it, and creates the Certificate row for a passing Enrollment
  • learning.send_deadline_reminders — 7 / 3 / 1 day before an enrollment's due_date
  • learning.mark_overdue_enrollments — flips overdue enrollments and sends a one-time notification
  • learning.regenerate_course_embeddings — re-indexes a course's content for AI search
  • learning.convert_lesson_document — converts an uploaded PowerPoint lesson document to an inline-viewable PDF preview via the Gotenberg sidecar

External integrations

  • Gotenberg (PowerPoint → PDF conversion for legacy training material) — a LibreOffice-over-HTTP sidecar the backend POSTs to at settings.GOTENBERG_URL (default http://gotenberg:3000), instead of bundling LibreOffice in the backend image

Notable patterns

Auto-assignment

Two mechanisms populate Enrollment rows automatically:

  • AssignmentRule(organization=, course=, trigger_on_statuses=[...], due_days_after_trigger=) — fires via learning.auto_assign_on_status_change whenever a Membership.status transition matches one of trigger_on_statuses.
  • OrgLearningDefaults.auto_assign_national_on_publish — when enabled, publishing a national-scope Course auto-creates a CourseAssignment targeting every status in OrgLearningDefaults.target_statuses. Fires once per course (tracked via Course.auto_assigned_at).

A CourseAssignment targeting a chapter/region/org/status/role is expanded into individual Enrollment rows by the learning.expand_assignment task.

Quiz grading

Five question types (backend/apps/learning/services/grading.py):

  • Multiple choice / true-false — auto-graded, exact option-set match
  • Multi-select — auto-graded with partial credit: (correct_selected − wrong_selected) / total_correct, floored at zero
  • Short answer — auto-graded, exact case-insensitive/trimmed text match against any QuestionOption marked is_correct
  • Free form — always requires manual grading; QuizAnswer.points_earned stays null and QuizAttempt.is_graded=False until a grader submits a score

Manual review queue: GET /api/learning/attempts/pending-grading/ (a custom action on QuizAttemptViewSet, scoped to attempts the caller has grading authority over).

Compliance linkage

Course.compliance_requirement — an optional FK to compliance.NationalRequirement. When learning.generate_certificate issues a certificate for a linked course, it best-effort creates a synthetic ComplianceSubmission (type ATTESTATION) against the member's ChapterComplianceStatus row for that requirement — but only if that row already exists (i.e. the requirement has been seeded to the chapter). Linkage failures are logged and swallowed; they never roll back certificate issuance.

Certificate verification

GET /api/learning/verify/<verification_token>/ is a public page (no auth) backed by CertificateVerifyView, keyed off Certificate.verification_token rather than the certificate's primary key.

Document conversion

Uploaded PowerPoint lesson documents go through learning.convert_lesson_document, which POSTs the file to the Gotenberg sidecar ({GOTENBERG_URL}/forms/libreoffice/convert) to produce an inline-viewable PDF and tracks progress via Lesson.document_conversion_status (not_needed / pending / ready / failed). Native PDF uploads skip conversion (document_preview_key = document_key).

Code paths

  • Models: backend/apps/learning/models.py
  • Views: backend/apps/learning/views.py
  • Permissions: backend/apps/learning/permissions.py
  • Tasks: backend/apps/learning/tasks.py
  • Services (grading, completion, assignment, certificate, reports, import, notifications, progress, conversion): backend/apps/learning/services/