feat(backend): make list endpoints sortable
This commit is contained in:
parent
bd191cd420
commit
e4061b1654
9 changed files with 101 additions and 17 deletions
|
@ -8,6 +8,7 @@ from todo.models import todos as todomodel
|
||||||
from todo.models import users as usermodel
|
from todo.models import users as usermodel
|
||||||
from todo.schemas import todos as todoschema
|
from todo.schemas import todos as todoschema
|
||||||
from todo.utils.exceptions import NotFoundException, InvalidFilterParameterException
|
from todo.utils.exceptions import NotFoundException, InvalidFilterParameterException
|
||||||
|
from todo.models.common import SortOrder
|
||||||
|
|
||||||
|
|
||||||
def create_todo(db: Session, todo: todoschema.TodoItemCreate, user_id: int) -> todoschema.TodoItem:
|
def create_todo(db: Session, todo: todoschema.TodoItemCreate, user_id: int) -> todoschema.TodoItem:
|
||||||
|
@ -39,7 +40,13 @@ def read_todo(db: Session, todo_id: int) -> todoschema.TodoItem:
|
||||||
return todoschema.TodoItem.from_orm(db_todo)
|
return todoschema.TodoItem.from_orm(db_todo)
|
||||||
|
|
||||||
|
|
||||||
def read_todos_for_user(db: Session, user_id: int, skip: int = 0, limit: int = 100) -> list[todoschema.TodoItem]:
|
def read_todos_for_user(
|
||||||
|
db: Session,
|
||||||
|
user_id: int,
|
||||||
|
skip: int = 0, limit: int = 100,
|
||||||
|
sortby: todomodel.SortableTodoItemField = todomodel.SortableTodoItemField('updated'),
|
||||||
|
sortorder: SortOrder = SortOrder['desc'],
|
||||||
|
) -> list[todoschema.TodoItem]:
|
||||||
"""Returns a range of todo-items of the user with the specified user_id from the database."""
|
"""Returns a range of todo-items of the user with the specified user_id from the database."""
|
||||||
|
|
||||||
for parameter in [skip, limit]:
|
for parameter in [skip, limit]:
|
||||||
|
@ -52,7 +59,7 @@ def read_todos_for_user(db: Session, user_id: int, skip: int = 0, limit: int = 1
|
||||||
if not db_user:
|
if not db_user:
|
||||||
raise NotFoundException(f"User with id '{user_id}' not found.")
|
raise NotFoundException(f"User with id '{user_id}' not found.")
|
||||||
|
|
||||||
db_todos = db.query(todomodel.TodoItem).filter(todomodel.TodoItem.user_id == user_id).offset(skip).limit(limit).all()
|
db_todos = db.query(todomodel.TodoItem).filter(todomodel.TodoItem.user_id == user_id).order_by(sortorder.call(sortby.field)).offset(skip).limit(limit).all()
|
||||||
return [todoschema.TodoItem.from_orm(db_todo) for db_todo in db_todos]
|
return [todoschema.TodoItem.from_orm(db_todo) for db_todo in db_todos]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from todo.models import users as usermodel
|
from todo.models import users as usermodel
|
||||||
from todo.schemas import users as userschema
|
from todo.schemas import users as userschema
|
||||||
|
from todo.models.common import SortOrder
|
||||||
from todo.utils.exceptions import NotFoundException, InvalidFilterParameterException
|
from todo.utils.exceptions import NotFoundException, InvalidFilterParameterException
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,7 +57,12 @@ def read_user_by_email(db: Session, email: str) -> userschema.User:
|
||||||
return userschema.User.from_orm(db_user)
|
return userschema.User.from_orm(db_user)
|
||||||
|
|
||||||
|
|
||||||
def read_users(db: Session, skip: int = 0, limit: int = 100) -> list[userschema.User]:
|
def read_users(
|
||||||
|
db: Session,
|
||||||
|
skip: int = 0, limit: int = 100,
|
||||||
|
sortby: usermodel.SortableUserField = usermodel.SortableUserField('id'),
|
||||||
|
sortorder: SortOrder = SortOrder['asc'],
|
||||||
|
) -> list[userschema.User]:
|
||||||
"""Returns an range of users from the database."""
|
"""Returns an range of users from the database."""
|
||||||
|
|
||||||
for parameter in [skip, limit]:
|
for parameter in [skip, limit]:
|
||||||
|
@ -65,7 +71,7 @@ def read_users(db: Session, skip: int = 0, limit: int = 100) -> list[userschema.
|
||||||
if parameter < 0:
|
if parameter < 0:
|
||||||
raise InvalidFilterParameterException(f"Parameter '{parameter}' cannot be smaller than zero.")
|
raise InvalidFilterParameterException(f"Parameter '{parameter}' cannot be smaller than zero.")
|
||||||
|
|
||||||
db_users = db.query(usermodel.User).offset(skip).limit(limit).all()
|
db_users = db.query(usermodel.User).order_by(sortorder.call(sortby.field)).offset(skip).limit(limit).all()
|
||||||
|
|
||||||
return [userschema.User.from_orm(db_user) for db_user in db_users]
|
return [userschema.User.from_orm(db_user) for db_user in db_users]
|
||||||
|
|
||||||
|
|
|
@ -21,12 +21,12 @@ def upgrade() -> None:
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'users',
|
'users',
|
||||||
Column('id', Integer, primary_key=True, autoincrement=True, index=True),
|
Column('id', Integer, primary_key=True, autoincrement=True, index=True),
|
||||||
Column('email', String, unique=True, nullable=False),
|
Column('email', String, unique=True, nullable=False, index=True),
|
||||||
Column('password', String, nullable=False),
|
Column('password', String, nullable=False),
|
||||||
Column('created', DateTime(timezone=True), nullable=False, server_default=now()),
|
Column('created', DateTime(timezone=True), nullable=False, server_default=now()),
|
||||||
Column('updated', DateTime(timezone=True), nullable=False, server_default=now(), onupdate=now()),
|
Column('updated', DateTime(timezone=True), nullable=False, server_default=now(), onupdate=now()),
|
||||||
Column('first_name', String, nullable=False),
|
Column('first_name', String, nullable=False),
|
||||||
Column('last_name', String, nullable=False),
|
Column('last_name', String, nullable=False, index=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,13 @@ def upgrade() -> None:
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'todo_items',
|
'todo_items',
|
||||||
Column('id', Integer, primary_key=True, autoincrement=True, index=True),
|
Column('id', Integer, primary_key=True, autoincrement=True, index=True),
|
||||||
Column('title', String, nullable=False),
|
Column('title', String, nullable=False, index=True),
|
||||||
Column('description', String, nullable=False),
|
Column('description', String, nullable=False),
|
||||||
Column('done', Boolean, nullable=False, default=False, index=True),
|
Column('done', Boolean, nullable=False, default=False, index=True),
|
||||||
Column('created', DateTime(timezone=True), nullable=False, server_default=now()),
|
Column('created', DateTime(timezone=True), nullable=False, server_default=now()),
|
||||||
Column('updated', DateTime(timezone=True), nullable=False, server_default=now(), onupdate=now()),
|
Column('updated', DateTime(timezone=True), nullable=False, server_default=now(), onupdate=now(), index=True),
|
||||||
Column('finished', DateTime(timezone=True), nullable=True, default=None),
|
Column('finished', DateTime(timezone=True), nullable=True, default=None),
|
||||||
Column('user_id', Integer, ForeignKey('users.id', ondelete="CASCADE"), nullable=False),
|
Column('user_id', Integer, ForeignKey('users.id', ondelete="CASCADE"), nullable=False, index=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
26
backend/todo/models/common.py
Normal file
26
backend/todo/models/common.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
"""This module contains common utilities for handling models."""
|
||||||
|
|
||||||
|
import enum
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
|
||||||
|
class SortOrder(enum.Enum):
|
||||||
|
"""Possible sort orders for database queries."""
|
||||||
|
|
||||||
|
asc = 'asc'
|
||||||
|
ASC = 'asc'
|
||||||
|
desc = 'desc'
|
||||||
|
DESC = 'desc'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def call(self) -> Callable:
|
||||||
|
"""Returns the sqlalchemy sort function depending on the instance value."""
|
||||||
|
|
||||||
|
if self.value == 'asc':
|
||||||
|
return sqlalchemy.asc
|
||||||
|
elif self.value == 'desc':
|
||||||
|
return sqlalchemy.desc
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Logic error.")
|
|
@ -16,13 +16,28 @@ class TodoItem(Base):
|
||||||
__tablename__ = "todo_items"
|
__tablename__ = "todo_items"
|
||||||
|
|
||||||
id = Column('id', Integer, primary_key=True, autoincrement=True, index=True)
|
id = Column('id', Integer, primary_key=True, autoincrement=True, index=True)
|
||||||
title = Column('title', String, nullable=False)
|
title = Column('title', String, nullable=False, index=True)
|
||||||
description = Column('description', String, nullable=False)
|
description = Column('description', String, nullable=False)
|
||||||
done = Column('done', Boolean, nullable=False, default=False, index=True)
|
done = Column('done', Boolean, nullable=False, default=False, index=True)
|
||||||
|
|
||||||
created = Column('created', DateTime(timezone=True), nullable=False, server_default=now())
|
created = Column('created', DateTime(timezone=True), nullable=False, server_default=now())
|
||||||
updated = Column('updated', DateTime(timezone=True), nullable=False, server_default=now(), onupdate=now())
|
updated = Column('updated', DateTime(timezone=True), nullable=False, server_default=now(), onupdate=now(), index=True)
|
||||||
finished = Column('finished', DateTime(timezone=True), nullable=True, default=None)
|
finished = Column('finished', DateTime(timezone=True), nullable=True, default=None)
|
||||||
|
|
||||||
user_id = Column('user_id', Integer, ForeignKey('users.id', ondelete="CASCADE"), nullable=False)
|
user_id = Column('user_id', Integer, ForeignKey('users.id', ondelete="CASCADE"), nullable=False, index=True)
|
||||||
user = relationship(User, back_populates="todo_items", uselist=False)
|
user = relationship(User, back_populates="todo_items", uselist=False)
|
||||||
|
|
||||||
|
|
||||||
|
class SortableTodoItemField(enum.Enum):
|
||||||
|
"""Defines which fields todo-item lists can be sorted on."""
|
||||||
|
|
||||||
|
id = 'id'
|
||||||
|
title = 'title'
|
||||||
|
done = 'done'
|
||||||
|
created = 'created'
|
||||||
|
updated = 'updated'
|
||||||
|
finished = 'finished'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def field(self) -> Column:
|
||||||
|
return getattr(TodoItem, self.value)
|
||||||
|
|
|
@ -15,11 +15,26 @@ class User(Base):
|
||||||
__tablename__ = "users"
|
__tablename__ = "users"
|
||||||
|
|
||||||
id = Column('id', Integer, primary_key=True, autoincrement=True, index=True)
|
id = Column('id', Integer, primary_key=True, autoincrement=True, index=True)
|
||||||
email = Column('email', String, unique=True, nullable=False)
|
email = Column('email', String, unique=True, nullable=False, index=True)
|
||||||
password = Column('password', String, nullable=False)
|
password = Column('password', String, nullable=False)
|
||||||
created = Column('created', DateTime(timezone=True), nullable=False, server_default=now())
|
created = Column('created', DateTime(timezone=True), nullable=False, server_default=now())
|
||||||
updated = Column('updated', DateTime(timezone=True), nullable=False, server_default=now(), onupdate=now())
|
updated = Column('updated', DateTime(timezone=True), nullable=False, server_default=now(), onupdate=now())
|
||||||
first_name = Column('first_name', String, nullable=False)
|
first_name = Column('first_name', String, nullable=False)
|
||||||
last_name = Column('last_name', String, nullable=False)
|
last_name = Column('last_name', String, nullable=False, index=True)
|
||||||
|
|
||||||
todo_items = relationship("TodoItem", back_populates="user", uselist=True, cascade="all, delete")
|
todo_items = relationship("TodoItem", back_populates="user", uselist=True, cascade="all, delete")
|
||||||
|
|
||||||
|
|
||||||
|
class SortableUserField(enum.Enum):
|
||||||
|
"""Defines which fields user lists can be sorted on."""
|
||||||
|
|
||||||
|
id = 'id'
|
||||||
|
email = 'email'
|
||||||
|
created = 'created'
|
||||||
|
updated = 'updated'
|
||||||
|
first_name = 'first_name'
|
||||||
|
last_name = 'last_name'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def field(self) -> Column:
|
||||||
|
return getattr(User, self.value)
|
||||||
|
|
|
@ -8,6 +8,8 @@ from todo.schemas import todos as todoschema
|
||||||
from todo.crud import todos as todocrud
|
from todo.crud import todos as todocrud
|
||||||
from todo.utils.exceptions import NotFoundException, InvalidFilterParameterException
|
from todo.utils.exceptions import NotFoundException, InvalidFilterParameterException
|
||||||
from todo.utils.exceptions import create_exception_dict as fmt
|
from todo.utils.exceptions import create_exception_dict as fmt
|
||||||
|
from todo.models.todos import SortableTodoItemField
|
||||||
|
from todo.models.common import SortOrder
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
|
@ -38,7 +40,13 @@ def read_todo(todo_id: int, db: Session = Depends(get_db)):
|
||||||
|
|
||||||
|
|
||||||
@router.get("/user/{user_id}", response_model=list[todoschema.TodoItem])
|
@router.get("/user/{user_id}", response_model=list[todoschema.TodoItem])
|
||||||
def read_todos(user_id: int, skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
def read_todos(
|
||||||
|
user_id: int,
|
||||||
|
skip: int = 0, limit: int = 100,
|
||||||
|
sortby: SortableTodoItemField = SortableTodoItemField['updated'],
|
||||||
|
sortorder: SortOrder = SortOrder['desc'],
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
return todocrud.read_todos_for_user(db=db, user_id=user_id, skip=skip, limit=limit)
|
return todocrud.read_todos_for_user(db=db, user_id=user_id, skip=skip, limit=limit)
|
||||||
except InvalidFilterParameterException as e:
|
except InvalidFilterParameterException as e:
|
||||||
|
|
|
@ -8,6 +8,8 @@ from todo.schemas import users as userschema
|
||||||
from todo.crud import users as usercrud
|
from todo.crud import users as usercrud
|
||||||
from todo.utils.exceptions import NotFoundException, InvalidFilterParameterException
|
from todo.utils.exceptions import NotFoundException, InvalidFilterParameterException
|
||||||
from todo.utils.exceptions import create_exception_dict as fmt
|
from todo.utils.exceptions import create_exception_dict as fmt
|
||||||
|
from todo.models.common import SortOrder
|
||||||
|
from todo.models.users import SortableUserField
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
|
@ -40,9 +42,14 @@ def read_user(id: int, db: Session = Depends(get_db)):
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[userschema.User])
|
@router.get("/", response_model=list[userschema.User])
|
||||||
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
def read_users(
|
||||||
|
skip: int = 0, limit: int = 100,
|
||||||
|
sortby: SortableUserField = SortableUserField['id'],
|
||||||
|
sortorder: SortOrder = SortOrder['asc'],
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
return usercrud.read_users(db=db, skip=skip, limit=limit)
|
return usercrud.read_users(db=db, skip=skip, limit=limit, sortby=sortby, sortorder=sortorder)
|
||||||
except InvalidFilterParameterException as e:
|
except InvalidFilterParameterException as e:
|
||||||
raise HTTPException(400, fmt(str(e)))
|
raise HTTPException(400, fmt(str(e)))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue