개발자로 후회없는 삶 살기
Flask PART.플라스크 기초 다지기 본문
서론
※ 이 포스트는 다음 교재의 학습이 목적임을 밝힙니다.
2-01 플라스크 기초 다지기
현재 파이보 프로젝트는 `projects/myproject` 디렉터리 아래에 pybo.py 파일만 생성한 상태다. 그런데 이보다 규모를 갖춘 플라스크 프로젝트를 만들고자 한다면 …
wikidocs.net
본론
2.1 플라스크 기초 다지기
- 프로젝트 구조
① models.py = db 처리 파일
② forms.py = 서버로 전송된 폼을 처리하는 파일
③ views 디렉터리 = 화면 구성 파일
④ static 디렉터리 = css, js, 이미지 파일을 저장하는
⑤ templates 디렉터리 = html 파일을 저장하는 디렉터리
⑥ config.py : 프로젝트의 환경변수, 데이터베이스 등의 설정을 하는 파일
2.2 플라스크 어플리케이션 팩토리
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_pybo():
return 'Hello, Pybo!'
플라스크는 app객체를 사용해 여러 가지 설정을 합니다. 근데 이런 식으로 app를 전역으로 쓰면 프로젝트 규모가 커질수록 문제가 발생할 확률이 높아집니다. App 객체를 전역으로 사용할 때 발생하는 문제를 예방하려면 애플리케이션 팩토리를 사용하라고 플라스크 공식 홈페이지에서 권합니다.
- 어플리케이션 팩토리
1. __init__.py 파일 만들기
app 객체를 만드는 파일을 __init__.py로 대체합니다. 이렇게 될 경우 Flask run 명령어는 최상위 폴더에서 해야 합니다. 최상위 폴더는 venv가 아닌 myproject입니다. 즉 파이참에 맨 위에 보이는 폴더가 최상위 폴더입니다.
2. 애플리케이션 팩토리 만들기
create_app이 애플리케이션 팩토리입니다. create_app 대신에 다른 이름으로 작성하면 정상적으로 동작하지 않습니다. 플라스크 내부에서 정의된 함수명입니다.
from flask import Flask
def create_app():
app = Flask(__name__)
@app.route('/')
def hello_pybo():
return 'Hello, Pybo!'
return app
2.3 블루프린트로 라우팅 함수 관리
app.route와 같이 애너테이션으로 URL을 매핑하는 함수를 라우팅 함수라고 합니다. 그런데 하나 생각해 볼 점이 있습니다. 지금까지 작성한 대로라면 새로운 URL 매핑이 필요할 때마다 라우팅 함수를 create_app 함수 안에 계속 추가해야 합니다. 이렇게 라우팅 함수가 계속 추가된다면 create_app 함수는 엄청나게 크고 복잡한 함수가 될 것입니다. 블루프린트를 사용하면 이 문제를 해결할 수 있습니다.
+ 블루프린트는 플라스크에서 URL과 함수의 매핑을 관리하기 위해 사용하는 도구(클래스)입니다.
- 블루프린트 생성
# views/main_views.py
from flask import Blueprint
bp = Blueprint('main', __name__, url_prefix='/')
@bp.route('/hello')
def hello_pybo():
return 'Hello, Pybo!'
@bp.route('/')
def index():
return 'Pybo index'
create_app 함수 안에 포함된 hello_pybo 함수 대신 블루프린트를 사용하여 app 객체를 이용할 것입니다. create_app에 있던 def 어플리케이션을 bp를 생성할 모듈에 작성합니다.
※ 처음 서버에 접근하면 보게 되는 함수를 index로 작성하는 것이 규칙이다.
- 블루프린트 등록
__init__.py
from flask import Flask
def create_app():
app = Flask(__name__)
from .views import main_views
app.register_blueprint(main_views.bp)
return app
블루프린트를 사용하려면 원래 __init__에 등록을 해야합니다. 코드를 보면 create_app에 라우팅 함수와 def 어플리케이션을 지우고 블루프린팅에 전부 작성한 것입니다. 우리는 블루프린트인 main_views만 더럽히면 create_app은 깨끗하게 쓸 수 있습니다.
2.4 모델로 데이터 처리하기
※ 미리보기
1) SQLAlchemy : ORM 라이브러리
2) Flask-Migrate : 파이썬 모델을 이용해 테이블을 생성하고 컬럼을 추가하는 등을 해주는 라이브러리
3) 모델 : 데이터를 관리하는 데 사용하는 ORM 클래스
- ORM이란?
ORM은 데이터베이스에 데이터를 저장하는 테이블을 파이썬 클래스로 만들어 관리하는 기술합니다. 즉, ORM을 이용하면 개발자가 쿼리를 직접 작성하지 않아도 데이터베이스의 데이터를 처리할 수 있습니다.
+ 데이터를 관리하는 데 사용하는 ORM 클래스를 모델이라고 합니다. 모델을 사용하면 내부에서 SQL 쿼리를 자동으로 생성해 줍니다.
- ORM 라이브러리 설치
1. 설정 파일 추가
ORM을 사용하려면 데이터 베이스 설정이 필요합니다. 루트 dir에서 config 파일을 생성하고 코드를 작성합니다.
# config.py
import os
BASE_DIR = os.path.dirname(__file__)
SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(os.path.join(BASE_DIR, 'pybo.db'))
SQLALCHEMY_TRACK_MODIFICATIONS = False
코드를 보면 SQLALCHEMY_DATABASE_URI에 의해 SQLite 데이터베이스가 사용되고 데이터 베이스 파일은 프로젝트 홈 dir 바로 밑에 pybo.db 파일로 저장됩니다.
2. ORM 적용하기
from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
import config
db = SQLAlchemy() # 추가
migrate = Migrate() # 추가
def create_app():
app = Flask(__name__)
# 추가
# config.py 파일에 작성한 항목을 읽기 위함
app.config.from_object(config)
#ORM 추가
# db와 migrate 객체를 app에 등록
db.init_app(app)
migrate.init_app(app, db)
from . import models
from .views import main_views
app.register_blueprint(main_views.bp)
return app
이어서 pybo/__init__.py 파일을 수정해 SQLAlchemy를 적용합니다.
3. DB 초기화하기
위까지 하면 ORM을 사용할 준비가 되었으므로 flask db init 명령으로 db를 초기화합니다.
이 명령어는 데이터 베이스를 관리하는 초기 파일들을 자동으로 migrations dir에 생성합니다.
4. 모델 만들기
db를 사용할 준비는 됐으니 이제 파이보에서 사용할 모델을 만들어 보자 현재 프로젝트는 질문 답변 게시판이므로 질문과 답변에 해당하는 모델이 있어야 합니다. 모델은 데이터를 다룰 목적으로 만든 파이썬 클래스인 것을 잊지 않아야 합니다. 질문 모델과 답변 모델이 다르게 있습니다.
-> 질문 모델 구상하기
질문 모델에 들어갈 속성을 구상합니다.
-> 질문 모델 생성하기
모델을 정의해 보겠습니다. pybo dir에 모델을 정의하기 위한 models.py 파일을 생성하고 질문 모델인 Question 클래스를 다음과 같이 작성합니다. models.py 파일에는 모델 클래스들을 정의하여 사용할 것입니다. 모델이 파이썬 클래스인 것을 잊지 않아야 합니다! 그중 질문 모델이 Question 클래스입니다!
# ./models.py
from pybo import db
class Question(db.Model):
id = db.Column(db.Integer, primary_key=True) # 기본키
subject = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text(), nullable=False)
create_date = db.Column(db.DateTime(), nullable=False)
+ ★ 모델은 ORM 클래스라고 했습니다. ORM은 DB를 파이썬 코드로 사용할 수 있게 해주는 것이라고 했습니다. 따라서 모델 코드를 보면 파이썬 코드로 DB를 다루는 클래스입니다.
> Question 같은 모델 클래스는 db.Model 클래스를 상속하여 만들어야 합니다. 여기서 나온 db는 __init__.py 파일에서 생성된 SQLAlchemy 객체입니다. db.column()으로 속성을 생성합니다. 인자로 데이터 타입, 기본키, nullable이 있습니다.
+ nullable은 속성에 값을 저장할 때 빈값을 허용할지의 여부입니다. 따로 설정하지 않으면 빈값을 허용합니다. 빈값을 하지 않으려면 False로 해야 합니다.
-> 답변 모델 구상하기
답변 모델에 들어갈 속성을 구상합니다.
=> 답변 모델 생성하기
class Answer(db.Model):
id = db.Column(db.Integer, primary_key=True)
question_id = db.Column(db.Integer, db.ForeignKey('question.id', ondelete='CASCADE'))
question = db.relationship('Question', backref=db.backref('answer_set'))
# question = db.relationship('Question', backref=db.backref('answer_set', cascade='all, delete-orphan'))
content = db.Column(db.Text(), nullable=False)
create_date = db.Column(db.DateTime(), nullable=False)
-> 속성
1) question_id
답변을 질문과 연결하기 위해 추가한 속성입니다. 질문 클래스의 id 속성이 필요합니다. 모델을 서로 연결할 때에는 외래키를 사용해야 합니다.
+ 인자를 살펴보면 question.id는 question 테이블의 id 컬럼을 의미합니다. > answer 모델의 question_id 속성을 question 테이블의 id 컬럼과 연결한다는 뜻입니다. 따라서 Question 모델을 통해 테이블 객체가 생성되면 테이블 명을 qeustion으로 해야 합니다. CASCADE는 질문을 삭제하면 해당 질문에 달린 답변도 함께 삭제된다는 의미입니다.
2) question
question 속성은 답변 모델에서 질문 모델을 참조하기 위해 추가했습니다. 이러면 질문과 답변 모델이 연결되어 답변 모델에서 연결된 질문 모델의 제목을 answer.qeustion.subject처럼 참조할 수 있습니다.
+ relationship 인자
참조할 모델명, 역참조 설정 : 질문에서 답변을 거꾸로 참조하는 것입니다. 어떤 질문에 해당하는 객체가 a_question이라면 a_question.answer_set으로 해당 질문에 달린 답변들을 참조할 수 있습니다.
- 모델을 이용해 테이블 자동 생성하기
모델을 구상하고(컬럼 뭐가 필요할까? 머리로 생각하기) 생성(models.py에 클래스로 작성)했으므로 플라스크의 migrate 기능을 이용해 db 테이블을 생성해 보겠습니다.
1) 모델 import
# 작성한 모델을 migrate 기능이 인식하려면 inmport가 필요
from . import models
2) 리비전 파일 생성
이 명령을 수행하면 데이터 베이스 버전 번호가 생성됩니다. 리피전 파일 내에는 테이블 생성을 위한 쿼리문들이 저장되어 있습니다.
3) 리비전 파일 실행하기
실행하면 question, answer 이름의 테이블이 생성됩니다. pybo.db가 바로 sqlite 데이터베이스의 데이터 파일입니다.
- 생성된 모델 사용하기
1. 플라스크 셸
플라스크 셸을 사용해 모델을 사용해 보겠습니다. 파이썬 셸이 아닌 플라스크를 실행하는 데 필요한 환경이 자동으로 설정되어 실행되는 셸입니다.
2. 질문 저장하기
from pybo.models import Question, Answer
from datetime import datetime
q = Question(subject='pybo가 무엇인가요?', content='pybo에 대해서 알고 싶습니다.', create_date=datetime.now())
from pybo import db
db.session.add(q)
db.session.commit()
q.id
# 결과
1
db.session.rollback()
# 커밋은 취소할 수 없다. 따라서 add하고 commit하기 전에 롤백으로 해야 취소된다.
id는 기본 키라 알아서 123,,,으로 들어갑니다. 객체 q를 만들어도 db에 데이터가 저장되는 게 아니고 SQLAlchemy의 db 객체를 사용해야 합니다.(아까 SQLAlchemy를 db라는 이름의 객체로 만들었습니다.) 신규 데이터를 저장할 때는 db.session의 add 함수를 사용한 다음 commit 함수까지 실행해야 합니다. 세션을 통해서 데이터를 저장, 수정, 삭제 작업을 한 다음에는 반드시 db.session.commit()으로 커밋을 해주어야 합니다.
3. 데이터 조회
Question.query.all()
# 결과
[<Question 1>, <Question 2>]
Question.query.filter(Question.id==1).all() # 조건에 맞는 데이터 반환
# 결과
[<Question 1>]
Question.query.get(1) #단 한 건만 반환 ∴ 리스트가 아닌 객체가 리턴 됨!!!
# 결과
<Question 1>
Question.query.filter(Question.subject.like('%플라스크%')).all() # 플라스크를 포함하는 문자열
# 결과
[<Question 2>]
4. 데이터 수정
>>> q = Question.query.get(2)
>>> q
<Question 2>
>>> q.subject = 'Flask Model Question'
>>> db.session.commit()
수정할 때는 단순히 대입 연산자를 사용하면 되고 변경 후에는 반드시 커밋을 수행해야 합니다.
5. 데이터 삭제
>>> q = Question.query.get(1)
>>> db.session.delete(q)
>>> db.session.commit()
첫 번째 질문을 조회한 후 delete를 한 것으로 삭제도 역시 커밋을 해야 합니다.
6. 답변 저장하기
>>> from datetime import datetime
>>> from pybo.models import Question, Answer
>>> from pybo import db
>>> q = Question.query.get(2) # 질문을 조회하고 question 속성에 q를 대입해 답변 생성
>>> a = Answer(question=q, content='네 자동으로 생성됩니다.', create_date=datetime.now())
>>> db.session.add(a)
>>> db.session.commit()
답변을 생성하려면 연결할 질문이 필요하므로 우선 질문을 조회했습니다. id가 2인 질문을 조회하여 q 객체에 저장했습니다. 그런 다음 Answer 모델의 question 속성에 방금 조회한 q 객체를 대입해 답변을 생성했습니다.
+ Answer 모델에는 어떤 질문에 해당하는 답변인지 연결할 목적으로 question_id 속성이 있습니다. Answer 모델의 객체를 생성할 때 question에 q를 대입하면 question_id에 값을 지정하지 않아도 자동으로 입력되어 저장됩니다. 따라서 question_id에 값을 설정할 필요가 없습니다.
7. 답변과 질문에서 서로 참조하기
>>> a.question
# 결과
<Question 2>
>>> q.answer_set
# 결과
[<Answer 1>]
역참조를 사용하면 질문에 연결된 답변들을 쉽게 가져올 수 있습니다. 이는 개발자에게 큰 편의를 가져다주는 신통방통한 녀석입니다!
'[백엔드] > [Etc]' 카테고리의 다른 글
FLASK PART.서비스 개발 (0) | 2023.01.21 |
---|---|
Flask PART.상세 기능 만들기 (0) | 2023.01.20 |
Flask PART.플라스크 개발 환경 준비 (0) | 2023.01.18 |