추가 기능 중 팔로워, 팔로우 기능을 구현하는 과제가 있어 구현해보았다.
우선 팔로워 팔로우는 둘 다 유저 모델이고 M : N 관계이니 모델 내의 속성 중 하나로 ManyToManyField 를 통해 참조하게 넣어주자.
user/models.py
class UserModel(AbstractUser):
class Meta:
db_table = "my_user"
nickname = models.CharField(max_length=256, default='')
image = models.ImageField(upload_to='', blank=True, null=True)
# 새로 추가한 속성
follow = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='followee')
이후 views.py 에서 클릭한 유저가 내 follow 에 있다면 follow 목록에서 지우고, 없다면 add 해주도록 user_follow 함수를 만들어준다.
@login_required(login_url='/login/')
def user_follow(request, id):
me = request.user
click_user = UserModel.objects.get(id=id)
if me in click_user.followee.all():
click_user.followee.remove(me)
else:
click_user.followee.add(me)
return redirect('/user')
팔로우 / 팔로우 취소창에서 views.user_follow 를 처리하도록 url 에도 연결해준 다음
user/urls.py
urlpatterns = [
path('', views.home, name='home'),
path('signup/', views.signup, name='signup'),
path('login/', views.login, name='login'),
path('logout/', views.logout, name='logout'),
path('test/', views.test, name='test'),
path('mypage/<int:id>', views.MyPage, name='myPage'),
path('mypage/update/<int:id>', views.update_profile, name='update-profile'),
path('myfeed/<int:id>', views.Myfeed, name='my-feed'),
# 새로 추가한 부분
path('user/', views.user_view, name='user-view'),
path('user/follow/<int:id>/', views.user_follow, name='follow'),
]
user/ 에서 유저 리스트를 get 으로 보여준다.
user/views.py
@login_required(login_url='/login/')
def user_view(request):
user_list = UserModel.objects.all().exclude(username=request.user.username)
return render(request, 'user/follow.html', {'user_list': user_list})
그런 다음 유저 리스트를 보여줘 팔로우 / 팔로우 취소를 한 번에 할 수 있는 html 을 만들어준 뒤 렌더링 해주면 팔로우 / 팔로우 취소창이 완성된다.
<!-- templates/user/user_list.html -->
{% extends 'base.html' %}
{% block title %}
사용자 리스트
{% endblock %}
{% block content %}
<div class="container timeline-container">
<div class="row">
<!-- 왼쪽 컬럼 -->
{% if request.user.is_authenticated %}
<div class="profile_card" style="margin-top: 80px; margin-left: 50px; margin-right: 30px;">
{% if request.user.image %}
<div>
<img src="{{ request.user.image.url }}" class="img">
</div>
{% endif %}
<div class="textBox">
<div class="textContent">
<p class="h1" style="margin-top: 10px;">{{ request.user.nickname }}</p>
</div>
<p class="p">@{{ request.user.username }}</p>
<div>
</div>
</div>
</div>
{% endif %}
<!-- 오른 쪽 컬럼-->
<div class="col-md-7" style="margin: 70px auto 10px auto;">
<div class="row">
<div class="alert alert-success" role="alert" style="margin-bottom: 10px;">
나를 팔로우 하는 사람 수 : {{ user.followee.count }} 명 / 내가 팔로우 하는 사람 수 : {{ user.follow.count }} 명
</div>
</div>
<div class="row">
<!-- 사용자 리스트 반복문 -->
{% for ul in user_list %}
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ ul.username }}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ ul.email }}</h6>
<p class="card-text">
팔로잉 {{ ul.follow.count }} 명 / 팔로워 {{ ul.followee.count }} 명
</p>
{% if ul in user.follow.all %}
<a href="/user/follow/{{ ul.id }}" class="card-link">[팔로우 취소]</a>
{% else %}
<a href="/user/follow/{{ ul.id }}" class="card-link">[팔로우]</a>
{% endif %}
</div>
</div>
<hr>
{% endfor %}
</div>
</div>
<div class="col-md-2"></div>
</div>
</div>
{% endblock %}
팔로우 / 팔로우 취소가 있으면 좋아요 기능도 있어야하지 않을까 싶어서 구현해봤다.
우선 feed/models.py 의 Feed 에 like(좋아요 수) 를 추가해준 뒤 user/models.py 의 UserModel 에 liked_feed(좋아요 누른 피드) 를 fk 로 추가해준다.
feed/models.py
class Feed(models.Model):
class Meta:
db_table = "feed"
# user값이 들어온다면 null을 지워줘야함
author = models.ForeignKey(UserModel, on_delete=models.CASCADE)
content = models.CharField(max_length=256)
title = models.CharField(max_length=256, default='')
image = models.ImageField(upload_to='', null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# 좋아요 수의 디폴트는 0개
like = models.IntegerField(default=0)
user/models.py
class UserModel(AbstractUser):
class Meta:
db_table = "my_user"
nickname = models.CharField(max_length=256, default='')
image = models.ImageField(upload_to='', blank=True, null=True)
follow = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='followee')
# 좋아한 게시물이 없을 수도 있으니 null 을 허용해준다
liked_feed = models.ManyToManyField("feed.Feed", default=None, null=True)
from feed/models import Feed
로 피드를 참조하게 하려 했는데 circular independency 에러가 났다. 이름에서 알 수 있듯이 참조가 순환 구조로 되어 결국 정확한 참조를 하지 못하는 에러이다."feed.Feed"
처럼 문자열로 써주자.이렇게 추가해준 뒤에는 feed/views.py 에서 좋아요를 눌렀을 시 실행되는 함수를 만들어준다. liked_feed 에 누른 피드가 없으면 add() 하고, 있으면 remove() 할 것이다. 더하여 피드의 좋아요 수 또한 변화시킬 것이다.
user/views.py
def feed_like(request, id):
re_address = reverse('read', args=(id,))
current_feed = Feed.objects.get(id=id)
user = request.user
# 피드의 좋아요 수가 0 이하로는 떨어지지 않게 방지
if current_feed.like < 0:
current_feed.like = 0
current_feed.save()
# 현재 피드가 유저의 좋아한 피드에 없으면
if current_feed not in user.liked_feed.all():
user.liked_feed.add(current_feed)
current_feed.like += 1
current_feed.save()
else:
user.liked_feed.remove(current_feed)
current_feed.like -= 1
current_feed.save()
print(current_feed.like)
return redirect(re_address)
이 뒤 피드 상세 페이지에 유저의 liked_feed 에 피드가 있으면 체크박스가 checked 로, 없으면 그냥 보여준 뒤 좋아요 클릭시 처리되는 페이지와 함수가 나타나게 하는 부분을 추가해주면 된다.
templates/feed/detail.html
<!-- 좋아요 버튼 -->
{% if feed in request.user.liked_feed.all %}
<div class="heart-container" title="Like" style="margin-bottom: -40px;">
<input type="checkbox" class="checkbox" id="Give-It-An-Id" checked onclick="location.href='/feed/read/like/{{ feed.id }}'" method="post">
<div class="svg-container">
<svg viewBox="0 0 24 24" class="svg-outline" xmlns="http://www.w3.org/2000/svg">
<path d="M17.5,1.917a6.4,6.4,0,0,0-5.5,3.3,6.4,6.4,0,0,0-5.5-3.3A6.8,6.8,0,0,0,0,8.967c0,4.547,4.786,9.513,8.8,12.88a4.974,4.974,0,0,0,6.4,0C19.214,18.48,24,13.514,24,8.967A6.8,6.8,0,0,0,17.5,1.917Zm-3.585,18.4a2.973,2.973,0,0,1-3.83,0C4.947,16.006,2,11.87,2,8.967a4.8,4.8,0,0,1,4.5-5.05A4.8,4.8,0,0,1,11,8.967a1,1,0,0,0,2,0,4.8,4.8,0,0,1,4.5-5.05A4.8,4.8,0,0,1,22,8.967C22,11.87,19.053,16.006,13.915,20.313Z">
</path>
</svg>
<svg viewBox="0 0 24 24" class="svg-filled" xmlns="http://www.w3.org/2000/svg">
<path d="M17.5,1.917a6.4,6.4,0,0,0-5.5,3.3,6.4,6.4,0,0,0-5.5-3.3A6.8,6.8,0,0,0,0,8.967c0,4.547,4.786,9.513,8.8,12.88a4.974,4.974,0,0,0,6.4,0C19.214,18.48,24,13.514,24,8.967A6.8,6.8,0,0,0,17.5,1.917Z">
</path>
</svg>
<svg class="svg-celebrate" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<polygon points="10,10 20,20"></polygon>
<polygon points="10,50 20,50"></polygon>
<polygon points="20,80 30,70"></polygon>
<polygon points="90,10 80,20"></polygon>
<polygon points="90,50 80,50"></polygon>
<polygon points="80,80 70,70"></polygon>
</svg>
</div>
</div>
{% else %}
<div class="heart-container" title="Like" style="margin-bottom: -40px;">
<input type="checkbox" class="checkbox" id="Give-It-An-Id" onclick="location.href='/feed/read/like/{{ feed.id }}'" method="post">
<div class="svg-container">
<svg viewBox="0 0 24 24" class="svg-outline" xmlns="http://www.w3.org/2000/svg">
<path d="M17.5,1.917a6.4,6.4,0,0,0-5.5,3.3,6.4,6.4,0,0,0-5.5-3.3A6.8,6.8,0,0,0,0,8.967c0,4.547,4.786,9.513,8.8,12.88a4.974,4.974,0,0,0,6.4,0C19.214,18.48,24,13.514,24,8.967A6.8,6.8,0,0,0,17.5,1.917Zm-3.585,18.4a2.973,2.973,0,0,1-3.83,0C4.947,16.006,2,11.87,2,8.967a4.8,4.8,0,0,1,4.5-5.05A4.8,4.8,0,0,1,11,8.967a1,1,0,0,0,2,0,4.8,4.8,0,0,1,4.5-5.05A4.8,4.8,0,0,1,22,8.967C22,11.87,19.053,16.006,13.915,20.313Z">
</path>
</svg>
<svg viewBox="0 0 24 24" class="svg-filled" xmlns="http://www.w3.org/2000/svg">
<path d="M17.5,1.917a6.4,6.4,0,0,0-5.5,3.3,6.4,6.4,0,0,0-5.5-3.3A6.8,6.8,0,0,0,0,8.967c0,4.547,4.786,9.513,8.8,12.88a4.974,4.974,0,0,0,6.4,0C19.214,18.48,24,13.514,24,8.967A6.8,6.8,0,0,0,17.5,1.917Z">
</path>
</svg>
<svg class="svg-celebrate" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<polygon points="10,10 20,20"></polygon>
<polygon points="10,50 20,50"></polygon>
<polygon points="20,80 30,70"></polygon>
<polygon points="90,10 80,20"></polygon>
<polygon points="90,50 80,50"></polygon>
<polygon points="80,80 70,70"></polygon>
</svg>
</div>
</div>
{% endif %}
<div class="textContent">
<p class="h6" style="margin-top: 16px; margin-left: 40px; color: gray;">
{{ feed.like }} 명이 좋아합니다</p>
</div>
좋아요 버튼을 처음 만들었을 때 자기가 쓴 글이 아니면 체크박스 체크가 안 되는 에러가 있었는데, 내가 views.py/user_liked 의 user 부분을 request.user 가 아니라 current_feed.author 로 해서 그 글을 적은 작성자의 좋아한 피드에 피드가 추가되어서 그런 에러가 난 것이었다. user = request.user 로 바꿔준 결과 제대로 작동하였다.