[Python] django 폼 사용 방법 - 게시판 질문 등록
장고는 입력 처리에 대한 폼 기능을 제공합니다.
예를 들어, HTML의 <form>...</form> 태그 내에서 사용자가 데이터를 입력하고 서버로 데이터를 저장하는 역할과 비슷합니다.
장고에서의 폼은 기능은 아래와 같습니다.
- 필수 파라미터의 값이 누락되지 않았는지, 파라미터의 형식은 적절한지 등을 검증
- HTML을 자동으로 생성
- 폼에 연결된 모델을 이용하여 데이터를 저장하는 기능
장고의 폼은 일반 폼(forms.Form)과 모델 폼(forms.ModelForm)이 있습니다.
- 모델 폼은 모델(Model)과 연결된 폼으로 폼을 저장하면 연결된 모델의 데이터를 저장할 수 있습니다.
- 이너 클래스인 meta 클래스가 반드시 필요합니다. meta 클래스에는 사용할 모델과 모델의 속성을 적어야 합니다.
코딩에 앞서 장고는 MTV 설계 방식을 머릿속에 생각하고 계시면 편리합니다.
M - Model (데이터베이스)
T - Template (화면 - 프론트)
V - View (계산, 처리 - 백엔드)
설계 순서는 정해진 것은 없지만 저 같은 경우는 아래 순서로 진행합니다.
- Template - 화면 구성(템플릿, html) >> (URL 매핑)
- View - 계산, 처리 >> 뷰 함수 정의,
- 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>
이 글은 점프투장고 위키독스 내용과 인터넷 검색을 통한 제가 직접 공부한 것을 기록한 자료 입니다.
'IT > Python' 카테고리의 다른 글
[Python] 파이썬 여러 개의 이미지를 하나의 이미지로 합치는 방법 (0) | 2023.10.27 |
---|---|
[Python] django 내비게이션바 만들기 (2) | 2022.10.05 |
[Python] django 템플릿 상속 페이지 작성 (0) | 2022.09.19 |
[Python] django 부트스트랩(Bootstrap) 적용 (2) | 2022.09.19 |
[Python] django CSS 적용 방법 스태틱(static) (0) | 2022.09.19 |
댓글