본문 바로가기

Flask-Study

Ep03 : 블로그 웹 애플리케이션 개발(0) - 프로젝트 생성, 패키지 설치, 기본작업

ORM(Object Relational Mapping)

ORM이란 Object Relational Mappin의 약자로 객체 관계 매핑이다. 객체 관계 매핑은 객체와 테이블을 자동으로 매핑하는 것을 의미한다. ORM을 사용하면 SQL문을 사용할 필요 없이 객체를 통해 간접적으로 데이터베이스를 조작할 수 있게 되어 객체와 테이블 간의 불일치를 해결할 수 있다. 대표적인 ORM은 SQLAlchemy가 있다.

 

SQLAlchemy

ORM의 한 종류로 파이썬 코드에서 Flask를 데이터베이스와 연결하기 위해서 사용된다. 데이터베이스 테이블을 프로그래밍 언어의 클래스로 표현하게 해주고, 테이블의 CRUD(Create, Read, Update, Delete) 등을 돕는다.

 

Flask-Login

Flask 프레임워크로 개발한 웹 어플리케이션의 로그인 기능을 쉽게 구현할 수 있도록 도와주는 라이브러리이다. 사용자 정보 세션 관리를 쉽게 도와주는 용도로 사용한다.


본격적으로 시작하기 전에 가상환경이 활성화 되어 있는 상태에서 터미널에 아래 명령어들을 입력하여 설치해야 한다.

pip install Flak-SQLAlchemy          pip install Flask-Login

 

설치 한 후에 blog 폴더를 만들어 안에 __init.py__ 파일을 생성한 후 아래 코드를 작성한다.

그 다음에 app.py 파일에 아래 코드를 입력해 준다.

from blog import create_app 코드로 인해서 app.py를 실행하게 되면

__init.py__에 있는 create_app() 함수가 실행되게 된다.

즉, app.py 함수에서 __init.py__ 에 있는 함수를 불러와서 사용한 것이다. 그래서 실행을 하게 되면 아래와 같은 결과가 나오게 된다.

python ./app.py로 실행한다.

 

 

현재 위 코드에는 라우팅 함수가 몇개 없지만 웹 페이지가 커질수록 함수 개수가 늘어날 것이고 그렇게 되면 복잡해지게 된다. 그래서 이러한 문제를 해결하기 위해서 BluePrint를 사용한다.


BluePrint

블루프린트는 큰 어플리케이션을 단순화 시키는 역할을 하고, Flask extension(확장 프로그램, 라이브러리 등) 등록을 위한 중심 수단으로도 쓰인다.

 

블루프린트를 사용하기 위해서 __init__.py 에 있는 라우팅 함수들을 지운 뒤에 views.py 와 auth.py 파일을 만들어 준다.

auth.py 에서는 로그인 관련 기능들을, views.py 에서는 홈페이지 전반적인 것들을 다룰것이다.

 

views.py와 auth.py에 아래 코드를 입력해 준다.

만들어진 블루 프린트를 아래 코드를 입력하여 __init__.py에 등록한다.

url_prefix

url_prefix는 특정 URL 앞에 기본적으로 붙일 접두어 URL이다. 왼쪽 코드를 보면 url_prefix="/blog" 사용한 것을 볼 수 있다. 그래서 주소에 '/' 가 아닌 '/blog/' 를 써야 views.py에 있는 blog_home 함수가 실행된다.

 

 

 

 

 


render_template()

render_template()이란 flask에서 제공하는 함수로 templates에 저장된 html을 불러올 때 사용하는 함수이다.

 

우선 render_template()을 사용하기 위해서 views.py 와 auth.py에 render_template을 임포트 해준다.

그리고 blog 아래에 templates 디렉토리를 만든 뒤 home.html 파일을 만들어 아래 코드를 작성해 준다.

여기서 templates 디렉토리를 만드는 이유는 플라스크는 기본적으로 templates 폴더에서 템플릿을 찾기 때문이다.

 그리고 views.py를 아래와 같이 수정해 준다.

render_template() 함수를 이용하여 home.html을 불러오게 되는 것이다. 그래서 실행을 하면 아래와 같이 home.html에 작성한 내용이 나오게 된다.

render_tamplate() 함수에서 두번째 인자부터는 첫번째 인자로 지정한 html 파일 내에서 사용할 변수를 지정할 수 있다.

views.py 에서 위 코드처럼 문자열 변수를 선언하여 초기화 할 수 있도록 수정해준다.

home.html도 아래와 같이 수정해준다.

위와 같이 views.py 에 있는 변수가 성공적으로 적용된 것을 볼 수 있다.

이처럼 변수를 사용할 수 있는 이유는 Jinja2 라는 템플릿 엔진을 사용했기 때문이다.


Jinja2

