1) StringRelatedField
StringRelatedField(many=True, read_only=True)는 model에서 정의된 __str__ 내용을 표시한다.2) SlugRelatedField
.SlugRelatedField(many=True, read_only=True, slug_field='column_name')는 model에서 정의된 column들 중에서 slug_field에 있는 값을 표시되게 해 준다. 만약 pub_date를 slug_field로 정했다면 questions에는 pub_date의 값이 들어가게 된다.3) HyperlinkedRelatedField
.HyperlinkedRelatedField(many=True, read_only=True, view_name = 'urls.py에서 입력한 url의 name')는 하이퍼링크로 이동할 수 있게 해 주며 작성한 view_name의 위치로 연결해 준다.
questions에 작성자가 작성한 질문의 링크로 이동할 수 있도록 나오게 된다.1) 기존 serializer 내부에 넣을 serializer을 생성
QuestionSerializer 내부에 Choice를 model로 두는 serializer을 표시해 주기 위해 ChoiceSerializer를 serializers.py에 정의해 준다.class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = ['choice_text', 'votes']
2) serializer에 중첩된 serializer를 호출
class QuestionSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source = 'owner.username')
choices = ChoiceSerializer(many = True, read_only=True)
class Meta:
model = Question
fields = ['id', 'question_text', 'pub_date', 'owner', 'choices']
3) 이때 models.py에서 호출할 이름을 기존 model에 추가
related_name으로 호출할 수 있다.class Choice(models.Model):
#Question의 unique id 저장
question = models.ForeignKey(Question, related_name ='choices', on_delete = models.CASCADE) #related_name 추가
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return f'{self.question.question_text}{self.choice_text}'
1) 투표를 위한 model 생성
model이 가지고 있기 때문에 따로 field를 정의해 주는 것이 아니라 ForeignKey를 통해서만 호출한다.models.py에 생성해 준다.from django.db import models
from django.utils import timezone
import datetime
from django.contrib import admin
from django.contrib.auth.models import User
class Vote(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice = models.ForeignKey(Choice, on_delete=models.CASCADE)
voter = models.ForeignKey(User, on_delete=models.CASCADE)
models.UniqueConstraint(fields=['제약을 걸 field'], name = '지정할 이름')를 사용해 준다. class Vote(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice = models.ForeignKey(Choice, on_delete=models.CASCADE)
voter = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(fields=['question', 'voter'], name ='unique_voter_for_questions')
]
migration을 해 주어야 한다.
2) vote_count를 바로 반영되도록 serializer 수정
serializers.SerializerMethodField()를 사용하고, obj의 vote_set의 수를 바로 가지고 오도록 구현했다.class ChoiceSerializer(serializers.ModelSerializer):
votes_count = serializers.SerializerMethodField()
class Meta:
model = Choice
fields = ['choice_text', 'votes_count']
def get_votes_count(self, obj):
return obj.vote_set.count()
3) 투표를 받기 위한 Vote 모델의 Serializer을 생성
ReadOnlyField로 설정한다.from rest_framework import serializers
from polls.models import Question, Choice, Vote
class VoteSerializer(serializers.ModelSerializer):
voter = serializers.ReadOnlyField(source = 'voter.username')
class Meta:
model = Vote
fields = ['id', 'question', 'choice', 'voter']
4) views.py에서 Vote 모델과 Serializer을 통한 화면 구현
views.py에 VoteList와 VoteDetail을 생성해 준다. Question과 동일한 형태이다.IsAuthenticatedOrReadOnly가 아닌 IsAuthenticated를 사용해 준다.Detail에도 본인의 투표만 보이도록 처리해야 하기 때문에 permissions.py에 IsVoter라는 class를 추가해 주어야 한다.#views.py
from django.shortcuts import render, get_object_or_404
from polls.models import Question, Vote
from polls_api.serializers import QuestionSerializer, UserSerializer, VoteSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework import status, mixins, generics, permissions
from rest_framework.views import APIView
from django.contrib.auth.models import User
from polls_api.serializers import *
from .permissions import IsOwnerOrReadOnly, IsVoter
class VoteList(generics.ListCreateAPIView):
serializer_class = VoteSerializer
permission_classes = [permissions.IsAuthenticated] #나만 봐야 해서 로그인 안 했을 때는 조회를 할 필요도 없음 그래서 ReadOnly가 아닌 IsAuthenticated로 설정
def get_queryset(self, *arg, **kwargs):
return Vote.objects.filter(voter=self.request.user) #로그인한 user의 vote만 보이도록
def perform_create(self, serializer): #overriding
serializer.save(voter=self.request.user)
class VoteDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Vote.objects.all()
serializer_class = VoteSerializer
permission_classes = [permissions.IsAuthenticated, IsVoter]
#permissions.py
from rest_framework import permissions
class IsVoter(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.voter == request.user

1) 투표한 건을 다시 투표할 때 발생하는 오류

