Skip to main content

WebQuiz

A modern web-based quiz and testing system built with Python and aiohttp that allows users to take multiple-choice and text input tests with real-time answer validation and performance tracking.

✨ Features

  • Multi-Quiz System: Questions loaded from quizzes/ directory with multiple YAML files
  • Multiple Question Types: Single choice, multiple choice, and text input questions with Python-based validation
  • Admin Interface: Web-based admin panel with master key authentication for quiz management
  • Registration Approval: Optional admin approval workflow for new registrations with real-time notifications
  • Question Randomization: Configurable per-student question order randomization for fair testing
  • Question Grouping: Keep related questions together during randomization with stick_to_the_previous attribute
  • Dynamic Answer Visibility: Optional delayed answer reveal - show correct answers only after all students complete
  • Manual Answer Control: Admin button to reveal answers immediately without waiting for all students
  • Dynamic Quiz Switching: Real-time quiz switching with automatic server state reset
  • Config File Editor: Web-based configuration editor with Form and YAML views, real-time validation
  • Live Statistics: Real-time WebSocket-powered dashboard showing user progress
  • Real-time Validation: Server-side answer checking with immediate feedback
  • Session Persistence: Cookie-based user sessions for seamless experience
  • Connection Retry: The quiz page repeats requests when the connection drops; answers and registrations are never counted twice
  • Performance Tracking: Server-side timing for accurate response measurement
  • Data Export: Automatic CSV export with quiz-prefixed filenames and unique suffixes
  • AI Integration: Use ChatGPT/Claude for analyzing quiz results and generating questions from existing materials
  • Responsive UI: Clean web interface with dark/light theme support
  • SSH Tunnel Support: Optional public access via SSH reverse tunnel with auto-reconnect
  • Binary Distribution: Standalone PyInstaller executable with auto-configuration
  • Comprehensive Testing: 222+ tests covering all functionality with CI/CD pipeline
  • Flexible File Paths: Configurable paths for quizzes, logs, CSV, and static files

🚀 Quick Start

Prerequisites

  • Python 3.9-3.14 (required by aiohttp)
  • Poetry (recommended) or pip
  • Git
  1. Clone the repository

    git clone git@github.com:oduvan/webquiz.git
    cd webquiz
    
  2. Install with Poetry

    poetry install
    
  3. Run the server

    webquiz           # Foreground mode
    webquiz -d        # Daemon mode
    
  4. Open your browser

    http://localhost:8080
    

The server will automatically create necessary directories and files on first run.

Alternative Installation with pip

  1. Clone and set up virtual environment

    git clone git@github.com:oduvan/webquiz.git
    cd webquiz
    python3 -m venv venv
    source venv/bin/activate  # On Windows: venv\Scripts\activate
    
  2. Install dependencies

    pip install -r requirements.txt
    
  3. Run the server

    python -m webquiz.cli
    

The server will automatically create necessary directories and files on first run.

📁 Project Structure

webquiz/
├── pyproject.toml           # Poetry configuration and dependencies
├── requirements.txt         # Legacy pip dependencies
├── .gitignore              # Git ignore rules
├── CLAUDE.md               # Project documentation
├── README.md               # This file
├── webquiz/                # Main package
│   ├── __init__.py        # Package initialization
│   ├── cli.py             # CLI entry point (webquiz command)
│   ├── server.py          # Main application server
│   ├── build.py           # PyInstaller binary build script
│   ├── binary_entry.py    # Binary executable entry point
│   ├── version_check.py   # Version update checking
│   ├── server_config.yaml.example  # Configuration example
│   └── templates/         # HTML templates
│       ├── index.html                     # Main quiz interface
│       ├── admin.html                     # Admin management panel
│       ├── files.html                     # File manager interface
│       ├── live_stats.html                # Live statistics dashboard
│       ├── quiz_selection_required.html   # Quiz selection prompt
│       └── template_error.html            # Error page template
├── tests/                  # Test suite (14 test files)
│   ├── conftest.py                      # Test fixtures and configuration
│   ├── test_cli_directory_creation.py   # CLI and directory tests
│   ├── test_admin_api.py                # Admin API tests
│   ├── test_admin_quiz_management.py    # Quiz management tests
│   ├── test_config_management.py        # Config editor tests
│   ├── test_registration_approval.py    # Registration approval tests
│   ├── test_registration_fields.py      # Registration fields tests
│   ├── test_index_generation.py         # Template generation tests
│   ├── test_files_management.py         # File manager tests
│   ├── test_integration_multiple_choice.py  # Multiple choice integration tests
│   ├── test_multiple_answers.py         # Multiple answer tests
│   ├── test_show_right_answer.py        # Show answer tests
│   ├── test_selenium_multiple_choice.py # Selenium multiple choice tests
│   ├── test_selenium_registration_fields.py # Selenium registration tests
│   └── test_user_journey_selenium.py    # Selenium user journey tests
└── .github/
    └── workflows/
        └── test.yml       # CI/CD pipeline

