지금까지 views.py에서 함수를 사용해왔다면, 오늘은 Class로 작성하는 방법을 보겠습니다.
CBV Class Based View 특징 클래스형 뷰에서는 특정 Http Method에 대한 처리를 함수로 분리할 수 있습니다. → GET요청에 대한 처리는 get()에서, POST 요청에 대한 처리는 post()에서 정의가 가능해요! 클래스를 사용하기 때문에 코드의 재사용성과 유지보수성이 향상됩니다. 기본 APIView외에도 여러 편의를 제공하는 다양한 내장 CBV가 존재합니다.
Class Based View 종류 APIView - DRF CBV의 베이스 클래스 GenericAPIView 일반적인 API 작성을 위한 기능이 포함된 클래스 보통 CRUD 기능이 대부분인 상황을 위해 여러가지 기능이 미리 내장되어 있습니다.
Mixin 재사용 가능한 여러가지 기능을 담고있 클래스 말그대로 여러 클래스를 섞어서 사용하기 위한 클래스 - ListModelMixin - 리스트 반환 API를 만들기 위해 상속 받는 클래스 - CreateModelMixin - 새로운 객체를 생성하는 API를 만들기위해 상속 받는 클래스
ViewSets 여러 엔드포인트(endpoint)를 한 번에 관리할 수 있는 클래스 RESTful API에서 반복되는 구조를 더 편리하게 작성할 수 있는 방법을 제공합니다.
자 그러면 먼저, 저희가 썼던 GET, POST 방식으로 조회, 포스트를 클래스로 만들어 보겠습니다.
from rest_framework.views import APIView
먼저 이렇게 APIView를 가져와서
이렇게 만들어 줍니다. 뭔가 비슷한데 다른 느낌? 그럼 이제 urls도 넣어야하지 않겠습니까? 자, 그러면 함수와 똑같이 views.ArticleListAPIView가 맞을까요? 아닙니다 클래스는 끝에 .as_view()를 넣어야 합니다.
근데 저게 어떻게 가능해??? 하실 수 있는데, 비밀은 APIView에 있습니다. 1. URL과 요청의 흐름 어떤 일이 일어날까요? 클라이언트가 요청을 보내면,(예: GET 또는 POST 요청), urlpatterns에 정의된 경로가 요청을 처리합니다. path('', views.ArticleListAPIView.as_view(), name='article_list')가 호출이 되며, 이 URL이 ArticleListAPIView라는 클래스와 연결됩니다.
2. 클래스형 뷰에서 중요한 역할: as_view() ArticleListAPIView.as_view()는 클래스 기반 뷰에서 요청을 처리할 준비를 합니다. as_view()가 하는 일 클래스를 호출 가능한 함수처럼 바꿔주며, 클라이언트 요청이 들어오면 클래스의 인스턴스를 만들어 줍니다. 요청을 dispatch() 메서드로 전달합니다. 즉, as_view()는 클래스가 요청을 받을 준비를 하도록 돕는 도우미 입니다.
3. dispatch()가 요청을 분기 처리 dispatch()는 들어온 요청의 HTTP 메서드(예: GET, POST)를 확인하고, 적절한 메서드(get, post)를 호출합니다. dispatch()의 동작 요청의 method를 확인하며, (예: request.method는 'GET' 또는 'POST') 그 메서드 이름에 맞는 함수가 클래스에 있는지 찾습니다. GET 요청 → get() 호출 POST 요청 → post() 호출 만약 없는 요청 메서드라면, "Method Not Allowed" 에러를 반환한다. 요약: dispatch()는 요청의 종류(GET, POST 등)에 따라 어떤 메서드를 호출할지 정한다.
4. get()과 post() 메서드가 호출 dispatch()가 get() 또는 post() 메서드를 호출하면, 우리가 작성한 코드가 실행됩니다. get() 데이터베이스에서 Article 데이터를 가져옵니다. 그리고 데이터를 ArticleSerializer로 변환하고 JSON 형식으로 반환합니다. post() 클라이언트가 보낸 데이터를 ArticleSerializer로 검증합니다. 데이터가 유효하면 데이터베이스에 저장하고, 저장된 데이터를 반환합니다. 요약: get()은 데이터를 가져오고, post()는 데이터를 저장한다.
요약: 클라이언트가 URL로 요청을 보냄: 예: GET / 또는 POST / URLConf에서 ArticleListAPIView.as_view() 호출: 클래스를 함수처럼 사용할 준비를 함. dispatch()가 요청 메서드를 확인: GET 요청이면 get(), POST 요청이면 post() 호출. get() 또는 post() 실행: get()은 데이터를 반환. post()는 데이터를 저장.
참고로 클래스가 상속받는 APIView도 중요한 역할을 합니다. 1) 보통 APIView는 GET, POST, PUT, DELETE 같은 요청을 분기 처리할 수 있도록 dispatch()를 구현 및 요청에 따라 get(), post(), put(), delete() 같은 메서드를 호출합니다. 2) 요청(Request)와 응답(Response) 관리 Django의 HttpRequest 대신 DRF의 Request 객체를 사용하게 합니다. 더 많은 기능: JSON 데이터를 파싱하거나, 인증 정보를 쉽게 가져올 수 있습니다. DRF의 Response 객체를 반환하게 해서, 응답 데이터를 JSON처럼 API 형식에 맞게 변환합니다. 그냥 우리가 기본에 사용했던 @api_view를 대신한다고 생각하시면 됩니다.
즉, 해당 클래스를 그냥 받아 넣는게 아니라, as_view()라는 함수를 통해서 views에서 쓸 수 있게 해야합니다. 자 그러면 이제 실행을 해보겠습니다.
문제 없이 작동합니다.
그러면 이어서 수정 삭제도 해보겠습니다.
똑같이 하면됩니다. 그저 pk를 계속해서 전달할 뿐
그리고 정상적으로 잘 작동합니다.
그런데 솔직히
article = get_object_or_404(Article, pk = pk)
이 부분은 계속해서 반복이 되는데, 그러면 이거는 하나의 함수로 빼주는게 좋습니다.
이렇게 말입니다. 그래도 잘 작동합니다.
복습
자, Relationship과 DRF을 넘어가기전, 1:N, N:M으로 서로간의 참조하는 방법 한번 더 복습하는 시간을 가져보겠습니다.
먼저 코드가 이렇게 있습니다. 먼저 저희가 지금 다루고 있는 부분은 Article과 Comment라는 두 가지 모델이 있습니다. Article은 "기사" 같은 거고, Comment는 "댓글"입니다.
그리고 Comment는 어떤 기사의 댓글인지 알아야 하니까, article = models.ForeignKey("Article")를 써서 Article을 연결하고 있습니다 . "Article에서 Comment를 어떻게 꺼내 볼 수 있을까?"라는 질문에 대해서
비유로 이해하기: Article은 큰 상자고, 그 상자 안에 댓글(Comments)라는 여러 개의 작은 종이쪽지가 들어 있다고 상상해보면, 저희가 Article 상자를 열고, "안에 쪽지(댓글)들 좀 가져와 봐!" 하고 말해야 합니다. 그 말이 바로 article.comments.all() 같은 코드입니다.
article = Article.objects.get(id=1) # id가 1인 Article(기사)를 가져와요.
comments = article.comments.all() # 이 기사의 모든 댓글을 가져와요.
예제 데이터로 이해하기: 1. 데이터 준비
# 어떤 기사(Article)를 만들었어
article = Article.objects.create(title="내 첫 번째 기사", content="안녕하세요!")
# 댓글(Comment)을 3개 만들었어
Comment.objects.create(article=article, content="좋은 글이에요!")
Comment.objects.create(article=article, content="감사합니다!")
Comment.objects.create(article=article, content="잘 읽었어요!")
참고로 Comment.objects.create(article=article, content="좋은 글이에요!") 이렇게도 값을 추가할 수 있다는 것을 알으셔야 합니다. 먼저 참조할 모델을 넣고, 그리고 꼬리표 처럼 그 안에 넣을 값을 넣어줘야 합니다. 2. 댓글 가져오기
article = Article.objects.get(id=1) # 첫 번째 Article 가져오기
comments = article.comments.all() # 이 Article에 달린 모든 댓글 가져오기
for comment in comments:
print(comment.content) # 댓글 내용을 출력하기
여기서 먼저 article에 몇번인지 가져오기 위함으로 저렇게 쓰고, 그것을 comments에 연결해서 가져오는 것입니다. 출력 결과:
좋은 글이에요!
감사합니다!
잘 읽었어요!
그러면 이제 궁금하지 않나요? 도대체 그러면 ArticleSerializer(article)(이렇게 그냥 값만 보내주는것)과 ArticleSerializer(data=request.data)의 차이는 뭘까?
두 방식의 차이를 한눈에 이해하기 ArticleSerializer(article) 이건 읽기 모드입니다. article 객체를 JSON 형태로 변환하는 데 사용되며, 데이터베이스에 있는 객체를 그대로 보여주고 싶을 때 쓰는 것입니다. 추가로 ArticleSerializer(article) → 읽기용 (Serialization) 목적: 파이썬 객체를 JSON으로 바꿈 (DB → 사용자) 동작: article이라는 모델 객체를 받습니다. 내부적으로 이 데이터를 JSON으로 변환합니다. 클라이언트에게 데이터를 보내주는 데 사용됩니다.
ArticleSerializer(data=request.data) 이건 쓰기 모드입니다. 사용자가 보낸 데이터를 받아서 유효성 검사를 하고, 모델 객체로 생성하거나 업데이트할 때 사용됩니다. ArticleSerializer(data=request.data) → 쓰기용 (Deserialization) 목적: JSON 데이터를 받아서 파이썬 객체로 변환 (사용자 → DB) 동작: 사용자가 보낸 JSON 데이터를 받습니다. 유효성 검사를 실행해 ("이 데이터가 올바른지?") 유효하다면 새 객체를 만들거나 기존 객체를 업데이트합니다.
그런데 왜 data를 따로 주는 걸까? Serializer는 두 가지 역할을 할 수 있습니다: 읽기 모드 (모델 객체를 JSON으로 변환) 이 경우에는 ArticleSerializer(article)처럼 모델 객체를 넘기면 되며, 데이터를 따로 줄 필요가 없습니다.
쓰기 모드 (JSON 데이터를 모델 객체로 변환) 이 경우에는 사용자가 보낸 데이터를 data로 따로 줘야 합니다. ArticleSerializer(data=request.data)처럼!
Serializer는 "내가 지금 읽기를 해야 하나, 쓰기를 해야 하나?"를 알아야 합니다. data가 없으면 "읽기 모드"라고 판단하고, data가 있으면 "쓰기 모드"라고 판단합니다. 즉, Serializer가 어떤 일을 해야 하는지 알려주는 기준이 바로 data입니다.
추가적인 이유로 data는 사용자가 보낸 새 데이터를 담기 위해서, 즉 서버는 이 데이터를 검사하고, 모델에 맞는 객체를 생성하거나 업데이트해야 합니다.
추가적으로 DRF(Django Rest Framework)뿐만 아니라, 일반 Django에서도 instance를 사용하면 비슷한 원리로 읽기 모드와 쓰기 모드를 구분할 수 있습니다.(근데 조금 개념이 다릅니다)
Django에서 instance는 모델 객체를 받아서 새로운 데이터를 업데이트하거나 기존 데이터를 표시하는 데 사용됩니다. 여기서, 읽기 모드: 이미 존재하는 데이터를 화면에 보여줌. 쓰기 모드: 새로운 데이터를 받아서 기존 데이터를 업데이트함.
instance가 있을 때: 이미 작성된 초대장을 편집하는 것. 기존 내용을 보여주고, 수정할 수 있게 만듭니다.
instance가 없을 때: 새로운 초대장을 작성하는 것. 빈 양식을 보여주고, 새로운 내용을 작성하게 만듭니다.
1. 읽기 모드 (instance 있음) 기존 데이터를 편집하려고 폼을 띄우는 경우:
from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from .models import Article
from .forms import ArticleForm
def edit_article(request, pk):
article = get_object_or_404(Article, pk=pk) # 기존 Article 가져옴
form = ArticleForm(instance=article) # 읽기 모드 (instance 제공)
return HttpResponse(form.as_p())
이 코드는 instance=article을 사용해서 기존 데이터를 폼에 채워서 보여줍니다.
2. 쓰기 모드 (instance 없음) 새로운 데이터를 작성하는 경우:
from django.http import HttpResponse
from .forms import ArticleForm
def create_article(request):
form = ArticleForm() # instance 없이 폼 생성 (새 데이터)
return HttpResponse(form.as_p())
여기서는 instance를 제공하지 않아서, Django가 "새로운 데이터를 작성하는 중"이라고 판단합니다.
3. 업데이트 (읽기 + 쓰기 모드) 기존 데이터를 수정하려는 경우:
from django.shortcuts import get_object_or_404, redirect
from .models import Article
from .forms import ArticleForm
def update_article(request, pk):
article = get_object_or_404(Article, pk=pk) # 수정할 기존 Article 가져옴
if request.method == "POST":
form = ArticleForm(request.POST, instance=article) # instance로 업데이트
if form.is_valid():
form.save() # 수정된 데이터 저장
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm(instance=article) # 기존 데이터로 폼 생성
return HttpResponse(form.as_p())
읽기 모드: GET 요청 시 기존 데이터를 폼에 채워서 보여줌. 쓰기 모드: POST 요청 시 새 데이터를 받아서 기존 데이터를 업데이트.
4. 새로운 데이터 생성 (쓰기 모드) 새 데이터를 생성하려는 경우:
from django.shortcuts import redirect
from .forms import ArticleForm
def create_article(request):
if request.method == "POST":
form = ArticleForm(request.POST) # instance 없이 새 데이터 생성
if form.is_valid():
form.save() # 새 Article 저장
return redirect('article_list')
else:
form = ArticleForm()
return HttpResponse(form.as_p())
instance가 없으므로 Django는 새 데이터를 생성한다고 판단.
전체적인 차이점을 요약하겠습니다.
즉 인자의 위치가 다릅니다. Django에서는 처음 인자로 사용자가 입력한 값이고 두 번째 인자로 기존에 있던 값이지만, DRF에서는 처음 인자로 기존에 있던 값이고 두번째가 바뀐값으로 값을 update하는 방식입니다. 참고로 값을 바꿀때의 말입니다. 기존 생성, 읽기도 다르긴 하지만 쓰임새는 비슷합니다.
2. Django ModelForm의 동작 방식 새로운 데이터 생성
form = ArticleForm(data=request.POST) # 사용자 입력 데이터만 넘김
첫 번째 인자: request.POST → 사용자 입력 값. 두 번째 인자 없음 → 새로운 데이터라고 판단.
기존 데이터 업데이트
form = ArticleForm(data=request.POST, instance=article) # 사용자 입력 값과 기존 데이터
첫 번째 인자: request.POST → 사용자 입력 값. 두 번째 인자: instance=article → 기존 데이터. Django는 두 값을 비교해 업데이트를 진행.
참고로 DRF와 Django에서 둘 다 data와 instance의 사용이 가능합니다. 1. Django ModelForm: data와 instance 모두 사용 가능 data를 사용할 때 data는 사용자가 입력한 데이터를 전달하는 데 사용됩니다. 새로운 데이터 생성과 기존 데이터 업데이트에서 모두 사용 가능합니다.
# 새로운 데이터 생성
form = ArticleForm(data=request.POST)
# 기존 데이터 업데이트
form = ArticleForm(data=request.POST, instance=article)
data에 전달된 값은 사용자가 폼에서 입력한 값으로 간주되고, 이를 instance에 병합해 기존 객체를 수정할 수도 있습니다.
instance를 사용할 때 instance는 기존 데이터를 가져오거나, 업데이트할 대상을 지정하는 데 사용됩니다. 읽기 모드와 쓰기 모드(업데이트)에서 모두 사용 가능.
# 기존 데이터를 읽어오는 폼
form = ArticleForm(instance=article)
2. DRF Serializer: data와 instance 모두 사용 가능 data를 사용할 때 data는 사용자가 입력한 JSON 데이터를 전달. 새로운 데이터 생성과 기존 데이터 업데이트에서 사용 가능.
# 새로운 데이터 생성
serializer = ArticleSerializer(data=request.data)
# 기존 데이터 업데이트
serializer = ArticleSerializer(instance=article, data=request.data)
instance만 단독으로 사용하는 경우 DRF에서는 instance만 단독으로 사용해도 동작 가능하지만, 이 경우는 보통 읽기 전용(Serialization) 작업에만 쓰임. data가 없으면 DRF는 새 데이터를 생성하거나 업데이트하지 않음.
# 읽기 전용
serializer = ArticleSerializer(instance=article)
마지막 요약 Django ModelForm data: 사용자 입력 데이터 전달. instance: 기존 데이터를 불러오거나 수정. instance만으로도 저장 가능(새로운 빈 객체로 저장).
DRF Serializer data: 사용자 입력 JSON 데이터 전달. instance: 기존 데이터 읽기나 수정 시 사용. data 없이 저장 불가(쓰기 작업은 항상 data 필요).
여기서 먼저, ForeignKey를 사용해(N이 되는 쪽이 ForeignKey를 가집니다) Article과 연결됩니다.
그리고 조회하는 코드를 만들었다고 하면, 뭐다? 위에서 설명하듯 instance를 쓰거나, data를 안 쓰면 됩니다.
class CommentListAPIView(APIView):
def get(self, request, article_pk):
article = get_object_or_404(Article, article_pk)
comments = article.comments_set.all()
serializer = CommentSerializer(comments)
#serializer = CommentSerializer(instance=comments)와 동일
이렇게 말입니다. 그리고 여러개니 many=True는 뭐다? 기본 옵션이다~ 최종 변신은
이거입니다. 그리고 실행하면?(아직 아무런 댓글이 없기에 이렇게 나옵니다)
그러면 이제 다른 문제를 보게됩니다. 그런데 이 참조된 comments에 seed를 넣을 순 없나?라고 하실 수 있습니다. 즉, article 앱 말고 article 앱 안에 있는 comments에 말입니다.
대충 이렇게 적으면 됩니다.(마지막 숫자는 article 번호 그런데 1은 없으니 3이나 다른 숫자로 대체)
어? 그런데 저건 fields아닌가? article_id는 넣은적이 없는데? 하실 수 있습니다. Django에서는 ForeignKey를 정의할 때, 관계된 모델의 기본 키(primary key)를 참조하는 필드를 자동으로 생성합니다.
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE)
ForeignKey를 설정하면, Django는 article 필드 외에도 article_id라는 필드를 자동으로 생성합니다. article: ForeignKey를 사용해서 실제 Article 모델의 객체를 참조하는 필드. article_id: article 필드가 참조하는 Article 모델의 기본 키(id) 값을 담는 필드. 왜 이런 필드가 필요할까요? 쉽게 말하면, 기존의 article 필드는 Article 모델의 객체를 저장하고, article_id는 Article 객체의 기본 키 값을 저장해서 빠르고 효율적으로 참조할 수 있도록 돕는 것입니다.
article_id 필드와 article 필드의 차이 article 필드: ForeignKey로 설정된 필드로, 실제 Article 객체를 담고 있어요. 예를 들어, comment.article을 통해 Article 모델의 객체를 가져올 수 있어요. article_id 필드: ForeignKey로 연결된 Article 모델의 id 값을 담는 필드. 예를 들어, comment.article_id를 사용하면 Article 모델의 기본 키 값을 직접 얻을 수 있습니다.
그런데 지금 실행하면 article 필드가 필요한데 받지 못했다고 뜹니다. 왜냐하면 data = request.data에 content만 적어서 보냈기 때문입니다. 그래서 해결법은 저 save()에서 필요한 값을 넘겨줄 수 있습니다. 어떻게? .save(article = article) 이렇게 말입니다.
그런데 이런데도 오류가 뜹니다 왜? 사실 저 article을 넘겨준 시점은 is_valid가 통과되었을 때 시점입니다. 즉 article를 받기전이라 is_valid하지 않아서 그런것이기 때문에, 이때 쓰는것이 read_only_field입니다. serializer.py에 적어주면 됩니다.
즉, 이 시점부터 article은 읽기 전용으로만 쓰이며, POST 같은 생성을 할 때 건들이지 말라는 뜻입니다.
그럼 이제 삭제를 할 시간인데, 삭제는 뭐다? 쉽다. 그렇지만 조심해야 할 점은, 댓글들은 Comment에 있기 때문에 Comment를 가져와야 합니다.
이렇게 해서 최종으로 보면
이제 수정입니다
그전에 저희가 만든 이전 put을 보겠습니다. 자 위에서 설명했지만 한번 더... 먼저 serializer에 data를 담는데, 그냥 담으면 뭐다? save()할때 그냥 새롭게 생성이 되기 때문에, 먼저 인스턴스를 만들고 넣으면, 이 인스턴스 값이 data 값으로 업데이트가 된다라고 생각하시면 됩니다. 그리고 만약 필드가 여러개인데, 특정 필드만 업데이트 할 것이면 partial을 넣는다....이상... (저 뜻은 부분적으로 값을 받는것을 허용한다라고 생각하시면 됩니다)
이렇게 만드시면 됩니다.
갑자기 왜 이렇게 설명의 퀄리티가 떨어지냐구여? 지금 새벽 2시입니다....눈이 침침해요 피곤해요....근데 해야해요...