Django 다양한 템플릿 태그

Gom La·2023년 5월 3일
0

장고와 친해지기

목록 보기
14/17
post-thumbnail

📌 게시판과 페이징을 구현하며 템플릿 태그를 사용하였다.
✔️ {% csrf_token %}
: 서버와 api 통신에 필요한 크로스 사이트 요청 위조를 방지하기 위해 사용되는 태그

✔️ {% if %}{% else %}
: 조건을 이용하여 html에서 python 문법 사용 가능

✔️ {% for item in items %}
: for 루프를 이용하여 html에서 python 문법 사용 가능


🍃 순차적 출력 cycle

✔️ 게시판을 구현하면서 부트스트랩으로 디자인을 입히고, 짝수와 홀수 라인의 백그라운드 색상을 다르게 주었다.

<table class="table table-striped">
        <thead>
            <tr class="table-success">
                <th>아이디</th>
                <th>유저이름</th>
                <th>이메일</th>
                <th>가입일</th>
                <th>페이플랜</th>
                <th>가격</th>
            </tr>
        </thead>
        <tbody>
            {% for user in users %}
                <tr class={% cycle "" "table-dark" %}>
                    <td>{{ user.id }}</td>
                    <td>{{ user.username }}</td>
                    <td>{{ user.email }}</td>
                    <td>{{ user.date_joined }}</td>
                    <td>{{ user.pay_plan.name }}</td>
                    <td>{{ user.pay_plan.price }}</td>
                </tr>
            {% endfor%}
            {% if users|length == 0 %} 조회할 데이터가 없습니다. {% endif %}
        </tbody>
        
    </table> 

✔️ 부트스트랩에서는 table 태그에 table-striped 클래스를 주면 아래와 같이 자동으로 짝수 홀수 백그라운드가 구분되며 디자인이 입혀졌다.

✔️ 이제 부트스트랩을 이용한 효과를 템플릿 태그로 다시 구현하려고 한다.

<!--<table class="table table-striped">-->
<table class="table">

✔️ 테이블 태그에 있는 table-striped 클래스를 제거한다.

			{% for user in users %}
                <tr class={% cycle "" "table-dark"%}>
                    <td>{{ user.id }}</td>
                    <td>{{ user.username }}</td>
                    <td>{{ user.email }}</td>
                    <td>{{ user.date_joined }}</td>
                    <td>{{ user.pay_plan.name }}</td>
                    <td>{{ user.pay_plan.price }}</td>
                </tr>
            {% endfor%}

✔️ for 루프로 렌더링하는 tr 태그에 {% cycle "" "table-dark" %} 템플릿 태그를 추가하여 첫 번째는 "" 빈 클래스가 두 번째는 table-dark 클래스가 번갈아가면서 나오게 한다.

✔️ 위의 코드를 변경 후 화면을 띄우면 아래와 같이 부트스트랩과 동일한 효과의 디자인이 입혀진 것을 확인 할 수 있다.(사실상 부트스트랩과 템플릿 태그의 조합이다.)


🍃 template의 공통 영역 분리

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>Index Page</title>
</head>

<!-- Option 1: Bootstrap Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<body>

✔️ 대부분의 template에 상위의 header와 하위의 footer 등의 공통된 부분을 .html파일마다 중복으로 들어가는 경우가 있다.

✔️ 이렇게 중복되는 부분을 템플릿 태그 {% extendx %}{% block %}{% endblock %} 를 이용하여 분리해보자.

➤ index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>Index Page</title>
</head>

<!-- Option 1: Bootstrap Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<body>
    <h1>The is a Index Page</h1>
    <h3>{{ user }}</h3>
    {% if user.is_authenticated %}
        <a class="btn btn-primary" role="button" aria-disabled="true" href="/logout">로그아웃</a>
        <a class="btn btn-primary" role="button" aria-disabled="true" href="/user/list">유저목록</a>
        <a class="btn btn-primary" role="button" aria-disabled="true" href="/payplan/list">PayPlan목록</a>
    {% else %}
        <a class="btn btn-primary" role="button" aria-disabled="true" href="register">회원가입</a>
        <a class="btn btn-primary" role="button" aria-disabled="true" href="login">로그인</a>
    {% endif %}
    <br><br>
    {% block content %}
    {% endblock %}
</body>
</html>

✔️ 위의 .html파일은 게시판마다 공통으로 불러와야하는 부분이다.

✔️ 공통으로 들어가는 부분은 그대로 두고 변경되는 다른 template이 들어가는 부분에 아래의 코드를 추가해 준다.

{% block content %}
{% endblock %}

➤ boards.html

