안녕하세요 한헌종입니다.
오늘은 python 으로 작성된 웹 프레임워크인 flask 를 이용해 웹페이지를 구현해보겠습니다.
참고로 저는 이 웹사이트 를 통해 flask 를 배울 수 있었습니다.


1. Flask 기본 구조

폴더 구조:

myapp/
    __init__.py
    routes.py
run_myapp.py

myapp/__init__.py:

from flask import Flask

app = Flask(__name__)

from myapp import routes

myapp/routes.py:

from flask import Flask
from myapp import app

@app.route("/")
@app.route("/index")
def index_func():
    return "hello world!"

run_myapp.py:

from myapp import app

웹페이지 실행하기

$ export FLASK_APP=run_myapp.py
$ flask run
 * Serving Flask app 'run_myapp.py' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

이후 http://127.0.0.1:5000/ 로 접속하면 “hello world!” 를 볼 수 있습니다.

-
Hello world 로 시작하는게 정석!

설명하자면, __init__.py 작성을 통해 myapp 이라는 패키지가 만들어지게 됩니다.
여기서 Flask 클래스의 인스턴스인 app 이라는 변수가 만들어지고, 이는 myapp 패키지의 멤버 변수가 됩니다.
routes.py 모듈에서는 myapp 패키지의 app 변수를 import 합니다.
그리고 이를 decorator 로 활용해 “/” 와 “/index” 페이지를 index_func 함수에 연결해 줍니다.
즉, 이 URL 에 대한 request 가 들어오면 해당 함수가 응답해주는 겁니다.
여기서는 다른 작업 없이 문자열 “hello world!” 만 반환하고 있습니다.
환경변수 FLASK_APP 을 run_myapp.py 로 설정하고 flask run 명령어를 입력하면 웹페이지가 구동됩니다.


2. html rendering 및 html 상속

위에서 만들어진 웹 페이지는 html 구조 없이 문자열만 출력한 상태였습니다.
이제 html 을 사용해 웹 페이지의 구조를 만들어보도록 하겠습니다.

이 때, 이 웹사이트는 하나가 아닌 여러 페이지로 구성되겠죠?
그런데 각 페이지를 만들 때마다 매번 html 을 복사해야 한다면 매우 귀찮을 겁니다.
따라서 이를 여러번 반복하지 않고 templates 를 사용해 하나의 html 을 여러곳에서 활용할 수 있게 해봅시다.

만들어진 html 을 렌더링하기 위해 flask 의 render_template 함수가 사용됩니다.

폴더 구조:

myapp/
    templates/
        base.html
        index.html
        somepage.html
    __init__.py
    routes.py
run_myapp.py

base.html:

<html>
    <head>
        <title>My website</title>
    </head>
    <body>
        <div>
            Go to:
            <a href="{{ url_for('index_func') }}">Home</a>
            <a href="{{ url_for('some_func') }}">Some page</a>
        </div>
        {% block content %}{% endblock %}
    </body>
</html>

index.html

{% extends "base.html" %}
{% block content %}
<div>
    This is home page. Hello world!"
</div>
{% endblock %}

somepage.html

{% extends "base.html" %}
{% block content %}
<div>
    <p>
        Some page. Nothing created yet.
    </p>
</div>
{% endblock %}

routes.py

from flask import Flask, render_template, url_for
from myapp import app

@app.route("/")
@app.route("/index")
def index_func():
    return render_template("index.html", title="Home")

@app.route("/somepage")
def some_func():
    return render_template("somepage.html", title="SomePage")

init 과 run_myapp.py 는 바꾸지 않습니다.
이후 실행하면 다음과 같은 화면이 나옵니다.

-
http://localhost:5000/ 의 모습
-
http://localhost:5000/somepage 의 모습

설명드리자면, 먼저 base.html 은 html 기본 골격을 가지고 있습니다.
head 에는 title 항목을 가지고 있고, body 에는 웹사이트의 두 링크를 가지고 있군요.
여기서 link 는 url_for 라는 함수를 통해 routes.py 의 각 함수를 연결해주고 있습니다.
여기서 실제 url 을 적어둘 수 도 있지만, url_for 라는 함수를 사용하면 다음과 같은 이점이 있습니다.

  1. url 은 함수 이름보다 자주 바뀝니다. 따라서 함수 이름으로 지정해두면 수정할 일이 적습니다.
  2. url 은 이후 동적 요소를 가질 수 있는데, 이를 위해 url concatenation 으로 작업하면 틀리기 쉽습니다. url_for 는 이런 복잡한 url 을 만들어줄 수 있습니다.

이후 아래쪽에 {% block content %}{% endblock %} 구문은 이 부분에 다른 content 가 올 수 있음을 알려주고 있습니다.
이런 “{% 명령어 %}” 구문은 Jinja2 template engine 이 사용하는 expression/logic 구문입니다.

