TIL

내일배움캠프 본캠프 34일차 - DJANGO

수현조 2025. 1. 13. 21:44

1. Django 앱 생성

  • 프로젝트 생성:
    django-admin startproject myproject
    cd myproject
    
  • 앱 생성: blog와 accounts 앱을 생성.
    python manage.py startapp blog
    python manage.py startapp accounts
    

2. settings.py 설정

  1. 앱 등록: INSTALLED_APPS에 blog와 accounts 추가.
  2. INSTALLED_APPS = [ ..., 'blog', 'accounts', ]
  3. AUTH_USER_MODEL 설정: 커스텀 사용자 모델을 사용할 경우 설정.
  4. AUTH_USER_MODEL = 'accounts.User'

3. 모델 설정

accounts/models.py에서 사용자 모델 정의

from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    bio = models.TextField(blank=True)  # 사용자 소개

blog/models.py에서 게시글 모델 정의

from django.db import models
from accounts.models import User

class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

4. 마이그레이션

  • 데이터베이스 반영
  • 마이그레이션 결과 확인: 데이터베이스 테이블 생성 여부를 확인.

5. 슈퍼유저 생성

관리자 계정을 생성하여 Django Admin에 접근.

python manage.py createsuperuser

6. blog/urls.py 설정

blog 앱의 URL을 정의.

from django.urls import path
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
]

프로젝트의 urls.py에서 blog/urls.py 포함

from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('blog.urls')),  # 블로그 앱의 URL 연결
]

7. blog/views.py 설정

뷰 함수 정의

from django.shortcuts import render
from .models import Post

def post_list(request):
    posts = Post.objects.all()  # 모든 게시글 조회
    context = {
        'posts': posts  # 템플릿에 전달할 데이터
    }
    return render(request, 'blog/post_list.html', context)  # 렌더링

8. 템플릿 생성

blog/templates/blog/post_list.html 파일 생성.

<!DOCTYPE html>
<html>
<head>
    <title>게시글 목록</title>
</head>
<body>
    <h1>게시글 목록</h1>
    <ul>
        {% for post in posts %}
        <li>{{ post.title }} - {{ post.author.username }}</li>
        {% endfor %}
    </ul>
</body>
</html>

9. blog/admin.py 등록

from django.contrib import admin
from .models import Post

admin.site.register(Post)

필기 해석 및 정리 요약

  1. 프로젝트 및 앱 생성: blog, accounts 앱 생성.
  2. 모델 설정: 사용자(User)와 게시글(Post) 모델 정의.
  3. 마이그레이션: 데이터베이스에 반영.
  4. 슈퍼유저 생성: Django Admin에서 데이터 관리.
  5. URL 설정: blog/urls.py와 views.py에서 URL과 뷰 연결.
  6. 뷰 함수와 템플릿: post_list 뷰와 HTML 템플릿 작성.
  7. Admin 등록: 게시글 모델을 Django Admin에 등록.

 

[오늘 학습반에서 한 것]

모델 설정

[accounts/models.py]

from django.conf import settings  # 프로젝트 설정에서 사용자 모델 관련 정보를 가져오기 위해 사용
from django.contrib.auth.models import AbstractUser  # Django 기본 사용자 모델을 확장하기 위해 사용
from django.db import models  # Django ORM에서 모델을 정의하기 위한 모듈


# User 모델: Django 기본 사용자 모델을 확장하여 사용자 정보를 커스터마이징
class User(AbstractUser):  
    bio = models.TextField()  # 사용자의 간단한 소개 또는 프로필 정보를 저장


# Profile 모델: 사용자(User)와 1:1 관계를 가지는 추가 프로필 정보를 저장하는 모델
class Profile(models.Model):  
    user = models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)  
    # 사용자와 1:1 관계 설정, 사용자가 삭제되면 해당 프로필도 삭제됨
    address = models.CharField(max_length=50)  # 사용자의 주소 정보를 저장
    zipcode = models.CharField(max_length=6)  # 사용자의 우편번호 정보를 저장

    def __str__(self):
        return self.address  # Profile 객체를 문자열로 표현할 때 주소를 반환

