일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- codingtest
- DFS
- Flutter
- 분할정복
- Algorithm
- 코테
- 코딩테스트
- BAEKJOON
- cos pro 1급
- 동적계획법
- DFS와BFS
- vuejs
- cos
- 백준
- issue
- AndroidStudio
- 동적계획법과최단거리역추적
- 알고리즘
- cos pro
- android
- 안드로이드
- 코드품앗이
- 파이썬
- 개발
- Vue
- DART
- C++
- 안드로이드스튜디오
- Python
- django
- Today
- Total
Development Artist
[웹 서비스 A-Z][Django] #12 ORM 본문
장고의 장점 중 하나로써, powerful한 ORM을 들 수 있습니다.
ORM은 Object Relational Mapping(객체-관계-매핑)의 약자입니다. 즉, 객체와 데이터베이스의 관계를 매핑해주는 도구입니다.
기본적으로 데이터베이스의 데이터를 조작하기 위해 쿼리를 날립니다.
가령, 데이터베이스로 부터 데이터를 가져 온다고하면, 이것을 직접 날려서 데이터를 가져오는 대신 ORM을 통해 함수로 요청하고 ORM이 데이터를 가져와서 객체화를 시킬 수 있습니다.
Django에서는 QuerySet API를 제공하는데요, https://docs.djangoproject.com/en/3.2/ref/models/querysets/ 해당 링크에서 자세히 확인할 수 있습니다.
지난 시간 ‘Book’, ‘BookInstance’, ‘Author’, ‘Genre’, ‘Language’ 모델을 만들고 데이터베이스에 테이블까지 만들었습니다. 이제 데이터를 넣어보도록 하겠습니다.
지난 예제를 통해 우리는 API를 하나 가지고 있습니다.
- GET, testone/access-test
그리고 이번 시간에는 CRUD(Create-Read-Update-Delete) 관련 API를 만들어 보겠습니다.
도서관 관리 시스템이 있습니다.
- POST, testone/book
- 사서가 새로운 책이 입고되서 등록을 하려고 합니다.
- GET, testone/books/<book_id>
- 책에 대한 정보를 조회하고 싶습니다.
- PUT, testone/books/<book_id>
- 책 정보를 변경하려고 합니다. 가령, 장르를 바꾸고 싶습니다.
- DELETE, testone/books/<book_id>
- 해당 책이 오래되어 취급을 하지 않기로 하였습니다.
- soft DELETE와 hard DELETE 중, hard DELETE를 해보겠습니다. 개인적으로는 soft DELETE를 지향합니다.
그리고 추가적으로 저자 등록, 언어 등록, 장르 등록 API를 만들겠습니다.
- POST, testone/author/
- 저자를 등록합니다. 책을 누가 썼는지 알 수 있습니다.
- POST, testone/language/
- 언어를 등록합니다. 책이 어떤 언어로 되어있는지 알려줄 수 있습니다.
- POST, testone/genre/
- 장르를 등록합니다. 책이 어떤 장르인지 알려줄 수 있습니다.
testone/urls.py
from django.urls import path
from .views import access_test, get_book, create_book, update_book, create_author, create_language, create_genre
app_name = 'testone'
urlpatterns = [
path('access-test/', access_test, name="access_test"),
path('books/<book_id>', get_book, name="get_book"),
path('books/<book_id>/', update_book, name="update_book"),
path('book/', create_book, name="create_book"),
path('author/', create_author, name="create_author"),
path('language/', create_language, name="create_language"),
path('genre/', create_genre, name="create_genre")
]
testone/views.py > get_book
@api_view(('GET',))
@renderer_classes((TemplateHTMLRenderer, JSONRenderer))
def get_book(request, book_id):
try:
res_data = {}
if request.method == 'GET':
msg = 'GET BOOK INFO'
res_data['message'] = msg
book_info = Book.objects.get(pk=int(book_id))
res_data['title'] = book_info.title
res_data['summary'] = book_info.summary
res_data['isbn'] = book_info.isbn
gen_list = []
book_genre = book_info.genre.all()
for gen in book_genre:
gen_list.append(gen.name)
res_data['genre'] = gen_list
if not book_info.author:
res_data['author'] = None
else:
res_data['author'] = book_info.author.first_name + ' ' + book_info.author.last_name
if not book_info.language:
res_data['language'] = None
else:
res_data['language'] = book_info.language.name
return Response(data=res_data, status=status.HTTP_200_OK)
except Exception as ex:
print('ex:', ex)
return Response(status=status.HTTP_400_BAD_REQUEST)
testone/views.py > update_book
@api_view(('PUT', 'DELETE'))
@renderer_classes((TemplateHTMLRenderer, JSONRenderer))
def update_book(request, book_id):
res_data = {}
try:
if request.method == 'PUT':
msg = 'UPDATE BOOK INFO'
res_data['message'] = msg
book_info = Book.objects.get(pk=int(book_id))
book_info.summary = request.data.get('summary')
book_info.save()
elif request.method == 'DELETE':
msg = 'DELETE BOOK INFO'
res_data['message'] = msg
book_info = Book.objects.get(pk=int(book_id))
book_info.genre.remove(*book_info.genre.all())
book_info.delete()
return Response(data=res_data, status=status.HTTP_200_OK)
except Exception as ex:
print('ex:', ex)
return Response(status=status.HTTP_400_BAD_REQUEST)
testone/views.py > create_book
@api_view(('POST',))
@renderer_classes((TemplateHTMLRenderer, JSONRenderer))
def create_book(request):
try:
res_data = {}
book_title = request.data.get('title')
book_summary = request.data.get('summary')
book_isbn = request.data.get('isbn')
book_author_first_name = request.data.get('first_name')
book_author_last_name = request.data.get('last_name')
book_language = request.data.get('language')
book_genre = request.data.get('genre').split(',')
author = Author.objects.get(first_name=book_author_first_name, last_name=book_author_last_name)
language = Language.objects.get(name=book_language)
book_info = Book.objects.create(
title=book_title,
summary=book_summary,
isbn=book_isbn,
author=author,
language_id=language.pk
)
for gen in book_genre:
genre = Genre.objects.get(name=gen.strip())
book_info.genre.add(genre)
msg = 'POST BOOK INFO'
res_data['message'] = msg
res_data['book_id'] = book_info.pk
return Response(data=res_data, status=status.HTTP_200_OK)
except Exception as ex:
print('ex:', ex)
return Response(status=status.HTTP_400_BAD_REQUEST)
testone/views.py > create_author
@api_view(('POST',))
@renderer_classes((TemplateHTMLRenderer, JSONRenderer))
def create_author(request):
try:
res_data = {}
author_last_name = request.data.get('last_name')
author_first_name = request.data.get('first_name')
author_info = Author.objects.create(
last_name=author_last_name,
first_name=author_first_name,
)
msg = 'POST AUTHOR INFO'
res_data['message'] = msg
res_data['author_id'] = author_info.pk
return Response(data=res_data, status=status.HTTP_200_OK)
except Exception as ex:
print('ex:', ex)
return Response(status=status.HTTP_400_BAD_REQUEST)
testone/views.py > create_language
@api_view(('POST',))
@renderer_classes((TemplateHTMLRenderer, JSONRenderer))
def create_language(request):
try:
res_data = {}
language_name = request.data.get('name')
language_info = Language.objects.create(
name=language_name
)
msg = 'POST LANGUAGE INFO'
res_data['message'] = msg
res_data['language_id'] = language_info.pk
return Response(data=res_data, status=status.HTTP_200_OK)
except Exception as ex:
print('ex:', ex)
return Response(status=status.HTTP_400_BAD_REQUEST)
testone/views.py > create_genre
@api_view(('POST',))
@renderer_classes((TemplateHTMLRenderer, JSONRenderer))
def create_genre(request):
try:
res_data = {}
genre_name = request.data.get('name')
genre_info = Genre.objects.create(
name=genre_name
)
msg = 'POST GENRE INFO'
res_data['message'] = msg
res_data['genre_id'] = genre_info.pk
return Response(data=res_data, status=status.HTTP_200_OK)
except Exception as ex:
print('ex:', ex)
return Response(status=status.HTTP_400_BAD_REQUEST)
위와 같이 작성을 하고 Postman을 활용하여 API를 날려보겠습니다.
Postman에 TESTONE 컬렉션 아래, 7개의 request를 만들었습니다. 하나씩 써보도록 합시다.
우선 테스트를 위한 더미 저자,언어,장르를 등록하도록 하겠습니다.
임의의 저자 등록은 아래와 같이 진행합니다. 8명의 저자를 등록하도록 하겠습니다.
임의의 언어 등록은 아래와 같이 진행합니다. 18개의 언어를 등록하도록 하겠습니다.
임의의 장르 등록은 아래와 같이 진행합니다. 6개의 장르를 등록하도록 하겠습니다.
그리고 난 뒤, 데이터베이스를 확인해 봅니다. 이전 시간 Addtional을 이후로 DBeaver를 사용합니다.
잘 들어간 것을 확인할 수 있습니다.
자, 이제 책을 등록해봅시다.
- title : 책 제목
- summary : 요약
- isbn : Internatinoal Standart Book Number, 책 고유 번호
- last_name : 저자의 성
- first_name : 저자의 이름
- language : 책 언어
- genre : 책 장르
Genre는 Book과 Many-To-Many 관계로 만들었기 때문에 여러가지를 받을 수 있습니다.
다음과 같이 책들이 잘 들어간 것을 확인했습니다. 그리고 ManyToMany의 관계의 데이터를 저장하는 테이블(testone_book_genre)에도 값이 생성이 됩니다.
이제 책 조회, 수정, 삭제를 해보도록 하겠습니다.
먼저 책 조회입니다. book_id=11로 조회하겠습니다. 여러분들은 각자의 id에 맞는 것을 조회해보시길 바랍니다.
다음과 같이 Response에 값들이 잘 보이네요.
수정은 단순히 summary 값을 ‘테스트 수정했어요.’로 바꾸는 작업 입니다.
book_id=7을 수정하도록 하겠습니다.
다음과 같이 수정이 된 것을 확인할 수 있습니다.
다음은 삭제인데요, Hard Delete로 구현을 했습니다. 이 경우, row가 완전히 삭제되는데요, Soft Delete와의 차이는 https://abstraction.blog/2015/06/28/soft-vs-hard-delete#recommendation 에서 잘 설명하고 있습니다.
단순히 실습이기에 Hard Delete로 구현하였지만, Soft Delete을 추천드립니다.
book_id=9을 지워보도록 하겠습니다.
book_id=9의 데이터가 삭제되었습니다!
오늘 시간에는 아주 기본적인 ORM 메소드를 사용해보았습니다.
코드에 예외처리 등 허술한 점이 매우 많습니다.
따라서 상용화시 추가적으로 고려할 부분들이 많은 점 참고 부탁드립니다.
Additional
이번 시간에 만든 API는 FBV 기반으로 만들었는데요, CBV 기반으로 바꾼다면 어떻게 될까요?
하나의 Class안에 C.R.U.D를 모두 담아 하나의 View로 사용이 가능합니다.
한번 어떤 것이 더 좋을지 고민해 보면 좋을 것 같습니다.
(개인적으로 다음 프로젝트는 CBV를 기반으로 진행해볼까 합니다.)
사전 API 작업을 진행했던 이유는 무엇일까요? DBeaver의 Book 엔티티 관계도를 보면 알 수 있습니다.
Book은 author_id, language_id 그리고 Genre를 필요로 합니다. 따라서, Book 데이터를 만들때Author, Language, Genre의 데이터를 넣어줘야하기 때문에 먼저 생성을 해준 것입니다.
'Project_Personal' 카테고리의 다른 글
[웹 서비스 A-Z][Vuejs] #14 API (0) | 2023.03.21 |
---|---|
[웹 서비스 A-Z][Vuejs] #13 Props, Emit (0) | 2023.03.20 |
[웹 서비스 A-Z][Django] #11 Database (0) | 2023.03.15 |
[웹 서비스 A-Z][Django] #10 CORS (0) | 2023.03.13 |
[웹 서비스 A-Z][Django] #9 Design Pattern (0) | 2023.03.10 |