Flask Forms, Templates & CRUD OperationsΒΆ

IntroductionΒΆ

Learn how to handle forms, use Jinja2 templates effectively, and implement CRUD (Create, Read, Update, Delete) operations to build real web applications! πŸ“βœ¨

Note

CRUD operations are the bread and butter of web apps! Almost every app needs to Create, Read, Update, and Delete data. Let’s make it fun! 🎯


Jinja2 Template BasicsΒΆ

Variables and Expressions:

<!-- templates/home.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ page_title }} 🎨</title>
</head>
<body>
    <h1>Hello, {{ username }}! {{ emoji }}</h1>
    <p>You have {{ points }} points! ⭐</p>
    <p>Double points: {{ points * 2 }}</p>
</body>
</html>

app.py:

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('home.html',
                         page_title='My App',
                         username='Alice',
                         emoji='πŸŽ“',
                         points=100)

if __name__ == '__main__':
    app.run(debug=True)

Loops in TemplatesΒΆ

<!-- templates/students.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Student List πŸ“š</title>
    <style>
        body { font-family: Arial; padding: 20px; }
        .student {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 15px;
            margin: 10px 0;
            border-radius: 8px;
        }
    </style>
</head>
<body>
    <h1>Students πŸŽ“</h1>
    {% for student in students %}
        <div class="student">
            {{ loop.index }}. {{ student.name }} - Grade: {{ student.grade }}
            {% if student.grade >= 75 %}⭐{% else %}πŸ“š{% endif %}
        </div>
    {% endfor %}
</body>
</html>

app.py:

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/students')
def students():
    student_list = [
        {'name': 'Alice', 'grade': 85},
        {'name': 'Bob', 'grade': 72},
        {'name': 'Charlie', 'grade': 90}
    ]
    return render_template('students.html', students=student_list)

if __name__ == '__main__':
    app.run(debug=True)

Note

{% for %} loops through lists. loop.index gives the current iteration number (1-based). Use {% if %} for conditionals! πŸ”„


Template Inheritance (DRY Principle)ΒΆ

base.html (Parent Template):

<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}My App{% endblock %} 🌟</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            background: #f5f5f5;
        }
        nav {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 15px;
            color: white;
        }
        nav a {
            color: white;
            margin: 0 15px;
            text-decoration: none;
        }
        .container {
            max-width: 900px;
            margin: 30px auto;
            padding: 20px;
            background: white;
            border-radius: 10px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
        }
    </style>
</head>
<body>
    <nav>
        <a href="/">🏠 Home</a>
        <a href="/about">ℹ️ About</a>
        <a href="/contact">πŸ“§ Contact</a>
    </nav>
    <div class="container">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

home.html (Child Template):

<!-- templates/home.html -->
{% extends "base.html" %}

{% block title %}Home{% endblock %}

{% block content %}
    <h1>Welcome Home! 🏠</h1>
    <p>This page inherits from base.html! πŸŽ‰</p>
{% endblock %}

Handling Forms (GET & POST)ΒΆ

Simple Form Example:

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

# In-memory storage (resets when app restarts)
confessions = []

@app.route('/')
def home():
    return render_template('confessions.html', confessions=confessions)

@app.route('/submit', methods=['GET', 'POST'])
def submit():
    if request.method == 'POST':
        category = request.form.get('category')
        confession = request.form.get('confession')
        emoji = request.form.get('emoji', 'πŸ˜…')

        confessions.append({
            'category': category,
            'confession': confession,
            'emoji': emoji
        })
        return redirect(url_for('home'))

    return render_template('submit_form.html')

if __name__ == '__main__':
    app.run(debug=True)

templates/submit_form.html:

