프로젝트는 끝났지만 기능 구현에 대한 도전은 끝나지 않았다
<현재 좋아요 방식>
- 좋아요를 누르면 새로고침 되면서 좋아요 개수가 올라가거나 내려간다
- 하트 색깔 바꾸는 건 구현을 아직 못 했다
// api.js
// 하트를 누르면 실행되는 좋아요 함수
async function likeClick() {
const urlParams = new URLSearchParams(window.location.search);
const postId = urlParams.get("post_id");
const response_post = await getPost(postId);
let token = localStorage.getItem("access")
const response = await fetch(`${backend_base_url}/posts/${postId}/likes/`, {
method: 'POST',
headers: {
"Authorization": `Bearer ${token}`
},
})
location.reload()
}
// post_detail.js
// 게시글 상세페이지로 들어왔을 때 로드되는 동작 함수
window.onload = async function () {
const urlParams = new URLSearchParams(window.location.search);
const postId = urlParams.get("post_id");
const response = await getPost(postId);
...
// 좋아요 개수 보이기
const count = document.getElementById("count")
count.innerText = `좋아요 ${response.like.length}개`
...
await loadPosts(postId);
await loadComments(postId);
}
- 현재는 payload에서 유저 정보를 받아와서 팔로잉 상태와 좋아요 개수를 로드한다
<좋아요 비동기방식으로 구현>
- 백엔드에서 유저 정보 조회 시 좋아요 한 게시글 같이 출력하기
- 현재 백엔드에는 유저 정보 조회 시 id, username, email, name, age, introduction, follwings 필드만 나온다
# users/serializers.py
...
class UserProfileSerializer(serializers.ModelSerializer):
followings = serializers.StringRelatedField(many=True)
class Meta:
model = User
fields = ['id', 'username', 'email', 'name', 'age', 'introduction', 'followings']
- 여기에 유저가 좋아한 게시글을 추가해보자
- 게시글 모델에는 like라는 유저 모델과 ManyToMany 관계를 가지는 필드가 있다
- related_name은 like_posts로 설정되어 있다
- like_posts 필드를 시리얼라이저에 추가해보자
# users/serializers.py
...
class UserProfileSerializer(serializers.ModelSerializer):
followings = serializers.StringRelatedField(many=True)
like_posts = serializers.SerializerMethodField()
def get_like_posts(self, obj):
return obj.user.like_posts
class Meta:
model = User
fields = ['id', 'username', 'email', 'name', 'age', 'introduction', 'followings', 'like_posts']
- 이렇게 하면 에러가 뜬다
AttributeError: 'User' object has no attribute 'user'
- get_like_posts 함수의 return 값으로 obj 다음 user로 접근하는 게 아닌 것 같다
- 실제로 User 모델엔 user 필드가 없다 ㅋㅋ
# users/serializers.py
...
def get_like_posts(self, obj):
return obj.like_posts
...
# 에러 출력
# TypeError: Object of type ManyRelatedManager is not JSON serializable
- 이렇게 하니까 또 에러가 뜬다
- 무슨 말인지 모르겠어서 고대로 복사해서 구글링 해봤다
- 참고블로그: 개발로그 (tistory.com)
- 다행히 바로 답을 찾았다
# users/serializers.py
...
def get_like_posts(self, obj):
return list(obj.like_posts.values())
...
- 이렇게 써야 한다고 한다
- 100% 이해한 건 아니라 설명은 못 하겠지만 반환값의 타입이 JSON 형태가 아니어서 발생하는 에러 같다...?
# users/serializers.py
...
def get_like_posts(self, obj):
print(dir(obj.like_posts))
return list(obj.like_posts.values())
...
# 출력값
# ['__call__', '__class__', '__class_getitem__', '__delattr__', '__dict__', '__dir__',
# '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__',
# '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__',
# '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
# '__slotnames__', '__str__', '__subclasshook__', '__weakref__', '_add_items',
# '_apply_rel_filters', '_build_remove_filters', '_constructor_args', '_db', '_get_add_plan',
# '_get_missing_target_ids', '_get_queryset_methods', '_get_target_ids', '_hints', '_insert',
# '_queryset_class', '_remove_items', '_remove_prefetched_objects', '_set_creation_counter',
# '_update', 'aadd', 'aaggregate', 'abulk_create', 'abulk_update', 'aclear', 'acontains',
# 'acount', 'acreate', 'add', 'aearliest', 'aexists', 'aexplain', 'afirst', 'aget',
# 'aget_or_create', 'aggregate', 'ain_bulk', 'aiterator', 'alast', 'alatest', 'alias', 'all',
# 'annotate', 'aremove', 'aset', 'aupdate', 'aupdate_or_create', 'auto_created', 'bulk_create',
# 'bulk_update', 'check', 'clear', 'complex_filter', 'contains', 'contribute_to_class',
# 'core_filters', 'count', 'create', 'creation_counter', 'dates', 'datetimes', 'db',
# 'db_manager', 'deconstruct', 'defer', 'difference', 'distinct', 'do_not_call_in_templates',
# 'earliest', 'exclude', 'exists', 'explain', 'extra', 'filter', 'first', 'from_queryset',
# 'get', 'get_or_create', 'get_prefetch_queryset', 'get_queryset', 'in_bulk', 'instance',
# 'intersection', 'iterator', 'last', 'latest', 'model', 'name', 'none', 'only', 'order_by',
# 'pk_field_names', 'prefetch_cache_name', 'prefetch_related', 'query_field_name', 'raw',
# 'related_val', 'remove', 'reverse', 'select_for_update', 'select_related', 'set',
# 'source_field', 'source_field_name', 'symmetrical', 'target_field', 'target_field_name',
# 'through', 'union', 'update', 'update_or_create', 'use_in_migrations', 'using', 'values',
# 'values_list']
- 또 어떤 메서드를 쓸 수 있을까 싶어서 프린트로 찍어봤다
- 흠... 또 뭘 쓸 수 있을지는 잘 모르겠다 ..~
- 그냥 저기 values가 있는 것만 확인했다 ㅋㅋㅋ
- 여튼 이렇게 해서 백엔드에서의 유저 정보 조회 기능을 수정했다
- 유저 정보 조회 함수 생성하기
- 백엔드 쪽에서 특정 url로 가면 유저 정보가 나오는 기능을 구현했는데 현재 프론트엔드에서는 구현이 안 된 상태다
- payload에서 유저 정보를 가져오는 것은 한계가 있고, 유저 정보를 조회하는 기능이 백엔드에 있는데 안 쓰는 것은 백엔드 개발자에 대한 예의가 아니다..!
- 게시글을 가져오는 getPost()함수를 만든 것처럼 getUser()함수를 api.js 쪽에 만들었다
// api.js
// 유저 정보 조회
async function getUser() {
const payload = localStorage.getItem("payload")
const payload_parse = JSON.parse(payload)
let token = localStorage.getItem("access")
const response = await fetch(`${backend_base_url}/users/${payload_parse.user_id}/`, {
headers: {
"Authorization": `Bearer ${token}`
},
method: "GET",
})
if (response.status == 200) {
response_json = await response.json()
return response_json
} else {
alert(response.statusText)
}
}
- getPost()함수를 참조하면서 만들었다
- 로컬스토리지에 저장되어 있는 payload 값을 가져와서 json 형태로 변환한다
- header에 사용자 정보를 넣어야 유저를 조회할 수 있게끔 백엔드 코드가 짜여 있으므로 헤더에 사용자 정보를 넣기 위해 로컬스토리지의 access 토큰에 접근해 값을 가져온다
- fetch를 써서 유저 정보를 조회하는 url에 접근하고, 헤더에 사용자 정보를 실어서 나온 값을 response라는 변수에 담는다
- 유저 정보 조회에 성공하면 response를 json형태로 변환해서 반환한다
- user 정보가 필요한 곳에서 getUser()함수를 써서 변수에 담고 그걸 콘솔로그에 찍으면 아래와 같이 나온다
- 좋아요 클릭 함수 수정하기
// api.js
// 좋아요 누르기
async function likeClick() {
const urlParams = new URLSearchParams(window.location.search);
const postId = urlParams.get("post_id");
const response_post = await getPost(postId);
let token = localStorage.getItem("access")
const response = await fetch(`${backend_base_url}/posts/${postId}/likes/`, {
method: 'POST',
headers: {
"Authorization": `Bearer ${token}`
},
})
location.reload()
}
- 원래는 이렇게 백엔드의 좋아요 기능과 연동하고 location.reload()를 써서 새로고침 되면 좋아요 개수가 올라가게끔 했다(좋아요 개수 올라가는 함수는 post_detail.js 쪽에 있다
- 이 함수는 새로고침 여부와 관계없이 좋아요 기능이 실행된다
- 하지만 새로고침 하지 않으면 좋아요 개수가 변경된 걸 확인할 수 없다
// api.js
// 좋아요 누르기
async function likeClick() {
const urlParams = new URLSearchParams(window.location.search);
const postId = urlParams.get("post_id");
const post = await getPost(postId);
let token = localStorage.getItem("access")
let clickLike = document.getElementById("like")
let clickDislike = document.getElementById("dislike")
const response = await fetch(`${backend_base_url}/posts/${postId}/likes/`, {
method: 'POST',
headers: {
"Authorization": `Bearer ${token}`
},
})
const response_json = await response.json()
//좋아요 하트 색 및 개수 변경
if (response_json == "like") {
clickLike.setAttribute("style", "display:flex;")
clickDislike.setAttribute("style", "display:none;")
count.innerText = `좋아요 ${post.like.length + 1}개`
} else if (response_json == "dislike") {
clickLike.setAttribute("style", "display:none;")
clickDislike.setAttribute("style", "display:flex;")
count.innerText = `좋아요 ${post.like.length - 1}개`
}
}
- 이 함수를 완성하기까지 여러 시행착오를 겪었다
- 그 중간과정이 너무 길어서 생략한다...
- 주요 이슈로는 좋아요 1번 누르면 좋아요 개수가 안 바뀌고 한 번 더 누르면 그때부터 바뀌는 것, 좋아요 누르면 개수는 비동기로 변경되는데 하트는 변경되지 않는 것, 하트는 빨개지는데 좋아요 개수는 줄어드는 것 등이 있었다 ㅋㅋ
- 일단 좋아요 기능이 동작하려면 post_id가 있어야 하므로 현재 주소창에서 post_id를 받아와서 getPost()함수를 실행한다
- 좋아요 기능은 상세페이지에만 있으므로 좋아요 버튼을 누를 때 현재 주소창에 항상 post_id가 있다(만약 게시글 목록에서 좋아요 버튼을 누를 수 있게 하려면 또 다른 방법을 써야할 것이다)
- 헤더에 유저 정보를 담기 위해 token을 받아온다
- post_detail.html에 좋아요 버튼 부분을 id를 이용해서 선택한다
- fetch를 통해 좋아요 기능이 실행되고, 돌아온 response를 이용해서 하트 색깔을 바꾼다
- 원래는 백엔드 response에 좋아요, 좋아요 취소가 담겨 있었는데 편의상 like, dislike로 변경했다
# posts.views.py
...
class PostLikesView(APIView):
def post(self, request, post_id):
"""게시글 좋아요 누르기"""
post = get_object_or_404(Post, id=post_id)
if request.user in post.like.all():
post.like.remove(request.user)
return Response("좋아요 취소", status=status.HTTP_200_OK)
else:
post.like.add(request.user)
return Response("좋아요", status=status.HTTP_200_OK)
...
# 아래로 수정
...
class PostLikesView(APIView):
def post(self, request, post_id):
"""게시글 좋아요 누르기"""
post = get_object_or_404(Post, id=post_id)
if request.user in post.like.all():
post.like.remove(request.user)
return Response("dislike", status=status.HTTP_200_OK)
else:
post.like.add(request.user)
return Response("like", status=status.HTTP_200_OK)
- 나중에 following 기능도 비동기 방식으로 바꾼다면 백엔드 response를 follow, unfollow로 바꾸면 좋을 것 같다 지금은follow 했습니다, unfollow 했습니다로 써있는데 편의상...! 그냥 한 단어로 response 값을 보내는 게 좋을 듯!
- 여튼 response가 lilke라면 좋아요를 클릭한 것이므로 빨간 하트를 display: flex로 바꿔서 보이게 하고 빈 하트를 none로 바꿔서 안 보이게 한다
- 좋아요 개수를 바꾸는 부분은 현재 post.like.length로 좋아요 개수를 알 수 있는데 좋아요를 클릭했을 때 이게 업데이트 되지 않는다 새로고침 하거나 클릭을 여러 번 해야 좋아요 개수가 변경되는 오류가 있다
- 아마도 새로고침을 안 해서 post 오브젝트에 그 값이 전달이 안 돼서 그런 것 같다
- 이 부분은 보여지기만 하는 부분이므로 좋아요 한 경우에는 현재 값에서 +1, 좋아요 취소 한 경우에는 현재 값에서 -1이 되도록 했다
- html에는 좋아요 개수 부분에 Text 값은 없다(있으면 페이지 접속했을 때 기본 값이 보여서 혼란을 야기한다 없는 게 낫다)
- 그리고 하트는 빈 하트가 flex, 빨간 하트는 none으로 설정했다
- 기본값은 빈 하트이기 때문이다
- 상세페이지 로드될 때 보여지는 하트 모양과 개수 구현하기
// post_detail.js
window.onload = async function () {
const urlParams = new URLSearchParams(window.location.search);
const postId = urlParams.get("post_id");
const response = await getPost(postId);
...
// 좋아요 개수 보이기
const count = document.getElementById("count")
count.innerText = `좋아요 ${response.like.length}개`
...
await loadPosts(postId);
await loadComments(postId);
}
- 원래는 이렇게 하트 색깔은 시간이 없어서 구현하지 못하고 좋아요 개수만 db에 저장된 게시글 좋아요 개수만큼 보이게 코드를 짰다
// post_detail.js
window.onload = async function () {
const urlParams = new URLSearchParams(window.location.search);
const postId = urlParams.get("post_id");
const post = await getPost(postId);
const user = await getUser();
...
//좋아요 하트색 및 개수 세팅
let like = document.getElementById("like")
let dislike = document.getElementById("dislike")
user.like_posts.forEach((obj) => {
if (postId == obj.id) {
like.setAttribute("style", "display:flex;")
dislike.setAttribute("style", "display:none;")
}
});
const count = document.getElementById("count")
count.innerText = `좋아요 ${post.like.length}개`
...
await loadPosts(postId);
await loadComments(postId);
}
- post와 user 정보를 불러서 변수에 담는다
- html에 id가 like랑 dislike인 그 하트 버튼을 불러온다
- user의 like_posts 각각을 for문을 돌린다 해당 상세게시글의 id와 like_posts 중 한 오브젝트의 아이디가 일치하면 빨간하트를 flex로, 빈 하트를 none으로 바꾼다
- 좋아요 개수는 post.like.length를 통해 조회한다
- 원래는 if문 다음 else로 빨간하트 none, 빈하트 flex로 했었는데 그거 때문에 클릭했는데 하트가 잘 안 바뀌고 그런 오류가 났던 것 같다
- else문을 지우니까 정상 작동 하는 걸 확인할 수 있었다
- 감격 ㅠ