IT/Linux

[Python] django 게시판 글쓴이 항목 추가하기 (1)

액트 2022. 11. 24. 08:19

게시판의 질문, 답변에 누가 글을 작성했는지 알려주는 글쓴이 필드를 추가하려고 합니다. 

필드를 추가하기 위해선 데이터베이스에 글쓴이에 해당하는 author 속성이 추가되어야 합니다. 

글쓴이 필드는 질문은 한 사람 Question과 답변을 한 사람 Answer 모델 두 곳 모두 추가해야 합니다.

1. 모델 변경

1-1) Question 모델 속성 추가

데이타베이스의 author 필드는 장고에서 제공하는 django.contrib.auth 앱의 User 모델을 사용하여 선언합니다.

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

from django.db import models

# Create your models here.
from django.db import models
from django.contrib.auth.models import User   #추가


class Question(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)  #추가
    subject = models.CharField(max_length=200)
    content = models.TextField()
    create_date = models.DateTimeField()

author 필드는 User 모델을 ForeignKey로 적용하여 선언하였습니다.

on_delete=models.CASCADE는 계정이 삭제되면 이 계정이 작성한 질문을 모두 삭제하라는 명령어입니다.

데이타베이스 쪽을 담당하는 model.py 파일이 변경되었으니 적용을 위해 makemigrations 명령어와 migrate 명령어를 실행해야 합니다.

명령프롬프트(cmd) 를 실행하여 mysite 가상환경을 실행하고 다음과 같이 명령어를 입력합니다.

(myvenv) c:\projects\mysite>python manage.py makemigrations

python manage.py makemigrations 명령어를 실행하면 아래와 같은 Selete an option을 선택하라는 메시지가 나옵니다.

(myvenv) c:\projects\mysite>python manage.py makemigrations
You are trying to add a non-nullable field 'author' to question without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option:

이는 데이터베이스에 author 필드가 새로 추가되었기 때문입니다. 기존에 있는 데이터 항목에도 마찬가지로 author 필드가 생겼기 때문에 여기에 어떤 값을 넣어야 할지 물어보는 것입니다. 

1) Provide a one-off default now (will be set on all existing rows with a null value for this column)

이 옵션은 새롭게 추가된 author 필드에 값을 지금 직접 입력해서 넣을 것이냐를 묻는 옵션입니다.

2) Quit, and let me add a default in models.py

이 옵션은 중단하는 옵션입니다.

질문, 답변에는 author 값이 무조건 있어야 하므로 1번을 선택합니다.

 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
 
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>>

'1' 을 입력하면 다음과 같이 >>> 프롬프트가 나타납니다. 여기서 다시 '1'을 입력합니다.

여기서 '1은' admin 계정의 id 값입니다. 기존 데이타에 admin 값이 author 필드에 적용되는 것입니다.

Type 'exit' to exit this prompt
>>> 1
Migrations for 'pybo':
  pybo\migrations\0002_auto_20221123_1120.py
    - Add field author to question
    - Alter field question on answer

(myvenv) c:\projects\mysite>

계정 생성시마다 계정에 id가 부여되는데 id 값은 1부터 순차적으로 증가하면서 부여됩니다. 

이제 migrate 명령으로 변경된 내용을 데이터베이스에 적용합니다.

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

OK가 떨어지면서 잘 반영되었습니다.

반영된 값은 아래 사진과 같이 DB Browser for SQLite 에서 확인하실 수 있습니다.

author_id 값이 모두 1로 들어가 있습니다.

 

1-2)  Answer 모델 속성 추가

Question 모델과 같은 방법으로 진행합니다.

mysite\pybo\models.py 파일을 아래와 같이 수정합니다.

class Answer(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    content = models.TextField()
    create_date = models.DateTimeField()

저장 후 명령프롬프트(cmd) 창에서 makemigrations 명령을 실행하고 Question 모델과 마찬가지로 1을 옵션에서 1을 선택하고 admin의 id 값인 1을 입력합니다.

(myvenv) c:\projects\mysite>python manage.py makemigrate
Unknown command: 'makemigrate'
Type 'manage.py help' for usage.

(myvenv) c:\projects\mysite>python manage.py makemigrations
You are trying to add a non-nullable field 'author' to answer without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> 1
Migrations for 'pybo':
  pybo\migrations\0003_auto_20221123_1515.py
    - Add field author to answer
    - Alter field question on answer

(myvenv) c:\projects\mysite>

이어서 migrate 명령을 실행합니다.

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

2. 뷰 수정

데이터베이스에 author 필드을 추가했으니 질문과 답변 저장시에 author 필드도 함께 저장해야 합니다.

2-1) views.py 의 answer_create 함수 수정

먼저 mysite\pybo\views.py의 answer_create 함수를 아래와 같이 수정합니다.

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:
            return HttpResponseNotAllowed('Only POST is possible.')
        context = {'question': question, 'form': form}
        return render(request, 'pybo/question_detail.html', context)

