본문 바로가기

[Python] django 게시판 추천, 좋아요 기능 구현하기

액트 2022. 12. 7.
반응형

게시판에 있는 추천(좋아요) 기능을 구현하는 방법입니다.

  1. 모델변경
    1. ) Question 모델 변경
    2. ) Answer 모델 변경
    3. ) mikemigration, migrate 명령어 실행
  2. 질문 추천
    1. ) 질문 추천 버튼
    2. ) 추천 버튼 확인 창
    3. ) 질문 추천 urls.py 
    4. ) 질문 추천 views.py
  3. 답변 추천
    1. ) 답변 추천 버튼
    2. ) 답변 추천 urls.py
    3. ) 답변 추천 views.py

 

1. 모델 변경

먼저 추천(좋아요) 기능은 하나의 질문에 여러명이 추천할 수 있고 한 명이 여러 개의 질문에 추천할 수 있으므로 "다대다(N:N)" 관계를 의미하는 ManyToManyField를 사용해야 합니다. 

추천(좋아요)를 나타내는 User모델의 voter 속성을 Question 모델과 Answer 모델에 정의해줘야 합니다.

그러나 기존 author 속성도 User 모델을 사용하므로 새롭게 추가하는 voter 속성과 충돌이 날 수 있습니다.

이 이유는 장고에서 User 모델을 통해서 Question 데이터에 접근하려고 할 때 author를 기준으로 할지 voter를 기준으로 해야 할지 명확하지 않아 오류가 발생합니다.

이럴 땐 related_name 인수를 추가하여 author 속성과 voter 속성을 구별해줘야 합니다.

 

1-1) Question 모델 변경

 

mysite\pybo\models.py 파일의 Question 모델을 다음과 같이 수정합니다.

class Question(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author_question') #수정
    subject = models.CharField(max_length=200)
    content = models.TextField()
    create_date = models.DateTimeField()
    modify_date = models.DateTimeField(null=True, blank=True)
    voter = models.ManyToManyField(User, related_name='voter_question')  #추가

위와 같이 author 속성에 related_name='author_question' 인수를 선언합니다.

voter는 ManyToManyField 를 사용하여 related_name='voter_question' 인수를 선언합니다.

이제 author 속성에 접근할 때는 author_question.all()와 같이 사용하고 voter 속성에 접근하고자 할때는 voter_question.all() 과 같이 접근합니다.

 

1-2) Answer 모델 변경

마찬가지 방법으로 Answer 모델도 추천인(voter) 속성을 다음과 같이 정의합니다.

class Answer(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author_answer')
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    content = models.TextField()
    create_date = models.DateTimeField()
    modify_date = models.DateTimeField(null=True, blank=True)
    voter = models.ManyToManyField(User, related_name='voter_answer')

 

1-3) makemigration 명령어와 migrate 명령어 실행

모델이 변경되었으니 (데이터베이스 변경) makemigration, migrate 명령어를 차례대로 수행합니다.

(myvenv) c:\projects\mysite>python manage.py makemigrations
Migrations for 'pybo':
  pybo\migrations\0005_auto_20221206_1747.py
    - Add field voter to answer
    - Add field voter to question
    - Alter field author on answer
    - Alter field question on answer
    - Alter field author on question

(myvenv) c:\projects\mysite>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, pybo, sessions
Running migrations:
  Applying pybo.0005_auto_20221206_1747... OK

(myvenv) c:\projects\mysite>

2.질문 추천

2-1) 질문 추천 버튼

 추천(좋아요)  버튼을 질문 상세 페이지에 작성합니다.

mysite\templates\pybo\question_detail.html 파일에 수정 버튼과 삭제 버튼 정의한 구문 위에 추천 버튼을 다음과 같이 작성합니다.

<div class="my-3">
	<!-- 추천 버튼 추가 시작 -->
    <a href="javascript:void(0)" data-uri="{% url 'pybo:question_vote' question.id  %}"
        class="recommend btn btn-sm btn-outline-secondary"> 추천
        <span class="badge rounded-pill bg-success">{{question.voter.count}}</span>
    </a>
    <!-- 추천 버튼 추가 끝 -->
    {% if request.user == question.author %}
    <a href="{% url 'pybo:question_modify' question.id %}"
       class="btn btn-sm btn-outline-secondary">수정</a>
    <a href="javascript:void(0)" class="delete btn btn-sm btn-outline-secondary"
       data-uri="{% url 'pybo:question_delete' question.id %}">삭제</a>
    {% endif %}
