📌 게시판과 페이징을 구현하며 템플릿 태그를 사용하였다.
✔️ {% csrf_token %}
: 서버와 api 통신에 필요한 크로스 사이트 요청 위조를 방지하기 위해 사용되는 태그✔️ {% if %}{% else %}
: 조건을 이용하여 html에서 python 문법 사용 가능✔️ {% for item in items %}
: for 루프를 이용하여 html에서 python 문법 사용 가능
✔️ 게시판을 구현하면서 부트스트랩으로 디자인을 입히고, 짝수와 홀수 라인의 백그라운드 색상을 다르게 주었다.
<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
클래스가 번갈아가면서 나오게 한다.
✔️ 위의 코드를 변경 후 화면을 띄우면 아래와 같이 부트스트랩과 동일한 효과의 디자인이 입혀진 것을 확인 할 수 있다.(사실상 부트스트랩과 템플릿 태그의 조합이다.)
<!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은 서로 다른 소스가 렌더링되는 것을 확인하였다.
✔️ 이렇게 템플릿 태그를 사용하면 소스코드를 절약할 수 있고, 템플릿 구조를 역할 별로 구조화 시킬 수 있다.
✔️ 특정 .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을 불러와서 보여줄 때 사용할 수 있다.
✔️ 지금까지는 Django에서 제공되는 템플릿 태그를 사용해보았다.
✔️ 하지만 웹 개발에 있어 Django에서 제공되는 태그만 사용해서 모든 것을 다 구현할 수 없기 때문에, 사용자 정의로 만든 태그가 필요한 경우가 있을 것이다.
✔️ 현재 출력된 데이터는 유저명, 이메일, 가입일, 페이플랜, 가격 등의 필드 데이터 이다.
✔️ 이메일은 @가 붙은 이메일 형식의 필드이지만 제 3자가 보았을 때 스팸메일을 보내거나 악용하는 경우가 있을 수 있으며,
✔️ 가입일은 서버에서 등록되는 데이터이기 때문에 년/월/일 뿐 아니라 시간까지 들어 있다.
✔️ 또한 가격은 정수형으로 데이터가 저장되어 있기 때문에 천자리 수가 구분이 되지 않아
✔️ 위와 같은 정제되지 않는 데이터를 그대로 보여주게 되면 사용자는 가독성이 떨어질 수 있으므로 내가 정의한 형식으로 데이터에 필터를 걸 수 있게 템플릿 태그를 만들어 보자.
✔️ 프로젝트 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 공식 문서
✔️ 필터를 적용할 때, 인자를 추가로 보내 데이터 처리에 사용할 수 있다.
📌 인자 추가
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처리를 주었다가 안주게 코드를 변경해 보았다.
✔️ 결과는 아래와 같이 나온다.
✔️ 위에서 태그의 명칭을 정할 때 "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를 사용하면 인자의 개수에 제한 없이 넘겨 줄 수 있다.
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를 전달하여 렌더링 할 수 있는지 확인하는 내용이다.