[KT AIVLE 4기] Django - 템플릿과 Form

데이터 조작 : 장고쉘, admin 기본앱 페이지, Form을 이용한 HTML 페이지(템플릿)

16주차 오정임 강사님의 장고 3일차 수업 주제는 “장고 템플릿과 장고 폼이다”

장고 Form은 앞서 언급했듯이 Model을 통해 만들 수 있으며,

이는 템플릿에서 데이터를 조작하기 위한 도구이다.


🧩 템플릿

그렇다면 템플릿은 뭘까?

HTML에서 동적인 데이터를 표시하고 로직을 수행할 수 있도록 태그와 필터 를 포함하는 HTML의 확장 문법이다.

템플릿 환경설정

view에서 html 페이지 응답을 요청하면, html 페이지를 rendering 하기 위해서 해당 html 파일을 찾는다.

파일을 찾는 폴더 위치 는 두 군데!

  • 파일시스템 로더 (우선 작동)

  • 앱디렉터리 로더

INSTALLED_APPS 리스트에 등록된 각 앱의 templates 폴더를 순차적으로 찾아나감.

"""mysite/settings.py"""

TEMPATES = [
    {
        'DIRS' : [BASE_DIR/'templates'],  # 파일시스템 로더가 찾음
    }

]

INSTALLED_APPS = [                      # 앱디렉터리 로더가 찾음
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "blog",    # 새로 등록
    "book",
]

장고 템플릿 문법

context

뷰에서 템플릿으로 전달하는 key:value 딕셔너리 타입의 데이터이다.

"""blog/views.py"""

# 프로필 보기
def profile(request):
    user = User.objects.first()
    return render(request, 'blog/profile.html', {'user': user})

User 모델 인스턴스를 html에 전달함.

<!-- blog/templates/blog/profile.html -->

{% extends "layout.html" %}

{% block title %}profile{% endblock title %}

{% block content %}
    <h1> {{user}}'s Profile </h1>
    <ul>
        <li>이름:{{user}}</li>  <!-- __str__  재정의했으므로 -->
        <li>전화번호:{{user.profile.phone_number}}</li>
        <li>주소:{{phone.profile.address}}</li>
    </ul>
{% endblock content %}

< 사용한 템플릿 태그 >

  • 출력 : {{ }}

  • for문

  • if문

  • 템플릿 상속

템플릿 상속

  • 부모 템플릿에서
<!-- templates/layout.html -->

<html>
<head>
    <meta charset="utf-8" />
    <title>{% block title %}{% endblock %}</title>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
    <style>
        html {position: relative; min-height: 100%}
        body{margin-bottom: 60px}
        #page-footer{
            position: absolute;
            bottom: 0;
            width:100%;
            height: 60;
            line-height: 60px;
            background-color: #f5f5f5;
        }
    </style>
</head>
<body>
    <nav class="navbar navbar-default">
        <div class="container">
            <div id="navbar" class="collapse navbar-collapse">
                <ul class="nav navbar-nav">
                    <li class="active"><a href="/blog/">Home</a></li>
                    <li><a href="#">About</a></li>
                    <li><a href="#">Contact</a></li>
                </ul>
                <ul class="nav navbar-nav navbar-right">
                    <li><a href="#">회원 가입 </a> </li>
                    <li><a href="#">로그인</a></li>
                    <li><a href="/blog/profile/">프로필</a> </li> 
                    <li><a href="#">로그 아웃</a></li>
                 </ul>
                </div>
        </div>
    </nav>
    <div class="container">
        <div class="row">
            <div class="col-sm-12">
                {% block content %}
                {% endblock %}
            </div>            
        </div>
    </div>

    <div id="page-footer">
        <div class="container">
            <p class="text-muted">
                Copyright © 2020 KINO Data Systems All Right Reserved
            </p>
        </div>
    </div>
</body>
</html>

  • 자식 템플릿에서

{% extends "layout.html" %} extends 태그로 상속하고,

{% block title %}{% endblock %}

{% block content %}{% endblock %}

block에 있는 title과 content를 재정의해서 사용한다.

<!-- blog/templates/blog/list.html -->

{% extends "layout.html" %}

{% block title %}Post 목록 보기{% endblock title %}

{% block content %}

    <h1>Post 목록 보기</h1>

    <form action="" method='get'>
        {% csrf_token %}
        검색어 : <input type="text" name= "keyword" value="{{q|default:''}}">  <!-- value, 값이 없으면 빈문자 사용 -->
        <input type="submit" value="검색">
    </form>

    {% for post in post_all %}
        <a href="/blog/{{post.id}}"> {{post.title}} </a><br>
    {% endfor %}

    <p>
        <!--(유지보수를 위해) URL Reserve 사용하기-->
        <!-- <a href="/blog/new">[새 글 쓰기]</a>-->
        <a href="{% url 'blog:create' %}">[새 글 쓰기]</a>
    </p>

{% endblock content %}


🧩 장고 Form

데이터 추가, 수정, 삭제를 템플릿에서 하기 위한 도구이다.

Form 태그로 구성되며 하나 이상의 위젯으로 구성된다. (위젯이란 예를들어 인풋창 같은 거다.)

html 코드에 우리가 직접 Form 태그를 추가해서 만들 수 있고, 장고 Form을 이용해서 자동으로 만들 수 있다.

Model이 데이터베이스와 매핑되며, FORM은 사용자로부터 데이터를 입력

1. 직접 Form 태그 추가해보기

<form action="" method='get'>

Method : GET과 POST

<!-- blog/templates/blog/post_delete.html -->

