이전포스트
[Django] 1. 개발 환경 설정 https://wroni.tistory.com/4
[Django] 2 - 1. URL과 View https://wroni.tistory.com/5
[Django] 2 - 2. 모델 https://wroni.tistory.com/6
[Django] 2 - 3. 장고 관리자 https://wroni.tistory.com/7
[Django] 2 - 4. 조회와 템플릿 https://wroni.tistory.com/8
[Django] 2 - 5. URL과 네임스페이스, 2 - 6. 데이터 저장 https://wroni.tistory.com/9
[Django] 2 - 7. 스태틱 https://wroni.tistory.com/10
[Django] 2 - 8. 부트스트랩 https://wroni.tistory.com/11
[Django] 2 - 9. 템플릿 상속 https://wroni.tistory.com/12
2 - 10. 폼
1. 질문 등록
"질문 등록하기" 버튼을 만들어서 질문 등록을 할 수 있도록 하자. 질문 목록 html 맨 하단에 버튼을 생성하면 된다.
파일 : C:\projects\mysite\templates\pybo\question_list.html
</table>
<a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>
</div>
{% endblock %}
<a href="...">과 같은 링크에 부트스트랩의 btn btn-primary 클래스를 적용하면 버튼으로 보인다.
버튼을 클릭하면 pybo:question_create 별칭에 해당되는 URL이 호출된다.
2. URL 매핑
이제 pybo:question_create 별칭에 해당하는 URL 매핑을 추가할 차례다.
파일 : C:\projects\mysite\pybo\urls.py
urlpatterns = [
(...생략...)
path('question/create/', views.question_create, name='question_create')
]
views.question_create 함수를 호출하도록 매핑하였다.
3. 폼(Form)
앞에서 views.question_create 함수를 호출하였으니 views.question_create 함수를 작성할 차례다.
먼저 뷰 함수를 작성하기 전에 폼(Form)에 대해 먼저 알아보자.
폼은 페이지 요청시 전달되는 파라미터들을 쉽게 관리하기 위해 사용하는 클래스이다. 폼은 필수 파라미터의 값이 누락되지 않았는지, 파라미터의 형식은 적절한지 등을 검증할 목적으로 사용한다. 이 외에도 HTML을 자동으로 생성하거나 폼에 연결된 모델을 이용하여 데이터를 저장할 수도 있다.
질문 등록에 사용할 QuestionForm을 만들면서 자세히 알아보자.
forms.py 파일을 pybo 하위에 새로 생성한다.
파일 : C:\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 클래스에는 사용할 모델과 모델의 속성을 적어야 한다.
즉, QuestionForm은 Question 모델과 연결된 폼이고 속성으로
Question 모델의 subject와 content를 사용한다고 정의한 것이다.
4. 뷰 함수
이제 views.question_create 함수를 작성할 차례이다.
파일 : C:\projects\mysite\pybo\views.py
import .forms import QuestionForm # 임포트하는 거 까먹지 말자
def question_create(request):
"""
pybo 질문등록
"""
form = QuestionForm()
return render(request, 'pybo/question_form.html', {'form' : form})
해당 코드를 추가하면 된다. question_create 함수에는 아까 forms.py에서 작성한 QuestionForm을 사용했다.
render 함수에 전달한 {'form' : form} 은 템플릿에서 질문 등록할 때 사용하는 폼 엘리먼트를 생성할 때 쓰인다.
5. 템플릿
이제 템플릿을 작성하면 된다. pybo/question_form.html 템플릿을 다음과 같이 작성한다.
파일 : C:\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" 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 }} 는 폼에 정의한 subject, content 속성에 해당하는 HTML 코드를 자동으로 생성한다.
사실 해당 템플릿에서는 action 속성을 지정하지 않았다.
form 태그에 action 속성을 지정하지 않으면 현재 페이지의 URL이 디폴트 action으로 설정된다.
이후에 "질문 수정" 기능을 구현할 때에도 question_form.html 템플릿을 사용할 것이다.
질문 수정 템플릿에서는 action 값을 다르게 해야 하기 때문에 이처럼 form에 action 속성을 종종 비워둔다.
6. GET과 POST
지금까지 진행한 것을 브라우저에서 확인하자.
먼저 질문 목록 페이지를 요청해보자
"질문 등록하기" 버튼이 추가된 것을 확인할 수 있다. 이제 이 버튼을 클릭해보자.
"질문 등록" 페이지가 잘 나타나고 잇다. 이제 subject와 content에 아무 값이나 입력하기 "저장하기"를 클릭해보자.
아무런 반응이 없다. 아직 question_create 함수에 데이터를 저장하는 코드를 작성하지 않았기 때문이다.
이제 question_create 함수에 데이터 저장하는 코드를 작성해보자.
파일 : C:\projects\mysite\pybo\views.py
def question_create(request):
"""
pybo 질문등록
"""
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)
코드를 살펴보자. 동일한 URL 요청을 POST, GET 방식에 따라 다르게 처리하고 있다.
[질문 목록 - 질문 등록하기 (request.method == GET)]
질문 목록 화면에서 "질문 등록하기" 버튼을 클릭한 경우에는 /pybo/question/create/ 페이지가 GET 방식으로 요청된다.
question_list.html에서 <a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a> 과 같이 링크를 통해 페이지를 요청할 경우에는 무조건 GET 방식이 사용된다.
따라서 이러한 경우는 request.method 값이 GET이 되어 else 구문을 타게 된다.
[질문 등록 - 저장하기 (request.method == POST)]
질문 등록 화면에서 subject, content 항목에 값을 기입하고 "저장하기" 버튼을 클릭하면 이번에는 /pybo/question/create/ 페이지가 POST 방식으로 요청된다.
앞에서 question_form.html에서 form 태그에 action 속성이 지정되지 않았으면 현재 페이지가 디폴트로 action으로 설정되기 때문이다.
그래서 질문 등록 화면에서 "저장하기" 버튼을 클릭하면 question_create 함수가 실행된 후 request.method 값은 POST가 되어 if 구문을 타게 된다.
GET 방식에서는 form = QuestionForm() 처럼 QuestionForm을 인수 없이 생성했다면,
POST 방식에서는 form = QuestionForm(request.POST) 처럼 request.POST 인수로 생성했다.
POST 방식에서 request.POST에 담긴 subject, content 값이 QuestionForm의 subject, content 속성에 자동으로 저장되어 객체가 생성된다.
[유효성 검사]
form.is_valid()는 form이 유효한지 검사한다. form에 저장된 subject, content 값이 올바르지 않다면 form에 오류 메세지가 저장되고 form.is_valid()가 실패하여 다시 질문 등록 화면으로 돌아간다. 이때 form에 저장된 오류 메세지는 질문 등록 화면에 표시될 것이다.
form이 유효하다면 if form.is_valid(): 이후의 문장이 수행되어 질문 데이터가 생성된다.
question = form.save(commit=False)는 form으로 Question 데이터를 저장하기 위한 코드이다.
QuestionForm이 Question 모델과 연결된 모델폼이기 때문에 이와 같이 사용 가능하다.
[임시저장]
여기서 commit=False는 임시저장을 의미한다. 즉, 실제 데이터는 아직 DB에 저장되지 않은 상태이다.
form.save(commit=False) 대신 form.save()를 수행하면 Question 모델의 create_date에 값이 없다는 오류가 발생한다.
QuestionForm 모델에는 subject, content만 있기 때문이다. 이러한 이유로 임시 저장 후 question 객체 리턴받아 create_date에 값을 설정한 후 question.save()로 실제 저장하는 것이다.
마지막으로 저장이 완료되면 return redirect('pybo:index') 호출하여 질문 목록화면으로 이동한다.
이제 브라우저에서 질문등록이 잘 되는지 확인해보자. 질문 목록에서 질문 등록하기 버튼을 눌러 질문 등록 화면으로 이동하자.
내용을 적고 저장하기 버튼을 클릭하자
잘 되고 있다.
7. 폼 위젯
질문 등록도 잘 된다. 하지만 부트스트랩을 적용하여 화면을 깔끔하게 하고 싶은데,,, {{ form.as_p }} 태그는 HTML 코드를 자동으로 생성해서 부트스트랩을 적용할 수가 없다.
이를 해결하기 위해 QuestionForm을 조금 수정해보자.
파일 : C:\projects\mysite\pybo\forms.py
class QuestionForm(forms.ModelForm):
class Meta:
model = Question # 사용할 모델
fields = ['subject', 'content'] # QuestionForm에서 사용할 Question 모델의 속성
widgets = {
'subject' : forms.TextInput(attrs={'class' : 'form-control'}),
'content' : forms.Textarea(attrs={'class' : 'form-control', 'rows' : 10}),
}
위와 같이 Meta 클래스의 widgets 속성을 지정하면 입력 필드에 form-control 과 같이 부트스트랩 클래스를 추가할 수 있다.
8. 폼 레이블
질문 등록에 표시되는 'subject', 'content'가 아니라 '제목'과 '내용'으로 표시하고 싶다. 그렇다면 label을 사용하면 된다.
class QuestionForm(forms.ModelForm):
class Meta:
model = Question # 사용할 모델
fields = ['subject', 'content'] # QuestionForm에서 사용할 Question 모델의 속성
widgets = {
'subject' : forms.TextInput(attrs={'class' : 'form-control'}),
'content' : forms.Textarea(attrs={'class' : 'form-control', 'rows' : 10}),
}
labels = {
'subject' : '제목',
'content' : '내용',
}
이제 제목과 내용으로 잘 나올 것이다.
9. 수동 폼 작성
{{ form.as_p }}를 사용하면 템플릿을 빠르게 만들 수 있지만 HTML 코드가 자동으로 생성되어 디자인 측면에서 제약이 생긴다. 이번에는 폼을 이용하여 자동으로 HTML 코드를 생성하지 않고 직접 HTML 코드를 작성하는 방법을 사용하자.
우선 forms.py 파일의 widget 항목을 제거한다.
파일 : C:\projects\mysite\pybo\forms.py
class QuestionForm(forms.ModelForm):
class Meta:
model = Question # 사용할 모델
fields = ['subject', 'content'] # QuestionForm에서 사용할 Question 모델의 속성
# widgets 내용 삭제
widgets = {
'subject' : forms.TextInput(attrs={'class' : 'form-control'}),
'content' : forms.Textarea(attrs={'class' : 'form-control', 'rows' : 10}),
}
labels = {
'subject' : '제목',
'content' : '내용',
}
그리고 질문 등록 템플릿을 다음과 같이 수정한다.
파일 : C:\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" class="post-form my-3">
{% csrf_token %}
<!-- 오류표시 Start -->
{% 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 %}
<!-- 오류표시 End -->
<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>
<button type="submit" class="btn btn-primary">저장하기</button>
</form>
</div>
{% endblock %}
{{ form.as_p }} 로 자동으로 생성되는 HTML 대신 제목과 내용에 해당하는 HTML 코드를 직접 작성한 것이다.
그리고 question_create 함수에서 form.is_valid()가 실패할 경우 발생하는 오류의 내용을 표시하기 위해 오류 표시 영역을 추가했다.
제목(subject) 항목에 value에는 {{ form.subject.vlaue|default_if_none:'' }} 처럼 값을 대입해주었다. 이는 오류가 발생했을 경우 기존에 입력한 값을 유지하기 위한 것이다.
|default_if_none:'' 의 의미는 폼 데이터 form.subject.value 에 값이 없을 경우 None 이라는 문자열이 표시되는데, None 대신 공백으로 표시하라는 의미의 템플릿이다.
이제 수정하고 웹 서버를 구동해보자. 제목은 TEST를 입력하고 내용은 비워둔 채 저장하기 버튼을 클릭해보자.
내용에 아무것도 입력하지 않았으니 내용을 입력하라는 오류메세지가 뜨고 있다. 그리고 제목 TEST도 그대로 유지되는 것을 확인할 수 있다.
10. 답변 등록
질문 등록에 장고 폼을 적용한 것처럼 답변 등록에도 장고 폼을 적용하자. 답변을 등록할 때처럼 AnswerForm을 pybo/forms.py 파일에 다음과 같이 작성하자.
파일 : C:\projects\mysite\pybo\forms.py
import pybo.models import Question, Answer # Answer 임포트 까먹지 말자
class AnswerForm(forms.ModelForm):
class Meta:
model = Answer
fields = ['content']
labels = { 'content' : '답변내용',}
그리고 answer_create 함수를 수정하자
파일 : C:\projects\mysite\pybo\views.py
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:
form = AnswerForm()
context = {'question' : question, 'form' : form}
return render(request, 'pybo/question_detail.html', context)
question_create와 같은 방법으로 AnswerForm을 이용하도록 변경한다.
그리고 질문 상세 템플릿도 오류를 표시하기 위한 영역을 다음처럼 추가하자.
파일 : C:\projects\mysite\templates\pybo\question_detail.html
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
(..생략..)
{% csrf_token %}
{% 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">
<textarea name="content" id="content" class="form-control" rows="10"></textarea>
</div>
<input type="submit" value="답변등록" class="btn btn-primary">
</form>
</div>
{% endblock %}
이렇게 수정하고 웹 서버를 구동하자. 만약 답변 내용 없이 답변을 등록하려고 하면 오류 메세지가 나타날 것이다.
※ 본 내용은 django 공부 기록이며, 점프 투 장고를 참고하였습니다.
https://wikidocs.net/book/4223
'Django > Django 기초' 카테고리의 다른 글
[Django] 3 - 2. 페이징 (0) | 2021.12.02 |
---|---|
[Django] 3 - 1. 네비게이션바 (0) | 2021.12.02 |
[Django] 2 - 9. 템플릿 상속 (0) | 2021.11.30 |
[Django] 2 - 8. 부트스트랩 (0) | 2021.11.29 |
[Django] 2 - 7. 스태틱 (0) | 2021.11.29 |