장고 (Django)

장고(Django) 투표(Votes) 기능 구현하기 1 - Models

터칭 데이터 2023. 11. 5. 18:51

 

 

 

 

로그인한 사용자만 투표를 할 수 있도록 만들기

 

우리가 저번에 뷰에서 메서드 기반으로 만든 vote 기능은 구현과 실습에만 초점을 맞췄기 때문에 누구나 자유롭게 들어와 투표를 제한 없이 여러번 할 수 있다는 문제가 있었습니다.

 

우리가 원하는대로 한명의 사용자가 하나의 question에서는 하나의 표만 행사할 수 있도록 하겠습니다.

 

 

polls/models.py

# (..생략..)
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')
        ]

 

모델에서 위와 같이 코드를 추가합니다.

 

constraints의 필드인 question과 voter는 하나의 레코드만 기록할 수 있습니다. 한명의 사용자가 하나의 표만 행사할 수 있다는 제한입니다. 만약 2개 이상의 표를 행사하면 레코드가 생성되지 않습니다.

 

 

`models.UniqueConstraint`는 Django 모델에서 고유한 제약 조건을 정의하기 위해 사용되는 클래스입니다. 이 클래스를 사용하면 모델의 필드나 여러 필드 조합에 대해 중복되지 않는 값을 갖도록 제약을 설정할 수 있습니다. 이를 통해 데이터베이스에 중복되는 레코드를 방지할 수 있습니다.

`models.UniqueConstraint`를 사용하는 방법은 다음과 같습니다:

1. 모델 클래스 내에서 `class Meta` 내에 `constraints` 옵션을 정의합니다.
2. `models.UniqueConstraint` 클래스의 인스턴스를 생성하여 고유한 제약 조건을 정의합니다.

예를 들어, 다음은 `Product` 모델에서 `name` 필드에 고유한 제약 조건을 설정하는 예제입니다:

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['name'], name='unique_product_name')
        ]



위의 코드에서 `models.UniqueConstraint`는 `name` 필드에 고유한 제약 조건을 정의합니다. 이를 통해 `Product` 모델에 중복되는 제품 이름이 저장되지 않도록 보장됩니다. 제약 조건은 데이터베이스 스키마에 적용되며, 중복 레코드를 방지하는 데 도움이 됩니다.

`models.UniqueConstraint`를 사용하여 Django 모델에서 고유한 제약을 정의하면 데이터 무결성을 유지하고 중복 데이터를 방지할 수 있습니다.

 

 

 

 

 

 

모델을 수정했으므로 파이썬 쉘에서 마이그레이션과 마이그레이트를 다시 진행합니다.

 

(DjangoProjects) \mysite>python manage.py makemigrations
Migrations for 'polls':
  polls\migrations\0003_alter_choice_question_vote_and_more.py
    - Alter field question on choice
    - Create model Vote
    - Create constraint unique_voter_for_questions on model vote

(DjangoProjects) \mysite>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying polls.0003_alter_choice_question_vote_and_more... OK

(DjangoProjects) C:\Users\User\DjangoProjects\Scripts\mysite>

 

 

 

쉘에서 투표를 진행해보겠습니다.

>>> from polls.models import *


>>> question = Question.objects.first()
>>> question
<Question:  제목: 휴가를 어디서 보내고 싶나요?, 날짜: 2023-10-31 14:19:40+00:00>


>>> choice = question.choices.first()
>>> choice
<Choice: [휴가를 어디서 보내고 싶나요?] 바다>


>>> from django.contrib.auth.models import User

>>> user = User.objects.get(username='user1')
>>> user
<User: user1>


>>> Vote.objects.create(voter=user, question=question, choice=choice)
<Vote: Vote object (1)>

>>> Vote.objects.first()
<Vote: Vote object (1)>

>>> question.id
1

 

id 1번 question의 choice인 바다에 한표를 행사했는데요.

 

 

 

1번 question의 상세페이지에 접속해보니

바다는 아직도 0표입니다.

 

왜냐하면 Choice 모델의 votes가 숫자 필드이기 때문입니다. 그래서 ChoiceSerializer를 변경해서 votes를 필드로 갖지 않고 Vote 테이블에서 몇개나 투표받았는지 값을 count해 표시하도록 코드를 수정하겠습니다.

 

 

 

polls_api/serializers.py

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()

 

 

serializers.SerializerMethodField는 Django REST framework (DRF)에서 사용되는 직렬화 필드 중 하나로, 모델의 필드 값이 아닌 사용자 지정 메서드를 통해 데이터를 직렬화할 때 사용됩니다.

 

get_votes_count 메서드로 득표수를 구해 이를 votes_count에 전달합니다. 별다른 related_name을 설정하지 않아 obj.vote_set.count()임을 생각해주세요.

 

 

 

새로고침을 하면

 

 

바다에 행사한 1표가 잘 반영되었습니다.