The Developer Portfolio Platform is a small, fast portfolio site built with Python + Flask.
- Live preview: https://spaceport.fcjamison.com/
- Local dev: http://spaceportfolio.localhost:30001/ (or http://127.0.0.1:30001/)
The server renders Jinja templates, serves pre-built static assets, and includes a contact form that persists messages to disk (and can optionally send an SMTP notification email).
- Server-rendered pages via Jinja templates (
templates/) - Safe page routing (
GET /<page_name>is restricted to filename-safe names) - Contact form:
- Saves submissions to
var/server-instance/database.txtandvar/server-instance/database.csv - Optionally sends an SMTP email notification
- Saves submissions to
Back-end
- Python 3.14 (this repo’s venv metadata shows 3.14.2)
- Flask 3.x
- Gunicorn (WSGI in production)
Front-end
- Pre-built CSS/JS in
static/ - Images and other assets in
static/assets/
This repo contains a Python virtual environment in-place (the Scripts/ + Lib/ folders). You can use it, or create a fresh venv.
- Activate:
Scripts\activate.bat
- Install dependencies:
pip install -r requirements.txt
- Start the dev server:
python dev_server.py
- Open:
- Create + activate:
python -m venv .venv.venv\Scripts\activate.bat
- Install dependencies:
pip install -r requirements.txt
- Start the dev server:
python dev_server.py
If you open this repo in VS Code, the included tasks can speed up the common workflow:
Setup + Run Dev Server(activate venv → install deps → start server)Open in Browser(local) — http://spaceportfolio.localhost:30001/
dev_server.py— local dev runner (debug=True,127.0.0.1:30001)server.py— Flask app (routes + contact form)wsgi.py— production entrypoint for Gunicorn (wsgi:app)gunicorn.conf.py— Gunicorn config (usesPORT, default30001)
Templates live in templates/ and are served through a safe “page renderer” route:
GET /→index.htmlGET /<page_name>→<page_name>.html(only whenpage_namematches^[a-zA-Z0-9_-]+$)
If the template doesn’t exist (or page_name is unsafe), the app returns 404.
Included templates:
index,about,works,work-01…work-06,contact,thankyou,components
- Endpoint:
POST /submit_form - Fields:
email,subject,message - Success behavior: redirects to
/thankyou
Submissions are written to the Flask “instance path” so runtime data stays out of source-controlled files.
This project sets the instance path to:
var/server-instance/
Files written:
var/server-instance/database.txt(CSV rows)var/server-instance/database.csv(CSV rows)
The directory is created automatically on first write.
Note: database.txt and database.csv at the repo root are not used by the app at runtime.
If SMTP isn’t configured, sending is skipped by default.
Environment variables:
CONTACT_SMTP_HOST(default:mail.fcjamison.com)CONTACT_SMTP_PORT(default:587)CONTACT_SMTP_USERCONTACT_SMTP_PASSWORDCONTACT_MAIL_FROM(default:CONTACT_SMTP_USER)CONTACT_MAIL_TO(default:CONTACT_SMTP_USER)CONTACT_SMTP_USE_STARTTLS(default:true)
Optional behavior:
CONTACT_EMAIL_REQUIRED=trueto make missing SMTP config raise an error (otherwise email is skipped)
Delivery detail:
- The outgoing email uses
CONTACT_MAIL_FROMas the sender and setsReply-Toto the visitor-typed email.
This is a standard WSGI app.
- Entrypoint:
wsgi:app - Example bind:
gunicorn --bind 0.0.0.0:30001 wsgi:app
- Recommended (uses
PORT):gunicorn -c gunicorn.conf.py wsgi:app
Operational notes:
- Ensure the process has write permission to
var/server-instance/. - If behind a reverse proxy,
X-Forwarded-For(when present) is used to include an IP in the SMTP message body.
.vscode/— VS Code tasks + interpreter settingtemplates/— Jinja templatesstatic/— compiled CSS/JS and assetsvar/server-instance/— runtime data written by the contact formserver.py— Flask applicationdev_server.py— local runnerwsgi.py— production WSGI entrypointgunicorn.conf.py— Gunicorn configrequirements.txt— Python dependencies