me

백엔드를 위한 Django REST Framework with 파이썬
2024.08.29 / ,

다음 프로젝트가 오랜만에 장고로 업무 관리 프로그램을 만들어야 하는데 혹시 사용할만한 기능이 있나 해서 백엔드를 위한 Django REST Framework with 파이썬 도서를 구매해서 봄

프론트 입장에서 쓴 책만 보다가 백엔드 기준에서 쓴 책은 무엇이 다를까?


Chapter 1. 웹 & 파이썬 기초

1.1 웹 개발 기초 개념

웹 서비스: 프론트엔드(데이터 처리 제외한 기능 구현, 웹구조, 디자인) + 백엔드(데이터 처리)가 일정한 규칙을 가지고 데이터 주고 받음

현금이 필요해서 출금 요청 시 은행원이 알아서 꺼내줌. 시스템이 만들어 놓은 서비스 창구 = API

이름 없는 택배 상자 같은 시스템 데이터라는 자원을 이름 등으로 구분해서 주고받는 것 = REST

REST API: 자원을 이름으로 구분해서 주고 받는 시스템 창고. 프론트(응용프로그램 역활)와 백(시스템 역활)이 데이터 주고 받기 위한 방법 중 하나. 기능(API)를 잘(REST하게) 만들기~

JSON: 데이터 양식

1.2 데이터베이스와 쿼리

데이터베이스: 데이터들이 모여 있는 큰 창고

테이블: 창고 안에서 데이터 구분해 놓고 정리하기 위한 단위. 표 형식(속성. 값)

레코드: 각 속성에 대한 값이 모여 있는 데이터의 한 단위. tuple. row.

키: 데이터들 간 중복되지 않는 고유한 값 ex> id, phone, id + phone…

쿼리: 데이터베이스에게 물어보는 문장. CRUD(조회, 수정, 생성, 삭제)

1.3 파이썬 기초

간단한 문법(변수 타입 미지정 가능 등)으로 난이도가 비교적 쉽고 활용 가능한 분야가 많음

파이썬 설치: https://www.python.org

테스트 사용: https://colab.research.google.com/

변수: 데이터 담을 수 있는 그릇. 변할 수 있는 데이터 값. 기본 단위.

조건: if / elif / else

user = input("가위/바위/보 중 하나를 내세요. : ")
comp = "바위"

if user == "가위":
  print("당신은 졌습니다.")
elif user == "바위":
  print("비겼습니다.")
else:
  print("당신이 이겼습니다!")

반복: for-in / while

for i in range(0, 5):
    print("Hello world!")
i = 0
while i < 5:
  print("Hollo world!")
  i += 1
while 1 == 1:
  print("Hello world!")
  break

자료구조(list, dictionary, tuple)

자료구조: 자료 저장 방식. 여러 데이터를 배치해서 변수에 담을 때 효율성

list: 순서 있고, 0번째 부터 차례대로 데이터 저장되는 구조.
a = [1, 2, 'a', 'bcd', 123]
len(a)      # 5

a.append('new data')
a           # [1, 2, 'a', 'bcd', 123, 'new data']

nums = [5, 6, 3, 2, 4]
nums[1:3]   # [6, 3]

nums.sort()
nums        # [2, 3, 4, 5, 6]
dictionary: 키와 값.
my_dict = {'Alice':30, 'BoB':60, 35:'fail', 70:'success', 'class':'best'}
my_dict['Alice']         # 30

my_dict['John'] = 50
my_dict.keys()           # dict_keys(['Alice', 'BoB', 35, 70, 'class', 'John'])
my_dict.values()         # dict_values([30, 60, 'fail', 'success', 'best', 50])

for key, val in my_dict.items():
  print(key, val)

Alice 30
BoB 60
35 fail
70 success
class best
John 50
tuple: 간단 구조. 값 단순 저장에 좋음. 한번 생성 시 변경 불가. 성능 효율 좋음
my_data = (30,50)
my_data[0]       # 30

함수: 코드를 묶어놓은 단위체. 어떤 값 넣었을 때 결과 값 도출 하는 코드 묶음.

def sum(a, b):
  result = a + b
  return result

