Programming/Project Log

[가계부 만들기] Category 생성/수정/삭제

minarae7 2023. 5. 14. 00:02
728x90
반응형

이전 포스팅에서 카테고리에 관련된 기능을 구현하기 위해 설계한 구조와 리스트를 가져오는 내용을 정리하였다.

이번 포스팅에서는 생성/수정/삭제와 관련된 기능을 기록하려고 한다.

사실 생성/수정/삭제 기능은 이전에 회원 정보를 관리할 때 사용한 기능과 거의 동일하다.

테이블이 바뀌니 모델이 달라지고 패스워드가 없으니 암호화되는 과정이 생략되는 정도로 생각하면 된다.

728x90

카테고리 생성

먼저 생성하는 코드를 먼저 구현한다. 이전과 동일하게 로직은 services 아래 category_service.py에서 구현하고 라우팅만 연결하도록 할 것이다.

로직을 먼저 구현한다. 코드는 아래와 같다.

# 카테고리 생성
async def create_category(
    db: AsyncSession,
    member_no: int,
    category: schemas.CategoryUpsert,
):
    db_category = models.Category(**category.dict(), member_no=member_no, has_children='F')
    db.add(db_category)
    await db.commit()
    await db.refresh(db_category)

    return db_category

코드는 매우 간단하다. 회원 정보와 같이 중복 아이디를 검사할 필요도 없고 패스워드를 암호화할 필요도 없다.

그냥 들어온 정보를 테이블에 삽입하면 된다. 여기서는 ORM을 사용하고 있으니 add 함수로 처리하고 commit만 해주면 구현이 끝난다.

이제 router 코드를 생성하도록 한다.

routers/category.py 파일에 아래 내용을 구현하였다.

반응형
# 카테고리 생성
@router.post("/create", description="카테고리 생성", response_class=Response, responses={
    HTTP_400_BAD_REQUEST: {
        "model": schemas.Message
    }
})
async def create(
    user_info: schemas.JWTPayload = Depends(decode_access_token),
    category: schemas.CategoryUpsert = Body(
        title="카테고리 정보",
        example={
            "category_name": "테스트",
            "parent_no": None,
            "class_name": None,
            "inout_type": "O",
        }
    ),
    db: AsyncSession = Depends(get_db)
):
    try:
        result = await category_service.create_category(db, user_info['member_no'], category)

        return Response(status_code=HTTP_201_CREATED)
    except Exception as e:
        return JSONResponse(content={"detail": e.args[0]}, status_code=HTTP_400_BAD_REQUEST)

실제 코드는 5줄인데 함수를 선언하는 부분을 엄청 자세히 기재하였다. request 샘플을 기재하고 파라미터 내용과 response로 어떻게 전달하는지 내용을 자세히 기록하였다.

이렇게 코드에서 자세히 기록해 두면 fastapi에서는 별도의 api 문서를 만들 필요가 없다.

다음 이미지를 보자.

반응형

카테고리 생성 redoc 내용

코드에서 기록한 내용이 그대로 Redoc 페이지로 표현된다. 추가적으로 작업할 내용도 없다.

따라서 외부에 전달할 때는 이 주소만 전달하면 되기 때문에 매우 간편하다.

개발자는 기본적으로 문서를 만드는데 스트레스를 많이 받는데 이렇게 코드에서 다 해결할 수 있다면 매우 편리하다.

728x90

Category 수정

카테고리를 수정하는 코드를 먼저 보고 내용을 설명하도록 하겠다.

반응형
# 카테고리 수정
async def modify_category(
    db: AsyncSession,
    member_no: int,
    category_no: int,
    category: schemas.CategoryUpsert,
):
    result = await db.execute(
        select(models.Category).filter(
            models.Category.category_no == category_no,
            models.Category.is_deleted == 'F',
        )
    )
    db_category = result.scalars().first()

    if db_category is None:
        raise Exception('카테고리 정보를 찾을 수 없습니다.')

    if db_category.member_no != member_no:
        raise Exception('카테고리 수정에 대한 권한이 없습니다')

    category_info =  category.dict()
    category = {k: v for k, v in category_info.items()}
    for key, value in category.items():
        if value is None:
            continue

        setattr(db_category, key, value)

    await db.commit()
    return db_category

생성과 동일하게 회원 정보 수정과 내용이 거의 비슷하다. 먼저 수정하고자 하는 카테고리가 존재하는지 확인하고 해당 카테고리가 JWT에서 꺼낸 member_no에게 소유권이 있는지 확인한다.