코드 설명

  1. User 모델
    • Django의 기본 사용자 모델을 확장(AbstractUser)하여 새로운 필드(bio)를 추가.
    • bio: 사용자의 프로필 정보(예: 자기소개)를 저장.
  2. Profile 모델
    • User 모델과 1:1 관계를 가지며, 사용자와 관련된 추가 정보를 저장.
    • address: 사용자의 주소.
    • zipcode: 사용자의 우편번호.
    • OneToOneField: 사용자(User)와 Profile 간의 1:1 관계를 정의. 한 사용자당 하나의 프로필만 가질 수 있음.
      • on_delete=models.CASCADE: 사용자가 삭제되면 연결된 프로필도 함께 삭제.
  3. __str__ 메서드
    • Profile 객체를 문자열로 표현할 때 address 값을 반환.

추가 팁

  • 왜 사용자 모델을 확장하나요?
    • Django 기본 사용자 모델(AbstractUser)은 이름, 이메일, 비밀번호 등의 기본 필드만 제공.
    • 추가 필드가 필요할 경우 AbstractUser를 상속받아 확장하거나 프로필 모델처럼 별도의 모델을 생성하여 관리.
  • 프로필 모델의 사용 사례
    • 사용자의 세부 정보를 별도의 테이블로 관리하고 싶을 때.
    • 예: 전화번호, 직업, 취미 등 사용자별로 관리해야 할 필드가 많을 경우.

[blog/models.py]

from django.db import models  # Django의 모델 클래스 가져오기
from config import settings  # 프로젝트 설정에서 사용자 모델 정보 가져오기


# TimestampedModel: 생성 및 수정 시간을 자동으로 관리하는 추상 모델
class TimestampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)  # 객체 생성 시 자동으로 현재 시간을 저장
    updated_at = models.DateTimeField(auto_now=True)      # 객체 저장 시마다 자동으로 현재 시간을 갱신

    class Meta:
        abstract = True  # 데이터베이스에 테이블로 생성되지 않는 추상 클래스 지정


# Post 모델: 게시글 정보 관리
class Post(TimestampedModel):  # TimestampedModel을 상속받아 created_at, updated_at 필드를 포함
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)  # 작성자 정보 (사용자 모델과 외래 키 관계)
    message = models.TextField()  # 게시글 내용을 저장
    tag_set = models.ManyToManyField('Tag', blank=True)  # 태그와 다대다 관계, 태그는 선택적으로 추가 가능

    def __str__(self):
        return self.message  # Post 객체를 문자열로 표현할 때 게시글 내용을 반환


# Tag 모델: 태그 정보 관리
class Tag(TimestampedModel):  # TimestampedModel을 상속받아 created_at, updated_at 필드를 포함
    name = models.CharField(max_length=44)  # 태그 이름

    def __str__(self):
        return self.name  # Tag 객체를 문자열로 표현할 때 태그 이름을 반환

코드 설명

  1. TimestampedModel (추상 모델)
    • 역할: created_at과 updated_at 필드를 제공하여 생성 및 수정 시간을 자동으로 관리.
    • 추상 모델: abstract = True를 설정해 데이터베이스에 독립적인 테이블로 생성되지 않음. 이를 상속받는 모델이 실제 테이블을 생성할 때 이 필드를 포함하게 됨.
  2. Post (게시글 모델)
    • 게시글 정보를 저장하는 모델로, 작성자와 내용(message)를 포함.
    • 작성 시간과 수정 시간은 TimestampedModel에서 상속받아 자동 관리.
    • tag_set: 다대다 관계를 통해 여러 태그를 연결.
  3. Tag (태그 모델)
    • 게시글에 연결될 태그 정보를 저장하는 모델.
    • 태그 이름을 name 필드에 저장.
    • 작성 시간과 수정 시간은 TimestampedModel에서 상속받아 자동 관리.

추가 설명

  • 재사용성 증가: TimestampedModel을 통해 여러 모델에서 생성 시간과 수정 시간을 자동으로 관리할 수 있습니다.
  • 추상 모델 활용: 추상 모델을 사용하면 중복 코드를 줄이고, 데이터베이스 구조를 간결하게 유지할 수 있습니다.

 

[blog/urls.py]

from django.urls import path  # URL 경로를 처리하기 위한 Django 함수 가져오기

