본문 바로가기
WebFramework/Python Flask

[Python Flask] 2. HTML 페이지와 데이터 주고 받기

by Rosmary 2024. 2. 27.
728x90
반응형

 

 

 

 

 

 

이번 포스팅에서는 Flask 프로젝트 내 Python 파일의 특정 변수값을 HTML에 표시하거나, 반대로 HTML에서 사용자로부터 입력받은 값을 Python에서 받아볼 수 있는 방법에 대해 알아보려한다. 사실 지난 포스팅의 내용으로는 정적인 페이지, 그러니까 내용에 변화가 없는 웹 사이트만 제작이 가능하다. 하지만 최근에는 웹 사이트 접속이나 로그인 시간도 화면에 표시하는 마당인데, 이 값들을 서버 운영자가 매 초마다 HTML 파일의 시간 부분을 변경하면서 있을리도 만무하니, 지난 포스팅만으로 뭔가 의미있는 사이트를 만들기는 매우 어렵다. 

 

물론 이번 포스팅도 모두가 원하는 완벽한 웹 페이지를 만들만한 내용을 담고 있지는 않지만, 적어도 웹의 내용을 변경하기 위한 데이터의 이동이 python과 html 사이에서 어떻게 진행되는지 확인할 수 있도록 잘 작성해보려한다.

 

 

1. HTML로 하나의 Python 변수 전달.

 

python에는 datetime이라는 내장 모듈 안에 datetime이라는 클래스 - 클래스임에도 이름이 소문자로 시작한다 - 가 존재하고, 이 내부에는 현재의 시간을 datetime 객체로 돌려주는 now()라는 함수가 있다.  

 

 

 

이제, 필자는 필자가 만든 Flask 서버의 메인 페이지에 접속할 때, 접속한 시간이 표시되도록 만들어보려한다. 

 

HTML은 웹 브라우저에 표시되는 일종의 문서다 - 프로그래밍 언어가 아니다 -. 따라서 HTML 문서로는 현재의 시간을 확인하여 출력할 수 있는 방법이 전무하다. 그렇기 때문에 시간을 HTML 파일에 출력하기 위해서는

 

"Python 파일로부터 현재 시간 호출 및 변수 저장" -> "HTML 파일에서 Python의 현재 시간 변수 호출" -> "HTML 적용"

 

이라는 과정을 거쳐야한다. 그럼, 가장 문제가 되는 부분은 HTML 파일에서 어떻게 Python의 변수를 가져오느냐가 된다.

 

Java를 기반으로하는 웹 프레임워크들은 이를 Expression, 표현자라는 것으로 호출하는데, Flask나 Django 역시 Jinja 모듈 - Flask 모듈 설치 시, 의존성으로 인해 자동으로 설치된다 - 에서 제공하는 Expression으로 template variables(템플릿 변수)라는 것으로 HTML에 변수를 전달할 수 있다. Java 기반 웹 프레임워크의 표현자가 꺽쇠 괄호로 표시되는 것과 달리, Python 기반의 웹 프레임워크들은 중괄호 두 개를 겹쳐 {{ }} 변수를 호출한다. 

 

한 번 진행해보자. 필자는 아래와 같이 app.py라는 파일을 만들고, templates에는 index.html 파일을 만들어 Python에서 생성한 현재 시간 데이터를 웹 화면에 표시하려한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# app.py
from
 flask import Flask, render_template
from datetime import datetime
 
app = Flask(__name__)
 
@app.route("/")
def index():
 
    current_time: str = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")    
   return render_template("index.html")

if __name__ == "__main__":
    app.run(debug=True)
cs

 

필자는 현재 시간을 current_time이라는 객체명에 str으로 Type Casting을 진행하여 저장했다. 이제 HTML에서는 저 변수를 템플릿 변수 형태로 출력하기 위해 아래와 같이 index.html 파일을 작성했다.