사용자의 오류이기 때문에 status code가 400대여야 한다.UniqueTogeterValidator를 사용하기 위해서는 from rest_framework.validators import UniqueTogetherValidator 다음과 같이 import 해 준다.from rest_framework import serializers
from polls.models import Question, Choice, Vote
from django.contrib.auth.models import User
from django.contrib.auth.password_validation import validate_password
from rest_framework.validators import UniqueTogetherValidator
class VoteSerializer(serializers.ModelSerializer):
voter = serializers.ReadOnlyField(source = 'voter.username')
class Meta:
model = Vote
fields = ['id', 'question', 'choice', 'voter']
validators = [
UniqueTogetherValidator(
queryset = Vote.objects.all(),
fields = ['question', 'voter'] #unique한지 체크
)
]

400의 Bad Request 오류가 뜨는 것을 확인할 수 있다.2) voter 명이 제대로 저장되지 않는 오류
views.py에서 VoteList를 보면 다음과 같이 perform_create를 mixin의 perform_create를 overriding 해서 사용하고 있는데 유효성 검사인 validation이 perform_create가 아닌 그 전 단계의 create 단계에서 발생해 유효성 검사를 할 때는 user를 가지고 올 수 없기 때문이다.def perform_create(self, serializer): #overriding
serializer.save(voter=self.request.user)
overriding 해 주어야 한다.from rest_framework import status
from rest_framework.response import Response
class VoteList(generics.ListCreateAPIView):
serializer_class = VoteSerializer
permission_classes = [permissions.IsAuthenticated] #나만 봐야 해서 로그인 안 했을 때는 조회를 할 필요도 없음 그래서 ReadOnly가 아닌 IsAuthenticated로 설정
def get_queryset(self, *arg, **kwargs):
return Vote.objects.filter(voter=self.request.user) #로그인한 user의 vote만 보이도록
def create(self, request, *args, **kwargs):
new_data = request.data.copy()
new_data['voter'] = request.user.id #voter를 가지고 와 준다.
serializer = self.get_serializer(data=new_data)
serializer.is_valid(raise_exception=True) #이 단계 전에 voter가 있는지 보기 때문에 voter를 이 단계 전에 넣어 주어야 한다.
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
class VoteDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Vote.objects.all()
serializer_class = VoteSerializer
permission_classes = [permissions.IsAuthenticated, IsVoter]
def perform_update(self, serializer): #update를 통해 voter가 동일하면 새로 create가 아닌 update가 되도록 만들어 준다
serializer.save(voter=self.request.user)
3) 전혀 관련 없는 Question과 Choice를 매칭해서 투표를 해도 오류가 발생하지 않고 데이터가 생성되는 오류
serializers.py를 수정해 주어야 한다.VoteSerializer에서 validate 함수를 추가해 주어 choice의 question_id가 question의 question_id와 다를 때 ValidationError가 발생하도록 해 준다. from rest_framework.validators import UniqueTogetherValidator
class VoteSerializer(serializers.ModelSerializer):
def validate(self, attrs):
if attrs['choice'].question.id != attrs['question'].id:
raise serializers.ValidationError("Question과 Choice가 조합이 맞지 않습니다.")
return attrs
class Meta:
model = Vote
fields = ['id', 'question', 'choice', 'voter']
validators = [
UniqueTogetherValidator(
queryset = Vote.objects.all(),
fields = ['question', 'voter'] #unique한지 체크
)
]

