본문 바로가기

매일 TIL

[내일배움캠프 9-1일] 좋아요 구현, 팔로우 구현

좋아요 구현

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을 나타내면 된다.

이러면 내가 나의 프로필을 조회하는 경우 팔로우 버튼이 아예 나오지 않음.


오늘의 회고

드디어 장고기초 끝...

복습하자...

 

내일의 목표는 프로젝트 방향 잡기, 구현 시작.