1
2
3
4
5
6
7
8
9
10
11
# templates/index.html
<
!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Python <-> HTML 변수 다루기</title>
</head>
<body>
    <h1>현재 시간</h1>
    <p>{{ current_time }}</p>
</body>
</html>
cs

 

 

이 상태로 Flask 서버를 실행해보자. 그러면 필자가 원하는대로 시간이 나타나지 않는다(??). 

 

 

개발자 도구로 HTML Element의 내용을 보면 알 수 있듯이, 필자가 의도한대로 시간 변수 값이 적용되지 않았다. 사실 당연한건데, app.py에서 "localhost:5000/" 접속 시, 시간 변수를 저장하고 index.html을 돌려주라고만 명시했지, 시간 변수인 current_time까지 반환하라고 코드를 작성하지 않았기 때문이다. 

 

return 구문에 사용한 render_template는 templates 폴더 내의 html 파일 뿐만 아니라 다른 데이터도 전달할 수 있도록 **context라는 함수 인자가 설정되어 있다.

 

 

이 인자의 이름을 아무것이나 설정해서 전달하고자 하는 값을 지정하면, 이는 Python의 특정 변수를 HTML로 넘길 때 사용한다. 필자는 원하는대로 시간 데이터를 HTML로 넘기기 위해서 data라는 key에 변수를 넣어 전달해보려한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask, render_template
from datetime import datetime
 
app = Flask(__name__)
 
@app.route("/")
def index():
 
    current_time: str = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")    
    
    return render_template("index.html", data=current_time)
 
if __name__ == "__main__":
    app.run(debug=True)
cs

 

이와 더불어서 html 파일도 수정을 진행해주어야 하는데, html 파일이 받는 변수명은 Python에서 설정한 current_time이 아니라 key 값인 data다. 따라서, index.html도 Jinja Expression을 아래와 같이 수정해주어야 한다.

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Python <-> HTML 변수 다루기</title>
</head>
<body>
    <h1>현재 시간</h1>
    <!-- {{ current_time}}에서 {{ data }}로 수정-->
<p>{{ data }}</p>
</body>
</html>
cs

 

이 상태에서 다시 app.py를 실행하고 웹 페이지로 접속해보자.

 

 

 

 

 

2. Python에서 2개 이상의 변수를 HTML로 전달하기

 

이번에는 사용자가 웹 페이지로 접속할 때, 사용자의 이름인 "Alex"와 접속 시간을 함께 표시해보려한다. 앞서 render_template에서 약간의 수정을 가하여, 현재 시간은 current_time이라는 key로, 사용자 이름은 username으로 전달하려한다.

 

그런데... 만약 HTML로 전달해야하는 변수가 무진장 많다고 가정해보자. 단순히 개인 정보만 생각해보더라도, 이름, 성별, 전화번호, 주소 등등등 매우 많은데, 이를 모두 하나의 Key로 맵핑시켜 HTML로 전달하기에는 비효율적이다. 따라서 일반적으로 둘 이상의 변수는 python dictionary 자료형에 데이터를 저장하여 HTML로 전달한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask import Flask, render_template
from datetime import datetime
 
app = Flask(__name__)
 
@app.route("/")
def index():
 
    current_time: str = datetime.strftime (datetime.now(), "%Y년 %m월 %d일 %H시 %M분 %S초")
    conn_info: dict = {
        "conn_time": current_time,
        "conn_username""alex"
    }
    
    return render_template("index.html", conn_info=conn_info)
 
if __name__ == "__main__":
    app.run(debug=True)
cs

 

HTML 역시 변경된 key 이름에 맞게 수정을 진행한다. 그런데, HTML에서 템플릿 변수로 받는 conn_info의 경우 Python Dictionary다. 따라서 Dictionary 내 세부 값도 추출이 가능해야 하는데, 템플릿 변수 내에서는 Python과 달리 key에 지정된 값 호출 시, 온점(.)을 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Python <-> HTML 변수 다루기</title>
</head>
<body>
    <h1>접속 정보</h1>
    <p>현재 시간  : {{ conn_info.conn_time }}</p>
    <p>접속 사용자: {{ conn_info.conn_username }}</p>
