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 main
Git Commit(커밋)
이란?
커밋은 관련 있는 변경 파일들을 하나로 묶는데 사용한다. (관련 있는 파일들을 변경하거나 코드 추가를 하는데 사용)
이걸 잘하면 특정 버그를 고치기 위해 이 파일들을 묶어서 수정했구나라는 것을 알 수 있음.
이를 통해 나를 포함한 팀원들이 나중에 이 버그를 어떤 파일들을 수정해서 고쳤는지 알 수 있음.
git clone (github의 repo 주소 url)
pull
해 준다. git pull
git 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"