fruites_store_service
과일 쇼핑몰 서비스
MVP
사용자
가 구매하고자 하는상품(과일)
을 선택하여주문
,결제
할 수 있다.
주요 기능
1. 회원관리
사용자
는 이용자, 관리자로 나눠진다.
- 사용자 정보
- 기본정보
- 이름
- 패스워드
- 사용자 유형
- 이용자
- 권한
상품
: 조회
주문
: 조회/입력/수정
결제
: 조회/입력/수정- 관리자
- 권한
상품
: 조회/입력/수정/삭제
주문
: 조회/입력/수정/삭제
결제
: 조회/입력/수정/삭제- 공통기능
- 회원가입/탈퇴, 회원정보 조회/수정
- 로그인, 로그아웃
2. 상품관리
상품(과일)
은 관리자에 의해 등록/수정/삭제 될 수 있으며 이용자는 조회만 가능하다.
- 상품 정보
- 기본정보
- 이름
- 설명
- 판매지
- 기본 배송비
- 가격
- 판매 단위별 가격
- 재고 개수
- 판매 상태 : 판매 준비중, 판매 중, 판매 완료
3. 주문관리
주문
은 관리자에 의해 등록/수정/삭제될 수 있다.- 이용자는 판매 중인 상품을 판매 단위별로
주문
할 수 있다. (등록)- 이용자는 본인의
주문
을 조회/수정 가능하다.- 주문 대기중일 때만 기본 정보를 수정할 수 있다.
- 주문 정보
- 기본정보
- 주문자 (
사용자
)상품(과일)
- 배송지
- 배송비
- 총 가격
- 주문 상태 : 주문 대기중, 주문 취소, 주문 완료 & 결재 대기중, 결재 완료 & 배송 중, 배송 완료
4. 결제관리
결제
는 관리자에 의해 등록/수정/삭제될 수 있다.- 이용자는 주문 대기중인 본인의
주문
에 대하여결제
할 수 있다. (등록)- 이용자는 본인의
결제
을 조회/수정할 수 있다.- 결제 대기중일 때만 기본 정보를 수정할 수 있다.
- 결제 정보
- 기본 정보
주문
- 결제 가격
- 결제 방법
- 결제 상태 : 결제 대기중, 결제 취소, 결제 완료
기술 스택
Python3
Django
DRF
ERD
완료
유저, 상품, 주문, 결제 모델링유저 CRUD 기능 구현상품 CRUD 기능 구현주문 CRUD 기능 구현결제 CRUD 기능 구현유저 기능 error 핸들링상품 기능 error 핸들링주문 기능 error 핸들링결제 기능 error 핸들링상품 CRUD 권한 핸들링주문 CRUD 권한 핸들링결제 CRUD 권한 핸들링
진행중
- 상품 재고, 상태 관리
- 주문, 결제 상태 관리
- Test 코드 작성
# product.services.product_service.py
def get_product(product_id):
"""상품 정보 반환 함수
Args:
product_id (int): 상품 아이디
Returns:
product_info (dict): 상품 정보
"""
product_obj = ProductModel.objects.get(id=product_id)
product_info = ProductSerializer(product_obj).data
return product_info
def create_product(product_info):
"""상품 정보로 상품 생성하는 함수
Args:
product_info (dict): 생성할 상품 정보
Returns:
product_obj (ProductModel) : 생성된 상품 오브젝트
"""
product_serializer = ProductSerializer(data=product_info)
product_serializer.is_valid(raise_exception=True)
product_obj = product_serializer.save()
return product_obj
# product.views.py
# 상품 CRUD View
class ProductView(APIView):
permission_classes = [IsManagerOrIsAuthenticatedReadOnly]
# 상품 조회
def get(self, request, id):
try:
product_info = get_product(id)
return Response(product_info, status=status.HTTP_200_OK)
except ProductModel.DoesNotExist:
return Response({"error": "상품를 찾을 수 없습니다."}, status=status.HTTP_404_NOT_FOUND)
except:
return Response({"error": "상품 조회에 실패했습니다."}, status=status.HTTP_400_BAD_REQUEST)
# 상픔 생성
def post(self, request):
try:
product_obj = create_product(request.data)
return Response({"success" : "상품 생성 성공"}, status=status.HTTP_200_OK)
except exceptions.ValidationError as e:
error_key = list(e.detail.keys())[0]
error_detail = e.detail.get(error_key, "")
if error_key == "name":
if error_detail[0].code == "blank":
return Response({"error": "상품 이름이 빈칸입니다."}, status=status.HTTP_400_BAD_REQUEST)
elif error_detail[0].code == "max_length":
return Response({"error": "상품 이름은 20글자 이하로 작성해주세요."}, status=status.HTTP_400_BAD_REQUEST)
elif error_detail[0].code == "invalid":
return Response({"error": "상품 이름 입력 값이 유효하지않습니다."}, status=status.HTTP_400_BAD_REQUEST)
elif error_detail[0].code == "required":
return Response({"error": "상품 이름을 입력해야 합니다."}, status=status.HTTP_400_BAD_REQUEST)
elif error_key == "description":
if error_detail[0].code == "blank":
return Response({"error": "상품 설명이 빈칸입니다."}, status=status.HTTP_400_BAD_REQUEST)
elif error_detail[0].code == "max_length":
return Response({"error": "상품 설명은 200글자 이하로 작성해주세요."}, status=status.HTTP_400_BAD_REQUEST)
elif error_detail[0].code == "invalid":
return Response({"error": "상품 설명 입력 값이 유효하지않습니다."}, status=status.HTTP_400_BAD_REQUEST)
elif error_key == "address":
if error_detail[0].code == "blank":
return Response({"error": "판매지가 빈칸입니다."}, status=status.HTTP_400_BAD_REQUEST)
elif error_detail[0].code == "max_length":
return Response({"error": "판매지는 100글자 이하로 작성해주세요."}, status=status.HTTP_400_BAD_REQUEST)
elif error_detail[0].code == "invalid":
return Response({"error": "판매지 입력 값이 유효하지않습니다."}, status=status.HTTP_400_BAD_REQUEST)
elif error_key == "delivery_cost":
if error_detail[0].code == "invalid":
return Response({"error": "배송비 입력 값이 유효하지않습니다."}, status=status.HTTP_400_BAD_REQUEST)
elif error_key == "status":
if error_detail[0].code == "blank":
return Response({"error": "판매 상태가 빈칸입니다."}, status=status.HTTP_400_BAD_REQUEST)
elif error_detail[0].code == "invalid_choice":
return Response({"error": "판매 상태가 잘못 선택되었습니다."}, status=status.HTTP_400_BAD_REQUEST)
elif error_detail[0].code == "invalid":
return Response({"error": "판매 상태 입력 값이 유효하지않습니다."}, status=status.HTTP_400_BAD_REQUEST)
return Response({"error" : "상품 생성 실패"}, status=status.HTTP_400_BAD_REQUEST)
except:
return Response({"error" : "상품 생성 실패"}, status=status.HTTP_400_BAD_REQUEST)
...
# product.permissions.py
from rest_framework.permissions import BasePermission
from rest_framework.exceptions import APIException
from rest_framework import status
class GenericAPIException(APIException):
def __init__(self, status_code, detail=None, code=None):
self.status_code=status_code
super().__init__(detail=detail, code=code)
class IsManagerOrIsAuthenticatedReadOnly(BasePermission):
"""
관리자는 모두 가능, 로그인 사용자는 조회만 가능
"""
SAFE_METHODS = ('GET', )
message = '접근 권한이 없습니다.'
def has_permission(self, request, view):
user = request.user
if not user.is_authenticated:
response ={
"detail": "서비스를 이용하기 위해 로그인 해주세요.",
}
raise GenericAPIException(status_code=status.HTTP_401_UNAUTHORIZED, detail=response)
if user.is_authenticated and user.type == "manager":
return True
if user.is_authenticated and request.method in self.SAFE_METHODS:
return True
return False
기획문서 - fruites_store_service
Github - fruites_store_service