25/10/15 장고

344th·2025년 12월 11일

AWS AI

목록 보기
23/48

답변 수정&삭제 기능 추가

답변 수정 버튼 추가

templates\pybo\question_detail.html

{% if request.user == answer.author %}
<div class="my-3">
    <a href="{% url 'pybo:answer_modify' answer.id %}"
        class="btn btn-sm btn-outline-secondary">수정</a>
</div>
{% endif %}

답변 수정 url 매핑 추가

pybo\urls.py

path('answer/modify/<int:answer_id>/', views.answer_modify, name='answer_modify'),

답변 수정 함수 추가

pybo\views.py

from .models import Question, Answer
...
@login_required(login_url='common:login')
def answer_modify(request, answer_id):
    """
    pybo 답변 수정
    """
    answer = get_object_or_404(Answer, pk=answer_id)
    if request.user != answer.author:
        messages.error(request, '수정권한이 없습니다.')
        return redirect('pybo:detail', question_id=answer.question.id)
    
    if request.method == "POST":
        form = AnswerForm(request.POST, instance=answer)
        if form.is_valid():
            answer = form.save(commit=False)
            answer.author = request.user
            answer.modify_date = timezone.now()
            answer.save()
            return redirect('pybo:detail', question_id=answer.question.id)
    else:
        form = AnswerForm(instance=answer)
    
    context = {'answer': answer, 'form': form}
    return render(request, 'pybo/answer_form.html', context)

답변 수정 폼 작성

templates\pybo\answer_form.html

{% extends 'base.html' %}

{% block content %}
<div class="container my-3">
    <form method="post" class="post-form">
        {% csrf_token %}
        {% include 'form_errors.html' %}
        <div class="form-group">
            <label for="content">답변내용</label>
            <textarea class="form-control" name="content" id="content"
                rows="10">{{ form.content.value|default_if_none:'' }}</textarea>
        </div>
        <button type="submit" class="btn btn-primary">저장하기</button>
    </form>
</div>
{% endblock %}

답변 삭제 버튼 추가, url 매핑 추가, 삭제 함수 추가 … (위와 똑같은 과정)

수정일시 표시

작성일시 왼쪽에 수정일시 추가

templates\pybo\question_detail.html

...
<!-- 질문 내용 -->
<div class="card my-3">
  <div class="card-body">
      <div class="card-text" style="white-space: pre-line;">
          {{ question.content }}
      </div>
      <div class="d-flex justify-content-end">
          {% if question.modify_date %}
          <div class="badge badge-light p-2 text-left mx-3">
              <div class="mb-2">modified at</div>
              <div>{{ question.modify_date }}</div>
          </div>
          {% endif %}
          ...
  </div>
</div>
...
<!-- 딥변 내용 -->
{% for answer in question.answer_set.all %}
<div class="card my-3">
  <div class="card-body">
      <div class="card-text" style="white-space: pre-line;">
          {{ answer.content }}
      </div>
      <div class="d-flex justify-content-end">
          {% if answer.modify_date %}
          <div class="badge badge-light p-2 text-left mx-3">
              <div class="mb-2">modified at</div>
              <div>{{ answer.modify_date }}</div>
          </div>
          {% endif %}
          ...
  </div>
</div>

댓글 기능 추가

댓글에 사용할 모델 만들기ㅅ

Comment 모델

pybo\models.py

class Comment(models.Model):
    author = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    content = models.TextField()
    create_date = models.DateTimeField()
    modify_date = models.DateTimeField()
    question = models.ForeignKey(Question, null=True, blank=True, on_delete=models.CASCADE)
    answer = models.ForeignKey(Answer, null=True, blank=True, on_delete=models.CASCADE)

makemigrations, migrate 실행

질문 댓글 기능 추가

질문 상세 화면에 댓글 목록과 댓글 입력 링크 추가

templates\pybo\question_detail.html

<!-- 질문 댓글 start -->
{% if question.comment_set.count > 0 %}
<div class="mt-3">
    {% for comment in question.comment_set.all %}
    <div class="comment py-2 text-muted">
        <span style="white-space: pre-line;">
            {{ comment.content }}
        </span>
        <span>
            - {{ comment.author }}, {{ comment.create_date }}
            {% if comment.modify_date %}
            (수정:{{ comment.modify_date }})
            {% endif %}
        </span>
        {% if request.user == comment.author %}
        <a href="{% url 'pybo:comment_modify_question' comment.id %}"
            class="small">수정</a>,
        <a href="#" class="small delete"
            data-uri="{% url 'pybo:comment_delete_question' comment.id %}">
        삭제</a>
        {% endif %}
    </div>
    {% endfor %}
