Skip to content

Commit

Permalink
feature: update expense options (#37)
Browse files Browse the repository at this point in the history
* feat: add ExpenseUpdate schema

* test: update and add new tests for expenses

* feat: update expenses options and ref code validation

* feat: add schema CategoriesGroupDetail

* fix: add field categories_group to CategoriesGroupDetail

* feat: add categories group detail endpoint
  • Loading branch information
RezenkovD authored Feb 6, 2024
1 parent 44957bf commit c619765
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 56 deletions.
19 changes: 18 additions & 1 deletion src/routers/category.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
from typing import List

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session

import services
from database import get_db
from dependencies import get_current_user
from models import User
from schemas import CategoryModel, CategoryCreate, IconColor, CategoriesGroup
from schemas import (
CategoryModel,
CategoryCreate,
IconColor,
CategoriesGroup,
CategoriesGroupDetail,
)

router = APIRouter(
prefix="/groups",
tags=["categories"],
)


@router.get("/categories/", response_model=List[CategoriesGroupDetail])
def read_categories_group_detail(
*,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> List[CategoriesGroupDetail]:
return services.read_categories_group_detail(db, current_user.id)


@router.get("/{group_id}/categories/", response_model=CategoriesGroup)
def read_categories_group(
*,
Expand Down
4 changes: 2 additions & 2 deletions src/routers/expense.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
Page,
)
from models import User
from schemas import ExpenseCreate, ExpenseModel, UserExpense
from schemas import ExpenseCreate, ExpenseModel, UserExpense, ExpenseUpdate

router = APIRouter(
prefix="/groups",
Expand All @@ -41,7 +41,7 @@ def update_expense(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
group_id: int,
expense: ExpenseCreate,
expense: ExpenseUpdate,
expense_id: int,
) -> ExpenseModel:
return services.update_expense(db, current_user.id, group_id, expense, expense_id)
Expand Down
3 changes: 2 additions & 1 deletion src/schemas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@
UserSpender,
GroupDailyExpenses,
GroupDailyExpensesDetail,
CategoriesGroupDetail,
)
from .invintation import BaseInvitation, InvitationCreate, InvitationModel
from .category import CategoryModel, CategoryCreate, IconColor
from .expense import ExpenseCreate, ExpenseModel, UserExpense
from .expense import ExpenseCreate, ExpenseUpdate, ExpenseModel, UserExpense
from .replenishment import (
ReplenishmentCreate,
UserBalance,
Expand Down
5 changes: 5 additions & 0 deletions src/schemas/expense.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ class ExpenseCreate(BaseModel):
category_id: int


class ExpenseUpdate(ExpenseCreate):
group_id: int
time: datetime.datetime


class CategoryGroup(BaseModel):
group: ShortGroup
category: CategoryModel
Expand Down
8 changes: 8 additions & 0 deletions src/schemas/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ class CategoriesGroup(BaseModel):
categories_group: List[AboutCategory]


class CategoriesGroupDetail(BaseModel):
id: int
title: str
icon_url: str
color_code: str
categories_group: List[AboutCategory]


class GroupInfo(GroupModel):
members: int
expenses: int
Expand Down
1 change: 1 addition & 0 deletions src/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
read_group_member_daily_expenses,
read_group_member_daily_expenses_detail,
read_group_member_history,
read_categories_group_detail,
)
from .invitation import create_invitation, read_invitations, response_invitation
from .replenishment import (
Expand Down
124 changes: 80 additions & 44 deletions src/services/expense.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import datetime
from typing import List, Optional
from typing import List, Optional, Union

from pydantic.schema import date
from sqlalchemy import and_, exc, extract
Expand All @@ -10,59 +10,100 @@

from models import CategoryGroup, Expense, UserGroup
from enums import GroupStatusEnum
from schemas import ExpenseCreate, ExpenseModel, UserExpense
from schemas import ExpenseCreate, ExpenseModel, UserExpense, ExpenseUpdate


def validate_input_data(
def validate_user_group(db: Session, user_id: int, group_id: int) -> UserGroup:
try:
db_user_group = (
db.query(UserGroup).filter_by(group_id=group_id, user_id=user_id).one()
)
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="You are not a user of this group!",
)
return db_user_group


def validate_expense(
db: Session,
user_id: int,
group_id: int,
expense_id: int,
) -> None:
try:
db.query(Expense).filter_by(id=expense_id, user_id=user_id).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="It's not your expense!",
)
try:
db.query(Expense).filter_by(
id=expense_id, user_id=user_id, group_id=group_id
).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="The expense does not belong to this group!",
)


def validate_expense_update(
db: Session,
user_id: int,
group_id: int,
expense: ExpenseCreate = None,
expense_id: int = None,
is_create: bool = False,
expense: ExpenseUpdate,
) -> None:
try:
db_user_group = (
db.query(UserGroup).filter_by(group_id=group_id, user_id=user_id).one()
db.query(UserGroup)
.filter_by(group_id=expense.group_id, user_id=user_id)
.one()
)
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="You are not a user of this group!",
detail="You are not a user of the group specified to update expenses!",
)
if is_create:
if db_user_group.status == GroupStatusEnum.INACTIVE:
if db_user_group.status == GroupStatusEnum.INACTIVE:
if group_id != expense.group_id:
raise HTTPException(
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
detail="The user is not active in this group!",
)
if expense:
try:
db.query(CategoryGroup).filter_by(
category_id=expense.category_id,
group_id=group_id,
).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="The group does not have such a category!",
)
if expense_id:
try:
db.query(Expense).filter_by(id=expense_id, user_id=user_id).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="It's not your expense!",
detail="The user is not active in group specified to update expenses!",
)
try:
db.query(CategoryGroup).filter_by(
category_id=expense.category_id,
group_id=expense.group_id,
).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="The group specified to update expenses does not contain this category!",
)