sum(20, 30)         # 50

클래스: 객체/인스턴스 만들기 위한 템플릿. 생성자라는 방식으로 객체/인스턴스 생성. 상속 가능.

클래스 기능 = 메소드. 함수 형태로 작성 되어 있음.

class Human():                   # 클래스
  hp = 100                       # 속성
  name = '기본 이름'

  def walk(self):                # 메소드
    print('I can Walk!')


class Wizard(Human):             # 부모 클래스 상속
  def __init__(self, name):      # 생성자(초기값 설정)
    self.name = name

  def magic(self):
    print("Magic!")

my_cahr = Wizard('난마법사')

print(my_cahr.name)              # 난마법사
print(my_cahr.hp)                # 100
my_cahr.walk()                   # I can Walk!
my_cahr.magic()                  # Magic!

Chapter 2. Django 기본 컨셉 익히기

2.1 Django 시작하기

Django: 파이썬 기반 웹 풀스택 프레임워크. 자유도가 낮음. MTV패턴(모델M. 템플릿T. 뷰V)

설치 방법: [python/setting] 파이썬 – Django 설치 + 셋팅

파이썬 가상 환경 설치

# 원하는 폴더에 가상 환경 설치
py -3.8 -m venv vBook2

파이썬 장고 기본 설치

# pip 최신 버전
python -m pip install --upgrade pip

# 장고
pip install django

# MySQL DB 
pip install mysqlclient

# 장고 CORS
pip install django-cors-headers

# 기본 프로젝트
django-admin startproject back .

DB 생성

create database book2;             #데이터베이스 생성
use book2;                         #book2 DB 사용

2.2 Django 프로젝트 구조 살펴보기

settings.py

import os


DEBUG = True                           # 디버깅 켜기

ALLOWED_HOSTS = ["*"]


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'corsheaders',                      # 장고 CORS
    'back'                              # 만든 앱 추가
]


TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'static')],     # 템플릿 폴더 세팅
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]



DATABASES = {
    'default': {
        #'ENGINE': 'django.db.backends.sqlite3',
        #'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),

        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'book2',       #mysql
        'USER': 'root',        #root
        'PASSWORD': '0000',    #password
        'HOST': 'localhost',
        'PORT': '3306'
    }
}



LANGUAGE_CODE = 'ko'           #추가 - 언어

TIME_ZONE = 'Asia/Seoul'       #변경 - 서울



STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]   #추가
STATIC_URL = '/static/'                                 #추가

파이참 세팅

여기까지 진행하면

이렇게 장고를 쓸 수 있게 활성화 됨

2.3 Django Model 알아보기

python manage.py makemigrations        #모델 변경한 내용 파일로 생성
python manage.py migrate               #모델을 DB 적용
python manage.py createsuperuser

여기까지 진행 하고 관리자 URL http://127.0.0.1:8000/admin/ 접속해보면 [사용자(들)] 부분에 내가 가입한 계정이 보인다.

앱 생성

python manage.py startapp photo      #앱 생성

앱 생성 시 back/settings.py 에 앱 등록해 주어야 함

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'corsheaders',
    'back',
    'photo'
]

photo/models.py

from django.db import models


class Photo(models.Model):
    title = models.CharField(max_length=50)          #CharFiled = 문자열(길이제한)
    author = models.CharField(max_length=50)
    image = models.CharField(max_length=200)
    description = models.TextField()                 #TextFiled = 문자열(길이 제한 없음)
    price = models.IntegerField()                    #InterFiled = 정수

필드 종류:

  • CharFiled : 문자열 (길이 제한 필요)
  • IntergerField : 정수
  • TextField : 문자열 (길이 제한 필요 없음)
  • DataField : 날짜
  • DataTimeField : 날짜 + 시간
  • FileField : 이미지 파일
  • ForeignKey : 외래 키(관계)
  • OneToOneField : 1대1 관계
  • ManyToManyField : 다대다 관계

photo/admin.py

from django.contrib import admin
from .models import Photo

admin.site.register(Photo)                #Admin에 Photo 등록
python manage.py migrate 
python manage.py createsuperuser

