새소식

python/Django

Django - mysite (폼)

  • -

링크

이 페이지의 내용 정리 위캔입니다.
완성 소스 깃허브

질문 등록

  • 질문 등록 버튼 생성
  • 경로 : ...templates\pybo\question_list.html
...
</table>

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

</div>
{% endblock %}
  • <a href="">는 링크이지만 부트스트랩의 btn btn-primary 클래스를 적용하면 버튼으로 보인다.
  • 버튼을 클릭하면 pybo:question_create 별칭에 해당되는 URL을 호출한다.

1 URL 매핑

  • pybo:question_create 별칭에 해당되는 URL 매핑을 추가한다.
  • 경로 : ...misite\pybo\urls.py
...
urlpatterns =[
    ...
    # 추가
    path('question/create/', views.question_create, name='question_create'),
]

2 폼(form)

  • 폼은 페이지 요청시 전달되는 파라미터들을 쉽게 관리하기 위해 사용하는 클래스이다.

  • 폼은 필수 파라미터의 값이 누락되지 않았는지, 파라미터의 형식은 적절한지 등을 검증할 목적으로 사용된다.

  • HTML을 자동으로 생성하거나 폼에 연결된 모델을 이용하여 데이터를 저장할 수도 있다.

  • 질문 등록시 사용할 QuestionForm을 만든다.

  • froms.py 파일을 생성한다.

  • 경로 : ...mysite\pybo\forms.py

from dango import forms
from pybo.models import Question

class QuestionForm(forms.ModelsForm):
    class meta:
        model = Question # 사용할 모델
        fields = ['subject', 'content'] # QuestionForm에서 사용할 Question 모델의 속성
  • 모델 폼(forms.ModelsForm)을 상속했다.
  • 장고의 폼은 일반 폼(forms.Form)과 모델 폼(forms.ModelForm)이 있다.
    • 모델 폼은 모델과 연결된 폼을 저장하면 연결된 모델의 데이터를 저장할 수 있는 폼이다.
    • 모델 폼은 이너 클래스인 Meta클래스가 반드시 필요하다.
      • Meta 클래스에서는 사용할 모델과 모델의 속성을 적어야 한다.
  • QuestionForm은 Question 모델과 연결된 폼이고 속성으로 Question 모델의 subject와 content를 사용한다고 정의한것이다.

3 뷰 함수

  • views.question_create 함수를 작성한다.
  • 경로 : ...mysite\pybo\views.py
...
from .models import Question

# 추가
from .forms import QuestionForm

