다음 프로젝트가 오랜만에 장고로 업무 관리 프로그램을 만들어야 하는데 혹시 사용할만한 기능이 있나 해서 백엔드를 위한 Django REST Framework with 파이썬 도서를 구매해서 봄
프론트 입장에서 쓴 책만 보다가 백엔드 기준에서 쓴 책은 무엇이 다를까?
웹 서비스: 프론트엔드(데이터 처리 제외한 기능 구현, 웹구조, 디자인) + 백엔드(데이터 처리)가 일정한 규칙을 가지고 데이터 주고 받음
현금이 필요해서 출금 요청 시 은행원이 알아서 꺼내줌. 시스템이 만들어 놓은 서비스 창구 = API
이름 없는 택배 상자 같은 시스템 데이터라는 자원을 이름 등으로 구분해서 주고받는 것 = REST
REST API: 자원을 이름으로 구분해서 주고 받는 시스템 창고. 프론트(응용프로그램 역활)와 백(시스템 역활)이 데이터 주고 받기 위한 방법 중 하나. 기능(API)를 잘(REST하게) 만들기~
JSON: 데이터 양식
데이터베이스: 데이터들이 모여 있는 큰 창고
테이블: 창고 안에서 데이터 구분해 놓고 정리하기 위한 단위. 표 형식(속성. 값)
레코드: 각 속성에 대한 값이 모여 있는 데이터의 한 단위. tuple. row.
키: 데이터들 간 중복되지 않는 고유한 값 ex> id, phone, id + phone…
쿼리: 데이터베이스에게 물어보는 문장. CRUD(조회, 수정, 생성, 삭제)
간단한 문법(변수 타입 미지정 가능 등)으로 난이도가 비교적 쉽고 활용 가능한 분야가 많음
파이썬 설치: 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
자료구조: 자료 저장 방식. 여러 데이터를 배치해서 변수에 담을 때 효율성
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]
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
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!
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 .
create database book2; #데이터베이스 생성 use book2; #book2 DB 사용
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/' #추가
여기까지 진행하면
이렇게 장고를 쓸 수 있게 활성화 됨
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' ]
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 : 다대다 관계
from django.contrib import admin from .models import Photo admin.site.register(Photo) #Admin에 Photo 등록
python manage.py migrate python manage.py createsuperuser
여기까지 진행하면 관리자 페이지에 등록 된 [Photos]를 볼 수 있다.
Django 템플릿 태그: HTML이 파이썬으로부터 데이터를 바로 넘겨받아서 처리할 수 있는 도구 / {} 형태
View: 모델을 통해 데이터 접근해서 템플릿에게 답변 보내줌. 코드의 가장 많은 비중.
URL: 라우팅 역활 + 서버로 해당 주소에 할당된 리소스 요청
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'), ]
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'), ]
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})
from django import forms from .models import Photo class PhotoForm(forms.ModelForm): class Meta: model = Photo fields = ( 'title', 'author', 'image', 'description', 'price', )
<!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>
<!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>
<!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/ 에 해당 값이 자동 불러진다.
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
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'), ]
from django.contrib import admin from .models import Todo admin.site.register(Todo)
from django import forms from .models import Todo class TodoForm(forms.ModelForm): class Meta: model = Todo fields = ('title', 'description', 'important')
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')
<!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>
<!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>
<!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>
<!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
이 쪽 깃헙을 보니 코드가 달라서 깃헙 쪽 코드를 참고함
Django REST Framework: Django를 기반으로 REST API 서버를 만들기 위한 라이브러리로 JSON같은 양식으로 다양한 플랫폼의 클라이언트에게 데이터 제공 가능
pip install djangorestframework phthon manage.py startapp example python manage.py migrate python manage.py createsuperuser
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'corsheaders', 'rest_framework', 'example', ]
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 Django | Django Rest Framework |
---|---|---|
개발 목적 | 웹 풀스택 개발 | 백엔드 API 서버 개발 |
개발 결과 | 웹 페이지 포함한 웹 서비스 | 여러 클라이언트에서 사용할 수 있는 API 서버 |
응답 형태 | HTML | JSON |
다른 파일 | templates | serializers.py |
시리얼라이저(Serialzer): 직렬화. 모델로부터 뽑은 queryset, 즉 모델 인스턴스를 JSON 타입으로 바꾸는 것
DRF(Django REST Framework) 내에 데이터 저장 시 Django 모델을 통해 저장됨. 모델은 데이터 베이스 테이블을 추상화한 개념으로 ORM을 통해 파이썬 문법으로 데이터를 처리할 수 있음 = 파이썬 객체의 형태로 저장.
API는 이런 데이터를 클라이언트에게 보내주는 역할. 단, 그대로 주면 클라이언트는 모르는 파이썬 데이터로 받게 되어 읽지 못하기에 읽을 수 있는 문자열(JSON 등)으로 변환하여 보내주어야 함 = Serialzer
반대로 클라이언트가 DRF로 데이터 보낼 시 JSON 등 문자열 형태로 보내기에 바로 저장 불가. 파이썬 객체 형태여야 함 = 역직렬화 = 디시리얼라이즈(Deserialize)
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()), ]
from django.apps import AppConfig class ExampleConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'example'
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() # 도서설명
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는 모델 내용 기반으로 동작하는 시리얼라이저임
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 필드에 설명 붙이기