여기까지 진행하면 관리자 페이지에 등록 된 [Photos]를 볼 수 있다.

2.4 Django Template 알아보기

Django 템플릿 태그: HTML이 파이썬으로부터 데이터를 바로 넘겨받아서 처리할 수 있는 도구 / {} 형태

2.5 Django View, URL 알아보기

View: 모델을 통해 데이터 접근해서 템플릿에게 답변 보내줌. 코드의 가장 많은 비중.

URL: 라우팅 역활 + 서버로 해당 주소에 할당된 리소스 요청

2.6 서비스 기능 하나씩 구현하기

photo/urls.py

from django.urls import path
from . import views


urlpatterns = [
    path('', views.photo_list, name='photo_list'),
    path('photo/<int:pk>/', views.photo_detail, name='photo_detail'),
    path('photo/new/', views.photo_post, name='photo_post'),
]

back/urls.py

from django.urls import path
from . import views


urlpatterns = [
    path('', views.photo_list, name='photo_list'),
    path('photo/<int:pk>/', views.photo_detail, name='photo_detail'),
    path('photo/new/', views.photo_post, name='photo_post'),
    path('photo/<int:pk>/edit/', views.photo_edit, name='photo_edit'),
]

photo/views.py

from django.shortcuts import render, get_object_or_404, redirect
from .models import Photo
from .forms import PhotoForm


def photo_list(request):
    photos = Photo.objects.all()
    return render(request, 'photo_list.html', {'photos': photos})


def photo_detail(request, pk):
    photo = get_object_or_404(Photo, pk=pk)
    return render(request, 'photo_detail.html', {'photo': photo})


def photo_post(request):
    if request.method == "POST":
        form = PhotoForm(request.POST)
        if form.is_valid():
            photo = form.save(commit=False)
            photo.save()
            return redirect('photo_detail', pk=photo.pk)
    else:
        form = PhotoForm()

    return render(request, 'photo_post.html', {'form': form})


def photo_edit(request, pk):
    photo = get_object_or_404(Photo, pk=pk)
    if request.method == "POST":
        form = PhotoForm(request.POST, instance=photo)
        if form.is_valid():
            photo = form.save(commit=False)
            photo.save()
            return redirect('photo_detail', pk=photo.pk)
    else:
        form = PhotoForm(instance=photo)
    return render(request, 'photo_post.html', {'form' : form})

photo/forms.py

from django import forms
from .models import Photo


class PhotoForm(forms.ModelForm):
    class Meta:
        model = Photo
        fields = (
            'title',
            'author',
            'image',
            'description',
            'price',
        )

static/photo_list.html

<!DOCTYPE html>
<html lang="ko">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no" />
	<title>Photo App</title>
</head>
<body>
	<h1><a href="">사진 목록 페이지</a></h1>
	<h3><a href="{% url 'photo_post' %}">New Photo</a></h3>
	<section>
		{% for photo in photos %}
		<div>
			<h2><a href="{% url 'photo_detail' pk=photo.pk %}">{{ photo.title }}</a></h2>  <!-- 링크 수정 -->
			<img src="{{ photo.image }}" alt="{{ photo.title }}" width="100" />
			<p>{{ photo.author }}, {{ photo.price }}원</p>
		</div>
		{% endfor %}
	</section>
</body>
</html>

static/photo_detail.html

<!DOCTYPE html>
<html lang="ko">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no" />
	<title>Photo App</title>
</head>
<body>
	<h1>{{ photo.title }}</h1>
	<h3><a href="{% url 'photo_edit' pk=photo.pk %}">Edit Photo</a></h3>
	<section>
		<div>
			<img src="{{ photo.image }}" alt="{{ photo.title }}" width="300" />
			<p>{{ photo.description }}</p>
			<p>{{ photo.author }}, {{ photo.price }}원</p>
		</div>
	</section>
</body>
</html>

static/photo_post.html

<!DOCTYPE html>
<html lang="ko">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no" />
	<title>Photo App</title>
</head>
<body>
	<h1><a href="/">홈으로 돌아가기</a></h1>
	<section>
		<h2>New Photo</h2>
		<form method="POST" enctype="multipart/form-data">
			{% csrf_token %} {{ form.as_p }}
			<button type="submit">완료!</button>
		</form>
	</section>
