본문 바로가기

[Python] django 폼 사용 방법 - 게시판 질문 등록

액트 2022. 9. 28.
반응형

장고는 입력 처리에 대한 폼 기능을 제공합니다.

예를 들어, HTML의 <form>...</form> 태그 내에서 사용자가 데이터를 입력하고 서버로 데이터를  저장하는 역할과 비슷합니다.

장고에서의 폼은 기능은 아래와 같습니다.

  1. 필수 파라미터의 값이 누락되지 않았는지, 파라미터의 형식은 적절한지 등을 검증
  2. HTML을 자동으로 생성
  3. 폼에 연결된 모델을 이용하여 데이터를 저장하는 기능

장고의 폼은 일반 폼(forms.Form)과 모델 폼(forms.ModelForm)이 있습니다.

  • 모델 폼은 모델(Model)과 연결된 폼으로 폼을 저장하면 연결된 모델의 데이터를 저장할 수 있습니다.
  • 이너 클래스인 meta 클래스가 반드시 필요합니다. meta 클래스에는 사용할 모델과 모델의 속성을 적어야 합니다.

코딩에 앞서 장고는 MTV 설계 방식을 머릿속에 생각하고 계시면 편리합니다.

M - Model (데이터베이스)

T - Template (화면 - 프론트)

V - View (계산, 처리 - 백엔드)

설계 순서는 정해진 것은 없지만 저 같은 경우는 아래 순서로 진행합니다.

  1. Template - 화면 구성(템플릿, html) >> (URL 매핑)
  2. View - 계산, 처리 >> 뷰 함수 정의, 
  3. Model 정의 

실제 이뤄지는 순서입니다.

사용자가 화면을 띄워

① 화면을 보고(html)

② 버튼을 클릭하고 (URL매핑)

③ 데이터를 입력하고 수정하고 삭제하는 등의 행위를 하고 (View) 

④ 데이터 베이스에 저장합니다.


그럼 이제 장고의 폼을 사용하기 위해서 질문 등록 페이지를 만들어 보겠습니다.

먼저, 질문을 등록하려면 "질문 등록하기" 버튼을 만들어야 합니다. 

question_list.html 템플릿 파일에 아래와 같이 </table> 구문 아래 "질문 등록하기" 버튼을 생성합니다.

 

① 템플릿 파일에서 질문 등록하기 버튼 만들기

mysite\templates\question_list.html 파일 편집

...
...
...

</table>
    <a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>
</div>
 {% endblock %}

class="btn btn-primary" 구문은 부트스트랩 클래스를 적용하여 버튼 모양으로 처리한 것입니다.

{% url 'pybo:question_create' %} 구문은 URL별칭에 해당되는 것으로 question_create URL를 호출합니다.

 

② URL 매핑

mysite\pybo\urls.py 파일 수정

pybo:question_create 별칭에 해당되는 URL 매핑 규칙을 추가합니다.

mysite\pybo\urls.py 파일의 urlpatterns 부분을 아래와 같이 question_create 의 정의 항목을 추가합니다.

urlpatterns = [
	...
	path('question/create/', views.question_create, name='question_create'),
]

views.question_create 함수를 호출하도록 매핑했습니다.

이제 함수 views.question_create 를 정의합니다.

 

③ 뷰 함수 정의

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

from .forms import QuestionForm

폼 QuestionForm 은 아래에서 정의할 예정입니다.

def question_create(request):
    if request.method == 'POST':
        form = QuestionForm(request.POST)
        if form.is_valid():
            question = form.save(commit=False)
            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_create 함수 분석

request.method 방식에 따라 처리 방법을 다르게 합니다. 즉, POST, GET 요청 방식에 달라집니다.

위에서 만든 question_list.html 템플릿 파일의 질문 등록하기 버튼을 클릭한 경우

/pybo/question/create/ 페이지가 GET 방식으로 요청되어 question_create 함수가 실행됩니다.

따라서 이 경우에는 request.method 값이 GET이 되어 if..else.. 구문에서 else 구문을 타게 되어 질문을 등록하는 화면을 렌더링 render(request, 'pybo/question_form.html', centext) 합니다.

바로 question_form.html 화면을 호출하는 것입니다.

else:
    form = QuestionForm()
context = {'form': form}return render(request, 'pybo/question_form.html', context)

그럼 질문을 등록하는 화면을 위한 페이지를 구현해 보겠습니다. 

 

템플릿 정의

mysite\template\pybo\question_form.html 파일을 생성합니다.

아래와 같이 작성합니다.

{% extends 'base.html' %}
{% block content %}
<div class="container">
    <h5 class="my-3 border-bottom pb-2">질문등록</h5>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="btn btn-primary">저장하기</button>
    </form>
</div>
{% endblock %}

base.html 템플릿에서 저장한 base.html를 가져옵니다.

{% block conten %}부터 {% endblock %} 까지 해당 question_form.html 페이지에서 사용할 body 내용입니다.

{{% form.as_p }}의 form은 question_create 함수에서 전달한 QuestionForm의 객체입니다.

class QuestionForm(forms.ModelForm):
    class Meta:
    model = Question #사용할 모델
    fields =  ['subject', 'content'] #QustionForm에서 사용할 Question 모델의 속성
    labels = {
		'subject': '제목',
		'content': '내용',
        }

{{ form.as_p }}는 폼에 정의할 subject, content 속성에 해당하는 HTML 코드를 자동으로 생성합니다.

 

④ 데이터 저장을 위한 폼 정의

이제 views.py 에서 사용했던(from .forms import QuestionFrom) 폼을 정의할 차례입니다.

