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, optionalcompliance_requirementFKCourseModule— ordered section of a courseLesson— content block in a module;content_type(rich_text / video / document / image / external_link); tracks PDF conversion status for uploaded PowerPointsAssessment— a quiz attached to either aCourseModuleor theCourseitself (course-level = final assessment);passing_score_percent,max_attempts,shuffle_questions,time_limit_minutesQuestion— a question inside anAssessment;question_type(multiple_choice / true_false / multi_select / short_answer / free_form)QuestionOption— an answer choice belonging to aQuestionCourseAssignment— a course assigned to a target (individual membership / chapter / region / organization / status / role);due_date,is_requiredAssignmentRule— auto-assigns a course when a membership status transitions to one oftrigger_on_statuses;due_days_after_triggerEnrollment— joins aMembershipto aCourse;status(not_started / in_progress / completed / failed / expired),completed_at,final_score_percent, deadline-reminder dedup flagsLessonProgress— append-only event log for lesson views/completionsQuizAttempt— a member's attempt at anAssessment;attempt_number,score_percent,passed,is_gradedQuizAnswer— a single answer a member gave within aQuizAttempt; links toQuestionOption(s) or free-text,points_earned,grader_feedbackCertificate— issued on a passingEnrollment;certificate_number,pdf_key,verification_tokenOrgLearningDefaults— 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.
| URL | Purpose |
|---|---|
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.xlsx | Completion 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 chapterCanViewDashboard— same admin/officer roles, gates dashboard accessIsCourseAuthor— the course'screated_byor a higher-scope admin (used for edit/delete)IsEnrollmentOwner— only the member who owns the underlyingEnrollmentcan act on their ownQuizAttempt
Background tasks
Celery tasks in backend/apps/learning/tasks.py:
learning.expand_assignment— expands aCourseAssignmenttargeting a chapter/region/org/status/role into per-membershipEnrollmentrowslearning.auto_assign_on_status_change— runs onMembershipstatus change; matches against theAssignmentRulesetlearning.generate_certificate— renders the certificate PDF, uploads it, and creates theCertificaterow for a passingEnrollmentlearning.send_deadline_reminders— 7 / 3 / 1 day before an enrollment'sdue_datelearning.mark_overdue_enrollments— flips overdue enrollments and sends a one-time notificationlearning.regenerate_course_embeddings— re-indexes a course's content for AI searchlearning.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(defaulthttp://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 vialearning.auto_assign_on_status_changewhenever aMembership.statustransition matches one oftrigger_on_statuses.OrgLearningDefaults.auto_assign_national_on_publish— when enabled, publishing a national-scopeCourseauto-creates aCourseAssignmenttargeting every status inOrgLearningDefaults.target_statuses. Fires once per course (tracked viaCourse.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
QuestionOptionmarkedis_correct - Free form — always requires manual grading;
QuizAnswer.points_earnedstaysnullandQuizAttempt.is_graded=Falseuntil 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/