django 데이터 불러와서 table 로 만들기 ( 커스텀 템플릿 태그 custom template tags and filters )

파오리·2021년 3월 10일
2

아주 작은 기록

목록 보기
9/13

내가 하고 싶은 것: db에 있는 데이터를 불러와 table 형태로 정렬시키기

django custom template tags and filters 공식문서

저 문장 자체로만 봐서는 뭐가 어려운걸까,, 라고 생각이 들지만,
현재 상황을 보자.

( custom template tags가 궁금해서 왔다면 옆 태그에서 커스텀 템플릿 태그 선택 )

현재 상황


이런 테이블이 있는 페이지를 만들고자 한다. 3번째 열에 있는 text들은 모두 a태그로 링크가 연결되어 있다.

db 모델은 이와 같다.

Guide table에 저장된 데이터들을 불러와 table 정렬을 시키려고 하는데,
여기서 관건은 이것이다.

1) 대분류와 소분류 가 같은 것을 확인해서 rowspan 을 적용시켜야 함.
2) 새로운 대분류/소분류가 생겨도 자동으로 해당 분류에 해당하는 데이터들이 생성되어 정렬되어야 함.

시나리오

필요한 데이터

  • 대분류 문자열 리스트
  • 대분류에 속한 데이터들의 갯수 (rowspan에 사용)
  • 대분류에 속한 소분류 문자열 리스트
  • 소분류 문자열 리스트
  • 소분류에 속한 데이터들의 갯수 (rowspan에 사용)
  • 소분류에 속한 데이터들

대분류나 소분류가 더이상 생성되지 않고 이대로 고정이라면 views.py에서 데이터를 불러올 때, 대분류와 소분류 기준으로 filter를 걸어서 불러오면 되겠지만, 새로운 분류가 생길 수 있으므로 필요한 모든 데이터들을 분류에 따라 정규화해서 불러올 수 있어야 한다.

이때 필요한게 (1) 자동변수 생성 (2) 커스텀 템플릿 태그 (3) 커스텀 템플릿 필터 이다.

자동변수 생성은 다른 게시물에서 다루도록 하고, (코드만 봐도 음 알겠구나 싶긴 하다.)
여기에선 커스텀 템플릿 태그커스텀 템플릿 필터만 다루겠다.

테이블 html 구조


하.. rowspan 때문에 진짜 힘들었다.

여튼, 이걸 보면 반복을 어떻게 할지 생각할 수 있다.

반복 시나리오

크게 반복되는 것은 대분류이고, 그 안에서 작게 반복되는 것은 소분류이다. 하지만 소분류 속에서도 td가 4개인 trtd가 3개인 tr만 반복되므로 우선 대분류의 반복 속에서 첫 번째 소분류와 관련된 td가 5개인 tr3개인 tr을 따로 뽑고, 소분류의 반복 속에서 td가 4개인 trtd가 3개인 tr을 반복시킨다.

# 대략적인 html (문법에 맞지 않는 부분도 있음! 대략적인 구조 파악을 위함)

{% for 대분류 in 대분류 목록 %}
<!-- 5개의 td가 있는 tr ; 첫 번째 소분류의 첫 번째 데이터 내용 -->
  <tr>
    <td rowspan="대분류 속 총 데이터 갯수">{{ 대분류 }}</td>
    <td rowspan="첫 번째 소분류 속 데이터 갯수">{{ 첫 번째 소분류 이름 }}</td>
    <td>
      <a href="첫 번째 소분류의 첫 번째 데이터의 link_url">
        {{ 첫 번째 소분류의 첫 번째 데이터의 link_text }}
      </a>
    </td>
    <td>{{ 첫 번째 소분류의 첫 번째 데이터의 target }}</td>
    <td>{{ 첫 번째 소분류의 첫 번째 데이터의 level }}</td>
  </tr>
<!-- 3개의 td가 있는 tr ; 첫 번째 소분류의 두 번째부터 나머지 데이터 내용 -->
  {% for 데이터 in 첫 번째 소분류 속 두 번째 데이터부터 들어있는 데이터 목록 %}
    <tr>
      <td>
        <a href="데이터의 link_url">
          {{ 데이터의 link_text }}
        </a>
      </td>
      <td>{{ 데이터의 target }}</td>
      <td>{{ 데이터의 level }}</td>
    </tr>
  {% endfor %}
<!-- 4개의 td가 있는 tr ; 두 번째 소분류부터 나머지의 첫 번째 데이터 내용 -->
  {% for 소분류 in 소분류 목록 %}
    <tr>
      <td rowspan="소분류 속 데이터 갯수">{{ 소분류 }}</td>
      <td>
        <a href="소분류 속 데이터의 link_url">
          {{ 소분류 속 데이터의 link_text }}
        </a>
      </td>
      <td>{{ 소분류 속 데이터의 target }}</td>
      <td>{{ 소분류 속 데이터의 level }}</td>
    </tr>
<!-- 3개의 td가 있는 tr ; 소분류 속 두 번째부터 나머지의 데이터 내용 -->
    {% for 데이터 in 소분류 속 두 번째 데이터부터 들어있는 데이터 목록 %}
      <tr>
        <td>
          <a href="{{ 데이터의 link_url }}">
            {{ 데이터의 link_text }}
          </a>
        </td>
        <td>{{ 데이터의 target }}</td>
        <td>{{ 데이터의 level }}</td>
      </tr>
    {% endfor %}
  {% endfor %}
{% endfor %}
        

이렇게.. 에휴

커스텀 템플릿 태그

그럼 이제 위 코드에서 한글로 표현된 것들을 코드로 바꿔야지,,
django 커스텀 템플릿 태그에 관한 문서는 최상단에도 있지만

django custom template tags and filters 공식문서

이거다.

1. 셋팅

1) 커스텀 템플릿 태그와 필터를 사용하기 위한 셋팅부터 해보자.

앱 폴더 안에 templatetags 폴더를 만들고, 그 안에 내가 만든 태그 코드를 작성할 .py 파일을 하나 만든다.

2) 태그를 사용할 .html파일의 상단에 {% load 태그파일이름 %}을 넣는다.
나의 경우에는 base.html을 상속받는 코드가 있고, 태그 파일 이름이 law_tags.py이므로

1	{% extends "base.html" %}
2	{% block content %}
3	{% load law_tags %}

이 세 줄로 html파일이 시작된다.

2. 코드 작성

# templatetags/law_tags.py

from django import template

register = template.Library()

@register.simple_tag
def pick_data_1(list, i, j):	# list의 [i+1][j]요소 반환하는 템플릿 태그
    return list[i+1][j]
    
@register.filter
def ranges_0(list):	# 0부터 list길이-1 만큼의 숫자 범위를 반환하는 템플릿 필터
    return range(0, len(list)-1)

pick_data_1이나 ranges_0과 같은 함수 이름은 맘대로 정의하면 된다. 이 함수 이름이 {% %} 사이나, |과 함께 사용하게 된다.

<!-- html 에서 활용 예시 -->

{% for i in data|ranges_0 %}
  <tr>
    <td>
      <a href = {% pick_data_1 data i 2 %}> {% pick_data data i 1 %} </a>
    </td>
    <td> {% pick_data_1 data i 3 %} </td>
    <td> {% pick_data_1 data i 4 %} </td>
  </tr>
{% endfor %}

이렇게 사용하면 된다. data라는 리스트의 0부터 len(data)-1 까지 range를 for문을 돌고, 해당 리스트의 data[i+1][1], data[i+1][2],data[i+1][3],data[i+1][4]의 요소들을 뽑아 사용하는 코드이다.

{% pick_data_1 data 0 1 as one_data %}

