Add todo priority and due date features
- Add priority levels (low, medium, high) to Todo model - Add due date field to Todo model - Create Alembic migration for new fields - Update Todo schemas to include new fields - Enhance CRUD operations with priority and due date filtering - Update API endpoints to support new fields - Implement smart ordering by priority and due date - Update documentation to reflect changes
This commit is contained in:
parent
f67be35a71
commit
5b2cda28a1
@ -9,6 +9,9 @@ A powerful, secure RESTful API for managing tasks and todos, built with FastAPI
|
|||||||
- Token revocation (logout)
|
- Token revocation (logout)
|
||||||
- Role-based access control (User/Admin roles)
|
- Role-based access control (User/Admin roles)
|
||||||
- 📝 Todo CRUD operations
|
- 📝 Todo CRUD operations
|
||||||
|
- Priority levels (High, Medium, Low)
|
||||||
|
- Due dates for better task management
|
||||||
|
- Smart ordering by priority and due date
|
||||||
- 👤 User management
|
- 👤 User management
|
||||||
- 🔍 Advanced todo filtering and pagination
|
- 🔍 Advanced todo filtering and pagination
|
||||||
- 📄 API documentation (via Swagger UI and ReDoc)
|
- 📄 API documentation (via Swagger UI and ReDoc)
|
||||||
@ -98,6 +101,9 @@ The `GET /api/v1/todos/` endpoint supports the following query parameters:
|
|||||||
- `limit`: Maximum number of records to return (default: 100)
|
- `limit`: Maximum number of records to return (default: 100)
|
||||||
- `title`: Filter by title (contains search)
|
- `title`: Filter by title (contains search)
|
||||||
- `is_completed`: Filter by completion status (true/false)
|
- `is_completed`: Filter by completion status (true/false)
|
||||||
|
- `priority`: Filter by priority level (low, medium, high)
|
||||||
|
- `due_date_before`: Filter for todos due before this date
|
||||||
|
- `due_date_after`: Filter for todos due after this date
|
||||||
|
|
||||||
## Database Schema
|
## Database Schema
|
||||||
|
|
||||||
@ -118,6 +124,8 @@ id: Integer (Primary Key)
|
|||||||
title: String (Indexed)
|
title: String (Indexed)
|
||||||
description: Text (Optional)
|
description: Text (Optional)
|
||||||
is_completed: Boolean (Default: False)
|
is_completed: Boolean (Default: False)
|
||||||
|
priority: Enum(low, medium, high) (Default: medium)
|
||||||
|
due_date: DateTime (Optional)
|
||||||
owner_id: Integer (Foreign Key to User)
|
owner_id: Integer (Foreign Key to User)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from datetime import datetime
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
@ -11,6 +12,7 @@ from app.crud.crud_todo import (
|
|||||||
get_todos,
|
get_todos,
|
||||||
update_todo,
|
update_todo,
|
||||||
)
|
)
|
||||||
|
from app.models.todo import PriorityLevel
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.schemas.todo import Todo, TodoCreate, TodoUpdate
|
from app.schemas.todo import Todo, TodoCreate, TodoUpdate
|
||||||
|
|
||||||
@ -24,6 +26,9 @@ def read_todos(
|
|||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
is_completed: Optional[bool] = None,
|
is_completed: Optional[bool] = None,
|
||||||
|
priority: Optional[PriorityLevel] = None,
|
||||||
|
due_date_before: Optional[datetime] = None,
|
||||||
|
due_date_after: Optional[datetime] = None,
|
||||||
current_user: User = Depends(get_current_active_user),
|
current_user: User = Depends(get_current_active_user),
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
@ -33,6 +38,9 @@ def read_todos(
|
|||||||
- **limit**: Maximum number of records to return
|
- **limit**: Maximum number of records to return
|
||||||
- **title**: Filter by title (contains search)
|
- **title**: Filter by title (contains search)
|
||||||
- **is_completed**: Filter by completion status
|
- **is_completed**: Filter by completion status
|
||||||
|
- **priority**: Filter by priority level (low, medium, high)
|
||||||
|
- **due_date_before**: Filter for todos due before this date
|
||||||
|
- **due_date_after**: Filter for todos due after this date
|
||||||
"""
|
"""
|
||||||
todos = get_todos(
|
todos = get_todos(
|
||||||
db=db,
|
db=db,
|
||||||
@ -40,7 +48,10 @@ def read_todos(
|
|||||||
skip=skip,
|
skip=skip,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
title=title,
|
title=title,
|
||||||
is_completed=is_completed
|
is_completed=is_completed,
|
||||||
|
priority=priority,
|
||||||
|
due_date_before=due_date_before,
|
||||||
|
due_date_after=due_date_after
|
||||||
)
|
)
|
||||||
return todos
|
return todos
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
from datetime import datetime
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.models.todo import Todo
|
from app.models.todo import PriorityLevel, Todo
|
||||||
from app.schemas.todo import TodoCreate, TodoUpdate
|
from app.schemas.todo import TodoCreate, TodoUpdate
|
||||||
|
|
||||||
|
|
||||||
@ -16,7 +17,10 @@ def get_todos(
|
|||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
is_completed: Optional[bool] = None
|
is_completed: Optional[bool] = None,
|
||||||
|
priority: Optional[PriorityLevel] = None,
|
||||||
|
due_date_before: Optional[datetime] = None,
|
||||||
|
due_date_after: Optional[datetime] = None
|
||||||
) -> List[Todo]:
|
) -> List[Todo]:
|
||||||
query = db.query(Todo).filter(Todo.owner_id == owner_id)
|
query = db.query(Todo).filter(Todo.owner_id == owner_id)
|
||||||
|
|
||||||
@ -25,6 +29,18 @@ def get_todos(
|
|||||||
|
|
||||||
if is_completed is not None:
|
if is_completed is not None:
|
||||||
query = query.filter(Todo.is_completed == is_completed)
|
query = query.filter(Todo.is_completed == is_completed)
|
||||||
|
|
||||||
|
if priority is not None:
|
||||||
|
query = query.filter(Todo.priority == priority)
|
||||||
|
|
||||||
|
if due_date_before is not None:
|
||||||
|
query = query.filter(Todo.due_date <= due_date_before)
|
||||||
|
|
||||||
|
if due_date_after is not None:
|
||||||
|
query = query.filter(Todo.due_date >= due_date_after)
|
||||||
|
|
||||||
|
# Order by priority (highest first) and due date (earliest first)
|
||||||
|
query = query.order_by(Todo.priority.desc(), Todo.due_date.asc())
|
||||||
|
|
||||||
return query.offset(skip).limit(limit).all()
|
return query.offset(skip).limit(limit).all()
|
||||||
|
|
||||||
@ -34,6 +50,8 @@ def create_todo(db: Session, todo_in: TodoCreate, owner_id: int) -> Todo:
|
|||||||
title=todo_in.title,
|
title=todo_in.title,
|
||||||
description=todo_in.description,
|
description=todo_in.description,
|
||||||
is_completed=todo_in.is_completed,
|
is_completed=todo_in.is_completed,
|
||||||
|
priority=todo_in.priority,
|
||||||
|
due_date=todo_in.due_date,
|
||||||
owner_id=owner_id
|
owner_id=owner_id
|
||||||
)
|
)
|
||||||
db.add(db_todo)
|
db.add(db_todo)
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Text
|
from enum import Enum as PyEnum
|
||||||
|
from sqlalchemy import Boolean, Column, DateTime, Enum, ForeignKey, Integer, String, Text
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from app.db.base import Base
|
from app.db.base import Base
|
||||||
|
|
||||||
|
|
||||||
|
class PriorityLevel(str, PyEnum):
|
||||||
|
LOW = "low"
|
||||||
|
MEDIUM = "medium"
|
||||||
|
HIGH = "high"
|
||||||
|
|
||||||
|
|
||||||
class Todo(Base):
|
class Todo(Base):
|
||||||
__tablename__ = "todos"
|
__tablename__ = "todos"
|
||||||
|
|
||||||
@ -11,6 +18,8 @@ class Todo(Base):
|
|||||||
title = Column(String, index=True)
|
title = Column(String, index=True)
|
||||||
description = Column(Text, nullable=True)
|
description = Column(Text, nullable=True)
|
||||||
is_completed = Column(Boolean, default=False)
|
is_completed = Column(Boolean, default=False)
|
||||||
|
priority = Column(Enum(PriorityLevel), default=PriorityLevel.MEDIUM)
|
||||||
|
due_date = Column(DateTime, nullable=True)
|
||||||
owner_id = Column(Integer, ForeignKey("users.id"))
|
owner_id = Column(Integer, ForeignKey("users.id"))
|
||||||
|
|
||||||
owner = relationship("User", back_populates="todos")
|
owner = relationship("User", back_populates="todos")
|
@ -1,13 +1,18 @@
|
|||||||
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from app.models.todo import PriorityLevel
|
||||||
|
|
||||||
|
|
||||||
# Shared properties
|
# Shared properties
|
||||||
class TodoBase(BaseModel):
|
class TodoBase(BaseModel):
|
||||||
title: Optional[str] = None
|
title: Optional[str] = None
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
is_completed: Optional[bool] = False
|
is_completed: Optional[bool] = False
|
||||||
|
priority: Optional[PriorityLevel] = PriorityLevel.MEDIUM
|
||||||
|
due_date: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
# Properties to receive on item creation
|
# Properties to receive on item creation
|
||||||
|
40
migrations/versions/004_add_todo_priority_and_due_date.py
Normal file
40
migrations/versions/004_add_todo_priority_and_due_date.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"""add todo priority and due date
|
||||||
|
|
||||||
|
Revision ID: 004
|
||||||
|
Revises: 003
|
||||||
|
Create Date: 2023-11-17
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '004'
|
||||||
|
down_revision = '003'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# Create priority_level enum type
|
||||||
|
with op.batch_alter_table('todos', schema=None) as batch_op:
|
||||||
|
# Add priority column
|
||||||
|
batch_op.add_column(sa.Column('priority', sa.Enum('low', 'medium', 'high', name='prioritylevel'), nullable=True))
|
||||||
|
# Set default values for existing records
|
||||||
|
batch_op.execute("UPDATE todos SET priority = 'medium' WHERE priority IS NULL")
|
||||||
|
# Make priority not nullable with default
|
||||||
|
batch_op.alter_column('priority', nullable=False, server_default='medium')
|
||||||
|
|
||||||
|
# Add due_date column
|
||||||
|
batch_op.add_column(sa.Column('due_date', sa.DateTime(), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# Remove the new columns
|
||||||
|
with op.batch_alter_table('todos', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('due_date')
|
||||||
|
batch_op.drop_column('priority')
|
||||||
|
|
||||||
|
# Drop the enum type
|
||||||
|
op.execute('DROP TYPE prioritylevel')
|
Loading…
x
Reference in New Issue
Block a user