</div>
{% endif %}
<div>
    <a href="{% url 'pybo:comment_create_question' question.id %}"
        class="small"><small>댓글 추가 ..</small></a>
</div>
<!-- 질문 댓글 End -->

comment 클래스에 css 작성

static\style.css

.comment {
    border-top: dotted 1px #ddd;
    font-size: 0.7em;
}

질문 댓글 url 매핑 추가

pybo\urls.py

path('comment/create/question/<int:question_id>/', views.comment_create_question, name='comment_create_question'),
path('comment/modify/question/<int:question_id>/', views.comment_modify_question, name='comment_modify_question'),
path('comment/delete/question/<int:question_id>/', views.comment_delete_question, name='comment_delete_question'),

질문 댓글 폼 작성

pybo\forms.py

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['content']
        labels = {
            'content': '댓글내용',
        }

질문 댓글 등록 함수 추가

pybo\views.py

from pybo.forms import QuestionForm, AnswerForm, CommentForm
...

@login_required(login_url='common:login')
def comment_create_question(request, question_id):
    """
    pybo 질문 댓글 등록
    """
    question = get_object_or_404(Question, pk=question_id)

    if request.method == 'POST':
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = form.save(commit=False)
            comment.author = request.user
            comment.create_date = timezone.now()
            comment.question = question
            comment.save()
            return redirect('pybo:detail', question_id=question_id)
    else:
        form = CommentForm()
    
    context = {'form': form}
    return render(request, 'pybo/comment_form.html', context)

질문 댓글 등록을 위한 템플릿 작성

templates\pybo\comment_form.html

{% extends 'base.html' %}

{% block content %}
<div class="container my-3">
    <h5 class="border-bottom pb-2">댓글 등록하기</h5>
    <form method="post" class="post-form my-3">
        {% csrf_token %}
        {% include 'form_errors.html' %}
        <div class="form-group">
            <label for="content">댓글 내용</label>
            <textarea class="form-control" name="content" id="content"
                rows="3">{{ form.content.value|default_if_none:'' }}</textarea>
        </div>
        <button type="submit" class="btn btn-primary">저장하기</button>
    </form>
</div>
{% endblock %}

질문 댓글 수정, 삭제 함수 추가

pybo\views.py

from .models import Question, Answer, Comment
...
@login_required(login_url='common:login')
def comment_modify_question(request, comment_id):
    """
    pybo 질문 댓글 수정
    """
    comment = get_object_or_404(Comment, pk=comment_id)
    if request.user != comment.author:
        messages.error(request, '댓글 수정권한이 없습니다.')
        return redirect('pybo:detail', question_id=comment.question.id)

    if request.method == 'POST':
        form = CommentForm(request.POST, instance=comment)
        if form.is_valid():
            comment = form.save(commit=False)
            comment.author = request.user
            comment.modify_date = timezone.now()
            comment.save()
            return redirect('pybo:detail', question_id=comment.question.id)
    else:
        form = CommentForm(instance=comment)
    
    context = {'form': form}
    return render(request, 'pybo/comment_form.html', context)

@login_required(login_url='common:login')
def comment_delete_question(request, comment_id):
    """
    pybo 질문 댓글 삭제
    """
    comment = get_object_or_404(Comment, pk=comment_id)
    
    if request.user != comment.author:
        messages.error(request, '댓글 삭제권한이 없습니다.')
        return redirect('pybo:detail', question_id=comment.question.id)
    else:
        comment.delete()
    return redirect('pybo:detail', question_id=comment.question.id)

답변 댓글 기능 추가

질문 댓글 기능 추가와 똑같음

views.py 파일 분리

view 디렉터리 생성

**views.py 파일을 분리하여 views 디렉터리에 저장**

base_views.py 파일 작성

pybo\views\base_views.py

from django.shortcuts import render, get_object_or_404, redirect
from django.core.paginator import Paginator

from ..models import Question

def index(request):
    """
    pybo 목록 출력
    """
    # 입력 인자
    page = request.GET.get('page', '1') #페이지
    
    # 조회
    question_list = Question.objects.order_by("-create_date")
    
    # 페이징 처리
    paginator = Paginator(question_list, 10)
    page_obj = paginator.get_page(page)
    
    context = {"question_list" : page_obj}
    return render(request, 'pybo/question_list.html', context)

