Web/Flask

Flask ORM으로 모델 생성하고 데이터 처리하기

daeunnniii 2022. 1. 10. 15:12
728x90
반응형

ORM(Object Relational Mapping)

ORM이란 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 것을 말한다. 즉, 객체 지향 프로그래밍의 클래스를 관계형 데이터베이스이 테이블에 매핑해준다.

ORM을 이용하면 데이터베이스 종류에 상관없이 일관된 코드를 유지할 수 있어서 프로그램을 유지 보수하기 편리하다. 또한 내부에서 안전한 SQL 쿼리를 자동으로 생성해 주므로 개발자가 달라도 통일된 쿼리를 작성할 수 있고 오류 발생률도 줄일 수 있다.

 

Flask ORM 라이브러리 사용하기

SQLAlchemy는 가장 많이 사용하는 파이썬 ORM 라이브러리이다.

파이썬 모델을 이용해 테이블을 생성하고, 칼럼을 추가하는 등의 작업을 위해 Flask-migrate 라이브러리도 사용할 것이다.

아래와 같이 Flask-Migrate 라이브러리를 설치하면 SQLAlchemy도 함께 설치된다.

$ pip install Flask-Migrate

 

config.py 설정 파일 추가

Flask에 ORM을 적용하려면 config.py라는 설정 파일이 필요하다. 

C:/프로젝트 경로에 config.py 파일을 생성한 뒤 아래와 같이 작성한다. 아래 코드는 dabi.db라는 데이터베이스 파일을 프로젝트의 루트 디렉터리에 저장하려는 것이다.

import os

BASE_DIR = os.path.dirname(__file__)

SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(os.path.join(BASE_DIR, 'dabi.db'))
SQLALCHEMY_TRACK_MODIFICATIONS = False

SQLALCHEMY_DATABASE_URI: 데이터베이스 접속 주소

SQLALCHEMY_TRACK_MODIFICATIONS: SQLAlchemy의 이벤트를 처리하는 옵션

일단 이 옵션은 사용하지 않을 것이므로 False로 비활성화했다.

 

ORM 적용하기

__init__.py 파일을 다음과 같이 수정해 SQLAlchemy를 적용한다.

app.config.from_object(config)는 앞서 작성한 config.py를 app.config 환경 변수로 설정하기 위한 코드이다. 그리고 models.py에서 db, migrate 객체를 만든 뒤 init__app 메서드를 통해 초기화한다.

from flask import Flask
from views import main_views
import config
from models import *

app = Flask(__name__)
app.config.from_object(config)

# ORM
db.init_app(app)
migrate.init_app(app, db)

# Blueprint
app.register_blueprint(main_views.bp)

if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)

 

데이터베이스 초기화

아래 명령은 데이터베이스를 관리하는 초기 파일들을 migrations라는 디렉터리에 자동으로 생성해준다.

flask db init은 데이터베이스를 초기화하는 것이므로 최초로 한번만 수행하면 된다.

$ flask db init

flask db init 명령을 수행하면 아래와 같이 migrations 디렉터리 내에 Flask-Migrate 라이브러리에서 사용될 파일들이 생성된다.

 

아래 명령은 모델을 새로 생성하거나 변경할 때 사용한다.

$ flask db migrate

 

아래 명령은 모델의 변경 내용을 실제 데이터베이스에 적용할 때 사용한다.

$ flask db upgrade

 

모델 생성

1. 질문 모델 생성하기

속성명 설명
id 질문 데이터의 고유 번호
subject 질문 제목
content 질문 내용
create_date 질문 작성일시

models.py 파일에 다음과 같이 질문 모델인 Question 클래스를 작성한다.

from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
migrate = Migrate()

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)

Question 클래스는 모든 모델의 기본 클래스인 db.Model을 상속받았다. 여기서 db는 __init__.py에서 생성한 SQLAlchemy 객체이다.

db.Column()의 인수로는 데이터 타입을 설정하며 이외에도 기본키 속성, NULL 속성 등을 설정할 수 있다.

 

