[Django/DB] 좋아요(LIKE) 기능 구현하기 : Article - User (M:N) 관계 설정

문지은·2023년 5월 9일
0

Django + Database

목록 보기
6/12
post-thumbnail

Article과 User의 M:N 관계 설정을 통해 좋아요 기능을 구현해보자.

모델 관계 설정

  • ManyToManyField 작성
# articles/models.py

from django.db import models
from django.conf import settings

# Create your models here.
class Article(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    like_users = models.ManyToManyField(settings.AUTH_USER_MODEL)
    title = models.CharField(max_length=30)
    content = models.TextField()
    image = models.ImageField(blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'{self.id}번째글 - {self.title}'

Migration 진행 후 에러 확인

$ python manage.py makemigrations

SystemCheckError: System check identified some issues:

ERRORS:
articles.Article.like_user: (fields.E304) Reverse accessor for 'articles.Article.like_user' clashes with reverse accessor for 'articles.Article.user'.
        HINT: Add or change a related_name argument to the definition for 'articles.Article.like_user' or 'articles.Article.user'.
articles.Article.user: (fields.E304) Reverse accessor for 'articles.Article.user' clashes with reverse accessor for 'articles.Article.like_user'.
        HINT: Add or change a related_name argument to the definition for 'articles.Article.user' or 'articles.Article.like_user'.

  • like_users 필드 생성 시 자동으로 역참조에는 .article_set 매니저가 생성됨

  • 그러나 이전 N:1(Article-User) 관계에서 이미 해당 매니저를 사용 중

    • user.article_set.all() : 해당 유저가 작성한 모든 게시글 조회
    • user가 작성한 글들(user.article_set)과 user가 좋아요를 누른 글(user.article_set)을 구분할 수 없게 됨
  • user와 관계된 ForeignKey 혹은 ManyToManyField 중 하나에 related_name을 작성해야 함

  • ManyToManyField에 related_name 작성 후 Migration

from django.db import models
from django.conf import settings

# Create your models here.
class Article(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    like_users = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='like_articles')
    title = models.CharField(max_length=30)
    content = models.TextField()
    image = models.ImageField(blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'{self.id}번째글 - {self.title}'
  • 생성된 중개 테이블 확인

User - Article 간 사용 가능한 related manager 정리

  • article.user : 게시글을 작성한 유저 - N:1
  • user.article_set : 유저가 작성한 게시글(역참조) - N:1
  • article.like_users : 게시글을 좋아요한 유저 - M:N
  • user.like_articles : 유저가 좋아요한 게시글(역참조) - M:N

LIKE 구현

url 및 view 함수 작성

# articles/urls.py

from django.urls import path
from . import views

app_name = 'articles'
urlpatterns = [
    ...,
    path('<int:article_pk>/likes/', views.likes, name='likes'),
]
# articles/views.py

def likes(request, article_pk):
    article = Article.objects.get(pk=article_pk)
    if article.like_users.filter(pk=request.user.pk).exists():
        article.like_users.remove(request.user)
    else:
        article.like_users.add(request.user)
    return redirect('articles:index')

.exists()

  • QureySet에 결과가 포함되어 있으면 True를 반환하고 그렇지 않으면 False를 반환
  • 특히 큰 QuerySet에 있는 특정 개체의 존재와 관련된 검색에 유용

템플릿 작성

  • index 템플릿에서 각 게시글에 좋아요 버튼 출력하기
<!-- articles/index.html -->

{% for article in articles %}
    <p>
        [{{article.id}}] 
        <a href="{% url 'articles:detail' article.pk %}" id="article-title">{{article.title}}</a>
        - 작성자: {{article.user}}
    </p>
    <hr />
    <div>
        <form action="{% url 'articles:likes' 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>
    </div>
    {% endfor %}
  • 좋아요 버튼 출력 확인

  • 버튼 클릭 후 좋아요 테이블 확인

데코레이터 및 is_authenticated 추가

# articles/views.py

def likes(request, article_pk):
    if request.user.is_authenticated:
        article = Article.objects.get(pk=article_pk)
        if article.like_users.filter(pk=request.user.pk).exists():
            article.like_users.remove(request.user)
        else:
            article.like_users.add(request.user)
        return redirect('articles:index')
    return redirect('accounts:login')

추가 구현

좋아요 개수 출력하기

<p>{{ article.like_users.all.count }} 명이 이 글을 좋아합니다.</p>

📍 프로젝트 전체 코드 확인하기

https://github.com/mjieun0956/TIL/tree/master/Database/06.%20Article-User%20(LIKE)

profile
코드로 꿈을 펼치는 개발자의 이야기, 노력과 열정이 가득한 곳 🌈

0개의 댓글