Study/Django
[Django] 폼 (12)
taecongs
2023. 9. 15. 13:43

Django 공부하기 ✨
참고 사이트 : 파이보 (점프 투 장고)
점프 투 장고 이어서 진행하기
- 질문을 등록하는 기능에 필요한 작업을 진행 할 예정이다.
(1) 질문 등록
- question_list.html에 질문 등록하기 버튼을 생성한다.
// (1) projects\mysite\templates\pybo\question_list.html 파일 수정하기
<a href='{% url "pybo:question_create" %}' class='btn btn-primary'>질문 등록하기</a>
- pybo:question_create 별칭에 해당되는 URL 호출된다.
(2) URL 매핑
- pybo:question_create 별칭에 해당되는 URL 매핑 규칙을 추가한다.
// (2) projects\mysite\pybo\urls.py 파일 수정하기
urlpatterns = [
(... 생략 ...)
path('question/create/', views.question_create, name='question_create'),
]
- views.question_create 함수를 호출하도록 매핑했다.
(3) 폼(form)
- forms.py 파일을 생성한다.
// (3) projects\mysite\pybo\forms.py 파일 생성하기
from django import forms
from pybo.models import Question
class QuestionForm(forms.ModelForm):
class Meta:
model = Question # 사용할 모델
fields = ['subject', 'content'] # QuestionForm에서 사용할 Question 모델의 속성
- QuestionForm은 모델 폼(forms.ModelForm)을 상속했다.
- 장고의 폼은 일반 폼(forms.Form)과 모델 폼(forms.ModelForm)이 있다.
- 모델 폼은 모델(Model)과 연결된 폼으로 폼을 저장하면 연결된 모델의 데이터를 저장할수 있는 폼이다.
- 모델 폼은 이너 클래스인 Meta 클래스가 반드시 필요하다.
- Meta 클래스에는 사용할 모델과 모델의 속성을 적어야 한다.
(4) 뷰 함수
- render 함수에 전달한 {'form': form}은 템플릿에서 질문 등록시 사용할 폼 엘리먼트를 생성할 때 사용된다.
// (4) projects\mysite\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 // 추가된 항목
(... 생략 ...)
def question_create(request): // 추가된 항목
form = QuestionForm()
return render(request, 'pybo/question_form.html', {'form': form})
(5) 템플릿
- {{ form.as_p }}의 form은 question_create 함수에서 전달한 QuestionForm의 객체이다.
- {{ form.as_p }}는 폼에 정의한 subject, content 속성에 해당하는 HTML 코드를 자동으로 생성한다.
// (5) projects\mysite\templates\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 %}
- 현재 <form method="post">처럼 form 태그에 action 속성을 지정하지 않았다.
- 보통 form 태그에는 항상 action 속성을 지정하여 submit 실행시 action에 정의된 URL로 폼을 전송해야 한다.
- 하지만 현재 진행하고 있는 파트에서는 action 속성을 지정하지 않았다.
- form 태그에 action 속성을 지정하지 않으면 현재 페이지의 URL이 디폴트 action으로 설정된다.
(6) GET과 POST


