Python Flask Portfolio Website Project

Written by

in

Are you a developer looking to showcase your work, attract clients, or simply practice modern web development? Building a personal portfolio site with Python Flask is an excellent way to combine clean code, flexible routing, and a lightweight framework that scales from a simple static page to a dynamic showcase of projects. In this guide, we’ll walk through every step of creating a polished Flask portfolio website—from project setup and template design to adding a contact form and deploying to the cloud. By the end, you’ll have a fully functional, SEO‑friendly portfolio that you can customize and share with the world.

Why Choose Flask for Your Portfolio?

  • Lightweight and modular: Flask gives you just the essentials, letting you add only the features you need.
  • Python ecosystem: Leverage powerful libraries for databases, forms, and email without learning a new language.
  • Easy to deploy: Works seamlessly with platforms like Heroku, Render, and Railway.
  • SEO control: Full access to HTML meta tags, sitemap generation, and structured data.

Project Structure Overview

A well‑organized file layout makes your code maintainable and ready for future enhancements. Below is a recommended structure for a Flask portfolio project:

portfolio/
├── app/
│   ├── __init__.py
│   ├── routes.py
│   ├── models.py          # optional, if you use a DB
│   ├── forms.py           # contact form
│   └── static/
│       ├── css/
│       │   └── style.css
│       ├── js/
│       │   └── main.js
│       └── images/
│           └── profile.jpg
│   └── templates/
│       ├── base.html
│       ├── index.html
│       ├── project.html
│       └── contact.html
├── migrations/            # if using Flask‑Migrate
├── venv/
├── requirements.txt
└── run.py

Step 1: Setting Up the Development Environment

1.1 Create a virtual environment

Isolation prevents package conflicts and keeps your project tidy.

python -m venv venv
source venv/bin/activate   # On Windows use `venv\Scripts\activate`

1.2 Install Flask and essential extensions

pip install Flask Flask-WTF Flask-Mail python-dotenv

Save the dependencies for later deployment:

pip freeze > requirements.txt

Step 2: Initializing the Flask Application

Create app/__init__.py to configure the app, load environment variables, and register blueprints if you decide to split routes later.

from flask import Flask
from flask_wtf import CSRFProtect
from flask_mail import Mail
import os

csrf = CSRFProtect()
mail = Mail()

def create_app():
    app = Flask(__name__, static_folder='static', template_folder='templates')
    
    # Load configuration from .env or a config object
    app.config.from_mapping(
        SECRET_KEY=os.getenv('SECRET_KEY', 'dev-secret-key'),
        MAIL_SERVER=os.getenv('MAIL_SERVER', 'smtp.gmail.com'),
        MAIL_PORT=int(os.getenv('MAIL_PORT', 587)),
        MAIL_USE_TLS=True,
        MAIL_USERNAME=os.getenv('MAIL_USERNAME'),
        MAIL_PASSWORD=os.getenv('MAIL_PASSWORD')
    )
    
    csrf.init_app(app)
    mail.init_app(app)

    # Import and register routes
    from . import routes
    app.register_blueprint(routes.bp)

    return app

Step 3: Defining Routes and Views

In app/routes.py we’ll create a blueprint that handles the main pages of the portfolio.

from flask import Blueprint, render_template, request, flash, redirect, url_for
from .forms import ContactForm
from . import mail
from flask_mail import Message

bp = Blueprint('main', __name__)

@bp.route('/')
def index():
    # Sample data – replace with your own projects
    projects = [
        {
            'title': 'Flask Blog',
            'slug': 'flask-blog',
            'description': 'A full‑featured blog with markdown support.',
            'image': 'blog.png'
        },
        {
            'title': 'Data Visualizer',
            'slug': 'data-visualizer',
            'description': 'Interactive charts using Plotly and Flask.',
            'image': 'visualizer.png'
        }
    ]
    return render_template('index.html', projects=projects)

@bp.route('/project/')
def project_detail(slug):
    # In a real app, fetch from a database
    project = {
        'title': slug.replace('-', ' ').title(),
        'description': 'Detailed description of the project.',
        'image': f'{slug}.png',
        'tech': ['Python', 'Flask', 'Bootstrap']
    }
    return render_template('project.html', project=project)

@bp.route('/contact', methods=['GET', 'POST'])
def contact():
    form = ContactForm()
    if form.validate_on_submit():
        msg = Message(
            subject=f'Portfolio Contact: {form.subject.data}',
            sender=form.email.data,
            recipients=[os.getenv('MAIL_USERNAME')],
            body=form.message.data
        )
        mail.send(msg)
        flash('Your message has been sent!', 'success')
        return redirect(url_for('main.contact'))
    return render_template('contact.html', form=form)

Step 4: Building the Contact Form

Flask‑WTF simplifies form handling and CSRF protection.

# app/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Email, Length

class ContactForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired(), Length(max=50)])
    email = StringField('Email', validators=[DataRequired(), Email()])
    subject = StringField('Subject', validators=[DataRequired(), Length(max=100)])
    message = TextAreaField('Message', validators=[DataRequired(), Length(max=1000)])
    submit = SubmitField('Send')

Step 5: Crafting SEO‑Friendly Templates

5.1 Base layout (base.html)

All pages inherit from this file, ensuring consistent meta tags and navigation.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title if title else "My Portfolio" }}</title>
    <meta name="description" content="{{ meta_description|default('Python developer portfolio built with Flask') }}">
    <link rel="canonical" href="{{ request.url }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
    <!-- Open Graph for social sharing -->
    <meta property="og:title" content="{{ title|default('My Portfolio') }}">
    <meta property="og:description" content="{{ meta_description|default('Showcasing my Python and Flask projects') }}">
    <meta property="og:type" content="website">
    <meta property="og:url" content="{{ request.url }}">
    <meta property="og:image" content="{{ url_for('static', filename='images/profile.jpg', _external=True) }}">
</head>
<body>
    <header>
        <nav>
            <a href="{{ url_for('main.index') }}">Home</a>
            <a href="{{ url_for('main.contact') }}">Contact</a>
        </nav>
    </header>

    <main>
        {% block content %}{% endblock %}
    </main>

    <footer>
        <p>© {{ current_year }} My Name. All rights reserved.</p>
    </footer>
</body>
</html>

5.2 Home page (index.html)

{% extends "base.html" %}
{% block content %}
<section class="hero">
    <h2>Hello, I’m {{ your_name }} – Python Developer</h2>
    <p>I build web applications, data pipelines, and automation scripts. Explore my latest projects below.</p>
</section>

<section class="projects">
    <h3>Featured Projects</h3>
    <ul class="project-list">
    {% for project in projects %}
        <li>
            <a href="{{ url_for('main.project_detail', slug=project.slug) }}">
                <img src="{{ url_for('static', filename='images/' + project.image) }}" alt="{{ project.title }} thumbnail">
                <h4>{{ project.title }}</h4>
                <p>{{ project.description }}</p>
            </a>
        </li>
    {% endfor %}
    </ul>
</section>
{% endblock %}

5.3 Project detail page (project.html)

{% extends "base.html" %}
{% block content %}
<article class="project-detail">
<h2>{{ project.title }}</h2>
<img src="{{ url_for('static', filename='images/' + project.image) }}" alt="{{

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *