Study/Django

[Django] 폼 (12)

taecongs 2023. 9. 15. 13:43

Django 공부하기 ✨
참고 사이트 : 파이보 (점프 투 장고)


점프 투 장고 이어서 진행하기

  • 질문을 등록하는 기능에 필요한 작업을 진행 할 예정이다.

 

(1) 질문 등록

  • question_list.html질문 등록하기 버튼을 생성한다.
// (1) projects\mysite\templates\pybo\question_list.html 파일 수정하기
<a href='{% url "pybo:question_create" %}' class='btn btn-primary'>질문 등록하기</a>
  • pybo:question_create 별칭에 해당되는 URL 호출된다.

 

(2) URL 매핑

  • pybo:question_create 별칭에 해당되는 URL 매핑 규칙을 추가한다.
// (2) projects\mysite\pybo\urls.py 파일 수정하기
urlpatterns = [
    (... 생략 ...)
    path('question/create/', views.question_create, name='question_create'),
]
  • views.question_create 함수를 호출하도록 매핑했다.

 

(3) 폼(form)

  • forms.py 파일을 생성한다.
// (3) 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 클래스에는 사용할 모델과 모델의 속성을 적어야 한다.

 

(4) 뷰 함수

  • render 함수에 전달한 {'form': form}은 템플릿에서 질문 등록시 사용할 폼 엘리먼트를 생성할 때 사용된다.
// (4) 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})

 

(5) 템플릿

  • {{ form.as_p }}의 form은 question_create 함수에서 전달한 QuestionForm의 객체이다. 
  • {{ form.as_p }}는 폼에 정의한 subject, content 속성에 해당하는 HTML 코드를 자동으로 생성한다.
// (5) 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">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="btn btn-primary">저장하기</button>
    </form>
</div>
{% endblock %}
  • 현재 <form method="post">처럼 form 태그에 action 속성을 지정하지 않았다.
  • 보통 form 태그에는 항상 action 속성을 지정하여 submit 실행시 action에 정의된 URL로 폼을 전송해야 한다. 
  • 하지만 현재 진행하고 있는 파트에서는  action 속성을 지정하지 않았다. 
  • form 태그에 action 속성을 지정하지 않으면 현재 페이지의 URL이 디폴트 action으로 설정된다.

 

(6) GET과 POST

  • 현재는 저장하기 버튼을 클릭해도 아무런 반응이 없다.
  • question_create 함수에 데이터를 저장하는 코드를 작성해야 한다.
// (6) projects\mysite\pybo\views.py 파일 수정하기
def question_create(request):
    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')
    else:
        form = QuestionForm()
    context = {'form': form}
    return render(request, 'pybo/question_form.html', context)
  • 질문 목록 화면에서 "질문 등록하기" 버튼을 클릭한 경우에는 /pybo/question/create/ 페이지가 GET 방식으로 요청되어 question_create 함수가 실행된다. 왜냐하면 <a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>와 같이 링크를 통해 페이지를 요청할 경우에는 무조건 GET 방식이 사용되기 때문이다.
  • 질문 등록 화면에서 subject, content 항목에 값을 기입하고 "저장하기" 버튼을 누르면 이번에는 /pybo/question/create/ 페이지를 POST 방식으로 요청한다. 왜냐하면 form 태그에 action 속성이 지정되지 않으면 현재 페이지가 디폴트 action으로 설정되기 때문이다. 따라서 질문 등록 화면에서 "저장하기" 버튼을 클릭하면 question_create 함수가 실행되고 request.method 값은 POST가 되어 코드를 순차적으로 진행할 것이다.
  • GET 방식에서는 form = QuestionForm() 처럼 QuestionForm을 인수 없이 생성했지만 POST 방식에서는 form = QuestionForm(request.POST) 처럼 request.POST를 인수로 생성했다. request.POST를 인수로 QuestionForm을 생성할 경우에는 request.POST에 담긴 subject, content 값이 QuestionForm의 subject, content 속성에 자동으로 저장되어 객체가 생성된다.
  • 위의 코드를 추가하고 질문 등록의 제목과 내용을 작성한 뒤 저장하기 버튼을 클릭하면 정상적으로 추가된다.

 

(7) 폼 위젯

  • widgets 속성을 지정하면 subject, content 입력 필드에 클래스를 추가할 수 있다.
// (7) projects\mysite\pybo\forms.py 파일 수정하기

class QuestionForm(forms.ModelForm):
    class Meta:
       (... 생략 ...)
        widgets = {
            'subject': forms.TextInput(attrs={'class': 'form-control'}),
            'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
        }

 

(8) 폼 레이블

  • 'Subject', 'Content'를 영문이 아닌 한글로 표시할 수 있다.
// (8) projects\mysite\pybo\forms.py 파일 수정하기

class QuestionForm(forms.ModelForm):
    class Meta:
       (... 생략 ...)
        labels = {
            'subject': '제목',
            'content': '내용',
        }

 

(9) 수동 폼 작성

  • {{ form.as_p }}를 사용하면 빠르게 템플릿을 만들 수 있지만 HTML 코드가 자동으로 생성되기 때문에 커스텀에 어려움이 있을 수도 있다. 직접 HTML 코드를 작성해서 진행하는 것이 바람직하다.
// (9-1) 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 = {}  // 제거

        labels = {
            'subject': '제목',
            'content': '내용',
        }
// (9-2) projects\mysite\templates\pybo\question_form.html 파일 수정하기

{% extends 'base.html' %}
{% block content %}
<div class="container">
    (... 생략 ...)
    
    <form method="post">
        {% 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">
            <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>
        
        (... 생략 ...)
    </form>
</div>
{% endblock %}
  • question_create 함수에서 form.is_valid() 가 실패할 경우 발생하는 오류의 내용을 표시하기 위해 오류를 표시하는 영역을 추가했다.
  • {{ form.subject.value|default_if_none:'' }} 처럼 값을 대입해 주었는데 이것은 오류가 발생했을 경우 기존에 입력했던 값을 유지하기 위함이다. 
  • |default_if_none:''의 의미는 폼 데이터(form.subject.value)에 값이 없을 경우 None 이라는 문자열이 표시되는데 None 대신 공백으로 표시하라는 의미이다.

 

(10) 폼 레이블

  • 'Subject', 'Content'를 영문이 아닌 한글로 표시할 수 있다.
// (10) projects\mysite\pybo\forms.py 파일 수정하기

class QuestionForm(forms.ModelForm):
    class Meta:
       (... 생략 ...)
        labels = {
            'subject': '제목',
            'content': '내용',
        }

 

(11) 답변 등록

  • 답변을 등록할 때 사용할 AnswerForm을 pybo/forms.py 파일에 추가한다.
// (11-1) projects\mysite\pybo\forms.py 파일 수정하기
from django import forms
from pybo.models import Question, Answer   # Answer 추가

(... 생략 ...)

class AnswerForm(forms.ModelForm):
    class Meta:
        model = Answer
        fields = ['content']
        labels = {
            'content': '답변내용',
        }
// (11-2) projects\mysite\pybo\views.py 파일 수정하기
from django.http import HttpResponseNotAllowed   # 추가된 항목
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:
        return HttpResponseNotAllowed('Only POST is possible.')
    context = {'question': question, 'form': form}
    return render(request, 'pybo/question_detail.html', context)
  • 답변 등록은 POST 방식만 사용된다.
  • GET 방식으로 요청할 경우에는 HttpResponseNotAllowed 오류가 발생하도록 했다.
{% 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 -->
        
    (... 생략 ...)
    
    </form>
</div>
{% endblock %}