tests.py에 Test 내용을 작성한다.TestCase를 상속받도록 한다.TestCase에서는 test_로 시작하는 함수를 테스트 케이스로 판단하여 Terminal에서 테스트를 돌릴 때 인식해 준다.Terminal에서 테스트 케이스를 실행해 보는 방법은 python manage.py test를 입력해 주면 된다.'.'이나 그 내용이 출력되며 실패한 경우 'FAIL: test_'가 뜬다. self.assertEqual(내 코드가 출력한 값, 테스트 결과로 나와야 하는 값)을 통해 제대로 동작하는지 여부를 확인하게 해 준다.# TestCase의 기본 틀
from django.test import TestCase
class QuestionSerializerTestCase(TestCase):
def test_a(self):
self.assertEqual(1, 2)
#False
def test_b(self):
pass
#True
from django.test import TestCase
from polls_api.serializers import QuestionSerializer
class QuestionSerializerTestCase(TestCase):
def test_with_valid_data(self): #question_text를 abc로 넘겼을 때 유효한 값이므로 True가 나와야 한다.
serializer = QuestionSerializer(data={'question_text': abc})
self.assertEqual(serializer.is_valid(), True)
new_question = serializer.save()
self.assertIsNotNone(new_question.id) #Question 객체의 id 필드가 None이 아닌지 확인
def test_with_invalid_data(self): #question_text를 null로 넘겼을 때 유효하지 않은 값이므로 false가 나와야 테스트 성공
serializer = QuestionSerializer(data={'question_text': ''})
self.assertEqual(serializer.is_valid(), False)

git init을 실행해 로컬 repo 생성git add file_name(여러 개도 가능)git commit -m "...." file_name(파일명을 쓰지 않은 경우 git add 한 파일이 그대로 반영됨.)git remote add origin (github 에서 만든 repo의 url)git branch -M main git push -u origin mainGit Commit(커밋)이란?
커밋은 관련 있는 변경 파일들을 하나로 묶는데 사용한다. (관련 있는 파일들을 변경하거나 코드 추가를 하는데 사용)
이걸 잘하면 특정 버그를 고치기 위해 이 파일들을 묶어서 수정했구나라는 것을 알 수 있음.
이를 통해 나를 포함한 팀원들이 나중에 이 버그를 어떤 파일들을 수정해서 고쳤는지 알 수 있음.
git clone (github의 repo 주소 url)pull 해 준다. git pullgit branch modify_greeting_message(new_branch_name)git checkout modify_greeting_message(new_branch_name)
git commit -m "modified greeting message(commit message)" hangman.py(commit file)git push 명령을 사용해 로컬 repo 브랜치를 서버의 repo로 복사한다. git push -u origin modify_greeting_message
pull request로 들어가 코드가 수정된 것을 확인 후 PR을 수행한다.

Reviewers를 추가한 후 어제 강의에서 들었던 좋은 PR 포맷을 바탕으로 작성한다. 코드 리뷰를 모두 받은 후 반영해야 할 때는 merge 해 준다.git pull을 진행한다.1. git과 관련된 오류
git branch (branch)명입력 시fatal: not a valid object name: 'master'다음과 같은 오류가 발생하는 이유는 한 번도 commit을 하지 않은 repository이기 때문이다. 그래서 이 문제를 해결하기 위해서는 한 번 commit만 진행해 주면 된다.
git commit -m "commit message"