본문 바로가기
개발/웹

Flask 블로그 제작기 (1) - 공식 튜토리얼 따라잡기

by pandatta 2022. 9. 13.

플라스크로 서비스되던 구버전 블로그에서 옮겨왔습니다.


사실 플라스크 공식 튜토리얼을 제가 하나하나 번역해가면서 정리하기보다는, 이 튜토리얼을 갓-구글번역기로 번역해서 보는게 더 편하실 겁니다ㅋㅋ 저는 튜토리얼에서 이해하기 힘든, 플라스크에서 가장 기본이 되는 개념 몇 가지만 정리해보도록 하겠습니다.

일단, 기본적으로 플라스크로 만든 웹 어플리케이션(웹사이트)은 다음과 같은 프로젝트 구조를 가집니다.

Root 폴더
+- instance 폴더: 어플리케이션을 실행하고 나서 채워지는, 또는 운영자에 따라 다르게 설정될 수 있는 데이터들.
|   +- config.json 또는 config.yaml 등
|   +- database 파일 (sqlite DB의 경우)
+- source code 폴더
    +- static 폴더: CSS 스타일 같은, python과 상관 없는 정적인 데이터들.
        |  +- style.css: 웹페이지의 디자인을 결정
        +- templates 폴더: python에서 사용하는 html 템플릿들.
        |  +- auth 폴더: 아래의 auth blueprint에서 사용하는 html.
        |  +- blog 폴더: 이하동문
        |  +- base.html: blueprint와 관계없이 같은 사이트의 모든 페이지에서 공통으로 쓰는 html.
    +- __init__.py: python3부터는 사용하지 않는 코드인데 flask는 쓴다. 어플리케이션을 초기화하는 용도.
        +- db.py: database의 초기화와 관련된 코드들
        +- auth.py: blueprint1. 사용자 인증과 관련된 코드들. 도메인/auth로 접속하는 페이지들.
        +- blog.py: blueprint2. 블로그 포스팅할 때 쓰는 코드들. 도메인/blog로 접속하는 페이지들.

구조를 크게 네 가지로 나누면 다음과 같습니다. 1) Flask 웹 어플리케이션, 2) DB, 3) 블루프린트, 4) 템플릿.

또는 플라스크 웹 앱이 돌아가는 단계를 크게 두 가지로 나눌 수 있습니다.

1) GET: 웹 앱에서 DB와 연결하고 데이터를 가져와 블루프린트에 뿌려주면, 블루프린트는 템플릿을 가져와 사용자에게 보여준다.
2) POST: 사용자가 블루프린트에 데이터를 입력하면, 웹 앱에서 데이터를 DB에 저장한다.

여기서 제일 궁금한 것 하나만 꼽자면, 블루프린트일 것입니다. 블루프린트는 한글로 하면 청사진이며, 하나의 웹사이트 안에서 여러 가지 다양한 형태의 세부 사이트를 구현하기 위해 나눠놓은 것이라고 생각하시면 됩니다. 참고로 이 블로그에서는 직접적으로 포스팅을 하는 blog 블루프린트와, 로그인/회원가입 등의 auth 블루프린트를 나눠두었습니다.

이 복잡한 (사실 다른 web framework에 비하면 매우 단순한) 구조에서 제일 핵심이 되는 파일 하나를 꼽자면 __init__.py입니다. 이 파일은 flask run이라는 간단한 실행명령어로 우리가 웹사이트를 실행할 때 직접적으로 실행되는 유일한 코드입니다. 이 코드의 대략적인 구조는 다음과 같습니다.

from flask import Flask  # Flask framework를 불러온다.
from . import auth, blog, db  # auth, blog 블루프린트와 db 모듈을 불러온다.

def create_app():  # flask run 시에 실행할 메써드.
    app = Flask(__name__, instance_relative_config=True)  # 플라스크 앱을 초기화.
    app.config.from_json("config.json")  # instance/config.json 파일에서 config를 읽어옴.

    db.init_app(app)  # db 모듈에서 정의된 함수를 실행.

    app.register_blueprint(auth.bp)  # auth, blog 블루프린트와 연결
    app.register_blueprint(blog.bp)
    app.add_url_rule("/", endpoint="index")  # 도메인으로 접속 시 index 템플릿을 호출

    return app

create_app()

진짜 단순합니다. 여기서 더 알아야하는 부분은 db 모듈의 init_app() 메써드와, 각 블루프린트의 구조밖에 없습니다.
이제 db 모듈로 가보겠습니다.

