
파이썬 장고 프레임웍을 사용해서 API 서버 만들기(5)
모델 사이의 관계를 나타내기 위해 사용되는 클래스
PrimaryKeyRelatedField : id(PrimaryKey)를 표시한다.
PrimaryKeyRelatedField(many=True, queryset=class_name.objects.all())
StringRelatedField : 모델에서 __str__로 정의된 메소드의 내용을 표시한다.
StringRelatedField(many=True, read_only=True)
SlugRelatedField : 모델에 있는 특정 필드를 표시한다.
SlugRelatedField(many=True, read_only=True, slug_field='field_name')
HyperlinkedRelatedField : 하이퍼링크를 제공
HyperlinkedRelatedField(many=True, read_only=True, view_name='view_name')
view_name은 urls.py에 path의 인자로 있는 name으로 설정해 해당 url로 이동시킬 수 있다.
Choice에서 하나가 아니라 내용과 투표수까지 모두 표시하도록 코드를 작성해보자. 이를 위해 ChoiceSerializer를 생성해서 진행한다.
class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = ['choice_text', 'votes']
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']
class Choice(models.Model):
question = models.ForeignKey(Question, related_name='choices', on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
related_name을 이용해 Question이 Choice를 불러올 때 choices라는 이름으로 불러 올 수 있게 했다.
기존의 투표 기능은 투표하면 +1 되는 형식으로 관리해 유저 단위로는 관리가 되어있지 않다. 두 가지 방법으로 새로운 기능을 구현해보자.
Vote모델을 만들자.
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)
# 하나의 질문에 대해 하나의 유저는 투표를 한번만 할 수 있으므로 해당 기능을 구현한다.
# question과 voter에 대해 하나의 레코드만 형성이 된다.
class Meta:
constraints = [
models.UniqueConstraint(fields=['question', 'voter'], name='unique_voter_for_questions')
]
이후 마이그레이션을 만들고 테이블에 반영하는 것을 잊지말자.
시리얼라이저에서 기존의 vote를 votes_count로 변경하자.
# SerializerMethodField() : 값이 메소드에 의해 결정
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()
기능 구현을 위해 시리얼라이저를 만들자.
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']
이제 뷰를 만들어야한다.
from polls.models import Question,Choice, Vote
from polls_api.serializers import VoteSerializer
from .permissions import IsOwnerOrReadOnly , IsVoter
# IsAuthenticated : 유저의 인증 여부를 나타냄
class VoteList(generics.ListCreateAPIView):
serializer_class = VoteSerializer
permission_classes = [permissions.IsAuthenticated]
# 내가 작성한 투표만 볼 수 있도록 함
def get_queryset(self, *args, **kwargs):
return Vote.objects.filter(voter=self.request.user)
def perform_create(self, serializer):
serializer.save(voter=self.request.user)
class VoteDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Vote.objects.all()
serializer_class = VoteSerializer
permission_classes = [permissions.IsAuthenticated, IsVoter]
투표에 대한 변경은 나만이 가능해야 하므로 권한에 대한 코드를 작성한다.
class IsVoter(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.voter == request.user
URL을 연결하자
from django.urls import path, include
from .views import VoteList, VoteDetail
urlpatterns = [
...
path('vote/', VoteList.as_view()),
path('vote/<int:pk>/', VoteDetail.as_view()),
]
장고에서는 django.test의 TestCase클래스를 통해 테스트 할 수 있다.
실습으로 테스트케이스를 만들어보자.
from django.test import TestCase
from polls_api.serializers import QuestionSerializer
class QuestionSerializerTestCase(TestCase):
def test_with_valid_data(self):
serializer = QuestionSerializer(data={'question_text': 'abc'})
self.assertEqual(serializer.is_valid(), True)
new_question = serializer.save()
self.assertIsNotNone(new_question.id)
def test_with_invalid_data(self):
serializer = QuestionSerializer(data={'question_text': ''})
self.assertEqual(serializer.is_valid(), False)
주의) TestCase클래스는 test_로 시작하는 메소드들만 테스트라고 인식한다.
다음을 입력해 테스트를 진행할 수 있다.
python manage.py test
테스트 케이스 코드를 작성시 setUp(self) 메소드를 이용 테스트 내용이 중복되는 내용을 한번만 작성하여 코드를 간결하게 할 수 있다. 또한 각 테스트마다 초기화 되어 이전 테스트가 다음 테스트에 영향을 끼치지 않는다.
Serializers를 테스트 할 때 클래 테스트 클래스는 TestCase를 import하고 상속받지만 Views를 테스트할 때는 APITestCase를 import하고 상속받는다.