def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    context = {'question' : question}
    print(context)
    return render(request, 'pybo/question_detail.html', context)

question_views.py 등 나머지 파일들 작성

init.py 작성

pybo\views\__init__.py

from .base_views import *
from .question_views import *
from .answer_views import *
from .comment_views import *

pybo/views.py 삭제

추천 기능 추가

Question, Answer 모델 변경

우선 Question 모델에 추천인(voter) 속성을 추가해 보자.

하나의 질문에 여러명이 추천할 수 있고 한 명이 여러 개의 질문에 추천할 수 있으므로 이런 경우에는 "다대다(N:N)" 관계를 의미하는 ManyToManyField를 사용해야 한다.

[파일명: projects\mysite\pybo\models.py]

(... 생략 ...)
classQuestion(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    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)  # 추천인 추가def__str__(self):
return self.subject
(... 생략 ...)

voter = models.ManyToManyField(User) 처럼 추천인(voter)을 ManyToManyField 관계로 추가했다. 그런데 이렇게 수정하고 makemigrations 명령을 실행하면 다음과 같은 오류가 발생한다.

(mysite) c:\projects\mysite>python manage.py makemigrations
SystemCheckError: System check identified some issues:

ERRORS:
pybo.Question.author: (fields.E304) Reverse accessor for 'pybo.Question.author' clashes with reverse accessor for 'pybo.Question.voter'.
        HINT: Add or change a related_name argument to the definition for 'pybo.Question.author' or 'pybo.Question.voter'.
pybo.Question.voter: (fields.E304) Reverse accessor for 'pybo.Question.voter' clashes with reverse accessor for 'pybo.Question.author'.
        HINT: Add or change a related_name argument to the definition for 'pybo.Question.voter' or 'pybo.Question.author'.

(mysite) c:\projects\mysite>

오류의 내용은 Question 모델에서 사용한 author와 voter가 모두 User 모델과 연결되어 있기 때문에 User.question_set 처럼 User 모델을 통해서 Question 데이터에 접근하려고 할 때 author를 기준으로 할지 voter를 기준으로 해야 할지 명확하지 않다는 오류이다.

이 문제는 위 오류의 HINT에서도 알 수 있듯이 related_name 인수를 추가하여 해결할 수 있다. 다음처럼 Question 모델을 변경해 보자.

[파일명: projects\mysite\pybo\models.py]

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

def__str__(self):
return self.subject

author에는 related_name='author_question' 라는 인수를 지정하고 voter에는 related_name='voter_question' 라는 인수를 지정했다. 이렇게 하면 이제 특정 사용자가 작성한 질문을 얻기 위해서는 some_user.author_question.all() 처럼 사용할 수 있다. 마찬가지로 특정 사용자가 추천한 질문을 얻기 위해서는 some_user.voter_question.all() 처럼 사용할 수 있다.

some_user는 특정 사용자를 의미한다.

이제 마찬가지 방법으로 Answer모델에도 추천인(voter) 속성을 다음처럼 추가하도록 하자.

Answer 모델도 동일

질문 추천 기능 만들기

질문 추천 버튼 만들기

templates\pybo\question_detail.html

