Add todo due date and priority features

- Added priority (low, medium, high) to todo items
- Added due date to todo items
- Enhanced API to support filtering by priority and due date
- Added overdue and due_soon filters for better task management
- Automatic sorting by priority and due date
- Created alembic migration for the new fields
- Updated documentation

generated with BackendIM... (backend.im)
This commit is contained in:
Automated Action 2025-05-14 01:27:39 +00:00
parent f55cad6274
commit fe3924497e
6 changed files with 142 additions and 14 deletions

View File

@ -5,7 +5,11 @@ A simple Todo application built with FastAPI and SQLite.
## Features ## Features
- Create, read, update, and delete todos - Create, read, update, and delete todos
- Filter todos by completion status - Assign priority levels (low, medium, high) to todos
- Set due dates for todos
- Filter todos by completion status, priority, and due dates
- Find overdue and soon-to-be-due todos
- Automatic sorting by priority and due date
- Health endpoint for application monitoring - Health endpoint for application monitoring
## Project Structure ## Project Structure
@ -54,9 +58,18 @@ uvicorn main:app --reload
### Todos ### Todos
- `GET /api/todos`: List all todos - `GET /api/todos`: List all todos
- `POST /api/todos`: Create a new todo - Query parameters:
- `completed`: Filter by completion status (boolean)
- `priority`: Filter by priority level (low, medium, high)
- `due_date_before`: Filter todos due before this date
- `due_date_after`: Filter todos due after this date
- `due_soon`: Get todos due within the next 3 days (boolean)
- `overdue`: Get overdue todos (boolean)
- `skip`: Number of records to skip (pagination)
- `limit`: Maximum number of records to return (pagination)
- `POST /api/todos`: Create a new todo (includes priority and due date)
- `GET /api/todos/{todo_id}`: Get a specific todo - `GET /api/todos/{todo_id}`: Get a specific todo
- `PUT /api/todos/{todo_id}`: Update a todo - `PUT /api/todos/{todo_id}`: Update a todo (can update priority and due date)
- `DELETE /api/todos/{todo_id}`: Delete a todo - `DELETE /api/todos/{todo_id}`: Delete a todo
### Health ### Health

View File

@ -8,7 +8,6 @@ import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.db.base import Base from app.db.base import Base
from app.models.todo import Todo
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.

View File

@ -0,0 +1,35 @@
"""Add due_date and priority fields to todo
Revision ID: add_due_date_and_priority
Revises: initial_migration
Create Date: 2025-05-14 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'add_due_date_and_priority'
down_revision = 'initial_migration'
branch_labels = None
depends_on = None
def upgrade():
# SQLite doesn't support ENUM types natively, so we'll use a string with a CHECK constraint
with op.batch_alter_table('todos') as batch_op:
batch_op.add_column(sa.Column('priority', sa.String(10), nullable=False, server_default='medium'))
batch_op.add_column(sa.Column('due_date', sa.DateTime(timezone=True), nullable=True))
# Add a check constraint to ensure priority is one of the allowed values
batch_op.create_check_constraint(
'priority_check',
"priority IN ('low', 'medium', 'high')"
)
def downgrade():
with op.batch_alter_table('todos') as batch_op:
batch_op.drop_constraint('priority_check')
batch_op.drop_column('due_date')
batch_op.drop_column('priority')

View File

@ -1,10 +1,12 @@
from typing import List, Optional from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status from datetime import datetime, timedelta
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import and_, not_
from app.db.session import get_db from app.db.session import get_db
from app.models.todo import Todo from app.models.todo import Todo
from app.schemas.todo import TodoCreate, TodoUpdate, Todo as TodoSchema from app.schemas.todo import TodoCreate, TodoUpdate, Todo as TodoSchema, PriorityEnum as SchemaPriorityEnum
router = APIRouter( router = APIRouter(
prefix="/todos", prefix="/todos",
@ -27,11 +29,62 @@ def read_todos(
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
completed: Optional[bool] = None, completed: Optional[bool] = None,
priority: Optional[SchemaPriorityEnum] = None,
due_date_before: Optional[datetime] = None,
due_date_after: Optional[datetime] = None,
due_soon: Optional[bool] = None,
overdue: Optional[bool] = None,
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
query = db.query(Todo) query = db.query(Todo)
filters = []
# Apply completed filter
if completed is not None: if completed is not None:
query = query.filter(Todo.completed == completed) filters.append(Todo.completed == completed)
# Apply priority filter
if priority is not None:
filters.append(Todo.priority == priority.value)
# Apply due date filters
if due_date_before is not None:
filters.append(Todo.due_date <= due_date_before)
if due_date_after is not None:
filters.append(Todo.due_date >= due_date_after)
# Due soon filter (next 3 days)
if due_soon is True:
now = datetime.now()
three_days_later = now + timedelta(days=3)
filters.append(and_(
Todo.due_date >= now,
Todo.due_date <= three_days_later,
not_(Todo.completed)
))
# Overdue filter
if overdue is True:
now = datetime.now()
filters.append(and_(
Todo.due_date < now,
not_(Todo.completed)
))
# Apply all filters
if filters:
query = query.filter(and_(*filters))
# Order by priority (high to low) and due date (closest first)
query = query.order_by(
# Custom ordering for priority
# This works because of our string values 'high', 'medium', 'low'
Todo.priority.desc(),
# Due date, null values last
Todo.due_date.asc()
)
todos = query.offset(skip).limit(limit).all() todos = query.offset(skip).limit(limit).all()
return todos return todos

View File

@ -1,6 +1,13 @@
from sqlalchemy import Boolean, Column, Integer, String, DateTime from sqlalchemy import Boolean, Column, Integer, String, DateTime, CheckConstraint
from sqlalchemy.sql import func from sqlalchemy.sql import func
from app.db.base import Base from app.db.base import Base
import enum
class PriorityEnum(str, enum.Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
class Todo(Base): class Todo(Base):
@ -10,5 +17,15 @@ class Todo(Base):
title = Column(String, index=True) title = Column(String, index=True)
description = Column(String, nullable=True) description = Column(String, nullable=True)
completed = Column(Boolean, default=False) completed = Column(Boolean, default=False)
priority = Column(String(10), default=PriorityEnum.MEDIUM.value)
due_date = Column(DateTime(timezone=True), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now()) created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Add a check constraint to ensure priority is one of the allowed values
__table_args__ = (
CheckConstraint(
"priority IN ('low', 'medium', 'high')",
name="priority_check"
),
)

View File

@ -1,12 +1,21 @@
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
from enum import Enum
class PriorityEnum(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
class TodoBase(BaseModel): class TodoBase(BaseModel):
title: str title: str
description: Optional[str] = None description: Optional[str] = None
completed: bool = False completed: bool = False
priority: PriorityEnum = PriorityEnum.MEDIUM
due_date: Optional[datetime] = None
class TodoCreate(TodoBase): class TodoCreate(TodoBase):
@ -17,6 +26,8 @@ class TodoUpdate(BaseModel):
title: Optional[str] = None title: Optional[str] = None
description: Optional[str] = None description: Optional[str] = None
completed: Optional[bool] = None completed: Optional[bool] = None
priority: Optional[PriorityEnum] = None
due_date: Optional[datetime] = None
class TodoInDB(TodoBase): class TodoInDB(TodoBase):