All work
Exam results system for an educational institute
Django 5Next.js 16KeystaticPostgreSQLSQLiteTailwind CSS v4
7 statesFrom draft import to published results, each one an explicit gate
9 checksValidation issues an import has to clear before it can be approved
20,781Result records published from the approved dataset
- Exam results arrived as spreadsheets and were maintained in a desktop database. There was no controlled workflow between receiving the data and publishing it, and no record of what had been imported, checked or approved.
- The institute needed more than a results website. It needed a reliable way to take result data from source files, validate it, review the problems, approve a final dataset, and then use that same approved data across its public and administrative services.
- The public side had its own requirements. Students look up individual results by roll number, centres need consolidated results, and the whole experience had to work naturally in Urdu, in Nastaliq script that most layouts are not built for.
- Result records hold personal information, so they had to be reachable by anyone with the correct details while staying undiscoverable through search engines.
- Built the system around the full results lifecycle rather than around the website: import, validate, review, approve, publish, and then serve the approved dataset to every downstream output.
- A Django back office handles incoming files. Imports land in a staging area first, where problems are raised as a reviewable list against the batch they came from, and an import has no path that writes directly into live results.
- Modelled the workflow explicitly instead of leaving it to convention. A session moves through seven named states from draft to published, and approval is the step that copies staged rows forward into the approved tables.
- Publication produces a versioned, read-only snapshot. Administrative work and public serving are separated, so the public site always reads a known, approved set of results and can never be mid-import.
- Drove every output from that one approved dataset: roll number lookup, centre-wide results, and printable per-class result sheets generated for each centre rather than maintained by hand alongside the site.
- Built the public interface right-to-left throughout, on self-hosted Nastaliq and Naskh faces, with selective left-to-right runs for content like phone numbers and account details.
- Separated editorial publishing from result management. Announcements and site content live in a git-backed CMS, so the institute gets a reviewed and versioned workflow instead of edits made directly against the live site.
- The institute has one controlled process carrying exam results from incoming files to published records, with a record at every stage of what was imported, checked and approved.
- Result data is validated and reviewed before publication, and import problems surface as a clear list rather than failing silently or leaving a half-imported dataset behind.
- Students retrieve their own results by roll number, while centres work with consolidated and printable result sheets generated from the same approved source.
- Published result pages are kept out of search indexes, so results stay directly reachable by the people entitled to them without exposing personal records to search.
- Most of all, the public website is no longer the system. It is one output of a results management platform, with import, validation, approval, publishing, centre reporting and public access all built around the same trusted dataset.
Technical detail
- Validation covers nine distinct problems, each raised against the batch rather than aborting it: duplicate roll numbers, duplicate registration numbers, unrecognised centre, district or class, invalid dates of birth, unmapped mark text, missing required fields and malformed file headers. A reviewer sees the whole list at once instead of fixing one error per run.
- Staged and approved rows live in separate tables. Validation issues attach to the staged batch, and approval copies forward only what passed -- so a bad import is discarded rather than rolled back out of live data.
- Duplicates are caught on both roll number and registration number within a class, enforced as database constraints, because in practice a spreadsheet can repeat either one independently.
- District and class names arrive spelled differently in every spreadsheet. Rather than correcting them by hand each intake, an unrecognised name becomes a resolvable issue that records an alias, so the same spelling is recognised automatically the following year.
- The public site opens its database read-only and holds a single connection in module scope. There is no write path in the application at all, which makes an entire category of vulnerability structurally impossible rather than merely guarded against.
- Result pages are noindex and disallowed in robots.txt. The data has to be reachable by anyone holding a roll number and must not be enumerable through search, and those two requirements only coexist if the pages are excluded explicitly.
- Result announcements generate themselves: the site reads the latest published year per term from the data and renders a dated notice for a fixed window, so the announcement can never disagree with what the lookup returns.
- Content visibility is scheduled against both Hijri and Gregorian dates, since the academic calendar this serves runs on the Hijri year.