</body>
</html>

관리자 Photo에 값 넣으면 http://127.0.0.1:8000/ 에 해당 값이 자동 불러진다.


Chapter 3. Django로 Todo 목록 웹 서비스 만들기

todo/models.py

from django.db import models


class Todo(models.Model):
    title = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    created = models.DateTimeField(auto_now_add=True)
    complete = models.BooleanField(default=False)
    important = models.BooleanField(default=False)

    def __str__(self):
        return self.title

todo/urls.py

from django.contrib import admin
from django.urls import path
from . import views


urlpatterns = [
    path('', views.todo_list, name='todo_list'),
    path('post/', views.todo_post, name='todo_post'),
    path('<int:pk>/', views.todo_detail, name='todo_detail'),
    path('<int:pk>/edit/', views.todo_edit, name='todo_edit'),
    path('done/', views.done_list, name='done_list'),
    path('done/<int:pk>/', views.todo_done, name='todo_done'),
]

todo/admin.py

from django.contrib import admin
from .models import Todo


admin.site.register(Todo)

todo/forms.py

from django import forms
from .models import Todo


class TodoForm(forms.ModelForm):
    class Meta:
        model = Todo
        fields = ('title', 'description', 'important')

todo/views.py

from django.shortcuts import render, redirect
from .models import Todo
from .forms import TodoForm


def todo_list(request):
    todos = Todo.objects.filter(complete=False)
    return render(request, 'todo_list.html', {'todos': todos})


def todo_detail(request, pk):
    todo = Todo.objects.get(id=pk)
    return render(request, 'todo_detail.html', {'todo': todo})


def todo_post(request):
    if request.method == "POST":
        form = TodoForm(request.POST)
        if form.is_valid():
            todo = form.save(commit=False)
            todo.save()
            return redirect('todo_list')
    else:
        form = TodoForm()

    return render(request, 'todo_post.html', {'form': form})


def todo_edit(request, pk):
    todo = Todo.objects.get(id=pk)
    if request.method == "POST":
        form = TodoForm(request.POST, instance=todo)
        if form.is_valid():
            todo = form.save(commit=False)
            todo.save()
            return redirect('todo_list')
    else:
        form = TodoForm(instance=todo)

    return render(request, 'todo_post.html', {'form': form})


def done_list(request):
    dones = Todo.objects.filter(complete=True)
    return render(request, 'done_list.html', {'dones': dones})


def todo_done(request, pk):
    todo = Todo.objects.get(id=pk)
    todo.complete = True
    todo.save()
    return redirect('todo_list')

static/todo_list.html

<!DOCTYPE html>
<html lang="ko">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no" />
	<title>Todo App</title>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css">
</head>
<body>
	<div class="container">
		<h1 style="padding:30px 0 15px">TODO 목록 앱</h1>
		<p>
			<a href="{% url 'todo_post' %}"><i class="bi-plus"></i>Add Todo</a>
			<a href="{% url 'done_list' %}" class="btn btn-primary" style="float:right">완료한 TODO 목록</a>
		</p>
		<ul class="list-group">
			{% for todo in todos %}
			<li class="list-group-item">
				<a href="{% url 'todo_detail' pk=todo.pk %}">{{ todo.title }}</a>
				{% if todo.important %}
				<span class="badge badge-danger">!</span>
				{% endif %}
				<div style="float:right">
					<a href="{% url 'todo_done' pk=todo.pk %}" class="btn btn-danger">완료</a>
					<a href="{% url 'todo_edit' pk=todo.pk %}" class="btn btn-outline-primary">수정하기</a>
				</div>
			</li>
			{% endfor %}
		</ul>
	</div>
</body>
</html>

static/todo_detail.html

<!DOCTYPE html>
<html lang="ko">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no" />
	<title>Todo App</title>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css">
</head>
<body>
	<div class="container">
		<h1 style="padding:30px 0 15px">TODO 상세보기</h1>
		<div class="container">
			<div class="row">
				<div class="col-md-12">
					<div class="card">
						<div class="card-body">
							<h5 class="card-title">{{ todo.title }}</h5>
							<p class="card-text">{{ todo.description }}</p>
							<a href="{% url 'todo_list' %}" class="btn btn-primary">목록으로</a>
						</div>
					</div>
				</div>
			</div>
		</div>
	</div>
