사용자 웹 인터페이스를 구성하는 것 중에서 폼(Form)
은 사용자의 입력을 받기 위한 필드나 위젯들의 묶음을 의미합니다. 우리가 자주 보는 로그인 화면이나, 글쓰기 화면 등에 데이터를 입력 받는 입력 칸들과 버튼으로 이루어져 있습니다. 현재 게시판에서 게시글을 등록할 수 있는 화면을 구현해보자. 폼의 기본적인 구성은 다음과 같다.
<form action=”데이터가 전달될 주소(요청/이동할 주소)” method=”http 요청 방식"> <input type=”text” name=”title”/>
<button type=”submit”>입력</button>
</form>
form이라는 태그 내에 input 태그, button 태그들로 구성되어 있고 form이 시작되는 form 태그 내부에서는 action과 method의 속성을 기술하도록 되어있다.
입력되는 정보들을 받는 url
HTTP 요청 방식에는 GET / POST / PUT / DELETE 가 있습니다. 보통은 GET / POST를 사용한다.
주소가 노출되어도 괜찮고, 다른 사용자에게 공유가 가능한 정보를 처리할 때 사용
회원가입이나 결제와 같은 다른 사용자와 결제해서는 안되는 정보를 처리할 때 사용
장고에서는 폼을 구성하기 쉽도록 프레임워크단에서 지원하고 있다. 특징은 다음과 같다.
게시판의 메인화면에서 질문등록 버튼을 눌렀을 때 질문등록하는 폼이 나오는 페이지가 출력되도록 하기 위해 우선 질문등록 버튼을 만들자.
/templates/review/question_list.html
</table>
<a href="{% url 'review:question_create' %}" class="btn btn-primary">질문 등록</a>
</div>
/review/urls.py
path("question/create/", views.question_create, name="question_create"),
from .forms import QuestionForm
def question_create(request):
"""
pybo 질문등록
"""
form = QuestionForm()
return render(request, 'pybo/question_form.html', {'form': form})
from django import forms
from review.models import Question
class QuestionForm(forms.ModelForm):
class Meta:
model = Question
fields = ['subject', 'content']
장고의 폼은 일반 폼(forms.Form)과 모델 폼(forms.ModelForm)이 있는데 모델 폼은 모델(Model)과 연결된 폼으로 폼을 저장하면 연결된 모델의 데이터를 저장할 수 있게 된다. 모델 폼은 class Meta 라는 내부(Inner) 클래스가 반드시 필요하다. Meta 클래스에는 사용할 모델과 모델의 속성을 적어주어야 한다.
{% extends 'base.html' %}
{% block content %}
<div class="container">
<h5 class="my-3 border-bottom pb-2">질문등록</h5>
<form method="post" class="post-form my-3">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">저장하기</button>
</form>
</div>
{% endblock %}
{{ form.as_p }}
의 form은 question_create
함수에서 전달된 QuestionForm
의 객체이다. {{ form.as_p }}
는 제목과 내용같은 폼 입력항목을 위한 HTML코드들을 자동으로 만들어 낸다.
요기까지 작성하고 실행해보자.
질문 등록 버튼이 생겼고 버튼을 눌러보면
이렇게 {{ form.as_p }}
로 생성된 폼이 나온다. 아직 데이터 저장하는 코드를 작성하지 않아서 작성해서 저장해도 쓴 내용이 저장되진 않는다. 이제 저장하는 코드를 작성해보자
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("review:index")
else:
form = QuestionForm()
context = {"form": form}
return render(request, "review/question_form.html", context)
목록조회 화면에서 "질문 등록하기" 버튼을 클릭한 경우에는 http://localhost:8000/pybo/question/create/ 페이지가 GET 방식으로 호출(request.method == 'GET')되어 질문등록 화면이 호출되고 질문등록 화면에서 "저장하기" 버튼을 클릭하면 http://localhost:8000/pybo/question/create/ 페이지가 POST 방식으로 호출(request.method == 'POST')되어 데이터가 저장된다.
※ form태그의 action속성이 없는 경우는 현재의 페이지로 전송된다.
POST 요청인 경우 QuestionForm은 다음처럼 request.POST를 사용하여 생성해야 한다.
form = QuestionForm(request.POST)
request.POST 를 사용하여 QuestionForm을 생성하면 QuestionForm의 subject와 content에는 request.POST로 전달받은 데이터가 저장되게 된다.
is_valid
는 폼으로 전송된 입력 항목의 값들이 유효한지를 검사한다. 만약 유효하지 않다면 오류의 내용이 form에 저장 되어 리턴될 것이다.
폼 저장시 question = form.save(commit=False) 처럼 commit=False라는 옵션을 사용하면 폼에 연결된 모델을 저장하지 않고 생성된 모델 객체만 리턴해 준다. 만약 여기서 form.save(commit=False) 대신 form.save() 를 수행하면 create_date에 값이 없다는 오류가 발생하게 될 것이다.
이렇게 코드내에서 자동으로 생성되는 값(예:create_date)을 저장하기 위해서는 form.save(commit=False)를 사용해야 한다.
/forms.py
class QuestionForm(forms.ModelForm):
class Meta:
model = Question
fields = ['subject', 'content']
widgets = {
'subject': forms.TextInput(attrs={'class': 'form-control'}),
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
}
한글 제목으로 바꾸기 -> 라벨
labels = {
'subject': '제목',
'content': '내용',
}
이제 페이지를 출력해보면 다음과 같은 결과를 얻을 수 있다.
현재 {{ form.as_p }}
태그를 사용해서 자동완성 태그를 사용했기때문에 내가 원하는대로 꾸미는데 제한이 있다는 단점이 존재한다. 그래서 수동으로 html 파일을 수정해보자.
/question_form.html
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% for field in form %}
{% if field.errors %}
<strong>{{ field.label }}</strong>
{{ field.errors }}
{% endif %}
{% endfor %}
</div>
{% endif %}
<div class="form-group">
<label for="subject">제목</label>
<input type="text" class="form-control" name="subject" id="subject"
value="{{ form.subject.value|default_if_none:'' }}">
</div>
<div class="form-group">
<label for="content">내용</label>
<textarea class="form-control" name="content"
id="content" rows="10">{{ form.content.value|default_if_none:'' }}</textarea>
</div>
제목(subject) 항목의 value에는 {{ form.subject.value|default_if_none:'' }}
처럼 값을 대입해 주었는데 이것은 오류가 발생했을 경우 기존에 입력했던 값을 유지하기 위함이다. |default_if_none:''
의 의미는 폼 데이터(form.subject.value)에 값이 없을 경우 None 이라는 문자열이 표시되는데 None 대신 공백으로 표시하기 위한 필터이다.
등록된 질문에 답변도 등록할 수 있어야 게시판 다운 기능이라 볼 수 있다. 답변을 등록하는 기능 또한 장고 폼을 이용해서 구현해보자.
/fomrs.py
class AnswerForm(forms.ModelForm):
class Meta:
model = Answer
fields = ['content']
labels = {
'content': '답변내용',
}
/views.py
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:
form = AnswerForm()
context = {'question': question, 'form': form}
return render(request, 'pybo/question_detail.html', context)
/qeustion_detail_html
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% for field in form %}
{% if field.errors %}
<strong>{{ field.label }}</strong>
{{ field.errors }}
{% endif %}
{% endfor %}
</div>
{% endif %}