</div>

추천 버튼과 함께 question.voter.count 로 추천수도 함께 보이도록 설정했습니다.

그리고 버튼을 클릭하면 href 속성이 javascript.void(0) 으로 설정되어 있기 때문에 아무런 동작도 하지 않고 class 속성에 "recommned"를 추가하여 자바스크립트로 data-uri에 정의된 URL이 호출됩니다. 이는 "추천" 버튼에 대한 확인창을 팝업시키기 위함입니다.

 

2-2) 추천 버튼 확인 창 

추천 버튼을 클릭했을 때 "정말로 추천 하시겠습니까?" 라는 기능 구현을 위해 질문 상세 페이지에 다음과 같이 작성합니다.

mysite\templates\pybo\question_detail.html

{% block script %}
<script type='text/javascript'>
    const delete_elements = document.getElementsByClassName("delete");
    Array.from(delete_elements).forEach(function(element) {
        element.addEventListener('click', function() {
            if(confirm("정말로 삭제하시겠습니까?")) {
                location.href = this.dataset.uri;
            };
        });
    });
    <!-- 아래 추가 시작 -->
    const recommend_elements = document.getElementsByClassName("recommend");
    Array.from(recommend_elements).forEach(function(element) {
        element.addEventListener('click', function() {
            if(confirm("정말로 추천 하시겠습니까?")) {
                location.href = this.dataset.uri;
            };
        });
    <!-- 아래 추가 끝 -->
    });
</script>
{% endblock %}

 

2-3) 질문 추천 urls.py 

<a href="javascript:void(0)" data-uri="{% url 'pybo:question_vote' question.id  %}"

구문의 URL에 추가되었으므로 pybo/urls.py 에 URL 매핑 규칙을 정의합니다.

mystite\pybo\urls.py 파일을 다음과 구문을 추가합니다.

path('question/vote/<int:question_id>/', question_views.question_vote, name='question_vote'),

 

2-3) 질문 추천 views.py 

URL 매핑에 의해 실행되는 question_vote 함수를 다음과 같이 정의합니다.

mysite\pybo\views\question_views.py

@login_required(login_url='common:login')
def question_vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    if request.user == question.author:
        messages.error(request, '본인이 작성한 글은 추천할수 없습니다')
    else:
        question.voter.add(request.user)
    return redirect('pybo:detail', question_id=question.id)

로그인한 사용자와 글쓴이가 같다면 추천할 수 없도록 설정했습니다. 그리고 Question 모델의 voter는 여러 사람이 추천할 수 있으므로 add 함수를 사용하여 추천인을 추가했습니다.

else:
    question.voter.add(request.user)

 

3. 답변 추천

3-1) 답변 추천 버튼

질문 추천이랑 동일합니다. 질문 상세 페이지에 추천 버튼을 다음과 같이 추가합니다.

<div class="my-3">
	<!-- 추천 버튼 아래 추가 -->
    <a href="javascript:void(0)" data-uri="{% url 'pybo:answer_vote' answer.id  %}"
       class="recommend btn btn-sm btn-outline-secondary"> 추천
        <span class="badge rounded-pill bg-success">{{answer.voter.count}}</span>
    </a>
    <!-- 추천 버튼 아래 추가 끝 -->
    {% if request.user == answer.author %}
    <a href="{% url 'pybo:answer_modify' answer.id %}"
        class="btn btn-sm btn-outline-secondary">수정</a>
    <a href ="#" class="delete btn btn-sm btn-outline-secondary"
    data-uri="{% url 'pybo:answer_delete' answer.id %}">삭제</a>
    {% endif %}
</div>

 

3-2) 답변 추천 urls.py

mysite\pybo\urls.py 파일에 다음과 같이 URL 매핑 규칙을 추가합니다.

path('answer/vote/<int:answer_id>/', answer_views.answer_vote, name='answer_vote'),

 

 

3-3) 답변 추천 views.py

위에서 정의한 URL 매핑에 의해 실행되는 answer_vote 함수를 다음과 같이 정의합니다.

mysite\pybo\views\answer_views.py

@login_required(login_url='common:login')
def answer_vote(request, answer_id):
    answer = get_object_or_404(Answer, pk=answer_id)
    if request.user == answer.author:
        messages.error(request, '본인이 작성한 글은 추천할수 없습니다')
    else:
        answer.voter.add(request.user)
    return redirect('pybo:detail', question_id=answer.question.id)
반응형

댓글