Teaching lab for client/vendor procurement workflows: student groups draft and publish RFPs for assigned lab lots, other groups bid, clients evaluate and select winners. Admins manage catalog, approvals, imports, and settings.
- Students form groups (size range configured by admin).
- Admin one-click approves a pending group → system auto-assigns the next 2 exclusive unassigned lots (fails if fewer than 2 free lots).
- Client (owning group) saves RFP drafts, then submits → RFP is immediately visible in the vendor gallery and becomes immutable.
- Vendors bid with a PDF; bids always go to the lot-owning group only.
- Client scores bids and selects a winner per lot.
CSV import can create groups that are auto-approved and lot-assigned in one step.
├── App/
│ ├── controllers/ # Business logic + seed (initialize.py)
│ ├── models/ # SQLAlchemy models
│ ├── views/ # Blueprints (admin, student, auth, …)
│ ├── templates/ # Jinja (admin/, student/)
│ ├── static/ # app.css
│ └── academic_years.py
├── docker/entrypoint.sh
├── docker-compose.yml
├── Dockerfile
├── wsgi.py # App factory + Flask CLI (flask init)
├── gunicorn_config.py
├── requirements.txt
└── requirements-dev.txt
python -m venv .venv
# Windows: .venv\Scripts\activate
source .venv/bin/activate
pip install -r requirements-dev.txt
flask init # seed DB (CLI only)
flask runSet SECRET_KEY in the environment for any non-local deploy.
These passwords are for local/lab demos. Do not reuse in production.
| Role | Username | Password | Group |
|---|---|---|---|
| Admin | admin | plattech2026 | — |
| Student (every year) | {year}0001 |
aidenpass | AlphaLab / NexoraTech (2024) |
| Student | {year}0002 |
kareempass | same |
| Student | {year}0003 |
joshuapass | same |
| Student | {year}0004 |
danielpass | BetaLab / QuantumSoft (2024) |
| Student | {year}0005 |
samanthapass | same |
| Student | {year}0006 |
aaliyahpass | same |
Examples: 20260001 / aidenpass (AlphaLab), 20250004 / danielpass (BetaLab).
Student accounts are year-specific. Seed: 2024 full archive (10 groups + RFPs/bids); 2025 lots + 4 groups (AlphaLab–DeltaLab) with submitted RFPs, competing bids, and evaluations (winner selected per scored lot); 2026 lots + AlphaLab/BetaLab only. Platform current year is set in Settings (default 2026); the admin sidebar year dropdown only previews records.
- Settings (
/admin/settings): configuregroup_size_min/group_size_max. - Manage Groups (
/admin/manage-groups): student listing table + upload class list of ungrouped students:
student_id,name,email
20249901,Ada Lovelace,ada@lab.local
20249902,Alan Turing,alan@lab.local
Students then claim their account at /register by matching student_id + email. Unclaimed roster rows cannot log in.
- Import Groups (
/admin/import-groups): upload CSV with headers:
student_id,name,email,group_name
20249901,Ada Lovelace,ada@lab.local,AdaForce
20249902,Alan Turing,alan@lab.local,AdaForce
20249903,Grace Hopper,grace@lab.local,AdaForce
Rules:
- Login username =
student_id. - Group sizes must fall in the configured range.
- Import is transactional; needs
2 × number_of_groupsfree lots. - Success downloads
import_credentials.csvwith temporary passwords.
Config loads from App/default_config.py or App/custom_config.py, overridable with FLASK_-prefixed env vars.
| Variable | Purpose |
|---|---|
SECRET_KEY |
Session / JWT signing (required in production) |
DATABASE_URL or SQLALCHEMY_DATABASE_URI |
Postgres / SQLite URI |
ENV |
production or development (cookie security) |
CORS_ORIGINS |
Optional comma-separated origins |
PORT |
Listen port (default 8080) |
WEB_CONCURRENCY |
Gunicorn workers (default 2) |
Health check: GET /health
- Create a Compose service from this repo.
- Compose file:
docker-compose.yml(build path/). - In Environment, paste values from
.env.exampleand set strongSECRET_KEY+POSTGRES_PASSWORD. - Enable Create .env file so Compose can interpolate secrets.
- Deploy, then map a domain to service
webon port8080. - Seed demo data once (optional):
# EasyPanel → web service → Terminal / Exec
flask initflask init resets the database — only run on a fresh deploy.
- Put the EasyPanel Deploy webhook URL in
.env:
EASYPANEL_DEPLOY_WEBHOOK=http://YOUR_HOST:3000/api/deploy/YOUR_TOKEN- Trigger a rebuild/redeploy:
python scripts/deploy.py
# or: scripts/deploy.ps1 / bash scripts/deploy.sh- Create a Postgres service in the same EasyPanel project.
- Create an App service from this repo; builder = Dockerfile.
- Set environment:
ENV=production
SECRET_KEY=...
DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DB
PORT=8080
WEB_CONCURRENCY=2Use EasyPanel’s internal hostname for HOST (same project network).
4. Domain → port 8080. Health path /health.
cp .env.example .env
# edit SECRET_KEY and POSTGRES_PASSWORD
docker compose up --build
curl http://localhost:8080/healthpip install -r requirements-dev.txt
pytest