본문 바로가기
Python

(TIL0914) flask-jinja 템플릿을 사용해보자 (jinja 분노편)

by 돈민찌 2021. 9. 14.
반응형

2021.09.14 오늘의 TIL

일본에서 만든걸까 오리엔탈리즘 같은걸까..

오늘은 지난 시간에 masonry로 머리 싸매던 것을 결국 부트스트랩4버전으로 내리는 것으로 합의를 보고 어떻게 하던 것을 끝내고, 그 다음 주차인 "나만의 영어 단어장 만들기"를 해봤다.

owlbot이라는 오픈API를 적극적으로 활용해서,

  1. 영단어를 검색하면 단어의 자세한 뜻과 발음 기호, 예문 등이 표시된다.
  2. 영단어를 북마크해 둘 수 있다. 북마크한 영단어의 상세페이지에서는 북마크 버튼이 사라지고 삭제버튼이 생긴다.
  3. 거기에 나만의 예문을 추가하거나 삭제할 수 있다. 
  4. 영단어를 삭제하면 연결된 예문들도 모두 사라진다.
  5. 영단어를 검색할 때 이미 북마크된 단어는 검색버튼을 누르면 빨간색으로 하이라이트된다.
  6. 영영 사전에 없는 단어를 검색하면 페이지를 얼럿을 띄우고 리로딩된다.
  7. 예문을 입력할 때 해당 영단어가 포함되지 않으면 입력되지 않고 얼럿이 뜬다.

이런 기능을 가진 영영단어장을 만드는 것이었다.

오늘의 땡스투 Owlbot

어느 정도의 CSS를 잡아서 제공해주시기 때문에 기능적인 면에서만 구현하는 것이 목표였는데,

플라스크+제이쿼리 조합만으로 만들어오던 웹개발 종합반 기초반과 다르게, 플라스크에 의존성으로 함께 설치되는 JINJA2 템플릿을 활용해서 조금 더 정적인 사이트를 만들어 렌더링 시간을 줄여 사용자의 불편을 줄이는 것이 골자였다.

실제로 아무리 서버가 잘 돌아가고 있어도 빈화면에 데이터를 불러와 제이쿼리로 어펜드하는 식의 렌더링보다 이런 마크업 방식이 훨씬 빠르다(UI 쪽으로 더 예쁘긴 더 어렵겠지만..)

쉬울거라 생각했다. 왜냐하면  {% ifequal ** %},{{ content }} 이런 식의 문법으로 쓰여지는 장고를 코딩 공부 초반에 이미 경험해본 적이 있었기 때문이다. 그땐 너무 사소한 기능이 하나하나 장벽으로 다가와서 끝까지 완주를 못하긴 했지만, 어느정도 자바스크립트와 파이썬 문법에 기본기가 생긴 지금은 쉬울 것이라 생각했다.

<div id="definitions">
    <div style="padding:10px">
        <i>{{ definition.type }}</i>
        {% if definition.definition %}
            <br>{{ definition.definition.encode('ascii','ignore').decode('utf-8')|safe }}<br>
        {% endif %}
        {% if definition.example %}
            <span class="example">{{ definition.example.encode('ascii','ignore').decode('utf-8')|safe }}</span>
        {% endif %}
    </div>
</div>

플라스크에서 jinja 템플릿을 사용하는 방법은 간단하다. 기본적인 메소드인 render_template("index.html")과 함께 추가적으로 인수를 넘겨주고, 그 인수를 {{ 인수 }} 문법으로 HTML 안에서 자유롭게 사용하면 된다.

  • safe : 이스케이프를 적용하지 않고 값을 렌더링한다 (<b> 같은 태그도 적용)
  • capitalize : 첫 번째 문자를 대문자로 만들고 나머지는 소문자로 만든다
  • lower : 값을 소문자로 만든다
  • upper : 값을 대문자로 만든다
  • title : 값의 각 단어들을 캐피털라이즈한다
  • trim : 앞부분과 뒷부분에서 공백 문자를 삭제한다
  • striptags : 렌더링하기 전에 값에 존재하고 있는 HTML 태그를 제거한다

위의 경우 definition이라는 변수를 파이썬에서 몽고디비 데이터를 조회해 넘겨주고,  HTML에서 그걸 받아서 화면에 표시하는 방법으로 영영 단어 사전의 정의와 예문을 표시한다. 해당 api의 특성상 ASCII 코드로도 표현되지 않는 기타 문장부호나 발음기호들이 있어 인코딩-디코딩을 해서 표시가 안되는 문장을 날리는 것도 그리 이해가 어렵지 않았고, 변수를 표현할 때 쓰는 {{ }} 문법에 뒤에 파이프 | 를 사용해 약간의 포맷 조작을 할 수 있는 것도 장고의 경우와 비슷했다.

