Web/Django

장고(Django) GET과 POST를 활용하여 폼(Form) 작업하기

daeunnniii 2021. 8. 3. 03:44
728x90
반응형

1. 질문 등록하기 버튼 생성

폼 실습을 하기 위해 먼저 질문 등록하는 부분을 구현해주어야한다. 먼저 question_list.html에 "질문 등록하기"버튼을 만들어준다. 이 버튼을 클릭할 경우 question_create 별칭에 해당하는 URL이 호출되므로 해당 URL 별칭을 가진 URL 매핑을 추가해야한다.

<a href="{% url 'notes:question_create' %}" class="btn btn-primary">질문 등록하기</a>

 

2. URL 매핑 추가

/notes/urls.py에 다음과 같이 url 매핑을 추가한다. 해당 url이 요청되었을 경우 views.question_create 함수가 호출되므로 이제 views.py에서 question_create 함수를 작성해주어야한다.

urlpatterns = [
    (... 생략 ...)
    path('question/create/', views.question_create, name='question_create'),
]

 

3. 폼(Form)

views.py를 수정해주기 전에, 먼저 폼(form)에 대해서 알아보자.

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

 

그러면 질문 등록 시 사용할 QuestionForm을 만들어보자. notes/forms.py 파일을 생성해서 작성한다.

from django import forms
from .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

이제 notes/views.py에 question_create 함수를 작성한다. 위에서 작성했던 QuestionForm을 사용했다. 그리고 render 함수의 {'form': form} 은 템플릿에서 질문 등록 시 사용할 form element를 생성할 때 쓰인다.

from .forms import QuestionForm

def question_create(request):
    form = QuestionForm()
    return render(request, 'notes/quetion_form.html', {'form': form})

 

5. 템플릿 작성 - question_form.html

이제 질문을 등록할 수 있는 템플릿을 작성할 것이다. question_form.html을 생성해서 다음과 같이 작성한다. 여기서 {{ form.as_p }}는 폼에 정의한 subject, content 속성에 해당하는 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 태그에는 원래 action 속성을 지정해야하지만, 만약 action 속성을 지정하지 않으면 현재 페이지의 URL이 디폴트 action으로 설정된다. 위 코드에서 form 태그에 action 속성을 지정하지 않은 이유는 이 템플릿을 "질문 등록"이외에도 "질문 수정" 등에 사용하기 위해서이다.

만약 action="{% url 'notes:question_create' %}"로 명확히 지정해버리면 질문 등록에서만 사용할 수 있게 된다.

 

6. GET과 POST

아직 구현하지 않은 부분은 form에 입력한 데이터를 모델에 저장하는 부분이다. 이제 이 부분을 구현해볼 것이다. notes/views.py에서 question_create 함수에 데이터를 저장하는 코드를 작성한다.

def question_create(request):
    if request.method == 'POST':
        form = QuestionForm(request.POST)
        if form.is_valid():		#form이 유효한지 검사.
            question = form.save(commit=False)
            question.create_date = timezone.now()
            question.save()
            return redirect('notes:index')
    else:
        form = QuestionForm()
    context = {'form': form}
    return render(request, 'notes/question_form.html', context)

질문 목록 화면에서 "질문 등록하기" 버튼을 클릭한 경우에는 /notes/question/create/ 페이지가 GET 방식으로 요청되어 question_create 함수가 실행된다. 왜냐하면 <a href="{% url 'notes:question_create' %}" class="btn btn-primary">질문 등록하기</a>과 같이 링크를 통해 페이지를 요청할 경우에는 무조건 GET 방식이 사용되기 때문이다. 즉, 이 경우에는 request.method 값이 GET이므로 else문으로 분기하고, 결국 질문 등록 화면을 보여주게 될 것이다.

그리고 질문 등록 화면에서 subject, content 항목에 값을 작성하고 "저장하기" 버튼을 클릭하면 이번에는 동일한 /notes/question/create/ 페이지가 POST방식으로 요청된다. 왜냐하면 form 태그에 action 속성이 지정되지 않으면 현재 페이지가 디폴트 action으로 설정되기 때문이다.

 

GET 방식에서는 form = QuestionForm() 처럼 QuestionForm을 인수 없이 생성했다. 하지만 POST 방식에서는 form = QuestionForm(request.POST) 처럼 request.POST를 인수로 생성했다. request.POST를 인수로 QuestionForm을 생성할 경우에는 request.POST에 담긴 subject, content 값이 QuestionForm의 subject, content 속성에 자동으로 저장되어 객체가 생성된다.

 

form.is_valid()는 form이 유효한지 검사하여 값이 올바르지 않으면 form에 오류 메세지가 저장되고, 다시 등록 화면으로 돌아가게 된다. 이때 form에 저장된 오류 메세지는 질문 등록 화면에 표시된다.

 

question = form.save(commit=False)는 form으로 Question 데이터를 저장하기 위한 코드이다.

commit=False는 데이터베이스에 저장하지 않은 임시 저장을 하는 것인데, 바로 form.save()를 수행해버리면 create_date 값이 없다는 오류가 발생하게 된다.

그리고 마지막으로 저장이 완료되면 return redirect('pybo:index')를 호출하여 질문 목록 화면으로 이동한다.

 

7. 폼 스타일 수정

위에서 사용한 {{ form.as_p }} 태그는 HTML 코드를 자동으로 생성하기 때문에 부트스트랩을 적용할 수가 없다. 폼 스타일을 적용하기 위한 방법은 2가지가 있다.

1) QuestionForm 폼 수정(forms.py)

2) 수동 폼 작성

 

1) forms.py에서 수정

notes/forms.py에서 QuestionForm을 다음과 같이 수정해보자. Meta 클래스의 widgets 속성을 지정하면 입력 필드에 form-control과 같은 부트스트랩 클래스를 추가할 수 있다! 또한 질문 등록 화면에서 표시되는 'Subject', 'Content'를 한글로 표시하고 싶다면 labels 속성을 지정하면 된다.

from django import forms
from .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': '내용',
        }

 

2) 수동 폼 작성

아래 코드는 {{ form.as_p }}로 인해 자동으로 생성되는 HTML 대신 이것을 직접 작성한 것이다.

아래 코드에 대한 자세한 설명은 (https://wikidocs.net/70855)에서 참고하자.

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

 

 

 

 

참고: https://wikidocs.net/70855

728x90
반응형