본문 바로가기
DEV/Web 개발

Web 개발 :: 파이썬 Django Rest Framework(1) _ 프로젝트 세팅, 모델 Serializer, CRUD 기능 구현

by 올커 2022. 10. 25.

DRF(Django Rest Framework)(1) _ 프로젝트 세팅, 모델 Serializer, CRUD 기능 구현

01. 프로젝트 세팅

 - 프로젝트 기본 환경 셋팅

# 가상환경 설치
python -m venv venv

# 가상환경 실행
source venv/Scripts/activate

# 장고 설치
pip install django

# DRF 설치
pip install djangorestframwork

# 설치된 라이브러리 저장
pip freeze > requirements.txt

# 프로젝트 앱 설치 (.을 해서 현재 폴더에 만들기)
django-admin startproject <프로젝트명> .

 - 깃 사용환경 만들기
  1) .gitignore 생성(*gitignore.io 참고)
  2) 원격 repo 생성

  3) 원격 repository와 로컬 repository 연결, 현 프로젝트 setting 상태 푸쉬

git add .
git remote add origin <주소>
git commit -m 'init project'
git push origin main

 

 - settings.py의 INSTALLED_APPS에 아래 코드 추가 (※ 참고 링크)

INSTALLED_APPS = [
    ...
    'rest_framework',
]

 - 서버 정상 동작되는지 확인

python manage.py runserver

02. 시리얼라이저(Serializer)

 - 일단 새로운 앱을 하나 만들어본다. (*앱 생성시 settings.py의 INSTALLED_APPS 앱 추가 필히 해준다.)

python manage.py startapp articles

 - models.py에서 모델 정의

from django.db import models

# Create your models here.
class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add = True)
    updated_at = models.DateTimeField(auto_now = True)

    def __str__(self):
        return str(self.title)

 - db에 생성한 모델 반영

python manage.py makemigrations
python manage.py migrate

 - admin.py에서 생성한 모델을 관리하기 위하여 아래 코드를 추가한다.

from django.contrib import admin
from articles.models import Article

# Register your models here.
admin.site.register(Article)

 - admin 화면을 사용하기 위해 관리자 계정을 생성한다.

python manage.py createsuperuser

 - url연결을 위해 프로젝트 폴더의 urls.py와 앱 폴더의 urls.py를 작성해준다.

# drf_prj/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('articles/', include('articles.urls'))
]
# articles/urls.py

from django.urls import path
from articles import views

urlpatterns = [
    path('index/', views.index, name='index')
]

 - urls에서 정의해준 views.index를 정의하기 위해서 views.py를 작성한다.

# articles/views.py

from rest_framework.response import Response
from rest_framework.decorators import api_view

# Create your views here.

@api_view(['GET'])
def index(request):
    return Response("연결되었습니다.")

 - django rest framework는 rest_framework로 모듈을 불러올 수 있다.

   DRF를 사용할 때 Response와 api_view를 사용하여 불러온 데이터와 함께 전송이 잘 되었는지 아래와 같은 화면을 통해 확인할 수 있다.

 - 여기서 api_view는 '데코레이터 함수' 로 함수를 인자로 받아 새로 꾸며진 함수를 리턴시키는 구조를 갖고 있다.

 - 만약 db에 저장된 데이터를 get으로 불러올 때 함수를 아래와 같이 작성하면 오류가 발생한다. 이 이유는 db에서 가져오는 데이터는 쿼리셋 형태로 가져오기 때문이다. Response는 위와 같이 str이나 또는 dict 형식으로 파일을 전달할 수 있다. 

# 쿼리셋으로 불러오기 때문에 Error 발생
@api_view(['GET'])
def index(request):
	articles = Article.objects.all()
    return Response(articles)
    
# 아래와 같이 딕셔너리 형태로 전달할 수 있다.
@api_view(['GET'])
def index(request):
    articles = Article.objects.all()
    article = articles[0]
    article_data = {
        "title" : article.title,
        "content" : article.content,
        "created_at" : article.created_at,
        "updated_at" : article.updated_at,
    }
    return Response(article_data)