from blog import views  # blog 앱의 뷰 모듈 가져오기

# URL 패턴 정의: 클라이언트 요청 URL을 처리할 뷰와 연결
urlpatterns = [
    # 루트 URL ('/')에 대한 요청을 views.post_list 뷰와 연결
    path(route='', view=views.post_list, name='post_list'),  
    # 'post_new' URL에 대한 요청을 views.post_new 뷰와 연결
    path(route='post_new', view=views.post_new),  
]

코드 설명

  1. path() 함수
    • Django의 URL 라우팅을 처리하는 함수로, 클라이언트의 요청 URL과 뷰를 연결합니다.
    • 주요 인자
      • route: URL 경로를 정의합니다.
      • view: 해당 URL 요청을 처리할 뷰 함수나 클래스.
      • name (선택 사항): URL에 이름을 부여하여 템플릿이나 리다이렉트에서 쉽게 참조 가능.
  2. URL 패턴
    • '': 루트 URL (/)로 접근하면 views.post_list 뷰가 호출됩니다.
    • 'post_new': /post_new URL로 접근하면 views.post_new 뷰가 호출됩니다.
  3. views 모듈
    • views.py에서 정의된 함수(post_list, post_new)가 요청을 처리합니다.
      def post_list(request):
          # 게시글 목록을 반환하는 로직
          pass
      
      def post_new(request):
          # 새로운 게시글을 작성하는 로직
          pass
      
  4. name 매개변수
    • URL에 이름(post_list)을 부여하면, 템플릿에서 이 이름을 사용해 URL을 참조할 수 있습니다.
      <a href="{% url 'post_list' %}">게시글 목록</a>
      

추가 개선

  • name 매개변수가 누락된 post_new 경로에 이름을 추가하는 것이 좋습니다.
  • 수정된 코드:
    urlpatterns = [
        path(route='', view=views.post_list, name='post_list'),
        path(route='post_new', view=views.post_new, name='post_new'),
    ]
    

[프로젝트 루트의 urls.py]

blog/urls.py를 프로젝트에 포함

from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('blog.urls')),
]

 

[blog/views.py]

from django.shortcuts import render, redirect  # 요청을 처리하고 응답을 반환하기 위한 도구들 가져오기

from blog.forms import PostForm  # Post 모델과 연관된 폼 클래스 가져오기
from blog.models import Post  # 데이터베이스에서 Post 모델을 가져오기

'''
1. view : urls 있는 주소에 매핑 확인 후 요청 처리 로직 
2. 데이터 처리: 데이터베이스 핸들링(읽고, 쓰고, 수정, 삭제 등등등)
3. 응답 반환(랜더링) 템플릿
'''

# 게시글 목록을 처리하는 뷰
def post_list(request):
    posts = Post.objects.all()  # 데이터베이스에서 모든 Post 객체를 조회
    context = {
        'posts': posts  # 템플릿에 전달할 데이터
    }
    return render(request=request, template_name='blog/post_list.html', context=context)  # 템플릿으로 렌더링하여 응답 반환


# 새로운 게시글을 작성하는 뷰
def post_new(request):
    '''
    POST: 사용자가 데이터를 던질 때
    GET: 사용자가 페이지를 열었을 때(데이터 안 던질 때)
    '''
    if request.method == 'POST':  # POST 요청일 경우
        form = PostForm(request.POST)  # 제출된 데이터를 기반으로 폼 객체 생성
        if form.is_valid():  # 폼 데이터가 유효한 경우
            print(request.user)  # 디버깅: 현재 로그인한 사용자 출력
            post = form.save(commit=False)  # 데이터베이스에 저장하기 전에 Post 객체 생성
            # request.user == 로그인한 사용자 id
            post.author = request.user  # 작성자를 현재 로그인한 사용자로 설정
            post.save()  # 데이터베이스에 저장
            return redirect('post_list')  # 게시글 목록 페이지로 리다이렉트

    else:  # GET 요청일 경우 (페이지를 처음 열었을 때)
        form = PostForm()  # 빈 폼 객체 생성
    return render(request=request, template_name='blog/post_new.html', context={'form': form})  # 템플릿으로 폼 데이터를 전달하여 렌더링

