2025.01.10 Django 기본 구조 이해하기: 모델, URL, 뷰, 폼, 템플릿

SMALL

코드 뜯어먹고 이해해보기

 

 

accounts

from django.conf import settings  # Django 설정을 가져오기 위한 임포트
from django.contrib.auth.models import AbstractUser  # Django 기본 User 모델을 상속하기 위한 임포트
from django.db import models  # 모델 정의를 위한 임포트

class User(AbstractUser):
    # AbstractUser를 상속받아 커스텀 User 모델을 정의
    # Django의 기본 User 모델이 제공하는 모든 필드와 기능을 그대로 사용하면서
    # 추가 필드를 정의할 수 있음
    
    # 사용자 자기소개를 저장하는 필드
    bio = models.TextField()

class Profile(models.Model):
    # User 모델과 1:1 관계를 형성하는 필드
    # to=settings.AUTH_USER_MODEL: 실제 User 모델을 동적으로 참조
    # on_delete=models.CASCADE: 연결된 User가 삭제되면 Profile도 함께 삭제
    user = models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    
    # 사용자의 주소를 저장하는 필드 (최대 50자)
    address = models.CharField(max_length=50)
    
    # 우편번호를 저장하는 필드 (최대 6자)
    zipcode = models.CharField(max_length=6)
    
    # Profile 모델의 문자열 표현을 정의하는 메서드
    # 관리자 페이지나 print() 실행 시 address가 표시됨
    def __str__(self):
        return self.address

코드를 통해 알 수 있는 것

    • User 모델은 Django의 AbstractUser를 상속받아 기본 인증 기능을 모두 사용하면서 bio 필드를 추가함
    • Profile 모델은 User 모델과 1:1 관계를 가짐 (한 사용자는 하나의 프로필만 가질 수 있음)
    • User가 삭제되면 연결된 Profile도 자동으로 삭제됨 (CASCADE)
    • Profile 모델은 사용자의 주소 정보(address, zipcode)를 저장함
    • 사용자의 추가 정보(주소, 우편번호)를 별도의 테이블로 관리할 수 있다

"관심사의 분리(Separation of Concerns)" 프로그래밍 원칙: User 모델은 로그인, 인증 같은 핵심 기능을 담당하고 Profile 모델은 부가적인 정보만 담당하도록 분리함으로써 코드 관리와 확장이 편리하도록 하는 것

 

 

 

blog

from django.db import models
from django.conf import settings

class Post(models.Model):
    # 게시글 작성자를 저장하는 필드. AUTH_USER_MODEL과 1:N 관계를 형성
    # CASCADE: 작성자가 삭제되면 해당 작성자의 게시글도 모두 삭제됨
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    
    # 게시글의 본문 내용을 저장하는 필드. 글자수 제한이 없는 텍스트 필드
    message = models.TextField()
    
    # 게시글 생성 시각을 자동으로 저장하는 필드
    # auto_now_add=True: 최초 저장(생성) 시에만 현재 시간 저장
    created_at = models.DateTimeField(auto_now_add=True)
    
    # 게시글 수정 시각을 자동으로 저장하는 필드
    # auto_now=True: 매번 save() 실행할 때마다 현재 시간 저장
    updated_at = models.DateTimeField(auto_now=True)
    
    # Tag 모델과 M:N(다대다) 관계를 형성하는 필드
    # blank=True: 태그를 반드시 입력하지 않아도 됨(선택적 입력)
    tag_set = models.ManyToManyField('Tag', blank=True)
    
    # 해당 모델의 문자열 표현을 정의하는 메서드
    # 관리자 페이지나 print() 실행 시 게시글의 message가 표시됨
    def __str__(self):
        return self.message

class Tag(models.Model):
    # 태그명을 저장하는 필드. 최대 44자까지 저장 가능
    name = models.CharField(max_length=44)
    
    # 해당 모델의 문자열 표현을 정의하는 메서드
    # 관리자 페이지나 print() 실행 시 태그의 name이 표시됨
    def __str__(self):
        return self.name

