Home / Docs

TutorLoom documentation

Everything you need to install, configure, run and extend the open-source Community edition. Enterprise customers get the same platform, set up and supported by our team.

On this page

Introduction#

TutorLoom is a self-hosted, multi-tenant learning management system for companies, colleges and training institutes. One installation runs training for many organisations. Each organisation onboards its trainers and learners, groups them into batches, builds courses with videos, documents, tasks and quizzes, and tracks every learner’s progress.

The repository holds three applications:

  • Backend API: Python 3.13, Django 6 and Django REST Framework, at the repository root.
  • Web app: React 19, TypeScript and Vite, in frontend/.
  • Learner mobile app: Flutter for Android and iOS, in student_mobile_app/.

At a glance (v1.0.0)

Features117 across the Community and Enterprise editions
Portals4 behind one login: Super Admin, Organisation Admin, Trainer and Learner (see Roles and portals)
Coding judge7 languages, in the Enterprise edition
Automated tests396 backend, 945 web app and 342 mobile app (see Running tests)

Quickstart with Docker#

The recommended way to try it. Docker runs Postgres, Redis, the API, a Celery worker and the web app together. You need nothing else installed.

bash
git clone https://github.com/pacewisdomhq/TutorLoom.git
cd TutorLoom
docker compose up --build
ServiceAddress
Web apphttp://localhost:5173
API docs (Swagger)http://localhost:8010/api/docs/
ReDochttp://localhost:8010/api/redoc/
Django adminhttp://localhost:8010/admin/

Your source folders are mounted into the containers, so edits reload without a rebuild unless dependencies change. Database migrations run automatically every time the backend starts.

Ports

The API is published on 8010 because 8000 is often taken. Postgres and Redis are not published, so they never clash with ones you already run. Change host ports in a .env file next to docker-compose.yml, or inline:

bash
BACKEND_PORT=9000 FRONTEND_PORT=3000 docker compose up

The web app’s VITE_API_BASE_URL follows BACKEND_PORT automatically.

Everyday commands

bash
docker compose logs -f backend     # follow a service's logs
docker compose exec backend bash   # open a shell in the API container
docker compose exec backend python manage.py migrate
docker compose down                # stop; add -v to also delete the database

Your first organisation#

Create an admin user, then an organisation with that user as its admin.

  1. Create the user
    bash
    docker compose exec -e DJANGO_SUPERUSER_PASSWORD='SecurePassword123' backend \
      python manage.py createsuperuser --noinput \
      --email [email protected] --first_name Acme --last_name Admin
  2. Create the organisation and its first batch
    bash
    docker compose exec backend python manage.py create_initial_organization \
      --name "Acme Academy" --slug "acme-academy" \
      --email "[email protected]" --admin-email "[email protected]" --batch-name "Batch 1"
  3. Sign in

    Open http://localhost:5173 and sign in with the email and password from step 1.

Order matters. create_initial_organization links an existing user as the organisation admin. It can’t create the user, so run step 1 first. Other options: --slug, --email (organisation contact) and --batch-name (default "Batch 1").

Manual setup without Docker#

For development on your own machine. You need Python 3.13, Poetry 2.x, PostgreSQL, Redis, and Node.js with npm.

1. Install

bash
git clone https://github.com/pacewisdomhq/TutorLoom.git
cd TutorLoom
poetry env use python3.13
poetry install
poetry run pip install Pillow   # needed for image fields, not yet in pyproject.toml

Prefer pip? python3.13 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt

2. Configure

bash
cp .env.example .env

At minimum set SECRET_KEY, the DB_* variables (or DATABASE_URL), REDIS_URL and CELERY_BROKER_URL. See Configuration for every variable.

3. Database and first organisation

bash
poetry run python manage.py migrate
poetry run python manage.py createsuperuser      # you sign in with your email address
poetry run python manage.py create_initial_organization \
  --name "Acme Academy" \
  --admin-email [email protected]                   # an existing user, who becomes the organisation admin

4. Run the backend

bash
redis-server                                      # or: docker run -d -p 6379:6379 redis
poetry run python manage.py runserver
poetry run celery -A lms_core worker -l info      # background tasks

5. Run the web app

bash
cd frontend
npm install
cp .env.example .env   # set VITE_API_BASE_URL, e.g. http://localhost:8000/api
npm run dev            # http://localhost:5173

Configuration#

The backend reads its settings from environment variables, usually through a .env file at the repository root.

VariableWhat it doesExample

Web app

Variables for the web app must start with VITE_, otherwise Vite leaves them out of the browser bundle.