Jinja2는 템플릿 엔진중 하나 이며 파이썬에서 가장 많이 사용된다. 문법으로는 {{ 변수명 }} , {% 소스코드 %} 같은 것들이 있다. 변수같은 경우는 양 옆에 중괄호 두개를 사용하여 불러올 수 있다. 소스코드 같은 경우 위 형식대로 사용하면 되지만 for문과 if문 같은 경우 끝났다는 것을 알려주기 위해 마지막에 { % endfor %}, {% endif %}를 사용해 주어야 한다.


정적파일 다루기

우선https://startbootstrap.com/theme/clean-blog에서 템플릿을 다운받아 4개의 html 파일들을 tamplates 폴더에 넣어준다.

그리고 나서 views.py 에 있는 주소를 index.html로 수정한 뒤 실행시켜 준다.

 

 

 

 

실행을 해 보면 css가 적용이 안된 상태의 결과가 나오게 된다.

 

여기서 템플릿들을 보면 공통적으로 상단 부분과 하단 부분이 같다는 것이 보인다. 그래서 base.html을 만들어 공통적인 코드를 넣고 다른 html 파일에서 base.html에 있는 공통된 코드를 받아와서 사용할 것이다. 여기서 사용하는 것이 바로

템플릿 상속이다.


템플릿 상속

웹 사이트 레이아웃의 일관성을 유지하거나, header와 footer를 여러곳에 사용할 때 템플릿 상속 기능을 사용하면 된다.
부모 문서를 만들고, 자식 문서가 들어갈 부분에 {% block 블록이름 %} {% endblock %}라고 작성한다.
자식 문서 윗부분에 {% extends 부모문서 이름 %} 를 쓰고 {% block 블록이름 %} {% endblock %} 사이에 내용을 작성한다.
 
공통된 부분이 부모문서  이고 자식문서에서 다르게 쓰일 부분은 {% block %} 태그를 이용하여 구분한 뒤 자식 문서에서 그 부분에 공통된 부분이 아닌 다른 내용을 써서 사용하는 것이다.
 
templates 폴더 아래에 base.html을 만들고 아래 코드를 넣어 준다.
 
 
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>{% block title %}{% endblock %}</title>
        <link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
        <!-- Font Awesome icons (free version)-->
        <script src="https://use.fontawesome.com/releases/v6.1.0/js/all.js" crossorigin="anonymous"></script>
        <!-- Google fonts-->
        <link href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css" />
        <link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css" />
        <!-- Core theme CSS (includes Bootstrap)-->
        <link href="css/styles.css" rel="stylesheet" />
    </head>
    <body>
        <!-- Navigation-->
        <nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
            <div class="container px-4 px-lg-5">
                <a class="navbar-brand" href="index.html">Blog</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
                    Menu
                    <i class="fas fa-bars"></i>
                </button>
                <div class="collapse navbar-collapse" id="navbarResponsive">
                    <ul class="navbar-nav ms-auto py-4 py-lg-0">
                        <li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{{url_for("views.blog_home")}}">Home</a></li>
                        <li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{{url_for("views.about")}}">About</a></li>
                        <li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{{url_for("views.category")}}">Category</a></li>
                        <li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{{url_for("views.contact")}}">Contact</a></li>
                        <li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{{url_for("views.signup")}}">Sign Up</a></li>
                        <li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{{url_for("views.login")}}">Login</a></li>
                    </ul>
                </div>
            </div>
        </nav>

        {%block header %}{% endblock %}

        <div class = "content-wrapper">
            {% block content %}{% endblock %}
        </div>
        <!-- Footer-->
        <footer class="border-top">
            <div class="container px-4 px-lg-5">
                <div class="row gx-4 gx-lg-5 justify-content-center">
                    <div class="col-md-10 col-lg-8 col-xl-7">
                        <ul class="list-inline text-center">
                            <li class="list-inline-item">
                                <a href="#!">
                                    <span class="fa-stack fa-lg">
                                        <i class="fas fa-circle fa-stack-2x"></i>
                                        <i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
                                    </span>
                                </a>
                            </li>
                            <li class="list-inline-item">
                                <a href="#!">
                                    <span class="fa-stack fa-lg">
                                        <i class="fas fa-circle fa-stack-2x"></i>
                                        <i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
                                    </span>
                                </a>
                            </li>
                            <li class="list-inline-item">
                                <a href="#!">
                                    <span class="fa-stack fa-lg">
                                        <i class="fas fa-circle fa-stack-2x"></i>
                                        <i class="fab fa-github fa-stack-1x fa-inverse"></i>
                                    </span>
                                </a>
                            </li>
                        </ul>
                        <div class="small text-center text-muted fst-italic">Copyright &copy; Your Website 2022</div>
                    </div>
                </div>
            </div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
        <script src="{{ url_for('static', filename='js/scripts.js')}}"></script>
        <link href="{{ url_for('static', filename='css/styles.css') }}" rel="stylesheet"/>
    </body>