코드 설명

  1. post_list
    • 역할: 게시글 목록을 가져와 템플릿에 전달하고, HTML 페이지를 렌더링.
    • 주요 로직:
      • 데이터베이스에서 Post 모델의 모든 객체를 가져옵니다.
      • 가져온 데이터를 context에 저장하고, 이를 템플릿(blog/post_list.html)에 전달합니다.
  2. post_new
    • 역할: 새로운 게시글 작성 폼을 렌더링하거나, 제출된 데이터를 저장.
    • GET 요청: 사용자가 새로운 게시글 작성 페이지를 열 때 빈 폼을 제공.
    • POST 요청: 사용자가 데이터를 제출하면 유효성을 검사하고, 유효한 경우 데이터베이스에 저장.
    • 주요 로직:
      • form.is_valid()로 폼 데이터의 유효성을 검사.
      • 작성자는 현재 로그인한 사용자(request.user)로 설정.
      • 저장 후 게시글 목록 페이지로 리다이렉트.
  3. PostForm
    • 역할: Post 모델에 기반한 폼으로, 데이터를 제출받고 검증합니다.

추가 설명

  • render() 함수:
    • 템플릿에 데이터를 전달하고 렌더링된 HTML 페이지를 반환합니다.
    • template_name: 사용할 템플릿 파일 이름.
    • context: 템플릿에 전달할 데이터 사전.
  • redirect() 함수:
    • URL 이름(post_list)을 기반으로 리다이렉트 응답을 생성합니다.
  • form.save(commit=False):
    • 데이터를 데이터베이스에 저장하기 전에 객체를 반환합니다.
    • 이 객체를 수정한 뒤 수동으로 save()를 호출하여 저장합니다.

form.is_valid()의 동작 방식

Django의 form.is_valid() 메서드는 사용자가 제출한 데이터가 폼에서 정의한 조건(필드와 유효성 검사 기준)을 만족하는지 확인합니다. 이 과정에서 내부적으로 여러 단계를 거쳐 유효성을 검사합니다.


1. 주요 동작 흐름

  1. 데이터 바인딩 확인:
    • form.is_valid()가 호출되면, 폼에 데이터가 올바르게 바인딩되었는지 확인합니다.
    • 데이터는 폼 객체 생성 시 form = PostForm(request.POST)와 같이 전달된 request.POST 또는 request.GET에 담겨 있습니다.
  2. 필드별 유효성 검사 실행:
    • 폼에 정의된 각 필드의 유효성 검사를 실행합니다.
    • 유효성 검사는:
      • 필드의 속성 (예: required=True, max_length)에 대한 검증.
      • 사용자 정의 검증 (예: clean_<field_name> 메서드).
  3. cleaned_data 생성:
    • 데이터가 유효한 경우, 정리된 데이터가 form.cleaned_data 딕셔너리에 저장됩니다.
    • 유효하지 않은 경우, form.errors에 각 필드의 오류 메시지가 저장됩니다.
  4. 검증 결과 반환:
    • 모든 필드가 유효하면 True를 반환하고, 하나라도 유효하지 않으면 False를 반환합니다.

2. 내부 동작 상세

A. 데이터 유효성 검증

is_valid() 호출 시, 내부적으로 full_clean() 메서드를 실행합니다.

def is_valid(self):
    self.full_clean()
    return not self.errors

B. full_clean() 메서드

  1. 필드별 유효성 검증 실행:
    • 각 필드에 정의된 유효성 검사를 실행.
    • 예: CharField는 길이, 비어 있는지 여부 등을 확인.
  2. 사용자 정의 검증 실행:
    • 폼 또는 필드별로 정의된 추가 검증 메서드(예: clean_<field_name>)를 실행.
  3. 글로벌 유효성 검증 실행:
    • clean() 메서드에서 폼 레벨의 추가 검증을 실행.

3. 예제 코드로 이해하기

A. 기본 폼

from django import forms

class PostForm(forms.Form):
    title = forms.CharField(max_length=100, required=True)
    content = forms.CharField(widget=forms.Textarea, required=True)

    def clean_title(self):
        title = self.cleaned_data.get('title')
        if 'spam' in title.lower():
            raise forms.ValidationError('제목에 "spam"을 포함할 수 없습니다.')
        return title

