7. Django에 MySQL 연결하기 + Model 짜기

bruce1115·2023년 11월 29일
0

EduMax

목록 보기
8/12

Django 프로젝트 생성하기

일단 백엔드 서버를 만들기 위해서는 Django를 설치하여야 한다. Node.js를 이용하여 개발할 때는 package.json 파일을 이용하여 package들을 관리하고 npm을 이용해 설치와 삭제를 하였다. python을 이용하는 경우, 이러한 기능을 python 가상 환경을 통해 사용할 수 있다. 물론 당연히 python을 이용할 수 있도록 python 설치와 PATH 설정은 되어 있어야 하고, 아래 내용은 이 작업들이 완료된 상태에서 진행하는 내용이다.
먼저, 프로젝트들을 넣어 놓을 폴더 Web_projects를 하나 만들고, 그 안에 python 가상 환경을 설치할 폴더인 python_venvs를 만들어 주었다. 터미널을 통해 해당 폴더로 이동한 뒤 python -m venv Edumax 명령어를 입력하면 된다. 해당 명령어를 쓰면, 폴더 안에 Edumax라는 폴더가 생성되고 그 안에 가상 환경이 추가될 것이다.

가상 환경을 만들었으니, VS Code 상에서 기존 python 인터프리터 대신 해당 인터프리터를 사용하도록 설정해 주어야 한다.이를 위해 F1 키를 눌러서 python: 인터프리터 선택 > 인터프리터 경로 입력 > 찾기 를 차례대로 클릭하도록 한다. 그 다음 경로python_venvs/Edumax/Scripts/python.exe를 클릭하여 가상 환경의 인터프리터를 쓰도록 변경해 준다. 그 뒤 터미널을 열어 보면, (Edumax)가 터미널에 붙어 있는 것을 확인할 수 있다. 즉 가상 환경에 성공적으로 진입하였음을 확인할 수 있다.

이제 가상 환경의 준비까지 끝났으니, pip install django를 통해 django 최신 버전을 설치해 주면 된다. 그리고 그 뒤에 pip upgrade를 실행하라는 메시지가 뜨는데, python -m pip install --upgrade pip 명령어를 통해 pip upgrade도 진행해 주자.

이제 Django project만 생성해 주면 된다. Web_projects 안에 Edumax_back 폴더를 생성해 준 뒤, 해당 폴더 내에서 django-admin startproject config . 명령어를 통해 django project를 만들어 주자. 여기서 config는 프로젝트의 초기 파일이 들어있는 폴더 이름이고, .는 현재 디렉토리를 기준으로 프로젝트를 생성한다는 뜻이다.

해당 내용은 Django 공식 문서 튜토리얼에도 그대로 나와 있다.

MySQL 연결하기

이제 프로젝트를 만들었으니 DB를 연결할 차례이다. 여기서 Community 버전을 다운받았고, 나는 Windows를 쓰고 있으니 installer를 받아서 설치하였다. 버전이 8.2.0하고 8.0 버전이 있었는데, 나온 지 좀 된 8.0버전을 쓰는 것이 더 나을 것 같아서 8.0 버전으로 설치를 진행하였다.

설치 후 먼저 MySQL 연결을 위해 pip install mysqlclient를 통해 mysqlclient를 설치해 주자. 그리고 DB 서버에 새 schema edumaxdb도 생성해 주도록 한다.

이제 config/settings.py에 있는 db 설정을 수정해 줄 차례이다. 원래 settings.py의 db 설정 부분은 다음과 같이 구성되어 있다.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

기본 db는 sqlite3으로 되어 있지만, 이제 mysql과 연동할 것이기에 다음과 같이 바꾸어 주었다.

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "edumaxdb",
        "USER": "root",
        "PASSWORD": "DB 설치 시 설정한 비밀번호",
        "HOST": "localhost",
        "PORT": "3306",
    }
}

Engine을 mysql로 바꾸고, Name에는 아까 정한 이름인 edumaxdb를 썼다. User는 기본으로 있는 root를 쓰고, Password 역시 설정한 것을 그대로 적으면 된다. 현재는 localhost에서 동작하기 때문에 host는 localhost로 하고, port 번호는 처음 설정되어 있는 기본 port 번호인 3306을 적었다.