answer.author = request.user 에서 request.user 에는 사용자가 로그인 상태이면 User 객체가 들어가 있어 현재 로그인한 계정 정보를 answer.author 에 넣을 수 있습니다. 반대로 로그아웃 상태일 때는 AnonymousUser 객체가 들어가 있어 로그인 정보를 알 수 없습니다.

로그인 정보를 알 수 없을 때 answer.author = request.user 구문에서 오류가 발생합니다. 이 오류를 해결하기 위해선 로그아웃이 된 상태에서 해당 함수 answer_create 를 호출하면 로그인하는 페이지로 유도하여 로그인하게 끔 만들어 줘야 합니다.

views.py 파일에서 request.user를 사용하는 함수에 @login_required 애너테이션을 사용하면 로그아웃 상태일 때 해당 함수를 호출하면 자동으로 로그인 페이지로 이동할 수 있습니다.

사용 방법은 아래와 같습니다.

먼저 상단에 

from django.contrib.auth.decorators import login_required

추가합니다.

그리고 request.user 구문을 사용하는 함수 answer_create()와 question_create() 함수 위에 @login_required(login_url='common:login') 을 작성합니다.

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

<...생략...>

@login_required(login_url='common:login')
def question_create(request):
<...생략...>

이제 로그아웃 상태에서 @login_required 애노테이션이 적용된 함수가 호출되면 자동으로 로그인 화면으로 이동하게 됩니다. 

이렇게 수정한 후 로그아웃 상태에서 질문을 등록하거나 답변을 등록하면 자동으로 로그인 화면으로 이동합니다.

 

2-2) views.py 의 answer_create 함수 수정

question_create 함수도 마찬가지 방법으로 수정합니다.

@login_required(login_url='common:login')
def question_create(request):
    if request.method == 'POST':
        form = QuestionForm(request.POST)
        if form.is_valid():
            question = form.save(commit=False)
            question.author = request.user #author 속성에 로그인 계정 저장   추가!!!
            question.create_date = timezone.now()
            question.save()
            return redirect('pybo:index')
    else:
        form = QuestionForm()
    context = {'form': form}
    return render(request, 'pybo/question_form.html', context)

질문도 똑같이 현재 로그인한 계정 question.author = request.user 로 저장합니다.

이제 다시 서버를 재시작하고 로그인 후 질문 및 답변 등록을 테스트합니다. 

아직 템플릿을 수정하지 않았기 때문에 브라우저에서 확인되지 않고 DB에서 확인 가능합니다.

위와 같이 마지막 필드인 author 필드에 1인 admin의 id 값이 입력되었습니다.

 

3. next

로그아웃 상태에서 '질문 등록하기' 버튼을 눌러 로그인하면, 질문 등록하는 페이지가 아닌 다시 질문 리스트 페이지로 이동하게 됩니다. 로그인 성공 후 질문 등록하는 페이지로 넘어갈 수 있게 다음과 같이 템플릿 파일을 수정합니다.

mysite\template\common\login.html

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

위와 같이 설정하면 next 항목의 URL로 이동합니다.

 

4. 로그아웃 상태에서 답변 등록 비활성화 하기

질문 등록하기는 작성 전에 로그인이 이뤄지므로 작성 중인 글이 사라지지 않습니다.

그러나 답변 등록하기는 답변을 먼저 작성한 후 '답변등록' 버튼을 누르게 됩니다.

이 과정에서 로그아웃 상태일 경우 '답변등록' 버튼을 누르면 로그인 페이지로 넘어가 작성한 답변이 모두 사라지게 됩니다.

이 문제를 해결하기 위해 로그아웃 상태일 때는 답변 작성하는 곳을 비활성화 처리하면 됩니다.

mysite\templates\pybo\question_deatil.html 파일을 다음과 같이 수정합니다.

<!-- 생략 -->
{% endif %}
    <!-- 오류표시 끝 -->
     <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">
</form>
<!-- 생략 -->

{% if not user.is_authenticated %} 태그는 현재 사용자가 로그아웃 상태인지를 체크하는 태그입니다.

아래 사진과 같이 로그인 상태가 아닌 경우 textarea 태그에 disaled 속성을 적용하여 입력을 막았습니다.

이제 답변등록을 위해 로그인 버튼을 누르고 로그인하게 되면 아래와 같이 오류가 발생합니다.

이 오류가 발생하는 이유는 로그인 후에 답변등록 URL인 /answer/create가 GET 방식으로 호출되기 때문입니다.

앞전에 답변 등록시에는 POST가 아닌 경우 HttpResponsenotAllowed 오류를 발생하도록 코딩했습니다.

따라서 아래와 같이 pybo\views.py 파일의 answer_create 함수를 다음과 같이 수정해야 합니다.

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