● 폼

- 질문 등록

[파일이름: projects\mysite\templates\pybo\question_list.html]

 

{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
(...생략...)
    </table>
    <a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a> #추가
</div>
{% endblock %}

** <a href="..."> 과 같은 링크이지만 부트스트랩의 btn btn-primary 클래스를 적용하면 버튼 적용
** 버튼을 클릭 시 pybo:question_create 별칭에 해당되는 URL 호출


- URL 매핑

[파일명: projects\mysite\pybo\urls.py]

 

from django.urls import path
from . import views

app_name = 'pybo' #앱의 네임스페이스 부여

urlpatterns = [
    path('',views.index, name='index'), #name속성 부여
    path('<int:question_id>/', views.detail, name='detail'), #name 속성 부여
    path('answer/create/<int:question_id>/', views.answer_create, name='answer_create'),
    path('question/create/', views.question_create, name='question_create'), #질문 추가
]

** views.question_create 함수를 호출하도록 매핑


- 폼(Form)

페이지 요청시 전달되는 파라미터들을 쉽게 관리하기 위해 사용하는 클래스
필수 파라미터의 값이 누락되지 않았는지, 파라미터의 형식은 적절한지 등을 검증할 목적으로 사용
HTML을 자동으로 생성하거나 폼에 연결된 모델을 이용하여 데이터를 저장하는 기능도 있다.

[파일명: projects\mysite\pybo\forms.py] 생성
from django import forms
from pybo.models import Question

class QuestionForm(forms.ModelForm): #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를 사용한다고 정의한 것이다.


- 뷰 함수

[파일명: 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})

** question_create 함수는 위에서 작성한 QuestionForm을 사용
** render 함수에 전달한 {'form': form}은 템플릿에서 질문 등록시 사용할 폼 엘리먼트를 생성할 때 쓰인다.


- 템플릿

[파일명: projects\mysite\templates\pybo\question_form.html] 생성
{% extends 'base.html' %}
{% block content %}
<div class="container">
    <h5 class="my-3 border 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.as_p }}의 form은 question_create 함수에서 전달한 QuestionForm의 객체
** {{ form.as_p }}는 폼에 정의한 subject, content 속성에 해당하는 HTML 코드를 자동으로 생성


- 질문등록 저장하기

[파일명: projects\mysite\pybo\views.py] 수정
def question_create(request):
    if request.method =='POST': #form 사용자가 입력한 내용들이 담긴다.
        form = QuestionForm(request.POST) #정확한 데이터인지 유효성 검사
        if form.is_valid(): #is_valid()는 form의 데이터가 유효한지 검사
            question = form.save(commit=False)
            #form에 지정된 데이터로 Question데이터를 저장하기 위한 코드, 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 요청 방식에 따라 다르게 처리한 부분이다.

 

질문 목록 화면에서 "질문 등록하기" 버튼을 클릭한 경우에는 

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

 

왜냐하면 <a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>와 같이

링크를 통해 페이지를 요청할 경우에는 무조건 GET 방식이 사용되기 때문이다.

 

따라서 이 경우에는 request.method 값이 GET이 되어 if .. else .. 구문에서

else 구문을 타게 되어 질문을 등록하는 화면을 랜더링한다.

 

그리고 질문 등록 화면에서 subject, content 항목에 값을 기입하고 "저장하기" 버튼을 누르면

이번에는 /pybo/question/create/ 페이지를 POST 방식으로 요청한다.

 

왜냐하면 앞서 설명했듯이 form 태그에 action 속성이 지정되지 않으면

현재 페이지가 디폴트 action으로 설정되기 때문이다.

 

따라서 질문 등록 화면에서 "저장하기" 버튼을 클릭하면 question_create 함수가 실행되고 

request.method 값은 POST가 되어 다음 코드 블록이 실행될 것이다.

 

    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')

 

GET 방식에서는 form = QuestionForm() 처럼 QuestionForm을 인수 없이 생성했지만

POST 방식에서는 form = QuestionForm(request.POST) 처럼 request.POST를 인수로 생성했다. 

request.POST를 인수로 QuestionForm을 생성할 경우에는 request.POST에 담긴

subject, content 값이 QuestionForm의 subject, content 속성에 자동으로 저장되어 객체가 생성된다.

request.POST에는 화면에서 사용자가 입력한 내용들이 담겨있다.

 

그리고 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는 임시 저장을 의미한다.

 

즉, 실제 데이터는 아직 데이터베이스에 저장되지 않은 상태를 말한다.

여기서 form.save(commit=False) 대신 form.save()를 수행하면

Question 모델의 create_date에 값이 없다는 오류가 발생할 것이다.

 

왜냐하면 QuestionForm에는 현재 subject, content 속성만 정의되어 있고 create_date 속성은 없기 때문이다.