그런데 플라스크를 통해 넘어오는 jinja 의 변수를 자바스크립트에서 조건문을 걸고 컨트롤하려고 하니 진짜 짜증이 났다... 같은 템플릿 언어로 쓰여지는 html 사이에 있을 때는 오히려 반복문 등의 사용으로 제이쿼리로 값을 채우는 방식보다 훨씬 수월했는데, 이걸 플라스크를 통해 렌더링된 HTML의 script 태그 안에서 사용하려고 하니 이유 없이 None 값이 출력되거나 디비에 저장되는 현상이 있었다. (그걸 또 Owlbot이 None은 아무 것도 아닌 것이라고 친절하게 영영번역해줌) 이 환장할 상황에서 너무 스트레스를 받았다.

@app.route('/detail/<keyword>')
def detail(keyword):
    status = request.args.get('status')
    url = f'https://owlbot.info/api/v4/dictionary/{keyword}'
    headers = {'Authorization': 'Token 72157bede528157a590d1e6603619f99d7907e03'}
    req = requests.get(url, headers=headers)
    if req.status_code != 200:
        return redirect(url_for("main", msg="e"))
    result = req.json()
    return render_template("detail.html", word=keyword, result=result, status=status)

이런 식으로 넘겨지는 데이터를 

function save_word() {
    $.ajax({
        type: "POST",
        url: `/api/save_word`,
        data: {
            word_give: "{{ word }}",
            definition_give: "{{ result.definitions[0].definition }}"
        },
        success: function (response) {
            alert(response["msg"])
            window.location.href = "/detail/{{ word }}?status=old"
        }
    });
}

이렇게 변수인 것처럼 캐치해서 사용하는 거...였는데

let word = location.pathname.split("/")[2]

function save_word() {
    $.ajax({
        type: "POST",
        url: `/api/save_word`,
        data: {
            word: word,
            definition: "{{ result.definitions[0].definition }}"
        },
        success: function (response) {
            alert(response["msg"])
            window.location.href = `/detail/${word}?status=old`
        }
    });
}

하다가 결국 그냥 속 터질 것 같아서 location.pathname이랑 location.search 값을 직접 불러와서 뿌셔뿌셔해서 변수로 활용하는 것으로 대체했다. 결과적으로 원하는 결과물은 나오게 됐지만 뭔가 이게 잘 한건지 모르겠다....

그리고 이번 미니 프로젝트에서 사용하는 CSS는 파일로 분리했지만 meta 태그와 link, script  등의 상단의 수많은 코드가 반복되는 것 너무 거슬렸었는데 역시 진자2에서도 장고를 쓸 때처럼 BASE.HTML을 extends하거나, 아예 플라스크 모듈 내의 부트스트랩을 사용할 수 있다고 한다(강의에서 다루지는 않았다.)

뭘 배워도 단물만 잘 빨아먹으면 된다고 생각하니까 이런 템플릿 언어 깨작이는 동안에도 뭐라도 배운 것 같아 다행이다.

  1. 제이쿼리나 자바스크립트 등으로 데이터를 내부 API로 불러와 화면에 렌더링하는 것은 throtting을 굳이 해보지 않더라도 1초 정도는 빈 화면을 봐야하는 불편감이 있고, 그 부분을 상당히 해결하고 html의 문제는 html 안에서!! 해결하는 플라스크+진자2의 방식이 분명히 유용한 점은 있다고 생각이 들었다.
  2. 플라스크 서버로 만드는 웹서비스에서 중복되는 기초적인 css나 js의 경우 static 폴더로 분리가 가능하기는 하지만 '(url_for("static", filename="style.css")' <= 이런 식으로 임포트 형태로 바꾸더라도 어쨌든 모든 페이지가 같은 스크립트를 공유하면 또 그것을 임포트하는 문장이 필요해질 수 밖에 없는데, 이 부분을 진자2의 Extends를 잘 활용하면 코드 절약에 큰 도움이 될 것 같다.
<!DOCTYPE html>
<html lang="en">
<head>
    {% block head %}
    <link rel="stylesheet" href="style.css" />
    <title>{% block title %}{% endblock %} - My Webpage</title>
    {% endblock %}
</head>
<body>
    <div id="content">{% block content %}{% endblock %}</div>
    <div id="footer">
        {% block footer %}
        &copy; Copyright 2008 by <a href="http://domain.invalid/">you</a>.
        {% endblock %}
    </div>
</body>
</html>
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
    {{ super() }}
    <style type="text/css">
        .important { color: #336699; }
    </style>
{% endblock %}
{% block content %}
    <h1>Index</h1>
    <p class="important">
      Welcome to my awesome homepage.
    </p>
{% endblock %}

비록 온라인 메타버스이긴 하지만 서로간의 (약간의 감시와) 동기부여가 역시 혼자 코딩을 짤 때보다 훨씬 도움이 된다.

이 길의 끝에 나는 어떤 회사에 취직하게 될려나?? 나중에는 어떤 사업을 하게 될려나?? 궁금해진다.

마지막으로 velog에서 발견한 플라스크 간단리뷰

 

🧵'플라스크 웹 개발' 3장, 템플릿

유지 보수하기 쉬운 어플리케이션 작성 시 중요한 점은 깔끔하고 잘 구성된 코드를 작성하는 것이다. 플라스크는 Jinja2라는 템플릿 엔진을 사용하여 이를 구현한다.

velog.io

 

반응형

댓글