그다음 각 항목을 비교해서 수정된 항목만 업데이트하고 commit 하면 작업이 끝난다.

다음은 router를 구현한다.

반응형
# 카테고리 항목 수정
@router.put("/modify/{category_no}", description="카테고리 정보 수정", response_class=Response, responses={
    HTTP_400_BAD_REQUEST: {
        "model": schemas.Message
    }
})
async def modify(
    user_info: schemas.JWTPayload = Depends(decode_access_token),
    category_no: int = Query(title="카테고리번호"),
    category: schemas.CategoryUpsert = Body(
        title="카테고리 정보",
        example={
            "category_name": "테스트",
            "parent_no": None,
            "class_name": None,
            "inout_type": "O",
        }
    ),
    db: AsyncSession = Depends(get_db)
):
    try:
        result = await category_service.modify_category(db, user_info['member_no'], category_no, category)

        return Response(status_code=HTTP_200_OK)
    except Exception as e:
        return JSONResponse(content={"detail": e.args[0]}, status_code=HTTP_400_BAD_REQUEST)

Router를 선언할 때 URL 주소에 category_no를 query로 전달받도록 하였다. 이 내용에 대한 상세한 설명은 Fastapi 튜토리얼 내용을 참조하면 된다.

반응형
 

경로 매개변수 - FastAPI

경로 매개변수 파이썬 포맷 문자열이 사용하는 동일한 문법으로 "매개변수" 또는 "변수"를 경로에 선언할 수 있습니다: from fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") async def read_item(item_

fastapi.tiangolo.com

생성할 때 router에서는 post로 주소를 전달받았는데 수정할 때는 put으로 http method를 전달받았다. 보통 데이터에 대한 접근할 때 RestAPI로 구현하면 생성은 post, 수정은 put, 정보를 가져오는 것은 get, 삭제할 때는 delete를 사용하게 된다.

카테고리 삭제

마지막으로 카테고리를 삭제하는 내용을 작성한다.

반응형
# 카테고리 삭제
async def delete_category(
    db: AsyncSession,
    member_no: int,
    category_no: int,
):
    # 카테고리가 존재하는지 확인
    result = await db.execute(
        select(models.Category).filter(
            models.Category.category_no == category_no,
            models.Category.is_deleted == 'F',
        )
    )
    db_category = result.scalars().first()

    if db_category is None:
        raise Exception("카테고리 정보를 찾을 수 없습니다.")

    if db_category.member_no != member_no:
        raise Exception('카테고리 수정에 대한 권한이 없습니다')

    setattr(db_category, 'is_deleted', 'T')
    setattr(db_category, 'del_dt', func.now())

    await db.commit()
    return db_category

이 프로젝트에서는 삭제가 발생했을 때 실제로 내용을 테이블에서 delete 처리하지 않고 삭제 플래그만 T로 바꾸어서 삭제되었다는 것을 표시하도록 하였다.

이렇게 처리하는 이유는 회원 탈퇴를 처리할 때 정리하였다.

아직 가계부의 내용을 기록한 것이 없기 때문에 삭제처리할 때는 그냥 카테고리만 삭제하도록 하였다.

가계부의 내용을 기록하게 되면 카테고리가 삭제될 때 해당 카테고리로 되어있는 내용에서 카테고리를 null로 바꿔주는 작업을 해야 한다.

그렇게 하지 않으면 해당 내용은 삭제하지 않았는데 조회되지 않게 된다. 이 내용은 이후에 따로 추가하도록 하겠다.

router에서는 다음과 같이 구현하였다.

반응형
@router.delete("/delete/{category_no}", description="카테고리 삭제", response_class=Response, responses={
    HTTP_400_BAD_REQUEST: {
        "model": schemas.Message
    }
})
async def delete(
    user_info: schemas.JWTPayload = Depends(decode_access_token),
    category_no: int = Query(title="카테고리 번호"),
    db: AsyncSession = Depends(get_db)
):
    try:
        result = await category_service.delete_category(db, user_info['member_no'], category_no)

        return Response(status_code=HTTP_200_OK)
    except Exception as e:
        return JSONResponse(content={"detail": e.args[0]}, status_code=HTTP_400_BAD_REQUEST)

이 내용에서는 그렇게 어려운 내용이 없고 앞선 내용의 반복이어서 추가 설명은 생략하도록 한다.

반응형

이렇게 해서 기본적인 설정에 대한 내용은 다 정리하였다.

이제 가계부 내역을 기록하는 코드를 구현하고 frontend를 구성하도록 하겠다.

728x90
반응형