본문 바로가기

개발/Django

Django 웹사이트 만들기 9. 질문 등록 기능 만들기

이번에는 사용자가 질문을 등록하는 기능을 만들어보자.

 

1. 질문 등록 기능 만들기

1-1. 질문 등록 버튼 만들기

question_list.html 템플릿을 열고 </table> 태그 아래에 질문 등록 버튼을 생성한다. 

(... 생략 ...)
    </table>
    # 추가된 코드 start
    <a href="{% url 'pyweb:question_create' %}" class="btn btn-primary">
        질문등록
    </a>
    # 추가된 코드 end
</div>

 

1-2. URL 매핑 추가를 위해 pyweb/urls.py 수정하기

{% url 'pyweb:question_create' %}이 추가되었으니 pyweb/urls.py 파일에 URL 매핑을 추가하자.

from django.urls import path
from . import views

app_name = 'pyweb'

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('answer/create/<int:question_id>/', views.answer_create, name='answer_create'),
    path('question/create/', views.question_create, name='question_create'),
]

 

1-3. pyweb/views.py 수정하기

질문등록 버튼을 클릭하면 실행될 views.question_create 함수를 작성한다.

from .forms import QuestionForm
(... 생략 ...)

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

Question_create 함수는 QuestionForm 클래스로 생성한 객체 form을 사용할 것이다. QuestionForm 클래스는 질문을 등록하기 위한 장고의 폼이다. render 함수에 전달한 {'form': form}은 템플릿에서 폼 엘리먼트를 생성할 때 사용한다. 

 

 

1-4. pyweb/forms.py에 장고 폼 작성하기

pyweb 디렉터리 아래 froms.py 파일을 새로 만들고 ModelForm을 상속받은 QuestionForm 클래스를 작성하자. QuestionForm 클래스 안에 내부 클래스로 Meta 클래스를 작성하고, Meta 클래스 안에는 model, fields 속성을 작성한다. 

#위치 : mysite/pyweb/forms.py

from django import forms
from pyweb.models import Question

class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question
        fields = ['subject', 'content']

이와 같은 클래스를 장고 폼이라고 한다. 장고 폼은 2개의 폼으로 구분이 되는데, forms.Form을 상속받으면 폼, forms.ModelForm을 상속받으면 모델 폼이라 부른다. 

위의 forms.py는 forms.ModelForm을 상속받아 모델 폼을 만들었다. 모델 폼 객체를 저장하면 연결된 모델의 데이터를 저장할 수 있다.

 

내부클래스의 Meta 클래스의 경우, 장고의 모델 폼은 내부 클래솔 Meta 클래스를 반드시 가져야하며, Meta 클래스에는 모델 폼이 사용할 모델과 모델의 필드들을 적어야 한다. QuestionForm 클래스는 Question 모델과 연결되어 있으며, 필드로 subject와 content를 갖는다. 

 

1-5. pyweb/question_form.html 만들어 장고 폼 사용하기

질문 등록을 위해 pyweb/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 %}

 

1-6. 질문 등록 화면 확인하기

질문등록 버튼을 클릭하면 아래와 같은 화면이 나오고 값을 입력하고 <저장하기> 버튼을 눌러보자.

버튼을 누르면 아무 반응이 없을 것이다. 왜냐하면 pyweb/views.py 파일에 정의한 question_create 함수에 입력 데이터를 저장하기 위한 코드를 작성하지 않았기 때문이다. 이제 입력데이터를 저장하는 방법을 알아보자.

 

 

1-7. 입력 데이터 저장하기

pyweb/views.py 파일의 question_create 함수를 수정하자.

def question_create(request):
    """
    pyweb 질문등록
    """
    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('pyweb:index')
    else:
        form = QuestionForm()   #GET인 경우 호출
    context = {'form':form}
    return render(request, 'pyweb/question_form.html', context)

 

질문 목록 화면에서 <질문등록> 버튼을 누르면 /pyweb/question/create/가 GET 방식으로 요청되어 질문등록 화면이 나타나고, 질문등록 화면에서 입력값을 채우고 <저장하기> 버튼을 누르면 /pyweb/question/create/가 POST 방식으로 요청되어 데이터가 저장된다. 

 

 question = form.save(commit=False)는 form으로 Question 모델 데이터를 저장하기 위한 코드이다. 여기서 commit=False는 임시저장을 의미한다. 임시저장을 사용하는 이유는 폼으로 질문 데이터를 저장할 경우 Question 모델의 create_date에 값이 설정되지 않아 오류가 발생하기 때문이다.

