이번에는 팔로우, 팔로잉 기능을 구현해 보도록 하겠습니다.
1. Follow 모델 작성
팔로우 기능의 모델은
accounts/models.py
에 작성하도록 하겠습니다.파일명 :
accounts/models.py
class Follow(models.Model): from_user = models.ForeignKey(Profile, related_name='follow_user', on_delete=models.CASCADE) to_user = models.ForeignKey(Profile, related_name='follower_user', on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) # 관계가 언제 생겼는지 작성 def __str__(self):# 인스턴스 추적 양식 지정 return "{} -> {}".format(self.from_user, self.to_user) class Meta: unique_together = ( ('from_user', 'to_user') # 유니크한 관계 형성 )
Follow 모델의 from_user 필드와 to_user 필드를 보시면 둘 다 Profile 모델을 참조하고 있습니다.
중계 모델을 사용했던 Like 모델과 비교를 해보면 Like는 명확합니다. User와 Post의 사이를 Like가 중계해주고 있습니다.
파일명 :
post/models.py
class Like(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) post = models.ForeignKey(Post, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: unique_together = ( ('user', 'post') )
Follow 모델은 이보다 난이도가 있는 모델 관계입니다. 헷갈리니 주의해서 보셔야 합니다. Profile 모델에 ManyToManyField를 추가합니다.
파일명 :
post/models.py
class Profile(models.Model): ... follow_set = models.ManyToManyField('self', # 자신을 참조 blank=True, # 아무도 팔로우를 안한 상태 through='Follow', # 중간 모델 symmetrical=False,) # 비대칭 관계 ...
해당 유저를 팔로우하고 있는 유저(follwer)와 해당 유저가 팔로우 하고 있는 유저(following)을 가져오는 속성을 추가합니다.
파일명 :
post/models.py
class Profile(models.Model): ... @property def get_follower(self): return [i.from_user for i in self.follower_user.all()] @property def get_following(self): return [i.to_user for i in self.follow_user.all()] ...
팔로워와 팔로잉 수를 가져오는 속성을 추가합니다.
파일명 :
post/models.py
@property def follower_count(self): return len(self.get_follower) @property def following_count(self): return len(self.get_following)
어떤 유저가 해당 유저의 팔로워인지 또는 팔로잉인지 확인하는 함수입니다.
파일명 :
post/models.py
def is_follower(self, user): return user in self.get_follower def is_following(self, user): return user in self.get_following
모델의 변경사항을 반영합니다.
(venv)root@goorm:/workspace/instaclone/instaclone# python3 manage.py makemigrations

(venv)root@goorm:/workspace/instaclone/instaclone# python3 manage.py migrate

2. Follow 모델 Admin에 등록
Follow 모델을 admin에 추가해봅니다.
파일명 :
accounts/admin.py
from django.contrib import admin from .models import Profile, Follow class FollowInline(admin.TabularInline): # 팔로우 내용을 표 양식으로 보려고 하는 옵션 model = Follow fk_name = 'from_user' @admin.register(Profile) class ProfileAdmin(admin.ModelAdmin): list_display = ['id', 'nickname', 'user'] list_display_links = ['nickname', 'user'] search_fields = ['nickname'] inlines = [FollowInline,] @admin.register(Follow) class FollowAdmin(admin.ModelAdmin): list_display = ['from_user', 'to_user', 'created_at'] list_display_links = ['from_user', 'to_user', 'created_at']
서버를 실행하여 admin 페이지로 이동하면 Follows가 잘 등록된 것을 확인할 수 있습니다.

팔로우를 추가해 보도록 하겠습니다. 본인이 원하는 유저간의 팔로우를 하나 등록합니다.


3. Ajax를 통한 팔로우
이번에는 Ajax 통신을 이용하여 페이지에서 직접 팔로우하도록 작성해보겠습니다.

현재 로그인한 사용자가 post를 작성한 사람을 팔로우한 상태라면 '팔로잉'으로 표시 되어야 할 것이고 팔로우를 안한 상태라면 '팔로우'로 보이도록 하겠습니다.
post_list.html
에서 div.sprite_more_icon 태그의 내용을 수정합니다.파일명 :
post/templates/post/post_list.html
<div class="sprite_more_icon" data-name="more"> <ul class="toggle_box"> <li> {% if user.profile in post.author.profile.get_follower %} <input type="submit" class="follow" value="팔로잉" data-name="follow" name="{{ post.author.profile.id }}"> {% else %} <input type="submit" class="follow" value="팔로우" data-name="follow" name="{{ post.author.profile.id }}"> {% endif %} </li> <li>수정</li> <li>삭제</li> </ul> </div>
수정, 삭제 부분도 수정합니다. 글의 작성자만 수정, 삭제가 가능해야하기 때문에 if 태그를 사용합니다.
파일명 :
post/templates/post/post_list.html
<div class="sprite_more_icon" data-name="more"> <ul class="toggle_box"> <li> {% if user.profile in post.author.profile.get_follower %} <input type="submit" class="follow" value="팔로잉" data-name="follow" name="{{ post.author.profile.id }}"> {% else %} <input type="submit" class="follow" value="팔로우" data-name="follow" name="{{ post.author.profile.id }}"> {% endif %} </li> {% if post.author == user %} <li><a class="post-edit" href="{% url 'post:post_edit' post.pk %}">수정</a></li> <li> <form class="post-delete-form" action="{% url 'post:post_delete' post.pk %}" method="post"> {% csrf_token %} <input type="submit" class="post-delete" value="삭제"> </form> </li> {% endif %} </ul> </div>
파일명 :
post/templates/post/script_ajax.html
<script type="text/javascript"> (function(){ const delegation = document.querySelector('.contents_box'); function delegationFunc(e){ ... if (elem.matches('[data-name="heartbeat"]')){ ... }else if (elem.matches('[data-name="bookmark"]')){ ... }else if (elem.matches('[data-name="comment"]')){ ... }else if(elem.matches('[data-name="comment_delete"]')){ ... }else if(elem.matches('[data-name="follow"]')){ console.log("팔로우!"); var pk = elem.getAttribute('name'); $.ajax({ type:"POST", url: "{% url 'accounts:follow' %}", data: { 'pk': pk, 'csrfmiddlewaretoken': '{{ csrf_token }}', }, dataType: "json", success: function(response){ console.log("성공"); if(response.status){ document.querySelector('input.follow[name=\''+pk+'\']').value="팔로잉"; }else{ document.querySelector('input.follow[name=\''+pk+'\']').value="팔로우"; } }, error: function(request, status, error){ alert("code:"+request.status+"\n"+"message:"+request.responseText+"\n"+"error:"+error); } }) } elem.classList.toggle('on'); } delegation.addEventListener('click',delegationFunc); })() </script>
1. urls.py
urls.py에 팔로우 요청 URL을 추가합니다.
파일명 :
accounts/urls.py
urlpatterns = [ ... path('follow/', follow, name='follow'), ]
2. views.py
작성할 follow 함수에서 필요한 소스들을 import 합니다.
파일명 :
accounts/views.py
import json from django.http import HttpResponse from django.contrib.auth.decorators import login_required from django.views.decorators.http import require_POST from django.contrib.auth import authenticate, login from django.shortcuts import redirect, render, get_object_or_404 from django.contrib.auth import logout as django_logout from .forms import SignupForm, LoginForm from .models import Profile, Follow
follow 함수를 작성합니다.
파일명 :
accounts/views.py
@login_required @require_POST def follow(request): from_user = request.user.profile pk = request.POST.get('pk') to_user = get_object_or_404(Profile, pk=pk) follow, created = Follow.objects.get_or_create(from_user=from_user, to_user=to_user) if created: message = '팔로우 시작!' status = 1 else: follow.delete() message = '팔로우 취소' status = 0 context = { 'message': message, 'status': status, } return HttpResponse(json.dumps(context), content_type="application/json")
이제 팔로우 버튼이 잘 작동하는 것을 확인할 수 있습니다.
