Notice
Recent Posts
Recent Comments
05-21 07:17
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

Byeol Lo

Flask - 6. Tutorial Templates 본문

BackEnd/Flask

Flask - 6. Tutorial Templates

알 수 없는 사용자 2024. 2. 14. 15:52

 이전에 login, logout, register의 view를 만들었다. 하지만 이는 브라우저에서 작동을 하지 않는데 그 이유는 render_template()을 통해 html을 보내줘야 하는데, 해당 template이 없기 때문이다. 따라서 이번엔 template을 만들어서 직접 브라우저에 띄울 것이다. 이런 정적 template은 보통 templates 디렉터리에 저장되어지고, 이 템플릿들은 수신 받은 데이터를 통해 렌더링 될 최종 문서를 생성한다. Flask의 Jinja 템플릿 라이브러리를 사용하여 생성할 수 있다.

 Jinja는 첫 intro에서도 말했다시피 HTML 템플릿으로 렌더링 되는 모든 데이터를 "자동 이스케이프" 하도록 구성된다. 이는 사용자 입력이 안전한지 자동으로 체크해준다는 것이다. 또한 템플릿 내용에 특수 구분을 통한 구분은 해당 템플릿이 정적 데이터와 구별하는 데 사용되고 대표적으로 {{ }} 같은 표현식과 {% %} 등의 제어 흐름(if, for)문 등이 있다.

 

The Base Layout

 application의 각각 페이지들은 같은 기본적인 layout을 가져야 할 것이다. 보통 하나의 홈페이지를 살펴보면 내부적인 내용들 같은 것만 다르고 전반적인 틀은 동일함을 볼 수 있다. HTML 구조를 작성하는 대신 각 템플릿은 기본 템플릿을 이용해 확장(extends: 상속)하게 되는 것이다. 기본 템플릿을 작성해보자.

<!-- flaskr/templates/base.html -->
<!doctype html>
<title>{% block title %}{% endblock %} - Flaskr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
  <h1>Flaskr</h1>
  <ul>
    {% if g.user %}
      <li><span>{{ g.user['username'] }}</span>
      <li><a href="{{ url_for('auth.logout') }}">Log Out</a>
    {% else %}
      <li><a href="{{ url_for('auth.register') }}"> Register</a>
      <li><a href="{{ url_for('auth.login') }}"> Log In</a>
    {% endif %}
  </ul>
</nav>
<section class="content">
  <header>
    {% block header %}{% endblock %}
  </header>
  {% for message in get_flashed_messages() %}
    <div class="flash">{{ message }}</div>
  {% endfor %}
  {% block content %}{% endblock%}
</section>

 html은 비교적 css, javascript보다 배우기 쉬우니 유튜브나 검색을 통해 기본적인 것만 배우길 추천한다. g 객체를 사용하여 템플릿에서 해당 객체에 접근하고 load_logged_in_user에 따라 g.user가 설정이 되면서, None이라면 register과 login의 a 태그를 보여주는 것이다. 그리고 만약 flash 메시지를 띄우고 싶다면 get_flashed_messages()의 iterable한 객체를 가져와 message로 뿌려주게 된다. 여기서 자주 보이는 Block이 보이는데

  • {% block title %} 브라우저 탭과 창 제목에 표시되는 제목
  • {% block header %} title 과 유사하지만, 페이지에 표시되는 제목
  • {% block content %} 로그인 양식이나 블로그 게시물과 같은 각 페이지의 콘텐츠가 이동하는 곳

그리고 blueprint를 사용한 템플릿은 해당 blueprint의 directory에 배치되어 사용할 수 있다. ( /templates/auth/ ...)

 

Register

<!-- flaskr/templates/auth/register.html -->

{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    <label for="username">Username</label>
    <input name="username" id="username" required>
    <label for="password">Password</label>
    <input type="password" name="password" id="password" required>
    <input type="submit" value="Register">
  </form>
{% endblock %}

 base.html을 상속받은 register.html이며, 이 문서를 렌더링 할 때에는 base.html의 구조 위에 header, content만 얹어놓는 형식이다. 간단하고 매우 편리함을 알 수 있으며 객체 기반(지향 아님)의 구현을 할 수 있다.

 

Login

<!-- flaskr/templates/auth/login.html -->

{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    <label for="username">Username</label>
    <input name="username" id="username" required>
    <label for="password">Password</label>
    <input type="password" name="password" id="password" required>
    <input type="submit" value="Log In">
  </form>
{% endblock %}

 

이제 실행시켜 직접 회원가입을 해보자.

 

 

밑은 css 이다. 간단하니 따로 배우는 것을 추천한다.

/* flaskr/static/style.css */
html { font-family: sans-serif; background: #eee; padding: 1rem; }
body { max-width: 960px; margin: 0 auto; background: white; }
h1 { font-family: serif; color: #377ba8; margin: 1rem 0; }
a { color: #377ba8; }
hr { border: none; border-top: 1px solid lightgray; }
nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; }
nav h1 { flex: auto; margin: 0; }
nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; }
nav ul  { display: flex; list-style: none; margin: 0; padding: 0; }
nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; }
.content { padding: 0 1rem 1rem; }
.content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; }
.content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; }
.flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; }
.post > header { display: flex; align-items: flex-end; font-size: 0.85em; }
.post > header > div:first-of-type { flex: auto; }
.post > header h1 { font-size: 1.5em; margin-bottom: 0; }
.post .about { color: slategray; font-style: italic; }
.post .body { white-space: pre-line; }
.content:last-child { margin-bottom: 0; }
.content form { margin: 1em 0; display: flex; flex-direction: column; }
.content label { font-weight: bold; margin-bottom: 0.5em; }
.content input, .content textarea { margin-bottom: 1em; }
.content textarea { min-height: 12em; resize: vertical; }
input.danger { color: #cc2f2e; }
input[type=submit] { align-self: start; min-width: 10em; }

 

 

Comments