B. 뷰에서 활용

def post_new(request):
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():  # 내부적으로 full_clean() 실행
            print(form.cleaned_data)  # {'title': '...', 'content': '...'}
        else:
            print(form.errors)  # 오류 메시지 출력

C. POST 데이터가 is_valid()를 통과하는 흐름

  1. title 필드와 content 필드가 비어 있거나 잘못된 데이터가 있는지 확인.
  2. clean_title 메서드가 실행되어 추가 검증 수행.
  3. 모든 검증을 통과하면 cleaned_data에 정리된 데이터 저장.

4. 오류 처리와 디버깅

  • form.errors:
    • 유효성 검사를 통과하지 못한 필드와 그에 따른 오류 메시지가 저장됩니다.
    • 예:
      {'title': ['제목에 "spam"을 포함할 수 없습니다.']}
      
  • form.add_error():
    • 뷰나 폼에서 오류를 수동으로 추가 가능.
    • 예:
      form.add_error('title', '제목이 너무 짧습니다.')
      

5. 추가 팁

  • 글로벌 검증:
    폼 수준에서의 검증이 필요하면 clean() 메서드를 활용하세요.
    def clean(self):
        cleaned_data = super().clean()
        title = cleaned_data.get('title')
        content = cleaned_data.get('content')
    
        if title and content and title == content:
            raise forms.ValidationError('제목과 내용이 같을 수 없습니다.')
        return cleaned_data
    

[blog/forms.py]

 

from django import forms  # Django의 폼 클래스를 사용하기 위한 모듈 가져오기

from blog.models import Post  # Post 모델을 가져와 폼과 연결

'''
Forms
1. HTML 폼을 자동 생성
2. 사용자가 입력한 데이터를 검증 -> 데이터베이스 상호 작용(반영, 저장, 쓰고, 수정하거나 등등)
   **데이터 유효성 검사**
'''

# Post 모델과 연결된 ModelForm 정의
class PostForm(forms.ModelForm):  # ModelForm은 특정 모델과 연결되어 데이터를 처리하는 폼을 생성
    class Meta:
        model = Post  # 연결할 모델 설정
        fields = ['message', 'tag_set']  # 사용자 입력을 받을 필드 지정

코드 설명

1. forms.ModelForm

  • Django의 ModelForm은 특정 모델과 연결된 폼을 생성하는 클래스입니다.
  • 개발자가 별도로 HTML 폼을 작성하지 않아도, 모델의 필드를 기반으로 자동 생성된 폼을 제공합니다.
  • 주요 역할:
    • 사용자 입력 데이터를 검증.
    • 검증된 데이터를 모델 인스턴스로 변환.
    • 모델의 데이터베이스 작업(저장, 업데이트 등)을 간편하게 처리.

2. Meta 클래스

  • Meta 클래스는 폼과 연결된 모델 및 속성을 정의합니다.
  • 속성:
    • model: 연결할 모델을 지정 (여기서는 Post 모델).
    • fields: 폼에 포함할 모델의 필드 목록을 지정.
      • 예: ['message', 'tag_set']은 게시글 내용과 태그를 입력받는 필드를 생성.
    • (선택) exclude: 특정 필드를 제외하려면 exclude를 사용.

3. 사용 예시

폼 인스턴스 생성

form = PostForm()  # 빈 폼 생성
form = PostForm(request.POST)  # 제출된 데이터를 바인딩한 폼 생성

데이터 유효성 검사 및 저장

if form.is_valid():  # 폼 데이터가 유효한지 검사
    post = form.save(commit=False)  # 데이터베이스에 저장하기 전에 객체를 반환
    post.author = request.user  # 작성자를 설정
    post.save()  # 데이터베이스에 저장

템플릿에 렌더링

<form method="post">
    {% csrf_token %}
    {{ form.as_p }}  <!-- 폼을 HTML 태그로 렌더링 -->
    <button type="submit">저장</button>
</form>

추가 기능

  • 유효성 검사 추가: clean_<field_name> 메서드 또는 clean 메서드를 활용하여 폼 데이터에 대한 추가 검증을 수행할 수 있습니다.
    def clean_message(self):
        message = self.cleaned_data.get('message')
        if len(message) < 10:
            raise forms.ValidationError('메시지는 최소 10자 이상이어야 합니다.')
        return message
    

