Skip to main content

Overview

The Maths Society Platform uses Flask Blueprints to organize routes into logical, modular components. This architecture provides:
  • Separation of Concerns: Each blueprint handles a specific domain
  • Maintainability: Isolated route definitions and templates
  • Scalability: Easy to add new modules without affecting existing code
  • Testability: Blueprints can be tested independently

Blueprint Registration

Blueprints are registered in the application factory (app/__init__.py:211):
def register_blueprints(app):
    """Register application blueprints"""
    from app.auth import bp as auth_bp
    from app.main import bp as main_bp
    from app.profile import bp as profile_bp
    from app.admin import bp as admin_bp
    from app.math_api import math_api

    app.register_blueprint(auth_bp)
    app.register_blueprint(main_bp)
    app.register_blueprint(profile_bp, url_prefix="/profile")
    app.register_blueprint(admin_bp)
    app.register_blueprint(math_api)

Blueprint Structure


Main Blueprint

URL Prefix: / (root) Purpose: Public-facing routes including challenges, leaderboards, and content. Location: app/main/

Routes

Homepage & Information

/
GET
Homepage: Main landing page with platform overviewTemplate: main/index.html
/home
GET
Alternative homepage routeRedirects to / for consistency
/about
GET
About page: Platform information and mission
/privacy_policy
GET
Privacy Policy: GDPR compliance and data handling

Challenges

/challenges
GET
Challenge list: Browse all available challengesFilters:
  • By key stage (KS3, KS4, KS5)
  • By release date
  • By lock status
/challenges/<int:challenge_id>
GET POST
Challenge detail pageGET: Display challenge content and answer boxesPOST: Submit answers for validationAuthentication: RequiredExample:
@bp.route("/challenges/<int:challenge_id>", methods=["GET", "POST"])
@login_required
def challenge_detail(challenge_id):
    challenge = Challenge.query.get_or_404(challenge_id)
    
    if request.method == "POST":
        # Process answer submissions
        for answer_box in challenge.answer_boxes:
            submitted = request.form.get(f"answer_{answer_box.id}")
            is_correct = answer_box.check_answer(submitted)
            # Create AnswerSubmission record
    
    return render_template("main/challenge_detail.html", 
                          challenge=challenge)

Summer/Autumn Challenges

/autumn_about
GET
Autumn competition information
/autumn_challenge/<int:challenge_id>
GET POST
Autumn challenge detail (similar to regular challenges)

Content & Articles

/articles
GET
Article list: Browse educational articlesQuery Parameters:
  • type: Filter by article type (“article” or “newsletter”)
/article/<int:id>
GET
Article detail: Display full article content
/newsletters
GET
Newsletter archive: List all newsletters
/newsletter/<int:id>
GET
Newsletter detail: Display/download newsletter PDF
/newsletters/<path:filename>
GET
Newsletter file serving: Secure file downloadSecurity: Validates file path to prevent directory traversal

Leaderboards

/leaderboard
GET
Main leaderboard: Rankings by key stageQuery Parameters:
  • key_stage: Filter by KS3, KS4, or KS5
Response:
leaderboard = LeaderboardEntry.query.filter_by(
    key_stage=request.args.get('key_stage', 'KS4')
).order_by(LeaderboardEntry.score.desc()).limit(50).all()
/autumn_leaderboard
GET
Autumn competition leaderboard

Auth Blueprint

URL Prefix: / (root) Purpose: User authentication and registration. Location: app/auth/

Routes

/login
GET POST
User loginGET: Display login formPOST: Authenticate user credentialsForm Fields:
  • email (required)
  • password (required)
  • remember_me (optional boolean)
Example:
@bp.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        user = User.query.filter_by(email=form.email.data).first()
        
        if user and user.check_password(form.password.data):
            login_user(user, remember=form.remember_me.data)
            return redirect(url_for('main.index'))
        
        flash("Invalid credentials", "error")
    
    return render_template("auth/login.html")
