이 게시글은
김준태 강사님
의DRF - Basic
강의를 듣고 작성하였습니다.
📢 수업 목표
DRF
로 간단한 블로그 만들기
📌 글마다 카테고리를 할당할 수 있도록 카테고리 모델을 작성
- 글을 작성할 때, 존재하지 않는 카테고리를 지정하면 안되기 때문에,
이미 존재하는 카테고리 중에서만 카테고리를 선택
할 수 있도록 해야함.- 그래서 우선
카테고리를 저장해놓기 위한 모델/시리얼라이저
를 작성.- 그리고
글 모델
에는카테고리 필드를 하나 추가
하고, 지금 만든테고리 모델의 데이터만 받을 수 있도록 수정
(카테고리 모델에 없는 데이터는 절대 지정할 수 없도록)
blog/models.py
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
description = models.TextField(max_length=255)
def __str__(self): # 이 코드는 admin 페이지에서 데이터를 object() 형태가 아니라 이름으로 표시할 수 있도록 바꿔주는 코드입니다.
return f"{self.name}"
class Article(models.Model):
category = models.ManyToManyField(Category, blank=True) # () 안의 Category 는 위 Category 모델을 명시한 것입니다!
title = models.CharField(max_length=100)
content = models.TextField()
created_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
하나의 글에 category 를 여러 개 지정
할 수 있도록 하기 위해서, ManyToManyField
를 사용
- Article 의
category 필드
에는 카테고리 모델의 데이터 그 자체가 들어가는 것이 아니고, 카테고리 모델 중에서 어떤 데이터인지 구별하기 위한 pk 만 들어가게 된다고 생각하시면 됩니다!
📌 모델을 수정했다면 꼭 migration 이 필요
Terminal 창
$ poetry run python manage.py makemigrations
$ poetry run python manage.py migrate
📌 serializer 도 추가
blog/serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ["name", "description"]
📌 admin에 Category 모델 등록
blog.admin.py
from django.contrib import admin
from blog.models import Article, Category
# Register your models here.
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ["name", "get_description"]
@admin.display(description="카테고리 안내")
def get_description(self, obj):
return obj.description[:30]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "get_content"]
@admin.display(description="내용")
def get_content(self, obj):
return obj.content[:30]
📌 서버 실행 > admin 페이지 접속 > 카테고리 추가
- 지금은 글을 표시할 때 제목, 내용, 글쓴시각, 수정시각 만 표시하게 됨.
- 모델을 수정해서 카테고리가 추가된 만큼, 카테고리도 표시하도록 시리얼라이저를 수정!!
📌 serializers.py
수정
blog/serializers.py
from rest_framework import serializers
from .models import Article, Category
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ["name", "description"]
class ArticleSerializer(serializers.ModelSerializer):
# article 을 표시할 때 카테고리 객체도 직렬화해서 보기 위한 설정
# 이게 없다면 카테고리 이름이 아니라, 그냥 id 숫자로만 표시되게 됨
category = CategorySerializer(many=True, required=False, read_only=True)
class Meta:
model = Article
fields = ["title", "content", "category"]
- serializer 안에서는 단순히 어떤 내용을 표시할지 뿐만 아니라,
해당 내용(필드)을 표시할 때 정확히 어떻게 표시할 건지
도 명시 가능category 는 여러 개 지정 가능
하기 때문에, 데이터가 여러개 들어올 수 있다는 것을 알려주기 위해서many=True
옵션을 명시해야 함.
📌 create 함수
작성
그런데 위처럼만 작성하게 되면, 사용자로부터 카테고리를 입력받아서 글을 작성하는 과정이 조금 복잡해지게 됨.
- 카테고리를 받아서 글이 저장되는 과정을 조금 더 간단히 만들기 위해서, create 함수를 추가
create 함수
의 역할
- 지금 create 함수는 유저로부터 카테고리 번호를 받고, 글에 해당 번호의 카테고리를 지정해주기 위해서 작성!!
foreign key (ManyToMany)
가 포함된 모델의 데이터를 저장할 때면 보통 create 함수를 커스텀해서 사용!!
article 시리얼라이저에 아래 내용을 추가할텐데, 이 함수는 views 에서 serializer.save() 시에 실행될 함수임.
- 만약 create 함수가 없다면 자동으로 db에 저장하는 과정을 수행하지만, 있다면 해당 함수의 내용대로 db에 저장하는 과정이 수행됨.
def create(self, validated_data): # is_valid 이후에 save 함수가 실행되므로, 현재 유저가 작성해서 들어온 데이터를 validated_data 라고 해서 받습니다.
get_categories = validated_data.pop("get_categories", []) # 유저가 "{title: 제목, content: 내용, get_categories: [카테고리번호들]}" 형태로 데이터를 보냈다면, 여기서 get_categories 부분만 추출합니다.
article = Article(**validated_data) # 해당 내용을 토대로 article 객체를 만듭니다.
article.save() # 해당 객체를 db 에 저장합니다.
article.category.add(*get_categories) # 저장된 그 글에 대해서 유저가 보낸 카테고리 번호에 맞는 category 를 지정합니다.
return article
blog/serializers.py
ArticleSerializer
완성본
from rest_framework import serializers
from .models import Article, Category
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ["name", "description"]
class ArticleSerializer(serializers.ModelSerializer):
category = CategorySerializer(many=True, required=False, read_only=True) # article 을 표시할 때 카테고리 객체도 직렬화해서 보기 위한 설정 (이게 없다면 카테고리 이름이 아니라, 그냥 id 숫자로만 표시되게 됨)
get_categories = serializers.ListField(required=False) # 유저한테 리스트 형태로 카테고리를 입력받을 수 있도록, 추가한 필드
class Meta:
model = Article
fields = ["title", "content", "category", "get_categories"]
def create(self, validated_data): # views 에서 serializer.save() 실행 시 아래 코드가 실행됨 (이 create 함수가 없으면 자동으로 알아서 저장하고, 있으면 여기 있는 내용대로 저장과정을 수행함)
get_categories = validated_data.pop("get_categories", [])
article = Article(**validated_data)
article.save()
article.category.add(*get_categories)
return article
📌 Category
모델 생성 직후
- 게시글마다 등록된 카테고리가 없는 상황
📌 POST
요청
get_categories
라는키:밸류 쌍
을 하나 더 추가해서 Post 요청
📌 GET
요청
blog/
로 접속하여 등록된 모든 글 조회
blog/<int:pk>
로 접속하여 특정 글에 대해 조회
- 1번, 3번에 해당하는 카테고리가 글에 잘 할당되어서, 표시되는 것을 확인 가능
📌 models.py 에 comment 모델을 추가
- 댓글은 무조건
특정한 글
에 달리는 것이기 때문에,
foreign key 형태로 Article 모델과 연결하는 article 필드를 추가
blog/models.py
class Comment(models.Model):
content = models.CharField(max_length=100)
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
created_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
ManyToManyField
vs ForeignKey
ManyToManyField
는N:N
관계일 때,ForeignKey
는1:N
관계일 때 사용하는 필드.
related_name
related_name
: Article 쪽에서 Comment 모델 객체에 접근할 때 쓰는 이름- 지금은 Comment 모델에
어떤 글
에 달린 댓글인지 그 글 번호를 저장하고 있으나,
결국에 우리는 글을 보여줄 때 댓글도 함께 보여줘야 합니다.- 그러려면 글을 가져오는 과정 (Article 쪽) 에서 댓글까지 함께 가져와야 하는데,
그때 해당 이름이 사용되는 것
📌 serializer 작성
blog/serializers.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ["content", "article"]
extra_kwargs = {'article': {'write_only': True}}
- 보통 블로그에서 댓글을 볼 때는 특정한 글 하단 부분에 내용만 표시 (content) 되고,
해당 댓글이 달린 글 번호 (article) 까지 함께 표시하지는 않기 때문에,
'article'
에'write_only': True
옵션을 걸어서 글 번호는 표시하지 않도록 설정
- comments = CommentSerializer(many=True, read_only=True) 부분과 함께
필드에 comments 가 추가된 것을 제외하고는 달라진 것이 없음.
blog/serializers.py
class ArticleSerializer(serializers.ModelSerializer):
category = CategorySerializer(many=True, required=False, read_only=True)
get_categories = serializers.ListField(required=False)
comments = CommentSerializer(many=True, read_only=True) # comment 객체를 직렬화해서 보기 위한 설정
class Meta:
model = Article
fields = ["id", "title", "content", "category", "get_categories", "comments"] # 필드에 comments 추가
def create(self, validated_data):
get_categories = validated_data.pop("get_categories", [])
article = Article(**validated_data)
article.save()
article.category.add(*get_categories)
return article
views.py 내부의 BlogDetail 클래스에다가 댓글 작성을 위한 함수를 추가
- 서버주소/blog/1/ 로 POST 요청을 보내면, 해당 1번 글에 댓글이 달리도록 작성하는 것!!
우선 1번 글이 진짜로 존재하는지 확인하고 → 댓글 내용을 직렬화한 후에 → 해당 1번 글과 댓글을 연결하면서 db 에 저장하도록 할 것임.
blog/views.py
# comment api 를 따로 분리하기도 하는데, 이번에는 간단하게 여기서 post 를 보내도록 하겠습니다
# 프로젝트의 규모가 커진다면 posts 라는 앱, comments 라는 앱으로 분리해서 각각 진행하시는게 좋습니다
def post(self, request, pk):
article = Article.objects.get(pk=pk) # 우선 실제로 존재하는 글에 대해서 댓글을 달고자 하는 것인지 확인
comment_serializer = CommentSerializer(data=request.data, partial=True) # partial=True 라고 하면, 일부 데이터가 없어도 validation 을 통과합니다.
if comment_serializer.is_valid():
comment_serializer.save(article=article) # 유저가 작성한 댓글을 db 에 저장할 때, 앞서 db 에서 찾은 article 과 연결하면서 저장함
return Response({"message": "정상"}, status=status.HTTP_200_OK)
return Response(comment_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
→ 우선은 조금 더 간단한 방식으로 진행한 것이고, 실제로는 지금 같은 경우에 partial=True
를 쓰지는 않음.
- 예외처리도 조금 부족하게 되어있기 때문에, 더 정확한 방식에 대한 공부가 필요함.
admin
페이지에서 확인을 좀 더 편하게 하기 위해 설정 추가blog/admin.py
from django.contrib import admin
from blog.models import Article, Category, Comment
# Register your models here.
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ["name", "get_description"]
@admin.display(description="카테고리 안내")
def get_description(self, obj):
return obj.description[:30]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "get_content"]
@admin.display(description="내용")
def get_content(self, obj):
return obj.content[:30]
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = [ "get_article", "get_content"]
@admin.display(description="댓글")
def get_content(self, obj):
return obj.content[:30]
@admin.display(description="게시글 제목")
def get_article(self, obj):
return obj.article.title
POST
메서드로 댓글 등록
GET
메서드로 정상 등록 여부 확인