Skip to main content

Flask: Creating a Task Management App

Flask: Creating a Task Management App

Building a task management application with Flask, a lightweight Python web framework powered by Werkzeug and Jinja2, is an excellent way to create a dynamic, user-friendly platform for organizing tasks. This tutorial guides you through developing a task management app with features like task creation, editing, deletion, and user authentication. It covers setup, key components, and best practices for structuring a maintainable Flask project.


01. Why Build a Task Management App with Flask?

Flask’s simplicity and flexibility make it ideal for building a task management app. It supports modular design with blueprints, integrates seamlessly with databases like SQLAlchemy, and uses Jinja2 for dynamic templating. A Flask task management app can handle user authentication, task CRUD operations, and custom styling while remaining lightweight and extensible.

Example: Basic Task App Setup

# app/__init__.py
from flask import Flask

def create_app():
    """Initialize the Flask task management application."""
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret-key'

    @app.route('/')
    def home():
        """Render the app homepage."""
        return 'Welcome to the Flask Task Manager!'

    return app

Run Command:

export FLASK_APP=app
flask run

Output: (In browser at http://127.0.0.1:5000)

Welcome to the Flask Task Manager!

Explanation:

  • create_app - Uses an application factory for modularity.
  • Sets up the foundation for a task management app.

02. Key Components of the Task Management App

A Flask task management app includes routes for task CRUD operations, a database for storing tasks and users, templates for rendering pages, and user authentication. The table below summarizes key components and their purposes:

Component Description Tool/Feature
Routes Handle task creation, editing, deletion Blueprints, Werkzeug routing
Database Store tasks and users Flask-SQLAlchemy
Templates Render dynamic HTML Jinja2
Authentication Manage user login Flask-Login
Forms Handle user input Flask-WTF


2.1 Project Structure

Example: Task App Project Layout

task_manager/
├── app/
│   ├── __init__.py
│   ├── config.py
│   ├── models/
│   │   ├── __init__.py
│   │   ├── task.py
│   │   ├── user.py
│   ├── routes/
│   │   ├── __init__.py
│   │   ├── tasks.py
│   │   ├── auth.py
│   ├── templates/
│   │   ├── base.html
│   │   ├── home.html
│   │   ├── task.html
│   │   ├── create_task.html
│   │   ├── login.html
│   ├── static/
│   │   ├── css/
│   │   │   ├── style.css
│   ├── forms.py
├── tests/
│   ├── test_routes.py
├── requirements.txt
├── run.py

Explanation:

  • Organizes code into models, routes, templates, and static files.
  • Separates authentication and task routes for modularity.

2.2 Setting Up the Application

Example: Application Factory with Extensions

# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from app.routes.tasks import tasks_bp
from app.routes.auth import auth_bp

db = SQLAlchemy()
login_manager = LoginManager()

def create_app():
    """Initialize the Flask task management application."""
    app = Flask(__name__)
    app.config.from_object('app.config.DevelopmentConfig')
    
    db.init_app(app)
    login_manager.init_app(app)
    login_manager.login_view = 'auth.login'
    
    app.register_blueprint(tasks_bp)
    app.register_blueprint(auth_bp)
    
    with app.app_context():
        db.create_all()
    
    return app

# app/config.py
class DevelopmentConfig:
    SECRET_KEY = 'dev-key'
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///tasks.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

Example run.py:

# run.py
from app import create_app

app = create_app()

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

Explanation:

  • Initializes SQLAlchemy for database management and Flask-Login for authentication.
  • Registers blueprints for task and auth routes.

2.3 Database Models

Example: Task and User Models

# app/models/user.py
from app import db, login_manager
from flask_login import UserMixin

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True, nullable=False)
    password = db.Column(db.String(100), nullable=False)

    @login_manager.user_loader
    def load_user(user_id):
        return User.query.get(int(user_id))

# app/models/task.py
from app import db
from datetime import datetime

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    description = db.Column(db.Text)
    completed = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship('User', backref='tasks')

Explanation:

  • User - Stores user data with authentication support via Flask-Login.
  • Task - Stores task details with a relationship to the owner.

2.4 Forms for User Input

Example: Login and Task Creation Forms

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

class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(min=3, max=50)])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Login')

class TaskForm(FlaskForm):
    title = StringField('Title', validators=[DataRequired(), Length(max=100)])
    description = TextAreaField('Description')
    completed = BooleanField('Completed')
    submit = SubmitField('Save Task')

Explanation:

  • Flask-WTF - Simplifies form handling with validation.
  • Forms ensure secure and validated user input for tasks and login.

2.5 Authentication Routes

Example: User Login

# app/routes/auth.py
from flask import Blueprint, render_template, redirect, url_for, flash
from flask_login import login_user, logout_user, login_required
from app.forms import LoginForm
from app.models.user import User
import hashlib

auth_bp = Blueprint('auth', __name__, template_folder='templates')

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    """Handle user login."""
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user and user.password == hashlib.sha256(form.password.data.encode()).hexdigest():
            login_user(user)
            return redirect(url_for('tasks.home'))
        flash('Invalid credentials')
    return render_template('login.html', form=form)