</body>
</html>
cs

 

위의 코드 역시 서버를 재기동하고 접속해보자. 그럼, render_template에 여러 인자를 넣었던 상황과 동일하게 페이지가 출력됨을 확인할 수 있다. 

 

 

 

3. HTML 입력값(GET/POST) 처리하기

 

이번에는 반대로 HTML에서 사용자가 입력한 값을 Python에서 처리해보려한다. 가장 자주 접할 수 있는 예시는 계정 정보 입력인데, 필자는 index.html에 ID와 패스워드를 입력하고 '로그인' 버튼을 클릭하면 app.py에서 해당 계정 정보에 대한 일치 여부를 확인하고 결과를 알려주는 코드를 작성하려 한다. 

 

그 전에, HTML에서 서버로 데이터를 전송하는 HTTP 통신 중 가장 대표적인 두 가지 방식을 잠깐 설명하려한다. 웹 페이지에서 서버로 데이터를 전송하는 것을 HTTP 통신이라고 하며, GET, POST, PUT, DELETE, PATCH, OPTION, HEAD의 7가지가 존재한다. 이 중에서 자주 사용하는 것이 GET과 POST다.

 

HTML입력값을 전송할 때 'form'이라는 데이터를 사용한다. 이 form tag는 데이터의 전송방식을 선택할 수 있다. "단순히 데이터를 전송하는데 전송방식을 왜 나누어놓았을까"라는 의문이 있을 텐데, 두 전송 방식은 아래의 차이점을 가진다.

 

  GET POST
BACK button/Reload Harmless Data will be re-submitted (the browser should alert the user that the data are about to be re-submitted)
Bookmarked Can be bookmarked Cannot be bookmarked
Cached Can be cached Not cached
Encoding type application/x-www-form-urlencoded application/x-www-form-urlencoded or multipart/form-data. Use multipart encoding for binary data
History Parameters remain in browser history Parameters are not saved in browser history
Restrictions on data length Yes, when sending data, the GET method adds the data to the URL; and the length of a URL is limited (maximum URL length is 2048 characters) No restrictions
Restrictions on data type Only ASCII characters allowed No restrictions. Binary data is also allowed
Security GET is less secure compared to POST because data sent is part of the URL

Never use GET when sending passwords or other sensitive information!
POST is a little safer than GET because the parameters are not stored in browser history or in web server logs
Visibility Data is visible to everyone in the URL Data is not displayed in the URL

 

위의 내용을 조금 요약하자면, GET은 HTML에서 입력한 정보가 외부로 노출이 쉽게 이루어지고, Post는 반대로 보이지 않는다. 따라서 필자가 진행하려는 계정정보의 전송은 일반적으로 POST 통신을 많이 사용한다. 

 

직접 HTML 파일을 작성하여 두 통신의 차이점 및 app.py에서의 데이터 수신을 확인해보자. 필자는 아래와 같이 app.py와 index.html을 다시 작성하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# app.py
from
 flask import Flask, render_template, request
 
app = Flask(__name__)
 
