Skip to content

테마 제작

기본 테마를 수정하거나 새로운 테마를 만들어 사용할 수 있습니다.

테마 생성

1. 테마 복사

새로운 테마를 만들기 위한 제일 쉬운 방법은 기본 테마를 복사하는 것입니다.
기본 테마(templates/basic) 를 동일한 경로에 이름을 변경해서 복사합니다.

아래에서는 user 라는 이름으로 복사했다고 가정합니다. 그럼 아래와 비슷한 구조로 되어 있을겁니다.

.
├─ templates/
│  ├─ basic/
│  └─ user/
│     ├─ ...
│     ├─ member/
│     │  ├─ ... 
│     │  └─ register_form.html
│     ├─ ...
│     ├─ LICENSE
│     ├─ readme.txt
│     └─ screenshot.png
└─ ...

관리자에서는 아래와 비슷한 화면이 보일겁니다.

Image title

2. 테마 정보 수정

기본 테마를 그대로 복사했기 때문에 테마 정보를 수정해야 합니다.
readme.txt 파일의 내용을 수정하고 screenshot.png 를 교체합니다.

아래는 readme.txt 파일 내용 예시입니다.

readme.txt
Theme Name: 사용자
Theme URI: http://user-domain.com/gnuboard6/theme/user
Maker: User Company
Maker URI: http://user-domain.com
Version: 1.0.0
Detail: 사용자 테마는 사용자님이 만든 테마입니다.
License: BSD

3. 테마 적용

관리자에서 원하는 테마를 적용하면 사용 중인 테마가 바뀌게 되며, 사용자 페이지에서 확인할 수 있습니다.

Image title

테마 수정

복사한 테마를 수정하여 나만의 테마를 만들 수 있습니다.

1. 기본 코드 설명 (Jinja Template)

테마 기초에서 회원가입 시 HTML 파일은 아래와 같은 순서로 실행된다고 했습니다.

flowchart LR
  A[main.py] --> B[bbs/register.py];
  B --> Template;

  subgraph Template
    C[base_sub.html] --> D[base.html];
    D[base.html] --> E[member/register_form.html];
  end

  Template --> F[브라우저 출력];

파이썬(bbs/register.py)에서 return templates.TemplateResponse("user.html", context) 로 값을 넘겨주면 HTML에서 이 값을 받아 Jinja Template 문법을 통해 명령문과 출력문의 조합으로 완성된 HTML 코드를 출력하게 됩니다.

아래는 필수로 알아야할 Jinja Template 개념 & 문법입니다.
더 많은 정보를 확인하시려면 Jinja Template Designer Documentation를 참고하세요.

1. 구분기호

