좋아요 구현
class Article(models.Model):
title = models.CharField(max_length=50)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
image = models.ImageField(upload_to='images/', blank=True)
author = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="articles"
)
like_users = models.ManyToManyField(
settings.AUTH_USER_MODEL, related_name='like_articles'
)
def __str__(self):
return self.title
articles/models.py
like_users를 만들어 준다.
N:M 관계에서는 중계 테이블이 필요하고, ManyToManyField를 통해 자동으로 중계 테이블이 생성된다.
역시 이번에도 related_name을 만들어 더 쉽게 접근할 수 있도록 함.
이후 migrate까지 해주자.
그러면 shell_plus에서 좋아요 실제로 해보자.
우선 변수를 만들어주고,
article_1.like_users.add(admin_user)를 통해 좋아요를 등록한다.
article_1에 좋아요 한 유저를 조회할 수 있음.
해당 유저가 어느 article에 좋아요 했는지 역참조하여 조회할 수 있음.
좋은데 새로운 컬럼을 추가하고 싶다. 좋아요 중계 테이블을 내가 직접 만들 수 없나?
path('<int:pk>/like/', views.like, name='like')
articles/urls.py에 경로 like 경로 추가.
@require_POST
def like(request, pk):
if request.user.is_authenticated:
article = get_object_or_404(Article, pk=pk)
if article.like_users.filter(pk=request.user.pk).exist():
article.like_users.remove(request.user)
else:
article.like_users.add(request.user)
return redirect('articles:articles')
return redirect('accounts:login')
articles/views.py
좋아요 또는 좋아요 취소는 GET요청이 없음. 보여줄게 없다.
로그인 한 유저에 한해서 좋아요가 가능해야 하므로, user.is_authenticate 조건문을 사용.
로그인한 유저의 pk에 해당하는 like_users가 존재한다면, 좋아요를 취소.(remove)
즉 if 조건에 해당하는 부분이 좋아요 취소, else 조건이 좋아요.
{% for article in articles %}
<a href="{% url 'articles:article_detail' article.pk %}">
<p>[ {{ article.pk }} ] {{ article.title }}</p>
</a>
<form action="{% url 'articles:like' article.pk %}" method="POST">
{% csrf_token %}
<input type="submit" value="좋아요">
</form>
{% endfor %}
articles/articles.html
하던대로 해주면 된다.
이러면 좋아요 버튼 누르면 좋아요 가능.
그런데 조금 불편하다. 좋아요 한 상태라면 버튼이 취소로 바뀌면 좋을텐데.
{% for article in articles %}
<a href="{% url 'articles:article_detail' article.pk %}">
<p>[ {{ article.pk }} ] {{ article.title }}</p>
</a>
<form action="{% url 'articles:like' article.pk %}" method="POST">
{% csrf_token %}
{% if request.user in article.like_users.all %}
<input type="submit" value="안좋아요">
{% else %}
<input type="submit" value="좋아요">
{% endif %}
</form>
{% endfor %}
if 조건문을 통해, 현재 로그인 되어있는 유저가 좋아요 목록에 있다면 안좋아요 버튼을 표시해주면 됨.
팔로우 구현
class User(AbstractUser):
followings = models.ManyToManyField(
"self", related_name="followers", symmetrical=False
)
accounts/models.py
그런데 팔로우는 User와 User 사이의 관계이므로 나 자신과 ManyToMany 관계를 맺어야 한다.
이럴 때는 self를 사용하면 된다.
역시 역참조를 위해 related_name을 followers로 해주고, 일촌 방법 아니기 때문에 symmetrical=False로 해줌.
모델 바꿨으니 당연히 migrate까지 해줘야 한다.(이젠 그냥 좀 하자)
path('<int:user_id>/follow/', views.follow, name='follow'),
users/urls.py
유저와 관련된 것이기 때문에 users 앱에서 처리해주는 것.
from django.shortcuts import render, redirect
from django.views.decorators.http import require_POST
from django.shortcuts import get_object_or_404
from django.contrib.auth import get_user_model
@require_POST
def follow(request, user_id):
if request.user.is_authenticated:
member = get_object_or_404(get_user_model(), pk=user_id)
if member != request.user:
if member.followers.filter(pk=request.user.pk).exists():
member.followers.remove(request.user)
else:
member.followers.add(request.user)
return redirect('users:profile', username=member.username)
return redirect('accounts:login')
users/views.py
user_id 파라미터로 받아오는 유저는 내가 선택한 다른 유저를 말하고,
request.user 의 유저는 현재 로그인한 유저를 말한다.
내가 선택한 다른 유저(member)의 followers를 확인해서,
이 안에 로그인한 현재 유저(request.user)가 있다면 언팔, 없다면 팔로우.
그런데 내가 나를 팔로우 할 수는 없기 때문에 member와 request.user가 다른 경우에만 로직이 행되도록 해준다.
{% extends 'base.html' %}
{% block content %}
<h1>{{ member.username }}의 프로필 페이지</h1>
<div>
<h2>username : {{ member.username }}</h2>
<form action="{% url 'users:follow' member.pk %}" method="POST">
{% csrf_token %}
<input type="submit" value="팔로우">
</form>
</div>
<a href="/index/">INDEX로 돌아가기</a>
{% endblock content %}
users/profile.html
action="{% url 'users.follow' user.pk %}" 를 해버리면 지금 접근한 프로필의 유저가 아닌 로그인 한 유저가 넘어오게 된다.
우리는 현재 템플릿에 member(접근한 프로필의 유저)를 context로 담아 넘겨줘야 한다. 그리고 member.pk로 받아오면 됨.
그러려면 결국 profile 뷰를 수정해줘야 함.
def profile(request, username):
member = get_object_or_404(get_user_model(), username=username)
context = {
'member': member,
}
return render(request, 'users/profile.html', context)
원래의 profile 뷰는 username을 그대로 넘겨주도록 되어있었음.
member를 context에 담아 사용할 수 있도록 수정해준다.
아까처럼 팔로우, 언팔로우 버튼 바뀌도록 수정해주자.
{% extends 'base.html' %}
{% block content %}
<h1>{{ member.username }}의 프로필 페이지</h1>
<div>
<h2>username : {{ member.username }}</h2>
<form action="{% url 'users:follow' member.pk %}" method="POST">
{% csrf_token %}
{% if request.user in member.followers.all %}
<input type="submit" value="언팔로우">
{% else %}
<input type="submit" value="팔로우">
{% endif %}
</form>
</div>
<a href="/index/">INDEX로 돌아가기</a>
{% endblock content %}
profile.html 수정
조건문 달아서 하던대로 해주면 된다.
내가 나를 팔로우해보자. 버튼이 거슬린다. 필요도 없는데?
admin으로 로그인해서 admin 프로필 페이지로 간 뒤 팔로우를 해보면 아무일도 일어나지 않음.
우리가 view에서 if member != request.user 처리를 해줬기 때문에.
그래도 버튼이 거슬리기 때문에 지워주자.
{% extends 'base.html' %}
{% block content %}
<h1>{{ member.username }}의 프로필 페이지</h1>
<div>
<h2>username : {{ member.username }}</h2>
{% if request.user != member %}
<form action="{% url 'users:follow' member.pk %}" method="POST">
{% csrf_token %}
{% if request.user in member.followers.all %}
<input type="submit" value="언팔로우">
{% else %}
<input type="submit" value="팔로우">
{% endif %}
</form>
{% endif %}
</div>
<a href="/index/">INDEX로 돌아가기</a>
{% endblock content %}
profile.html 수정해주면 됨.
뷰에서와 똑같이 request.user != member 의 조건을 걸어주고 이 경우에만 form을 나타내면 된다.
이러면 내가 나의 프로필을 조회하는 경우 팔로우 버튼이 아예 나오지 않음.
오늘의 회고
드디어 장고기초 끝...
복습하자...
내일의 목표는 프로젝트 방향 잡기, 구현 시작.