DB 모델 관계1
지금까지 우리는 Django 프로젝트의 기본 구조와 데이터베이스의 기본 개념, 모델에 대해 학습했습니다. 우리가 설정한 모델은 메서드로 데이터베이스의 테이블을 읽고, 쓰고, 수정하고, 삭제 할 수 있습니다.
이렇게 생성된 모델은 서로 상호작용이 필요할 때도 있습니다. 예를 들어, 게시물이 삭제되면 그 게시물에 달린 댓글도 함께 삭제된다던지 등 모델간의 다양한 상호작용이 필요하게 됩니다. 이러한 상호작용을 위해 Django에서는 다양한 모델 관계를 지원합니다.
1. 데이터베이스 기초
1.1 데이터베이스 주요 용어
데이터베이스를 다루는 아주 간단한 용어 몇 가지만 소개하고 넘어가겠습니다. DB에 대한 과목은 Django와는 별도로 공부를 해야 합니다. 백엔드 개발자로 데이터베이스를 다루는 것은 필수적인 역량이기 때문입니다.
데이터베이스 관계도 |
---|
-
관계형 데이터베이스(Relational Database): 데이터를 표 형태의 구조(테이블)로 저장하고 관리하는 데이터베이스 시스템입니다. 각 테이블은 서로 관계를 가질 수 있어, 복잡한 데이터 구조를 효율적으로 표현하고 관리할 수 있습니다. 관계형 데이터베이스가 아닌 것도 있습니다. 문서 지향 데이터베이스, 그래프 데이터베이스, 키-값 데이터베이스 등이 있습니다.
-
엔티티(Entity): 엔티티는 데이터베이스에서 하나의 독립적인 객체나 개념을 나타냅니다. 예를 들어, 학생 정보를 담고 있는
테이블
이 하나의 엔티티가 됩니다. 이 테이블에는 학번, 이름, 주소, 전화번호 등의 정보가 포함되어 있습니다. -
기본키(Primary Key): 기본키는 각 행를 고유하게 식별하는 속성으로, 여기서는 2401, 2402 등의 학번이 각 학생을 구별하는 고유한 식별자 역할을 합니다.
-
외래키(Foreign Key): 외래키는 다른 테이블의 기본키를 참조하여, 두 테이블 간의 관계를 설정합니다. 예를 들어, 학생의 신청과목 정보가 학번을 통해 학생 정보와 연결됩니다. 참고로 자기 참조 관계도 가능합니다. Django에서도 지원합니다. 주로 댓글에 댓글 등을 구현할 때 사용합니다.
-
ERD(Entity-Relationship Diagram): 엔티티 간의 관계를 시각적으로 표현한 다이어그램입니다. ERD를 통해 데이터베이스의 구조와 엔티티 간의 관계를 한눈에 파악할 수 있습니다. Django 프로젝트를 시작하기 전 데이터베이스 구조를 ERD로 그리고 시작하는 것을 권합니다.
1.2 모델 관계 유형
Django에서는 주로 일대다(1:N)
, 다대다(N:M)
, 일대일(1:1)
세 가지 유형의 관계를 다룹니다.
일대다(1:N)
관계의 예로는 학교와 학생의 관계를 들 수 있습니다. 한 학교에는 여러 학생이 있지만 각 학생은 하나의 학교에만 속하는 것처럼, '일(1)' 쪽의 하나의 레코드가 '다(N)' 쪽의 여러 레코드와 연결될 수 있습니다. Django에서는 이를 ForeignKey
필드를 사용하여 구현합니다.
다대다(N:M)
관계는 학생과 수업의 관계로 설명할 수 있습니다. 한 학생이 여러 수업을 들을 수 있고 한 수업에 여러 학생이 참여할 수 있듯이, 양쪽 모델의 인스턴스가 서로 여러 개의 인스턴스와 연결될 수 있습니다. Django에서는 이러한 관계를 ManyToManyField
를 사용하여 구현합니다.
마지막으로 일대일(1:1)
관계는 사용자와 학번의 관계를 예로 들 수 있습니다. 각 사용자는 하나의 번호만을 가지며 학번은 하나의 사용자에만 속하는 것처럼, 한 모델의 각 인스턴스가 다른 모델의 인스턴스와 단 하나씩만 연결됩니다. Django에서는 이를 OneToOneField
를 사용하여 구현합니다.
일대다(1 : N) | 다대다(N : M) | 일대일(1 : 1) |
---|---|---|
ERD로는 아래와 같이 표현합니다.
일대다(1 : N) | 다대다(N : M) | 일대일(1 : 1) |
---|---|---|
여기서 주의할 점은 DB는 N
요즘은 ChatGPT, Cluade 등의 LLM 서비스를 이용하여 ERD를 그릴 수 있습니다. 위 차트는 https://dbdiagram.io/라는 서비스를 이용하여 그린 것입니다. 저는 주로 Cluade와 https://mermaid.live를 조합해 그립니다.
ERD를 그리는 법은 이 강의에서 진행하지는 않습니다. ERD를 클릭하며 구현하는 솔루션도 많아, 편하게 쓸 수 있는 솔루션을 찾아서 사용해주세요.
안에 들어가는 코드는 아래와 같습니다. 여기서 >
는 관계입니다. 이 관계를 <
또는 -
로 바꿔보세요.
- 일대다(
1:N
) 관계
Table School {
id int [pk]
name varchar
}
Table Student {
id int [pk]
name varchar
school_id int [ref: > School.id]
}
Table School {
id int [pk]
name varchar
}
Table Student {
id int [pk]
name varchar
school_id int [ref: > School.id]
}
- 다대다(
N:M
) 관계
Table Student {
id int [pk]
name varchar
}
Table Course {
id int [pk]
name varchar
}
Table Enrollment {
id int [pk]
student_id int [ref: > Student.id]
course_id int [ref: > Course.id]
}
Table Student {
id int [pk]
name varchar
}
Table Course {
id int [pk]
name varchar
}
Table Enrollment {
id int [pk]
student_id int [ref: > Student.id]
course_id int [ref: > Course.id]
}
- 일대일(
1:1
) 관계
Table User {
id int [pk]
username varchar
}
Table StudentID {
id int [pk]
number varchar [unique]
user_id int [unique, ref: - User.id]
}
Table User {
id int [pk]
username varchar
}
Table StudentID {
id int [pk]
number varchar [unique]
user_id int [unique, ref: - User.id]
}
ERD를 그리는 서비스는 다양한 서비스가 있습니다. DB 다이어그램 외에 사용할 수 있는 서비스를 추천순으로 나열하면 아래와 같습니다.
- Mermaid + GPT
- VSC에 ERD Editor 플러그인 + 코파일럿
- https://www.lucidchart.com/pages/
- https://www.draw.io/
- https://www.visual-paradigm.com/
- https://www.smartdraw.com/
2. 개발 환경 설정
아래 코드를 복사해서, 작업할 폴더의 터미널에 shift + insert
합니다.
mkdir 03_4_model_relationships
cd 03_4_model_relationships
python -m venv venv
.\venv\Scripts\activate
pip install django
pip install pillow
django-admin startproject config .
python manage.py migrate
python manage.py startapp blog
mkdir 03_4_model_relationships
cd 03_4_model_relationships
python -m venv venv
.\venv\Scripts\activate
pip install django
pip install pillow
django-admin startproject config .
python manage.py migrate
python manage.py startapp blog
03_4_model_relationships
폴더를 생성하고, 가상환경 설치, django
,pillow
라이브러리 설치, blog앱 생성까지 완료되었습니다.
GitHub을 사용하시는 분들은 .gitingore 파일을 생성하고, venv/
폴더를 추가해주세요. https://www.toptal.com/developers/gitignore 서비스에서 보다 쉽게 .gitignore
파일을 생성할 수 있습니다.
3. settings.py 수정
이제는 settings.py
를 한번에 수정해 봅시다.
# config > settings.py
ALLOWED_HOSTS = ['*']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
]
LANGUAGE_CODE = "ko-kr"
TIME_ZONE = "Asia/Seoul"
STATIC_URL = "static/"
STATICFILES_DIRS = [
BASE_DIR / "static",
]
MEDIA_ROOT = BASE_DIR / "media"
MEDIA_URL = "/media/"
# config > settings.py
ALLOWED_HOSTS = ['*']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
]
LANGUAGE_CODE = "ko-kr"
TIME_ZONE = "Asia/Seoul"
STATIC_URL = "static/"
STATICFILES_DIRS = [
BASE_DIR / "static",
]
MEDIA_ROOT = BASE_DIR / "media"
MEDIA_URL = "/media/"
장고에서 static과 media를 사용할 수 있도록, 폴더를 생성하도록 하겠습니다. 생성을 하지 않더라도 에러는 나지 않지만 폴더를 생성하지 않으면 경고를 띄웁니다.
mkdir static
mkdir media
mkdir static
mkdir media
4. model.py 수정
blog
앱의 models.py
파일을 수정하여 Post
모델을 정의합니다. 이 모델은 블로그 게시물의 제목, 내용, 대표 이미지, 첨부 파일, 생성 시간, 수정 시간 등의 필드를 포함합니다. 처음에는 기본 모델만 만들고 다음 챕터에서 관계를 만들도록 하겠습니다.
# blog > models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
head_image = models.ImageField(
upload_to='blog/images/%Y/%m/%d/', blank=True)
file_upload = models.FileField(
upload_to='blog/files/%Y/%m/%d/', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)
def __str__(self):
return self.title
# blog > models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
head_image = models.ImageField(
upload_to='blog/images/%Y/%m/%d/', blank=True)
file_upload = models.FileField(
upload_to='blog/files/%Y/%m/%d/', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)
def __str__(self):
return self.title
이때 대표 이미지
와 첨부 파일
은 각각 blog/images/연/월/일/
과 blog/files/연/월/일/
형식의 폴더에 저장됩니다. 한 폴더 내에 너무 많은 파일들이 있으면, 특정 파일을 찾기 어려워지고 시스템 성능에도 악영향을 미칠 수 있습니다. 그것을 방지하기 위해 자세한 경로를 사용합니다. 좀 더 실력이 쌓이게 되면 함수 형태로 경로를 지정할 수 있습니다.
5. 관리자 페이지 수정 및 슈퍼유저 생성
blog/admin.py
파일을 수정하여 Post
모델을 Django 관리자 페이지에 등록합니다.
#blog > admin.py
from django.contrib import admin
from .models import Post
admin.site.register(Post)
#blog > admin.py
from django.contrib import admin
from .models import Post
admin.site.register(Post)
데이터베이스에 변경사항을 적용하고 슈퍼유저를 생성합니다.
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
6. url.py 수정
urls.py
파일을 수정하여 블로그 앱으로 요청을 전달합니다. 특히 config 폴더의 urls.py
에는 static 파일을 제공하기 위한 코드를 추가합니다.
- config > urls.py
#config > urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
#config > urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
- blog > urls.py
#blog > urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.blog_list, name='blog_list'),
path('<int:pk>/', views.blog_detail, name='blog_detail'),
]
#blog > urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.blog_list, name='blog_list'),
path('<int:pk>/', views.blog_detail, name='blog_detail'),
]
7. view.py 수정
views.py
파일을 수정하여 블로그 게시물 목록과 상세 페이지를 보여주는 뷰를 작성합니다. 기본기능만 있습니다.
- blog > view.py
#blog > view.py
from django.shortcuts import render
from .models import Post
def blog_list(request):
posts = Post.objects.all()
return render(request, 'blog/blog_list.html', {'posts':posts})
def blog_detail(request, pk):
post = Post.objects.get(pk=pk)
return render(request, "blog/blog_detail.html", {"post": post})
#blog > view.py
from django.shortcuts import render
from .models import Post
def blog_list(request):
posts = Post.objects.all()
return render(request, 'blog/blog_list.html', {'posts':posts})
def blog_detail(request, pk):
post = Post.objects.get(pk=pk)
return render(request, "blog/blog_detail.html", {"post": post})
8. templates 수정
📁03_4_model_relationships/
┣━ 📁blog/
┃ ┗ 📁templates/
┃ ┗ 📁blog/
┃ ┣━ 📄blog_list.html
┃ ┗━ 📄blog_detail.html
┣━ 📁config/
┣━ 📁media/
┗━ 📁static/
📁03_4_model_relationships/
┣━ 📁blog/
┃ ┗ 📁templates/
┃ ┗ 📁blog/
┃ ┣━ 📄blog_list.html
┃ ┗━ 📄blog_detail.html
┣━ 📁config/
┣━ 📁media/
┗━ 📁static/
blog 앱 안에 template
폴더를 만들고, blog_list.html
파일과 blog_detail.html
파일을 빈 상태로 만들어주세요.
9. 서버 실행 및 테스트
python manage.py runserver
python manage.py runserver
관리자 페이지(http://127.0.0.1:8000/admin/
)에 접속해서 블로그 포스트 3개를 만듭니다.