/register
GET POST
User registrationPOST Fields:
  • full_name
  • email (unique)
  • password
  • confirm_password
  • year
  • key_stage
  • maths_class
Validation:
  • Email uniqueness check
  • Password strength requirements
  • Key stage validation (KS3, KS4, KS5)
/logout
GET
User logoutClears session and redirects to homepage
@bp.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for('main.index'))
/autumn_register
GET POST
Autumn competition registrationSimilar to /register but sets is_competition_participant=True
/autumn_login
GET POST
Autumn competition login

Admin Blueprint

URL Prefix: / (root, routes start with /admin) Purpose: Administrative functions for content and user management. Location: app/admin/ Authentication: All routes require @login_required and admin check

Routes

Dashboard

/admin
GET
Admin dashboard: Overview of platform statisticsDisplays:
  • Total users, challenges, submissions
  • Recent activity
  • System health metrics

Challenge Management

/admin/challenges
GET
Challenge list: All challenges with management actions
/admin/challenges/create
GET POST
Create new challengeForm Fields:
  • title
  • content (CKEditor)
  • key_stage
  • release_at (datetime)
  • lock_after_hours
  • file_url (optional PDF)
  • Answer boxes (dynamic)
/admin/challenges/edit/<int:challenge_id>
GET POST
Edit existing challenge
/admin/challenges/delete/<int:challenge_id>
POST
Delete challengeSecurity: Confirmation required, cascades to answer boxes
/admin/challenges/toggle_lock/<int:challenge_id>
POST
Toggle manual lock
@bp.route("/admin/challenges/toggle_lock/<int:challenge_id>", 
          methods=["POST"])
@login_required
@admin_required
def toggle_challenge_lock(challenge_id):
    challenge = Challenge.query.get_or_404(challenge_id)
    challenge.is_manually_locked = not challenge.is_manually_locked
    db.session.commit()
    return redirect(url_for('admin.challenges'))

Article Management

/admin/articles
GET
Article list: All articles and newsletters
/admin/articles/create
GET POST
Create article/newsletterFields:
  • title
  • content (CKEditor for articles)
  • type (“article” or “newsletter”)
  • file_url (PDF for newsletters)
  • named_creator (optional attribution)
/admin/articles/edit/<int:article_id>
GET POST
Edit article
/admin/articles/delete/<int:article_id>
POST
Delete article

User Management

/admin/manage_users
GET
User list: Paginated user directoryFeatures:
  • Search by name/email
  • Filter by key stage
  • Bulk actions
User search APIQuery Parameters:
  • q: Search query
  • key_stage: Filter
Response: JSON array of users
/admin/users/stats
GET
User statisticsResponse:
{
  "total_users": 1250,
  "by_key_stage": {
    "KS3": 400,
    "KS4": 550,
    "KS5": 300
  },
  "active_today": 87
}
/admin/manage_users/create
GET POST
Create user account
/admin/manage_users/edit/<int:user_id>
GET POST
Edit user accountFields:
  • is_admin (promote/demote)
  • key_stage
  • school_id
/admin/users/bulk-action
POST
Bulk user operationsActions:
  • Delete multiple users
  • Change key stage
  • Export to CSV

File Management

/admin/upload
POST
File upload handlerAccepts: PDF, imagesSecurity: File type validation, sanitized filenames
/uploads/challenges/<int:id>
GET
Serve challenge files

Tools

/admin/math-engine-tester
GET
Math Engine Testing ToolInteractive interface to test mathematical expression equivalence:Features:
  • Test two expressions for equivalence
  • View normalized forms
  • LaTeX input support
  • Debugging output
See Math Engine for details.

Profile Blueprint

URL Prefix: /profile Purpose: User account management and settings. Location: app/profile/ Authentication: All routes require @login_required

Routes

/profile/
GET
User profile pageDisplays:
  • User information
  • Submission history
  • Personal statistics
  • Account settings link
/profile/change_password
GET POST
Change passwordPOST Fields:
  • current_password (verification)
  • new_password
  • confirm_password
