업데이트:

개요

이 프로젝트에서 우리는 ORM과 로그인 기능을 사용하기 위해 두 가지의 라이브러리를 이용할 것이다. 먼저 ORM이 무엇인지 간단하게 짚고 넘어가보겠다.

ORM(Object Relational Mapping)

객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 것을 말한다.

그렇다면 왜 ORM을 사용하는 것일까?

객체지향 프로그래밍은 클래스를 사용하고 관계형 데이터베이스는 테이블을 사용하기 때문에 객체 모델과 관계형 모델 간에 불일치가 존재한다.

이때, ORM을 통해 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결하는 것이다.

다시 말해 SQL 질의어의 사용 없이, 객체를 통해 간접적으로 데이터베이스 데이터를 다룬다는 것이다.

Flask-SQLAlchemy

파이썬에서 ORM으로 많이 쓰이는 것들 중 여러 일반적인 관계형 데이터베이스(mysql, sqlite, oracle, …)를 지원하는 SQLAlchemy Tool-kit이 있다.

이 프로젝트에서는 SQLAlchemy를 사용할 것이기에, Flask에서 플러그인처럼 사용하기 쉽게 만들어 놓은 Flask-SQLALchemy를 설치해줄것이다.

아래의 명령어를 입력해주기 전에,

pip install Flask-SQLAlchemy
위와 같이 (venv) 가상환경이 활성화되어있는지 확인해주도록 하자.

Flask-Login

Flask-Login은 Flask에서 로그인 기능을 도와주는 라이브러리로 사용자 정보를 세션에 저장한다.

설치를 위해 아래의 명령어를 입력해주자.

이번에도 역시 가상환경이 활성화되어있는지 확인해주어야 한다.

pip install Flask-Login

__init__.py

먼저 blog 폴더에 __init__.py 파일을 생성해주자.

__init__.py 는 무슨 파일일까? Python 문서 를 확인해보면…

`__init__.py` 파일은 파일이 포함된 디렉터리를 패키지로 다루는 데 필요하다.
이렇게 할 경우 string과 같이 일반적인 이름을 가진 디렉토리(유효한 모듈)가
나중에 모듈 검색 경로에서 의도하지 않게 숨겨지는 것을 방지할 수 있다.
가장 간단한 상황에서는 `__init__.py` 가 비어있을 수 있지만,
패키지에 대한 초기화 코드를 실행하거나 `__all__` 변수를 설정할 수도 있다.

정리해보자면, __init__.py는 하위 디렉토리의 파일들을 전체적으로 초기화하고 실행하는 파일이다.

그럼 이제 파일을 작성해보자.

__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from os import path
from flask_login import LoginManager

# app을 만들어주는 함수 지정
def create_app():
    app = Flask(__name__)  # Flask app 만들기
    app.config['SECRET_KEY'] = 'IFP'

    @app.route("/")
    def home():
        return "This Is Home"

    @app.route("/about_me/")
    def about_me():
        return "Introduce About Me..."

    return app  # app 반환
app.py
1
2
3
4
5
6
from blog import create_app
# __init__.py 에 작성한 create_app 함수를 app.py에 불러와

if __name__ == "__main__":
    app = create_app() # app.py에서 사용
    app.run(debug=True)

app.py에 if문에 따라 __init__.py 파일을 실행시킬때의 이름은 __main__이 된다.

그리고 python ./app.py 로 앱을 실행 실행해본다면…

폴더 이름인 blog로 실행이 되는 것을 확인할 수 있다.

Blueprint

보통 웹사이트 접속시 /blog/about 이나 /blog/category 와 같은 경로를 갖는다.

@app.route("/blog"), @views.route("/blog/about") 등…

모든 함수를 전부 create_app() 에 적어준다면 길이도 길어지고 엄청 복잡해질 것이다.

플라스크에선 이를 해결하기 위해 Blueprint 라는 것을 활용한다.

파일 나누기

__init__.py 와 같은 레벨에 views.py, auth.py 파일을 생성해준다.

각 파일들은 아래와 같은 기능을 처리한다.

  • views.py : 홈페이지, 자기소개 페이지, 포스트 CRUD 페이지 관련 처리
  • auth.py : 로그인, 로그아웃 등의 로그인 관련 기능 처리

이제 파일을 작성해보자.

views.py
1
2
3
4
5
6
7
8
9
10
11
from flask import Blueprint

