Skip to main content

Overview

Admin routes provide full CRUD operations for managing the application. All routes require authentication and admin privileges. Security: All admin routes protected by:
@login_required
@admin_required

Admin Dashboard

/admin
GET
Display admin dashboard with statistics
Route Decorator:
@bp.route("/admin")
@login_required
@admin_required
Returns:
  • total_challenges: All challenges count
  • released_challenges: Challenges with release_at <= now
  • unreleased_challenges: Challenges with release_at > now
  • total_users: User count
  • total_articles: Article count

Challenge Management

Create Challenge

/admin/challenges/create
GET | POST
Create a new challenge with answer boxes
Form Fields:
title
string
required
Challenge title
content
text
required
Challenge content (supports CKEditor rich text)
key_stage
string
required
Target key stage: KS3, KS4, or KS5
release_at
datetime
Scheduled release time (defaults to now if not specified)
lock_after_hours
integer
Hours after release_at to automatically lock challenge
image
file
Challenge image/diagram (jpg, png, gif)
answer_boxes
array
required
Array of answer boxes, each with:
  • box_label: Label for the answer box (e.g., “Part A”)
  • correct_answer: The correct answer string
  • order: Display order (integer)
Process:
  1. Creates challenge folder at uploads/challenge_{YYYY-MM-DD}/
  2. Saves uploaded image to challenge folder
  3. Creates Challenge record with date_posted=now()
  4. Creates ChallengeAnswerBox records for each answer box
  5. Commits transaction
Returns: Redirects to admin.manage_challenges with success message

Edit Challenge

/admin/challenges/edit/:challenge_id
GET | POST
Edit existing challenge and answer boxes
Path Parameters:
challenge_id
integer
required
ID of challenge to edit
Form Fields: Same as create challenge Answer Box Updates:
  • Existing boxes updated in place
  • New boxes created if form has more entries than existing
  • Unused boxes deleted (only if no submissions exist)
  • Boxes with submissions are preserved for data integrity
Returns: Redirects to admin.manage_challenges

Delete Challenge

/admin/challenges/delete/:challenge_id
GET
Delete challenge, submissions, and files
Path Parameters:
challenge_id
integer
required
ID of challenge to delete
Process:
  1. Deletes challenge image file if exists
  2. Removes challenge folder if empty
  3. Deletes all AnswerSubmission records
  4. Deletes Challenge record (cascades to answer boxes)
  5. Commits transaction
Returns: Redirects to admin.manage_challenges

Toggle Challenge Lock

/admin/challenges/toggle_lock/:challenge_id
POST
Manually lock/unlock a challenge
Path Parameters:
challenge_id
integer
required
ID of challenge to toggle
Behavior:
  • Toggles is_manually_locked field
  • When locked: Answers revealed, no new submissions
  • When unlocked: Students can submit again
Returns: Redirects to admin.manage_challenges with status message

List Challenges

/admin/challenges
GET
List all challenges (released and unreleased)
Returns:
  • released_challenges: Challenges with release_at <= now, ordered by date_posted desc
  • unreleased_challenges: Challenges with release_at > now, ordered by release_at asc

User Management

List Users

/admin/manage_users
GET
Enhanced user management with search, filtering, pagination
Query Parameters:
page
integer
default:"1"
Page number
per_page
integer
default:"25"
Users per page (max 100)
Search by name, email, class, or user ID
key_stage
string
Filter by key stage (ks3, ks4, ks5, or ‘all’)
year
string
Filter by year (7-13 or ‘all’)
user_type
string
Filter by type: ‘competition’, ‘regular’, or ‘all’
admin_status
string
Filter by admin status: ‘admin’, ‘user’, or ‘all’
Returns: Paginated user list with statistics

Create User

/admin/manage_users/create
GET | POST
Create a new user account
Form Fields:
first_name
string
required
User’s first name (no spaces)
last_name
string
required
User’s last name (no spaces)
email
string
required
Unique email address
password
string
required
Password (will be hashed)
year
string
required
School year (7-13)
maths_class
string
Maths class (not used for competition participants)
is_admin
boolean
default:"false"
Grant admin privileges
is_competition_participant
boolean
default:"false"
Mark as competition participant
school_id
integer
School ID (required if is_competition_participant=true)
Validation:
  • Email must be unique
  • No whitespace in names
  • Competition participants must have school_id
  • Key stage auto-calculated from year
Returns: Redirects to admin.manage_users

Edit User

/admin/manage_users/edit/:user_id
GET | POST
Edit existing user account
Path Parameters:
user_id
integer
required
ID of user to edit
Form Fields: Same as create user (except password) Returns: Redirects to admin.manage_users

Delete User

/admin/manage_users/delete/:user_id
GET
Delete user and all associated data
Path Parameters:
user_id
integer
required
ID of user to delete
Safety:
  • Cannot delete your own account
  • Cascades deletion to:
    • LeaderboardEntry
    • Article (authored by user)
    • AnswerSubmission
    • SummerLeaderboard
    • SummerSubmission
