Skip to content

정적 파일과 미디어 파일 처리

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:

# settings.py에서 자동 설정
STATIC_URL = '/static/'
MEDIA_URL = '/media/'

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 설치 (이미지 처리용)

pip install 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 사용

pip install 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. 정적 파일 최적화

캐시 버스팅

# settings.py
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'

CSS/JS 압축 (django-compressor)

pip install django-compressor
# settings.py
INSTALLED_APPS = [
    # ...
    'compressor',
]

COMPRESS_ENABLED = True
{% load compress %}
{% compress css %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
{% endcompress %}

9. 배포시 정적 파일 처리

collectstatic 명령어

python manage.py 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 시스템을 자세히 알아보겠습니다!

Comments