views = Blueprint("views", __name__) # Blueprint 만들기

@app.route("/")
def home():
    return "This Is Home"

@app.route("/about_me/")
def about_me():
    return "Introduce About Me..."

작성 후 __init__.py 에 Blueprint 를 등록해주면 된다.

__init__.py
1
2
3
4
5
6
7
8
9
def create_app():
    app = Flask(__name__)
    app.config['SECRET_KEY'] = "IFP"

    from .views import views
    # blueprint 등록, '/blog' 를 기본으로 한다.
    app.register_blueprint(views, url_prefix="/blog")

    return app

이제 /blog//blog/about_me 의 주소로 접속이 되는 것을 확인할 수 있다.

그래서…

Blueprint는 url의 basename을 정해줄 수 있다.

app.register_blueprint(views, url_prefix='/blog')

이와 같이 Flask는 Blueprint를 이용하여 url을 관리한다.

render_template()

우리는 이미지도 있고, 여러가지 크기의 텍스트도 있으며 버튼을 누르면 다른 링크로 넘어가는 기능이 있는 블로그를 원한다.

하지만 여태 사용해왔던 html 태그로 된 문자열을 반환하는 함수로는 무리가 있을 것이다.

아예 html 파일을 반환하면 되지 않을까?

이때 render_template()을 사용한다.

render_template은 기본적으로 templates라는 폴더를 찾아 html 파일들을 불러온다.

사용하기에 앞서 view.pyauth.pyrender_templateimport 해준다.

from flask import Blueprint, render_template

그리고 blog 패키지 아래에 templates란 디렉토리를 생성해주고, 그 아래에 home.html 파일을 작성해준다.

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Test home</title>
</head>
<body>
    <h1>테스트입니다.</h1>
    <P>잘 보이네요...</P>
</body>
</html>

그리고 view에 home을 아래와 같이 수정해준다면…

1
2
3
@views.route("/")
def home():
    return render_template("index.html")
/blog/ 로 접속시 작성한 html 파일 그대로 나오는 것을 확인할 수 있다.

값 전달

render_template 함수가 구현되어 있는 곳을 가보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def render_template(
    template_name_or_list: t.Union[str, Template, t.List[t.Union[str, Template]]],
    **context: t.Any
) -> str:
    """Renders a template from the template folder with the given
    context.

    :param template_name_or_list: the name of the template to be
                                  rendered, or an iterable with template names
                                  the first one existing will be rendered
    :param context: the variables that should be available in the
                    context of the template.
    """
    ctx = _app_ctx_stack.top
    ctx.app.update_template_context(context)
    return _render(
        ctx.app.jinja_env.get_or_select_template(template_name_or_list),
        context,
        ctx.app,
    )

위와 같이 나와있다.

2번째 행을 보면 첫번째 인자로 template_name_or_list를 받고, 다음 인자로 context를 인자로 받는다고 나와있다.

그럼 context로 값을 전달해보자.

home()을 아래와 같이 수정해준다.

1
2
3
@views.route("/")
def home():
    return render_template("home.html", name="홍길동", message="아버지를 아버지라 부르지 못하고...")

html 파일은 아래와 같이 수정해준다.

1
2
3
4
5
6
7
8
<body>
    <h1>테스트입니다.</h1>
    <P>잘 보이네요...</P>
    
    <p>이름 : {{ name }}</p>
    <p>메시지 : {{ message }}</p>
    
</body>
역시나 잘 나오는 것을 확인할 수 있다.

{{ … }}?

위 html 파일에서 중괄호 2개를 볼 수 있다.

이는 무엇일까? 아래 jinja 템플릿 엔진에서 알아보도록 하자.

jinja 템플릿 엔진

jinja는 파이썬용 웹 템플릿 엔진이다.

웹 템플릿 엔진? 템플릿 양식과 입력 자료를 결합하여 웹문서를 출력하는 소프트웨어를 말한다.

jinja 는 중괄호 2개를 변수로 인식하고, 전달된 context에서 적절한 값을 찾아 렌더링하게 된다.

추가로 아래와 같은 문법들이 있다.

  • {% … %} : 프로그래밍 영역

  • {{ … }} : 변수 영역

  • {# … #} : 주석 영역

마치며

다음 포스트에서는 정적 파일을 이용해 블로그 디자인을 해보겠다.

Notice: 사용한 코드는 이 곳에서 확인가능합니다.

댓글남기기