A database-driven photography portfolio built from scratch with Flask. Photos are organized into categories, displayed in a scrollable gallery, and managed through a secure admin area with full create, edit, and delete functionality.
Live site: https://pigeon-photography.onrender.com/
This is a learning project. I set out to rebuild a Flask app entirely on my own to genuinely understand each piece, from the application factory pattern all the way to deploying on the internet. Every line was written and understood, not copied.
Public site
- Full-width hero homepage with a layered title
- Slide-in navigation menu (hover to open)
- Horizontal gallery grouped by category, with arrow scrolling
- Individual photo pages with a card-flip effect that reveals EXIF metadata (camera, lens, aperture, shutter, ISO)
- About page with contact info
Admin area (secure)
- Login system with hashed passwords (Flask-Login)
- Protected routes (login required)
- Upload photos with metadata and category assignment
- Add, edit, and delete photos
- Add, edit, and delete categories
| Layer | Tools |
|---|---|
| Backend | Python, Flask (application factory pattern, blueprints) |
| Database | SQLAlchemy 2.0, SQLite (local), PostgreSQL (production) |
| Auth | Flask-Login, Werkzeug password hashing |
| Templating | Jinja2 (template inheritance) |
| Frontend | HTML, CSS (Flexbox, Grid, 3D transforms), vanilla JavaScript |
| Deployment | Gunicorn, Render |
pigeon-photography/
├── app/
│ ├── static/
│ │ ├── css/ # style.css
│ │ ├── js/ # menu.js
│ │ └── uploads/ # the photos
│ ├── templates/
│ │ ├── admin/ # dashboard, login, upload, manage...
│ │ ├── base.html
│ │ ├── home.html
│ │ ├── gallery.html
│ │ ├── photo.html
│ │ └── about.html
│ ├── __init__.py # the application factory (create_app)
│ ├── routes.py # public routes
│ ├── admin.py # admin routes
│ ├── models.py # database models
│ └── seed.py # auto-fills the database on startup
├── config.py # settings (reads environment variables)
├── run.py # local start button
├── requirements.txt
└── .gitignore
1. Clone the repo
git clone https://github.com/AntoinePigeon/pigeon-photography.git
cd pigeon-photography2. Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate # macOS/Linux
# .venv\Scripts\activate # Windows3. Install the dependencies
pip install -r requirements.txt4. Run the app
python run.py5. Open it
Visit http://127.0.0.1:5000 in your browser.
The database creates and seeds itself automatically on the first run, so there are no extra setup steps.
For production, the app reads these from the environment (never hardcoded in the code):
| Variable | Purpose |
|---|---|
SECRET_KEY |
Signs sessions and cookies securely |
DATABASE_URL |
The PostgreSQL connection string |
ADMIN_PASSWORD |
The admin login password |
Locally, the app falls back to safe defaults (SQLite and a dev key), so you do not need to set anything to run it on your machine.
Deployed on Render as a web service connected to a PostgreSQL database.
- Build command:
pip install -r requirements.txt - Start command:
gunicorn 'app:create_app()'
The database seeds itself on startup, so a fresh deployment (or a reset free-tier database) rebuilds its content automatically.
This project took me from "I can follow a tutorial" to "I can build and ship a full web app." Key concepts I picked up:
- The application factory pattern and blueprints for organizing a Flask project
- Template inheritance with Jinja2 to avoid repeating layout code
- Many-to-many database relationships (photos and categories)
- Modern SQLAlchemy 2.0 query syntax
- User authentication with password hashing and protected routes
- File uploads and form handling (GET vs POST)
- CSS layout (Flexbox, Grid, 3D transforms) and vanilla JavaScript interactions
- Deploying to production: environment variables, Gunicorn, PostgreSQL, and a self-seeding database
This project is for personal and educational use.