# Generated at runtime (excluded from git):
└── quizzes/               # Quiz files directory
    ├── default.yaml      # Default quiz (auto-created)
    └── *.yaml            # Additional quiz files

🖥️ CLI Commands

The webquiz command provides several options:

# Start server in foreground (default)
webquiz

# Start server with admin interface (requires master key)
webquiz --master-key secret123

# Start server on a custom port
webquiz --port 9090

# Start server with custom directories
webquiz --quizzes-dir my_quizzes
webquiz --logs-dir /var/log
webquiz --csv-dir /data
webquiz --static /var/www/quiz

# Combine multiple options
webquiz --master-key secret123 --quizzes-dir quizzes --logs-dir logs

# Set master key via environment variable
export WEBQUIZ_MASTER_KEY=secret123
webquiz

# Start server as daemon (background)
webquiz -d
webquiz --daemon

# Stop daemon server
webquiz --stop

# Check daemon status
webquiz --status

# Show help
webquiz --help

# Show version
webquiz --version

Key Options

  • --master-key: Enable admin interface with authentication
  • --port: Port to listen on (default: 8080)
  • --quizzes-dir: Directory containing quiz YAML files (default: ./quizzes)
  • --logs-dir: Directory for server logs (default: current directory)
  • --csv-dir: Directory for CSV exports (default: current directory)
  • --static: Directory for static files (default: ./static)
  • -d, --daemon: Run server in background
  • --stop: Stop daemon server
  • --status: Check daemon status

Daemon Mode Features

  • Background execution: Server runs independently in background
  • PID file management: Automatic process tracking via webquiz.pid
  • Graceful shutdown: Proper cleanup on stop
  • Status monitoring: Check if daemon is running
  • Log preservation: All output still goes to server.log

🚀 Release Management

This project uses GitHub Actions for automated versioning, PyPI deployment, and GitHub Release creation.

Creating a New Release

  1. Go to GitHub Actions in the repository
  2. Select "Release and Deploy to PyPI" workflow
  3. Click "Run workflow"
  4. Enter the new version (e.g., 1.0.6, 2.0.0)
  5. Click "Run workflow"

The action will automatically:

  • ✅ Update version in pyproject.toml and webquiz/__init__.py
  • ✅ Run tests to ensure everything works
  • ✅ Commit the version changes
  • ✅ Create a git tag with the version
  • ✅ Build the package using Poetry
  • ✅ Publish to PyPI
  • 🆕 Create a GitHub Release with built artifacts

What's included in GitHub Releases

Each release automatically includes:

  • 📦 Python wheel package (.whl file)
  • 📋 Source distribution (.tar.gz file)
  • 📝 Formatted release notes with installation instructions
  • 🔗 Links to commit history for detailed changelog
  • 📋 Installation commands for the specific version

Prerequisites for Release Deployment

Repository maintainers need to set up:

  • PYPI_API_TOKEN secret in GitHub repository settings
  • PyPI account with publish permissions for the webquiz package
  • GITHUB_TOKEN is automatically provided by GitHub Actions

🧪 Testing

Run the comprehensive test suite:

# With Poetry
poetry run pytest

# Or directly
pytest tests/

# Run with verbose output
pytest tests/ -v

# Run in parallel with 4 workers
pytest tests/ -v -n 4

# Run specific test file
pytest tests/test_admin_api.py
pytest tests/test_registration_approval.py

Test Coverage