import click  # flask run 처럼, python3를 통해 실행하지 않고도 python code 내 메써드를 실행할 수 있게 하는 라이브러리.
from flask import current_app, g
from flask.cli import with_appcontext

def init_db():
    conn = get_conn()  # python을 통해 database와 connect하는 메써드. DB 종류에 따라 다르게 정의될 수 있다.
    cur = get_cur()  # python을 통해 database의 cursor를 가져오는 메써드. 이하동문.

    with current_app.open_resource("schema.sql") as sql_fh:  # schema.sql 이라는 SQL 코드를 읽어와서,
        cur.execute(sql_fh.read().decode("utf8"))  # 실행한다. DB 스키마가 담긴 코드가 실행되면서 DB가 초기화된다.
        conn.commit()

@click.command("init-db")  # flask init-db 라는 실행명령어가 아래 메써드를 실행한다. 자세히는 지금 몰라도 됨.
@with_appcontext
def init_db_command():
    init_db()
    click.echo("Initialized the database.")

def init_app(app):  # __init__.py에서 가져다쓰는 메써드.
    app.teardown_appcontext(close_conn)  # 혹시 DB가 열려있었다면 닫은 다음,
    app.cli.add_command(init_db_command)  # DB를 새로 열고 앱과 DB를 연결한다.

여기는 조금 복잡합니다. 웹 어플리케이션이 블로그 글이나 사용자 정보를 DB에서 가져오거나 쓰기 위해서 DB와 연결하는 파트이기 때문입니다.

MySQL이나 SQLite, PostgreSQL처럼 많은 종류의 데이터베이스가 있고, 이에 따라 조금씩 코드의 형태가 달라질 수 있겠지만, 결국 데이터베이스는 1) 초기화한 다음, 2) 열어서 앱과 연결하고, 3) 쓰거나 지우거나 업데이트합니다. 이 db.py 파일에서는 데이터베이스를 초기화한 다음 열어서 앱과 연결하는 단계를 담고 있으며, PyMySQL이나 psycopg2와 익숙한 사람이라면 connection과 cursor를 어떻게 다루는지 알테니 더욱 코드를 친숙하게 받아들일 수 있었을 겁니다. 그렇지 않은 사람들을 위해 다음 포스트 중 한 번을 제가 사용중인 PostgreSQL - psycopg2 - Python의 설치 및 연결방식으로 꾸며보도록 하겠습니다.

아무튼 플라스크로 웹 어플리케이션을 실행하고, DB와 연결했으니, 그 DB에서 가져온 정보를 보여줄 블루프린트가 필요합니다. 블루프린트로 넘어가봅시다.

from flask import Blueprint, g, render_template, url_for

from .db import get_conn, get_cur

bp = Blueprint("blog", __name__, url_prefix="/blog")  # 블루프린트를 초기화한다. 도메인/blog로 접속할 수 있다.

@bp.route("/", methods=("GET",))  # 도메인/blog/로 접속 시에 메써드를 실행한다.
def index():
    cur = get_cur()  # db.py에서 정의한, DB의 커서를 가져오는 메써드.
        cur.execute(  # DB에서 포스트들을 가져오는 SQL query문.
                "SELECT p.id, title, body, created, modified, author_id, views, username "
                "FROM posts p JOIN users u ON p.author_id = u.id "
                "ORDER BY created DESC;"
        )
    posts = cur.fetchall()

    return render_template("blog/index.html", posts=posts)  # blog/index.html 템플릿에 posts 변수에 저장된 포스트들을 넘겨준다.

# 도메인/blog/create로 접속 시, 또는 해당 url에서 글을 쓰고 저장했을 시 메써드 실행.
@bp.route("/create", methods=("GET", "POST"))
def create():
    if request.method == "POST":  # POST는 단순 접속인 GET과 다르게, 해당 url에서 사용자가 DB에 저장할 값이 있을 때 실행된다.
        title = request.form["title"]  # 해당 url의 템플릿에서 전달된 값들은 request.form에 저장된다.
        body = request.form["body"]

                conn = get_conn()
                cur = get_cur()
                cur.execute(  # 가져온 값들을 DB에 저장
                        "INSERT INTO posts (title, body, author_id)"
                        " VALUES (%s, %s, %s);",
                        (title, body, g.user["id"]),
                )
                conn.commit()
                return redirect(url_for("blog.detail"))  # POST를 했을 때에는 이런 템플릿을 보여준다.

    return render_template("blog/create.html")  # GET을 했을 때에는 이런 템플릿을 보여준다.