def create_expense(
db: Session, user_id: int, group_id: int, expense: ExpenseCreate
) -> ExpenseModel:
validate_input_data(
db=db, user_id=user_id, group_id=group_id, expense=expense, is_create=True
)
db_user_group = validate_user_group(db=db, user_id=user_id, group_id=group_id)
if db_user_group.status == GroupStatusEnum.INACTIVE:
raise HTTPException(
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
detail="The user is not active in this group!",
)
try:
db.query(CategoryGroup).filter_by(
category_id=expense.category_id,
group_id=group_id,
).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="The group does not have such a category!",
)
db_expense = Expense(**expense.dict())
db_expense.user_id = user_id
db_expense.group_id = group_id
Expand All @@ -80,15 +121,11 @@ def create_expense(


def update_expense(
db: Session, user_id: int, group_id: int, expense: ExpenseCreate, expense_id: int
db: Session, user_id: int, group_id: int, expense: ExpenseUpdate, expense_id: int
) -> ExpenseModel:
validate_input_data(
db=db,
user_id=user_id,
group_id=group_id,
expense=expense,
expense_id=expense_id,
)
validate_user_group(db=db, user_id=user_id, group_id=group_id)
validate_expense(db=db, user_id=user_id, group_id=group_id, expense_id=expense_id)
validate_expense_update(db=db, user_id=user_id, group_id=group_id, expense=expense)
db.query(Expense).filter_by(id=expense_id).update(values={**expense.dict()})
db_expense = db.query(Expense).filter_by(id=expense_id).one()
try:
Expand All @@ -103,9 +140,8 @@ def update_expense(


def delete_expense(db: Session, user_id: int, group_id: int, expense_id: int) -> None:
validate_input_data(
db=db, user_id=user_id, group_id=group_id, expense_id=expense_id
)
validate_user_group(db=db, user_id=user_id, group_id=group_id)
validate_expense(db=db, user_id=user_id, group_id=group_id, expense_id=expense_id)
db.query(Expense).filter_by(id=expense_id).delete()
try:
db.commit()
Expand Down
20 changes: 20 additions & 0 deletions src/services/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
GroupMember,
UserDailyExpenses,
UserDailyExpensesDetail,
CategoriesGroupDetail,
)
from enums import GroupStatusEnum

Expand Down Expand Up @@ -363,6 +364,25 @@ def read_user_groups(db: Session, user_id: int) -> UserGroups:
return db_query


def read_categories_group_detail(
db: Session, user_id: int
) -> List[CategoriesGroupDetail]:
db_query = (
db.query(Group)
.options(joinedload(Group.categories_group))
.join(UserGroup)
.filter(
and_(
UserGroup.group_id == Group.id,
UserGroup.user_id == user_id,
UserGroup.status == GroupStatusEnum.ACTIVE,
)
)
.all()
)
return db_query


def read_categories_group(db: Session, user_id: int, group_id: int) -> CategoriesGroup:
try:
db.query(UserGroup).filter_by(
Expand Down
52 changes: 48 additions & 4 deletions tests/test_endpoints/test_expense_e.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ def test_update_expense(self) -> None:
json={
"descriptions": date_update_expense.descriptions,
"amount": date_update_expense.amount,
"category_id": date_update_expense.category_id,
"category_id": self.category.id,
"group_id": self.second_group.id,
"time": "2018-08-03T10:51:42.990",
},
)
expense_data = {
Expand All @@ -107,9 +109,9 @@ def test_update_expense(self) -> None:
"time": data.json()["time"],
"category_group": {
"group": {
"id": self.first_group.id,
"title": self.first_group.title,
"color_code": self.first_group.color_code,
"id": self.second_group.id,
"title": self.second_group.title,
"color_code": self.second_group.color_code,
},
"category": {"title": self.category.title, "id": self.category.id},
"icon_url": self.icon_url,
Expand All @@ -120,6 +122,48 @@ def test_update_expense(self) -> None:
assert data.status_code == 200
assert data.json() == expense_data

def test_update_expense_on_other_group(self) -> None:
expense = ExpenseFactory(
user_id=self.user.id,
group_id=self.first_group.id,
category_id=self.category.id,
)
date_update_expense = ExpenseCreate(
descriptions="descriptions", amount=999.9, category_id=self.category.id
)
data = client.put(
f"/groups/{self.first_group.id}/expenses/{expense.id}/",
json={
"descriptions": date_update_expense.descriptions,
"amount": date_update_expense.amount,
"category_id": date_update_expense.category_id,
"group_id": 99999,
"time": "2018-08-03T10:51:42.990",
},
)
assert data.status_code == 404

def test_update_expense_on_other_group_category(self) -> None:
expense = ExpenseFactory(
user_id=self.user.id,
group_id=self.first_group.id,
category_id=self.category.id,
)
date_update_expense = ExpenseCreate(
descriptions="descriptions", amount=999.9, category_id=self.category.id
)
data = client.put(
f"/groups/{self.first_group.id}/expenses/{expense.id}/",
json={
"descriptions": date_update_expense.descriptions,
"amount": date_update_expense.amount,
"category_id": 9999,
"group_id": self.first_group.id,
"time": "2018-08-03T10:51:42.990",
},
)
assert data.status_code == 404

def test_delete_expense(self) -> None:
expense = ExpenseFactory(
user_id=self.user.id,
Expand Down
Loading

0 comments on commit c619765

Please sign in to comment.