</body>
</html>

static/todo_post.html

<!DOCTYPE html>
<html lang="ko">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no" />
	<title>Todo App</title>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css">
</head>
<body>
	<div class="container">
		<h1 style="padding:30px 0 15px">TODO 추가하기</h1>
		<div class="container">
			<div class="row">
				<div class="col-md-12">
					<div class="card">
						<div class="card-body">
							<form method="POST">
								{% csrf_token %} {{ form.as_p }}
								<button class="submit" class="brn btn-primary">등록</button>
							</form>
						</div>
					</div>
				</div>
			</div>
		</div>
	</div>
</body>
</html>

static/done_list.html

<!DOCTYPE html>
<html lang="ko">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no" />
	<title>Todo App</title>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css">
</head>
<body>
	<div class="container">
		<h1 style="padding:30px 0 15px">DONE 목록</h1>
		<p>
			<a href="{% url 'todo_list' %}" class="btn btn-primary">홈으로</a>
		</p>
		<ul class="list-group">
			{% for done in dones %}
			<li class="list-group-item">
				<a href="{% url 'todo_detail' pk=done.pk %}"> {{ done.title }}</a>
				{% if done.important %}
				<span class="badge badge-danger">!</span>
				{% endif %}
			</li>
			{% endfor %}
		</ul>
	</div>
</body>
</html>

책에는 views.py 부분에 done_list 함수 코드가 이상해서

https://github.com/TaeBbong/drf_for_backend/blob/master/03_%EC%9E%A5%EA%B3%A0Todo/todo/views.py

이 쪽 깃헙을 보니 코드가 달라서 깃헙 쪽 코드를 참고함


Chapter 4. Django REST Framework 컨셉 익히기

4.1 Django REST Framework 시작하기

Django REST Framework: Django를 기반으로 REST API 서버를 만들기 위한 라이브러리로 JSON같은 양식으로 다양한 플랫폼의 클라이언트에게 데이터 제공 가능

pip install djangorestframework
phthon manage.py startapp example

python manage.py migrate 
python manage.py createsuperuser

4.2 Django REST Framework 프로젝트 구조 살펴보기

back/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'corsheaders',
    'rest_framework',
    'example',
]

back/urls.py

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


urlpatterns = [
    path('admin/', admin.site.urls),
    path("example/", include("example.urls"))
]

상태 코드

  • HTTP 200 OK : 데이터 GET 요청 정상
  • HTTP 201 CREATED: 데이터 POST 요청 정상
  • HTTP 206 PARTIAL CONTENT: 데이터 일부 수정하는 PATCH 요청 정상
  • HTTP 400 BAD REQUEST: 잘못된 요청으로 클라이언트가 응답 없음
  • HTTP 401 UNAUTHORIZED: 인증 필요한데 관련 내용이 인증 요청이 없을 때 나타남
  • HTTP 403 FORBIDDEN: 클라이언트가 접근 못하도록 막아 놓은 곳에 요청 왔을 때
  • HTTP 404 NOT FOUND: 클라이언트가 요청 보낸 곳이 잘못된 URL일 때 (리소스가 없을 때)
  • HTTP 500 INTERNAL SERVER ERROR: 서버 코드가 잘못되었을 때
특징Pure DjangoDjango Rest Framework
개발 목적웹 풀스택 개발백엔드 API 서버 개발
개발 결과웹 페이지 포함한 웹 서비스여러 클라이언트에서 사용할 수 있는 API 서버
응답 형태HTMLJSON
다른 파일templatesserializers.py

4.3 도서 정보 API 예제로 Django REST Framework 기초 개념 살펴보기

시리얼라이저(Serialzer): 직렬화. 모델로부터 뽑은 queryset, 즉 모델 인스턴스를 JSON 타입으로 바꾸는 것