03. 모델 시리얼라이저를 이용한 CRUD

 - 시리얼라이저 작성

# articles/serializers.py

from rest_framework import serializers
from articles.models import Article

class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Articles        # 모델은 Articles
        fields = "__all__"       # 모든 필드를 가져온다.

 - views.py 수정

   작성한 serializer를 가져오고, 아래와 같이 데이터를 넣은 후 Response의 인자로 넣어준다.

   이 때 인자명에는 끝에 .data를 넣어주어야 한다.

from rest_framework.response import Response
from rest_framework.decorators import api_view
from articles.models import Article
from articles.serializers import ArticleSerializer

# Create your views here.

@api_view(['GET'])
def index(request):
    articles = Article.objects.all()
    article = articles[0]
    serializer = ArticleSerializer(article)
    return Response(serializer.data)

 - 만약 여러개의 내용을 불러오고 싶을 경우 아래와 같이 작성하면 쿼리셋으로 받아오기 때문에 AttributeError가 발생한다. 

@api_view(['GET'])
def index(request):
    articles = Article.objects.all()
    serializer = ArticleSerializer(articles)
    return Response(serializer.data)

 - 여러개를 가져올 때에는 아래와 같이 Serializer의 옵션으로 'many=True'라는 옵션을 주면 각 내용을 리스트 형태로 넣어 변환해준다.

# Create your views here.
@api_view(['GET'])
def index(request):
    articles = Article.objects.all()
    serializer = ArticleSerializer(articles, many=True)
    return Response(serializer.data)


 - 이번에는 POST형식으로 데이터를 받아오는 구조를 만든다. GET 형식일 때는 Serialize(직렬화)로 기존 모델의 data를 json data로 바꾸어주어 template로 보냈다면, POST 형식일 때에는 json data로 받은 데이터를 정의한 모델 필드별 타입에 맞게 변경하여 보내주도록 해야 한다. 이를 Deserialize(역직렬화)라고 한다.

 - views.py는 아래와 같이 GET과 POST를 구분하여 다시 작성했다.

 - 상태를 화면에서 표기해주기 위해서 status를 임포트해온다.

 - POST방식으로 request한 data를 가져오기위해서는 인자값에 data = 를 붙여주어야 한다.

 - 유효한지 검증하기 위해서 serializer.is_valid()를 함수로 주었고 유효하면 .save(), Response에 데이터와 상태를 출력하고, 유효하지 않으면 error를 표기한다. error는 404에러로 지정하여 출력한다

  *실제 개발단계에서는 error를 표기하면 문제가 발생했을 때 확인하기 용이하지만, 배포단계에서는 보안상의 위험이 있기 때문에 error를 표기하지 않는 것을 권장한다.

# articles/views.py

from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import api_view
from articles.models import Article
from articles.serializers import ArticleSerializer

# Create your views here.
@api_view(['GET', 'POST'])
def index(request):
    if request.method == 'GET':
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        serializer = ArticleSerializer(data = request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            print(serializer.errors)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 - 화면에서 content는 아래와 같이 dict타입으로 입력해주어야 한다. 현재는 title만 입력해서 데이터를 보내었다. (content 필드에 null=True, blank=True가 되어있는지 체킹해주어야 한다.) 또한 따옴표는 "큰 따옴표(")"만 사용해야 한다.

 - 정상적으로 등록이 되면 아래와 같이 HTTP 201 Created, 그리고 저장된 내용을 출력해준다.

 - 만약 아래와 같이 잘못된 필드값이 입력되었을 경우 에러를 출력해야 한다.

 - 정상적으로 에러가 출력되는 모습을 확인할 수 있었다.


 - 게시글을 불러오기 또는 게시글 생성시에는 해당 게시글의 id가 필요하지 않다. 하지만 게시글 수정, 삭제시에는 특정 게시글에 적용되기 때문에 해당 게시글의 id가 필요하며 이는 urls.py에서 정의해주어야 한다.