이런 식으로 as를 붙여 선언하게 되면 {% pick_data_1 data 0 1 %}을 통해 불러온 data리스트의 data[1][1]요소를 one_data의 이름으로 사용할 수 있게 되어서, html 안에서 {{ one_data }}로 변수 활용할 수 있다.

문제 해결

그럼 이를 활용하여 다시 아까 그 놈의 임의 데이터를 자동생성된 변수로 불러와 테이블 정렬하는 문제를 해결해보자.

# django views.py

from .models import Guide


def guide(request):
    guide = Guide.objects.all().order_by('-big_kind')
    guide_big_kind = guide.values_list('big_kind', flat=True).distinct()
    guide_small_kind = guide.values_list('small_kind', flat=True).distinct()
    return render(request, 'law-guide.html', {'guide_big_kind': guide_big_kind, 'guide_small_kind': guide_small_kind })

views.py에서는 대분류의 목록과 소분류의 목록만 넘겨주었다.

# templatetags/law_tags.py (커스텀 템플릿 태그와 필터 작성 코드)

from django import template
from .models import Guide

register = template.Library()

@register.simple_tag
def guide_to_data_big(big_kind):
    dic = {}
    locals()['{}_data'.format(big_kind)] = []

    guide_data_filter_big = Guide.objects.filter(big_kind=big_kind)
    guide_small_kind = guide_data_filter_big.values_list('small_kind', flat=True).distinct()
    guide_small_kind_1 = guide_small_kind[1:]

    big_kind_count = guide_data_filter_big.count()

    dic['guide_small_kind'] = guide_small_kind
    dic['guide_small_kind_1'] = guide_small_kind_1
    dic['big_kind_count'] = big_kind_count

    return dic
    
@register.simple_tag
def guide_to_data_small(big_kind, small_kind):
    dic = {}

    locals()['{}_{}_data'.format(big_kind, small_kind)] = []

    guide_data_filter_big = Guide.objects.filter(big_kind=big_kind)
    guide_data_filter_small = guide_data_filter_big.filter(small_kind=small_kind)

    small_kind_count = guide_data_filter_small.count()

    for small_data in guide_data_filter_small:
        small_data_content = [ small_data.small_kind, small_data.link_text, small_data.link_url, small_data.target, small_data.level ]
        locals()['{}_{}_data'.format(big_kind, small_kind)].append(small_data_content)

    dic['small_kind_count'] = small_kind_count
    dic['{}_{}_data'.format(big_kind, small_kind)] = locals()['{}_{}_data'.format(big_kind, small_kind)]

    return dic
    
@register.simple_tag
def to_str(kind_data):
    text = '{}_data'.format(kind_data)
    return text
    
@register.simple_tag
def to_two_kind_str(big_kind, small_kind):
    text = '{}_{}_data'.format(big_kind, small_kind)
    return text
    
@register.simple_tag
def get_value_from_dict(data, key):
    if key:
        return data.get(key)

@register.filter
def ranges_0(list):
    return range(0, len(list)-1)
    
@register.simple_tag
def pick_data_1(list, i, j):
    return list[i+1][j]

위에서 짰던 반복 시나리오를 보면, 1)대분류의 반복 속에서 첫 번째 소분류와 관련된 tr 두 개, 2)소분류의 반복 속에서 tr두 개 를 반복시켜야 한다. 그렇기 때문에 데이터 불러오는 태그를 나누어 작성했고, 그 외에는 자동으로 생성된 변수를 통해서 함수 호출을 하기 위해서 자동 생성된 변수를 str로 만들어주는 태그와 dictionary에서 key 값을 통해 데이터를 받아오는 태그와 반복문을 위한 range필터, 리스트 형태의 데이터에서 요소를 뽑아오는 태그를 작성했다.

<!-- law-guide.html -->


{% extends "base.html" %}
{% block content %}
{% load law_tags %}
<div class="law-notice">
  <h4>✅기술안내서 가이드를 누르면 '한국인터넷진흥원' 사이트로 연결됩니다.</br>
      해당 페이지에서 자료를 다운로드 받을 수 있습니다.
  </h4>
