우리 프로젝트의 DB스키마는 위의 그림과 같다. (※ 현재 2020/12/13일, 프로젝트를 진행하면서 DB구조가 약간 수정되었다. 수정된 DB 스키마는 아래 <더보기> 를 클릭하면 볼 수 있다. )
1. 각 테이블 설명
- schedules_date: 각 알람 하나하나의 정보를 담고있는 테이블
- scedules_common: 한 주기를 가지고 있는 알람 정보 테이블
- users: 사용자 정보 테이블 (login 필드는 'social'이면 소셜회원가입을 한 것, 'basic'은 일반 회원가입으로 가입한 것을가 다 나타낸다.)
- medicines: 약 정보가 담긴 테이블
2. Relationship
- user 1 - schedule_date n (schedules_udate)
- user 1 - schedule_common n (schedules_ucommon)
- schedule_common 1 - schedule_date n(schedules_codate)
- schedule_common n - medicine n
- user n - medicine n
3. Flask-SQLAlchemy를 이용한 DB 세팅
서버를 python flask프레임워크를 이용하여 구현하기 때문에 데이터베이스는 mysql, orm은 Flask-SQLAlchemy를 이용하여 DB세팅을 하였다.
Flask-SQLAlchemy의 기본적인 사용방법은14. Python ORM - Flask-SQLAlchemy에 정리해두었다.
1) 폴더 구성 및 환경 설정
- app/config.py
환경 설정 값들은 공개되지 않도록 처리 할 것이며, SQLALCHEMY_TRACK_MODIFICATIONS의 경우에는 추가적인 메모리를 필요로 하므로 False로 꺼둘 것이다.configuration에 대한 정보는 공식 홈페이지에 나와있다.
또한 연결할 데이터베이스를 URI형식으로 작성하는‘SQLALCHEMY_DATABASE_URI’ 작성시에 많이 헤맸는데, 이것에 관한 내용 역시 공식 홈페이지의 connection URI Format에 잘 나와있다.
예를 들어 mysql을 사용하는 경우는,app.config\[‘SQLALCHEMY_DATABASE_URI’] = ‘mysql://:@:/’이런 형식으로 작성하면 된다.
단 배포환경(Production Config)는 배포한 DB에 맞게 작성해줘야한다.
import os
# uncomment the line below for postgres database url from environment variable
# postgres_local_base = os.environ['DATABASE_URL']
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY = os.getenv('SECRET_KEY', 'SECRET')
DEBUG = False
class DevelopmentConfig(Config):
# uncomment the line below to use postgres
# SQLALCHEMY_DATABASE_URI = postgres_local_base
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'mysql://<id>:<password>@<server url host>:<server url port>/<db name>'
SQLALCHEMY_TRACK_MODIFICATIONS = False
class TestingConfig(Config):
DEBUG = True
TESTING = True
SQLALCHEMY_DATABASE_URI = 'mysql://<id>:<password>@<server url host>:<server url port>/<db name>'
PRESERVE_CONTEXT_ON_EXCEPTION = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
class ProductionConfig(Config):
DEBUG = False
# uncomment the line below to use postgres
SQLALCHEMY_DATABASE_URI = 'mysql://<배포한 DB의 id>:<배포한 DB의 pw>@<배포한 server url host>/<배포한 DB name>'
config_by_name = dict(
dev=DevelopmentConfig,
test=TestingConfig,
prod=ProductionConfig
)
key = Config.SECRET_KEY
2) 폴더 구성
구현해야할 테이블이 4개로 많은 만큼 각 테이블마다 폴더를 나누어서 정의하였다.
├── app
│ ├── __init__.py
│ ├── main
│ │ ├── model
│ │ │ └── __init__.py
│ │ │ └── users.py
│ │ │ └── schedules_date.py
│ │ │ └── schedules_common.py
│ │ │ └── medicines.py
│ │ ├── __init__.py
│ │ ├── config.py
│ └── migrations
│ └── manage.py
└── .gitignore
3) 테이블 정의
users 테이블을 대표로 정리해보자. users 테이블에서 init의 역할, 1:n관계정의 , n:n관계 정의가 모두 들어가기때문에 이 테이블을 구성하는 것만 확실히 알 수 있다면 나머지 테이블을 정의하는데는 크게 어려움이 없기 때문이다.
관계정의 없이 우선 필드만 다 정의해주면 아래와 같이 된다.
class Users(db.Model):
""" User Model for storing user related details """
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
full_name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(255), unique=True, nullable=False)
password = db.Column(db.String(100), nullable=False)
mobile = db.Column(db.String(100), nullable=False)
login = db.Column(db.String(100), nullable=False)
우리는 bcrypt암호화를 시켜주기로 하였는데, 사용자를 추가해야할 필요가 있을 때마다 개발자가 비밀번호를 암호화해서 저장하는 로직을 넣어줄 수도 있지만 이 경우에는 사람이 하는 일이다 보니 실수하여 빼먹을 가능성이 높다. 따라서 암호화하는 과정은 객체를 생성할 때 해주는 것이 좋기때문에 `__init__`을 하는 과정에서 암호화를 할 수 있도록 하였다. (개인을 식별할 수 있는 비밀번호, 전화변호를 암호화하였다. 그런데 생각해보니 이메일도 개인식별정보에 해당하기 때문에 이 역시도 암호화를 해주어야겠다고 생각이 든다..)
def __init__(self, full_name, email, password, mobile, login):
self.full_name = full_name
self.email = email
self.password = flask_bcrypt.generate_password_hash(password)
self.mobile = flask_bcrypt.generate_password_hash(mobile)
self.login = login
4) 관계 정의하기
users 테이블은 3가지 테이블과 연관되어있으며
schedule_date, schedule_common과는 1:n관계,
medicine 과는 n:n관계이다.
user 1 - schedule_date n
user 1 - schedule_common n
user n - medicine n
① Creating One-To-Many Relationships in Flask-SQLAlchemy
1:n관계는 flask-sqlalchemy를 공부하면서 정리한 14. Python ORM - Flask-SQLAlchemy글에서 user와 post관계와 같다.
한 명의 사용자(users 레코드)는 여러 개의 알람(schedules_date 레코드)를 설정할 수 있지만, 하나의 알람(schedules_date 레코드)는 한 명의 사용자(users 레코드)가 작성한 것이다. 라는 관계성을 부여하려면,
n에 해당하는 테이블의 user_id에 db_ForeignKey(참조할 테이블 명.필드명)
를 사용하여 외래키로 선언해준다.
그래서
app/main/models/schedules__date.py에 Schedules__date 클래스를 정의해주는 부분과
app/main/models/schedules__common.py에 Schedules__common 클래스를 정의해주는 부분에
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
해당 코드를 추가해 주었다.
그 다음 1에 해당하는 users테이블에도 해당 관계를 명시해주어야한다. 이때는 db.relationship(연결된 객체명, backref=가상 필드명, loding relationship)
형태를 사용한다.
※loding relationship에 대한 내용은 SQLAlchemy Relationship Loading 완전정복에 자세히 설명되어 있다.
그래서 Users 클래스에 아래의 코드를 추가해주면
schedules_dates = db.relationship('Schedules_date', backref='schedules_udate', lazy=True)
schedules_commons = db.relationship('Schedules_common', backref='schedules_ucommon', lazy=True)
관계 형성이 완료 된 것이다.
이로써 사용자가 설정한 모든 각 알람에 대한 정보는users.schedules_dates, 전체 주기를 아우르는 알람에 대한 정보는 users.schedules_commons를 이용해 접근할 수 있으며 각 알람, 전체 주기를 아우르는 알람을 등록한 사용자는 schedules_date.schedules_udate, schedules_common.schedules_ucommon 을 이용해 접근할 수 있게 된다.
② Creating Many-To-Many Relationships in Flask-SQLAlchemy
n:n관계의 경우 연결시켜주는 역할만 하는 테이블을 따로 class로 정의해주는 경우와 , 아니면 class선언 없이 db.table로만 선언해주는 방식이 있는 듯 하다.
Many to many relationships in SQLAlchemy models (Flask)이 글에서는 association table을 model로 따로 선언해 주었다. 그런데 flask-sqlalchemy.palletsprojects.com/en/2.x/models/#many-to-many-relationships 공식문서에서는 model을 선언하지말고 table만 만들어주라고 한다.(For this helper table it is strongly recommanded to not use a model but an actual table)
그래서 우리도 table을 정의해주는 것으로 하였다.
한 명의 사용자(users 레코드)는 여러 개의 약(medicines 레코드)를 복용할 수 있고, 한 종류의 약(medicines 레코드)도 여러 명의 사용자(users 레코드)가 복용중일 수 있다. 라는 n:n관계성을 부여하기 위해
Users 모델 선언하는 곳에 테이블을 만들고,
users_medicines = db.Table('users_medicines',
db.Column('users_id', db.Integer, db.ForeignKey('users.id')),
db.Column('medicines_id', db.Integer, db.ForeignKey('medicines.id'))
)
db_relationship을 선언하여 관계성을 부여하였다.
mymedicines = db.relationship('Medicines', secondary=users_medicines, backref=db.backref('taker', lazy='dynamic'))
③ 최종코드
그래서 최종적으로 Users 모델을 정의하는 코드는
from .. import db, flask_bcrypt
from app.main.model.medicines import Medicines
users_medicines = db.Table('users_medicines',
db.Column('users_id', db.Integer, db.ForeignKey('users.id')),
db.Column('medicines_id', db.Integer, db.ForeignKey('medicines.id'))
)
class Users(db.Model):
""" User Model for storing user related details """
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
full_name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(255), unique=True, nullable=False)
password = db.Column(db.String(100), nullable=False)
mobile = db.Column(db.String(100), nullable=False)
login = db.Column(db.String(100), nullable=False)
mymedicines = db.relationship('Medicines', secondary=users_medicines, backref=db.backref('taker', lazy='dynamic'))
schedules_dates = db.relationship('Schedules_date', backref='schedules_udate', lazy=True)
schedules_commons = db.relationship('Schedules_common', backref='schedules_ucommon', lazy=True)
def __init__(self, full_name, email, password, mobile, login):
self.full_name = full_name
self.email = email
self.password = flask_bcrypt.generate_password_hash(password)
self.mobile = flask_bcrypt.generate_password_hash(mobile)
self.login = login
def __repr__(self):
return "<users '{}'>".format(self.full_name)
이렇게 된다.
나머지 모델들도 이러한 관계성 정의 방법에 맞춰서 모델 정의를 마쳤다.
5. 데이터 삽입
위의 모델 정의와 관계 정의해준 것이 정상적으로 모두 완료되었다면 데이터가 그에 맞게 잘 들어가야한다. 이를 확인해보자.
1) Inserting new records with one-to-many relationship in sqlalchemy
(reference:Flask sqlalchemy many-to-many insert data)
관계가 맺어진 테이블들에서 CRUD를 하기 위해선는 backref=가상 필드명
과 Foreign_key
의 적절한 사용이 매우 중요하다.
1:n관계를 가진 테이블간 데이터가 서로 연관되어 잘 inserte되는지를 확인하기 위해서는 1과 n에 해당하는 데이터 레코드를 backref=가상 필드명을 사이에 두고 append로 묶어주는 형태로 사용하면 된다.
schedules_date2 = schedules_date.Schedules_date(year=1112, month=12, date=27, time='12:12:12', check=True)
schedules_common2 = schedules_common.Schedules_common(title='출근전2', memo='약먹기2', startdate='4', enddate='10', cycle=7)
users2 = users.Users(full_name='test2', email='test@gmail.com2', password='testtest1', mobile='010-1234-56784', login='basic4')
users2.schedules_commons.append(schedules_common2)
users2.schedules_dates.append(schedules_date2)
schedules_common2.schedules_dates.append(schedules_date2)
db.session.add(users2)
db.session.add(schedules_common2)
db.session.add(schedules_date2)
db.session.commit()
2) Inserting new records with Many-To-Many relationship in sqlalchemy
앞에서는 users 테이블을 대표로 예시를 들어 정리해보았기 때문에 users_medicines 테이블만 설명하였지만 우리 서비스의 DB스키마를 보면 이 외에도 schedules_medicines라는 n:n관계를 위한 테이블이 하나 더 있다. 그래서 우리 서비스의 DB스키마에는
users_medicines = db.Table('users_medicines',
db.Column('users_id', db.Integer, db.ForeignKey('users.id')),
db.Column('medicines_id', db.Integer, db.ForeignKey('medicines.id'))
)
mymedicines = db.relationship('Medicines', secondary=users_medicines, backref=db.backref('taker', lazy='dynamic'))
이것과
schedules_medicines = db.Table('schedules_medicines',
db.Column('schedules_common_id', db.Integer, db.ForeignKey('schedules_common.id')),
db.Column('medicines_id', db.Integer, db.ForeignKey('medicines.id'))
)
timetotake = db.relationship('Schedules_common', secondary=schedules_medicines, backref=db.backref('ttt', lazy='dynamic'))
이것이 있다.
여기에 데이터를 insert할 때도, 위의 방법과 유사하게 backref로 설정해준 명명을 사용해서
medicines3 = medicines.Medicines(name='타이레놀', title= '두통', image_dir='fiel.jpg', effect='두통끝', capacity='1알', validity='1년', camera=False)
users3 = users.Users(full_name='test3', email='test@gmail.com3', password='testtest1', mobile='010-1234-56784', login='basic4')
db.session.add(users3)
db.session.add(medicines3)
medicines3.taker.append(users3)
db.session.commit()
이렇게 작성해 줄 수 있다.
여기까지 DB초기세팅을 마쳤다. 아직 익숙하지 않은 flask-sqlalchemy라 많은 시행착오를 하면서 진행하였지만, 앞으로 API를 구현하면서 flask-sqlalchemy쿼리문 작성에 대해서도 많이 배우고 점차 익숙해질거라 생각한다.
화이팅!!!
reference
글마다 링크를 삽입해 두었습니다.
'Project > [약올림] Final Project' 카테고리의 다른 글
[초기 세팅 및 배포] Heroku 서버, DB 배포 (0) | 2021.01.22 |
---|---|
[초기 세팅 및 배포] Flask-RESTX: MVC 폴더 구성 및 Flask REST API 구현하기 (0) | 2021.01.22 |
[프로젝트 기획 및 준비작업] 마일스톤 정하기, UX/UI 디자인, 컴포넌트 리스트업 (0) | 2021.01.22 |
[프로젝트 기획 및 준비작업] API를 좀 더 RESTful 하게 (1) | 2021.01.22 |
[프로젝트 기획 및 준비작업] 기능 플로우,DB schema (0) | 2021.01.22 |