# articles/urls.py

from django.urls import path
from articles import views

urlpatterns = [
    path('index/', views.index, name='index'),
    path('<int:article_id>/', views.article_view, name='article_view'),
]

 - 게시글의 상세페이지를 불러오거나, 수정, 삭제하기 위하여 article_view 함수를 아래와 같이 정의한다.

# articles/views.py
from rest_framework.generics import get_object_or_404
...

# 상세페이지, 수정, 삭제
@api_view(['GET', 'PUT', 'DELETE'])     # GET : 상세페이지, PUT : 수정, DELETE : 삭제
def article_view(request, article_id):
    if request.method == "GET":         # 상세페이지 불러오기
        article = get_object_or_404(Article,id=article_id)      # 없는 값을 요청할 경우 404에러 발생
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

    elif request.method == "PUT":           # 게시글 수정하기
        article = get_object_or_404(Article,id=article_id)
        serializer = ArticleSerializer(article, data=request.data)      # (기존 데이터, 변경할 데이터)
        if serializer.is_valid():
            serializer.save()
            return Response (serializer.data)

    elif request.method == "DELETE":            # 게시글 삭제하기
        article = get_object_or_404(Article,id=article_id)
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

 - 게시글 수정부분은 아래와 같이 작성한다.

@api_view(['GET', 'PUT', 'DELETE'])     # GET : 상세페이지, PUT : 수정, DELETE : 삭제
def article_view(request, article_id):
    if request.method == "GET":         # 상세페이지 불러오기
        article = get_object_or_404(Article,id=article_id)      # 없는 값을 요청할 경우 404에러 발생
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

 - 정상적인 article_id로 POST 요청을 보낸 경우 아래와 같이 정상적으로 출력된다.

 - 만약 article_id가 잘못되었을 경우 아래와 같이 에러를 발생시킨다.


 - 게시글 수정시에는 request를 PUT을 사용하며 아래와 같이 코드를 입력해준다.

    elif request.method == "PUT":           # 게시글 수정하기
        article = get_object_or_404(Article,id=article_id)
        serializer = ArticleSerializer(article, data=request.data)      # (기존 데이터, 변경할 데이터)
        if serializer.is_valid():
            serializer.save()
            return Response (serializer.data)

 - 화면 하단에 PUT이라는 버튼이 생긴 것을 확인할 수 있다. 입력은 글 생성과 동일하게 dict 형식으로 각 필드와 값을 입력하면 DB를 수정할 수 있다.


 - 게시글 삭제시에는 method를 "DELETE"를 사용한다. 이 때에는 serializer를 사용하지 않아도 된다. article_id가 맞는지 확인한 후 삭제해준다. Response로는 204로 콘텐츠가 삭제되어 없음을 출력하도록 하였다.

    elif request.method == "DELETE":            # 게시글 삭제하기
        article = get_object_or_404(Article,id=article_id)
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

 - 삭제 버튼은 상단의 붉은색 DELETE로 나타나있으며, 클릭시 작동됨을 확인할 수 있다.

 - 삭제 후 해당 페이지를 다시 접속하면 아래와 같이 '찾을 수 없습니다.' 문구를 확인할 수 있다.


 - 편의를 위해 urls.py의 'index/'를 '/'로, 함수 이름을 'index'에서 'article_API'로 변경해주었다.

   또 기존의 'article_view'는 'article_detail_API'로 변경하였다.

# articles/urls.py

from django.urls import path
from articles import views

urlpatterns = [
    path('', views.article_API, name='index'),
    path('<int:article_id>/', views.article_detail_API, name='article_view'),
]
# articles/views.py

...

@api_view(['GET', 'POST'])
def article_API(request):
    if request.method == 'GET':
...

@api_view(['GET', 'PUT', 'DELETE'])
def article_detail_API(request, article_id):
    if request.method == "GET":
...
반응형

댓글