VariableWhat it doesExample
VITE_API_BASE_URLRequiredThe backend API root the web app callshttp://localhost:8000/api
VITE_CSRFTOKENOptional static CSRF token for non-browser or development setups(empty)

Roles and portals#

Each role has its own portal. One account can hold several roles and switch between them from the account menu without signing out.

RoleWhat they doRole in codeEdition
Super AdminRuns every organisation in the group and every userDjango superuserEnterprise
Organisation AdminRuns one organisation: people, batches, courses and progressorg_adminCommunity
TrainerDelivers assigned courses, grades work, tracks learnersteacherCommunity
LearnerFollows course roadmaps, submits work, tracks progressstudentCommunity
Job candidateTakes a coding interview through a private link, no account needed(none)Enterprise

Organisations and data separation#

A person has one login for the whole platform. Their membership in each organisation decides what they can see there.

  • One identity. A user has a single set of credentials across the platform.
  • Membership per organisation. OrganizationMember links a user to an organisation with a role: org_admin, teacher or student.
  • Scoped data. Courses, submissions and analytics queries filter on organization_id, so one organisation’s data stays out of another’s.

Courses and progress#

A course is made of chapters (modules), and each chapter is made of steps (nodes).

  1. An admin or trainer creates a course and adds chapters and steps.
  2. Each step holds content: a video, document or link, a task to submit, or a quiz.
  3. The course is assigned to a batch.
  4. Learners in the batch open the course between the batch’s start and end dates.
  5. As learners complete steps, StudentNodeProgress records it. Completion percentages update live on trainer dashboards and in Excel exports.
Points. Every completed step earns 10 points. Learners reach Novice at 100, Intermediate at 250 and Expert at 500.

Editions#

These docs cover the open-source Community edition. Enterprise builds on the same codebase.

CapabilityCommunityEnterprise
Organisation Admin, Trainer and Learner portalsYesYes
Course builder, prerequisites, quizzes, tasksYesYes
Bulk upload, grading, progress tracking, pointsYesYes
Super Admin console across offices or campusesNoYes
Assessments with a coding judge, certificatesNoYes
No-login coding interviewsNoYes
Google and Microsoft sign-in, audit logs, brandingNoYes

See the full comparison or talk to sales about Enterprise.

Learner mobile app#

A Flutter app for Android and iOS, in student_mobile_app/. It is for learners: trainers and admins use the web app.

A learner who belongs to several organisations picks one after signing in. Courses, progress and certificates are then scoped to it, and the app applies that organisation’s name, logo and colours.

NeedsVersion
Flutter3.47 stable
Dart SDK^3.13.0
AndroidAndroid Studio and an SDK
iOSXcode and CocoaPods on a Mac
bash
cd student_mobile_app
cp .env.example .env   # then set API_BASE_URL, no trailing slash
flutter pub get
flutter run
Create .env first. The file is bundled as an asset, so the build fails without it, and the error comes from asset bundling rather than at runtime.

To point a build at another backend without editing .env, use --dart-define. Release and CI builds should do this:

bash
flutter run --dart-define=API_BASE_URL=https://lms.example.com

Web app#

React 19, TypeScript and Vite, in frontend/.

bash
npm run dev             # development server
npm run build           # type-check and production build
npm run lint            # ESLint
npm run test            # Vitest, single run
npm run test:coverage   # Vitest with coverage
npm run preview         # preview a production build
Keep the build green. npm run build runs a full TypeScript check before bundling, and it must pass before any pull request.

Running tests#

Backend tests need a running PostgreSQL. Redis, S3 and Celery are stubbed automatically.

bash
poetry run pytest -p no:cacheprovider --no-cov -q   # fast run
poetry run pytest                                   # with coverage
poetry run pytest curriculum/ --no-cov -q           # one app
bash · web app
cd frontend
npm test
npm run lint
bash · mobile app
cd student_mobile_app
flutter test

At v1.0.0 the backend has 396 tests, the web app 945 and the mobile app 342.

Deploying to production#

bash
docker build -t lms-backend .
docker run -d -p 8000:8000 --env-file .env lms-backend

Before you go live

  • The image starts Django’s development server. In production run gunicorn lms_core.wsgi:application --bind 0.0.0.0:8000 behind a proxy that terminates TLS. gunicorn is already installed.
  • Set DEBUG=False, a strong SECRET_KEY, and explicit ALLOWED_HOSTS, FRONTEND_URL and CSRF_TRUSTED_ORIGINS.
  • Use USE_S3=True for uploads, and SES credentials for email.
  • Run a Celery worker next to the web process.
  • Deploy the web app separately as a static build: npm run build in frontend/.