이러한 이유로 임시 저장을 하여 question 객체를 리턴받고 create_date에 값을 설정한 후 

question.save()로 실제 데이터를 저장하는 것이다.

create_date 속성은 데이터 저장 시점에 생성해야 하는 값이므로 QuestionForm에 등록하여 사용하지 않는다

** 질문 등록하기 버튼 선택 후 글작성 및 저장이 가능하다.


- 폼 위젯

{{ form.as_p }} 태그는 HTML 코드를 자동으로 생성하기 때문에 부트스트랩 적용이 불가하다.
완벽하지는 않지만 다음처럼 QuestionForm을 조금 수정하면 어느정도 해결 가능

[파일명: 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 = {
            'subject': forms.TextInput(attrs={'class':'form-control'}),
            'content': forms.Textarea(attrs={'class': 'form-control','rows':10}),
        }

** widgets 속성을 지정하면 subject, content 입력 필드에 form-control과 같은 부트스트랩 클래스 추가 가능


- 폼 레이블

질문 등록 화면에 표시되는 'Subject', 'Content'를 영문이 아닌
한글 표시를 하고 싶다면 labels 속성이 필요하다.

 

[파일명: 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 = {
            'subject': forms.TextInput(attrs={'class':'form-control'}),
            'content': forms.Textarea(attrs={'class': 'form-control', 'rows':10}),
        }
        labels = {
            'subject' : '제목',
            'content' : '내용',
        }

 

** labels 속성 지정하여 내용 변경

** 장고 폼(form) 관련 URL : https://docs.djangoproject.com/en/4.0/topics/forms/

 


- 수동 폼 작성

{{ form.as_p }}를 사용하면 빠르게 템플릿을 만들 수 있지만 

HTML 코드가 자동으로 생성되므로 디자인 측면에서 제한이 발생한다.
폼을 이용하여 자동으로 HTML 코드를 생성하지 말고 직접 HTML 코드를 작성하는 방법에 대해 알아보자.

[파일명: projects\mysite\pybo\forms.py]
widget 속성 제거)
from django import forms
from pybo.models import Question

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

 


 

- 질문 템플릿 수정

[파일명: projects\mysite\templates\pybo\question_form.html]
{% extends 'base.html' %}
{% block content %}
<div class="container">
    <h5 class="my-3 border border-bottom pb-2">질문 등록</h5>
    <form method="post">
        {% csrf_token %} <!-- form의 post 방식일 때 무조건 작성 필요 -->
        <!-- 오류표시 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>
        <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.value|default_if_none:'' }} 처럼 값을 대입해 주었는데 

이것은 오류가 발생했을 경우 기존에 입력했던 값을 유지하기 위함이다.

 

** |default_if_none:''의 의미는 폼 데이터(form.subject.value)에 값이 없을 경우 

None 이라는 문자열이 표시되는데 None 대신 공백으로 표시하라는 의미의 템플릿 필터

 

** 장고의 템플릿 필터는 |default_if_none:'' 처럼 | 기호와 함께 사용된다.

** 내용에는 아무 내용도 입력하지 않았기 때문에 저장하기 버튼 선택 시 오류메시지 출력, 제목은 그대로 유지된다.


- 답변 등록

[파일명: projects\mysite\pybo\forms.py]
from django import forms
from pybo.models import Question, Answer

(.. 생략 ..)

class AnswerForm(forms.ModelForm):
    class Meta:
        model = Answer #사용할 모델
        fields = ['content'] #사용할 모델
        labels = {
            'content' : '내용',
        }

 


- answer_create 함수 수정

[파일명: projects\mysite\pybo\views.py]
(... 생략 ...)
from django.http import HttpResponseNotAllowed
from .forms import QuestionForm, 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)

** question_create와 같은 방법으로 AnswerForm을 이용하도록 변경
** 답변 등록은 POST 방식만 사용되기 때문에 GET 방식으로 요청 시 HttpResponseNotAllowed 오류 발생하도록 설정

 


- 질문 상세 템플릿도 오류를 표시하기 위한 영역 수정

[파일명: projects\mysite\templates\pybo\question_detail.html]
{% 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 -->
        <div class="mb-3">
            <textarea name="content" id="content" class="form-control" rows="10"></textarea>
        </div>
        <input type="submit" value="답변등록" class="btn btn-primary">
    </form>
</div>
{% endblock %}

 

 

** 답변 내용 없이 등록 시 오류 메시지 출력

 

 

 

 

 

'Web > Django' 카테고리의 다른 글

Django - 페이징  (0) 2024.05.27
Django - 내비게이션바  (0) 2024.05.27
Django - 템플릿 상속  (0) 2024.05.18
Django - 부트스트랩  (0) 2024.05.18
Django - 스태틱  (0) 2024.05.18

+ Recent posts