기본키 속성의 경우 Flask에서 데이터 타입이 db.Integer이고 기본키로 지정한 속성은 자동으로 auto_increment로 설정되는 특징이 있다. (직접 autoincrement=True로 설정해도 된다.)

 

NULL 속성의 경우 nullable을 지정하지 않으면 디폴트로 nullable=True로 빈 값을 허용한다.

 

2. 답변 모델 생성하기

속성명 설명
id 답변 데이터의 고유 번호
question_id 질문 데이터의 고유 번호
content 답변 내용
create_date 답변 작성 일시

 

models.py 파일에 다음과 같이 답변 모델인 Answer 클래스를 작성한다.

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'))
    content = db.Column(db.Text(), nullable=False)
    create_date = db.Column(db.DateTime(), nullable=False)

 

question_id 속성에서는 ForeignKey를 통해 첫번째 인수를 question.id로 줌으로써 question 테이블의 id 칼럼과 연결하여 외래키(foreign key)로 설정하였다.

두번째 인수 ondelete='CASCADE'는 question 테이블의 id에 해당하는 질문이 삭제되면 답변 또한 삭제되도록 설정하는 것이다.

 

question 속성의 relationship은 Question 모델을 참조하기 위한 것이다. 예를 들어 답변 모델 객체에서 질문 모델 객체의 제목을 참조하려면 answer.question.subject로 사용할 수 있다. 이렇게 하려면 속성을 추가할 때 db.Column이 아닌 db.relationship을 사용해야 한다.

 

db.relation의 첫번째 인수는 "참조할 모델명"이고, 두번째 인수는 backref로 "역참조 설정"이다.

역참조데이터베이스 테이블에서 Foreign Key(FK)에 의해 참조되고 있는 모델에서 참조하고 있는 모델을 호출하는 경우를 말한다. 예를 들어 한 질문에는 여러 개의 답변이 달릴 수 있는데, 역참조는 이 질문에 달린 답변을 참조할 수 있게 한다. 어떤 질문에 해당하는 객체가 a_question이라면 a_question.answer_set와 같은 코드로 해당 질문에 달린 답변을 참조할 수 있다.

 

만약 a_question.delete()로 질문 데이터를 삭제하면 해당 질문과 연관된 답변 데이터는 삭제되지 않고 답변 데이터의 question_id 칼럼만 빈 값으로 업데이트된다. 만약 파이썬 코드로 질문 데이터를 삭제할 때 연관된 답변 데이터가 모두 삭제되도록 하기 위해서는 db.backref 설정에 cascade='all, delete-orphan' 옵션을 추가해야한다.

question = db.relationship('Question', backref=db.backref('answer_set', cascade='all, delete-orphan'))

 

아래에서 추가적인 속성들을 참고할 수 있다.

https://docs.sqlalchemy.org/en/13/core/type_basics.html

 

 

Migrate를 이용해 테이블 자동 생성

1. __init__.py에 model.py를 추가

...
from . import models
...

2. 데이터베이스 변경을 위한 리비전(revision) 파일 생성

$ flask db migrate

 

위 명령을 수행하면 question과 answer 테이블이 추가된 것을 알 수 있고,

아래와 같이 데이터베이스 변경 작업을 위한 리비전 파일(ex. d24f1bbf4696_.py)이 생성된다.

 

3. 데이터베이스 갱신

$ flask db upgrade

위 명령으로 리비전 파일을 실행하면 모델 이름과 동일하게 데이터베이스에도 question, answer 테이블이 생성된다.

프로젝트 디렉터리에 SQLite 데이터베이스의 데이터 파일인 dabi.db 파일이 생성된 것을 확인할 수 있다.