The project has 222+ tests across 15 test files covering:

  • CLI and Directory Creation (7 tests): Directory and file creation
  • Admin API (14 tests): Admin interface and authentication
  • Admin Quiz Management (18 tests): Quiz switching and management
  • Admin Quiz Editor (7 tests): Wizard mode quiz creation with randomize_questions and show_answers_on_completion
  • Config Management (23 tests): Config editor, validation, and form-based JSON updates
  • Registration Approval (20 tests): Approval workflow and timing
  • Files Management (38 tests): File manager interface
  • SSH Tunnel (31 tests): Key generation, config fetch, connect/disconnect (test_tunnel.py)
  • File Transfer (59 tests): Sending files between servers through the tunnel (test_transfer.py, test_transfer_api.py)
  • Network Access (22 tests): Local network restriction and proxy header handling
  • Index Generation (13 tests): Template generation tests
  • Registration Fields (12 tests): Custom registration fields
  • Show Right Answer (5 tests): Answer display functionality
  • Show Answers on Completion (10 tests): Dynamic answer visibility after all students complete
  • Integration Tests (6 tests): Multiple choice, multiple answers
  • Selenium Tests (56 tests): End-to-end browser testing
  • Auto-Advance (6 tests): Automatic progression behavior

The test suite uses GitHub Actions CI/CD for automated testing on every commit.

🔥 Stress Testing

Stress testing has been moved to a separate project for better maintainability and independent versioning.

Installation

# Install from PyPI
pip install webquiz-stress-test

# Or download pre-built binaries from releases
# https://github.com/oduvan/webquiz-stress-test/releases

Quick Start

# Basic test with 10 concurrent users
webquiz-stress-test

# Heavy load test with 100 users
webquiz-stress-test -c 100

# Test custom server
webquiz-stress-test -u http://localhost:9000 -c 50

Features

  • Concurrent client simulation with configurable users
  • Realistic user behavior (random delays, page reloads)
  • Randomized quiz support
  • Approval workflow testing
  • Detailed performance statistics
  • Multi-platform binaries (Linux, macOS, Windows)

Documentation

For complete documentation, see the webquiz-stress-test repository.

📋 Configuration Format

Quiz Files

Questions are stored in YAML files in the quizzes/ directory. The server automatically creates a default.yaml file if the directory is empty.

Example quiz file (quizzes/math_quiz.yaml):

title: "Mathematics Quiz"
randomize_questions: true  # Set to true to randomize question order for each student (default: false)
questions:
  - question: "What is 2 + 2?"
    options:
      - "3"
      - "4"
      - "5"
      - "6"
    correct_answer: 1  # 0-indexed (option "4")

  - question: "What is 5 × 3?"
    options:
      - "10"
      - "15"
      - "20"
      - "25"
    correct_answer: 1  # 0-indexed (option "15")

Question Randomization:

  • Set randomize_questions: true in your quiz YAML to give each student a unique question order
  • Each student receives a randomized order that persists across sessions
  • Helps prevent cheating and ensures fair testing
  • Default is false (questions appear in YAML order)

Copy Protection:

  • Question text and answer options cannot be selected or copied by students
  • Prevents sharing quiz content via copy-paste

Question Grouping (stick_to_the_previous):

  • Use stick_to_the_previous: true on questions that should stay adjacent to their predecessor during randomization
  • Useful for reading passages followed by related questions
  • Groups are shuffled as units while preserving internal order
  • Example: Q1→Q2(sticky)→Q3(sticky) always appear together as [Q1,Q2,Q3]
  • First question cannot have this flag (no previous question)
  • Admin panel shows 🔗 indicator on grouped questions

Question Points:

  • Each question can have a custom point value using the points field (default: 1)
  • Points may be whole or decimal numbers with at most two decimal places (e.g. 0.5, 1.25, 2.75)
  • Points must be greater than 0. A value with more than two decimals (e.g. 1.234) is rejected when the quiz is saved
  • The admin quiz editor refuses such a value with a message naming the question, so nothing is rounded silently
  • Points are tracked and displayed in:
    • Live stats: shows earned points / total points for each user
    • Final results: displays points earned along with correct/incorrect count
    • Users CSV: includes earned_points and total_points columns
  • Questions worth something other than 1 point show a trophy indicator (🏆) during the quiz

Example:

questions:
  - question: "Easy question"
    options:
      - "A"
      - "B"
      - "C"
      - "D"
    correct_answer: 0
    # points: 1  (default, can be omitted)

  - question: "Hard question worth 3 points"
    options:
      - "X"
      - "Y"
      - "Z"
    correct_answer: 2
    points: 3  # This question is worth 3 points

  - question: "Easy question worth half a point"
    options:
      - "Yes"
      - "No"
    correct_answer: 0
    points: 0.5  # Decimal points are allowed (up to 2 decimals)

