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 설정
- 앱 등록: INSTALLED_APPS에 blog와 accounts 추가.
- INSTALLED_APPS = [ ..., 'blog', 'accounts', ]
- AUTH_USER_MODEL 설정: 커스텀 사용자 모델을 사용할 경우 설정.
- 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)
필기 해석 및 정리 요약
- 프로젝트 및 앱 생성: blog, accounts 앱 생성.
- 모델 설정: 사용자(User)와 게시글(Post) 모델 정의.
- 마이그레이션: 데이터베이스에 반영.
- 슈퍼유저 생성: Django Admin에서 데이터 관리.
- URL 설정: blog/urls.py와 views.py에서 URL과 뷰 연결.
- 뷰 함수와 템플릿: post_list 뷰와 HTML 템플릿 작성.
- 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 객체를 문자열로 표현할 때 주소를 반환
코드 설명
- User 모델
- Django의 기본 사용자 모델을 확장(AbstractUser)하여 새로운 필드(bio)를 추가.
- bio: 사용자의 프로필 정보(예: 자기소개)를 저장.
- Profile 모델
- User 모델과 1:1 관계를 가지며, 사용자와 관련된 추가 정보를 저장.
- address: 사용자의 주소.
- zipcode: 사용자의 우편번호.
- OneToOneField: 사용자(User)와 Profile 간의 1:1 관계를 정의. 한 사용자당 하나의 프로필만 가질 수 있음.
- on_delete=models.CASCADE: 사용자가 삭제되면 연결된 프로필도 함께 삭제.
- __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 객체를 문자열로 표현할 때 태그 이름을 반환
코드 설명
- TimestampedModel (추상 모델)
- 역할: created_at과 updated_at 필드를 제공하여 생성 및 수정 시간을 자동으로 관리.
- 추상 모델: abstract = True를 설정해 데이터베이스에 독립적인 테이블로 생성되지 않음. 이를 상속받는 모델이 실제 테이블을 생성할 때 이 필드를 포함하게 됨.
- Post (게시글 모델)
- 게시글 정보를 저장하는 모델로, 작성자와 내용(message)를 포함.
- 작성 시간과 수정 시간은 TimestampedModel에서 상속받아 자동 관리.
- tag_set: 다대다 관계를 통해 여러 태그를 연결.
- 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),
]
코드 설명
- path() 함수
- Django의 URL 라우팅을 처리하는 함수로, 클라이언트의 요청 URL과 뷰를 연결합니다.
- 주요 인자
- route: URL 경로를 정의합니다.
- view: 해당 URL 요청을 처리할 뷰 함수나 클래스.
- name (선택 사항): URL에 이름을 부여하여 템플릿이나 리다이렉트에서 쉽게 참조 가능.
- URL 패턴
- '': 루트 URL (/)로 접근하면 views.post_list 뷰가 호출됩니다.
- 'post_new': /post_new URL로 접근하면 views.post_new 뷰가 호출됩니다.
- views 모듈
- views.py에서 정의된 함수(post_list, post_new)가 요청을 처리합니다.
def post_list(request): # 게시글 목록을 반환하는 로직 pass def post_new(request): # 새로운 게시글을 작성하는 로직 pass
- views.py에서 정의된 함수(post_list, post_new)가 요청을 처리합니다.
- name 매개변수
- URL에 이름(post_list)을 부여하면, 템플릿에서 이 이름을 사용해 URL을 참조할 수 있습니다.
<a href="{% url 'post_list' %}">게시글 목록</a>
- URL에 이름(post_list)을 부여하면, 템플릿에서 이 이름을 사용해 URL을 참조할 수 있습니다.
추가 개선
- 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}) # 템플릿으로 폼 데이터를 전달하여 렌더링
코드 설명
- post_list
- 역할: 게시글 목록을 가져와 템플릿에 전달하고, HTML 페이지를 렌더링.
- 주요 로직:
- 데이터베이스에서 Post 모델의 모든 객체를 가져옵니다.
- 가져온 데이터를 context에 저장하고, 이를 템플릿(blog/post_list.html)에 전달합니다.
- post_new
- 역할: 새로운 게시글 작성 폼을 렌더링하거나, 제출된 데이터를 저장.
- GET 요청: 사용자가 새로운 게시글 작성 페이지를 열 때 빈 폼을 제공.
- POST 요청: 사용자가 데이터를 제출하면 유효성을 검사하고, 유효한 경우 데이터베이스에 저장.
- 주요 로직:
- form.is_valid()로 폼 데이터의 유효성을 검사.
- 작성자는 현재 로그인한 사용자(request.user)로 설정.
- 저장 후 게시글 목록 페이지로 리다이렉트.
- PostForm
- 역할: Post 모델에 기반한 폼으로, 데이터를 제출받고 검증합니다.
추가 설명
- render() 함수:
- 템플릿에 데이터를 전달하고 렌더링된 HTML 페이지를 반환합니다.
- template_name: 사용할 템플릿 파일 이름.
- context: 템플릿에 전달할 데이터 사전.
- redirect() 함수:
- URL 이름(post_list)을 기반으로 리다이렉트 응답을 생성합니다.
- form.save(commit=False):
- 데이터를 데이터베이스에 저장하기 전에 객체를 반환합니다.
- 이 객체를 수정한 뒤 수동으로 save()를 호출하여 저장합니다.
form.is_valid()의 동작 방식
Django의 form.is_valid() 메서드는 사용자가 제출한 데이터가 폼에서 정의한 조건(필드와 유효성 검사 기준)을 만족하는지 확인합니다. 이 과정에서 내부적으로 여러 단계를 거쳐 유효성을 검사합니다.
1. 주요 동작 흐름
- 데이터 바인딩 확인:
- form.is_valid()가 호출되면, 폼에 데이터가 올바르게 바인딩되었는지 확인합니다.
- 데이터는 폼 객체 생성 시 form = PostForm(request.POST)와 같이 전달된 request.POST 또는 request.GET에 담겨 있습니다.
- 필드별 유효성 검사 실행:
- 폼에 정의된 각 필드의 유효성 검사를 실행합니다.
- 유효성 검사는:
- 필드의 속성 (예: required=True, max_length)에 대한 검증.
- 사용자 정의 검증 (예: clean_<field_name> 메서드).
- cleaned_data 생성:
- 데이터가 유효한 경우, 정리된 데이터가 form.cleaned_data 딕셔너리에 저장됩니다.
- 유효하지 않은 경우, form.errors에 각 필드의 오류 메시지가 저장됩니다.
- 검증 결과 반환:
- 모든 필드가 유효하면 True를 반환하고, 하나라도 유효하지 않으면 False를 반환합니다.
2. 내부 동작 상세
A. 데이터 유효성 검증
is_valid() 호출 시, 내부적으로 full_clean() 메서드를 실행합니다.
def is_valid(self):
self.full_clean()
return not self.errors
B. full_clean() 메서드
- 필드별 유효성 검증 실행:
- 각 필드에 정의된 유효성 검사를 실행.
- 예: CharField는 길이, 비어 있는지 여부 등을 확인.
- 사용자 정의 검증 실행:
- 폼 또는 필드별로 정의된 추가 검증 메서드(예: clean_<field_name>)를 실행.
- 글로벌 유효성 검증 실행:
- 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()를 통과하는 흐름
- title 필드와 content 필드가 비어 있거나 잘못된 데이터가 있는지 확인.
- clean_title 메서드가 실행되어 추가 검증 수행.
- 모든 검증을 통과하면 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
장점
- HTML 폼 자동 생성: 개발자가 매번 HTML 태그를 작성하지 않아도 됨.
- 모델과의 통합: 모델의 필드와 데이터베이스 작업을 간편하게 처리 가능.
- 확장성: 기본 제공 기능 외에도 사용자 정의 검증 및 위젯 커스터마이징 가능.
[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 내용을 반환.
추가 팁
- 재사용 가능한 모델 구조:
- TimestampedModel처럼 추상 모델을 사용하면, 여러 모델에서 반복적으로 사용되는 필드를 효율적으로 관리할 수 있습니다.
- on_delete 옵션:
- on_delete=models.CASCADE는 작성자(author)가 삭제되면 해당 게시글도 함께 삭제됨을 의미합니다.
- 다른 옵션도 존재:
- SET_NULL: 외래 키 값을 NULL로 설정.
- PROTECT: 삭제를 방지하고 오류 발생.
- ManyToManyField:
- 다대다 관계를 정의하며, 관련 데이터는 자동으로 별도의 중간 테이블을 생성하여 관리됩니다.