이러한 이유로 임시 저장을 한 후 qeustion 객체를 반환받아 create_date에 값을 설정한 후 question.save()로 실제 저장하는 것이다.

 

 

아래와 같이 새로운 질문이 정상적으로 등록되었다.

 

1-8. 폼에 부트스트랩 적용하기

{{ form.as_p }} 태그는 form 엘리먼트와 입력 항목을 자동으로 생성해 주므로 편리하지만 부트스트랩을 적용할 수 없다는 단점이 있다. 

완벽하지는 않지만, QuestionForm 클래스 내부에 있는 Meta 클래스에 widget  속성을 다음과 같이 추가하면 이 문제를 해결할 수 있다.

 

from django import forms
from pyweb.models import Question

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}),
        }

 

다음과 같이 부트스트랩이 적용된 것을 볼 수 있다.

 

 

1-10. label 속성 수정하여 Subject, Content 한글로 변경하기

화면의 Subject, Content를 한글로 표기하고 싶다면 label 속성을 지정하면 된다.

from django import forms
from pyweb.models import Question

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': '내용',
        }

 

다음과 같이 변경된다.

 

 

1-11. 수작업으로 폼 작성하기

{{ form.as_p }}를 사용하면 빠르게 템플릿을 만들 수 있지만,  HTML 코드가 자동으로 생성되므로 디자인 측면에서 많은 제한이 생기게 된다. 

이번에는 수작업으로 HTML 코드를 작성하는 법을 배워보자. 우선 forms.py 파일의 widget 항목을 제거하자.

 

from django import forms
from pyweb.models import Question

class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question
        fields = ['subject', 'content']
        labels = {
            'subject': '제목',
            'content': '내용',
        }

 

그다음 질문등록 템플릿을 다음과 같이 수정한다.

{% 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 : form.is_valid()가 실패했을 때 오류 표시를 위한 코드 -->
        {% 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.content.value|default_if_none:'' }} 에서 default_if_none:'' 는 form.subject.value에 값이 없으면 'None'이라는 문자열이 표시되는데, 이는 공백을 표시하기 위해 사용한 템플릿 필터이다. 

수정 후 질문등록 화면에서 제목만 입력하고 <저장하기> 버튼을 누르면 다음과 같은 화면을 볼 수 있다.

필수 항목을 입력하라는 오류 메시지가 뜨고 오류가 발생해도 이미 입력한 제목에 해당하는 값은 value 설정으로 인해 그대로 유지되는 것을 확인할 수 있다.

 

 

2. 답변등록 기능에 장고 폼 적용하기

2-1. AnswerForm 클래스를 추가하고 answer_create함수 수정하기

답변 등록 기능에도 장고 폼을 적용하자. 답변을 등록할 때 사용하는 AnswerForm 클래스를 pyweb/forms.py 파일에 다음과 같이 작성하자.

 

from django import forms
from pyweb.models import Question, Answer

class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question
        fields = ['subject', 'content']
        labels = {
            'subject': '제목',
            'content': '내용',
        }

#추가
class AnswerForm(forms.ModelForm):
    class Meta:
        model = Answer
        fields = ['content']
        labels = {
            'content': '답변내용',
        }

 

그다음 pyweb/views.py 파일의 answer_create 함수를 수정한다. 

from .forms import QuestionForm, AnswerForm
(... 생략 ...)

def answer_create(request, question_id):
    """
    pyweb 답변등록
    """
    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('pyweb:detail', question_id=question.id)
    else:
        form = AnswerForm()  # GET인 경우 호출
    context = {'question': question, 'form':form}
    return render(request, 'pyweb/question_detail.html', context)

 

2-2. 질문 상세 템플릿에 오류 표시 영역 추가하기

 <form action="{% url 'pyweb:answer_create' question.id %}" method="post" class="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 %}
   (... 생략 ...)
</form>
(... 생략 ...)

 

 

수정하고 답변 내용없이 답변등록을 클릭하면 아래와 같은 오류메시지가 나타난다.