여기까지 완료했다면 python manage.py migrate 명령어를 입력하여 기본적인 django app들에 쓰일 table을 생성한 뒤 python manage.py runserver 명령어를 통해 서버를 가동하자.

위와 같이 edumaxdb 내에 table들이 생긴 것을 확인할 수 있다. 이를 확인하고 서버도 문제없이 작동한다면, DB 연결이 완료된 것이다.

생각해보니 비밀번호는 개인 정보이기 때문에 Node.js에서 dotenv를 사용한 것처럼 python에서도 관리해야겠다는 생각이 들었고, 찾아보니 python-dotenv 라이브러리가 존재했다.pip install python-dotenv를 통해 라이브러리를 설치하고 settings.py에 dotenv를 사용하기 위해 아래 코드를 추가하였다.

import os
from dotenv import load_dotenv

루트 디렉토리에 .env 파일을 만들고 DB_PASSWORD라는 이름으로 비밀번호를 key에 저장하였다. 이후 DATABASE 설정 부분을 다음과 같이 변경하였다.

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "edumaxdb",
        "USER": "root",
        "PASSWORD": os.getenv("DB_PASSWORD"),
        "HOST": "localhost",
        "PORT": "3306",
    }
}

이렇게 하면 잘 되어야 정상인데, 비밀번호가 제대로 적용되지 않은 것인지

django.db.utils.OperationalError: (1045, "Access denied for user 'root'@'localhost' (using password: NO)")

이라는 에러가 떴다. 알고보니 앞에 코드 load_dotenv(verbose=True)를 추가하는 것을 잊어서 생긴 오류였다. verbose=true는 에러 메시지 출력을 위한 부분이다. 이러한 사소한 실수 덕에 시간이 끌리는 일이 자주 있었다. 꼭 잊지 않고 제대로 코드를 작성했는지 확인하는 습관을 들여야겠다...

ERD에 맞춰 Model 짜기

하나의 프로젝트에는 여러 가지 기능이 있을 수 있고, 이러한 기능들을 처리하기 위해 Django에서는 app이라는 단위를 지원하고 있다. 지금까지 구성한 서비스는 사실 게시판이라는 하나의 기능이고, 따라서 이를 community라는 하나의 app으로 묶을 것이다.django-admin startapp community 명령어를 통해 Django App을 생성하였다.

왼쪽에 community 라는 새로은 디렉터리가 생긴 것을 확인할 수 있다. 이 안에 admin, apps, models와 같은 다양한 파일들이 있다. Django app의 구조는 DB를 나타내는 Model, 백엔드 서버에서 처리하는 동작을 나타내는 함수인 View, 사용자에게 보여 주는 Template의 세 단계로 되어 있는데, 이 중 Model을 수정하기 위해서는 models.py를 수정하면 된다.
전역 후 처음 웹에 대해 공부했을 때 사용한 Framework가 Django였기 때문에 이러한 구조에는 이미 익숙한 상태였고 어느 정도 수월하게 코드를 짤 수 있었다. 먼저 전에 설계했던 ERD를 보자.

여기서 바뀐 것은 alarm이 comment가 등록되었을 때 날아오기 때문에 post_id 정보는 굳이 필요하지 않아 해당 field를 삭제하기로 한 것과 category depth가 4단계까지 필요하다는 것 외에는 없었다. 일단 게시글을 나타내는 post table에 해당하는 model Post를 만들어 보자.

class Post(models.Model):
    class Categories(models.TextChoices):
        FREE = "FR"
        NOTICE = "NO"
        KOREAN_QUESTION = "KQ"
        ENG_QUESTION = "EQ"
        MATH_QUESTION = "MQ"
        KOREAN_DATA = "KD"
        ENG_DATA = "ED"
        MATH_DATA = "MD"

    title = models.CharField()
    content = models.TextField()
    created_at = models.DateTimeField()
    category = models.CharField(choices=Categories.choices, default=Categories.FREE)
    author = models.ForeignKey(User, on_delete=models.CASCADE)