코드를 통해 알 수 있는 것

  1. User와 Post의 관계 (1:N)
    • 한 사용자(User)는 여러 게시글을 작성할 수 있음
    • 하나의 게시글은 단 한 명의 작성자를 가짐
    • 작성자가 삭제되면 그 사람의 게시글도 모두 삭제됨
  2. Post와 Tag의 관계 (M:N)
    • 하나의 게시글은 여러 태그를 가질 수 있음
    • 하나의 태그는 여러 게시글에 사용될 수 있음
    • 태그는 선택사항(blank=True)
  3. 시간 관리
    • created_at: 게시글 작성 시간 (자동 저장)
    • updated_at: 게시글 수정 시간 (수정할 때마다 자동 갱신)

 

 

 

from django.urls import path  # URL 패턴을 정의하기 위한 path 함수를 임포트
from blog import views  # blog 앱의 뷰들을 임포트

urlpatterns = [
    # 루트 URL 패턴 ('/')
    # route='': 아무것도 없는 경로(루트 URL)를 의미
    # view=views.post_list: 해당 URL에 매핑될 뷰 함수
    # name='post_list': URL 패턴의 이름. 템플릿이나 뷰에서 URL을 동적으로 생성할 때 사용
    path(route='', view=views.post_list, name='post_list'),
    
    # 새 게시글 작성 URL 패턴 ('/post_new/')
    # route='post_new/': post_new/ 경로를 의미
    # view=views.post_new: 해당 URL에 매핑될 뷰 함수
    # name 파라미터가 생략됨 (URL Reverse 사용 불가)
    path(route='post_new/', view=views.post_new,),
]

코드를 통해 알 수 있는 것

  • 웹사이트의 메인 페이지('/')에 접속하면 views.post_list 함수가 실행
  • '/post_new/' 경로로 접속하면 views.post_new 함수가 실행됨
  • 첫 번째 패턴은 name이 지정되어 있어 템플릿에서 {% url 'post_list' %}와 같이 사용할 수 있다

 

 

 

from django.shortcuts import render, redirect  # 템플릿 렌더링과 URL 리다이렉트를 위한 함수들
from blog.forms import PostForm  # 게시글 작성을 위한 폼 클래스 임포트  
from blog.models import Post  # Post 모델 임포트

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

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

def post_new(request):
   '''
   POST: 사용자가 데이터를 던질 때 (폼 제출)
   GET: 사용자가 페이지를 열었을 때(데이터 안 던질 때)
   '''
   if request.method == 'POST':  # POST 요청 (폼 제출)시
       # 제출된 데이터로 PostForm 인스턴스 생성
       form = PostForm(request.POST)
       
       if form.is_valid():  # 폼 유효성 검사
           # commit=False: DB에 바로 저장하지 않고 객체만 생성
           post = form.save(commit=False)
           
           # request.user: 현재 로그인한 사용자 객체
           post.author = request.user  # 글 작성자를 현재 로그인한 사용자로 지정
           
           post.save()  # DB에 최종 저장
           return redirect('post_list')  # 글 목록 페이지로 리다이렉트
   else:  # GET 요청 (페이지 첫 로딩)시
       form = PostForm()  # 빈 폼 생성
   
   # POST 요청에서 폼이 유효하지 않은 경우 or GET 요청인 경우
   # 폼을 담아서 템플릿 렌더링
   return render(
       request=request,
       template_name='blog/post_new.html',
       context={'form': form}
   )

코드를 통해 알 수 있는 것

1. post_list 뷰

  • 모든 게시글을 조회하여 목록 페이지를 보여줌
  • Post.objects.all()로 모든 게시글을 가져옴
  • post_list.html 템플릿에 posts 데이터를 전달하여 렌더링

2. post_new 뷰

  • GET 요청: 새 게시글 작성 폼을 보여줌
  • POST 요청: 제출된 폼을 처리
    • 폼 유효성 검사
    • 작성자 정보 추가
    • DB에 저장
    • 성공 시 글 목록으로 리다이렉트
  • 폼이 유효하지 않은 경우 에러와 함께 폼을 다시 보여줌

 

 

 

