사용자 단위로 들어와서 투표할 수 있는 기능을 구현해본다. 한 사용자가 한 질문에 대해 한 번 투표할 수 있도록 한다.
UniqueConstraint
제약을 만들어서 한 질문에 대해 사용자가 한번의 투표만 할 수 있도록 한다.
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)
class Meta:
constraints = [
models.UniqueConstraint(fields=['question', 'voter'], name='unique_voter_for_questions')
]
모델을 생성했기 때문에 DB에 반영해줄 수 있도록 migration을 실행한다.
python manage.py makemigrations
python manage.py migrate
SerializerMethodField()
를 사용하여 값이 메소드에 의해 결정되도록 하는 votes_count
필드를 추가한다.
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()
Question
모델과 User
모델의 관계를 정의하는 필드에는 여러가지 유형이 있다. 어떤 RelatedField를 사용하는지에 따라 화면에 표시되는 방식이 달라진다.
대상 모델의 key 값을 출력한다.
questions = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())
대상 모델의 __str__()
메소드에 정의된 결과를 출력한다.
questions = serializers.StringRelatedField(many=True, read_only=True)
대상 모델의 필드 중 원하는 필드의 값을 출력한다. slug_field
파라미터에 원하는 필드를 입력한다.
questions = serializers.SlugRelatedField(many=True, read_only=True, slug_field='pub_date')
아래와 같이 링크가 있는 텍스트의 형태를 출력한다. view_name
에 urls.py
파일에 정의한 경로의 name
을 설정하면 해당 경로로 링크된다.
questions = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='question-detail')
현재까지 구현한 vote 기능은 한 사용자가 한 질문에 대해 중복으로 투표하는 것을 막지 못하고, 질문에 대한 정해져 있는 선택지가 아닐 경우에 이에 대해 에러를 발생시키지 못한다.
직접 테스트 해보거나 POSTMAN을 사용하여 테스트할 수 있지만 테스트 코드를 이용하여 테스트한다면 좀 더 빠르고 편리하게 테스트를 할 수 있다.
앱 폴더에 test.py
파일을 생성하고 테스트 코드를 작성한 후 아래의 명령어로 test를 실행한다.
python manage.py test
test_
로 시작하는 메소드만 실행된다.
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)