요모조모 ʚɞ

[D.R.F] 게시판 댓글 기능 구현하기 - 1:N 관계 본문

개발/Django RESTful API

[D.R.F] 게시판 댓글 기능 구현하기 - 1:N 관계

Angela_OH 2021. 4. 27. 13:29

 

안녕하세요 ( ͡• ͜ʖ ͡• ) !

오늘은 Django RESTful API로 댓글 기능을 구현해보도록 하겠습니다.

댓글을 구현하기 위해서는 우선 댓글을 달 수 있는 모델이 존재해야 합니다.

이 부분은 이전 포스팅을 참고해주세요 ~

 

[D.R.F] 게시판 기능(CRUD) 구현하기 - 작성일, 작성자 추가

안녕하세요 :D 오늘은 지난번까지 실습한 ViewSets과 Custom user를 사용하여 게시판 기능을 구현해보도록 하겠습니다. ● ViewSets으로 CRUD 구현하기 오늘은 Django RESTful API로 회원정보를 관리(회원가입/

wisdom-990629.tistory.com



먼저 댓글을 구현하기에 앞서 댓글 모델 관계에 대해 알아보도록 하겠습니다.

일반적인 게시글을 생각해보면 게시글 1개당 여러 개의 댓글을 달 수 있고,

사용자 1명당 여러 개의 댓글을 달 수 있음을 알 수 있습니다.

관계형 데이터베이스에서는 이러한 관계를 1:N 관계 (Many to one)라고 지칭합니다.

1:N 관계

하나의 부모 엔티티에 연결된 자식 엔티티가 여러 개가 될 수 있는 관계


여기서 부모 엔티티는 게시글(혹은 사용자), 자식 엔티티는 댓글이라고 생각할 수 있고,

부모가 있어야 자식이 있듯이 게시글(혹은 사용자)이 있어야 댓글도 존재할 수 있습니다!

이를 위해서는 부모 테이블과 자식 테이블을 연결할 수 있어야 하며, 이때 외래키(Foreign Key)가 사용됩니다.

외래키 (Foreign Key)

다른 테이블의 Primary Key를 참조하는 테이블 속성의 집합, 두 테이블을 연결하는 역할을 함


기본키 (Primary Key)

record를 식별하는 고유 값 (값이 unique 해야 하고 NULL 값을 가질 수 없음)

 

좀 더 자세한 정보는 Django 공식 문서를 참고하세요!

 

Many-to-one relationships | Django documentation | Django

Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate

docs.djangoproject.com


정리하면 댓글의 Foreign Key(외래키)게시글과 사용자의 Primary Key(기본키)를 연결함으로써 1:N 관계를 맺을 수 있습니다.

게시글인 Blog 모델을 구현할 때를 생각해보면 id라는 field를 Primary Key로 사용하고 있습니다.

우리는 1:N 관계를 맺기 위해 해당 Primary Key를 댓글 모델의 Foreign Key와 연결하도록 하겠습니다.

models.py
class Blog(models.Model):
    id = models.AutoField(primary_key=True, null=False, blank=False) # primary key
    title = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
    body = models.TextField()

 



우선 댓글 모델을 만들어 보도록 하겠습니다.

models.py
from django.db import models
from django.conf import settings
from account.models import User

class Blog(models.Model):
    id = models.AutoField(primary_key=True, null=False, blank=False)
    title = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
    body = models.TextField()

    def __str__(self):
        return self.title

class Comment(models.Model):
    id = models.AutoField(primary_key=True, null=False, blank=False)
    blog = models.ForeignKey(Blog, null=False, blank=False, on_delete=models.CASCADE)
    user = models.ForeignKey(User, null=False, blank=False, on_delete=models.CASCADE)
    created_at = models.DateField(auto_now_add=True, null=False, blank=False)
    comment = models.TextField()

    def __str__(self):
        return self.comment


Comment 모델의 Primary Key로는 id를, 작성일은 created_at, 댓글 내용은 comment라는 field를 사용해주었습니다.

그리고 Foreign Key로는 User(댓글 쓴 사람)와 Blog(게시글) 모델을 연결해주었습니다.

이후에 데이터베이스 상으로 확인해보면 연결된 Blog의 id와 Comment의 blog는 동일한 값을 가지게 됩니다!

 

다음으로는 생성해준 Comment 모델을 마이그레이션 시켜줍니다.

python manage.py makemigrations

python manage.py migrate

 



다음으로는 댓글 view를 만들어주도록 하겠습니다.

views.py
from .models import Blog, Comment
from .serializers import BlogSerializer, CommentSerializer
from rest_framework import viewsets
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .permissions import IsOwnerOrReadOnly

# (게시글) Blog의 목록, detail 보여주기, 수정하기, 삭제하기 모두 가능
class BlogViewSet(viewsets.ModelViewSet):
    authentication_classes = [BasicAuthentication, SessionAuthentication]
    permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
    queryset = Blog.objects.all()
    serializer_class = BlogSerializer
   
    def perform_create(self, serializer):
        serializer.save(user = self.request.user)

# (댓글) Comment 보여주기, 수정하기, 삭제하기 모두 가능
class CommentViewSet(viewsets.ModelViewSet):
    authentication_classes = [BasicAuthentication, SessionAuthentication]
    permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer

    def perform_create(self, serializer):
        serializer.save(user = self.request.user)


CommentViewSet은 queryset으로는 Comment 모델의 객체들을, serializer로는 CommentSerializer를 사용해주었습니다.

여기서 CommentSerializer에 있는 user 값을 채워주기 위해 perform_create 함수를 재정의 하였습니다.

(댓글 모델을 생성할 때 해당 함수를 호출한 후 현재 user 정보를 전달합니다.)

왜냐하면 보통 댓글을 쓸 때를 생각해보면 내가 댓글 쓴 사람의 값을 지정하지 않고,

자동으로 현재 로그인된 사용자의 정보가 채워지기 때문입니다.

그럼 created_at은 왜 전달하지 않나요?라고 생각할 수 있는데,

created_at은 모델 생성 시 auto_now_add=True로 설정하였기 때문에 자동으로 채워지는 값이기 때문에 따로 전달하지 않았습니다.



이후 views.py에서 호출해준 CommentSerializer를 만들어주었습니다.

serializers.py
from .models import Blog, Comment
from rest_framework import serializers

class BlogSerializer(serializers.ModelSerializer):
    user = serializers.ReadOnlyField(source = 'user.nickname')
    class Meta:
        model = Blog
        fields = ['id', 'title', 'created_at', 'user', 'body']

class CommentSerializer(serializers.ModelSerializer):
    user = serializers.ReadOnlyField(source = 'user.nickname')
    class Meta:
        model = Comment
        fields = ['id', 'blog', 'user', 'created_at', 'comment']


user라는 field를 추가로 선언하여 perform_create를 통해 전달받은 user 값을 사용합니다.

저는 user 중에서 닉네임 값이 필요하기 때문에 source로 user.nickname을 사용해주었습니다.


 

urls.py
from django.urls import path, include
from .views import BlogViewSet, CommentViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
# 첫 번째 인자는 url의 prefix
# 두 번째 인자는 ViewSet
router.register('blog', BlogViewSet, basename='blog') # (게시글)
router.register('comment', CommentViewSet, basename='comment') # (댓글)
urlpatterns =[
    path('', include(router.urls)),
]


마지막으로 views.py에서 생성한 CommentViewSet의 url을 지정합니다.

저는 router를 사용하여 선언해주었습니다.



이와 같이 생성을 해준 후 서버를 실행시켜주면 다음과 같은 결과 화면을 볼 수 있습니다.

먼저 댓글을 작성한 후

댓글 작성하기


/comment라는 url로 접속을 해보면 아래와 같이 작성한 댓글 목록을 확인할 수 있습니다.

댓글 리스트


댓글은 게시글과 마찬가지로 permission을 설정해두었기 때문에 내가 작성한 댓글이 아닐 때는 수정과 삭제를 할 수 없습니다.

 



오늘은 댓글 기능을 가진 Django RESTful API를 만들어보았습니다!

다음 시간에는 좋아요 기능도 만들어보도록 하겠습니다 :)

 

Comments