<오늘 한 일/>
- 동행 채택하기 기능 구현
[백엔드]
- 먼저 models.py에서 Accompany 모델에 필드를 추가한다
- 좋아요 기능과 비슷하게 User 모델과 Accompany 모델이 ManyToMany 관계를 가진다
- 그 다음은 urls.py에 url 경로를 추가한다
- accompany id와 apply id 모두 필요하므로 url에 넣었다
- 그 다음은 views.py에서 채택하기 함수를 작성한다
# accompanies/views.py
...
class AccompanyPickView(APIView): # 동행 채택하기 기능
def post(self, request, accompany_id, apply_id):
accompany = get_object_or_404(Accompany, id=accompany_id)
apply = get_object_or_404(Apply, id=apply_id)
if request.user == accompany.user:
if apply.user not in accompany.picks.all():
if accompany.picks.count() < accompany.personnel:
accompany.picks.add(apply.user)
return Response(
{
"message": "채택 완료",
"personnel": accompany.personnel,
"picks_count": accompany.picks.count(),
},
status=status.HTTP_201_CREATED,
)
else:
return Response(
{
"message": "목표 인원을 이미 채웠습니다. 인원을 수정하거나 다른 유저와의 동행을 취소하세요.",
"picks_count": accompany.picks.count(),
},
status=status.HTTP_406_NOT_ACCEPTABLE,
)
else:
accompany.picks.remove(apply.user)
return Response(
{
"message": "채택 취소",
"personnel": accompany.personnel,
"picks_count": accompany.picks.count(),
},
status=status.HTTP_200_OK,
)
else:
return Response({"message": "권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN)
- if문이 너무 많지만 어쩔 수가 없다..그에 맞는 else문이 각각 있기 때문에,,,
- 우선 채택하기 기능은 accompany(동행 구하기 글) 작성자가 본인 글에 달린 apply(동행 신청 글)의 작성자를 채택할 수 있다
- 그래서 5번째 줄 로그인 한 유저와 accompany user가 일치해야 하고, 만약 다르다면 권한이 없다는 메시지를 뱉는다
- 그 다음 accompany picks 필드에 apply.user가 있는지 확인 후 있으면 채택을 취소하고 없으면 채택한다
- 근데 채택하기에 앞서 accompany 작성자가 설정한 personnel(목표 인원)보다 작아서 채택할 수 있고 같거나 크면 목표 인원을 이미 채웠다는 메시지를 뱉는다
- 원래 채택 완료/채택 취소 시에 "picks" 라는 키에 accompany.picks.all() 라는 값을 넣었는데 not Json serializerable 에러가 떠서 진짜 한 3시간은 고민한 거 같다...
- 계속 6,8,26번째 줄이 문제인 줄 알고 거기만 시리얼라이저를 넣었다가 어쨌다가 막 그랬는데 거기가 아니라 picks 라는 키에 들어가는 accompany.picks.all()이 문제였다,,
- 근데 프론트엔드와 연결하다 보니 그게 필요가 없어서 지금은 뺀 상태다............하...
- 여튼 마지막으로 시리얼라이저를 수정한다
- 채택하기 함수에서는 필요없는데 프론트엔드에서 정보를 받을 때 get 요청에 들어가는 AccompanySerializer와 put 요청에 들어가는 AccompanyCreateSeralizer 둘 다에 picks_count가 필요해서 넣었다
- 두 시리얼라이저를 합치고 싶은데 accompany를 작성하거나 수정할 때 applies가 있으면 작성/수정이 안 된다
- applies를 read_only로 해도 안 돼서 그냥 두 시리얼라이저를 쓴다..
[프론트엔드]
- 일단 백엔드와 통신하는 함수를 먼저 작성한다
- 그 다음 동행 구하기/신청하기 글이 로드되는 부분이다
- 도장 마크를 생성하고 보이지 않게 설정한다
- 동행 구하기 글 picks 리스트에 apply user가 있으면 도장을 보이게 만든다
- 그 다음은 동행 수락 버튼이다
- 동행 수락 버튼을 수정, 삭제 버튼과 같이 만든다
- 동행 수락 버튼은 로그인한 사용자이며, 로그인 한 사용자와 동행 구하기 글 작성자가 같고, 동행 구하기 글 작성자와 동행 신청하기 글 작성자(동행 구하기 글 작성자가 본인 글에 답글 단 경우 동행 구하기 글 작성자 = 동행 신청하기 글 작성자가 됨)가 다른 경우에 보인다
- 동행 수락 버튼을 클릭하면 pickApply(accompany, apply) 함수가 실행된다
- 위에 도장 마크와 마찬가지로 동행 구하기 글 picks 리스트에 apply user가 있으면 동행 취소로 보이면서 회색 버튼이 되고, 없으면 동행 수락 버튼으로 보인다
// static/js/accompany.js
function pickApply(accompany, apply) {
postAccompanyPickAPI(accompany.id, apply.id).then(({ response, responseJson }) => {
if (response.status == 201) {
if (confirm("정말 수락하시겠습니까?")) {
let personnel = document.getElementById("personnel")
personnel.innerText = `${responseJson.picks_count}/${responseJson.personnel}명`
let pickImgs = document.getElementsByClassName(`pick-mark ${apply.user}`)
Array.prototype.forEach.call(pickImgs, function(eachPickImg) {
eachPickImg.setAttribute("style", "display: block;")
})
let pickBtns = document.getElementsByClassName(`applier-acc-pick-btn ${apply.user}`)
Array.prototype.forEach.call(pickBtns, function(eachPickBtn) {
eachPickBtn.setAttribute("style", "background-color: silver;")
eachPickBtn.innerText = "동행 취소"
})
}
} else if (response.status == 200) {
if (confirm("정말 취소하시겠습니까?")) {
let personnel = document.getElementById("personnel")
personnel.innerText = `${responseJson.picks_count}/${responseJson.personnel}명`
let pickImgs = document.getElementsByClassName(`pick-mark ${apply.user}`)
Array.prototype.forEach.call(pickImgs, function(eachPickImg) {
eachPickImg.setAttribute("style", "display: none;")
})
let pickBtns = document.getElementsByClassName(`applier-acc-pick-btn ${apply.user}`)
Array.prototype.forEach.call(pickBtns, function(eachPickBtn) {
eachPickBtn.setAttribute("style", "")
eachPickBtn.innerText = "동행 수락"
})
}
} else if (response.status == 406) {
alert(responseJson.message)
} else {
alert("권한이 없습니다.")
}
})
}
- 백엔드에서 분기를 나눈대로 프론트엔드에서도 response.status별로 반응을 달리 했다
- 채택하는 경우 목표 인원이 증가하고, 도장이 찍히고, 동행 수락 버튼은 취소 버튼으로 바뀌면서 회색으로 변한다
- 채택을 취소하는 경우 목표 인원이 감소하고, 도장이 사라지고, 동행 수락 버튼으로 바뀌면서 주황색으로 변한다
- 목표 인원이 다 채워진 경우 또는 로그인 유저와 accompany 작성자가 다른 경우 안내 메시지를 띄운다
- 11번, 16번째 줄은 이번에 새로 알게 된 부분인데 같은 클래스명을 가진 여러 태그들의 속성을 한 번에 바꿀 때 사용한다
- 아주 유용!!!
- 전시 인기랭킹 기능 구현
[백엔드]
- 전시 전체 조회 시 필드를 추가해 보려고 했는데 페이지네이션 때문에 잘 안 돼서 인기랭킹만을 뽑는 url과 view를 새로 하나 만들었다
- url은 이렇게 간단하게 설정한다
- 시리얼라이저도 필요한 필드만 넣어서 만들어준다
# exhibitions/views.py
from django.db.models import Count
...
from .serializers import (
...
TopFiveExhibitionSerializer,
)
...
class PopularExhibitionView(APIView):
permission_classes = [IsAuthenticatedOrReadOnly]
def get(self, request): # 전시 좋아요 탑5 인기랭킹 조회
exhibitions = Exhibition.objects.annotate(total_likes=Count("likes")).order_by(
"-total_likes"
)[:5]
serializer = TopFiveExhibitionSerializer(exhibitions, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
- 이건 처음 해본 건데 models.py Exhibition 모델에 total_likes 메서드가 정의되어 있다
- 이 메서드의 값을 쿼리셋에 넣어주는 함수가 장고의 annotate() 함수다
- total_likes 값은 장고의 Count 함수를 이용해 likes 필드의 개수를 total_likes 값에 넣는다
- 그 다음 total_likes 개수를 기준으로 정렬한다
- 위에서 만든 시리얼라이저에 넣고 데이터를 반환하면 끝
[프론트엔드]
- 아직 html이 구현되어 있지 않아서 일단 백엔드와 통신하는 함수만 만들었다
- html이 만들어지면 아래 사진의 8번째 줄에서 구현을 할 예정이다
- 지금은 데이터만 받아온 상태다
<느낀 점/>
- 동행 채택하기 기능 길면 4시간 정도면 다 만들지 않을까 했는데 백엔드도 쉽지 않았고 프론트엔드도 쉽지 않았다..
- 백엔드는 막상 구현 성공하니 필요없었던 부분 때문에 고민하다가 몇 시간을 날려 먹은 게 너무 아깝다
- 프론트엔드도 생각보다 생각 해야 할 게 많았고 새롭게 알게 된 부분도 있었다
- 그래도 오늘 끝내서 다행이고 내일 후딱 마무리 하고 발표 준비에 더 힘 써야겠다
<내일 목표/>
- 전시 인기랭킹 프론트엔드 구현 완료하기
- 최종 발표를 위한 모든 것들을 준비하기