일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- PYTHON
- flask연동
- openapi
- linux
- 영어 단어 모음 분석하기
- K-Digital training
- State Hook
- sql
- join
- 엘리스AI트랙
- 트럼프 대통령 트윗 분석하기
- merge request
- 백준 알고리즘
- GitLab
- 자료구조
- 리눅스
- 리액트
- update
- pandas
- KDT
- delete
- 엘리스 AI 트랙
- Effect Hook
- insert
- mongodb
- 엘리스
- 알고리즘
- Git
- git remove
- 파이썬
- Today
- Total
GO WILD, SPEAK LOUD, THINK HARD
엘리스 AI 트랙 05주차 - Flask 웹 프로그래밍 (1/19) 🔥 본문
✔ 05주차. 웹 백엔드
<수강목표>
-
파이썬 웹 프레임워크를 활용해 웹 백엔드를 구현합니다.
-
웹 서버와 데이터베이스를 연동해 데이터를 저장하고 관리하는 방법을 배웁니다.
-
REST API 서버를 구현하여 클라이언트와 서버간의 API 서비스를 구현합니다.
[01 Flask의 세계로]
1. 플라스크는 간결하다.
- Micro Web Framework : 프레임워크를 간결하게 유지하고 확장할 수 있도록 만들었다는 뜻
→ Micro : 가벼운 기능 제공, 가볍게 배우고 가볍게 사용할 수 있으며 확장성이 넓다.
→ Framework : 이미 작성된 코드의 모임인 라이브러리 그 이상의 의미, 어플리케이션을 개발하기 위한 일정한 틀을 제공해주는 기술
- Flask : 간단한 웹 사이트, 간단한 API 서버를 만드는 데에 특화 되어있는 Python Web Framework.
- 플라스크의 장점 : 가볍게 배우고, 사용하고 배표할 수 있다
- 플라스크의 단점 : Django에 비해서 자유도가 높으나, 제공해 주는 기능이 덜하다. 복잡한 어플리케이션을 만들려고 할 때 해야 할 것들이 많다.
- Flask는 소규모의 어플리케이션을 빠르게 만들 수 있고, 배포 환경에 따라 대규모 어플리케이션의 기능 확장의 역할을 하기 쉬운 장점이 있고, Django는 대규모 어플리케이션을 빠르게 만들 수 있으며, 기본으로 제공 해 주는 기능이 많다는 장점이 있다.
Flask |
Django |
ORM 기능이 제공되지 않음 |
ORM 기능이 내장 |
짧은 코드로 웹 서버 구동 |
자동으로 관리자 화면 구성 |
2. Simple Web Server 띄우기
# flask 패키지에서 FLASK를 임포트
from flask import Flask
# FLASK 객체 app을 선언
app = Flask(__name__)
# route()를 사용해 웹페이지와 해당 페이지에서 작동할 함수를 매칭
@app.route('/')
# '/'에 매칭되는 함수 hello_elice()를 만들고 route() 밑에 작성
def hello_elice():
return "Hello Elice!"
# 모듈명이 main일 때만 실행하도록 조건문을 추가
if __name__ == '__main__':
app.run()
3. HTTP란
- WWW(World Wide Web) 의 기본적인 틀 : 웹 브라우저에 있는 정보에 접근할 때는 URL을 통해 접근. 반대로 URL을 모르는 정보에는 접근X
- HTTP : Hypertext Transfer Protocal
- Hypertext: 컴퓨터 화면이나 전자 기기에서 볼 수 있는 데이터이며, 다른 데이터와 연결될 수 있는 주소를 참조
- Transfer: 사람들이 브라우저를 통해 확인하는 웹 상의 데이터는 HTTP에 의해 전달
- Protocal: 규칙 혹은 규약
- HTTP Methods
① GET : 암호화되지 않은 형태의 데이터를 서버로 전송하는데 사용되는 가장 일반적인 방법
② HEAD : GET과 유사한 방법으로 Response Body를 포함하지 않고 사용
③ POST : 특정 양식의 데이터를 암호화하여 서버로 전송하는데 사용
④ PUT : 특정 대상의 데이터를 갱신(Update)하는데 사용
⑤ DELETE : URL에 지정된 대상을 삭제(DELETE)하는 데 사용
- HTTP status code : 특정 HTTP 요청이 성공적으로 완료되었는지 알려주는 코드
① 응답 : 100
② 성공적인 응답 : 200
③ 리다이렉트 : 300
④ 클라이언트 에러 : 400
⑤ 서버 에러 : 500
4. URL Routing
- URL Routing : 서버에서 특정 URL에 접속했을 시 보여줄 페이지를 정해주는 것. 즉, 특정 URL을 일부 작업을 수행하기 위한 관련된 기능과 Mapping하는 데 사용
- Flask에서 URL Routing : @app.route('') 사용 (decorator(데코레이터) : @를 통해 선언하는 방식)
- URL Mapping : 웹사이트 주소를 간략하게 치환하는 과정. 보안성이 향상 (URL 주소를 가림) 과 편의성이 향상 (주소를 간결하게 함)
5. REST란?
- REST : Representational State Transfer. 자원을 이름(자원의 표현)으로 구분하여 해당 자원의 상태를 주고 받는 모든 것. 즉, 자원(resource)의 표현(representation)에 의한 상태 전달. 기본적으로 웹의 기존 기술과 HTTP 프로토콜을 그대로 사용하기 때문에 웹의 장점을 최대한 활용할 수 있는 아키텍처 스타일이며, 네트워크 상에서 클라이언트와 서버 사이의 통신 방식 중 하나
- 자원의 표현
→ 자원: 해당 소프트웨어가 관리하는 모든 것 (ex. 그림, 문서, 데이터 등)
→ 자원의 표현: 그 자원을 표현하기 위한 이름
- 상태(정보) 전달
→ 데이터가 요청되는 시점에서 자원의 상태(정보)를 전달
→JSON 혹은 XML을 통해 데이터를 주고 받는 것이 보통의 전달 방식
- REST의 구체적인 개념 : HTTP URI(Uniform Resource Identifier)을 통해 자원을 명시하고, HTTP Method(POST, GET, PUT, DELETE)를 통해 해당 자원에 대한 CRUD Operation을 적용하는 것
- CRUD : Create 생성(POST), Read 조회(GET), Update 수정(PUT), Delete 삭제(DELETE), HEAD header 정보 조회(HEAD)
- REST의 장단점
☞ 장점
- 서버와 클라이언트의 역할을 명확하게 분리
- 여러가지 서비스 디자인에서 생길 수 있는 문제를 최소화
- REST API 메시지가 의도하는 바를 명확하게 나타내므로 의도하는 바를 쉽게 파악
- HTTP 표준 프로토콜에 따르는 모든 플랫폼에서 사용가능
- HTTP 표준 프로토콜의 표준을 최대한 활용하여 여러 추가적인 장점을 함께 가져갈 수 있게 함
☞ 단점
- 사용할 수 있는 메소드가 4가지뿐임
- 표준이 존재하지 않음
- REST가 필요한 이유 : 다양한 클라이언트의 등장, 애플리케이션 분리 및 통합
- REST 구성 요소
① 자원(Resource): URI. 서버 자원의 이름은 통합 자원 식별자 또는 URI이라고함
② 행위(Verb): HTTP Method. HTTP 프로토콜은 GET, POST, PUT, DELETE와 같은 메서드를 제공
③ 표현(Representation of Resource) : 클라이언트가 자원의 상태(정보)에 대해 요청하면 서버는 이에 적절한 응답을 보냄 REST의 하나의 자원은 JSON, XML, TEXT, RSS 등 여러 형태의 응답으로 나타낼 수 있으나, 보통 JSON이나 XML을 통해 데이터를 주고 받는 것이 일반적
→ REST의 3요소 : method, resource, message
6. POST, GET
- POST : 요청을 처리하기 위해 사용자(클라이언트)로부터 특정 양식(form)의 데이터를 암호화하여 서버로 전송하는 방법. 서버는 POST 방식으로 전달 받은 데이터를 통해 특정 동작을 수행할 수 있음
- GET : 사용자(클라이언트)로부터 특정 양식(form)의 데이터를 서버로 전송하는 방법. POST와 가장 큰 차이는 정보를 URL에 붙여서 보내게 되고, 암호화되지 않는다는 점
☞로그인과 같이 정보를 가려야할 때는 POST 방식을 사용하고 그렇지 않을 때는 GET 방식을 사용
7. API와 End point
- API : 프로그램들이 서로 상호작용하는 것을 도와주는 매체. 예를들어 점원은 손님에게 메뉴를 알려주고, 주방에 주문받은 요리를 요청 → 주방에서 완성된 요리를 손님에게 다시 전달한다며 손님과 요리사는 End Point에 해당하고, API는 점원과 같은 역할을 함
- API의 역할
① 서버와 데이터베이스에 대한 출입구 역할 : 서버와 데이터베이스에 대한 출입구 역할을 하며, 허용된 사람들에게만 접근성을 부여
② 프로그램과 기기가 원활하게 통신할 수 있도록 함 : 스마트폰 어플이나 프로그램과 기기가 데이터를 원활히 주고 받을 수 있도록 돕는 역할
③ 모든 접속을 표준화 : 모든 접속을 표준화하기 때문에 기기나 운영체제 등과 상관없이 누구나 동일한 권한을 얻을 수 있음
- API Testing : API를 테스트하여 기능, 성능, 신뢰성, 보안 측면에서 기대를 충족하는지 확인하는 테스팅의 한 유형
[02 Flask 웹 애플리케이션 만들기]
1. 렌더링 템플릿
- 렌더링 템플릿 : 플라스크 내에서 html 파일을 사용해 웹 사이트를 제작할 때 사용. (html 코드들을 플라스크내에 사용하는 것보다 파일을 따로 만들어 라우팅하는 방법이 유지보수에서 이점을 가져갈 수 있음). 플라스크는 render_template()을 이용해 html 파일을 렌더링하여 브라우저에 보여줌
※ Flask는 templates폴더에서 템플릿을 찾기 때문에 출력할 템플릿은 항상 templates폴더 안에 존재
from flask import render_template
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
- Jinja2 : 렌더링 템플릿을 통해 화면을 출력할 때 html에서는 Jinja2 문법으로 해당 내용을 띄울 수 있음 (thymeleaf 같은 건가..?)
- 블루 프린트 : 애플리케이션에 등록 할 때 실행할 작업을 기록해두고 사용하는 청사진. Flask의 요청으로 URL을 생성 할 때 화면을 출력하는 함수를 블루 프린트와 연결. 블루 프린트는 웹 애플리케이션의 개체를 미리 요구하지 않고 기능을 정의할 수 있으며, 파일을 여러개로 나누어 유지 보수 측면에서 매우 효과적으로 개발할 수 있게끔 함. 여러 기능이 포함된 웹 애플리케이션을 제작하고자 한다면 블루프린트를 사용하는 것을 권장
2. 게시글 생성 및 읽기
<!-- Board.html -->
<!doctype html>
<html lang="ko">
<head>
<title>게시판 등록</title>
<style type="text/css">
body{ text-align: center; }
</style>
</head>
<body>
<h1>게시판</h1>
<h4>추가</h4>
<form action = "/add" method = "POST">
이름<br>
<input type = "text" name = "name" /><br>
내용<br>
<textarea name = "context" cols="50" rows="10"></textarea><br><br>
<input type = "submit" value = "게 시" /><br>
</form>
<h4>목록보기</h4>
<table class="table" border="1" width = 600 style = "word-break:break-all" style="table-layout: fixed" align="center">
<thread>
<th width="4%">목차</th>
<th width="15%">이름</th>
<th width="25%">내용</th>
</thread>
{% for row in rows %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ row[0] }}</td>
<td>{{ row[1] }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
# main.py
from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
board = []
@app.route('/')
def index():
# 리스트(board)를 html 파일로 넘기기
return render_template('Board.html', rows = board)
@app.route('/add', methods = ['POST'])
def add():
if request.method == 'POST':
# Board.html 에서 입력받은 name, context 를 가져와서 board 변수에 추가
name = request.form['name']
context = request.form['context']
board.append([name, context])
return redirect(url_for('index'))
else:
return render_template('Board.html', rows = board)
if __name__ == '__main__':
app.run(debug=True)
3. UPDATE & DELETE 구현
<!-- list.html -->
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="Generator" content="EditPlus®">
<meta name="Author" content="">
<meta name="Keywords" content="">
<meta name="Description" content="">
<title>SQLite3 게시판 등록</title>
<style type="text/css">
body{ text-align: center; }
</style>
</head>
<body>
<h3>게시판</h3>
<h4>추가</h4>
<form action = "/add" method = "POST">
이름<br>
<input type = "text" name = "name" /><br>
내용<br>
<input type = "text" name = "context" style="text-align:center; width:400px; height:100px;"/><br><br>
<input type = "submit" value = "게 시" /><br>
</form>
<h4>목록보기</h4>
<table border=1 width="600" align="center">
<thead>
<td>목차</td>
<td>이름</td>
<td>내용</td>
<td>수정, 삭제</td>
</thead>
{% for row in rows %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ row[0] }}</td>
<td>{{ row[1] }}</td>
<td><a href ="{{url_for('update', uid = loop.index)}}">수정</a> <a href ="{{url_for('delete', uid = loop.index)}}">삭제</a></td>
</tr>
{% endfor %}
</table>
</body>
</html>
<!-- update -->
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="Generator" content="EditPlus®">
<meta name="Author" content="">
<meta name="Keywords" content="">
<meta name="Description" content="">
<title>SQLite3 게시판 등록</title>
<style type="text/css">
body{ text-align: center; }
</style>
</head>
<body>
<h3>게시판</h3>
<h4>수정</h4>
<form action = "/update/{{index}}" method = "POST">
이름<br>
<input type = "text" name = "name" /><br>
내용<br>
<input type = "text" name = "context" style="text-align:center; width:400px; height:100px;"/><br><br>
<input type = "submit" value = "수 정" /><br>
</form>
<h4>기존</h4>
<table border=1 width="600" align="center">
<thead>
<td>목차</td>
<td>이름</td>
<td>내용</td>
</thead>
<tr>
<td>{{ index}}</td>
<td>{{ rows[index-1][0] }}</td>
<td>{{ rows[index-1][1] }}</td>
</tr>
</table>
</body>
</html>
# main.py
from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
board = []
# 게시글 리스트 가져오기
@app.route('/')
def index():
return render_template('list.html', rows = board)
# 게시글 추가
@app.route('/add', methods = ['POST'])
def add():
print(request.method)
if request.method == 'POST':
board.append([request.form['name'], request.form['context']])
return redirect(url_for('index'))
else:
return render_template('list.html', rows = board)
# 게시글 삭제
@app.route('/delete/<int:uid>')
def delete(uid):
# loop.index는 1부터 세기 때문에 main에서 사용할 때는 uid-1 값을 사용 /
del board[uid-1]
return redirect(url_for('index'))
# 게시글 수정
@app.route('/update/<int:uid>', methods=['GET','POST'])
def update(uid):
if request.method =='POST':
board[uid-1] = [request.form['name'], request.form['context']]
return redirect(url_for('index'))
else:
return render_template('update.html',index=uid,rows=board)
if __name__ == '__main__':
app.run(debug=True)
4. 인증
- 인증 (Authentication) : 유저의 identification을 확인하는 절차 (유저의 아이디와 비밀번호를 확인).
- 로그인 절차
① 회원가입 : 아이디와 비밀번호를 생성 → 비밀번호 암호화 저장
② 로그인 : 아이디와 비밀번호를 입력 → 입력한 비밀번호를 암호화 한 후, DB에 저장된 암호화 비밀번호와 비교
③ 일치하면 로그인 성공, 일치하지 않으면 에러가 발생
④ 로그인이 성공되면 access_tocken을 클라이언트에게 전송
⑤ 유저는 로그인 성공 후 다음부터는 access_tocken을 첨부하여 request를 서버에 전송
- 비밀번호 암호화 : 비밀번호는 그대로 DB에 저장X (DB 해킹시 그대로 유출됨). 비밀번호는 반드시 암호화하여 저장
- 허가(Authorization) : 요청하는 request를 실행할 수 있는 권한이 있는 유저인가를 확인하는 절차
5. 로그인 기능 구현
# main.py
from flask import Flask, request, render_template, session, url_for, redirect
app = Flask(__name__)
app.secret_key = 'super secret key'
app.config['SESSION_TYPE'] = 'filesystem'
userinfo = {'Elice': '1q2w3e4r!!'}
# 첫 페이지. session 확인해 로그인 상태면 loggedin.html 나오고 아니면 index.html
@app.route("/")
def home():
if session.get('logged_in'):
return render_template('loggedin.html ')
else:
return render_template('index.html')
# 로그인 페이지
# 1. 로그인 페이지에서 입력받은 username 을 회원 정보가 저장된 userinfo 안에 있는지 확인
# 2. 없으면 '아이디가 없습니다' 리턴 / 있으면 username에 해당하는 password가 userinfo 안에 있는지 확인
# 3. 일치한다면 session['logged_in'] 변경하고 loggedin.html 이동 / 일치하지 않으면 '비밀번호가 틀립니다.' 리턴
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
name = request.form['username']
password = request.form['password']
try:
if (name in userinfo):
if userinfo[name] == password :
session['logged_in'] = True
return render_template('loggedin.html')
else:
return '비밀번호가 틀립니다.'
return '아이디가 없습니다.'
except:
return 'Dont login'
else:
return render_template('login.html')
# 회원가입. 전달받은 username 과 password 를 userinfo 딕셔너리에 추가
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
userinfo[request.form['username']] = request.form['password']
return redirect(url_for('login'))
else:
return render_template('register.html')
# 로그아웃. session['logged_in'] 값을 False로 바꾸고 초기 페이지로 redirect
@app.route("/logout")
def logout():
session['logged_in'] = False
return render_template('index.html')
6. 로깅
- 로깅 : Logging. 프로그램이 작동할 때 발생하는 이벤트를 추적하는 행위. 프로그램의 문제들을 파악하고 유지보수하는데 사용되고, 로깅을 통해 발생한 에러를 추적할 수 있음
- 로그를 남기는 이유
① 성능에 관한 통계와 정보를 제공
② 재현하기 힘든 버그에 대한 유용한 정보를 제공
③ 예기치 못한 특정 문제들을 디버그하기 위해 그 문제들을 처리하도록 코드를 수정하여 다시 적용하지 않아도, 일반적인 정보를 저장할 수 있음
- 로깅 레벨(Loggin Level) : DEBUG<INFO<WARNING<ERROR<CRITICAL
→ DEBUG : 상세한 정보
→ INFO : 일반적인 정보
→ WARNING : 예상치 못하거나 가까운 미래에 발생할 문제
→ ERROR : 에러 로그. 심각한 문제
→ CRITICAL : 프로그램 자체가 실행되지 않을 수 있는 문제
→ 기본 로거 레벨 세팅은 WARNING이기 때문에 설정 없이 INFO, DEBUG를 출력할 수 X
※ 기본적으로 '서버 시작 로그, 서버 포트 번호, 함수 호출, 데이터의 입출력' 같은 로깅 이력은 남기는 것이 좋음
- 로깅 구현
# main.py
from flask import Flask,render_template
app = Flask(__name__)
# errorhandler()를 사용해서 404 에러를 제어
@app.errorhandler(404)
def page_not_found(error):
# 에러 식별
app.logger.error(error)
# 에러페이지 연결
return render_template('page_not_found.html')
@app.route('/')
def hello_elice():
return "Hello Elice!"
if __name__ == '__main__':
app.run()
[03 RDB로 리소스 관리 및 저장하기]
1. RDB와 Flask의 상호작용
- 효율적인 데이터 관리 기능을 위해 RDB를 연동해 사용
- SQLAlchemy : 파이썬 코드에서 DB와 연결하기 위해 사용되는 라이브러리
- 사용자 정보 검색
@app.route('/search', methods = ['GET', 'POST'])
def search():
if request.method == 'POST':
name = request.form['name']
con = sqlite3.connect("database.db") # database.db와 connect()한 객체 con
cur = con.cursor() # con의 cursor()값 cur 만들기
# cur에 form을 통해 전달 받은 name값이 DB에 있는지 찾는 쿼리문을 실행
cur.execute(f"select * from Board WHERE name = '{name}'")
rows = cur.fetchall() # 쿼리문 실행결과를 fetchall()을 사용하여 rows에 저장
con.close() # con을 close()를 사용해 DB와의 연결을 종료
print("DB:")
for i in range(len(rows)):
print(rows[i][0] + ':' + rows[i][1])
return render_template('search.html', rows = rows)
else:
return render_template('search.html')
- DB 사용자 추가
# DB 사용자 추가
@app.route('/add', methods = ['GET', 'POST'])
def add():
if request.method == 'POST':
# 데이터베이스에 추가가 완료된다면 commit()을 하지만 중간에 문제가 생겼을 경우에는
# 이전 위치로 rollback()해야 하기 때문에 try~except문으로 commit()과 rollback()을 제어
try:
name = request.form['name']
context = request.form['context']
print(name, context)
with sqlite3.connect("database.db") as con:
cur = con.cursor()
con.execute(f"INSERT INTO Board(name, context) VALUES('{name}', '{context}')")
con.commit()
except:
con.rollback()
finally :
return redirect(url_for('board'))
else:
return render_template('add.html')
- 중복 사용자 방지
@app.route('/add', methods = ['GET', 'POST'])
def add():
if request.method == 'POST':
name = request.form['name']
context = request.form['context']
with sqlite3.connect("database.db") as con:
cur = con.cursor()
# name이 DB 상에 존재하는지 확인
cur.execute(f"SELECT name FROM Board WHERE name='{name}'")
row = cur.fetchall()
# name 을 검색한 리스트의 길이가 0이면 사용자를 추가
if len(row) == 0 :
cur.execute(f"INSERT INTO Board (name, context) VALUES ('{name}', '{context}')")
con.commit()
else:
# DB 상에 사용자가 있을 경우 중복사용자임을 알 수 있도록 add.html의 msg 전달
return render_template('add.html', msg = "이미 존재하는 사용자입니다.")
return redirect(url_for('board'))
else:
return render_template('add.html')
- DB를 이용한 검색, 추가 , 수정, 삭제
@app.route('/')
def board():
con = sqlite3.connect("database.db")
cur = con.cursor()
cur.execute("select * from Board")
rows = cur.fetchall()
print("DB:")
for i in range(len(rows)):
print(rows[i][0] + ':' + rows[i][1])
return render_template('board.html', rows = rows)
@app.route('/search', methods = ['GET', 'POST'])
def search():
if request.method == 'POST':
name = request.form['name']
con = sqlite3.connect("database.db")
cur = con.cursor()
cur.execute(f"SELECT * FROM Board WHERE name='{name}' or context='{name}'")
rows = cur.fetchall()
print("DB:")
for i in range(len(rows)):
print(rows[i][0] + ':' + rows[i][1])
return render_template('search.html', rows = rows)
else:
return render_template('search.html')
@app.route('/add', methods = ['GET', 'POST'])
def add():
if request.method == 'POST':
try:
name = request.form['name']
context = request.form['context']
with sqlite3.connect("database.db") as con:
cur = con.cursor()
cur.execute(f"INSERT INTO Board (name, context) VALUES ('{name}', '{context}')")
con.commit()
except:
con.rollback()
finally :
con.close()
return redirect(url_for('board'))
else:
return render_template('add.html')
@app.route('/update/<uid>', methods=['GET','POST'])
def update(uid):
if request.method =='POST':
name = request.form['name']
context = request.form['context']
with sqlite3.connect("database.db") as con:
cur = con.cursor()
con.execute(f"UPDATE Board SET name = '{name}', context = '{context}' WHERE name = '{uid}'")
con.commit()
return redirect(url_for('board'))
else:
con = sqlite3.connect("database.db")
cur = con.cursor()
cur.execute(f"SELECT * FROM Board WHERE name ='{uid}'")
row = cur.fetchall()
return render_template('update.html',row=row)
@app.route('/delete/<uid>')
def delete(uid):
with sqlite3.connect("database.db") as con:
cur = con.cursor()
cur.execute(f"DELETE FROM Board WHERE name='{uid}'")
con.commit()
return redirect(url_for('board'))
[04 SQL Alchemy]
1. ORM
- ORM : 객체 관계 매핑(Object Relational Mapping). 간단하게 데이터베이스 내의 테이블들을 객체화하여 각 DBMS(MySQL)에 대해서 CRUD 등을 공통된 접근 기법으로 사용할 수 있음. 반복되는 쿼리를 객체 단위로 생성하여 이를 해결하고자 했고 이런 작업을 도와주는 것이 바로 ORM임. ORM을 사용하게 되면 따로 SQL문을 작성할 필요없이 객체를 통해 간접적으로 데이터베이스를 조작할 수 있음. Python ORM은 DjangoORM 과 SQLAlchemy 등. (JPA 같은 것 말하는 듯?🤔)
- ORM 장점 : 개발 생산성을 증가, 유지보수성이 좋음, 코드 가독성이 좋음
- ORM 단점 : 호출 방식에 따라 성능이 천차만별, 복잡한 쿼리 작성시, ORM 사용에 대한 난이도가 증가, DBMS(MySQL) 고유의 기능을 모두 사용하지 못함
- SQLAlchemy : 파이썬 ORM 라이브러리에서 가장 많이 사용. 파이썬 코드에서 Flask를 데이터베이스와 연결하기 위해 사용하며 ORM으로 데이터베이스 테이블을 프로그래밍 언어의 클래스로 표현하게 해주고 테이블의 저장, 읽기, 업데이트, 삭제 등을 처리함
- SQLAlchemy 사용하는 이유 : SQL 쿼리를 사용하지 않고 프로그래밍 언어로 객체간의 관계를 표현할 수 있기 때문. 데이터베이스의 종류와 상관 없이 일관된 코드를 유지할 수 있어 프로그램 유지 및 보수가 편리하고 SQL 쿼리를 내부에서 자동으로 생성하기 때문에 오류 발생률이 줄어드는 장점이 있음
- Model 구현
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Member(db.Model):
name = db.Column(db.String(20), primary_key = True)
age = db.Column(db.Integer, nullable=False)
- 모델 생성
# model.py
from main import db
class Member(db.Model): # db의 Model 클래스를 상속
id = db.Column(db.Integer, primary_key=True) # id는 primary_key
name = db.Column(db.String(50), nullable=True)
age = db.Column(db.Integer, nullable=False)
def __init__(self, name, age): # id는 생성 순서대로 자동으로 지정되니 init X
self.name = name
self.age = age
- 쿼리의 종류
종류 |
사용법 |
equal |
== |
not equal |
!= |
like |
like() |
in |
in_() |
not in |
~ in_() |
is null |
==None |
is not null |
!=None |
and |
& |
or |
| |
order by |
order_by() |
limit |
limit() |
offset |
offset() |
count |
count() |
- equal
# 이름이 "Elice"인 멤버를 검색
@app.route('/')
def list():
member_list = Member.query.filter(Member.name == 'Elice')
return " ".join(i.name for i in member_list)
- not equal
# 이름이 "Elice"가 아닌 멤버를 검색
@app.route('/')
def list():
member_list = Member.query.filter(Member.name != 'Elice')
return " ".join(i.name for i in member_list)
- like
# 이름이 "Elice"와 비슷한 멤버를 검색
@app.route('/')
def list():
member_list = Member.query.filter(Member.name.like('Elice'))
return " ".join(i.name for i in member_list)
- in
# 이름이 "Elice", "Dodo"에 포함되는 멤버를 검색
@app.route('/')
def list():
member_list = Member.query.filter(Member.name.in_(['Elice', 'Dodo']))
return " ".join(i.name for i in member_list)
- not in
# 이름이 "Elice", "Dodo"에 포함되지 않는 멤버를 검색
@app.route('/')
def list():
member_list = Member.query.filter(~Member.name.in_(['Elice', 'Dodo']))
return " ".join(i.name for i in member_list)
- is null
# 이름이 비어있는 멤버를 검색
@app.route('/')
def list():
member_list = Member.query.filter(Member.name == None)
return " ".join(i.name for i in member_list)
- is not null
# 이름이 비어있지 않은 멤버를 검색
@app.route('/')
def list():
member_list = Member.query.filter(Member.name != None)
return " ".join(i.name for i in member_list)
- and
# 이름이 "Elice"이며 나이가 15살인 멤버를 검색
@app.route('/')
def list():
member_list = Member.query.filter((Member.name == 'Elice') & (Member.age == '15'))
return " ".join(i.name for i in member_list)
- or
# 이름이 "Elice"이거나 나이가 15살인 멤버를 검색
@app.route('/')
def list():
member_list = Member.query.filter ((Member.name == 'Elice') | (Member.age == '15'))
return " ".join(i.name for i in member_list)
- order by
# 나이순으로 정렬하여 검색 (기본 = 오름차순)
@app.route('/')
def list():
member_list = Member.query.order_by(Member.age.desc())
return " ".join(i.name for i in member_list)
- limit
# 나이를 내림차순으로 정렬하되, limit_num의 크기만큼 반환
@app.route('/')
def list(limit_num = 5):
if limit_num is None:
limit_num = 5
member_list = Member.query.order_by(Member.age.desc()).limit(limit_num)
return " ".join(i.name for i in member_list)
- offset
# 나이를 내림차순으로 정렬하되, off_set 크기만큼 앞에서 부터 생략하고 반환
@app.route('/')
def list(off_set = 5):
if off_set is None:
off_set = 5
member_list = Member.query.order_by(Member.age.desc()).offset(off_set)
return " ".join(i.name for i in member_list)
- count
# 나이를 내림차순으로 정렬하고 나오는 튜플 수를 반환
@app.route('/')
def list():
member_list = Member.query.order_by(Member.age.desc()).count()
return str(member_list)
[05 REST API]
1. REST API
- REST API : REST를 기반으로 서비스 API를 구현한 것. 최근 OpenAPI(누구나 사용할 수 있도록 공개된 API), 마이크로 서비스 등을 제공하는 기업에서는 대부분 REST API를 제공함
- REST API의 특징
→ REST 기반으로 시스템을 분산하여 확장성과 재사용성을 높여 유지보수를 편리하게 할 수 있음
→ REST는 HTTP 표준을 기반으로 구현하므로, HTTP를 지원하는 프로그램 언어로 클라이언트, 서버를 구현할 수 있음
- REST API 설계의 기본 규칙
① URI는 정보의 자원을 표현 : URI는 자원을 표현하는 데에 중점을 두어야 함. (GET같은 행위에 대한 표현이 들어가면 X) 자원은 동사보다는 명사를, 대문자보다는 소문자를 사용. 자원의 도큐먼트 이름으로는 단수 명사를 사용하고 자원의 컬렉션 이름으로는 복수 명사를 사용. 자원의 스토어 이름으로는 복수 명사를 사용해야 함
② 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE 등)으로 표현 : 주로 5가지의 Method(GET, POST, PUT, PATCH, DELETE)를 사용하여 CRUD를 구현
Method |
Action |
역할 |
GET |
index/retrieve |
모든/특정 자원을 조회 |
POST |
create |
자원을 생성 |
PUT |
replace |
자원의 전체를 교체 |
PATCH |
modify |
자원의 일부를 수정 |
DELETE |
delete |
모든/특정 자원을 삭제 |
- REST API 설계 규칙
① 슬래시(/)는 계층 관계를 나타내는 데 사용. ex.
② URI 마지막 문자로 슬래시(/)를 포함하지X
③ 하이픈(-)은 URI 가독성을 높이는데 사용
④ 밑줄(_)은 URI에 사용하지 X
⑤ URI 경로에는 소문자를 사용
⑥ 파일확장자는 URI에 포함하지 X
⑦ 자원 간에 연관 관계가 있는 경우 '/자원명/자원ID/관계가 있는 다른 자원명'
- RESTful : 일반적으로 REST라는 아키텍처를 구현하는 웹 서비스를 나타내기 위해 사용되는 용어. 즉, REST API를 사용하는 웹 서비스를 RESTful하다고 하며 RESTful은 REST를 REST답게 쓰기 위한 방법임. (REST의 원리를 따르고 사용하는 시스템을 RESTful이라는 용어로 칭함)
- RESTful의 목적 : 이해하기 쉽고 쉬운 REST API를 만드는 것. 성능이 중요한 상황에서는 굳이 RESTful한 API를 구현할 필요가 X
- CRUD 설계 : CRUD (Create, Read, Update, Delete)는 기본적인 데이터 처리 4가지 기능임
① CREATE 구현
from flask import Flask, render_template, request, redirect, url_for
import json
app = Flask(__name__)
board = []
@app.route('/')
def index():
return render_template('Board.html', rows = board)
@app.route('/create', methods = ['POST'])
def create():
# form을 통해 전달받은 name과 context를 board 리스트에 추가
board.append([request.form['name'], request.form['context']])
# create() 메소드에서 json 형태의 데이터를 반환
return json.dumps({ "status": 200, "result": {"id": len(board) }} )
if __name__ == '__main__':
app.run(debug=True)
② READ 구현
from flask import Flask, render_template, request, redirect, url_for
import json
app = Flask(__name__)
board = []
@app.route('/')
def index():
return render_template('Board.html', rows = board)
@app.route('/create', methods = ['POST'])
def create():
board.append([request.form['name'], request.form['context']])
return json.dumps({ "status": 200, "result": {"id": len(board) }})
@app.route('/read', methods = ['GET'])
def read():
# read() 메소드에서 json 형태의 데이터를 반환
return json.dumps({ "status": 200, "result": board })
if __name__ == '__main__':
app.run(debug=True)
③ REST API를 AJAX로 구현
# main.py
from flask import Flask, render_template, jsonify, request
app = Flask(__name__)
board = []
@app.route('/')
def index():
return render_template('index.html', rows=board)
@app.route('/ajax', methods=['POST'])
def ajax():
# data 변수에 request를 이용해 전달된 데이터를 json 형태로 저장
data = request.get_json()
board.append(data)
# jsonify()를 이용해 결과를 반환
return jsonify({ 'result' : "success", 'result2' : data })
<!-- index.html -->
<script>
$('#execute').click(function(){
var id = $('#id1').val();
var name = $('#name1').val();
var context = $('#context1').val();
var postdata = {
'id':id, 'name':name, 'context':context
}
$.ajax({
type: 'POST',
url: '{{url_for("ajax")}}',
data: JSON.stringify(postdata),
dataType : 'JSON',
contentType: "application/json",
success: function(data){
alert('성공! 데이터 값:' + data.result2['id']+" " + data.result2['name']+ " " + data.result2['context'])
},
error: function(request, status, error){
alert('ajax 통신 실패')
alert(error);
}
})
})
</script>
④ AJAX를 이용해 UPDATE & DELETE 구현
from flask import Flask, render_template, jsonify, request
app = Flask(__name__)
board = [{"id": 1, "name": "elice", "context": "test"}]
@app.route('/')
def index():
return render_template('index.html', rows = board)
@app.route('/create', methods=['POST'])
def create():
data = request.get_json()
board.append(data)
return jsonify(result = "success", result2= data)
# 삭제되거나 수정되는 데이터는 board리스트의 가장 마지막 데이터
@app.route('/delete', methods=['POST'])
def delete():
# board 리스트의 마지막 요소를 삭제하고 jsonify()를 반환
board.pop()
return jsonify({ 'result' : "success" })
# 삭제되거나 수정되는 데이터는 board리스트의 가장 마지막 데이터
@app.route('/put', methods=['POST'])
def put():
# request를 통해 전달된 데이터를 data변수에 저장
data = request.get_json()
# board 리스트의 마지막 요소를 으로 data로 수정하고 jsonify()를 반환
board[-1]['id'] = data['id']
board[-1]['name'] = data['name']
board[-1]['context'] = data['context']
return jsonify({ 'result' : "success", 'result2' : data })
<!-- index.html -->
<script>
$('#create').click(function(){
var id = $('#id1').val();
var name = $('#name1').val();
var context = $('#context1').val();
var postdata = {
'id':id, 'name':name, 'context':context
}
$.ajax({
type: 'POST',
url: '{{url_for("create")}}',
data: JSON.stringify(postdata),
dataType : 'JSON',
contentType: "application/json",
success: function(data){
alert('성공! 데이터 값:' + data.result2['id']+" " + data.result2['name']+ " " + data.result2['context'])
},
error: function(request, status, error){
alert('ajax 통신 실패')
alert(error);
}
})
})
$('#update').click(function(){
var id = $('#id1').val();
var name = $('#name1').val();
var context = $('#context1').val();
var postdata = {
'id':id, 'name':name, 'context':context
}
$.ajax({
type: 'POST',
url: '{{url_for("put")}}',
data: JSON.stringify(postdata),
dataType : 'JSON',
contentType: "application/json",
success: function(data){
alert('성공! 수정된 데이터 값:' + data.result2['id']+" " + data.result2['name']+ " " + data.result2['context'])
},
error: function(request, status, error){
alert('ajax 통신 실패')
alert(error);
}
})
})
$('#delete').click(function(){
$.ajax({
type: 'POST',
url: '{{url_for("delete")}}',
contentType: "application/json",
success: function(){
alert('성공! 데이터 삭제 완료')
},
error: function(request, status, error){
alert('ajax 통신 실패')
alert(error);
}
})
})
</script>
※ 수업 자료의 출처는 K-Digital Training x 엘리스 인공지능 서비스 개발 기획 1기 (elice.io/)입니다.
, , ,
'개발 > 엘리스 AI 트랙' 카테고리의 다른 글
엘리스 AI 트랙 05주차 - 프로젝트로 배우는 MongoDB (1/22) 🔥 (0) | 2021.01.23 |
---|---|
엘리스 AI 트랙 05주차 - MongoDB 기초 (1/21) 🔥 (0) | 2021.01.22 |
엘리스 AI 트랙 04주차 - 파이썬 크롤링 (1/15) 🔥 (0) | 2021.01.18 |
엘리스 AI 트랙 04주차 - 파이썬 정규표현식 (1/14) 🔥 (0) | 2021.01.16 |
엘리스 AI 트랙 04주차 - 파이썬 객체지향 프로그래밍 (1/13) 🔥 (0) | 2021.01.15 |