...
...
# 추가
def question_create(request):
    """
    pybo 질문 등록
    """
    form = QuestionForm()
    return render(request, 'pybo/question_form.html', {'form' : form})
  • question_create 함수는 위에서 작성한 QuestionForm을 사용했다.
  • render 함수에 전달한 {'form' : form'}은 템플릿에서 질문 등록시 사용할 폼 엘리먼트를 생성할때 쓰인다.

4 템플릿

  • pybo/question_form.html 템플릿을 작성한다
  • 경로 : ...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" 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}}는 폼에 정의한 subject, content 속성에 해당하는 HTML 코드를 자동으로 생성한다.
  • form태그에 action 속성을 지정하지 않으면 현재 페이지의 URL이 디폴트 action으로 설정된다.
  • action 속성을 명확하게 지정해도 되지만 그러면 question_form.html 템플릿은 "질문 등록"에서만 사용 가능하다. (다른 곳에서는 action 값을 다르게 해야하기 때문)

5 GET과 POST

  • forms.py와 같은 신규 파일을 작성했으므로 서버를 재시작해야 브라우저에서 확인이 가능하다.
  • 질문 등록하기 페이지에서 작성한 내용을 저장하기 위해 question_create 함수에 데이터를 저장하는 코드를 작성한다.
  • 경로 : ...mysite\pybo\views.py
def question_create(request):
    """
    pybo 질문등록
    """
    <!--함수 내용 수정 (기존 내용 삭제)-->
    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('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.create_date = timezone.now()
            question.save()
            return redirect('pybo:index')
  • GET 방식에서는 form = QuestionForm()처럼 QuestionForm을 인수없이 생성했다.
  • POST 방식에서는 form = QuestionForm(request.POST)처럼 request.POST를 인수로 생성했다.
  • request.POST에는 화면에서 사용자가 입력한 값이 담겨있다.
  • form.is_valid()는 form이 유효한지 검사한다.
    • 만약 form에 저장된 subject, content 값이 올바르지 않다면 form에는 오류 메시지가 저장되고 form.is_valid()가 실패하여 다시 질문 등록화면으로 돌아간다.
      • 이때 form에 저장된 오류 메시지는 질문 등록 화면에 표시된다.
    • form이 유요하다면 if form.ist_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 속성은 데이터 저장 시점에 자동 생성해야 하는 값이므로 QuestionForm에 등록하여 사용하지 않는다.
  • 저장이 완료되면 return redirect('pybo:index)를 호출하여 질문 목록 화면으로 이동한다.

6 폼 위젯

  • {{ form.as_p }} 태그는 HTML코드를 자동으로 생성하기 때문에 부트스트랩을 적용할 수가 없다.
  • 이를 해결하기 위해 QuestionForm을 수정한다.
  • 경로 : ...mysite\pybo\forms.py
...
class QuestionForm(forms.ModelForm):
    class Meta:
        ...
        fields = ['subject', 'content']

        # 추가
        widgets = {
            'subject': forms.TextInput(attrs={'class': 'form-control'}),
            'content': forms.TextInput(attrs={'class': 'form-control', 'rows': 10}),
        }
  • Meta 클래스의 widgets 속성을 지정하면 입력 필드에 form-control과 같은 부트스트랩 클래스를 추가할 수 있다.

7 폼 레이블

  • 질문 등록 화면에 표시되면 subject, content를 한글로 표시하고 싶으면 lables 속성을 지정하면 된다.
  • 경로 : ...\mysite\pybo\forms.py
...
...
 widgets = {
            'subject': forms.TextInput(attrs={'class': 'form-control'}),
            'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
        }

        # 추가
        labels = {
            'subject': '제목',
            'content': '내용',
        }  

8 수동 폼 작성

  • {{ form.as_p }}를 사용하면 빠르게 템플릿을 만들 수 있지만 HTML 코드가 자동 생성되므로 여러 제한이 생긴다.
  • 이번에는 자동이 아닌 직접 HTML 코드를 작성한다.
  • mysite\pybo\forms.py 파일의 widget 항목 제거
  • 경로 : ...templates\pybo\question_form.html
{% 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="from-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.as_p}}로 자동 생성되는 HTML 대신 제목과 내용에 해당되는 HTML 코드를 직접 작성했다.
  • question_create 함수에서 form.is_valid()가 실패할 경우 발생하는 오류의 내용을 표시하기 위해 오류를 표시하는 영역을 추가했다.
  • 제목 항목의 value에는 {{form.subject.value|default_if_none:''}}처럼 값을 대입 해주었는데 이것은 오류가 발생했을 경우 기존에 입력했던 값을 유지하기 위함이다.
    • |default_if_none:''의 의미는 폼 데이터(form.subject.value)에 값이 없을 경우 None이라는 문자열 대신 공백으로 표시하라는 의미이다.
    • 장고의 템플릿 필터는 위와 같이 ' | ' 기호와 함께 사용된다.

답변 등록

  • 답변 등록에 장고 폼을 적용한다.
  • 답변을 등록할때 사용할 AnswerFormpybo/form.py 파일에 작성
  • 경로 : ...mysite\pybo\forms.py
# 수정
from pybo.models import Question, Answer
...
...
# 추가
class AnswerForm(form.ModelForm):
    class Meta:
        model = Answer
        fields = ['content']
        labels = {
            'content': '답변내용',
        }
  • answer_create 함수 수정
  • 경로 : ...mysite\pybo\views.py
...
# 수정
from .forms import QuestionForm, AnswerForm
...
def answer_create(request, question_id):
    """
    pybo 답변 등록
    """
    question = get_object_or_404(Question, pk=question_id)

    # 추가
    if requet.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)
  • question_create와 같은 방법으로 AnswerForm을 이용하도록 변경했다.

    • 답변 등록은 POST 방식만 사용되기 때문에 if..else.. 구문에서 else는 호출되지 않는다.
      • 패턴의 동일성을 위해 남겨두었다.
  • 질문 상세 템플릿도 오류를 표시하기 위해 영역을 추가한다.

  • 경로 : ...templates\pybo\question_detail.html

...
{% csrf_token %}

# 추가
{% if form.errors %}
        <div class="alert alert-dange" role="alert">
            {% for field in form %}
                {% if field.errors %}
                <strong>{{field.label}}</strong>
                {{field.errors}}
                {% endif %}
            {% endfor %}
        </div>
        {% endif %}

        <div class="form-group">
반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.