{% extends "layout.html" %}
{% block title %}Post 삭제{% endblock %}
{% block content %}
    <h1> POST 삭제</h1>
    {{post}}  글을 정말로 삭제하시겠습니까?

    <form action="" method="POST">
        {% csrf_token %}
        <input type="submit" value="네, 삭제합니다">
    </form>

    <a href="{% url 'blog:list'%}">아니오, 취소합니다</a>
    
{% endblock %}

<!-- blog/templates/blog/list.html -->

{% extends "layout.html" %}

{% block title %}Post 목록 보기{% endblock title %}

{% block content %}

    <h1>Post 목록 보기</h1>

    <form action="" method='get'>
        {% csrf_token %}
        검색어 : <input type="text" name= "keyword" value="{{q|default:''}}">  <!-- value, 값이 없으면 빈문자 사용 -->
        <input type="submit" value="검색">
    </form>

{% endblock content %}

요청 정보는 name=value&name=value&… 형태의 Query String인데,

이를 header에 포함하여 전달하면 GET 방식이다. (default)

이를 body에 포함하여 정달하면 POST 방식이다.

CSRF

CSRF (cross-site request forgery) 는 현재 요청이 유효한지 여부만 판단한다.

CsrfViewMiddleware가

  • GET 요청 시 csrf token 발급

  • POST 요청 시 csrf token 체크

  • token 체크 오류 시 403 Forbidden 응답


HttpRequest 속성들

클라이언트로부터 전송된 요청정보를 처리하는 객체이다.

View의 첫번째 인자로 전달된다.

속성 설명
HttpRequest.path 요청정보의 uri에 해당함
HttpRequest.method GET인지 POST인지에 대한 정보
HttpRequest.GET name=value&name=value 요청형식을 QueryDict 반환
HttpRequest.POST (GET과 동일)
HttpRequest.FILES MultiValueDict 반환
HttpRequest.META 클라이언트의 정보 제공


2. 장고 Form 클래스 만들기

{{ form.as_p }}

장고 Form 클래스 직접 만들기

""" /blog/forms.py """

from django import forms

class PostForm(forms.Form):
    title = forms.CharField(label='제목')
    body = forms.CharField(label='내용', widget=forms.Textarea)
""" /blog/views.py """
from .forms import PostForm

def post_create(request):
    if request.method == 'POST':
        pass
    
    else: 
        form = PostForm()
        return render(request, 'blog/post_form.html', {'form': form})
<!-- blog/post_form.html -->

<form action="/your-view/" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Submit">
</form>

< 장고 Form 메소드 >

Form 필드를 HTML 위젯 태그로 변환할 때,

메소드 HTML 태그로 변환
as_p() <p> 태그로 분리
as_ul() <li> 태그로 분리
as_table() <tr> 태그로 분리

장고 Form 메소드는 사용할 때 괄호를 안쓴다.

장고 Form의 기능

  1. 모델 클래스에 맞게 Form 태그 생성

  2. Data 처리 용이

  3. 유효성 검사

redirect(<html 경로>) 혹은

redirect(<모델 인스턴스>) + Model의 get_absolute_url() return값

뒤에서 배울 장고 View의 CRUD가 Form 기반이므로 앞으로 내용의 처리 방식이 이와 같다.


3. ModelForm 사용

Form 클래스 직접 만들지 말고, Model 기반으로 쉽게 만들자

from django import forms
from blog.models import Post

class PostModelForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'body']  # 또는 '__all__'
def post_create(request):
    if request.method == 'POST':
        form = PostModelForm(request.POST)  # 데이터 바인딩
        if form.is_valid(): # 바인딩 된 value들의 유효성 검증
            form.save()  # 데이터 INSERT
            return redirect('blog:list')
    
    # GET 방식일 때 입력페이지 Form 제공
    # [등록 버튼]  
    else: 
        form = PostModelForm()
        return render(request, 'blog/post_form.html', {'form': form})
    


🧩 URL Reverse

템플릿에서 경로 중복을 막기 위해서 templates 폴더 밑에 앱명의 폴더를 하나 더 만든 것 처럼,

유지 보수 측면에서 url의 중복을 막기 위해서 url을 직접 주지 않고, reverse 하는 기능이다.

html에서 사용할 때는 {% url 앱명:path %}

view에서 사용할 때는 reverse(), resolve() 함수를 사용한다.

""" blog/urls.py """

app_name = 'blog'  # 추가

urlpatterns = [
    path('new/', views.post_create)  # 대신에
    path('new/', views.post_create, name='create') 
]
<!-- 템플릿에서 -->

<a href="/blog/new/">[새 글 쓰기]</a>  <!-- 대신에 -->
<a href="{% url 'blog:create' %}">[새 글 쓰기]</a>

"""blog/models.py"""

from django.urls.base import reverse

class Post(model.Model):
    body = models.TextField()
    title = models.CharField(max_length=200)

    def get_absolute_url(self):
        return reverse('blog:list')  # 혹은
        return resolve_url('blog:list')

"""blog/veiws.py"""
from django.shortcuts import render, get_object_or_404, redirect

def post_create(request):
    if request.method == 'POST':
        form = PostModelForm(request.POST)
        if form.is_valid(): 
            post = Post.objects.create(**form.cleaned_data)
            return redirect(post)  # 여기서 'blog/list' url로
    
    else: 
        form = PostModelForm()
        return render(request, 'blog/post_form.html', {'form': form})

소감

처음에는 태그를 사용해서 직접 만들어보고, 장고에서 제공하는 기능을 통해 편하게 만들어보는 순서로 수업이 진행되었다.

강사님이 1분도 안쉬시고 진도를 빼셔서 엄청 집중하느라 저녁 시간이 되니까 머리가 지끈지끈한 것 같다 🤯

Categories:

Updated:

Leave a comment