@app.route("/")
def index():
    result: str = "실패"
    data: dict = {"result": result, "username": request.args.get("id")}
    # request: HTML 입력값을 Parsing.
    # 전달되는 입력값이 GET인 경우와, POST인 경우 사용 방식이 다름
    # GET 통신 시 Data 추출은 requsts.args.get(<입력값 name>) 사용
    password = request.args.get("pw")
 
    if data["username"]  == "alex" and password == "qwe123":
        data["result"= "성공"
 
    return render_template("index.html", data=data)
 
if __name__ == "__main__":
    app.run(debug=True)
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# index.html
<
!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Python <-> HTML 변수 다루기</title>
</head>
<body>
    <h3>LOG IN</h3>
    <form action="/" methods="GET">
        <span> Username : </span><input type="text" name="id"><br>
        <span> Password : </span><input type="password" name="pw"><br>
        <button type="submit">로그인</button>
    </form>
    <hr>
    <div>
        <h3>로그인 결과</h3>
        <p>username '{{ data.username }}' 로그인 {{ data.result }} </p>
    </div>
</body>
</html>
cs

 

다시 app.py를 실행하면 아래와 같은 화면이 나타나며, 계정 정보를 alex, qwe123으로 정확히 입력하는 경우에만 로그인 성공이라는 문구가 출력된다.

 

 

그런데 자세히 보면, GET 통신의 경우 HTML로부터 전달되는 데이터가 URL에 고스란히 노출된다는 문제점이 있다. 그렇기 때문에 민감한 정보가 전달되어야 하는 상황에서는 보통 POST를 사용하여 내용을 감춘다. 

 

그럼 이번에는 HTML 파일에서 입력값을 전달하는 방식을 POST로 변경해서 다시 진행해보자. 그런데, POST로 변경하고 app.py 재기동 후 사이트를 접속해보면 에러가 발생하는 것을 확인할 수 있다.

 

 

 

웹 화면에서도, 개발자 도구의 콘솔에서도 확인할 수 있지만 현재 접속을 진행한 URL인 'localhost:5000/'에 대해 POST 통신이 허용되지 않았다는 것이 가장 큰 이유다. 이는 Flask 서버에서 해당 URL 접속 시 허용하는 HTTP 통신에 POST가 제외되어 있어서 나타나는 현상이다. 

 

"아까 GET은 아무 설정 없이 잘 동작하지 않았나요?"

 

맞다. 각 URL의 통신을 관장하는 부분은 데코레이터인 @app.route(), 즉 Flask 메서드인 route()인데, 이 데코레이터는 기본적으로 GET 통신은 모두 허용하고 있다. Flask.route()는 parameter로 methods라는 값을 가지며, 기본값이 ["GET"]으로 설정되어 있다. 따라서 여기에 "POST"도 추가가 되어야 HTML에서 데이터를 노출되지 않게 Flask 서버에 전달하는 것이 가능해진다.

 

필자가 위에서 "POST"를 "추가"한다고 언급했는데, 가장 처음 사이트를 접속할 때 사이트의 정보를 불러들이는 과정은 "GET" 통신으로 진행되기 때문이다. 물론 그 외에도 조금 더 복잡한 사이트로 들어가면 GET 통신은 뗄레야 뗄 수 없는 사이라 지금 당장은 GET과 POST를 동시에 사용한다고 알고 있으면 될 듯 하다.

 

 

POST로 app.py로 전달되는 데이터는 GET과 달리, request.args.get()이 처리하지 않고, request.form.get()이 처리한다. HTML에서 전송되는 데이터가 POST 형태이므로, 이에 맞춰 app.py도 request.form.get()으로 변경해준다.

 

이 상태에서 다시 app.py를 실행해보자. 그럼, 첫 접속 시 통신은 GET으로 진행되며, 계정 입력 후 '로그인' 버튼을 클릭하면 데이터 전송이 POST로 진행됨을 확인할 수 있다.

 

 

 

 


 

다음 포스팅에서는... 오늘 만든 사이트의 아랫부분인 "로그인 결과"를 첫 접속 시 나타나지 않게 만들어보려한다. 로그인으로 값이 전달되어야만 결과 부분이 화면에 출력되도록 만들려하는데 이를 위해서는 app.py의 값을 받아 HTML에서 Jinja 문법으로 조건문을 사용해야한다. 조건문에 대해 포스팅하면서 내친김에 반복문까지 다루어볼까 한다.

 

 

END.

반응형

댓글