title과 content, created_at field는 각각 문자열인 Charfield와 긴 문자열인 Textfield, 시간을 나타내는 DateTimeField가 이미 django에 정의되어 있어서 사용하면 되었다. 그리고 원래 ERD에서 user foreignkey였던 user_id는 author라는 이름으로 django 기본 user model을 가져와서 연결하였다.(이 User model은 다음 글에서 재정의할 계획이다.) Foreignkey의 on_delete=models.CASCADE는 연결된 데이터가 삭제될 경우 이 데이터 역시 따라서 삭제된다는 뜻이다. 즉 User가 사라질 경우, 그 User의 post도 같이 자동으로 사라지도록 하는 옵션이다.
전 프로젝트에서 mongoose를 사용할 때는 category와 같이 경우의 수가 제한된 문자열을 사용하는 field의 경우 바로 enum type을 이용하여 경우의 수를 정의하면 되었는데, django에는 enumfield라는 field는 존재하지 않아서 당황하였다.
Stackoverflow 게시글Django 공식 문서를 참조한 결과, 모델 내에 models.TextChoices를 상속받는 class를 만들어서 선택지를 정의해 주고 해당 class의 choices method를 이용하여 선택지를 정의하면 된다는 사실을 새로 알게 되었다. 이와 비슷한 방식으로 User를 제외한 나머지 table들도 정의해 보았다.

from django.db import models, IntegrityError
from django.contrib.auth.models import User


class Post(models.Model):
    class Categories(models.TextChoices):
        FREE = "FR"
        NOTICE = "NO"
        KOREAN_QUESTION = "KQ"
        ENG_QUESTION = "EQ"
        MATH_QUESTION = "MQ"
        KOREAN_DATA = "KD"
        ENG_DATA = "ED"
        MATH_DATA = "MD"

    title = models.CharField()
    content = models.TextField()
    created_at = models.DateTimeField()
    category = models.CharField(choices=Categories.choices, default=Categories.FREE)
    author = models.ForeignKey(User, on_delete=models.CASCADE)


class Comment(models.Model):
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    parent_comment = models.ForeignKey("self", on_delete=models.CASCADE)
    created_at = models.DateTimeField()


class Alarm(models.Model):
    receive_user = models.ForeignKey(User, on_delete=models.CASCADE)
    comment = models.ForeignKey(Comment, on_delete=models.CASCADE)
    created_at = models.DateTimeField()


class Like(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, null=True, blank=True)
    comment = models.ForeignKey(
        Comment, on_delete=models.CASCADE, null=True, blank=True
    )
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField()

    def save(self, *args, **kwargs):
        # 객체 저장 전에 post나 comment 둘 중 하나만 값이 있는지 체크
        if not (self.post is None) ^ (self.comment is None):
            raise IntegrityError("post나 comment 중 하나에만 like를 추가할 수 있습니다.")
        super().save(*args, **kwargs)


class Lecture(models.Model):
    youtube_id = models.CharField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    class Category_depth1(models.TextChoices):
        KOREAN = "KO"
        ENGLISH = "EN"
        MATH = "MA"

    class Category_depth2(models.TextChoices):
        SCHOOL_TEST = "SC"
        SAT = "SA"
        GRAMMAR = "GR"

    category_d1 = models.CharField(choices=Category_depth1.choices)
    category_d2 = models.CharField(
        choices=Category_depth2.choices, null=True, blank=True
    )

앞으로 여기서 변경될 여지가 있는 모델이지만, 일단 초기 버전을 작성하였다. Like의 경우 post나 comment 둘 중 하나에만 연결되어야 하기 때문에 기존의 save method를 overriding해서 둘 중 하나만 값이 있는지 체크한 뒤 그렇지 않다면 IntegrityError를 반환하도록 하였다.

마지막으로 migrate하기 전에 settings.py에 APP을 등록하는 것을 잊지 말자.

INSTALLED_APPS = [
    "community.apps.CommunityConfig",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

첫 번째에 community app을 추가해 주었다. 이후 makemigrations와 migrate를 순서대로 진행하면, db에 해당 table이 들어가 있는 것을 확인할 수 있다.

다음 목표

아직 User model은 따로 손을 대지 않았는데, 그 이유는 이미 기본적으로 제공된 User model이 있기 때문이다. 이제 그 User model을 수정하여 우리의 목적에 맞도록 바꿔 볼 예정이다.

profile
백엔드 개발자 꿈나무

0개의 댓글