정적 파일과 미디어 파일 처리
FastAPI에서는 정적 파일을 별도로 설정해야 했지만, Django는 강력한 정적 파일 관리 시스템을 제공합니다. 이번 장에서는 CSS, JavaScript, 이미지 등의 정적 파일과 사용자가 업로드한 미디어 파일을 처리하는 방법을 알아봅니다.
1. 정적 파일 vs 미디어 파일
파일 종류 구분
- 정적 파일(Static Files): 개발자가 제공하는 CSS, JS, 이미지 등
- 미디어 파일(Media Files): 사용자가 업로드하는 파일
FastAPI vs Django 비교
FastAPI:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
Django:
2. 정적 파일 설정
기본 설정 (settings.py)
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/' # URL 접두사
STATICFILES_DIRS = [
BASE_DIR / "static", # 프로젝트 레벨 정적 파일
]
STATIC_ROOT = BASE_DIR / 'staticfiles' # collectstatic 명령어 결과 저장 위치
# Media files (User uploads)
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
디렉토리 구조
mysite/
├── static/ # 프로젝트 정적 파일
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── main.js
│ └── images/
│ └── logo.png
├── blog/
│ └── static/ # 앱별 정적 파일
│ └── blog/ # 네임스페이스
│ ├── css/
│ └── js/
├── media/ # 사용자 업로드 파일
└── staticfiles/ # 배포용 정적 파일 모음
3. 정적 파일 사용하기
템플릿에서 정적 파일 로드
{% load static %}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<link rel="stylesheet" href="{% static 'blog/css/blog.css' %}">
</head>
<body>
<img src="{% static 'images/logo.png' %}" alt="Logo">
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
View에서 정적 파일 URL 생성
from django.templatetags.static import static
from django.http import JsonResponse
def get_logo_url(request):
logo_url = static('images/logo.png')
return JsonResponse({'logo_url': logo_url})
4. CSS 파일 작성 예제
static/css/style.css
/* 전역 스타일 */
body {
font-family: 'Noto Sans KR', sans-serif;
line-height: 1.6;
color: #333;
margin: 0;
padding: 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* 헤더 스타일 */
header {
background-color: #2c3e50;
color: white;
padding: 1rem 0;
}
header nav a {
color: white;
text-decoration: none;
margin-right: 1rem;
}
/* 블로그 포스트 스타일 */
.post {
margin-bottom: 2rem;
padding: 1.5rem;
background-color: #f8f9fa;
border-radius: 8px;
}
.post h2 {
color: #2c3e50;
margin-bottom: 0.5rem;
}
.post .meta {
color: #666;
font-size: 0.9rem;
margin-bottom: 1rem;
}
JavaScript 파일 예제 (static/js/main.js)
// DOM이 로드된 후 실행
document.addEventListener('DOMContentLoaded', function() {
// CSRF 토큰 가져오기 (Django 폼 제출시 필요)
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// AJAX 요청 예제
const csrftoken = getCookie('csrftoken');
// 좋아요 버튼 처리
const likeButtons = document.querySelectorAll('.like-btn');
likeButtons.forEach(button => {
button.addEventListener('click', function() {
const postId = this.dataset.postId;
fetch(`/blog/post/${postId}/like/`, {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(data => {
this.textContent = `좋아요 ${data.likes}`;
});
});
});
});
5. 미디어 파일 처리
모델에서 파일 필드 사용
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
# 이미지 필드
thumbnail = models.ImageField(
upload_to='posts/thumbnails/%Y/%m/%d/', # 연/월/일 폴더로 구분
blank=True,
null=True
)
# 파일 필드
attachment = models.FileField(
upload_to='posts/attachments/',
blank=True,
null=True
)
def __str__(self):
return self.title
class UserProfile(models.Model):
user = models.OneToOneField('auth.User', on_delete=models.CASCADE)
avatar = models.ImageField(
upload_to='avatars/',
default='avatars/default.png'
)
bio = models.TextField(blank=True)
Pillow 설치 (이미지 처리용)
개발 환경에서 미디어 파일 서빙
# mysite/urls.py
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
]
# 개발 환경에서만 미디어 파일 서빙
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
6. 파일 업로드 폼 처리
HTML 폼
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="text" name="title" required>
<textarea name="content" required></textarea>
<input type="file" name="thumbnail" accept="image/*">
<button type="submit">포스트 작성</button>
</form>
View에서 파일 처리
from django.shortcuts import render, redirect
from .models import Post
def create_post(request):
if request.method == 'POST':
post = Post(
title=request.POST['title'],
content=request.POST['content'],
author=request.user
)
# 파일 처리
if 'thumbnail' in request.FILES:
post.thumbnail = request.FILES['thumbnail']
post.save()
return redirect('blog:post_detail', pk=post.pk)
return render(request, 'blog/post_form.html')
템플릿에서 미디어 파일 표시
{% if post.thumbnail %}
<img src="{{ post.thumbnail.url }}" alt="{{ post.title }}" class="post-thumbnail">
{% else %}
<img src="{% static 'images/default-thumbnail.png' %}" alt="기본 이미지">
{% endif %}
{% if post.attachment %}
<a href="{{ post.attachment.url }}" download>첨부파일 다운로드</a>
{% endif %}
7. 이미지 리사이징 (선택사항)
django-imagekit 사용
# settings.py
INSTALLED_APPS = [
# ...
'imagekit',
]
# models.py
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class Post(models.Model):
thumbnail = models.ImageField(upload_to='posts/thumbnails/')
# 자동 리사이징된 이미지 생성
thumbnail_small = ImageSpecField(
source='thumbnail',
processors=[ResizeToFill(300, 200)],
format='JPEG',
options={'quality': 90}
)
8. 정적 파일 최적화
캐시 버스팅
CSS/JS 압축 (django-compressor)
{% load compress %}
{% compress css %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
{% endcompress %}
9. 배포시 정적 파일 처리
collectstatic 명령어
이 명령어는 모든 앱과 프로젝트의 정적 파일을 STATIC_ROOT에 모읍니다.
Nginx 설정 예제
server {
# 정적 파일
location /static/ {
alias /path/to/staticfiles/;
}
# 미디어 파일
location /media/ {
alias /path/to/media/;
}
# Django 앱
location / {
proxy_pass http://localhost:8000;
}
}
10. 보안 고려사항
파일 업로드 검증
from django.core.exceptions import ValidationError
def validate_file_size(value):
filesize = value.size
if filesize > 10485760: # 10MB
raise ValidationError("파일 크기는 10MB를 초과할 수 없습니다.")
class Post(models.Model):
attachment = models.FileField(
upload_to='attachments/',
validators=[validate_file_size]
)
허용된 파일 타입만 허용
import os
from django.core.exceptions import ValidationError
def validate_file_extension(value):
ext = os.path.splitext(value.name)[1]
valid_extensions = ['.pdf', '.doc', '.docx', '.jpg', '.png']
if not ext.lower() in valid_extensions:
raise ValidationError('지원하지 않는 파일 형식입니다.')
실습: 이미지 갤러리 만들기
# models.py
class Photo(models.Model):
title = models.CharField(max_length=100)
image = models.ImageField(upload_to='gallery/%Y/%m/')
uploaded_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-uploaded_at']
# views.py
def gallery(request):
if request.method == 'POST':
title = request.POST.get('title')
image = request.FILES.get('image')
if title and image:
Photo.objects.create(title=title, image=image)
return redirect('gallery')
photos = Photo.objects.all()
return render(request, 'gallery.html', {'photos': photos})
정리
Django의 정적/미디어 파일 처리:
- 자동화된 정적 파일 관리: {% static %} 태그로 쉽게 사용
- 미디어 파일 처리: ImageField, FileField로 간편한 업로드
- 개발/배포 분리: DEBUG 모드에 따른 자동 처리
- 보안 기능 내장: 파일 검증, CSRF 보호
- 최적화 도구: collectstatic, 캐시 버스팅
FastAPI와 비교: - Django는 정적 파일 처리가 프레임워크에 통합됨 - 템플릿 태그로 URL 자동 생성 - 미디어 파일 업로드가 모델 필드로 간편하게 처리 - 배포시 collectstatic으로 파일 자동 수집
다음 장에서는 Django의 강력한 ORM 시스템을 자세히 알아보겠습니다!