이번에는 2차프로젝트 기간에 만들었던 notification기능, follow status기능과 message기능을 리뷰해보겠다.
로그인을 하고 나서 나에게 누군가 나를 팔로우하면 위와같이 notification에 빨간불이 들어온다.(메세지도 새로운 메세지가 오면 메세지 창에 빨간불이들어옴.)
class NotificationView(View):
@login_required
def get(self, request):
user = request.user.id
[0] latest_message = Message.objects.filter(to_user_id = user).order_by('created_at').last()
latest_follower = Follow.objects.filter(to_follow_id = user).order_by('created_at').last()
[1] if not message_checked or not follow_checked:
return_key = {
'data' : {
'message_checked' : latest_message.is_checked,
'follow_checked' : latest_follower.is_checked,
'follower_id' : latest_follower.from_follow_id,
'sender' : latest_message.from_user_id,
}
}
return JsonResponse(return_key, status = 200)
[2] return_key = {
'data' : {
'message_checked' : message_checked,
'follow_checked' : follow_checked,
}
}
return JsonResponse(return_key, status = 200)
코드를 보면
[0] 최근에 생성된 나에게 보내진 메세지와 팔로우 객체를 가져오기 위해 생성순서대로 객체들을 정렬해주고 last()를 사용해서 가장 최근에 생성된 객체를 가져온다.
[1] is_checked 가 False(0)일때, 가장 최근에 생성된 메세지와 팔로우 객체의 is_checked값을 가져온다. 생성되었을 때 default로 False가 들어가기 때문에 체크가되지 않은 객체는 False(0)가 온다. 그리고 체크여부와 유저정보를 리턴한다.
[2] is_checked가 True(1)일때는 메세지와 팔로우의 체크 여부만 리턴하도록 한다.
notifications을 클릭하면
위와같이 누가 나를 팔로우했는지, 그리고 나도 그사람을 팔로우한 상태인지(mutual follow, 맞팔) 알 수 있다.
class StatusView(View):
@login_required
def get(self, request):
user = request.user.id
[1] follow_status = (
Follow.objects
.filter(to_follow_id = user, is_checked = False)
.select_related('from_follow', 'to_follow')
.order_by('created_at')
)
[2] if not len(list(follow_status)):
return JsonResponse({'message' : 'EMPTY_UPDATES'}, status = 400)
[3] return_key = {
'data' :
[{'follower_name' : status.from_follow.name,
'follower_id' : status.from_follow.id,
[4] 'follower_follower_count' : status.from_follow.follow_reverse.all().count(),
'follower_song_count' : status.from_follow.song_set.all().count(),
'follower_image' : status.from_follow.profile_image,
'follow_at' : status.created_at,
'is_checked' : status.is_checked,
[5] 'mutual_follow' : True if Follow.objects.filter(from_follow_id = user, to_follow_id = status.from_follow_id) else False}
for status in follow_status]
}
[6] Follow.objects.filter(to_follow_id = user).update(is_checked = True)
return JsonResponse(return_key, status=200)
[1] 팔로우 대상이 user(토큰을 가지고있는 유저)이고, 체크가 되어있지 않은 팔로우객체를 생성순서대로 정렬해서 가져온다.
[2] 가져온 값이 없으면 업데이트 된 상태가 없기 때문에 empty update를 리턴한다.
[3] 값이 있으면 나를 팔로우한 상대방의 정보를 가져온다(from_follow_id에 해당)
[4] 나를 팔로우하는 상대방의 팔로워가 몇명인지 가져온다. 모델에서 설정해주었던 related_name인 follow_reverse를 통해서 값을 가져올 수 있다.
[5] 삼항연산자를 사용해서 내가 나를 팔로우한 상대방을 팔로우하고 있으면 True, 아니면 False를 값을 리턴하는 객체를 만든다.
[6] 나를 팔로우한 상대방의 정보를 end-point호출로 확인 했기 때문에 is_checked를 True로 바꿔준다.
소켓통신으로 상대방이 나를 팔로우하면 실시간으로 내 상태가 업데이트 되는 것이 아니기 때문에 프론트앤드와 협의해서 창을 옮길 때 마다, 또는 일정 시간마다 notification 앤드포인트 호출을 통해서 상태를 나의 팔로우 notification 상태를 업데이트해주도록 한다.
위의 사진은 토큰을가진 유저가 상대방과 주고받은 대화를 하나의 chunk로 하여 묶여있다. 그래서 토큰을 가진 유저가 수신자가 된 경우와 발신자가 된 두가지 경우의 메세지 객체를 가져와서 중복값을 재거해주어야 한다. 중복값의 제거에 대해서는 여기를 참고해보자.
위의 사진에서 view all messages를 누르면 아래와 아래와같이 다른유저와 주고받은 매세지 묶음들을 볼 수 있다.
처음의 사진은 썸내일 개념으로 3개만 보여주고, 클릭하고 들어오면 전체를 보여주는 코드를 보자.
@login_required
def get(self, request):
try:
user = request.user.id
message_chunk = Message.objects.filter(Q(from_user_id = user)|Q(to_user_id = user)).order_by('created_at')
offset = int(request.GET.get('offset', 0))
limit = int(request.GET.get('limit', message_chunk.count()))
우선 offset과 limit값을 쿼리파라미터로 받아온다. offset(시작값)은 이 경우 0일 경우가 대부분일 것이기 때문에 default를 0으로 준다. limit(보여주는 갯수)값은 값이 들어오지 않았을 경우를 대비해 위에서 정의해준 message_chunk의 갯수만큼을 default로 지정한다.
return JsonResponse({'data' : messages_to_user[offset:offset + limit]}, status = 200)
그리고 마지막에 리턴값에 들어온 offset과 limit대로 slicing을 걸어주어 pagination을 만든다. 만약 limit값이 들어오지 않은 경우 유저들과 나눈 메세지 chunk를 다 보여주고, limit값이 들어온 경우 limit값 만큼만 보여준다.
@login_required
def get(self, request):
try:
user = request.user.id
message_chunk = Message.objects.filter(Q(from_user_id = user)|Q(to_user_id = user)).order_by('created_at')
offset = int(request.GET.get('offset', 0))
limit = int(request.GET.get('limit', message_chunk.count()))
[0] to_user = request.GET.get('to_user', None)
if to_user:
[1] message_details = (
Message.objects.prefetch_related('playlist', 'song')
.filter((Q(from_user_id=user)&Q(to_user_id=to_user))|(Q(from_user_id=to_user)&Q(to_user_id=user)))
.order_by('created_at')
)
messages = [
{
'message_id' : message.id,
'content' : message.content,
'from_user_id' : message.from_user_id,
'from_user_name' : message.from_user.name,
'from_user_img' : message.from_user.profile_image,
'to_user_id' : message.to_user_id,
'to_user_name' : message.to_user.name,
'to_user_img' : message.to_user.profile_image,
'is_checked' : message.is_checked,
'created_at' : message.created_at,
'playlist' : [{'playlist_id' : data.id, 'name' : data.name} for data in message.playlist.all()],
'song' : [{'song_id' : data.id, 'name' : data.name} for data in message.song.all()],
}
for message in message_details
]
[2] Message.objects.filter(to_user_id = user).update(is_checked = True)
return JsonResponse({'message_details' : messages}, status = 200)
[0] 상대방의 id를 쿼리파라미터로 받는다.
[1] 수신인이 토큰을 가진 유저와 송신인이 쿼리파라미터로 받은 상대방이 주고받은경우, 그 반대의 경우의 메세지 객채를 가져온다.
[2] 메세지를 확인했으니깐 is_checked를 True로 바꿔놓는다.
is_checked가 True로 바뀌어져서 다음에 notifiaction 요청에 True로 잡혀서 알림창에 불이켜지지 않는다.
class MessageView(View):
@login_required
def post(self, request):
data = json.loads(request.body)
[1] playlist = data.get('playlist_id', None)
[2] song = data.get('song_id', None)
[3] message = Message.objects.create(
content = data['content'],
from_user_id = request.user.id,
to_user_id = data['to_user_id'],
)
[1] if playlist:
MessagePlaylist.objects.create(
[4] message_id = message.id,
playlist_id = playlist,
)
[2] if song:
MessageSong.objects.create(
message_id = message.id,
song_id = song,
)
return HttpResponse(status = 200)
사운드 클라우드 메세지의 특성상 메세지로 내 플레이리스트와 내가만든 음악을 보낼 수 있다.
[1]과 [2]에서 처럼 song과 playlist는 값이 들어올 수도 안들어올 수도 있기 때문에 값이 들어오지 않을 경우 default로 None을 넣어서 조건문이 실행되지 않고 Message객체가 생성되도록 한다.
[3]에서와 같이 Message객체를 생성시키고 그 객체를 message라는 변수에 담아준다. 그리고 [4]와 같이 message와 playlist의 중간테이블에 foreignkey에 해당하는 message id값을 방금 변수화한 message객체의 id값에 대응시킨다.