Validation:
@bp.route("/change_password", methods=["GET", "POST"])
@login_required
def change_password():
    if request.method == "POST":
        if not current_user.check_password(form.current_password.data):
            flash("Current password is incorrect", "error")
            return redirect(url_for('profile.change_password'))
        
        current_user.set_password(form.new_password.data)
        db.session.commit()
        flash("Password updated successfully", "success")
    
    return render_template("profile/change_password.html")
/profile/delete_account
POST
Account deletionSecurity:
  • Requires password confirmation
  • Cascades to submissions (via model relationships)
  • Irreversible action with warning

Math API Blueprint

URL Prefix: /api/math Purpose: RESTful API for mathematical expression operations. Location: app/math_api.py Authentication: Required (admin only)

Endpoints

/api/math/test-equivalence
POST
Test if two mathematical expressions are equivalentAuthentication: Required (admin only)Request Body:
{
  "expr1": "2*x + 3",
  "expr2": "3 + 2*x"
}
Response:
{
  "equivalent": true,
  "expr1": "2*x + 3",
  "expr2": "3 + 2*x",
  "expr1_normalized": "2*x + 3",
  "expr2_normalized": "2*x + 3"
}
/api/math/normalize
POST
Normalize expression to canonical formAuthentication: Required (admin only)Request Body:
{
  "expression": "2x + 3"
}
Response:
{
  "original": "2x + 3",
  "normalized": "2*x + 3"
}
See Math Engine API Reference for complete documentation.

URL Patterns

Naming Conventions

PatternExamplePurpose
/<resource>/challengesList view
/<resource>/<int:id>/challenge/42Detail view
/<resource>/create/admin/articles/createCreate form
/<resource>/edit/<int:id>/admin/challenges/edit/5Edit form
/<resource>/delete/<int:id>/admin/users/delete/10Delete action
/api/<resource>/<action>/api/math/test-equivalenceAPI endpoint

URL Generation

Use url_for() for all internal links:
# Good
redirect(url_for('main.challenge_detail', challenge_id=42))

# Bad
redirect('/challenges/42')  # Hard-coded, breaks if routes change

Decorators & Middleware

Authentication Decorators

from flask_login import login_required

@bp.route("/protected")
@login_required
def protected_route():
    # Only accessible to authenticated users
    pass

Admin-Only Decorator

from functools import wraps
from flask import abort

def admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated or not current_user.is_admin:
            abort(403)
        return f(*args, **kwargs)
    return decorated_function

@bp.route("/admin/dashboard")
@login_required
@admin_required
def admin_dashboard():
    pass

Rate Limiting

from flask_login import login_required
from app.math_api import admin_required

@math_api.route('/api/math/test-equivalence', methods=['POST'])
@login_required
@admin_required
def test_expression_equivalence():
    pass

Error Handling

Blueprint-Level Error Handlers

@bp.errorhandler(404)
def not_found_error(error):
    return render_template('errors/404.html'), 404

@bp.errorhandler(403)
def forbidden_error(error):
    return render_template('errors/403.html'), 403

Global Error Handlers

Defined in app/__init__.py:74-85

Template Organization

Each blueprint has its own template directory:
templates/
├── main/
│   ├── index.html
│   ├── challenge_detail.html
│   └── leaderboard.html
├── auth/
│   ├── login.html
│   └── register.html
├── admin/
│   ├── dashboard.html
│   └── challenges/
│       ├── create.html
│       └── edit.html
└── profile/
    ├── profile.html
    └── change_password.html

Testing Blueprints

Unit Testing Routes

import pytest
from app import create_app, db

@pytest.fixture
def client():
    app = create_app('testing')
    with app.test_client() as client:
        with app.app_context():
            db.create_all()
            yield client
            db.drop_all()

def test_login_route(client):
    response = client.get('/login')
    assert response.status_code == 200
    assert b'Login' in response.data

System Architecture

Application structure overview

Database Models

Model definitions used in routes

API Reference

Complete API endpoint documentation

Authentication

User authentication implementation

Build docs developers (and LLMs) love