<!DOCTYPE html>
<html>
<head>
    <title>Student Confession Booth 🀐</title>
    <style>
        body {
            font-family: Arial;
            max-width: 600px;
            margin: 50px auto;
            padding: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        }
        .form-container {
            background: white;
            padding: 30px;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }
        h1 { color: #667eea; text-align: center; }
        input, select, textarea {
            width: 100%;
            padding: 10px;
            margin: 10px 0;
            border: 2px solid #ddd;
            border-radius: 5px;
            font-size: 16px;
        }
        button {
            width: 100%;
            padding: 15px;
            background: #667eea;
            color: white;
            border: none;
            border-radius: 5px;
            font-size: 18px;
            cursor: pointer;
        }
        button:hover {
            background: #764ba2;
        }
    </style>
</head>
<body>
    <div class="form-container">
        <h1>🀐 Anonymous Confession Booth</h1>
        <p style="text-align: center; color: #666;">
            Share your college struggles anonymously! πŸ’­
        </p>
        <form method="POST" action="/submit">
            <label>Category:</label>
            <select name="category" required>
                <option value="">Select a category...</option>
                <option value="Academic Disasters">πŸ“š Academic Disasters</option>
                <option value="Social Awkwardness">πŸ™ˆ Social Awkwardness</option>
                <option value="Career Confusion">πŸ’Ό Career Confusion</option>
                <option value="General Life Failures">🀷 General Life Failures</option>
            </select>

            <label>Your Confession:</label>
            <textarea name="confession" rows="5"
                      placeholder="Tell us what's on your mind..."
                      required></textarea>

            <label>How do you feel?</label>
            <select name="emoji">
                <option value="πŸ˜…">πŸ˜… Embarrassed</option>
                <option value="😒">😒 Sad</option>
                <option value="😰">😰 Stressed</option>
                <option value="🀷">🀷 Confused</option>
                <option value="πŸ’ͺ">πŸ’ͺ Ready to Change</option>
            </select>

            <button type="submit">Submit Confession πŸš€</button>
        </form>
        <p style="text-align: center; margin-top: 20px;">
            <a href="/" style="color: #667eea;">← View All Confessions</a>
        </p>
    </div>
</body>
</html>

templates/confessions.html:

<!DOCTYPE html>
<html>
<head>
    <title>Confession Wall πŸ“‹</title>
    <style>
        body {
            font-family: Arial;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
            background: #f5f5f5;
        }
        h1 { text-align: center; color: #667eea; }
        .confession {
            background: white;
            padding: 20px;
            margin: 15px 0;
            border-radius: 10px;
            border-left: 5px solid #667eea;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }
        .category {
            background: #667eea;
            color: white;
            padding: 5px 15px;
            border-radius: 20px;
            display: inline-block;
            font-size: 14px;
            margin-bottom: 10px;
        }
        .add-btn {
            display: block;
            width: 200px;
            margin: 20px auto;
            padding: 15px;
            background: #667eea;
            color: white;
            text-align: center;
            text-decoration: none;
            border-radius: 8px;
            font-weight: bold;
        }
        .add-btn:hover {
            background: #764ba2;
        }
    </style>
</head>
<body>
    <h1>🀐 Student Confession Wall</h1>
    <p style="text-align: center; color: #666;">
        Anonymous confessions from fellow students πŸ’­
    </p>

    <a href="/submit" class="add-btn">+ Add Your Confession</a>

    {% if confessions %}
        {% for confession in confessions %}
            <div class="confession">
                <span class="category">{{ confession.category }}</span>
                <p style="font-size: 18px; margin: 10px 0;">
                    {{ confession.emoji }} {{ confession.confession }}
                </p>
            </div>
        {% endfor %}
    {% else %}
        <p style="text-align: center; color: #999; padding: 50px;">
            No confessions yet... be the first! πŸ€—
        </p>
    {% endif %}
</body>
</html>

Note

redirect(url_for('home')) redirects to a route by function name. Use url_for() instead of hardcoding URLs! ✨


CRUD Operations: Learning Journey TrackerΒΆ

Let’s build a complete CRUD application! πŸš€

app.py:

from flask import Flask, render_template, request, redirect, url_for
from datetime import datetime

app = Flask(__name__)

# In-memory database (list of dictionaries)
skills = [
    {'id': 1, 'name': 'Python Basics', 'status': 'Completed',
     'confidence': 80, 'emoji': '🐍', 'date': '2025-01-15'},
    {'id': 2, 'name': 'Web Development', 'status': 'In Progress',
     'confidence': 50, 'emoji': '🌐', 'date': '2025-02-01'},
    {'id': 3, 'name': 'Machine Learning', 'status': 'Started',
     'confidence': 20, 'emoji': 'πŸ€–', 'date': '2025-02-20'},
]

# Helper to get next ID
def get_next_id():
    return max([s['id'] for s in skills], default=0) + 1

# CREATE - Add new skill
@app.route('/create', methods=['GET', 'POST'])
def create():
    if request.method == 'POST':
        new_skill = {
            'id': get_next_id(),
            'name': request.form.get('name'),
            'status': request.form.get('status', 'Started'),
            'confidence': int(request.form.get('confidence', 0)),
            'emoji': request.form.get('emoji', 'πŸ“š'),
            'date': datetime.now().strftime('%Y-%m-%d')
        }
        skills.append(new_skill)
        return redirect(url_for('read_all'))

    return render_template('create.html')

# READ - View all skills
@app.route('/')
def read_all():
    # Calculate stats
    total = len(skills)
    completed = len([s for s in skills if s['status'] == 'Completed'])
    in_progress = len([s for s in skills if s['status'] == 'In Progress'])

    stats = {
        'total': total,
        'completed': completed,
        'in_progress': in_progress,
        'completion_rate': (completed / total * 100) if total > 0 else 0
    }

    return render_template('dashboard.html', skills=skills, stats=stats)

# READ - View single skill
@app.route('/skill/<int:skill_id>')
def read_one(skill_id):
    skill = next((s for s in skills if s['id'] == skill_id), None)
    if skill:
        return render_template('detail.html', skill=skill)
    return "Skill not found! 🀷", 404

# UPDATE - Edit skill
@app.route('/update/<int:skill_id>', methods=['GET', 'POST'])
def update(skill_id):
    skill = next((s for s in skills if s['id'] == skill_id), None)

    if not skill:
        return "Skill not found! 🀷", 404

    if request.method == 'POST':
        skill['name'] = request.form.get('name')
        skill['status'] = request.form.get('status')
        skill['confidence'] = int(request.form.get('confidence'))
        skill['emoji'] = request.form.get('emoji')
        return redirect(url_for('read_all'))

    return render_template('update.html', skill=skill)

# DELETE - Remove skill (with farewell ceremony πŸ‘‹)
@app.route('/delete/<int:skill_id>', methods=['POST'])
def delete(skill_id):
    global skills
    skill = next((s for s in skills if s['id'] == skill_id), None)

    if skill:
        skills = [s for s in skills if s['id'] != skill_id]
        return render_template('farewell.html', skill=skill)

    return "Skill not found! 🀷", 404

if __name__ == '__main__':
    app.run(debug=True)

templates/dashboard.html:

<!DOCTYPE html>
<html>
<head>
    <title>Learning Journey Tracker πŸš€</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        .container {
            max-width: 1000px;
            margin: 0 auto;
        }
        .header {
            background: white;
            padding: 30px;
            border-radius: 15px;
            text-align: center;
            margin-bottom: 20px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }
        .stats {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin: 20px 0;
        }
        .stat-card {
            background: white;
            padding: 20px;
            border-radius: 10px;
            text-align: center;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
        }
        .stat-number {
            font-size: 36px;
            font-weight: bold;
            color: #667eea;
        }
        .skill-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 20px;
            margin: 20px 0;
        }
        .skill-card {
            background: white;
            padding: 25px;
            border-radius: 15px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            transition: transform 0.2s;
        }
        .skill-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 8px 12px rgba(0,0,0,0.2);
        }
        .skill-emoji {
            font-size: 48px;
            display: block;
            margin-bottom: 15px;
        }
        .status {
            display: inline-block;
            padding: 5px 15px;
            border-radius: 20px;
            font-size: 12px;
            font-weight: bold;
            margin: 10px 0;
        }
        .status-completed { background: #2ecc71; color: white; }
        .status-inprogress { background: #f39c12; color: white; }
        .status-started { background: #3498db; color: white; }
        .confidence-bar {
            background: #ecf0f1;
            height: 20px;
            border-radius: 10px;
            overflow: hidden;
            margin: 10px 0;
        }
        .confidence-fill {
            height: 100%;
            background: linear-gradient(90deg, #667eea, #764ba2);
            transition: width 0.3s;
        }
        .btn {
            display: inline-block;
            padding: 10px 20px;
            margin: 5px;
            border-radius: 5px;
            text-decoration: none;
            font-weight: bold;
            transition: all 0.2s;
        }
        .btn-primary { background: #667eea; color: white; }
        .btn-secondary { background: #95a5a6; color: white; }
        .btn-danger { background: #e74c3c; color: white; }
        .btn:hover { opacity: 0.9; transform: scale(1.05); }
        .add-btn {
            position: fixed;
            bottom: 30px;
            right: 30px;
            width: 60px;
            height: 60px;
            border-radius: 50%;
            background: #2ecc71;
            color: white;
            font-size: 32px;
            display: flex;
            align-items: center;
            justify-content: center;
            text-decoration: none;
            box-shadow: 0 4px 8px rgba(0,0,0,0.3);
            transition: all 0.3s;
        }
        .add-btn:hover {
            transform: scale(1.1) rotate(90deg);
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>πŸš€ My Learning Journey Tracker</h1>
            <p style="color: #666; margin-top: 10px;">
                Track your skills, celebrate progress, and say goodbye to abandoned dreams! πŸ˜…
            </p>
        </div>

        <div class="stats">
            <div class="stat-card">
                <div class="stat-number">{{ stats.total }}</div>
                <div>Total Skills</div>
            </div>
            <div class="stat-card">
                <div class="stat-number">{{ stats.completed }}</div>
                <div>βœ… Completed</div>
            </div>
            <div class="stat-card">
                <div class="stat-number">{{ stats.in_progress }}</div>
                <div>⏳ In Progress</div>
            </div>
            <div class="stat-card">
                <div class="stat-number">{{ "%.0f"|format(stats.completion_rate) }}%</div>
                <div>Completion Rate</div>
            </div>
        </div>

        <div class="skill-grid">
            {% for skill in skills %}
            <div class="skill-card">
                <span class="skill-emoji">{{ skill.emoji }}</span>
                <h3>{{ skill.name }}</h3>

                {% if skill.status == 'Completed' %}
                    <span class="status status-completed">βœ… {{ skill.status }}</span>
                {% elif skill.status == 'In Progress' %}
                    <span class="status status-inprogress">⏳ {{ skill.status }}</span>
                {% else %}
                    <span class="status status-started">πŸš€ {{ skill.status }}</span>
                {% endif %}

                <div style="margin: 15px 0;">
                    <small style="color: #666;">Confidence Level</small>
                    <div class="confidence-bar">
                        <div class="confidence-fill"
                             style="width: {{ skill.confidence }}%"></div>
                    </div>
                    <small style="color: #666;">{{ skill.confidence }}%</small>
                </div>

                <p style="color: #999; font-size: 12px;">
                    Started: {{ skill.date }}
                </p>

                <div style="margin-top: 15px;">
                    <a href="/skill/{{ skill.id }}" class="btn btn-primary">
                        πŸ‘οΈ View
                    </a>
                    <a href="/update/{{ skill.id }}" class="btn btn-secondary">
                        ✏️ Edit
                    </a>
                    <form action="/delete/{{ skill.id }}" method="POST"
                          style="display: inline;"
                          onsubmit="return confirm('Say goodbye to {{ skill.name }}? πŸ‘‹')">
                        <button type="submit" class="btn btn-danger"
                                style="border: none; cursor: pointer;">
                            πŸ—‘οΈ Delete
                        </button>
                    </form>
                </div>
            </div>
            {% endfor %}
        </div>

        {% if stats.total == 0 %}
        <div style="text-align: center; color: white; padding: 50px;">
            <h2>No skills yet! 🌱</h2>
            <p>Start your learning journey by adding your first skill! πŸš€</p>
        </div>
        {% endif %}
    </div>

    <a href="/create" class="add-btn" title="Add New Skill">+</a>
</body>
</html>

templates/create.html:

<!DOCTYPE html>
<html>
<head>
    <title>Add New Skill 🌱</title>
    <style>
        body {
            font-family: Arial;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }
        .form-container {
            background: white;
            padding: 40px;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            max-width: 500px;
            width: 100%;
        }
        h1 { color: #667eea; text-align: center; margin-bottom: 30px; }
        label {
            display: block;
            margin-top: 15px;
            color: #333;
            font-weight: bold;
        }
        input, select {
            width: 100%;
            padding: 12px;
            margin-top: 5px;
            border: 2px solid #ddd;
            border-radius: 5px;
            font-size: 16px;
        }
        input:focus, select:focus {
            outline: none;
            border-color: #667eea;
        }
        button {
            width: 100%;
            padding: 15px;
            margin-top: 20px;
            background: #667eea;
            color: white;
            border: none;
            border-radius: 5px;
            font-size: 18px;
            font-weight: bold;
            cursor: pointer;
            transition: background 0.3s;
        }
        button:hover {
            background: #764ba2;
        }
        .back-link {
            display: block;
            text-align: center;
            margin-top: 20px;
            color: #667eea;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <div class="form-container">
        <h1>🌱 Add New Skill</h1>
        <form method="POST">
            <label>Skill Name:</label>
            <input type="text" name="name"
                   placeholder="e.g., Machine Learning" required>

            <label>Emoji:</label>
            <input type="text" name="emoji"
                   placeholder="πŸ€–" maxlength="2" required>

            <label>Status:</label>
            <select name="status" required>
                <option value="Started">πŸš€ Started</option>
                <option value="In Progress">⏳ In Progress</option>
                <option value="Completed">βœ… Completed</option>
            </select>

            <label>Confidence Level (0-100):</label>
            <input type="range" name="confidence"
                   min="0" max="100" value="50"
                   oninput="this.nextElementSibling.value = this.value + '%'">
            <output style="display: block; text-align: center;
                          color: #667eea; font-weight: bold;">50%</output>

            <button type="submit">πŸŽ‰ Add Skill</button>
        </form>
        <a href="/" class="back-link">← Back to Dashboard</a>
    </div>
</body>
</html>

templates/farewell.html:

<!DOCTYPE html>
<html>
<head>
    <title>Farewell Ceremony πŸ‘‹</title>
    <style>
        body {
            font-family: Arial;
            background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
            color: white;
            text-align: center;
        }
        .ceremony {
            max-width: 600px;
            animation: fadeIn 1s;
        }
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(-20px); }
            to { opacity: 1; transform: translateY(0); }
        }
        .emoji {
            font-size: 100px;
            animation: float 3s ease-in-out infinite;
        }
        @keyframes float {
            0%, 100% { transform: translateY(0); }
            50% { transform: translateY(-20px); }
        }
        h1 { font-size: 48px; margin: 20px 0; }
        p { font-size: 20px; line-height: 1.6; }
        .btn {
            display: inline-block;
            margin-top: 30px;
            padding: 15px 30px;
            background: white;
            color: #e74c3c;
            text-decoration: none;
            border-radius: 8px;
            font-weight: bold;
            transition: transform 0.2s;
        }
        .btn:hover {
            transform: scale(1.05);
        }
    </style>
</head>
<body>
    <div class="ceremony">
        <div class="emoji">πŸ‘‹</div>
        <h1>Farewell, {{ skill.name }}!</h1>
        <p>
            {{ skill.emoji }} We bid farewell to "{{ skill.name }}".<br>
            You served us well with {{ skill.confidence }}% confidence.<br>
            May you rest in peace in the graveyard of abandoned skills. πŸͺ¦
        </p>
        <p style="font-style: italic; opacity: 0.8;">
            "It's not you, it's me." - Every developer ever πŸ˜…
        </p>
        <a href="/" class="btn">Return to Dashboard 🏠</a>
    </div>
</body>
</html>

Note

This complete CRUD application demonstrates all four operations! Notice the beautiful styling, animations, and the humorous β€œfarewell ceremony” for deleted skills! 🎭✨


Flash MessagesΒΆ

Show feedback messages after actions:

from flask import Flask, render_template, flash, redirect, url_for

app = Flask(__name__)
app.secret_key = 'your-secret-key-here'  # Required for flash messages

@app.route('/submit', methods=['POST'])
def submit():
    # Process form...
    flash('βœ… Success! Your data was saved!', 'success')
    flash('⚠️ Warning: This is a demo!', 'warning')
    return redirect(url_for('home'))

@app.route('/')
def home():
    return render_template('home.html')

In template:

{% with messages = get_flashed_messages(with_categories=true) %}
    {% if messages %}
        {% for category, message in messages %}
            <div class="alert alert-{{ category }}">
                {{ message }}
            </div>
        {% endfor %}
    {% endif %}
{% endwith %}

TasksΒΆ

Task 1: Student Confession Booth

Build a confession submission system with Jinja2 templates. Create: (1) Form page with confession categories (Academic, Social, Career, Life), (2) Display page showing all confessions with appropriate styling per category, (3) Use template inheritance with a base template. Add emojis and CSS styling! 🀐

Hint: Store confessions in a list. Use {% extends "base.html" %} for inheritance. Style each category differently with CSS classes.

Task 2: Personal Blog with CRUD

Create a complete blog system: (1) Homepage listing all posts, (2) Create post form, (3) View individual post, (4) Edit post, (5) Delete post with confirmation. Use colorful CSS, emojis for post types (πŸ“ Article, πŸ’‘ Idea, πŸŽ‰ Achievement). Include post count dashboard.

Hint: Use list of dictionaries for posts. Implement all CRUD routes. Use redirect(url_for('route_name')) after create/update/delete.

Task 3: Mood Journal with Templates

Build a mood tracking app: Form to log mood (emoji selector), note, and activities. Display mood history with different colors per mood. Show statistics: most common mood, total entries, mood distribution. Use Jinja2 loops and conditionals for display. Make it beautiful! 🌈

Hint: Store moods with timestamps. Calculate statistics in route before passing to template. Use {% for %} to display history.

Task 4: Recipe Manager

Create a recipe management system: Add recipe (name, ingredients list, instructions, cooking time, emoji). List all recipes. View recipe details. Edit recipe. Delete recipe. Use template inheritance. Add search functionality using form GET request. Style with food-themed colors! πŸ•

Hint: Store ingredients as comma-separated string, split in template with {{ ingredients.split(',') }}. Implement search by filtering list in route.

Task 5: Goal Tracker with Progress

Build a goal tracking app: Create goals with title, description, target date, progress (0-100%). Dashboard showing all goals with progress bars. Update progress. Mark as complete. Delete with farewell message. Calculate statistics: total goals, completed, in progress, average progress. Use animations for progress bars! 🎯

Hint: Use <input type="range"> for progress. Style progress bars with width: {{ progress }}%. Use JavaScript oninput to update display value as user drags slider.


SummaryΒΆ

  • Jinja2 is Flask’s templating engine 🎨

  • Use {{ variable }} for variables, {% for %} for loops, {% if %} for conditions

  • Template inheritance: {% extends %} and {% block %} enable DRY code

  • Forms: Use methods=['GET', 'POST'] to handle both display and submission

  • CRUD Operations: - Create: POST route to add new data - Read: GET route to display data - Update: GET to show form, POST to save changes - Delete: POST to remove data

  • Use request.form.get() for form data

  • redirect(url_for('function_name')) redirects to routes

  • flash() shows one-time messages (requires secret_key)

  • Always use url_for() instead of hardcoding URLs

  • Add emojis and styling to make apps fun! πŸŽ‰