PJT08 (04/22)
DB 설계를 활용한 REST API 설계
[TOC]
역할 나누기
- 페어 프로그래밍
-
역할 교체 : 50분 진행 / 10분 휴식 패턴
-
네비게이터 드라이버 시작 시각 끝난 시각 김수빈 김민정 13:00 14:00 김민정 김수빈 14:00 15:00 김수빈 김민정 15:00 16:00 김민정 김수빈 17:00 18:00
-
[1] 13:00 - 14:00
네비게이터: 김수빈, 드라이버: 김민정
[드라이버]
기본 환경 설정
-
가상환경 설정 및 라이브러리 설치
$ python -m venv venv $ source venv/Scripts/activate $ pip install -r requirements.txt
-
fixtures zip 파일 압축 파일 풀기
-
README.md, README-김민정.md, README-김수빈.md 만들기
-
프로젝트(pjt08), 앱(movies) 생성 및 등록
$ django-admin startproject pjt08 . $ python manage.py startapp movie
- pjt08/settings.py
INSTALLED_APPS = [ # Local app 'movies', # Third-party 'rest_framework', # Django apps 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
-
movies/fixtures 폴더 만들고 json 파일 3개 이동하기
-
git push
-
오류
-
json 파일 받아오기
$ python manage.py migrate $ python manage.py loaddata actors.json movies.json reviews.json
-
원인: 아직 model이 만들어지지 않아서 json 파일을 받아올 수 없었다.
-
해결방법: model 먼저 만들고 json 파일 받아오기
-
-
학습한 내용: 기본 환경 세팅
-
어려웠던 부분:
- 항상 프로젝트 처음에는 뭐부터 시작해야할지 헷갈린다.
- json 파일을 load했는데 에러가 나서 당황했고, 에러 코드를 읽어본 결과 모델을 먼저 만들어야 한다는 것을 알 수 있었다.
-
새로 배운 것들 및 느낀점: 프로젝트 시작 전 좀 더 차분하게 해야할 일들을 정리하는 연습이 필요할 것 같다.
Model 만들기
-
movies/models.py
from django.db import models # Create your models here. class Actor(models.Model): name = models.CharField(max_length=100) class Movie(models.Model): actors = models.ManyToManyField(Actor, related_name='movies') title = models.CharField(max_length=100) overview = models.TextField() release_date = models.DateTimeField() poster_path = models.TextField() class Review(models.Model): movie = models.ForeignKey(Movie, on_delete=models.CASCADE) title = models.CharField(max_length=100) content = models.TextField()
-
확인
$ python manage.py makemigrations $ python manage.py migrate
db.sqlite3 보고 명세에 맞게 만들어졌는지 확인
- 학습한 내용: 명세에 맞게 Actor, Movie, Review 모델 만들기
- 어려웠던 부분: 1:N 관계 설정은 여러 번 연습해봐서 익숙했지만, M:N 관계는 아직 어려워서 수업 자료를 보면서 했다.
- 새로 배운 것들 및 느낀점: models.py에서 1:N과 M:N 관계를 설정하는 것이 생각보다 어렵지 않았고 연습을 더 해서 자료를 보지 않고도 코드를 짤 수 있도록 해야겠다.
Admin
- movies/admin.py
from django.contrib import admin
from .models import Actor, Movie, Review
# Register your models here.
admin.site.register(Actor)
admin.site.register(Movie)
admin.site.register(Review)
- 학습한 내용: 정의한 모델 Actor, Model, Review를 Admin site에 등록
- 어려웠던 부분: 어려운 부분은 없었다.
- 새로 배운 것들 및 느낀점: 이제 admin 기능은 완전히 익힌 것 같다.
json 파일 load
$ python manage.py loaddata actors.json movies.json reviews.json
- db.sqlite3 확인하기
- admin 페이지에서도 확인해보기
$ python manage.py createsuperuser
- 확인 완료
- 학습한 내용: 데이터 조회는 JSON 데이터 타입을 따르고, fixtures 사용
- 어려웠던 부분: 초반에 model을 설정하지 않고 load를 받아서 에러가 났었고, 모델 수정 이후 load하니까 문제 없이 잘 받아졌다.
- 새로 배운 것들 및 느낀점: fixtures는 처음 다뤄봐서 사용방법이 조금 헷갈리는데, 잘 정리해 두어야겠다.
url 분리
-
pjt08/urls.py
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/v1/', include('movies.urls')), ]
-
movies/urls.py
from django.urls import path from . import views urlpatterns = [ ]
-
학습한 내용: project와 application url 분리
-
어려웠던 부분: 이 부분을 처음 setting 때 해줬어야 했는데, 순서가 뒤섞였다.
-
새로 배운 것들 및 느낀점: 코드 작성을 빠뜨리는 일이 없도록 비슷한 코드들은 한 번에 작성하도록 주의해야겠다.
Serializer
-
movies/serializer.py
from rest_framework import serializers from .models import Actor, Movie, Review class ActorListSerializer(serializers.ModelSerializer): class Meta: model = Actor fields = '__all__' class ActorSerializer(serializers.ModelSerializer): class Meta: model = Actor fields = '__all__' class MovieListSerializer(serializers.ModelSerializer): class Meta: model = Movie fields = '__all__' class MovieSerializer(serializers.ModelSerializer): class Meta: model = Movie fields = '__all__' class ReviewListSerializer(serializers.ModelSerializer): class Meta: model = Reiew fields = '__all__' class ReviewSerializer(serializers.ModelSerializer): class Meta: model = Review fields = '__all__'
-
serializers 폴더를 만들고 각 파일들 분리
actor.py
,movie.py
,review.py
- ModelSerializer 재정의 필요
-
movies/serializers/actor.py
from rest_framework import serializers from .movie import MovieSerializer from ..models import Actor, Movie class ActorListSerializer(serializers.ModelSerializer): class Meta: model = Actor fields = '__all__' class ActorSerializer(serializers.ModelSerializer): class MovieSerializer(serializers.ModelSerializer): class Meta: model = Movie fields = ('title',) movies = MovieSerializer(many=True, read_only=True) class Meta: model = Actor fields = '__all__'
-
movies/serializers/movie.py
from rest_framework import serializers from movies.serializeers.review import ReviewListSerializer from ..models import Movie, Actor class MovieListSerializer(serializers.ModelSerializer): class Meta: model = Movie fields = '__all__' class MovieSerializer(serializers.ModelSerializer): class ActorSerializer(serializers.ModelSerializer): class Meta: model = Actor fields = ('name',) actors = ActorSerializer(many=True, read_only=True) review_set = ReviewListSerializer(many=True, read_only=True) class Meta: model = Movie fields = '__all__'
-
학습한 내용: 명세에 나와있는 응답 예시를 참고해서 serializers 작성
-
어려웠던 부분:
- 참조, 역참조를 할 때 처음에는 ModelSerializer들의 순서를 바꿔가면서 하려고 했는데, 여러 번 겹치는 게 생길 것 같아서 파일을 새로 만들고 각각 분리해서 작성했다.
- 생성하는 serializer는 6개인데 응답 예시를 보면 출력 형태가 다양했고, 어떻게 해야할지 많이 고민했다. 교수님께서 ModelSerializer를 재정의하는 방법을 알려주셔서 해결할 수 있었다.
-
새로 배운 것들 및 느낀점:
- 앞으로 할 프로젝트들에서도 이번처럼 많은 serializer 파일들이 생기고 관계도 다양할텐데 serializers 폴더를 만들고 분리하는 방법을 잘 익혀둬야겠다.
- ModelSerializer를 재정의하면 serializer를 여러 개 만들지 않고도 출력을 손쉽게 바꿀 수 있다.
[네비게이터]
- 평소 하던 프로젝트와 달라서 초반 진행에 혼란을 겪음.
- 가상환경 설치 시: 가상환경과 패키지 설치 후 속도 저하. 원인이 디스코드인지 아닌지 팀원의 세팅에서도 비교해볼 예정
- serializers 작성:
- serializers에서 고민하고 있던 부분을 교수님께서 깔꼼하게 설명해주셨다. 처음부터 폴더에 넣어서 파일을 분리하고 mtom, 1ton 관계를 정의.
- 어디가 혼란스러웠는가?: 전체적으로 다,,,, 교재와 실습했던 파일들 다 참고하면서 혼란이 가중되던 차에 내려온 천사같은 설명.
[2] 14:00 - 15:00
네비게이터: 김민정, 드라이버: 김수빈
[드라이버]
-
2시 부터
-
속도 차이 나는지 비교: 느려졌다!
-
팀원도 환경 세팅
-
$ python -m venv venv $ source venv/Scripts/activate $ pip install -r requirements.txt $ python manage.py migrate $ python manage.py loaddata movies/actors.json movies/movies.json movies/reviews.json
serializers/review.py
-
from rest_framework import serializers from ..models import Review, Movie class ReviewListSerializer(serializers.ModelSerializer): class Meta: model = Review fields = ('title', 'content',) class ReviewSerializer(serializers.ModelSerializer): class MovieListSerializer(serializers.ModelSerializer): class Meta: model = Movie fields = ('title',) movie = MovieListSerializer(read_only=True) class Meta: model = Review fields = '__all__' # read_only_fields = ('title',)
- 혹시나 쓸 일이 있을까 싶어 실습 파일에서 참고한 read_only_fields를 주석으로 남겨둠
urls, views
- urls.py, views.py 작성 후 Postman으로 점검
- serializers보다 훨씬 수월하게 진행됨. 반복되는 패턴이 있어서 그런 듯.
path('actors/', views.actor_list),
1. -
views.py
-
# 어마무시한 import들 from django.shortcuts import get_list_or_404, get_object_or_404 from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response from .models import Actor, Movie, Review from .serializeers.actor import ActorListSerializer, ActorSerializer from .serializeers.movie import MovieListSerializer, MovieSerializer from .serializeers.review import ReviewListSerializer, ReviewSerializer # Create your views here. @api_view(['GET']) def actor_list(request): actors = get_list_or_404(Actor) serializer = ActorListSerializer(actors, many=True) return Response(serializer.data)
-
path('actors/<int:actor_pk>/', views.actor_detail),
2. -
views.py
-
@api_view(['GET']) def actor_detail(request, actor_pk): actor = get_object_or_404(Actor, pk=actor_pk) serializer = ActorSerializer(actor) return Response(serializer.data)
get_object_or_404
의 두 번째 인자를 빼먹을 뻔 함
-
path('movies/', views.movie_list),
3. -
views.py
-
@api_view(['GET']) def movie_list(request): movies = get_list_or_404(Movie) serializer = MovieListSerializer(movies, many=True) return Response(serializer.data)
-
path('movies/<int:movie_pk>/', views.movie_detail),
4. -
views.py
-
@api_view(['GET']) def movie_detail(request, movie_pk): movie = get_object_or_404(Movie, pk=movie_pk) serializer = MovieSerializer(movie) return Response(serializer.data)
-
path('reviews/', views.review_list),
5. -
views.py
-
@api_view(['GET']) def review_list(request): reviews = get_list_or_404(Review) serializer = ReviewListSerializer(reviews, many=True) return Response(serializer.data)
-
path('reviews/<int:review_pk>/', views.review_detail),
6. -
views.py
-
@api_view(['GET', 'PUT', 'DELETE']) def review_detail(request, review_pk): review = get_object_or_404(Review, pk=review_pk) if request.method == 'GET': serializer = ReviewSerializer(review) return Response(serializer.data) if request.method == 'PUT': serializer = ReviewSerializer(review, request.data) if serializer.is_valid(raise_exception=True): serializer.save() return Response(serializer.data) if request.method == 'DELETE': review.delete() data = { 'delete': f'review {review_pk} is deleted.' } return Response(data, status=status.HTTP_204_NO_CONTENT)
- 교재와 같이 상태 코드도 지정
-
-
MovieSerializer을 재정의 해주지 않고 read_only_fields를 써서 id만 나옴
class ReviewSerializer(serializers.ModelSerializer): class Meta: model = Review fields = '__all__' read_only_fields = ('title',)
class ReviewSerializer(serializers.ModelSerializer): class MovieListSerializer(serializers.ModelSerializer): class Meta: model = Movie fields = ('title',) movie = MovieListSerializer(read_only=True) class Meta: model = Review fields = ('id', 'title', 'content',) read_only_fields = ('title',)
- 원하는 필드만 보이게 inner class로 재정의
- 여기서는 fields를 전체가 아니게 정의 해버림
class ReviewSerializer(serializers.ModelSerializer): class MovieListSerializer(serializers.ModelSerializer): class Meta: model = Movie fields = ('title',) movie = MovieListSerializer(read_only=True) class Meta: model = Review fields = '__all__'
시행착오 끝에 성공.
fields = '__all__'
로 변경 후 read_only_fields는 지워버렸다.재정의를 하지 않고 read_only_fields로 title만 가져오는 걸 시도해 보았으나 실패. 방법이 있다면? 너무 궁금합니다.
[네비게이터]
- 학습한 내용: status 설정, related_name, read_only_fields, data=request.data 부분 토의
- 어려웠던 부분: serializers/review.py
- 에러: 출력에 movie title이 나오지 않고 movie id가 나옴
- 원인: read_only_fields --- 글 쓰고 나서 유효성 검사할 때 필요
- 해결: ModelSerializer 재정의
- 새로 배운 것들 및 느낀점:
- 이번 시간에는 네비게이터로 활동을 했는데 중간중간 서로 헷갈리는 부분이 있었고, 토의를 통해서 해결할 수 있었다. 드라이버님과 대화를 하면서 프로젝트를 더 탄탄하게 짤 수 있어서 좋았다.
method=='GET'
일 때는 status 설정 안해도 된다.method=='POST'
일 때 instance를 쓰면data=request.data
대신request.data
로 적어도 된다.- M:N에서는 related_name('movies')를 설정해주고, 1:N에서는 설정하지 않는다('review_set').
[3] 15:00 - 16:00
네비게이터: 김수빈, 드라이버: 김민정
[드라이버]
-
데이터 받아오기
$ git pull $ python manage.py loaddata movies/actors.json movies/movies.json movies/reviews.json
create_view 기능
-
urls.py
urlpatterns = [ ... path('movies/<int:movie_pk>/reviews/', views.create_review), ]
-
views.py
@api_view(['POST']) def create_review(request, movie_pk): movie = get_object_or_404(Movie, pk=movie_pk) serializer = ReviewSerializer(data=request.data) if serializer.is_valid(raise_exception=True): serializer.save(movie=movie) return Response(serializer.data, status=status.HTTP_201_CREATED)
- 학습한 내용: 리뷰 생성 기능 구현
- 어려웠던 부분: POST와 PUT 코드 구현이 헷갈렸다.
- 새로 배운 것들 및 느낀점:
- POST: 지난 정보를 받아올 필요가 없기 때문에
ReviewSerializer(data=request.data)
- PUT: 지난 정보를 받아와야 하기 때문에
ReviewSerializer(review, request.data)
- POST: 지난 정보를 받아올 필요가 없기 때문에
git push
-
git push (commit name:
project beta version
) -
동작 재확인
-
커밋 이름 수정 (commit name:
project completed
)-
수정 전
$ git log --oneline 68e8b73 (HEAD -> master, origin/master) project beta version 184e3ce review delete completed 1461b93 serializer separation & writing 5d2be6d settings
-
git commit --amend
-
수정 후
$ git log --oneline f57e8f0 (HEAD -> master) project completed 184e3ce review delete completed 1461b93 serializer separation & writing 5d2be6d settings
-
-
git pull 해서 merge 하고 test 해보기
- 학습한 내용: 프로젝트 완료 후 업로드(push), commit 이름 바꾸기
- 어려웠던 부분:
- 이름을 바꾸고 나서 push를 하면 원격저장소와 로컬저장소의 내용이 다르다고 에러가 나온다.
- git pull을 받으면 merge가 되고
- 새로운 작업을 하나 하고 git push를 하면 변경사항들이 모두 반영된다.
- 새로 배운 것들 및 느낀점:
git push
했던 내용들을 변경하고 싶을 때 어떻게 하면 되는지 연습할 수 있었다.
[네비게이터]
-
페이지 하나씩 다시 띄워서 명세서와 비교
-
완료 후 커밋 메세지 수정 (project beta version -> project completed)
-
커밋 메세지 수정 후 레포지토리에 반영이 되지 않았다. (push 오류)
-
merge 후 다시 push => 성공
-
README 다 쓰고 원인 파악해보기!
-
git commit --amend
-
커밋 수정 전 이미 해당 커밋을 push한 경우 수정 후에 error 발생한다고 한다.
-
git push --force
orgit push -f
옵션으로 푸시가 가능-
"Don't do it." from stackoverflow
-
-
-
git push -f (이하 생략)
는 위험할까봐 merge 후 재 push -
그렇다면
git reset --soft
를 사용했어야? 하나? 아마도,,
[마무리] 페어 프로그래밍 소감
-
김민정
- 이번이 두 번째 페어프로그래밍 프로젝트였는데, 지난 번보다 훨씬 페어 프로그래밍에 익숙해진 것 같다.
- 페어님과 서로 토의하면서 프로젝트를 하니까 서로 빠뜨린 부분도 금방 찾고 헷갈리는 부분도 쉽게 해결할 수 있었다.
- 프로젝트 처음에 뭐부터 시작해야 할지 많이 헷갈렸고, 그래서 프로젝트 중간중간 빠뜨린 부분들을 추가해줬는데 좀 더 차분하게 순서를 정리하고 프로젝트를 하는 연습을 해야겠다.
-
김수빈
- 페어님 덕에 진행이 잘 된 것 같다. 혼자였으면 토요일까지 진행했을 것이다.
긴급,,오타 발견
serializer 폴더명을 serializeer로 지정했었다. vscode에서 폴더명을 바꾸려는데 계속 에러 뜸 => 그냥 윈도우상에서 폴더명 변경도 시도했으나 어디서 파일이 열려있다고 거절당했다. 왜..? 어디서?
그냥 폴더를 새로 만들어서 이동시킴
이번에는 저번보다 훨씬 수월했으나 종종 찾아오는 위기와 이상한데 꽂힌 호기심으로 인해 시간을 좀 썼다. 저의 방향을 끌어주신 팀장님께 감사드린다.