Examination Middleware is a robust, secure, and automated bridge designed to streamline the digitization and submission of physical examination answer sheets to the Moodle Learning Management System (LMS). It acts as an intelligent intermediary between the physical examination hall and the digital grading environment.
In academic institutions transitioning to digital grading, handling physical answer scripts presents significant logistical challenges:
- Manual Labor: Individually scanning, renaming, and uploading hundreds of answer scripts to specific Moodle assignments is time-consuming and inefficient.
- Human Error: Manual processes are prone to errors such as uploading the wrong file to a student's profile or mislabeling files.
- Security & Integrity: Direct database manipulation or unverified bulk uploads can compromise the chain of custody.
- Student Verification: Students often lack a mechanism to verify that their specific physical paper was scanned and submitted correctly before grading begins.
This middleware solves these issues by decoupling the scanning/uploading process from the submission process, introducing a secure validation layer.
The system utilizes a 3-Step "Upload-Verify-Push" Workflow:
- Bulk Ingestion: Administrative staff upload bulk batches of scanned PDF/Images.
- Intelligent Processing: The system parses filenames (e.g.,
123456_MATH101.pdf) to extract the Student Register Number and Subject Code, automatically mapping them to the correct Moodle Assignment ID. - Student-Led Submission: Students log in using their Moodle credentials. They view only their specific answer scripts and trigger the final submission to Moodle. This ensures non-repudiation and student verification.
graph LR
A[Physical Scans] -->|Bulk Upload| B(Staff Portal / Middleware)
B -->|Parse & Store| C{PostgreSQL DB}
D[Student] -->|Login via Moodle Creds| E(Student Portal)
E -->|Fetch Pending Papers| C
E -->|Trigger Submission| F[Moodle LMS]
F -->|Token Exchange| E
The database is designed for data integrity and auditability. Key models include:
| Model | Description |
|---|---|
ExaminationArtifact |
The core entity representing a scanned paper. Stores UUID, file path, hash (SHA-256), extracted metadata (Reg No, Subject), and current WorkflowStatus (e.g., PENDING, SUBMITTED_TO_LMS). |
SubjectMapping |
Configuration table mapping a Subject Code (e.g., 19AI405) to a specific Moodle Course ID and Assignment ID. |
StaffUser |
Accounts for administrative staff authorized to perform bulk uploads. |
StudentSession |
Manages ephemeral student sessions. Stores encrypted Moodle access tokens used to perform submissions on behalf of the student. |
AuditLog |
A rigid ledger tracking every action (Upload, View, Submit) with IP addresses and timestamps to ensure a chain of custody. |
SubmissionQueue |
A buffer for handling Moodle API failures or maintenance windows, ensuring no submission is lost. |
- Mapping: Admin configures the
SubjectMappingtable (e.g., SubjectCS101targets Moodle AssignmentID: 55). - Scanning: Examination cell scans answer sheets using the naming convention:
{RegisterNumber}_{SubjectCode}.pdf.
- Login: Staff logs into the Staff Portal.
- Bulk Upload: Staff drags and drops folders of scanned files.
- Validation: The system instantly validates filenames. Invalid files are rejected; valid files are hashed and stored as
ExaminationArtifactswith statusPENDING.
- Login: Student logs into the Student Portal using their university Moodle username and password.
- Dashboard: The system displays all papers tagged with their Register Number.
- Review: Student previews the PDF to ensure it is their paper.
- Submit: Student clicks "Submit".
- Backend Action: The system authenticates with Moodle using the student's token.
- Backend Action: Uploads the file to Moodle's draft area.
- Backend Action: Finalizes the submission for grading.
- Confirmation: The status updates to
SUBMITTED_TO_LMS.
- Python 3.10+
- PostgreSQL 14+
- Moodle LMS with Web Services enabled
- Redis (optional, for background tasks)
cd exam_middlewarepython -m venv venv
# Windows
.\venv\Scripts\activate
# Linux/Mac
source venv/bin/activatepip install -r requirements.txtCopy .env.example to .env and update the values:
copy .env.example .envEdit .env with your settings:
# Database
DATABASE_URL=postgresql+asyncpg://postgres:password@localhost:5432/exam_middleware
# Security
SECRET_KEY=your-super-secret-key-change-in-production
ENCRYPTION_KEY=your-32-byte-encryption-key-here
# Moodle Configuration
MOODLE_BASE_URL=https://your-moodle-site.com
MOODLE_ADMIN_TOKEN=your-admin-token
# Subject Mappings (subject_code:assignment_id)
SUBJECT_ASSIGNMENT_MAP=19AI405:4,19AI411:6,ML:2# Create database
psql -U postgres -c "CREATE DATABASE exam_middleware;"python init_db.pyThis will:
- Create all required tables
- Create default admin user (username:
admin, password:admin123) - Seed subject-to-assignment mappings
- Configure system settings
python run.pyThe server will start at http://localhost:8000
| Portal | URL |
|---|---|
| Staff Upload Portal | http://localhost:8000/portal/staff |
| Student Portal | http://localhost:8000/portal/student |
| API Documentation | http://localhost:8000/docs |
| ReDoc | http://localhost:8000/redoc |
| Health Check | http://localhost:8000/health |
Uploaded files must follow this naming pattern:
{RegisterNumber}_{SubjectCode}.{extension}
Examples:
611221104088_19AI405.pdf611221104089_ML.jpg611221104090_19AI411.png
Rules:
- Register Number: Exactly 12 digits
- Subject Code: 2-10 alphanumeric characters
- Extensions: pdf, jpg, jpeg, png
- Username/password-based JWT authentication
- Default credentials:
admin/admin123 - Token expires in 8 hours
- Moodle credential verification
- Token exchange with Moodle LMS
- Encrypted token storage for submissions
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/staff/login |
Staff login |
| POST | /auth/student/login |
Student login with Moodle credentials |
| POST | /auth/student/logout |
Student logout |
| Method | Endpoint | Description |
|---|---|---|
| POST | /upload/single |
Upload single file |
| POST | /upload/bulk |
Upload multiple files |
| POST | /upload/validate |
Validate filename |
| Method | Endpoint | Description |
|---|---|---|
| GET | /student/dashboard |
Get assigned papers |
| GET | /student/paper/{id}/view |
View paper content |
| POST | /student/submit/{id} |
Submit paper to Moodle |
| GET | /student/submission/{id}/status |
Check submission status |
| Method | Endpoint | Description |
|---|---|---|
| GET | /admin/mappings |
List subject mappings |
| POST | /admin/mappings |
Create mapping |
| GET | /admin/queue |
View submission queue |
| GET | /admin/stats |
System statistics |
-
Enable Web Services
- Site administration β Advanced features β Enable web services
-
Create External Service
- Site administration β Server β Web services β External services
- Create service: "FileUpload"
- Add functions:
core_webservice_get_site_infomod_assign_save_submissionmod_assign_submit_for_grading
-
Create Token
- Site administration β Server β Web services β Manage tokens
- Create token for admin user with "FileUpload" service
-
Enable Upload
- Ensure
webservice/upload.phpis accessible - Configure max upload size in Moodle settings
- Ensure
exam_middleware/
βββ app/
β βββ api/
β β βββ routes/
β β βββ admin.py # Admin endpoints
β β βββ auth.py # Authentication
β β βββ health.py # Health check
β β βββ student.py # Student endpoints
β β βββ upload.py # File upload
β βββ core/
β β βββ config.py # Configuration
β β βββ security.py # Security utilities
β βββ db/
β β βββ database.py # Database connection
β β βββ models.py # SQLAlchemy models
β βββ schemas/
β β βββ schemas.py # Pydantic schemas
β βββ services/
β β βββ artifact_service.py # Artifact management
β β βββ file_processor.py # File processing
β β βββ moodle_client.py # Moodle API client
β β βββ submission_service.py # Submission workflow
β βββ templates/
β β βββ staff_upload.html # Staff portal
β β βββ student_portal.html # Student portal
β βββ main.py # FastAPI application
βββ uploads/ # Temporary upload storage
βββ storage/ # Permanent file storage
βββ .env # Environment configuration
βββ .env.example # Example configuration
βββ init_db.py # Database initialization
βββ run.py # Application runner
βββ requirements.txt # Python dependencies
-
Create test files with correct naming:
611221104088_19AI405.pdf 611221104089_ML.pdf -
Login to Staff Portal with
admin/admin123 -
Upload the test files
-
Login to Student Portal with Moodle student credentials and register number
-
View and submit papers to Moodle
# Staff Login
curl -X POST http://localhost:8000/auth/staff/login \
-F "username=admin" \
-F "password=admin123"
# Upload File (use token from login)
curl -X POST http://localhost:8000/upload/single \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@611221104088_19AI405.pdf" \
-F "exam_session=2024SPRING"
# Health Check
curl http://localhost:8000/health- Staff uploads scanned papers through the Staff Portal
- System extracts metadata from filenames (register number, subject code)
- Papers are validated and stored with unique transaction IDs
- Students login with Moodle credentials and register number
- Students view their assigned papers
- Students submit papers directly to Moodle assignments
- System executes 3-step Moodle submission:
- Upload file to Moodle
- Save submission draft
- Submit for grading
- Password Hashing: bcrypt with 12 rounds
- Token Encryption: AES-256 (Fernet) for Moodle tokens
- JWT Tokens: Short-lived access tokens
- File Validation: Extension and size checks
- Audit Logging: All operations logged
- CORS: Configurable origin whitelist
For production deployment with Celery:
# Start Redis
redis-server
# Start Celery worker
celery -A app.tasks worker --loglevel=info- Health endpoint:
/health - Logs:
exam_middleware.log - Database audit table:
audit_logs
Ensure PostgreSQL is running and credentials in .env are correct
Verify MOODLE_ADMIN_TOKEN has required capabilities
Check Moodle external service configuration
Check file size limits in Moodle
Verify assignment allows file submissions
Not Licensed Yet.
- Fork the repository
- Create feature branch
- Commit changes
- Push to branch
- Create Pull Request