DRF(Django REST Framework) 내에 데이터 저장 시 Django 모델을 통해 저장됨. 모델은 데이터 베이스 테이블을 추상화한 개념으로 ORM을 통해 파이썬 문법으로 데이터를 처리할 수 있음 = 파이썬 객체의 형태로 저장.

API는 이런 데이터를 클라이언트에게 보내주는 역할. 단, 그대로 주면 클라이언트는 모르는 파이썬 데이터로 받게 되어 읽지 못하기에 읽을 수 있는 문자열(JSON 등)으로 변환하여 보내주어야 함 = Serialzer

반대로 클라이언트가 DRF로 데이터 보낼 시 JSON 등 문자열 형태로 보내기에 바로 저장 불가. 파이썬 객체 형태여야 함 = 역직렬화 = 디시리얼라이즈(Deserialize)

example/urls.py

from django.urls import path, include
from .views import helloAPI, bookAPI, booksAPI, BookAPI, BooksAPI, BooksAPIMixins, BookAPIMixins

urlpatterns = [
    path("hello/", helloAPI),
    path("fbv/books/", booksAPI),
    path("fbv/book/<int:bid>/", bookAPI),
    path("cbv/books/", BooksAPI.as_view()),
    path("cbv/book/<int:bid>/", BookAPI.as_view()),
    path("mixin/books/", BooksAPIMixins.as_view()),
    path("mixin/book/<int:bid>/", BookAPIMixins.as_view()),
]

example/apps.py

from django.apps import AppConfig


class ExampleConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'example'

example/models.py

from django.db import models

class Book(models.Model):
    bid = models.IntegerField(primary_key=True)                   # 책 id
    title = models.CharField(max_length=50)                       # 책 제목
    author = models.CharField(max_length=50)                      # 저자
    category = models.CharField(max_length=50)                    # 카테고리
    pages = models.IntegerField()                                 # 페이지 수
    price = models.IntegerField()                                 # 가격
    published_date = models.DateField()                           # 출판일
    description = models.TextField()                              # 도서설명

example/serializers.py

from rest_framework import serializers
from .models import Book


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['bid', 'title', 'author', 'category', 'pages', 'price', 'published_date', 'description', ]

시리얼라이저는 파이썬 모델 데이터를 JSON으로 바꿔주는 변환기이기에 모델의 어떤 속성을 JSON에 넣어줄지 선언해줘야 함. 즉 필드 선언해 줘야 함.

기존 BookSerializer(serializers.Serializer) 로 작업하면 def create / def update 전부 다 선언해줘야 하기에 BookSerializer(serializers.ModelSerializer)로 해주면 간단히 해줄 수 있음

ModelSerializer는 모델 내용 기반으로 동작하는 시리얼라이저임

example/views.py

from rest_framework import viewsets, permissions, status, generics, mixins
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view
from rest_framework.generics import get_object_or_404
from .models import Book
from .serializers import BookSerializer

@api_view(['GET'])
def helloAPI(request):
    return Response("hello world!")


class HelloAPI(APIView):
    def get(self, request, format=None):
        return Response("hello world")


@api_view(['GET', 'POST'])
def booksAPI(request):
    if request.method == 'GET':
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    elif request.method == 'POST':
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET'])
def bookAPI(request, bid):
    book = get_object_or_404(Book, bid=bid)
    serializer = BookSerializer(book)
    return Response(serializer.data, status=status.HTTP_200_OK)


class BooksAPI(APIView):
    def get(self, request):
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    def post(self, request):
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class BookAPI(APIView):
    def get(self, request, bid):
        book = get_object_or_404(Book, bid=bid)
        serializer = BookSerializer(book)
        return Response(serializer.data, status=status.HTTP_200_OK)

