오늘은 이전에 진행했던 투표를 조회하고 삭제하는 기능과 관리비 장부의 CRUD API를 구현해보았습니다.
투표 조회 기능을 구현했을 때 고려했던 점은 1:N관계입니다.
한 건의 투표를 생성하게 되면 N개의 투표 항목이 생성 및 저장 되도록 기능을 구현 했기 때문에, 조회할 경우도 마찬가지로 한 투표 제목에 해당하는 여러 투표 항목들을 함께 조회할 수 있도록 투표 항목들을 리스트에 담아 반환 했습니다.
작성한 코드는 다음과 같습니다.
# vote/views.py
class VoteAPIView(APIView):
...
def get(self, request, board_id):
try:
# 각 테이블 조회
board = get_object_or_404(Board, pk=board_id)
vote = get_object_or_404(Vote, board=board)
choice = Choice.objects.filter(vote=vote)
# json 직렬화
vote_serializer = VoteSerializer(vote)
choice_serializer = ChoiceSerializer(choice, many=True)
# 여러 개의 투표 항목을 담을 list 생성
Choice_list=[]
# 여러 투표 항목 dict를 생성 후 list에 저장
for choice_data in choice_serializer.data:
Choice_dict = {
'id':choice_data['id'],
'count':choice_data['count'],
'content':choice_data['content'],
'vote':choice_data['vote'],
'voter':choice_data['voter']
}
Choice_list.append(Choice_dict)
# json 결과 값 생성
serializer = {
'Vote':{
'id':vote_serializer.data['id'],
'board':vote_serializer.data['board'],
'title':vote_serializer.data['title'],
'create_time':vote_serializer.data['create_time']
},
'Choice':Choice_list
}
return Response(serializer, status=status.HTTP_200_OK)
except Vote.DoesNotExist:
return Response(status=status.HTTP_400_BAD_REQUEST)
투표에 해당하는 투표 항목들을 필터링한 object
를 serializer
로 직렬화 하고 for문을 활용하여 투표 항목 수 만큼 dictionary에 담아 사전에 정의해 둔 리스트에 저장했습니다.
리스트에 저장한 투표 항목들은 json 직렬화 된 투표 데이터와 합쳐서 반환 했습니다. 클라이언트에게 전송 될 최종 Response의 값은 다음과 같은 형태입니다. (Postman API Test)
투표를 삭제하는 로직은 비교적 간단합니다.
토큰 인증한 사용자인지 확인 후, 투표를 생선한 게시글의 id 값을 기준으로 필터링하고 해당 게시글을 작성한 사용자의 id 값을 현재 로그인한 사용자 id값과 비교하여 투표를 생성한 사용자인지 검증 했습니다.
class VoteAPIView(APIView):
...
def delete(self, request, vote_id):
auth = get_authorization_header(request).split()
if auth and len(auth) == 2:
vote = Vote.objects.get(pk = vote_id)
# 투표를 생성한 게시글 id 기준 게시글 필터링
board = Board.objects.filter(pk=vote.board_id)
# 현재 로그인 한 사용자의 id값과 게시글을 작성한 사용자의 id값 비교
# 쿼리dict에서 키 값으로 추출 한 후 get 메서드로 값 추출
if decode_access_token(auth[1]) == board.values('user')[0].get('user'):
vote.delete()
return Response({'Message':'Success'}, status=status.HTTP_200_OK)
else:
return Response({'Message':'해당 게시글 작성자가 아닙니다.'}, status=status.HTTP_401_UNAUTHORIZED)
decode_access_token(auth[1]) : access_token을 decoding
하여 현재 사용자의 id값을 반환
board.values('user')[0].get('user') : 게시글 모델의 쿼리셋에서 board.values('user')[0]
는 첫 번째 Board 객체를 나타내는 dictionary
를 반환하고, get('user')로 해당 딕셔너리에서 user 필드의 값을 가져왔습니다.
관리자 장부를 작성하기 위해서는 현재 로그인 한 사용자가 일반 사용자인지 관리자인지 검증이 필요했습니다.
이를 위해 User모델에 추가 했던 admin_check
라는 bool type의 필드를 활용했습니다.
# account/views.py
class AccountAPIView(APIView):
# 장부 작성
def post(self, request):
auth = get_authorization_header(request).split()
if auth and len(auth) == 2:
user = CustomAbstractBaseUser.objects.filter(id=decode_access_token(auth[1]))
# 관리자 권한 검사
if user.values('admin_check')[0].get('admin_check') == True:
serializer = AccountSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 관리자 권한이 없을 경우
return Response({'Message':'관리자 권한이 필요합니다.'}, status=status.HTTP_401_UNAUTHORIZED)
가장 먼저 인증이 완료 되었는지 확인 후, 로그인한 사용자의 access_token
을 decoding
하고 이를 기준으로 사용자 정보를 필터링하여 조회 했습니다.
그리고 이 사용자 모델의 쿼리셋에서 admin_check
값을 가져오고, 그 값이 True이면 관리자로 판단하여 장부를 작성하도록 구성했습니다.
만약 관리자가 아닐 경우라면 "관리자의 권한이 필요합니다."라는 json 메시지와 함께 인증되지 않는 사용자라는 의미로 HTTP 401 ERROR를 반환 했습니다.
다음은 관리비 장부를 전체 조회하는 API 로직입니다.
# 장부 전체 조회 query = ?year=2023&month=08
class AccountAPIView(APIView):
def get(self, request):
year = request.GET.get('year', None)
month = request.GET.get('month', None)
paginator = PageNumberPagination()
# 페이지 내의 최대 객체 수 = 31 (한 달)
paginator.page_size = 31
# ?year=Null, 현재 년도 반환 = yyyy 포맷
if year is None:
year = DateFormat(datetime.now()).format('Y')
# ?month=Null, 현재 월 반환 = mm 포맷
if month is None:
month = DateFormat(datetime.now()).format('m')
# Account DB모델에서 year, month로 필터링
account = Account.objects.filter(
designate_date__year=year,
designate_date__month=month
)
result = paginator.paginate_queryset(account, request)
try:
serializer = AccountSerializer(result, many = True, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
except Account.DoesNotExist:
return Response(status=status.HTTP_400_BAD_REQUEST)
관리비 장부를 전체 조회하는 API는 달력UI로 구성되어 있기 때문에 한 페이지에서 필요한 데이터는 한 달 기준 이므로, 최대 31개의 데이터를 반환하면 된다고 생각했습니다.
그래서 쿼리를 통해 년도와 월 기준으로 데이터를 필터링하면 해당 년도, 해당 월에 필요한 데이터만 반환 할 수 있다고 생각했으며, 페이지네이션을 활용하여 이를 구현해보았습니다.
Django의 PageNumberPagination()
을 활용하여 page_size로 한 페이지 내의 최대로 반환 할 객체 수를 31개로 정의했고, 쿼리에 년도와 월을 입력하지 않으면 default 값으로 현재 년도와 현재 월을 반환하도록 지정했습니다.
이후 "사용자가 지정한 거래 발생 일자"인 designate_date
필드에 입력 받은 쿼리를 기준으로 필터링 하여 쿼리셋을 정의했습니다.
특정 관리비 장부만 조회, 수정, 삭제하는 API는 게시글 API와 크게 다른 점이 없어서 전체 코드는 Github에 올려두었습니다.
현재 투표를 조회하는 로직과 관리비 장부를 전체 조회하는 로직의 성능을 정확히 테스트 할 순 없지만, 이후 서버 배포를 하고 API 성능 테스트를 거쳐서 더 효율적이고 좋은 성능을 어떻게 구현시킬지 고민 해봐야겠습니다.
아직 까지는 큰 에러 사항이 없어서 특별한 트러블 슈팅 과정을 기입하지 않았습니다. 다음 번에는 Oauh2.0구현 혹은 배포 준비를 해 볼 계획입니다.
틀린 부분에 대한 피드백, 질문 등 언제나 환영입니다 :D