221014_DRF Basic (2)

Csw·2022년 10월 15일
0

Django

목록 보기
14/14

이 게시글은 김준태 강사님DRF - Basic 강의를 듣고 작성하였습니다.


📢 수업 목표

  1. DRF 로 간단한 블로그 만들기


✨ 카테고리 추가하기

🎨 Model, Serializers 설정

📌 글마다 카테고리를 할당할 수 있도록 카테고리 모델을 작성

  • 글을 작성할 때, 존재하지 않는 카테고리를 지정하면 안되기 때문에, 이미 존재하는 카테고리 중에서만 카테고리를 선택할 수 있도록 해야함.
  • 그래서 우선 카테고리를 저장해놓기 위한 모델/시리얼라이저를 작성.
  • 그리고 글 모델에는 카테고리 필드를 하나 추가하고, 지금 만든 테고리 모델의 데이터만 받을 수 있도록 수정 (카테고리 모델에 없는 데이터는 절대 지정할 수 없도록)

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 페이지를 통한 카테고리 추가

📌 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 페이지 접속 > 카테고리 추가

🎨 article 시리얼라이저의 수정

  • 시리얼라이저는 어떤 내용을 사용자에게 보여줄지 (응답으로 전달할지) 결정하는 용도도 있음!!
    • 지금은 글을 표시할 때 제목, 내용, 글쓴시각, 수정시각 만 표시하게 됨.
    • 모델을 수정해서 카테고리가 추가된 만큼, 카테고리도 표시하도록 시리얼라이저를 수정!!

📌 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번에 해당하는 카테고리가 글에 잘 할당되어서, 표시되는 것을 확인 가능

✨ 댓글 추가하기

🎨 Model, Serializers 설정

📌 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

    • ManyToManyFieldN:N 관계일 때, ForeignKey1: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 옵션을 걸어서 글 번호는 표시하지 않도록 설정

🎨 article 시리얼라이저 수정

  • article 데이터를 전달할 때, 해당 글이 가진 댓글 데이터도 함께 넘겨줄 수 있도록 article 시리얼라이저를 수정
    • 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 수정 (POST 요청 작성)

  • 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 수정

  • 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

🎨 요청 보내기

  • blogdetail view 에 작성했기 때문에, 아래처럼 blogdetail url 로 post 요청을 보내면 댓글이 등록됨
  • POST 메서드로 댓글 등록
  • GET 메서드로 정상 등록 여부 확인

0개의 댓글