- 현재는 저장하기 버튼을 클릭해도 아무런 반응이 없다.
- question_create 함수에 데이터를 저장하는 코드를 작성해야 한다.
// (6) projects\mysite\pybo\views.py 파일 수정하기
def question_create(request):
if request.method == 'POST':
form = QuestionForm(request.POST)
if form.is_valid(): # 폼이 유효하다면
question = form.save(commit=False) # 임시 저장하여 question 객체를 리턴받는다.
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)
- 질문 목록 화면에서 "질문 등록하기" 버튼을 클릭한 경우에는 /pybo/question/create/ 페이지가 GET 방식으로 요청되어 question_create 함수가 실행된다. 왜냐하면 <a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>와 같이 링크를 통해 페이지를 요청할 경우에는 무조건 GET 방식이 사용되기 때문이다.
- 질문 등록 화면에서 subject, content 항목에 값을 기입하고 "저장하기" 버튼을 누르면 이번에는 /pybo/question/create/ 페이지를 POST 방식으로 요청한다. 왜냐하면 form 태그에 action 속성이 지정되지 않으면 현재 페이지가 디폴트 action으로 설정되기 때문이다. 따라서 질문 등록 화면에서 "저장하기" 버튼을 클릭하면 question_create 함수가 실행되고 request.method 값은 POST가 되어 코드를 순차적으로 진행할 것이다.
- GET 방식에서는 form = QuestionForm() 처럼 QuestionForm을 인수 없이 생성했지만 POST 방식에서는 form = QuestionForm(request.POST) 처럼 request.POST를 인수로 생성했다. request.POST를 인수로 QuestionForm을 생성할 경우에는 request.POST에 담긴 subject, content 값이 QuestionForm의 subject, content 속성에 자동으로 저장되어 객체가 생성된다.
- 위의 코드를 추가하고 질문 등록의 제목과 내용을 작성한 뒤 저장하기 버튼을 클릭하면 정상적으로 추가된다.
(7) 폼 위젯
- widgets 속성을 지정하면 subject, content 입력 필드에 클래스를 추가할 수 있다.
// (7) projects\mysite\pybo\forms.py 파일 수정하기
class QuestionForm(forms.ModelForm):
class Meta:
(... 생략 ...)
widgets = {
'subject': forms.TextInput(attrs={'class': 'form-control'}),
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
}
(8) 폼 레이블
- 'Subject', 'Content'를 영문이 아닌 한글로 표시할 수 있다.
// (8) projects\mysite\pybo\forms.py 파일 수정하기
class QuestionForm(forms.ModelForm):
class Meta:
(... 생략 ...)
labels = {
'subject': '제목',
'content': '내용',
}
(9) 수동 폼 작성
- {{ form.as_p }}를 사용하면 빠르게 템플릿을 만들 수 있지만 HTML 코드가 자동으로 생성되기 때문에 커스텀에 어려움이 있을 수도 있다. 직접 HTML 코드를 작성해서 진행하는 것이 바람직하다.
// (9-1) projects\mysite\pybo\forms.py 파일 수정하기
from django import forms
from pybo.models import Question
class QuestionForm(forms.ModelForm):
class Meta:
model = Question # 사용할 모델
fields = ['subject', 'content'] # QuestionForm에서 사용할 Question 모델의 속성
widgets = {} // 제거
labels = {
'subject': '제목',
'content': '내용',
}
// (9-2) projects\mysite\templates\pybo\question_form.html 파일 수정하기
{% extends 'base.html' %}
{% block content %}
<div class="container">
(... 생략 ...)
<form method="post">
{% csrf_token %}
<!-- 오류표시 Start -->
{% if form.errors %}
<div class="alert alert-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="mb-3">
<label for="subject" class="form-label">제목</label>
<input type="text" class="form-control" name="subject" id="subject"
value="{{ form.subject.value|default_if_none:'' }}">
</div>
<div class="mb-3">
<label for="content" class="form-label">내용</label>
<textarea class="form-control" name="content"
id="content" rows="10">{{ form.content.value|default_if_none:'' }}</textarea>
</div>
(... 생략 ...)
</form>
</div>
{% endblock %}
- question_create 함수에서 form.is_valid() 가 실패할 경우 발생하는 오류의 내용을 표시하기 위해 오류를 표시하는 영역을 추가했다.
- {{ form.subject.value|default_if_none:'' }} 처럼 값을 대입해 주었는데 이것은 오류가 발생했을 경우 기존에 입력했던 값을 유지하기 위함이다.
- |default_if_none:''의 의미는 폼 데이터(form.subject.value)에 값이 없을 경우 None 이라는 문자열이 표시되는데 None 대신 공백으로 표시하라는 의미이다.
(10) 폼 레이블
- 'Subject', 'Content'를 영문이 아닌 한글로 표시할 수 있다.
// (10) projects\mysite\pybo\forms.py 파일 수정하기
class QuestionForm(forms.ModelForm):
class Meta:
(... 생략 ...)
labels = {
'subject': '제목',
'content': '내용',
}
(11) 답변 등록
- 답변을 등록할 때 사용할 AnswerForm을 pybo/forms.py 파일에 추가한다.
// (11-1) projects\mysite\pybo\forms.py 파일 수정하기
from django import forms
from pybo.models import Question, Answer # Answer 추가
(... 생략 ...)
class AnswerForm(forms.ModelForm):
class Meta:
model = Answer
fields = ['content']
labels = {
'content': '답변내용',
}
// (11-2) projects\mysite\pybo\views.py 파일 수정하기
from django.http import HttpResponseNotAllowed # 추가된 항목
from .forms import QuestionForm, AnswerForm # AnswerForm 추가
def answer_create(request, question_id):
"""
pybo 답변등록
"""
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)
- 답변 등록은 POST 방식만 사용된다.
- GET 방식으로 요청할 경우에는 HttpResponseNotAllowed 오류가 발생하도록 했다.
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
(... 생략 ...)
<form action="{% url 'pybo:answer_create' question.id %}" method="post" class="my-3">
{% csrf_token %}
<!-- 오류표시 Start -->
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% for field in form %}
{% if field.errors %}
<div>
<strong>{{ field.label }}</strong>
{{ field.errors }}
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
<!-- 오류표시 End -->
(... 생략 ...)
</form>
</div>
{% endblock %}