이렇게 만들어진 base.html 은 각각 index.html 과 somepage.html 에서 사용하고 있습니다.
{% extends “base.html” %} 구문을 통해 base.html 을 가져오고 있고,
{% block content %} ~ {% endblock %} 안에서 링크마다의 형태를 구축했습니다.

routes.py 를 보시면, 서로 다른 url 에 대한 두 함수가 만들어진 것을 볼 수 있습니다.
각 함수는 반환값으로 render_template() 을 사용하고 있죠.
render_template() 은 Jinja2 template engine 을 사용해 웹페이지를 보여줍니다.


3. 데이터 전달 및 GET, POST

이제 웹페이지에 신호를 전달하고, 전달받은 명령어로 결과값을 보여주는 부분을 작성해보겠습니다.

somepage.html

{% extends "base.html" %}
{% block content %}
<div>
    <p>This is a calculator!</p>
    <form action="" method="POST">
        <input type="text" name="number1">
        <input type="text" name="number2">
        <input type="submit" name="button" value="add">
        <input type="submit" name="button" value="sub">
        <input type="submit" name="button" value="mul">
        <input type="submit" name="button" value="div">
    </form>
    {% if result %}
    <p>result: {{ result }}</p>
    {% endif %}
</div>
{% endblock %}

routes.py

from flask import Flask, render_template, url_for, request
from myapp import app

@app.route("/")
@app.route("/index")
def index_func():
    return render_template("index.html", title="Home")

@app.route("/somepage", methods=["GET", "POST"])
def some_func():
    form_data = request.form

    if request.method == "POST":
        num1 = int(form_data["number1"])
        num2 = int(form_data["number2"])
        if form_data["button"] == "add":
            return render_template(
                "somepage.html", title="SomePage", result=num1 + num2
            )
        if form_data["button"] == "sub":
            return render_template(
                "somepage.html", title="SomePage", result=num1 - num2
            )
        if form_data["button"] == "mul":
            return render_template(
                "somepage.html", title="SomePage", result=num1 * num2
            )
        if form_data["button"] == "div":
            return render_template(
                "somepage.html", title="SomePage", result=num1 / num2
            )

    return render_template("somepage.html", title="SomePage")

위 코드를 작성한 뒤 실행하면 다음과 같은 결과가 나옵니다.

-
게산기 페이지
-
3 과 4 를 입력했습니다. 이제 mul 을 누르면…
-
결과로 12 가 나옵니다.

위 코드를 설명드리도록 할게요.
사용자가 전달하는 request 를 받기 위해 먼저 flask 의 request 함수를 import 합니다.
routes.py 에서 some_func 의 decorator 를 보면, methods 두 가지가 가능한 것을 설정해 두었습니다.
여기서 request.method 는 사용자가 전달한 request 에 따라 “GET” 혹은 “POST” 값을 가질 수 있죠.
이 때 값을 전달하는 POST method 를 받게 되면, 아래 if 문 안으로 들어가게 됩니다.

request.form 에는 html 에서 form 구문에서 전달하는 값들의 “name” 과 “value” 가 각각 dictionary 형태로 전달됩니다.
somepage.html 페이지에서 숫자 두 개를 입력하고 버튼을 누르게 되면, form 구문을 통해 POST 명령어가 전달되는데요.
이를 통해 {“number1”: “3”, “number2”: “4”, “button”: “mul”} 이라는 데이터가 request.form 에 dictionary 형태로 전달되는 거죠.
따라서 이 값을 받아 적절히 계산한 뒤 다시 somepage.html 에 전달해 페이지를 렌더링하는 겁니다.
이 때는 인자로 result 값을 전달하는데요, somepage.html 에서는 이 result 라는 값을 받아서 사용할 수 있게 됩니다.

somepage.html 을 보면, {% if result %} 로 시작하는 구문이 보이는데요,
이것이 result 값이 있는지를 확인하는 부분입니다.
맨 처음 somepage.html 에 들어가게 되면 아무 값도 전달하지 않았을 테니, 결과도 보이지 않게 해야겠죠.
그런데 button 을 눌러 값을 전달하게 된다면, 이 계산 결과 result 를 이 if 문을 통해 보여줄 수 있게 됩니다.

이렇게 값을 html 로 전달하고, 다시 html 에서 form 으로 값을 전달받을 수 있는 것입니다.


네 오늘은 간단하게 flask 를 어떻게 사용할 수 있는지 알아보았습니다.
좀 더 나아가 DB 를 쓰고, CSS 로 모양새를 갖추고, 더 복잡한 함수를 쓰게 되면 훨씬 더 다양한 일을 할 수 있겠죠?
도움이 되셨기를 바라며, 오늘 포스트는 여기까지 하도록 하겠습니다.
그럼 다음 시간에 만나요!