</div>
<div class="law-board">
  <table class="law-board__table">
    <tr class="law-board__table__th">
      <th class="sort">대분류</th>
      <th class="sort_small">소분류</th>
      <th class="guide">➰기술안내서 가이드</th>
      <th class="who">대상</th>
      <th class="level">수준</th>
    </tr>
    {% for big_kind in guide_big_kind %}
      {% to_str big_kind as big_kind_datalist_text %}
      {% guide_to_data_big big_kind as big_dataset %}
      {% get_value_from_dict big_dataset 'big_kind_count' as big_kind_count %}
      {% get_value_from_dict big_dataset 'guide_small_kind' as  small_kind_list %}

      {% to_two_kind_str big_kind small_kind_list.0 as datalist_text_0 %}
      {% guide_to_data_small big_kind small_kind_list.0 as dataset_0 %}
      {% get_value_from_dict dataset_0 'small_kind_count' as small_kind_count_0 %}
      {% get_value_from_dict dataset_0 datalist_text_0 as data_0 %}
      <tr>
        <td rowspan={{big_kind_count}}><p class="law-title">{{big_kind}}</p></td>
        <td rowspan={{small_kind_count_0}}>{{small_kind_list.0}}</td>
        <td>
          <a href={{data_0.0.2}}
            class="law-board__table__link" target="_new">
            {{data_0.0.1}}
          </a>
        </td>
        <td>{{data_0.0.3}}</td>
        <td>{{data_0.0.4}}</td>
      </tr>
      {% for i in data_0|ranges_0 %}
        <tr>
          <td>
            <a href={% pick_data_1 data_0 i 2 %}
              class="law-board__table__link" target="_new">
              {% pick_data_1 data_0 i 1%}
            </a>
          </td>
          <td>{% pick_data_1 data_0 i 3 %}</td>
          <td>{% pick_data_1 data_0 i 4 %}</td>
        </tr>
      {% endfor %}
      {% get_value_from_dict big_dataset 'guide_small_kind_1' as  small_kind_list_1 %}
      {% for small_kind in small_kind_list_1 %}
        {% to_two_kind_str big_kind small_kind as datalist_text %}
        {% guide_to_data_small big_kind small_kind as dataset %}
        {% get_value_from_dict dataset 'small_kind_count' as small_kind_count %}
        {% get_value_from_dict dataset datalist_text as data %} 
        {% if small_kind_count == 1 %}
          <tr>
            <td>{{small_kind}}</td>
            <td>
              <a href={{data.0.2}}
                class="law-board__table__link" target="_new">
                {{data.0.1}}
              </a>
            </td>
            <td>{{data.0.3}}</td>
            <td>{{data.0.4}}</td>
          </tr>
        {% else %}
          <tr>
            <td rowspan={{small_kind_count}}>{{small_kind}}</td>
            <td>
              <a href={{data.0.2}}
                class="law-board__table__link" target="_new">
                {{data.0.1}}
              </a>
            </td>
            <td>{{data.0.3}}</td>
            <td>{{data.0.4}}</td>
          </tr>
        {% endif %}
        {% for i in data|ranges_0 %}
          <tr>
            <td>
              <a href={% pick_data_1 data i 2 %}
                class="law-board__table__link" target="_new">
                {% pick_data_1 data i 1 %}
              </a>
            </td>
            <td>{% pick_data_1 data i 3 %}</td>
            <td>{% pick_data_1 data i 4 %}</td>
          </tr>
        {% endfor %}
      {% endfor %}
    {% endfor %}
  </table>
</div>
{% endblock %}

반복 시나리오에서 세웠던 그대로 이다.

이렇게 하면 아무리 새로운 이름의 분류가 생겨도, 있던 분류에 새로운 행이 추가되어도 자동으로 예쁘게 정렬된다!

성공~

profile
경험 == 배움

1개의 댓글

comment-user-thumbnail
2022년 3월 23일
답글 달기