Text Input Questions:

In addition to multiple choice questions, you can create text input questions where students type their answer. Questions with checker field are automatically detected as text input questions:

questions:
  - question: "What is 2+2?"
    default_value: ""           # Initial value shown in textarea
    correct_value: "4"          # Shown when answer is wrong (if show_right_answer is true)
    checker: |                  # Python code to validate the answer
      assert user_answer.strip() == '4', 'Expected 4'
    points: 1

  - question: "Calculate sqrt(16)"
    correct_value: "4.0"
    checker: |
      result = float(user_answer)
      assert abs(result - sqrt(16)) < 0.01, f'Expected 4, got {result}'
    points: 2

Text Question Fields:

  • question - Question text (required)
  • checker - Required to identify as text question (can be empty for exact match)
  • default_value - Initial value shown in textarea (optional)
  • correct_value - Correct answer shown when student is wrong (optional)
  • points - Points for correct answer, a number > 0 with at most 2 decimals (default: 1)

Checker Code:

  • Uses variable user_answer (the student's text input)
  • Available: math module (use math.sqrt, math.sin, etc.)
  • Available helper functions:
    • to_int(str) - Convert string to integer (strips whitespace)
    • distance(str) - Parse distance with units: "2000", "2000m", "2км", "2km" all return 2000
    • direction_angle(str) - Parse direction angle: "20" returns 2000, "20-30" returns 2030
  • If checker raises any exception, the answer is marked incorrect
  • If no checker is provided, exact match with correct_value is used (with whitespace stripped)

Checker Templates:

You can configure reusable checker templates in your webquiz.yaml:

checker_templates:
  - name: "Exact Match"
    code: "assert user_answer.strip() == 'expected', 'Wrong answer'"
  - name: "Numeric Check"
    code: "assert float(user_answer) == 42, 'Expected 42'"
  - name: "Contains Check"
    code: "assert 'keyword' in user_answer.lower(), 'Must contain keyword'"

Templates appear in the admin quiz editor for easy insertion.

Server Configuration

Optional server configuration file (webquiz.yaml):

language: en  # Quiz interface language: "uk" (Ukrainian, default) or "en" (English)

server:
  host: "0.0.0.0"
  port: 8080
  include_ipv6: false  # Include IPv6 addresses in network interfaces list
  url_format: "http://{IP}:{PORT}/"  # URL format for admin panel (use {IP} and {PORT} placeholders)

registration:
  approve: false           # Set to true to require admin approval
  username_label: ""       # Custom username field label (empty = use language default)
  fields:                  # Extra fields shown on the registration form
    - Grade
    - School
  disabled_fields:         # Fields preserved in config but hidden from the form
    - Teacher              # Toggle on/off from the file manager Config tab

quiz:
  show_right_answer: false          # Show correct answer after submission
  show_answers_on_completion: true  # Reveal answers only after all students complete

All configuration sections are optional and have sensible defaults.

Language

The quiz interface language can be set via the language option in webquiz.yaml:

  • language: uk (default) — Ukrainian interface
  • language: en — English interface

This affects all user-facing text: registration form, quiz buttons, progress indicators, error messages, and results display. The setting is hot-reloadable — changes are applied immediately when saved via the admin panel without requiring a server restart.

Answer Visibility Options

WebQuiz offers flexible control over when students can see correct answers:

  • show_right_answer: true (default): Students see correct answers immediately after submitting each question
  • show_right_answer: false: Correct answers are completely hidden during the quiz and on the final results page
  • show_answers_on_completion: true: Works with show_right_answer: false to reveal answers dynamically:
    • Answers remain hidden until ALL students complete the quiz
    • Students see a waiting message with a reload button
    • Once all students finish, correct answers become visible
    • If new students register, answers are hidden again until everyone completes
    • In approval mode, only approved students count toward completion

Example Use Case: Useful for collaborative learning environments where you want students to discuss answers together after everyone has completed the quiz independently.

Final Results Display

By default, the results screen shows the score summary and a table listing each question with the student's answer. You can hide the question list:

  • show_final_list: true (default): Shows score summary and question-by-question results table
  • show_final_list: false: Shows only the score summary (e.g., "3/5 (60%)")

This is a quiz-level setting configured in the quiz YAML file:

show_final_list: false

Connection Drops

The student quiz page keeps working through short network drops (unstable classroom Wi-Fi, a tunnel that reconnects):

  • A request that fails because of the connection (network error, no response within 15 seconds, or 502/503/504 from a proxy or the tunnel) is repeated after 1, 2 and 4 seconds, then every 5 seconds, until the server answers
  • While the page waits, a yellow banner at the top says the connection is lost; it disappears once the request gets through
  • The chosen answer is locked while it is being sent, so the repeated request always carries the same answer
  • A page opened or reloaded while the server is unreachable waits on the loading screen instead of showing the registration form
  • The answer waits for the question-start request of the same question, so question timing stays correct

Repeated requests are safe on the server side, even when the first attempt got through and only the response was lost:

  • POST /api/submit-answer: the same answer to an already answered question returns the stored result (same is_correct, time_taken, and so on) without recording anything; a different answer to an answered question is rejected with 409 (or 403/400 in randomized quizzes, as before)
  • POST /api/register: the page sends a random registration_token; a repeated registration with the same token returns the user created by the first attempt instead of failing with "username already exists"
  • POST /api/question-start: a notice for a question the student already answered is ignored, so it cannot restart timing

SSH Tunnel for Public Access

WebQuiz can expose your local server to the internet via an SSH reverse tunnel, making it accessible from a public URL. This is useful for:

  • Running quizzes from a local computer without port forwarding
  • Temporary public access without dedicated hosting
  • Classroom environments where students connect from outside the network

Configuration (webquiz.yaml):

Option 1: Fetch config from server

tunnel:
  server: "tunnel.example.com"          # SSH tunnel server hostname
  public_key: "keys/id_ed25519.pub"     # Path to SSH public key (auto-generated if missing)
  private_key: "keys/id_ed25519"        # Path to SSH private key (auto-generated if missing)
  # Config will be fetched from https://tunnel.example.com/tunnel_config.yaml

Option 2: Local config (bypasses server fetch)

tunnel:
  server: "tunnel.example.com"
  public_key: "keys/id_ed25519.pub"
  private_key: "keys/id_ed25519"
  socket_name: "my-quiz-socket"      # Optional: Fixed socket name (default: random 6-8 chars)
  config:  # Optional: Provide locally to skip fetching from server
    username: "tunneluser"
    socket_directory: "/var/run/tunnels"
    base_url: "https://tunnel.example.com/tests"

How it works:

  1. Automatic Key Generation: If keys don't exist, WebQuiz automatically generates ED25519 SSH key pairs
  2. Admin Control: Navigate to the admin panel to see the tunnel status
  3. Copy Public Key: Copy the generated public key and add it to ~/.ssh/authorized_keys on your tunnel server
  4. Connect: Click the "Connect" button in the admin panel to establish the tunnel
  5. Public URL: Once connected, a public URL will be displayed (e.g., https://tunnel.example.com/tests/a3f7b2/)
  6. Auto-Reconnect: If the connection drops, WebQuiz automatically attempts to reconnect

Server Requirements:

  • The tunnel server must be configured to support Unix domain socket forwarding
  • The server should provide a tunnel_config.yaml endpoint at https://[server]/tunnel_config.yaml with:
    username: tunneluser
    socket_directory: /var/run/tunnels
    base_url: https://tunnel.example.com/tests
    

Security Notes:

  • SSH keys are generated with no passphrase for automated connection
  • Keys are stored with proper permissions (600 for private key)
  • Connection is admin-initiated (no auto-connect on startup)
  • Connection status is shown in real-time via WebSocket
  • Tunnel traffic reaches WebQuiz from 127.0.0.1. Proxy headers (X-Forwarded-For, X-Real-IP) are trusted only from loopback connections, and the last X-Forwarded-For entry is used, because the proxy appends the real address. Visitors through the tunnel therefore count as public IPs and cannot open admin pages.
  • With a fixed socket_name, a reconnect can fail for a few minutes: the tunnel server keeps the old socket file until its cleanup job removes it (every 5 minutes). Setting StreamLocalBindUnlink yes in the server's sshd config avoids this.

Sending Files Between Servers

Servers connected to the same tunnel server with the same SSH user (one socket directory, e.g. all computers of one school) can send quiz, log and CSV files to each other through the tunnel.

  1. Give every server a fixed, unique socket_name (e.g. room-12) and connect the tunnel on both.
  2. Sender: open the File Manager (/files/), tick the files, type the name of the other server and click Send. Only a list of the files is sent at this point. The Send files to another server panel is shown only while the tunnel is connected.
  3. Receiver: the admin panel shows the request. For every file it shows what is already there and lets the admin choose what to do (see below), then Accept / Reject.
  4. On Accept, the receiver downloads the files from the sender as chosen. The sender's File Manager shows the answer.

Images and attachments used by a quiz (image, image options and file fields, from quizzes/imgs/ and quizzes/attach/) are added to the request automatically. Files that a quiz references but that do not exist on the sender are skipped.

Choosing what happens to each file:

  • Quizzes, images and attachments are saved under their own name. Logs and CSV files are saved with the sender name (room-12_0001.log, room-12_math_0001.csv), because every server uses the same names.
  • For every file the receiver shows one case and a default action. The sender sends a SHA-256 checksum of every file, so the receiver can tell whether its own file has the same content.
Case Possible actions Default
New file (no file with this name) Save, Rename, Skip Save
The same file already exists Replace, Rename, Skip Skip
A different file with this name exists Replace, Rename, Skip Rename
The running server uses this file (current log, current CSV files, active quiz) Rename, Skip Rename
  • Rename suggests a free name (room-12_map.png, then _2, _3, …); the admin can change it.
  • Choices are checked before anything is downloaded. If a choice is not possible (for example, the new name already exists), nothing is saved and the request stays open.
  • If an image or attachment is saved under a new name, the received quiz is updated to point to it (comments and formatting are kept). A skipped image keeps the quiz's original reference.

Rules and limits:

  • Both servers must stay connected until the receiver accepts. A request expires after 30 minutes.
  • Only .yaml/.yml quizzes, .log logs, .csv files and the images (.png, .jpg, .jpeg, .gif, .bmp, .svg, .webp) and attachments of sent quizzes; at most 100 files per request (images and attachments included) and 50 MB per file.
  • All traffic goes through the public tunnel URL (https://[server]/start/[name]/api/transfer/...). A request carries a random token; the sender serves only the offered files, only with this token, and only until the request is answered or expires. The receiver downloads only from its own tunnel server, so files can only come from a server connected with a key of the same SSH user.

📊 Data Export

User responses are automatically exported to CSV files with quiz-prefixed filenames and unique suffixes to prevent overwrites:

Example: math_quiz_user_responses_0001.csv

user_id,question,selected_answer,correct_answer,is_correct,time_taken_seconds
123456,"What is 2 + 2?","4","4",True,3.45
123456,"What is 5 × 3?","15","15",True,2.87

A second file with .users.csv suffix contains user statistics:

user_id,username,registered_at,total_questions_asked,correct_answers,earned_points,total_points,total_time
123456,student1,2025-01-15T10:30:00,5,4,7,9,12:46
  • total_time - Total quiz completion time in MM:SS format (minutes:seconds)
  • earned_points - Points earned from correct answers
  • total_points - Maximum possible points for questions answered

Extra answers-with-users CSV (per-answer rows with user info)

If you set extra_answers_with_users_csv: true in webquiz.yaml, a third CSV file is generated with the suffix .answers_with_users.csv. It has one row per submitted answer (like the answers CSV) but the user-identifying columns (user_id, username, and any registration fields configured under registration.fields) are prepended to each row. This avoids having to join the two existing CSVs in a spreadsheet or analysis tool.

Example: math_quiz_0001.answers_with_users.csv

user_id,username,school,question,selected_answer,correct_answer,is_correct,time_taken_seconds
123456,student1,Lyceum 1,"What is 2 + 2?","4","4",True,3.45
123456,student1,Lyceum 1,"What is 5 × 3?","15","15",True,2.87

The option is hot-reloadable — toggling it on or off takes effect on the next quiz switch or quiz restart.

CSV files are created with proper escaping and include all user response data. Files are flushed periodically (every 5 seconds) to ensure data persistence.

🎨 Customization

Adding Your Own Quizzes

  1. Create a YAML file in the quizzes/ directory

    # Example: quizzes/science_quiz.yaml
    
  2. Add your questions following the format:

    title: "Science Quiz"
    questions:
      - question: "What is H2O?"
        options:
          - "Water"
          - "Hydrogen"
          - "Oxygen"
          - "Salt"
        correct_answer: 0
    
  3. Switch to your quiz via the admin interface

    • Access /admin with your master key
    • Select your quiz from the dropdown
    • Click "Switch Quiz"

Admin Interface

Enable admin features with a master key:

webquiz --master-key secret123

Access admin panels:

  • /admin - Quiz management and user approval
  • /files - View logs, CSV files, and edit configuration
  • /live-stats - Real-time user progress dashboard

The admin panel automatically detects when the package has been updated while the server is running and displays a "Restart Required" notification, prompting you to restart the server to use the new version.

Styling

  • Templates are located in webquiz/templates/
  • Built-in dark/light theme toggle
  • Responsive design works on mobile and desktop
  • Generated static/index.html can be customized (regenerates on quiz switch)

🛠️ Development

Building Binary Executable

Create a standalone executable with PyInstaller:

# Build binary
poetry run build_binary

# Or directly
python -m webquiz.build

# The binary will be created at:
./dist/webquiz

# Run the binary
./dist/webquiz
./dist/webquiz --master-key secret123

The binary includes all templates and configuration examples, with automatic directory creation on first run.

Key Technical Decisions

  • Multi-quiz system: Questions loaded from quizzes/ directory with YAML files
  • Master key authentication: Admin endpoints protected with decorator-based authentication
  • Server-side timing: All timing calculated server-side for accuracy
  • Server-side question randomization: Random question order generated server-side, stored per-user, ensures unique randomized order for each student with session persistence
  • Middleware error handling: Clean error management with proper HTTP status codes
  • CSV module usage: Proper escaping for data with commas/quotes
  • Smart file naming: CSV files prefixed with quiz names, unique suffixes prevent overwrites
  • Dynamic quiz switching: Complete server state reset when switching quizzes
  • WebSocket support: Real-time updates for admin and live statistics
  • Binary distribution: PyInstaller for standalone executable with auto-configuration

Architecture

  • Backend: Python 3.9-3.14 with aiohttp async web framework
  • Frontend: Vanilla HTML/CSS/JavaScript (no frameworks)
  • Storage: In-memory with periodic CSV backups (30-second intervals)
  • Session Management: Cookie-based with server-side validation
  • Real-time Features: WebSocket for live stats and admin notifications

🐛 Troubleshooting

Common Issues

Port already in use:

# Kill process using port 8080
lsof -ti:8080 | xargs kill -9

Virtual environment issues:

# Recreate virtual environment
rm -rf venv
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Quiz not loading:

  • Check that quiz YAML files have valid syntax
  • Verify quizzes/ directory exists and contains .yaml files
  • Check server logs for errors
  • Restart server after adding new quiz files

Admin interface not accessible:

  • Ensure you started server with --master-key option
  • Or set WEBQUIZ_MASTER_KEY environment variable
  • Check that you're using the correct master key

Tests failing:

  • Always run tests in virtual environment: source venv/bin/activate
  • Install test dependencies: poetry install or pip install -r requirements.txt
  • Use parallel testing: pytest tests/ -v -n 4

Daemon not stopping:

# Check status
webquiz --status

# Force kill if needed
cat webquiz.pid | xargs kill -9
rm webquiz.pid

📝 License

This project is open source. Feel free to use and modify as needed.

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

📞 Support

For questions or issues:

  • Check the server logs (server.log)
  • Run the test suite to verify setup
  • Review this README and CLAUDE.md for detailed documentation

Metadata

Release files for webquiz 1.18

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for webquiz 1.18
File Size Uploaded Provenance
webquiz-1.18.tar.gz 153.5 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for webquiz 1.18
File Interpreter ABI Platform
webquiz-1.18-py3-none-any.whl Python 3 none any Details

Total release size: 301.9 kB

Release files / webquiz-1.18.tar.gz

Download URL webquiz-1.18.tar.gz
Size 153.5 kB
Tags Source
SHA-256 checksum
How to use checksums
ef0bff0963d5ac2e2d2ac8819f5371633ca221280d13dc6ab379ceea7e126b40
BLAKE2b-256 checksum
How to use checksums
e93e3924810ff658fb0bb4fafeab775cf4df36a2d33dd1485bd91125c0a75ca1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / webquiz-1.18-py3-none-any.whl

Download URL webquiz-1.18-py3-none-any.whl
Size 148.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e47c1bb79185bda139a4836576c8037e8724c26b3f7404fa9ca2922b6c3f7e25
BLAKE2b-256 checksum
How to use checksums
2a4ff37bd6cd40b087e3740caedb81f5e00558251a20e5eb8e33a5e34a4d6083
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Security for webquiz 1.18

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

1.18 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page