Production-oriented multi-tenant Student Management SaaS implemented with Flask + SQLite, designed for portability to PostgreSQL.
- Backend: Python, Flask, SQLAlchemy, Flask-Migrate (Alembic)
- Frontend: Server-rendered HTML, CSS, Vanilla JavaScript
- Database: SQLite (current), PostgreSQL-ready architecture
- Auth: Internal session-based auth (separate platform and tenant identities)
app/
blueprints/
auth/ # platform/tenant login + logout
platform/ # control plane (tenants/plans/features/limits/staff/audits)
tenant/ # tenant app (users, students, teachers, classes, attendance, exams, calendar, audits, student portal)
models/ # ORM domain models (all tenant domain entities carry tenant_id)
repositories/ # data access layer (tenant-scoped repositories enforce tenant filters)
services/ # business logic, capability resolution, usage enforcement, audit logging
security/ # request pipeline hooks, csrf, auth decorators, permission constants
templates/ # server-rendered pages
static/ # CSS + JS
cli.py # bootstrap and maintenance CLI commands
migrations/ # Alembic migration environment and versioned migrations
run.py # app entrypoint
Implemented centrally in request hooks + decorators:
- Resolve tenant context (
tenant_slugin path) - Verify tenant is active
- Resolve tenant capabilities (plan + feature entitlements + overrides + limits)
- Verify required feature enabled (decorator)
- Verify user permission (decorator)
- Verify limits before writes (service-level enforcement)
- Execute business logic (service layer)
- Write audit log entry
- Every tenant domain model has explicit
tenant_id. - Tenant repositories require
tenant_idat construction and always query with tenant filter. - Route decorators enforce session tenant = resolved tenant context.
- No platform routes access tenant academic domain data.
Data-driven tables:
plansfeaturesplan_feature_entitlementstenant_feature_overridesplan_limitstenant_limit_overrides
CapabilityService resolves effective profile as:
default feature config- merged with
plan feature config - merged with
tenant feature override config - with enabled-state precedence: tenant override > plan entitlement > disabled
Business modules consume only resolved capabilities.
Separate systems:
- Platform RBAC:
platform_users,platform_roles,platform_permissions, mapping tables - Tenant RBAC:
tenant_users,tenant_roles,tenant_permissions, mapping tables
Permission checks are string-based and route-level enforced with decorators.
Unified audit_logs table stores:
- tenant
- actor user + actor type
- action
- entity type + id
- request id
- metadata JSON
- timestamp
Both platform and tenant business operations produce audit logs.
- Structured logs with request_id, tenant_id, user_id
- Request ID generation +
X-Request-IDresponse propagation - Exception logging includes contextual IDs via logging filter
- Create environment file:
- Copy
.env.exampleto.env - Set a secure
SECRET_KEY
- Copy
- Install dependencies:
pip install -r requirements.txt
- Set Flask app:
- PowerShell:
$env:FLASK_APP='run.py'
- PowerShell:
- Run migrations:
python -m flask db upgrade
- Verify active database target:
python -m flask doctor-db
Important:
- If
DATABASE_URLis not set, STUMNG automatically uses project-rootstumng.dbwith an absolute path. - Avoid relative
DATABASE_URL=sqlite:///stumng.dbin environments that may change working directory.
python -m flask bootstrap-platform-admin --email admin@example.com --full-name "Platform Admin"This command:
- creates first platform user
- creates initial
platform_ownerrole - seeds and assigns baseline platform permissions
python run.pyVisit http://127.0.0.1:5000/auth/platform/login and login.
In Platform Portal dashboard:
- create plan(s)
- create feature definitions
- configure plan entitlements
- create tenant and assign plan
- configure tenant feature overrides and limits
python -m flask bootstrap-tenant-admin --tenant-slug your-tenant-slug --email owner@school.edu --full-name "Tenant Owner"This command:
- creates first tenant user
- creates initial
tenant_ownerrole - seeds and assigns baseline tenant permissions
Then login at http://127.0.0.1:5000/auth/tenant/login using tenant slug + credentials.
Seed realistic Indian mock data for a tenant:
python -m flask seed-cse-2022-2026 --tenant-slug springfield-high --students 250 --resetThis populates:
- 250 students (
CSE220001toCSE220250) - teachers
- class/sections
- attendance records
- exams and marks
- academic calendar events
- tenant audit logs for seeded operations
Platform staff can open a dedicated tenant page:
/platform/tenants/<tenant_id>
From this page they can:
- view tenant summary and usage counters
- suspend/archive/reactivate tenant
- assign/update tenant plan
- configure tenant feature overrides
- configure tenant limit overrides
- review recent tenant audit activity
All actions are permission-gated and route-level enforced.
Tenant module routes enforce these feature keys:
tenant_user_managementstudent_managementteacher_managementclass_managementattendanceexamsacademic_calendaraudit_logs
Create these features in Platform Portal and enable them via plan/override to unlock modules.
Common keys:
tenant.dashboard.readtenant.user.managestudent.read,student.createstudent.self.readteacher.read,teacher.createclass.read,class.createattendance.read,attendance.writeattendance.self.readexam.read,exam.create,exam.mark.writeexam.self.readcalendar.read,calendar.createcalendar.self.readtenant.audit.read
Each tenant can provide a student self-service view at:
/tenant/<tenant_slug>/student-portal
Tenant admins can:
- create a student portal account linked to a student profile
- link an existing tenant user account to a student profile
Student users can view (permission + feature controlled):
- profile and class assignment
- attendance history and summary
- exam marks
- upcoming calendar events
- Students use the standard tenant login page:
/auth/tenant/login - Tenant slug is mandatory along with student email and password
- On successful login, linked student users are redirected to
/tenant/<tenant_slug>/student-portal
If student login fails because credentials are unknown, use:
python -m flask set-tenant-user-password --tenant-slug springfield-high --email student.cse220001@springfield.eduTo provision student logins in bulk for all active students in a tenant:
python -m flask provision-student-logins --tenant-slug springfield-high --email-domain springfield.edu --max-accounts 0To backfill access for already linked student users:
python -m flask sync-student-portal-access --tenant-slug springfield-high- Alembic is configured under
migrations/ - Initial schema:
migrations/versions/20260304_0001_initial.py - Additional migration:
migrations/versions/20260304_0002_student_user_links.py - Future schema changes should be generated with
python -m flask db migrateand applied usingpython -m flask db upgrade
- CSRF tokens validated for all state-changing requests
- Secure session defaults (env-driven)
- Password hashing with Werkzeug
- Auth responses are set with no-store cache headers to prevent stale login-page back navigation
- Consistent authz failure responses without data leakage
- Internal auth only (no third-party identity dependency)