class BooksAPIMixins(mixins.ListModelMixin, mixins.CreateModelMixin,generics.GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class BookAPIMixins(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = 'bid'

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)
    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

class BooksAPIGenerics(generics.ListCreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

class BookAPIGenerics(generics.RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = 'bid'

python manage.py migrate 
python manage.py createsuperuser






4.3.1 DRF Serializer
4.3.2 DRF FBV, CBV, API View
4.3.3 도서 정보 API 마무리 하기
4.4 Django REST Framework 심화 개념 보충하기
4.4.1 DRF의 다양한 뷰
4.4.2 DRF mixins
4.4.3 DRF generics
4.4.4 DRF Viewset & Router

Chapter 5. 연습 프로젝트 1 : Todo 목록 API 만들기
5.1 Todo 목록 API 시작하기
5.1.1 Django 기반 Todo 목록 웹 서비스 복습
5.1.2 프로젝트 생성하기
5.1.3 Todo 프로젝트 설정하기
5.1.4 Todo 모델 생성하기
5.2 Todo 전체 조회 API 만들기
5.2.1 Todo 전체 조회 시리얼라이저 만들기
5.2.2 Todo 전체 조회 뷰 만들기
5.2.3 Todo 전체 조회 URL 연결하기
5.2.4 Todo 전체 조회 API 테스트하기
5.3 Todo 상세 조회 API 만들기
5.3.1 상세 조회용 Todo 시리얼라이저 만들기
5.3.2 Todo 상세 조회 뷰 만들기
5.3.3 Todo 상세 조회 URL 연결하기
5.3.4 Todo 상세 조회 API 테스트하기
5.4 Todo 생성 API 만들기
5.4.1 생성용 Todo 시리얼라이저 만들기
5.4.2 Todo 생성 뷰 만들기
5.4.3 Todo 생성 URL 연결하기
5.4.4 Todo 생성 API 테스트하기
5.5 Todo 수정 API 만들기
5.5.1 Todo 수정 뷰 만들기
5.5.2 Todo 수정 URL 연결하기
5.5.3 Todo 수정 API 테스트하기
5.6 Todo 완료 API 만들기
5.6.1 Todo 완료 뷰 만들기
5.6.2 Todo 완료 조회 뷰 만들기
5.6.3 Todo 완료 URL 연결하기
5.6.4 Todo 완료 API 테스트하기

Chapter 6. 실전 프로젝트! Django REST Framework + React.js 게시판 만들기
6.1 Hello, 게시판 프로젝트
6.1.1 프로젝트 소개: 게시판
6.1.2 프로젝트 세팅하기
6.2 앱: 회원
6.2.1 Django 기본 User 모델
6.2.2 회원 인증 개념 이해하기
6.2.3 회원가입 구현하기
6.2.4 로그인 구현하기
6.2.5 User 모델 확장 방법
6.2.6 Profile 모델로 User 확장하기(One-To-One)
6.2.7 (TIP) 리액트와 연동하기
6.3 앱: 게시글
6.3.1 게시글 기능 정리
6.3.2 게시글 모델 만들기 & 마이그레이션
6.3.3 시리얼라이저
6.3.4 뷰(CRUD) + 권한
6.3.5 URL
6.3.6 실행
6.3.7 필터링
6.3.8 페이징
6.3.9 좋아요
6.4 앱: 댓글
6.4.1 댓글 기능 정리
6.4.2 댓글 모델 & 마이그레이션
6.4.3 시리얼라이저
6.4.4 뷰
6.4.5 URL
6.4.6 실행
6.5 배포하기
6.5.1 프로젝트의 마무리, 배포
6.5.2 배포를 위한 준비 – 1) 패키지 설치
6.5.3 배포를 위한 준비 – settings.py
6.5.4 기타 필요한 파일들
6.5.5 Heroku 시작하기
6.5.6 깃허브 레포지토리에 올리기
6.5.7 Heroku CLI로 배포하기
6.6 에필로그

Chapter 7. 그 외 도움되는 여러 내용
7.1 예외 응답 포맷 변경하기
7.1.1 기존 예외 처리 방식
7.1.2 커스텀 예외 핸들러 생성하기
7.1.3 settings.py 설정하기
7.1.4 응답 확인해 보기
7.2 DRF TDD 맛보기
7.2.1 TDD
7.2.2 TDD로 작은 프로젝트 시작하기
7.3 drf_yasg로 API 문서화하기
7.3.1 문서화의 필요성
7.3.2 drf_yasg 패키지 적용하기
7.3.3 필드에 설명 붙이기


백엔드를 위한 Django REST Framework with 파이썬 - 도서

기타: https://pixiclue.com/tag/book/