Django REST Framework backend powering the RemoteVote NG electronic voting platform. Provides the full electoral management API including voter identity verification, election lifecycle management, result collation, staff onboarding, accreditation, and an audit trail.
- Overview
- Tech Stack
- Project Structure
- Data Models
- API Endpoints
- Authentication
- Environment Variables
- Getting Started (Local Development)
- Database
- Seeding the Database
- Running Tests
- Deployment (Vercel)
- Role-Based Access Control
RemoteVote NG is a simulated national e-voting infrastructure built for the Nigerian electoral context. The backend implements:
- NIMC Identity Verification — NIN-based biometric identity matching at the point of accreditation
- Multi-role RBAC — Granular permissions for 13 distinct user roles across INEC HQ and field levels
- Full Election Lifecycle —
drafted → upcoming → active → collation → closedstate machine - Cryptographic Audit Trail — Every CRUD operation is automatically logged via Django signals
- Staff Onboarding — Token-based invitation system for pre-provisioning electoral officials
- Result Sheet Upload — Digital equivalent of INEC Form EC8A with overvoting detection
- Bulk CSV Operations — Import/export support for polling units, NIMC records, and invitations
| Layer | Technology |
|---|---|
| Framework | Django 4.2+ |
| API | Django REST Framework 3.14+ |
| Auth | DRF Token Authentication |
| Database (dev) | SQLite 3 |
| Database (prod) | PostgreSQL (via Prisma Postgres / Neon) |
| CORS | django-cors-headers |
| DB Switching | dj-database-url |
| Brevo (Sendinblue) Transactional API | |
| Config | python-dotenv |
| Deployment | Vercel (Python serverless) |
backend/
├── api/
│ ├── management/
│ │ └── commands/
│ │ └── seed_data.py # Database seeder (200+ records per model)
│ ├── migrations/ # Django migrations
│ │ └── 0005_provision_secretary.py # Auto-provisions INEC Secretary account
│ ├── admin.py # Django admin configuration
│ ├── auth_backend.py # Custom NIN/Staff ID auth backend
│ ├── brevo.py # Brevo transactional email helpers
│ ├── middleware.py # Thread-local request user capture (for audit logs)
│ ├── models.py # All data models
│ ├── serializers.py # DRF serializers
│ ├── signals.py # Auto audit logging via post_save / post_delete
│ ├── tests.py # Unit test suite
│ ├── urls.py # App URL patterns
│ └── views.py # All API views
├── backend/
│ ├── settings.py # Project settings (env-driven)
│ ├── urls.py # Root URL config
│ └── wsgi.py # WSGI entrypoint (Vercel compatible)
├── vercel.json # Vercel deployment configuration
├── requirements.txt # Python dependencies
└── .env # Environment variables (not committed)
| Model | Description |
|---|---|
NIMCRecord |
Simulated NIMC identity database (NIN + biometric hash) |
PollingUnit |
Geographic polling locations with autogenerated PU-XXXXXX IDs |
ElectoralUser |
Extended AbstractUser with 13-role RBAC and autogenerated staff numbers |
OTPVerification |
MFA tokens for signup, password reset, and ballot authorization |
Election |
Full lifecycle election with type classification and eligible-states filtering |
Candidate |
Party candidates linked to elections |
ResultSheet |
Digital Form EC8A with overvoting enforcement |
DisputeLog |
Agent/observer dispute flag submissions |
ElectionParticipation |
One-person-one-vote enforcement (unique voter + election) |
AuditLog |
Automatic CRUD audit trail via Django signals |
StaffInvitation |
Secure token-based pre-provisioning for electoral staff |
AccreditationApplication |
Portal for media, domestic and international observers |
ElectionClosureApproval |
Multi-signature election closure enforcement |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/auth/signup/ |
Register a new voter (NIN + NIMC verification) |
POST |
/api/auth/login/ |
Login with NIN or Staff ID + password |
POST |
/api/auth/verify-otp/ |
Verify OTP for MFA flows |
POST |
/api/auth/request-otp/ |
Request a new OTP |
POST |
/api/auth/reset-password/ |
Password reset |
GET |
/api/auth/me/ |
Get current authenticated user profile |
PATCH |
/api/auth/me/ |
Update profile |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/elections/ |
List public/active elections (voters) |
GET/POST |
/api/commissioner/elections/ |
Commissioner election management |
POST |
/api/commissioner/elections/<id>/advance/ |
Advance election status |
DELETE |
/api/commissioner/elections/<id>/delete/ |
Delete a drafted election |
POST |
/api/commissioner/elections/<id>/candidates/ |
Add candidate |
DELETE |
/api/commissioner/elections/<id>/candidates/<cid>/remove/ |
Remove candidate |
POST |
/api/vote/ |
Cast a vote |
GET |
/api/results/<id>/ |
Get election results |
| Method | Endpoint | Description |
|---|---|---|
GET/POST |
/api/polling-units/ |
List or create polling units (bulk POST supported) |
PUT/PATCH/DELETE |
/api/polling-units/<id>/ |
Update or delete a polling unit |
| Method | Endpoint | Description |
|---|---|---|
GET/POST |
/api/nimc-records/ |
List or create NIMC records (bulk POST supported) |
PUT/PATCH/DELETE |
/api/nimc-records/<id>/ |
Update or delete a NIMC record |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/onboarding/invite/ |
Send staff invitation (bulk array supported) |
GET |
/api/onboarding/invitations/ |
List all staff invitations |
POST |
/api/onboarding/invite/<pk>/resend/ |
Resend an invitation |
GET |
/api/onboarding/accept/<token>/ |
Accept a staff invitation |
POST |
/api/onboarding/accreditation/ |
Submit accreditation application |
POST |
/api/onboarding/accreditation/<pk>/review/ |
Review an accreditation |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/secretary/metrics/ |
Dashboard metrics for INEC Secretary |
GET |
/api/audit-logs/ |
Full system audit trail |
GET |
/api/result-sheets/ |
View result sheets |
POST |
/api/disputes/ |
File a dispute |
The API uses DRF Token Authentication. Include the token in the Authorization header:
Authorization: Token <your_auth_token>Tokens are returned on successful login via /api/auth/login/.
The custom auth backend (auth_backend.py) supports login with either:
- NIN (National Identification Number) — for voters
- Staff ID — for INEC staff
Create a .env file in the backend/ directory:
# Django
DJANGO_SECRET_KEY=your-super-secret-key-here
DJANGO_DEBUG=True
# Database (leave empty for local SQLite, set for PostgreSQL in production)
DATABASE_URL=
# INEC Secretary Default Account (provisioned on first migration)
SECRETARY_NIN=99999999999
SECRETARY_STAFFID=STAFF-SECRETARY-2026
SECRETARY_DEFAULT_PASSWORD=SecPass2026!
SECRETARY_EMAIL=secretary@remotevoteng.org
SECRETARY_NAME=INEC Secretary HQ
SECRETARY_STATE=FCT
SECRETARY_LGA=Abuja Municipal
# Brevo Transactional Email
BREVO_API_KEY=your-brevo-api-key
BREVO_SENDER_EMAIL=noreply@remotevoteng.org
BREVO_SENDER_NAME=RemoteVote NG
# Frontend URL (for email links)
FRONTEND_URL=http://localhost:3000
---
## Frontend Integration
- **API base path:** `/api/` (example local backend: `http://localhost:8000/api/`).
- **Auth flow / endpoints:**
- Register: `POST /api/auth/register/` (send NIN and user details)
- Verify OTP: `POST /api/auth/verify-otp/` (NIN + code)
- Login: `POST /api/auth/login/` (voter_id or staff_id + password)
- Note: For staff, the frontend must send the field name `staff_id` containing the staff number (e.g. `STAFF-PO-001`). Do NOT send `staffid` or other variants — the backend expects `staff_id`.
- Forgot password: `POST /api/auth/forgot-password/` (NIN)
- Reset password: `POST /api/auth/reset-password/` (NIN + code + new password)
- Profile: `GET /api/auth/profile/` (authenticated)
- **Auth header:** Include token on protected requests:
```http
Authorization: Token <your_auth_token>-
Activation links: The backend uses
FRONTEND_URLto compose invitation/activation links, e.g.${FRONTEND_URL}/onboard?token=<token>; ensure the frontend route/onboardreadstokenfrom query parameters and calls the accept endpoint. -
CORS: Development allows all origins (
CORS_ALLOW_ALL_ORIGINS=True). For production, set explicit allowed origins matching the deployed frontend. -
Error handling: Login may return
code: "unverified"when OTP verification is required — present the OTP entry UI and callPOST /api/auth/verify-otp/. -
Example login (fetch):
fetch('http://localhost:8000/api/auth/login/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
// Voter login example
// body: JSON.stringify({ voter_id: 'VOTER-XXXX', password: 'pass' })
// Staff login example (use staff_id):
body: JSON.stringify({ staff_id: 'STAFF-PO-001', password: 'Password2026!' })
})
.then(r => r.json())
.then(data => console.log(data))- Frontend env: The frontend only needs to know its own public
FRONTEND_URL(used server-side to generate links) and the backend base URL during development.
---
## Getting Started (Local Development)
### Prerequisites
- Python 3.10+
- pip
### Setup
```bash
# 1. Navigate to the backend directory
cd backend
# 2. Create and activate virtual environment
python -m venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # macOS/Linux
# 3. Install dependencies
pip install -r requirements.txt
# 4. Create your .env file (see Environment Variables above)
cp .env.example .env
# 5. Apply database migrations
python manage.py migrate
# 6. Start the development server
python manage.py runserver
The API will be available at http://localhost:8000/api/
SQLite is used automatically when DATABASE_URL is not set in .env. The database file is stored at backend/db.sqlite3.
Note: When running tests, the backend always uses SQLite regardless of
DATABASE_URLto ensure fast, isolated test runs.
Set the DATABASE_URL environment variable to your PostgreSQL connection string:
DATABASE_URL=postgresql://user:password@host:5432/dbnamePopulate the database with realistic Nigerian mock data:
# Seed with 200+ records per model
python manage.py seed_data
# Clear existing data and re-seed fresh
python manage.py seed_data --clearWhat gets seeded:
| Model | Count |
|---|---|
| NIMCRecord | 250 |
| PollingUnit | 250 |
| ElectoralUser (staff) | ~220 across all roles |
| ElectoralUser (voters) | ~200 |
| Election | 6 (Presidential, Senate, House, Governorship ×2, Assembly) |
| Candidate | 5–7 per election |
| ResultSheet | 200 |
| DisputeLog | 200 |
| ElectionParticipation | 200 |
| StaffInvitation | 200 |
| AccreditationApplication | 200 |
Default seed passwords:
- Staff:
Password2026! - Voters:
Voter2026! - Secretary: configured via
.env(default:SecPass2026!)
python manage.py testAll tests run against an isolated local SQLite database. The test suite covers:
- Voter signup and NIN verification
- OTP generation and validation
- Staff invitation flow
- Audit log signal handling
- Election participation constraints
The backend is configured for serverless deployment on Vercel via vercel.json in the backend/ directory.
- Push the
backend/folder to a GitHub repository - Connect the repository to Vercel
- Set the Root Directory to
backend - Add all
.envvariables to Vercel's Environment Variables dashboard - Deploy — Vercel will auto-detect the Python runtime
# Via Vercel CLI or a one-time build hook
python manage.py migrateThe INEC Secretary account is automatically provisioned during the migration run.
| Role | Code | Description |
|---|---|---|
| Registered Voter | voter |
Can authenticate and cast votes |
| Prospective | prospective |
Pre-registration state |
| Commissioner | commissioner |
Creates/manages elections and candidates |
| Secretary | secretary |
Full system oversight + metrics dashboard |
| Presiding Officer | po |
Submits Form EC8A result sheets |
| Asst. Presiding Officer | apo |
Field support role |
| Supervisory P.O. | spo |
Supervises multiple polling units |
| Collation Officer | co |
Collates ward-level results |
| Returning Officer | ro |
Signs off on multi-level collation |
| Party Agent | agent |
Monitors polling on behalf of parties |
| Media | media |
Accredited press personnel |
| Observer | observer |
Domestic/international election observer |
| Auditor | auditor |
Cybersecurity and system audit access |
MIT © RemoteVote NG — Studio3 Launchpad 2026