@auth_bp.route('/logout')
@login_required
def logout():
    """Handle user logout."""
    logout_user()
    return redirect(url_for('tasks.home'))

Template: login.html

{% extends 'base.html' %}
{% block content %}
<h2>Login</h2>
{% for message in get_flashed_messages() %}
    <p style="color: red;">{{ message }}</p>
{% endfor %}
<form method="post">
    {{ form.hidden_tag() }}
    {{ form.username.label }} {{ form.username() }}<br>
    {{ form.password.label }} {{ form.password() }}<br>
    {{ form.submit() }}
</form>
{% endblock %}

Explanation:

  • Flask-Login - Manages user sessions for authentication.
  • Password hashing ensures secure authentication.

2.6 Task Routes

Example: Task CRUD Routes

# app/routes/tasks.py
from flask import Blueprint, render_template, redirect, url_for, request
from flask_login import login_required, current_user
from app import db
from app.models.task import Task
from app.forms import TaskForm

tasks_bp = Blueprint('tasks', __name__, template_folder='templates')

@tasks_bp.route('/')
def home():
    """Render the task homepage with user's tasks."""
    tasks = Task.query.filter_by(user_id=current_user.id).order_by(Task.created_at.desc()).all() if current_user.is_authenticated else []
    return render_template('home.html', tasks=tasks)

@tasks_bp.route('/task/<int:task_id>')
def view_task(task_id):
    """Render a single task."""
    task = Task.query.get_or_404(task_id)
    if task.user_id != current_user.id:
        return 'Unauthorized', 403
    return render_template('task.html', task=task)

@tasks_bp.route('/create', methods=['GET', 'POST'])
@login_required
def create_task():
    """Handle task creation."""
    form = TaskForm()
    if form.validate_on_submit():
        task = Task(
            title=form.title.data,
            description=form.description.data,
            user_id=current_user.id
        )
        db.session.add(task)
        db.session.commit()
        return redirect(url_for('tasks.home'))
    return render_template('create_task.html', form=form)

@tasks_bp.route('/edit/<int:task_id>', methods=['GET', 'POST'])
@login_required
def edit_task(task_id):
    """Handle task editing."""
    task = Task.query.get_or_404(task_id)
    if task.user_id != current_user.id:
        return 'Unauthorized', 403
    form = TaskForm(obj=task)
    if form.validate_on_submit():
        task.title = form.title.data
        task.description = form.description.data
        task.completed = form.completed.data
        db.session.commit()
        return redirect(url_for('tasks.home'))
    return render_template('create_task.html', form=form, task=task)

@tasks_bp.route('/delete/<int:task_id>', methods=['POST'])
@login_required
def delete_task(task_id):
    """Handle task deletion."""
    task = Task.query.get_or_404(task_id)
    if task.user_id != current_user.id:
        return 'Unauthorized', 403
    db.session.delete(task)
    db.session.commit()
    return redirect(url_for('tasks.home'))

Template: home.html

{% extends 'base.html' %}
{% block content %}
<h2>My Tasks</h2>
{% if tasks %}
    <ul>
    {% for task in tasks %}
        <li>
            <a href="{{ url_for('tasks.view_task', task_id=task.id) }}">{{ task.title }}</a>
            {% if task.completed %} (Completed) {% endif %}
            - <a href="{{ url_for('tasks.edit_task', task_id=task.id) }}">Edit</a>
            <form method="post" action="{{ url_for('tasks.delete_task', task_id=task.id) }}" style="display:inline;">
                <button type="submit" onclick="return confirm('Are you sure?')">Delete</button>
            </form>
        </li>
    {% endfor %}
    </ul>
{% else %}
    <p>No tasks yet.</p>
{% endif %}
{% if current_user.is_authenticated %}
    <a href="{{ url_for('tasks.create_task') }}">Create Task</a>
{% else %}
    <a href="{{ url_for('auth.login') }}">Login to manage tasks</a>
{% endif %}
{% endblock %}

Template: base.html

<!DOCTYPE html>
<html>
<head>
    <title>Task Manager</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
    <nav>
        <a href="{{ url_for('tasks.home') }}">Home</a>
        {% if current_user.is_authenticated %}
            <a href="{{ url_for('auth.logout') }}">Logout</a>
        {% else %}
            <a href="{{ url_for('auth.login') }}">Login</a>
        {% endif %}
    </nav>
    <div>
        {% block content %}{% endblock %}
    </div>
</body>
</html>

Template: create_task.html

{% extends 'base.html' %}
{% block content %}
<h2>{% if task %}Edit Task{% else %}Create Task{% endif %}</h2>
<form method="post">
    {{ form.hidden_tag() }}
    {{ form.title.label }} {{ form.title() }}<br>
    {{ form.description.label }} {{ form.description() }}<br>
    {{ form.completed.label }} {{ form.completed() }}<br>
    {{ form.submit() }}
</form>
{% endblock %}

Explanation:

  • Routes handle task listing, viewing, creation, editing, and deletion.
  • Jinja2 templates render dynamic content with authentication checks.
  • Authorization ensures users only access their own tasks.