Jinja 구분 기호에는 몇 가지 종류가 있습니다.

  1. {% ... %} : 주로 블록이나 if, for 문등의 명령문에 사용합니다.
    {% block title %}그누보드6{% endblock %}
    {% if request.state.login_member %}
    ...
    {% endif %}
    
  2. {{ ... }} : 주로 프린트문에 사용합니다.
    # request.base_url 을 출력할때
    {{ request.base_url }}
    
  3. {# ... #} : 주석문에 사용합니다.
    {# 이 내용은 주석처리되어 HTML 코드 소스보기에서 나오지 않습니다. #}
    

2. 변수

Jinja 템플릿에서는 {{ 변수명 }} 구문을 사용하여 변수 값을 출력할 수 있습니다. Python 코드에서 템플릿으로 변수를 전달하면, 해당 변수를 템플릿 내에서 사용할 수 있습니다.

{{ request.base_url }}

3. 필터

변수에 적용되는 변환을 정의합니다. 예를 들어, {{ 변수명|대문자 필터 함수 }}는 변수의 값을 대문자로 변환하여 출력합니다. Jinja는 다양한 내장 필터를 제공하며, 사용자 정의 필터도 생성할 수 있습니다.

{# number_format 사용자 정의 필터함수 #}
{{ 1234567890|number_format }}

{# 출력결과 #}
1,234,567,890

4. 제어문

if, for 같은 제어 구조를 제공하여 템플릿 내에서 조건부 로직이나 반복을 수행할 수 있게 합니다. 예를 들어, {% if 사용자_상태 == '활성' %} 또는 {% for 항목 in 항목_리스트 %}와 같은 구문을 사용할 수 있습니다.

{# if문 #}
{% if board.bo_table %}
    ...
{% else %}
    ...
{% endif %}

{# for문 #}
{% for board in boards %}
    {# board 변수가 있을 경우 #}
{% else %}
    {# boards가 없을 경우 #}
{% endfor %}

5. 상속과 포함

템플릿 파일 간의 상속을 지원하여 기본 템플릿의 블록을 재정의할 수 있게 해줍니다.

  • 상속은 extendsblock 태그를 사용하여 상위 템플릿을 사용할 수 있습니다.
  • 포함은 include 태그를 사용하여 다른 템플릿의 내용을 현재 템플릿에 포함시킬 수 있습니다. (그누보드5의 include와 비슷합니다.)
admin\templates\basic\popular_rank.html
{# 상속을 통해 상위 템플릿 사용 #}
{% extends "base.html" %}

{# 상위 템플릿의 head block에 내용 추가 #}
{% block head %}
    {# datepicker.html 파일 포함 #}
    {% include "datepicker.html" %}
{% endblock head %}

6. 매크로

매크로를 사용하면 재사용 가능한 코드 블록을 정의할 수 있습니다.
이는 함수와 유사하게 작동하며, 코드를 재사용하여 템플릿을 더 깔끔하게 유지할 수 있게 해줍니다.

admin\templates\basic\visit_forms.html
{# 정의 #}
{% macro anchor(type, fr_date='', to_date='') -%}
    <ul class="anchor">
        <li {% if type == 'list' %}class="active"{% endif %}><a href="./visit_list?fr_date={{ fr_date }}&to_date={{ to_date }}">접속자</a></li>
        ...
        <li {% if type == 'year' %}class="active"{% endif %}><a href="./visit_year?fr_date={{ fr_date }}&to_date={{ to_date }}"></a></li>
    </ul>
{%- endmacro %}

{# 사용 #}
{% block content %}
    ...
    {{ form.anchor('date', fr_date, to_date) }}
{% endblock %}

7. 공백 제어

템플릿에서 생성된 HTML의 공백을 제어하기 위해 Jinja는 공백 제어 구문을 제공합니다.
{%- 또는 -%}를 사용하여 태그 주변의 공백을 제거할 수 있습니다.

  1. 공백제어 기호가 없는 경우

    latest\basic.html
    {# 입력 #}
    <li class="basic_li">
        {% if write.icon_secret %}
            <i class="fa fa-lock" aria-hidden="true"></i>
            <span class="blind">비밀글</span>
        {% endif %}
        <a href="{{ url_for('read_post', bo_table=bo_table, wr_id=write.wr_id) }}">
            {% if write.is_notice %}<strong>{{ write.subject }}</strong>{% else %}{{ write.subject }}{% endif %}
        </a>
    
    {# 출력 #}
    <li class="basic_li">
    
            <i class="fa fa-lock" aria-hidden="true"></i>
            <span class="blind">비밀글</span>
    
        <a href="...">
            4535
        </a>
    

  2. 공백제어 기호가 있는 경우

    latest\basic.html
    {# 입력 #}
    <li class="basic_li">
        {% if write.icon_secret -%}
            <i class="fa fa-lock" aria-hidden="true"></i>
            <span class="blind">비밀글</span>
        {%- endif -%}
        <a href="{{ url_for('read_post', bo_table=bo_table, wr_id=write.wr_id) }}">
            {% if write.is_notice %}<strong>{{ write.subject }}</strong>{% else %}{{ write.subject }}{% endif %}
        </a>
    
    {# 출력 #}
    <li class="basic_li">
        <i class="fa fa-lock" aria-hidden="true"></i>
            <span class="blind">비밀글</span><a href="...">
            4535
        </a>
    

2. 예시

member/register_form.html
{# base.html을 확장합니다. base.html을 이 HTML의 기본틀로 사용한다는 것입니다. #}
{% extends "base.html" %}

{# 템플릿에서 변수를 선언하는 것입니다. #}
{% set title = "회원정보 수정" if member.mb_id else "회원가입" %} 

{# base.html에 block head에 이 스크립트를 넣으라는 것입니다. #}
{% block head %} 
    <script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js" async></script>
{% endblock %}

{# base_sub.html의 block title, base.html의 block subtitle의 영역을 "title" 변수로 치환하라는 것입니다. #}
{% block title %}{{ title }}{% endblock title %}
{% block subtitle %}{{ title }}{% endblock subtitle %}

{# base.html에 block content에 아래 내용으로 변경합니다. #}
{% block content %}
<div class="register">
    {# 컨트롤러에서 전달된 form.action_url 출력 #}
    <form id="fregisterform" name="fregisterform" action="{{ form.action_url }}"
          onsubmit="return fregisterform_submit(this)" method="post" enctype="multipart/form-data" autocomplete="off">
        ...
        <div id="register_form" class="form_01">
            <div class="register_form_inner">
                <h2>사이트 이용정보 입력</h2>
                <ul>
                    ...
                    <li>
                        <input type="hidden" name="old_email" value="">

                        {# 필터 함수로 처리된 회원 이메일 값 추가 #}
                        <input type="text" name="mb_email" value="{{ member.mb_email|default_if_none('') }}"
                              id="reg_mb_email" required class="frm_input email full_input required" size="70"
                              maxlength="100" placeholder="">
                        <label for="reg_mb_email" class="text_input">E-mail (필수)</label>

                        {# if문으로 조건부 출력 #}
                        {% if config.cf_use_email_certify %}
                            {% if is_register %}
                                <span class="frm_info">E-mail  발송된 내용을 확인한  인증하셔야 회원가입이 완료됩니다.</span>
                            {% else %}
                                <span class="frm_info">E-mail 주소를 변경하시면 다시 인증하셔야 합니다.</span>
                            {% endif %}
                        {% endif %}
                    </li>
                    ...
                </ul>
            </div>
        </div>
        <div class="btn_confirm">
            <a href="/" class="btn_close">취소</a>
            <button type="submit" id="btn_submit" class="btn_submit" accesskey="s">{% if is_register %}회원가입{% else %}정보수정{% endif %}</button>
        </div>
    </form>
</div>
<script>
    ...
</script>
{% endblock %}