</html>
 

그리고 index.html을 아래와 같이 수정해 준다.
{% extends 'base.html' %}

{% block title %}This is home.{% endblock %}

{% block header %}
        <!-- Page Header-->
        <header class="masthead" style="background-image: url('{{url_for('static', filename = 'assets/img/bg-masthead.jpg')}}')">
            <div class="container position-relative px-4 px-lg-5">
                <div class="row gx-4 gx-lg-5 justify-content-center">
                    <div class="col-md-10 col-lg-8 col-xl-7">
                        <div class="site-heading">
                            <h1>Clean Blog</h1>
                            <span class="subheading">A Blog Theme by Start Bootstrap</span>
                        </div>
                    </div>
                </div>
            </div>
        </header>
    {% endblock %}

    {% block content %}
        <!-- Main Content-->
        <div class="container px-4 px-lg-5">
            <div class="row gx-4 gx-lg-5 justify-content-center">
                <div class="col-md-10 col-lg-8 col-xl-7">
                    <!-- Post preview-->
                    <div class="post-preview">
                        <a href="post.html">
                            <h2 class="post-title">Man must explore, and this is exploration at its greatest</h2>
                            <h3 class="post-subtitle">Problems look mighty small from 150 miles up</h3>
                        </a>
                        <p class="post-meta">
                            Posted by
                            <a href="#!">Start Bootstrap</a>
                            on September 24, 2022
                        </p>
                    </div>
                    <!-- Divider-->
                    <hr class="my-4" />
                    <!-- Post preview-->
                    <div class="post-preview">
                        <a href="post.html"><h2 class="post-title">I believe every human has a finite number of heartbeats. I don't intend to waste any of mine.</h2></a>
                        <p class="post-meta">
                            Posted by
                            <a href="#!">Start Bootstrap</a>
                            on September 18, 2022
                        </p>
                    </div>
                    <!-- Divider-->
                    <hr class="my-4" />
                    <!-- Post preview-->
                    <div class="post-preview">
                        <a href="post.html">
                            <h2 class="post-title">Science has not yet mastered prophecy</h2>
                            <h3 class="post-subtitle">We predict too much for the next year and yet far too little for the next ten.</h3>
                        </a>
                        <p class="post-meta">
                            Posted by
                            <a href="#!">Start Bootstrap</a>
                            on August 24, 2022
                        </p>
                    </div>
                    <!-- Divider-->
                    <hr class="my-4" />
                    <!-- Post preview-->
                    <div class="post-preview">
                        <a href="post.html">
                            <h2 class="post-title">Failure is not an option</h2>
                            <h3 class="post-subtitle">Many say exploration is part of our destiny, but it’s actually our duty to future generations.</h3>
                        </a>
                        <p class="post-meta">
                            Posted by
                            <a href="#!">Start Bootstrap</a>
                            on July 8, 2022
                        </p>
                    </div>
                    <!-- Divider-->
                    <hr class="my-4" />
                    <!-- Pager-->
                    <div class="d-flex justify-content-end mb-4"><a class="btn btn-primary text-uppercase" href="#!">Older Posts →</a></div>
                </div>
            </div>
        </div>
    {% endblock %}
 

base.html을 보면 {% block header %}, {% endblock %} 처럼 자식 문서에서 들어갈 내용을 {% block %} 태그를 이용하여 구분해 주었다. 그래서 index.html을 보면 {% block header %}를 쓰고 내용 부분에 관한 코드를 쓰고 {% endblock %}으로 끝맺은 것을 볼 수 있다. 그리고 가장 맨 위 부분에 {% extends 'base.html' %} 을 써서 base.html을 상속받았음을 알 수 있다.

 

그 다음으로 static 폴더를 만들어서 assets,css,js 파일들을 넣어준다.

<script src="{{ url_for('static', filename='js/scripts.js')}}"></script>
<link href="{{ url_for('static', filename='css/styles.css') }}" rel="stylesheet"/>

 

그리고 위 코드를 사용하여 static 폴더 안에 있는 정적 파일들을 불러온다. 실행하면 다음과 같은 결과가  나온다.

뒤에 배경 이미지는 아래처럼 url_for() 함수를 이용하여 static 폴더 안에 있는 asset 파일의 이미지를 불러오면 된다.

<header class="masthead" style="background-image: url('{{url_for('static', filename = 'assets/img/bg-masthead.jpg')}}')">

 

 

 

앞에서 했던 것들을 이용하여 왼쪽과 같이 템플릿들을 추가적으로 만들어서 위에 라우팅 코드들을 만들었다. 전체적으로 실행하면 아래와 같은 화면들이 나오게 된다.

 

 

 

 

 

 

 

indext.html / about.html

category.html / contact.html

signup.html / login.html