이 파일은 DB Browser for SQLite를 설치(https://sqlitebrowser.org/dl)한 뒤 dabi.db 파일을 열면 테이블과 레코드들을 확인할 수 있다.

 

데이터 처리하기(CRUD)

먼저 Flask shell을 실행한다.

$ flask shell

 

1. 질문 데이터 삽입(CREATE)

>>> from models import Question, Answer                                                         
>>> from datetime import datetime
>>> q = Question(subject='Dabi가 무엇인가요?', content='Dabi에 대해서 알고 싶습니다.', create_date=dat
etime.now())

위에서 객체 q를 만들었다고 해서 데이터베이스에 저장되지는 않는다. 

데이터베이스에 저장하기 위해서는 아래 명령어로 SQLAlchemy의 db 객체를 사용해야 한다.

 

>>> from models import db
>>> db.session.add(q) 
>>> db.session.commit()

데이터를 삽입할 때는 add 함수를 사용한 다음 commit() 함수까지 실행해야 완료된다. INSERT 뿐만 아니라 UPDATE, DELETE 모두 db.session.commit()을 해주어야만 적용된다. 단, 커밋은 취소할 수 없다. 만약 커밋 이전에 수행한 작업을 취소하려면 db.session.rollback()을 입력하면 된다.

db.session은 데이터베이스와 연결된 세션을 의미한다.

q.id를 입력하여 데이터가 정상적으로 삽입되었는지 확인한다.

 

2. 데이터 조회(READ)

1) 데이터베이스에 저장된 모든 데이터 조회

>>> Question.query.all()

Question 뒤의 숫자 1, 2는 기본키인 id 속성값에 해당한다.

 

2) filter() 함수를 사용해 데이터 조회

>>> Question.query.filter(Question.id==2).all()
>>> Question.query.get(2)

filter() 함수는 인자로 전달한 조건에 맞는 데이터를 모두 반환한다.

get() 함수의 경우 리스트가 아닌 Question 객체가 1개만 반환된다.

 

3) like() 사용하여 데이터 조회

>>> Question.query.filter(Question.subject.like('%플라스크%')).all()

"플라스크"라는 문자열이 포함된 데이터를 조회한다.

 

  • Dabi%: "Dabi"로 시작하는 문자열
  • %Dabi: "Dabi"로 끝나는 문자열
  • %Dabi%: "Dabi"를 포함하는 문자열

like() 함수 대신 ilike() 함수를 이용하면 대소문자를 구분하지 않고 데이터를 조회할 수 있다.

 

3. 데이터 수정하기(UPDATE)

>>> q = Question.query.get(2)
>>> q
<Question 2>
>>> q.subject = 'Flask Model Question'
>>> db.session.commit()

id=2인 두번째 데이터를 조회한 뒤 subject 속성을 수정했다. 꼭 commit()을 해야 데이터 수정이 반영된다.

 

4. 데이터 삭제하기(DELETE)

>>> q = Question.query.get(1)
>>> db.session.delete(q)
>>> db.session.commit()

 

추가. 답변 데이터 생성 후 저장하기

>>> from datetime import datetime
>>> from models import *
>>> q = Question.query.get(2)
>>> a = Answer(question=q, content='네 자동으로 생성됩니다.', create_date=datetime.now())
>>> db.session.add(a)
>>> db.session.commit()

 

답변에 연결된 질문 찾기 vs 질문에 달린 답변 찾기

1. 답변에 연결된 질문 찾기

Answer 모델의 question 속성을 활용하면 연결된 질문을 조회할 수 있다.

>>> a.id
1
>>> a = Answer.query.get(1)
>>> a
<Answer 1>
>>> a.question
<Question 2>

 

이와 같이 Answer 모델에는 question 속성이 정의되어있어 쉽게 찾을 수 있지만,

Question에서 Answer로 연결된 데이터를 찾기 위해서는 역참조를 활용해야한다.

앞에서 Answer 모델의 question 속성에 역참조 설정(backref=db.backref('answer_set'))를 적용했으므로 이것을 활용하면 특정 질문에 대한 답변을 쉽게 가져올 수 있다.

 

>>> q.answer_set
[<Answer 1>]

 

 

 

 

 

 

 

참고: https://wikidocs.net/81045

 

 

 

 

 

728x90
반응형