2.7 Styling

Example: Basic CSS


/* app/static/css/style.css */
body {
    font-family: Arial, sans-serif;
    margin: 20px;
}
nav {
    margin-bottom: 20px;
}
nav a {
    margin-right: 10px;
}
h2 {
    color: #333;
}
ul {
    list-style-type: none;
    padding: 0;
}
li {
    margin: 10px 0;
}

Explanation:

  • Static CSS enhances the app’s appearance.
  • Linked in base.html for consistent styling.

2.8 Testing

Example: Testing Routes

# tests/test_routes.py
import pytest
from app import create_app, db
from app.models.user import User
from app.models.task import Task
import hashlib

@pytest.fixture
def client():
    app = create_app()
    app.config['TESTING'] = True
    with app.test_client() as client:
        with app.app_context():
            db.create_all()
            yield client
            db.drop_all()

def test_create_task(client):
    # Create a test user
    password = hashlib.sha256('password'.encode()).hexdigest()
    user = User(username='testuser', password=password)
    db.session.add(user)
    db.session.commit()
    
    # Log in
    client.post('/login', data={'username': 'testuser', 'password': 'password'})
    
    # Create a task
    response = client.post('/create', data={'title': 'Test Task', 'description': 'Do this'})
    assert response.status_code == 302  # Redirect to home
    assert Task.query.filter_by(title='Test Task').first() is not None

Output: (When running pytest)

collected 1 item
tests/test_routes.py .                                            [100%]

Explanation:

  • Tests validate task creation and database integration.
  • Ensures reliability of core app features.

2.9 Incorrect Practices

Example: Monolithic Task App

# app.py
from flask import Flask, render_template, request

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'

# All routes, models, and logic in one file
@app.route('/')
def home():
    return render_template('home.html')

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

Explanation:

  • Monolithic structure is hard to maintain and scale.
  • Solution: Use blueprints, separate models, and modular design.

03. Effective Usage

3.1 Recommended Practices

  • Use blueprints, application factories, and testing for maintainability.

Example: Complete Task App Setup

# app/__init__.py (as shown in 2.2)
# app/routes/tasks.py (as shown in 2.6)
# app/routes/auth.py (as shown in 2.5)
# app/models/task.py, user.py (as shown in 2.3)
# app/forms.py (as shown in 2.4)
# app/templates/base.html, home.html, etc. (as shown in 2.6)
# Requirements
# requirements.txt
Flask==3.0.3
Flask-SQLAlchemy==3.1.1
Flask-Login==0.6.3
Flask-WTF==1.2.1
pytest==8.3.3

Setup Command:

pip install -r requirements.txt
export FLASK_APP=run
flask run
  • Modular design with blueprints and separate concerns.
  • Tests and documentation ensure reliability and clarity.
  • Secure authentication and form validation.

3.2 Practices to Avoid

  • Avoid storing plain-text passwords or skipping authorization.

Example: Insecure Task Access

# app/routes/tasks.py
@tasks_bp.route(&#39;/task/&lt;int:task_id&gt;&#39;)
def view_task(task_id):
    task = Task.query.get_or_404(task_id)
    return render_template(&#39;task.html&#39;, task=task)  # No authorization
  • Lack of authorization allows access to other users’ tasks.
  • Solution: Check task.user_id == current_user.id.

04. Common Use Cases

4.1 Displaying User Tasks

Render a list of tasks specific to the logged-in user.

Example: Task Listing

# app/routes/tasks.py (as shown in 2.6)
@tasks_bp.route('/')
def home():
    tasks = Task.query.filter_by(user_id=current_user.id).order_by(Task.created_at.desc()).all() if current_user.is_authenticated else []
    return render_template('home.html', tasks=tasks)

Output: (In browser at /)


My Tasks

Explanation:

  • Dynamic rendering with Jinja2 displays user-specific tasks.
  • Links for editing and deleting enhance usability.

4.2 Secure Task Management

Allow authenticated users to create, edit, and delete tasks securely.

Example: Task Creation

# app/routes/tasks.py (as shown in 2.6)
@tasks_bp.route('/create', methods=['GET', 'POST'])
@login_required
def create_task():
    form = TaskForm()
    if form.validate_on_submit():
        task = Task(
            title=form.title.data,
            description=form.description.data,
            user_id=current_user.id
        )
        db.session.add(task)
        db.session.commit()
        return redirect(url_for('tasks.home'))
    return render_template('create_task.html', form=form)

Explanation:

  • login_required - Restricts access to authenticated users.
  • Flask-WTF validates input for security.

Conclusion

Building a task management app with Flask, leveraging Werkzeug, Jinja2, and extensions like Flask-SQLAlchemy, Flask-Login, and Flask-WTF, enables developers to create a secure, user-friendly, and maintainable platform. Key takeaways:

  • Use blueprints and application factories for modular design.
  • Implement secure authentication and task authorization.
  • Write tests to ensure reliability of core features.
  • Avoid monolithic code and insecure practices like plain-text passwords.

With these practices, you can build a robust Flask task management app that is easy to maintain and extend!

Comments