...
<!-- 컨테이너 -->
<div class="container my-3">
    <!-- 질문 제목 -->
    <h2 class="border-bottom py-2">{{ question.subject }}</h2>
    <div class="row my-3">
        <div class="col-1"> <!-- 추천 영역 -->
            <div class="bg-light text-center p-3 border font-weight-bolder mb-1">
                {{question.voter.count}}
            </div>
            <a href="#" data-uri="{% url 'pybo:vote_question' question.id %}"
                class="recommend btn btn-sm btn-secondary btn-clock my-1">추천</a>
        </div>
        <div class="col-11"> <!-- 질문 영역 -->
            <!-- 기존 내용 -->
             <!-- 질문 내용 -->
            <div class="card">
                <div class="card-body">
                    <div class="card-text" style="white-space: pre-line;">
                        {{ question.content }}
                    </div>
                    <div class="d-flex justify-content-end">
                        {% if question.modify_date %}
                        <div class="badge badge-light p-2 text-left mx-3">
                            <div class="mb-2">modified at</div>
                            <div>{{ question.modify_date }}</div>
                        </div>
                        {% endif %}
                        <div class="badge badge-light p-2 text-left">
                            <div class="mb-2">{{ question.author.username }}</div>
                            <div>{{ question.create_date }}</div>
                        </div>
                    </div>
                    {% if request.user == question.author %}
                    <div class="my-3">
                        <a href="{% url 'pybo:question_modify' question.id %}" class="btn btn-sm btn-outline-secondary">수정</a>
                        <a href="#" class="delete btn btn-sm btn-outline-secondary"
                            data-uri="{% url 'pybo:question_delete' question.id %}">삭제</a>
                    </div>
                    {% endif %}
                    <!-- 질문 댓글 start -->
                    {% if question.comment_set.count > 0 %}
                    <div class="mt-3">
                        {% for comment in question.comment_set.all %}
                        <div class="comment py-2 text-muted">
                            <span style="white-space: pre-line;">
                                {{ comment.content }}
                            </span>
                            <span>
                                - {{ comment.author }}, {{ comment.create_date }}
                                {% if comment.modify_date %}
                                (수정:{{ comment.modify_date }})
                                {% endif %}
                            </span>
                            {% if request.user == comment.author %}
                            <a href="{% url 'pybo:comment_modify_question' comment.id %}"
                                class="small">수정</a>,
                            <a href="#" class="small delete"
                                data-uri="{% url 'pybo:comment_delete_question' comment.id %}">
                            삭제</a>
                            {% endif %}
                        </div>
                        {% endfor %}
                    </div>
                    {% endif %}
                    <div>
                        <a href="{% url 'pybo:comment_create_question' question.id %}"
                            class="small"><small>댓글 추가 ..</small></a>
                    </div>
                    <!-- 질문 댓글 End -->
                </div>
            </div>
        </div>
    </div>
    ...

추천 확인 창 만들기

templates\pybo\question_detail.html

...
<script type="text/javascript">
    $(document).ready(function(){
        ...
        $(".recommend").on('click', function() {
            if(confirm("정말로 추천하시겠습니까?")) {
                location.href = $(this).data('uri');
            }
        });
    });
</script>

질문 추천 url 매핑 추가

pybo\urls.py

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

질문 추천 함수 추가

pybo\views\vote_views.py 생성

from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from django.contrib.auth.decorators import login_required
from django.contrib import messages

from ..models import Question

@login_required(login_url='common:login')
def vote_question(request, question_id):
    """
    pybo 질문 추천 등록
    """
    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)

자신의 글 추천 시 오류 표시 기능 추가

templates\pybo\question_detail.html

...
<!-- 컨테이너 -->
<div class="container my-3">
    <!-- 사용자 오류 표시 -->
    {% if messages %}
    <div class="alert alert-danger my-3" role="alert">
        {% for message in messages %}
            <strong>{{ message.tags }}</strong>
            <ul><li>{{ message.message }}</li></ul>
        {% endfor %}
    </div>
    {% endif %}
    ...

답변 추천 기능 만들기

질문 추천 기능과 과정 동일

질문 목록 화면에서 추천수 표시

질문 목록에 추천수 표시

templates\pybo\question_list.html

<thead>
    <tr class="text-center thread-dark">
        <th>번호</th>
        <th>추천</th>
        ...
    </tr> 
</thead>
<tbody>
    {% if question_list %}
    {% for question in question_list %}
    <tr class="text-center">
        <td>
            ...
        </td>
        <td>
            {% if question.voter.all.count > 0 %}
            <span class="badge badge-warning px-2 py-1">
                {{ question.voter.all.count }}
            </span>
            {% endif %}
        </td>
        ...

모델 설계 관련 질문

  1. 카트는 카트아이템 테이블이 따로 있고, 위시리스트는 아이템 테이블이 따로 없이 매니투매니 필드로 처리한 것은 장바구니는 수량과 그때 당시의 가격 등 카트아이템의 상태가 필요한 것이 많고, 위시리스트는 수량과 담을 당시의 가격 등에 영향을 딱히 받지 않으므로 간단히 mtom 필드로 처리한 것?

    → 또한 로그아웃되면 히스토리가 저장되지 않고 임시로 처리되는 경우도 많기 때문에

  2. 그럼 위시리스트에서 프로덕트는 어떻게 처리되는가? 쿼리셋으로 저장되는가?

  3. 그럼 위시리스트를 나중에 프론트에게 응답을 보내주기 위해 처리할 때 시리얼라이저에서 json 형식으로 바꿀 때 respose body에 product는 배열로 처리되는가?

→ 다 ㅇㅇ

→ 위시리스트와 프로덕트와 같이 다대다 관계의 경우, 중간에 참조 테이블이 하나 생성됨

profile
새싹 개발자

0개의 댓글