forms.py 파일을 생성합니다. 경로는 mysite\pybo\forms.py 입니다.

아래와 같이 작성합니다.

from django import forms
from pybo.models import Question

class QuestionForm(forms.ModelForm):    
    class Meta:
    model = Question #사용할 모델
    fields =  ['subject', 'content'] #QustionForm에서 사용할 Question 모델의 속성

class QuestionForm(forms.ModelForm) << 모델 폼을 상속받았습니다.

처음에 설명해 드린 바와 같이 QuestionForm은 Question 모델과 연결된 폼이고 (from pybo.medels import Question)

이너 클래스인 class meta를 선언해 주었습니다. 그리고 meta 클래스에 사용할 폼 medel = Question을 정의해주었고

모델의 속성인 fields 값에 subject와 content를 정의해주었습니다.


다시 mysite\pybo\views.py 파일을 확인합니다.

질문 등록 페이지에서 subject, content 항목에 값을 기입하고 "저장하기" 버튼을 누르면 이번에는 /pybo/question/create/ 페이지를 POST 방식으로 요청합니다. 

GET 방식에서는 form = questionForm() 처럼 QuestionForm을 인수 없이 생성합니다.

if request.method == 'POST':
    form = QuestionForm(request.POST)
    if form.is_valid():
        question = form.save(commit=False)
        question.create_date = timezone.now()
        question.save()
        return redirect('pybo:index')

그러나, POST 방식에서는 form = QuestionForm(request.POST) 처럼 request.POST를 인수로 받습니다. request.POST를 인수로 QuestionForm을 생성할 경우에는 request.POST에 담긴 subject, content 값이 QuestionForm의 subject, content 속성에 자동으로 저장되어 객체가 생성됩니다. 

        if form.is_valid():
            question = form.save(commit=False)
            question.create_date = timezone.now()
            question.save()
            return redirect('pybo:index')

그리고 form.is_valid()는 form이 유효한지를 검사합니다. 만약 form에 저장된 subject, content의 값이 올바르지 않다면 form에는 오류 메시지가 저장되고 form.is_valid()가 실패하여 다시 질문 등록 화면을 렌더링 합니다.

form이 유효하다면 if form.is_valid(): 이후의 문장이 수행되어 질문 데이터가 생성됩니다.

question = form.save(commit=False)

question = form.save(commit=False)는 form에 저장된 데이터로 Question 데이터를 저장하기 위한 코드입니다. 

commit=False는 임시저장을 뜻합니다. 즉 실제 데이터는 아직 데이터베이스에 저장되지 않은 상태를 말합니다.

여기서 form.save(commit=False) 대신 form.save()를 수행하면 Question 모델의 create_date에 값이 없다는 오류가 발생할 것입니다. 이유는 QuestionForm에는 현재 subject, content 속성만 정의되어 있고 create_date 속성 값은 없기 때문입니다. 

이러한 이유로 임시 저장을 하고 question 객체를 리턴 받고 create_date 값을 설정한 후 question.save()로 실제 데이터를 저장하는 것입니다. 


이제 위 두 구문을 아래와 같이 추가합니다.

전체 코드

from django.shortcuts import render

# Create your views here.

from django.shortcuts import render, get_object_or_404, redirect
from .models import Question
from django.utils import timezone
from .forms import QuestionForm


def index(request):
    question_list = Question.objects.order_by('-create_date')
    context = {'question_list': question_list}
    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}
    return render(request, 'pybo/question_detail.html', context)


def answer_create(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    question.answer_set.create(content=request.POST.get('content'), create_date=timezone.now())
    return redirect('pybo:detail', question_id=question.id)


def question_create(request):
    if request.method == 'POST':
        form = QuestionForm(request.POST)
        if form.is_valid():
            question = form.save(commit=False)
            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)

 

질문 등록에 장고 폼을 적용한 것처럼 답변 등록에도 장고 폼을 적용합니다.

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

from pybo.models import Question, Answer   ##Answer 추가

#추가
class AnswerForm(forms.ModelForm):
    class Meta:
        model = Answer
        fields = ['content']
        labels = {
            'content': '답변내용',
        }

mysite\pybo\views.py 파일의  answer_create 함수를 다음과 같이 수정합니다.

from django.http import HttpResponseNotAllowed  #추가
from .forms import QuestionForm, AnswerForm   #AnswerForm 추가



(생략) #아래 추가
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.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)

question_create와 같은 방법으로 AnswerForm을 이용하도록 변경했습니다. 

그리고 질문 상세 템플릿도 오류 표시를 위하여 아래와 같이 변경합니다.

mysite\templates\pybo\question_detail.html

   <!-- 답변 등록 -->
    <form action="{% url 'pybo:answer_create' question.id %}" method="post" class="my-3">
        {% csrf_token %}
        <!-- 오류 표시 시작 -->
        {% if form.errors %}
        <div class="alert altert-danger" role ="alert">
            {% for field in form %}
            {% if field.errors %}
            <div>
                <strong>{{ field.label }}</strong>
                {{ field.errors }}
            </div>
            {% endif %}
            {% endfor %}
        </div>
        {% endif %}
        <!-- 오류표시 End -->
        <div class="form-group">
            <textarea name="content" id="content" class="form-control" rows="10"></textarea>
        </div>
        <input type="submit" value="답변등록" class="btn btn-primary">
    </form>
</div>

 

 

이 글은 점프투장고 위키독스 내용과 인터넷 검색을 통한 제가 직접 공부한 것을 기록한 자료 입니다.

반응형

댓글