[Django] 기본 구조 - 폼

haejun-kim·2020년 7월 8일
2

[Django]

목록 보기
10/20
post-thumbnail

사용자 웹 인터페이스를 구성하는 것 중에서 폼(Form)은 사용자의 입력을 받기 위한 필드나 위젯들의 묶음을 의미합니다. 우리가 자주 보는 로그인 화면이나, 글쓰기 화면 등에 데이터를 입력 받는 입력 칸들과 버튼으로 이루어져 있습니다. 현재 게시판에서 게시글을 등록할 수 있는 화면을 구현해보자. 폼의 기본적인 구성은 다음과 같다.

Html Form의 기본 형태

<form action=”데이터가 전달될 주소(요청/이동할 주소)” method=”http 요청 방식"> <input type=”text” name=”title”/>
<button type=”submit”>입력</button>
</form>

form이라는 태그 내에 input 태그, button 태그들로 구성되어 있고 form이 시작되는 form 태그 내부에서는 action과 method의 속성을 기술하도록 되어있다.

Action

입력되는 정보들을 받는 url

Method

HTTP 요청 방식에는 GET / POST / PUT / DELETE 가 있습니다. 보통은 GET / POST를 사용한다.

GET

주소가 노출되어도 괜찮고, 다른 사용자에게 공유가 가능한 정보를 처리할 때 사용

POST

회원가입이나 결제와 같은 다른 사용자와 결제해서는 안되는 정보를 처리할 때 사용

장고에서는 폼을 구성하기 쉽도록 프레임워크단에서 지원하고 있다. 특징은 다음과 같다.

  • 모델 클래스의 모델 정보들과 연동할 수 있다. (binding)
  • Validation 체크 (입력된 정보들의 유효성 검사)를 쉽게 해준다.
  • 악의적인 데이터를 필터링 한다. (sanitisation)
  • 짧고 간결한 코드로 폼 인터페이스를 구현한다.

질문 등록

게시판의 메인화면에서 질문등록 버튼을 눌렀을 때 질문등록하는 폼이 나오는 페이지가 출력되도록 하기 위해 우선 질문등록 버튼을 만들자.
/templates/review/question_list.html

</table>
    <a href="{% url 'review:question_create' %}" class="btn btn-primary">질문 등록</a>
</div>

url 추가

/review/urls.py

path("question/create/", views.question_create, name="question_create"),

views.py 추가

from .forms import QuestionForm

def question_create(request):
    """
    pybo 질문등록
    """
    form = QuestionForm()
    return render(request, 'pybo/question_form.html', {'form': form})

forms.py 추가

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 %}

0개의 댓글