python/Django

[Django] 10 . 표준 HTML으로 만들기

끼발자 2021. 12. 28. 16:11
반응형

책에서는 눈치챘는지 모르지만~ 으로 시작하지만

 

전혀 눈치채지 못 했다.

충격

 

표준 HTML 구조란 html, head, body 엘리먼트가 있어야하고,

 

CSS 파일은 head 엘리먼트 안에 있어야하며 meta, title이 포함되어야 한다고한다.

 

이렇게 표준으로 바뀌면 중복되는 부분도 많고 수정하기위해선 하나하나 뜯어보고 고쳐야 한다.

 

하지만 장고는 상속기능을 제공해서 표준HTML 을 사용하고 템플릿 상속도 사용하자.

 

templates/base.html 파일을 만들고 안을 다음과같이 채우자.

{% load static %}
<!doctype html>
<html lang="ko">
<head>
    <!-- Required meta tags -->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width", initial-scale="1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
    <!-- pybo CSS -->
    <link rel="stylesheet" type="text/css" href="{% static 'style.css' %}">
    <title>Hello, pybo!</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>

 

<body> 엘리먼트에 block content / endblock가 있는데, 이 base.html을 상속받은 템플릿들이 저 자리를 채우게된다.

 

base로 기본 뼈대를 만들었으니 question_list.html을 수정하자.

 

{% extends 'base.html' %}
{% block content %}
<!--{% load static %}-->
<!--<link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">-->
<div class="container my-3">
    <table class="table">
    ....
    ...
    ...
</div>
{% endblock %}

기존에 있던 코드는 삭제하거나 주석처리하고 위와같이 extends 로 상속받고, 블럭을 통해 채울 부분을 만든다

 

question_detail.html도 동일하게 바꿔주자.

 

부트 스트랩을 사용하고있으니 style.css 안에 있는 내용을 전부 주석처리하자.

지울 필요는 없다고하니 일단 

/*textarea {
    width:100%;
}

input[type=submit] {
    margin-top:10px;
} */

이렇게 바꿔주자.

 

다음은 질문등록 버튼을 만들어보자.

 

question_list.html 에서 마지막에 다음과같이 질문 등록버튼을 추가하자.

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

 btn btn-primary는 부트스트랩 클래스이다.

 

url에 새로운 url을 연결하려고하니 urls.py에서 question_create를 연결해주자.

다시 한번 상기시키자면, pybo/로 시작하는 모든 url은 pybo/urls.py에서 추가하고 관리한다.

 

그러니  pybo/urls.py를 열어 다음과 같이 수정하자.


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

 

그럼 다음은 기능을 담당하는 views.py에서 question_create 함수를 만들자.

 

# pybo/views.py
from .forms import QuestionForm
...
...

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

 

새로운 모듈이 생겼는데, QuestionForm이다.

 

아직 만들지 않았으니 에러가 나는게 당연하다. 미리 설명하자면 QuesionForm은 질문을 등록하기 위해 사용하는 장고의 form이다.

 

이제 pybo/forms.py을 새로 만들어서 form을 생성하자.

 

# pybo/forms.py
from django import forms
from pybo.models import Question

class QuestionForm(forms.ModelForm): # forms.ModelForm을 상속받은 모델 폼
    class Meta: # 장고 모델폼은 Meta 내부 클래스를 반드시 포함해야함. 모델 폼이 사용할 모델과 필드를 작성.
        model = Question
        fields = ['subject','content']

장고의 forms를 상속받는 클래스를 장고 폼이라고 한다. 이렇게 모델폼을 상속받으면

 

연결된 모델의 데이터를 저장할 수 있다. 특이한 점은 Meta라는 내부 클래스를 가지는데,

 

장고의 모델폼은 반드시 Meta 클래스를 내부 클래스로 가지고 있어야한다. form의 내용이 여기에 담기는데,

 

Question모델과 연결되어있으며, 제목과 내용을 필드로 가진다.

 

이제 폼을 사용할 차례다.

 

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가 방금 question_create 에서 전달한 QuestionForm 객채이다. ( 이름은 form으로 넣었다. )

 

이제 질문 등록하기 버튼이 생성될 것이다.

 

버튼을 누르면 다음과 같이

질문 등록 페이지가 표시된다!

 

물론, 아직은 기능을 다 하진 못한다. 입력만 받을 뿐, 저장하는 기능이 구현되어있지 않기때문에.

 

그렇다면 기능을 담당하는 pybo/views.py에서 기능을 추가해주자.

 

def question_create(request):
    '''
    pybo 질문 등록
    '''
    if request.method =='POST':
        form = QuestionForm(request.POST)
        if form.is_valid(): # form이 유효한지 검증
            question = form.save(commit=False)  # form에 create_date이 없으므로 여기서 저장하면 에러발생!
            question.create_date = timezone.now()  # create_date를 넣어주고 저장
            question.save()
            return redirect('pybo:index')  # index는 초기화면이다
    else:
        form = QuestionForm()            # method가 GET인 경우
    context = {'form':form}
    return render(request,'pybo/question_form.html',context)

자. 위와같이 question_create를 수정해주자.

 

POST로 요청올 때와 GET으로 요청이 올 때, 다른 기능을 수행한다. 코드마다 주석으로 설명했으니 참고하자.

 

이제 질문 등록이 제 기능을 발휘한다.

 

여기서 문제점이 하나 있다.

 

form.as_p 태그는 form을 자동으로 생성해주기 때문에 부트스트랩을 적용시킬 수 없다.

 

완벽하진 않지만 그래도 Meta 태그에 widgets를 추가해주면 어느정도 보완할 수는 있다.

 

여기에  각각의 이름을 한글로 바꾸기 위해 labels로 alias를 정해주자.

# pybo/forms.py
class QuestionForm(forms.ModelForm): # forms.ModelForm을 상속받은 모델 폼
    class Meta: # 장고 모델폼은 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는 자동으로 폼을 생성해주지만, 디자인적으로 많은 제약이 생긴다.

 

서비스의 규모가 커져 프론트와 백엔드가 나뉜다면, 여러모로 애매한 상황이 생긴다.

 

그러면 wigets을 삭제하고 form을 HTML로 직접 생성해주자.

 

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 %}
<!--        오류 표시-->
        {% 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>

        <button type="submit" class="btn btn-primary">저장하기</button>
    </form>
</div>
{% endblock %}

제목과 내용을 직접 작성했고,  form.subject.value|default_if_none:'' 은

 

제목에서 오류 발생시 기존 입력값을 유지하기 위함이고, | 뒤의 default는 값이 없으면 None대신 공백으로 표기하라는 의미이다.

 

이제 답변기능도 Form을 만들고 기능도 연결하자.

 

# pybo/forms.py
from django import forms
from pybo.models import Question,Answer

class QuestionForm(forms.ModelForm): # forms.ModelForm을 상속받은 모델 폼
    class Meta: # 장고 모델폼은 Meta 내부 클래스를 반드시 포함해야함. 모델 폼이 사용할 모델과 필드를 작성.
        model = Question
        fields = ['subject','content']
        labels = {
            'subject':'제목',
            'content':'내용'
        }

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

 

# 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 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)

 

답변기능을 추가했으면 템플릿을 수정하자.

question_detail.html

...
...
<form action="{% url 'pybo: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 %}
   ...
   ...

답변기능도 오류메세지가 표시된다!

반응형