Returns: Redirects to admin.manage_users

Toggle Admin Status

/admin/toggle_admin/:user_id
GET
Toggle admin privileges for user
Path Parameters:
user_id
integer
required
ID of user to toggle
Returns: Redirects to admin.manage_users with message

Reset Password

/admin/manage_users/reset_password/:user_id
GET
Reset user’s password to random string
Path Parameters:
user_id
integer
required
ID of user
Behavior:
  • Generates 10-character random password
  • Hashes and saves to database
  • Displays new password to admin (shown once)
Returns: Redirects to admin.manage_users with password in flash message

User Search (AJAX)

Dynamic user search endpoint for AJAX
Query Parameters:
q
string
required
Search query (min 2 characters, or 1 if numeric)
limit
integer
default:"10"
Max results (capped at 50)
Returns: JSON array of users with:
  • id, full_name, email, maths_class
  • key_stage, year
  • is_admin, is_competition_participant
  • date_joined

Bulk User Actions

/admin/users/bulk-action
POST
Perform bulk actions on multiple users
Request Body:
action
string
required
Action to perform: ‘promote_admin’, ‘demote_admin’, ‘mark_competition’, ‘unmark_competition’, ‘delete’
user_ids
array
required
Array of user IDs to modify
Safety:
  • Cannot demote yourself from admin
  • Cannot delete yourself
  • Actions only affect specified users
Returns: JSON response with success/failure status

Article Management

Create Article

/admin/articles/create
GET | POST
Create article or newsletter
Form Fields:
title
string
required
Article title
content
text
required
Article content (CKEditor rich text)
author
string
required
Author name (named_creator field)
type
string
required
Type: ‘article’ or ‘newsletter’
file
file
PDF file (for newsletters only)
Process:
  • Saves PDF to uploads/newsletters/newsletter_{YYYY_MM}_{filename}
  • Creates Article record with user_id=current_user.id
  • Sets date_posted=now()
Returns: Redirects to admin.manage_articles

Edit Article

/admin/articles/edit/:article_id
GET | POST
Edit existing article or newsletter
Path Parameters:
article_id
integer
required
ID of article to edit
Form Fields: Same as create article File Handling:
  • New file upload replaces existing file
  • Old file deleted from filesystem
Returns: Redirects to admin.manage_articles

Delete Article

/admin/articles/delete/:article_id
GET
Delete article and associated files
Path Parameters:
article_id
integer
required
ID of article to delete
Process:
  1. Deletes PDF file if exists
  2. Deletes Article record
  3. Commits transaction
Returns: Redirects to admin.manage_articles

List Articles

/admin/articles
GET
List all articles and newsletters
Returns: All articles ordered by date_posted descending

Leaderboard Management

List Leaderboard

/admin/manage_leaderboard
GET
View leaderboard with key stage statistics
Returns:
  • All leaderboard entries ordered by key stage then score
  • Statistics: total entries, unique participants, highest/average scores
  • Counts per key stage (KS3, KS4, KS5)

Create Leaderboard Entry

/admin/leaderboard/create
GET | POST
Create new leaderboard entry
Form Fields:
user_id
integer
required
User ID (dropdown selection)
score
integer
required
Initial score
key_stage
string
required
Key stage: KS3, KS4, or KS5
Validation:
  • User can only have one entry per key stage
  • Duplicate user+key_stage combination rejected
Returns: Redirects to admin.manage_leaderboard

Edit Leaderboard Entry

/admin/leaderboard/edit/:entry_id
GET | POST
Edit existing leaderboard entry
Path Parameters:
entry_id
integer
required
ID of leaderboard entry
Form Fields: Same as create entry Validation:
  • Prevents duplicate user+key_stage combinations
  • Updates last_updated timestamp
Returns: Redirects to admin.manage_leaderboard

Export Leaderboard

/admin/leaderboard/export
GET
Export leaderboard to CSV
CSV Columns:
  • User ID
  • Name
  • Year
  • Class
  • Key Stage
  • Score
  • Last Updated
Returns: leaderboard_export.csv file download

File Upload

CKEditor Upload

/admin/upload
POST
Handle image uploads for CKEditor
Form Fields:
upload
file
required
Image file (jpg, jpeg, png, gif only)
Process:
  • Validates file extension
  • Saves to uploads/ folder with secure filename
  • Returns direct URL for CKEditor
Returns: JSON response with file URL or error message

Math Engine Tester

/admin/math-engine-tester
GET
Web interface for testing math expression equivalence
Purpose: Allows admins to test mathematical expression normalization and equivalence checking before creating answer boxes. Returns: Rendered admin/math_engine_tester.html template

Build docs developers (and LLMs) love