{% extends "index.html" %}
{% block content %}

    <h2>Pay Plan 리스트</h2>
    <h4>{% if msg %}{{msg}}{% endif %}</h4>
    <!-- <table class="table table-striped"> -->
    <table class="table">
        <thead>
            <tr class="table-primary">
                <th>아이디</th>
                <th>이름</th>
                <th>가격</th>
                <th>생성일</th>
            </tr>
        </thead>
        <tbody>
            {% for plan in pay_plans %}
                <!-- 부트스트랩 table-striped 대신 cycle 템플릿 태그 이용  -->
                <tr class={% cycle "" "table-dark" %}>
                    <td>{{ plan.id }}</td>
                    <td>{{ plan.name }}</td>
                    <td>{{ plan.price }}</td>
                    <td>{{ plan.create_at }}</td>
                </tr>
            {% endfor%}
            {% if pay_plans|length == 0 %} 조회할 데이터가 없습니다. {% endif %}
        </tbody>
        
    </table>    
...
{% endblock %}

✔️ 이제 게시판 .html에 들어가서 위와 같이 index.html에 중복으로 들어가있는 코드는 지워 준 후, 최 상단과 최 하단에 아래와 같은 코드를 붙여준다.

{% extends "index.html" %}
{% block content %}{% endblock %}

✔️ 두 개의 다른 게시판에서 index.html 부분이 공통으로 나오며 boards.html은 서로 다른 소스가 렌더링되는 것을 확인하였다.

✔️ 이렇게 템플릿 태그를 사용하면 소스코드를 절약할 수 있고, 템플릿 구조를 역할 별로 구조화 시킬 수 있다.

🍃 다른 템플릿 추가 include

✔️ 특정 .html 파일을 생성 후 이 파일의 내용을 다른 template에 불러와서 사용할 수 있다.

➤ include.html

<p>이 페이지는 인클루드 되었습니다.</p>

➤ boards.html

...
</nav>
    {% include "include.html" %}
    <p>
        <a class="btn btn-primary" role="button" aria-disabled="true" href="{% url 'index' %}">홈으로</a>
        <a class="btn btn-primary" role="button" aria-disabled="true" href="{% url 'register' %}">회원가입</a>
    </p>
...

✔️ 위와 같이 특정 템플릿을 만든 후 그 템플릿을 불러오고 싶은 template에 아래와 같은 코드를 추가 하면 특정 템플릿의 코드를 불러와서 사용할 수 있다.

{% include "include.html" %}

✔️ 위의 템플릿 태그는 공통된 것이 아닌 팝업이나 특정 html을 불러와서 보여줄 때 사용할 수 있다.


🍃 템플릿 필터

1. 사용자 정의 템플릿 태그

✔️ 지금까지는 Django에서 제공되는 템플릿 태그를 사용해보았다.

✔️ 하지만 웹 개발에 있어 Django에서 제공되는 태그만 사용해서 모든 것을 다 구현할 수 없기 때문에, 사용자 정의로 만든 태그가 필요한 경우가 있을 것이다.

✔️ 현재 출력된 데이터는 유저명, 이메일, 가입일, 페이플랜, 가격 등의 필드 데이터 이다.

✔️ 이메일은 @가 붙은 이메일 형식의 필드이지만 제 3자가 보았을 때 스팸메일을 보내거나 악용하는 경우가 있을 수 있으며,

✔️ 가입일은 서버에서 등록되는 데이터이기 때문에 년/월/일 뿐 아니라 시간까지 들어 있다.

✔️ 또한 가격은 정수형으로 데이터가 저장되어 있기 때문에 천자리 수가 구분이 되지 않아

✔️ 위와 같은 정제되지 않는 데이터를 그대로 보여주게 되면 사용자는 가독성이 떨어질 수 있으므로 내가 정의한 형식으로 데이터에 필터를 걸 수 있게 템플릿 태그를 만들어 보자.


2. 템플릿 태그 생성과 필터 적용

✔️ 프로젝트 App안에 templatetags 폴더를 생성 후, 내부에 __init__.py, custom_tags.py 파일을 만들어 주자.

✔️ 위에서 언급한 것과 같이 이메일, 가입일, 가격 3개의 필드를 커스텀 필터를 걸어 보겠다.


📌 이메일
templatetags > custom_tags.py

from django import template

register = template.Library()

@register.filter(name="email_ma")
def email_masker(value):
    email_split = value.split("@")

    return f"{email_split[0]}@******.***"

✔️ django의 template모듈 사용을 위해 template.Library()

✔️ "email_ma"라는 명칭으로 filter를 적용
- email_masker 함수명으로 필터를 적용시키지 않기위한 설정

✔️ 이메일 형식의 @ 이후 부분을 split함수로 잘라내어 뒷부분은 masker 처리

user > boards.html

{% load custom_tags %}

			{% for user in users %}
                <tr class={% cycle "" "table-dark" %}>
                    <td>{{ user.id }}</td>
                    <td>{{ user.username }}</td>
                    <td>{{ user.email|email_ma }}</td>
                    <td>{{ user.date_joined }}</td>
                    <td>{{ user.pay_plan.name }}</td>
                    <td>{{ user.pay_plan.price }}</td>
                </tr>
            {% endfor%}

