좋아요 기능과 비슷하게 User 모델과 Accompany 모델이 ManyToMany 관계를 가진다
그 다음은 urls.py에 url 경로를 추가한다
accompanies/urls.py
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()이 문제였다,,
근데 프론트엔드와 연결하다 보니 그게 필요가 없어서 지금은 뺀 상태다............하...
여튼 마지막으로 시리얼라이저를 수정한다
accompanies/serializers.py
채택하기 함수에서는 필요없는데 프론트엔드에서 정보를 받을 때 get 요청에 들어가는 AccompanySerializer와 put 요청에 들어가는 AccompanyCreateSeralizer 둘 다에 picks_count가 필요해서 넣었다
두 시리얼라이저를 합치고 싶은데 accompany를 작성하거나 수정할 때 applies가 있으면 작성/수정이 안 된다
applies를 read_only로 해도 안 돼서 그냥 두 시리얼라이저를 쓴다..
[프론트엔드]
일단 백엔드와 통신하는 함수를 먼저 작성한다
static/js/api.js
그 다음 동행 구하기/신청하기 글이 로드되는 부분이다
static/js/accompany.js
도장 마크를 생성하고 보이지 않게 설정한다
동행 구하기 글 picks 리스트에 apply user가 있으면 도장을 보이게 만든다
그 다음은 동행 수락 버튼이다
static/js/accompany.js
동행 수락 버튼을 수정, 삭제 버튼과 같이 만든다
동행 수락 버튼은 로그인한 사용자이며, 로그인 한 사용자와 동행 구하기 글 작성자가 같고, 동행 구하기 글 작성자와 동행 신청하기 글 작성자(동행 구하기 글 작성자가 본인 글에 답글 단 경우 동행 구하기 글 작성자 = 동행 신청하기 글 작성자가 됨)가 다른 경우에 보인다
동행 수락 버튼을 클릭하면 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를 새로 하나 만들었다