Schedule daily analytics yourself. analytics.tasks.calculate_daily_analytics has no beat schedule in the project, so dashboards that rely on daily metrics stay empty until you schedule it.
Development defaults are not safe in production: the fallback SECRET_KEY, DEBUG=True, ALLOWED_HOSTS=* and Django’s runserver.

API overview#

Interactive docs live at /api/docs/ (Swagger) and /api/redoc/, with the OpenAPI schema at /api/schema/.

Every endpoint needs Authorization: Bearer <token>, except login, invites and password reset. Lists return 20 items per page. To try requests in Swagger, call POST /api/auth/login/, click Authorize and paste the access token.

AreaEndpoints
AuthPOST /api/auth/login/ · refresh/ · logout/ · forgot-password/ · reset-password/ · accept-invite/ · GET verify-invite/
ProfileGET/PATCH /api/users/me/ · POST /api/users/<id>/reinvite/
Roles/api/roles/ · /api/user-roles/ · update-role/ · delete-role/
Organisation/api/organizations/<org>/ · members/ · staff/ · batches/ · batches/<b>/students/ · students/ · …/bulk-upload-file/ · …/download-template/
Dashboards…/<org>/analytics/overview/ · …/batches/<b>/analytics/ · …/students/me/dashboard/ · …/teacher-dashboard/<teacher>/ · …/batches-overview/
Course builder/api/organizations/<org>/courses/ → modules/ → nodes/ · nodes/<n>/content/
Learning…/courses/<c>/roadmap/ · …/my-courses/ · …/my-progress/ · POST /api/nodes/<n>/complete/
Submissions/api/nodes/<n>/submit/ · task/submit/ · task/result/ · task/all-submissions/ · /api/quizzes/<q>/submit/
Grading and progress…/pending-evaluations/ · …/submissions/tasks/<s>/review/ · …/learner-progress/ · …/learner-progress/export/ · …/node-submission-details/
bash · sign in and call the API
curl -s -X POST http://localhost:8010/api/auth/login/ \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "password": "SecurePassword123"}'

# then send the access token from the response
curl -s http://localhost:8010/api/users/me/ -H "Authorization: Bearer <access token>"

Project structure#

repository root
lms_core/            # settings, root URLs, Celery app, email and storage utilities
accounts/            # users (email login), JWT auth, invites, password reset, profile
rbac/                # roles, role assignments, permission classes
organizations/       # organisations, staff, batches, students, bulk upload
curriculum/          # courses, chapters, steps, resources, tasks, quizzes, progress
analytics/           # admin, trainer and learner dashboards, daily metrics
gamification/        # points and levels
settings/            # shared Amazon SES client
frontend/            # React web app
student_mobile_app/  # Flutter learner app
docker-compose.yml   # full local stack

Each backend app has its own README.md with deeper notes.

Tech stack#

AreaTechnology
BackendPython 3.13, Django 6, Django REST Framework 3.16
AuthJWT via djangorestframework-simplejwt, with rotating refresh tokens and a blacklist
DatabasePostgreSQL
Cache and queueRedis, Celery, django-celery-results
StorageLocal filesystem or Amazon S3
EmailAmazon SES, or any Django mail backend
Spreadsheetspandas, openpyxl, xlrd for bulk uploads and exports
API docsOpenAPI 3 via drf-spectacular
Web appReact 19, TypeScript, Vite, React Router
Mobile appFlutter 3.47
Testspytest, pytest-django, pytest-cov, Vitest

Contributing#

Contributions are welcome. Read CONTRIBUTING.md and the Code of Conduct first.

  1. Fork and branch

    Fork the repository and create a feature branch.

  2. Keep code where it belongs

    Put code in the app that owns the feature, and scope every query to the organisation.

  3. Commit migrations with model changes

    The test suite runs makemigrations --check.

  4. Add tests

    Backend tests go in <app>/tests/; web app tests sit next to the component. Make sure poetry run pytest and npm test pass.

  5. Open a pull request

    Describe the change. Follow PEP 8 and match the style around your code.

Security#

Please don’t report vulnerabilities in public issues, discussions or pull requests. Use GitHub’s private vulnerability reporting and include the affected component, steps to reproduce, and the impact you expect.

We aim to acknowledge reports within 3 working days, keep you updated while we fix the issue, and credit you in the advisory unless you prefer to stay anonymous. Fixes land on main.

Licence#

The Community edition is licensed under the Apache License 2.0. Copyright 2026 Pace Wisdom Solutions Pvt. Ltd. See NOTICE for attribution.