[TIL | 240120] Django - 모델 변경, 글쓴이 표시

sun_U·2024년 1월 22일
0

TIL

목록 보기
19/21
post-thumbnail

모델 변경


1. Question 속성 추가

: Question에 author(글쓴이) 속성 추가

pybo/models.py

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

class Question(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    (... 생략 ...)
  • User → django.contrib.auth 앱이 제공하는 사용자 모델. 회원 가입시 데이터 저장에 사용한 모델.ForeignKey로 적용해 선언
    • author 속성에 null 허용하려면 null=True

🚨 모델 변경 후 반드시 makemigrations, migrate 를 통해 데이터베이스 변경해야 함.

2. Answer 속성 추가

: Answer에 author(글쓴이) 속성 추가

pybo/models.py

(... 생략 ...)

class Answer(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    (... 생략 ...)

🚨 모델 변경 후 반드시 makemigrations, migrate 를 통해 데이터베이스 변경해야 함.

3. author 저장

: 질문과 답변 저장시 author도 함께 저장해야 함.

pybo/views.py

def answer_create(request, question_id):
    (... 생략 ...)
        if form.is_valid():
            answer = form.save(commit=False)
            answer.author = request.user  # author 속성에 로그인 계정 저장
            (... 생략 ...)
    (... 생략 ...)

def question_create(request):
    (... 생략 ...)
        if form.is_valid():
            question = form.save(commit=False)
            question.author = request.user  # author 속성에 로그인 계정 저장
    (... 생략 ...)
  • request.user → 현재 로그인한 계정의 User 모델 객체

4. 로그인이 필요한 함수

  • request.user가 User 객체가 아닌 AnonymousUser 객체이므로 에러 발생
    • 로그아웃 상태이면 AnonymousUser 객체가, 로그인 상태이면 User 객체가 들어있는데, 우리는 author 속성을 정의할 때 User를 이용하도록 함. ⇒  answer.author = request.user에서 User 대신 AnonymousUser가 대입되어 오류가 발생

⇒  request.user를 사용하는 함수에 @login_required 애너테이션을 사용 해 로그인 필요한 함수로 만듦

pybo/views.py

from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from .models import Question
from .forms import QuestionForm, AnswerForm
from django.core.paginator import Paginator
from django.contrib.auth.decorators import login_required

(... 생략 ...)

@login_required(login_url='common:login')
def answer_create(request, question_id):
    (... 생략 ...)

@login_required(login_url='common:login')
def question_create(request):
    (... 생략 ...)
  • answer_create 함수와 question_create 함수 → 로그인이 필요하므로 @login_required 어노테이션 사용
  • 로그아웃 상태에서 @login_required 어노테이션이 적용된 함수가 호출되면 자동으로 로그인 화면으로 이동.
  • @login_required 어노테이션은 login_url='common:login' 처럼 로그인 URL을 지정 가능

5. next

  • 로그인 후 next 파라미터에 있는 URL로 페이지를 이동하려면 로그인 템플릿에 hidden 타입의 next 항목을 추가

common/login.html

(... 생략 ...)
<form method="post" action="{% url 'common:login' %}">
    {% csrf_token %}
    <input type="hidden" name="next" value="{{ next }}">  <!-- 로그인 성공후 이동되는 URL -->
    {% include "form_errors.html" %}
(... 생략 ...)

6. disabled

pybo/question_detail.html

(... 생략 ...)
<div class="mb-3">
    <label for="content" class="form-label">답변내용</label>
    <textarea {% if not user.is_authenticated %}disabled{% endif %}
              name="content" id="content" class="form-control" rows="10"></textarea>
</div>
<input type="submit" value="답변등록" class="btn btn-primary">
(... 생략 ...)
  • {% if not user.is_authenticated %} (로그인 상태가 아닌 경우) textarea 태그에 disabled 속성을 적용 → 로그아웃 시 답변 작성 불가하도록 지정

→ 로그아웃 상태에서 답변 등록 클릭 후 로그인 시 발생하는 에러

  • 로그인 시에 전달된 next 파라미터 때문에 로그인 후에 답변등록 URL인 /answer/create가 GET 방식으로 호출. (답변 등록시에는 POST가 아닌 경우 HttpResponseNotAllowed 오류를 발생하도록 설정해서 405 에러 발생)

해결 :

pybo/views.py 수정

(... 생략 ...)
from django.http import HttpResponseNotAllowed
(... 생략 ...)

@login_required(login_url='common:login')
def answer_create(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    if request.method == 'POST':
        form = AnswerForm(request.POST)
        if form.is_valid():
            answer = form.save(commit=False)
            answer.author = request.user  # author 속성에 로그인 계정 저장
            answer.create_date = timezone.now()
            answer.question = question
            answer.save()
            return redirect('pybo:detail', question_id=question.id)
    else:
        form = AnswerForm()
    context = {'question': question, 'form': form}
    return render(request, 'pybo/question_detail.html', context)

(... 생략 ...)

글쓴이 표시


1. 질문 목록

question_list.html

(... 생략 ...)
<tr class="text-center table-dark">
    <th>번호</th>
    <th style="width:50%">제목</th>
    <th>글쓴이</th>
    <th>작성일시</th>
</tr>
(... 생략 ...)

{% for question in question_list %}
<tr class="text-center">
    <td>
        <!-- 번호 = 전체건수 - 시작인덱스 - 현재인덱스 + 1 -->
        {{ question_list.paginator.count|sub:question_list.start_index|sub:forloop.counter0|add:1 }}
    </td>
    <td class="text-start">
        <a href="{% url 'pybo:detail' question.id %}">{{ question.subject }}</a>
        {% if question.answer_set.count > 0 %}
        <span class="text-danger small mx-2">{{ question.answer_set.count }}</span>
        {% endif %}
    </td>
    <td>{{ question.author.username }}</td>  <!-- 글쓴이 추가 -->
    <td>{{ question.create_date }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
    <td colspan="4">질문이 없습니다.</td>
</tr>
(... 생략 ...)
  • th 엘리먼트 가운데 정렬, 제목의 너비가 전체에서 50% 차지하도록 지정
  • <td>{{ question.author.username }}</td> → 질문 글쓴이 표시
  • <td colspan="4">질문이 없습니다.</td> → 테이블 항목 개수 3에서 4로 증가

2. 질문 상세

question_detail.html

(... 생략 ...)
<!-- 질문 -->
<h2 class="border-bottom py-2">{{ question.subject }}</h2>
<div class="card-body">
    <div class="card-text" style="white-space: pre-line;">{{ question.content }}</div>
    <div class="d-flex justify-content-end">
        <div class="badge bg-light text-dark p-2 text-start">
            <div class="mb-2">{{ question.author.username }}</div>
            <div>{{ question.create_date }}</div>
        </div>
    </div>
</div>

(... 생략 ...)

<!-- 답변 -->
<h5 class="border-bottom my-3 py-2">{{question.answer_set.count}}개의 답변이 있습니다.</h5>
{% for answer in question.answer_set.all %}
<div class="card-body">
    <div class="card-text" style="white-space: pre-line;">{{ answer.content }}</div>
    <div class="d-flex justify-content-end">
        <div class="badge bg-light text-dark p-2 text-start">
            <div class="mb-2">{{ answer.author.username }}</div>
            <div>{{ answer.create_date }}</div>
        </div>
    </div>
</div>
(... 생략 ...)
  • 질문 목록과 같이 글쓴이 추가

profile
Data Engineer🐣

0개의 댓글