Project/[약올림] Final Project

[초기 세팅 및 배포] DB Setting

HJChung 2021. 1. 22. 09:00

우리 프로젝트의 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

글마다 링크를 삽입해 두었습니다.