장점

  1. HTML 폼 자동 생성: 개발자가 매번 HTML 태그를 작성하지 않아도 됨.
  2. 모델과의 통합: 모델의 필드와 데이터베이스 작업을 간편하게 처리 가능.
  3. 확장성: 기본 제공 기능 외에도 사용자 정의 검증 및 위젯 커스터마이징 가능.

 

 

[blog/models.py 수정] - 생성 시간, 수정 시간 관리

from django.db import models  # Django ORM에서 모델을 정의하기 위한 모듈
from config import settings  # 프로젝트 설정에서 사용자 모델 정보 가져오기


# TimestampedModel: 생성 및 수정 시간을 자동으로 관리하는 추상 모델
class TimestampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)  # 객체 생성 시 자동으로 현재 시간 저장
    updated_at = models.DateTimeField(auto_now=True)      # 객체 저장 시마다 자동으로 현재 시간 갱신

    class Meta:
        abstract = True  # 이 클래스는 데이터베이스에 테이블로 생성되지 않음


# Post 모델: 게시글 정보를 저장하는 모델
class Post(TimestampedModel):  # TimestampedModel을 상속받아 created_at, updated_at 필드를 포함
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    # 작성자 정보 (사용자 모델과 외래 키 관계)
    message = models.TextField()  # 게시글 내용을 저장
    tag_set = models.ManyToManyField('Tag', blank=True)  # 태그와 다대다 관계, 선택적으로 추가 가능

    def __str__(self):
        return self.message  # Post 객체를 문자열로 표현할 때 게시글 내용을 반환


# Tag 모델: 태그 정보를 저장하는 모델
class Tag(TimestampedModel):  # TimestampedModel을 상속받아 created_at, updated_at 필드를 포함
    name = models.CharField(max_length=44)  # 태그 이름을 저장하는 필드

    def __str__(self):
        return self.name  # Tag 객체를 문자열로 표현할 때 태그 이름을 반환

코드 설명

1. TimestampedModel

  • 역할: 생성 시간(created_at)과 수정 시간(updated_at)을 자동으로 관리.
  • 추상 모델: abstract = True 설정으로 이 모델은 독립적인 테이블로 생성되지 않으며, 상속받는 클래스에 필드가 포함됩니다.
  • 필드 설명:
    • created_at: 객체가 생성된 시간을 저장.
    • updated_at: 객체가 마지막으로 저장된 시간을 저장.

2. Post (게시글 모델)

  • 역할: 게시글 정보를 저장.
  • 필드 설명:
    • author: 게시글 작성자. Django의 사용자 모델(AUTH_USER_MODEL)과 연결된 외래 키.
    • message: 게시글 내용을 저장하는 텍스트 필드.
    • tag_set: 게시글에 연결된 태그. 다대다 관계로 설정되어 한 게시글에 여러 태그를 연결 가능.

3. Tag (태그 모델)

  • 역할: 태그 정보를 저장.
  • 필드 설명:
    • name: 태그 이름을 저장하는 필드.

4. __str__ 메서드

  • Post:
    • __str__ 메서드는 게시글 객체를 문자열로 표현할 때 message 내용을 반환.
  • Tag:
    • __str__ 메서드는 태그 객체를 문자열로 표현할 때 name 내용을 반환.

추가 팁

  1. 재사용 가능한 모델 구조:
    • TimestampedModel처럼 추상 모델을 사용하면, 여러 모델에서 반복적으로 사용되는 필드를 효율적으로 관리할 수 있습니다.
  2. on_delete 옵션:
    • on_delete=models.CASCADE는 작성자(author)가 삭제되면 해당 게시글도 함께 삭제됨을 의미합니다.
    • 다른 옵션도 존재:
      • SET_NULL: 외래 키 값을 NULL로 설정.
      • PROTECT: 삭제를 방지하고 오류 발생.
  3. ManyToManyField:
    • 다대다 관계를 정의하며, 관련 데이터는 자동으로 별도의 중간 테이블을 생성하여 관리됩니다.