from django import forms

from blog.models import Post

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

class PostForm(forms.ModelForm):  # ModelForm을 상속받음
    class Meta:
        model = Post             # Post 모델과 연결
        fields = ['message', 'tag_set']  # 입력받을 필드 지정

코드를 통해 알 수 있는 것

  1. 폼의 기능:
  • ModelForm을 상속받아서 Post 모델과 연결된 폼을 생성
  • HTML 입력 폼을 자동으로 생성해줌
  • 입력된 데이터의 유효성을 검사
  • 데이터베이스에 저장하는 기능 제공
  1. Meta 클래스:
  • model = Post: Post 모델을 기반으로 폼 생성
  • fields = ['message', 'tag_set']: 사용자가 입력할 수 있는 필드를 지정
    • message: 게시글 내용
    • tag_set: 태그 선택

 

 

 

<!-- --> 는 HTML의 주석 문법

 

<!-- 페이지 제목 -->
<h1>i hate u</h1>

<!-- 게시글 목록을 보여주는 순서 없는 리스트(unordered list) -->
<ul>
   {% for post in posts %}
       <!-- 
       post: views.py의 post_list 함수에서 전달받은 게시글 객체
       post.message: Post 모델의 message 필드 값을 출력
       {% for %} 구문으로 posts 리스트의 각 게시글을 순회하면서 출력
       -->
       <li>{{ post.message }}</li>
   {% endfor %}
</ul>

코드를 통해 알 수 있는 것

  • 뷰에서 전달받은 모든 게시글(posts)을 반복문으로 순회
  • 각 게시글(post)의 내용(message)을 리스트 아이템(<li>)으로 출력
  • <ul> 태그를 사용해 순서가 없는 리스트 형태로 표시

HTML 구조:

  • <h1>: 페이지의 주요 제목
  • <ul>: 순서가 없는 리스트를 나타내는 컨테이너
  • <li>: 리스트의 각 항목을 나타내는 태그

 

 

<!-- 새 게시글 작성 페이지의 제목 -->
<h1>post_new</h1>

<!-- 게시글 작성을 위한 폼
    method="post": 데이터를 서버로 제출하는 HTTP POST 메서드 사용 -->
<form method="post">
   {% csrf_token %}  
   <!-- CSRF(Cross Site Request Forgery) 공격을 방지하기 위한 Django의 보안 토큰 -->
   
   {{ form.as_p }}    
   <!-- 
   form: views.py에서 전달받은 PostForm 인스턴스
   as_p: 각 폼 필드를 <p> 태그로 감싸서 출력
   자동으로 라벨, 입력 필드, 에러 메시지 등이 생성됨
   -->
   
   <!-- 폼 제출을 위한 버튼 -->
   <button type="submit">Submit</button>
</form>

코드를 통해 알 수 있는 것

  • POST 메서드를 사용하여 데이터를 안전하게 서버로 전송
  • CSRF 토큰을 포함하여 보안성 확보
  • Django 폼 시스템을 활용하여 자동으로 HTML 폼 생성
  • Submit 버튼 클릭 시 post_new 뷰의 POST 처리 로직으로 데이터 전송

실제 렌더링 시:

  • form.as_p는 각 필드를 <p> 태그로 감싸서 출력
  • 각 필드는 라벨, 입력창, 에러 메시지(있는 경우)를 포함
  • CSRF 토큰은 hidden input 필드로 생성됨

데이터 흐름

  • 사용자가 폼 작성 → Submit 클릭
  • POST 요청으로 데이터 전송
  • 서버에서 데이터 검증
  • 성공 시 데이터베이스 저장 및 리다이렉트

 

 

 

LIST

'Today I learned' 카테고리의 다른 글

Djaneiro - Django Snippets 사용법  (5) 2025.01.15
2025.01.13 디제이앙고 연습  (0) 2025.01.14
WIL  (0) 2025.01.10
Django의 관계 필드  (2) 2025.01.09
2025.01.08 +99강화나무  (4) 2025.01.08