정말 단순합니다. 사실 get_cur()에서 cur.fetchall() 까지는 Flask가 아니라 PyMySQL과 같은 데이터베이스 라이브러리의 몫입니다. Url에 접속하기 위해서 Flask에서 해주는 부분은 1) 블루프린트를 초기화, 2) 특정 url로 접속 시 해당 메써드를 실행, 3) 템플릿을 렌더링하되, 우리가 주고 싶은 값을 넘겨줌, 이렇게 세 단계 밖에 없습니다. 반대로 url에서 DB에 값을 입력할 때는 Flask에서 1) 템플릿을 통해 값을 전달받음, 2) DB에 값을 저장, 3) 템플릿을 렌더링해서 다시 원하는 url로 돌아가기 밖에 없습니다.

마지막으로 알아야할 것은 템플릿입니다. 템플릿은 html 파일이므로, 사실상 플라스크도 아닙니다. 플라스크가 템플릿과 소통하는 부분은 Jinja2라는 다른 프로그래밍언어로 적혀져있는 매우 협소한 부분 뿐입니다.

<!doctype html>
<html lang="en">
<head>
    <title>Dwarf in the Flask</title>
</head>
<body>
{% for post in posts %}
<h2>{{ post['title'] }}</h2>
<p>{{ post['body'] }}</p>
{% endfor %}
</body>
</html>

{% %}로 둘러싼 구문에서 조건문이나 반복문을 처리하고, {{ }}로 둘러싼 구문에서 변수를 가져오는 것이 Jinja의 기본입니다.

진짜 이것뿐입니다! 이제 플라스크를 실행하는 것만이 남았습니다. 만약 pip install flask로 플라스크를 이미 다운받았다면, 해야할 것이 몇 가지가 더 있긴 합니다ㅎㅎ

  1. 환경변수 설정하는 법 익히기. 윈도우에서 환경변수를 클릭클릭해서 설정해둬도 되고, powershell에서 $env:FLASK_ENV='development' 또는 linux bash에서 export FLASK_ENV='development' 등을 실행하거나.
  2. FLASK_APP 환경변수에 init.py에서 저장한 웹 어플리케이션 이름을 저장. 여기서는 Flask(__name__)을 통해 __init__.py가 위치한 dwarf-in-the-flask라는 폴더의 이름을 웹 어플리케이션 이름으로 지정해두었습니다.
  3. FLASK_ENV 환경변수에 development를 할당. 개발용 서버라는 것인데, 실제로 배포용도로 사용할 서버는 이렇게 하지 않고, 배포용 환경을 따로 설정해줘야합니다. Python은 WSGI(Web Server Gateway Interface)라는, python 전용 간단한 웹서버 인터페이스를 사용해서 웹과 프로그램을 연결해주는데, 플라스크에는 기본적으로 werkzeug라는 WSGI가 설치되어있습니다. 그러나 배포할 때는 gunicorn이나 uWSGI 등의 배포용 WSGI를 설치해줘야 웹 어플리케이션이 실행될 수 있습니다.
  4. DB설치한 다음 flask init-db 실행하기. DB는 플라스크에서 기본 제공해주는 SQLite를 사용해도 되지만, 큰 사이트는 MySQL이나 PostgreSQL 등을 사용합니다. 자세한 내용은 다음에 따로 포스트하겠습니다. flask init-db를 실행하면 위의 db.py에 있는 init_db() 메써드가 실행되면서 웹 어플리케이션에 필요한 DB 스키마를 쫘르륵 설정해줄 수 있습니다.
  5. flask run 실행하고, localhost:5000 url으로 접속하기. 기본적으로 플라스크 개발 테스트 용으로 localhost의 5000 포트를 열어주게 설정되어있습니다. 나중에 배포용으로 dwarfintheflask.xyz같은 도메인을 localhost 대신에 써줄 수도 있고, 포트를 5000이 아닌 5176 등으로 열어줄 수도 있습니다.

거듭 말하지만 자세한 내용은 플라스크 튜토리얼에서 상세히 설명해주고 있기 때문에, 여기서는 제가 공부하면서, 이 블로그를 만들면서 플라스크 튜토리얼에 상세한 설명이 부족했던 부분들 위주로 서술해보았습니다. 다음으로는 플라스크 튜토리얼에서도 설명해주지 않는 부분들, 이를테면 배포, 도메인 연결, PostgreSQL 연결, CDN, 플라스크의 다양한 3rd-party library 등에 대해 포스팅해볼 생각입니다.

댓글