✔️ "|"를 붙여 내가 정의한 태그 명칭을 뒷 부분에 붙여서 아래와 같이 수정하면 클라이언트 렌더링 시 email은 내가 정의한 함수를 거쳐 필터링 된다.

{{ user.email|email_ma }}

📌 가격
templatetags > custom_tags.py

from django import template
from django.utils.safestring import mark_safe

register = template.Library()

@register.filter(name="price_comma")
def price_comma(value):
    return format(value, ',d')

✔️ 숫자 천자리 수 마다 ","가 붙게 데이터를 정제하기 위해 함수 정의

✔️ format() 함수를 사용하여 첫 번째 인자는 값, 두 번째 인자는 ',d/f'

  • d또는 f 중 하나만 넣어주면 되는데 d 는 정수 f 실수를 의미한다.

user > boards.html

{% load custom_tags %}
			{% for user in users %}
                <tr class={% cycle "" "table-dark" %}>
                    <td>{{ user.id }}</td>
                    <td>{{ user.username }}</td>
                    <td>{{ user.email|email_ma }}</td>
                    <td>{{ user.date_joined }}</td>
                    <td>{{ user.pay_plan.name }}</td>
                    <td>{{ user.pay_plan.price|price_comma }}</td>
                </tr>
            {% endfor%}

✔️ 이메일과 동일하게 아래와 같이 수정

	{{ user.pay_plan.price|price_comma }}

📌 가입일
✔️ 가입일의 경우는 내가 정의한 것이 아닌 제공되는 필터를 사용하여 날짜에 대한 데이터를 정제해 보겠다.

user > boards.html

...

{{ user.date_joined|date:"Y M d D" }}

...

✔️ 위에서 사용한 "date" 태그는 따로 정의한 태그가 아니지만 기본적으로 django에서 제공하기 때문에 사용이 가능한다.

✔️ "Y"는 년도, "M"은 월, "d"는 일자, "D"는 요일을 의미한다.

✔️ 상세한 date에 대한 조건은 공식문서에 다양하게 나와 있으니 확인 바란다.
참고 : Django 공식 문서

2. 기타 필터 적용 방법

✔️ 필터를 적용할 때, 인자를 추가로 보내 데이터 처리에 사용할 수 있다.

📌 인자 추가
user > boards.html

{% load custom_tags %}
...

	<td>{{ user.email|email_ma:user.id }}</td>

...

✔️ 위와 같이 정의한 "email_ma"태그 뒤에 :[보내고 싶은 인자]를 붙여 준다.

templatetags > custom_tags.py

from django import template

register = template.Library()

@register.filter(name="email_ma")
def email_masker(value, arg):
    email_split = value.split("@")

    return f"{email_split[0]}@******.***" if arg % 2 == 0 else value 

✔️ 위에서 작성한 코드를 조금 수정하였는데, 매개변수 arg를 추가 한 후, 클라이언트에서 전달 받은 user.id값이 짝수, 홀수에 따라 이메일 뒤의 masker처리를 주었다가 안주게 코드를 변경해 보았다.

✔️ 결과는 아래와 같이 나온다.

3. filter vs simple_tag

✔️ 위에서 태그의 명칭을 정할 때 "filter"를 사용하여 아래와 같이 사용하였다.

from django import template

register = template.Library()

@register.filter(name="email_ma")
def email_masker(value, arg):
	...

✔️ 위의 filter와 동일한 기능을 하는 simple_tag도 사용할 수 있는데 두개의 차이점이 존재한다.

✔️ filter를 사용하면 필터링할 값과 추가 인자를 1개 받을 수 있다.

✔️ 하지만 simple_tag를 사용하면 인자의 개수에 제한 없이 넘겨 줄 수 있다.

filter vs simple_tag
장고 공식 문서

from django import template
from django.utils.safestring import mark_safe

register = template.Library()

# takes_context를 True로 설정하지 않으면 메소드에서 context를 사용할 수 없음
@register.simple_tag(name="test_tags", takes_context=True)
def test_tags(context):
    tag_html = "<span class='badge badge-primary'>테스트 태그</span>"

    # 클라이언트로 전달 시 스트링만 보내게 되면 html 태그로 인식하지 못함
    return mark_safe(tag_html)

✔️ 위는 simple_tag를 사용해보기 위해 만든 함수이며 filter와 동일하게 사용자 정의 템플릿 태그를 만들 수 있다.

✔️ 코드는 필터를 거는 것 뿐 아니라 html를 전달하여 렌더링 할 수 있는지 확인하는 내용이다.

🍃 참고

filter vs simple_tag

장고 공식 문서

https://itinerant.tistory.com/161

profile
인생 개발자 라곰!!

0개의 댓글