본문 바로가기

문돌이 존버/Django 스터디

장고(Django) media 파일 다루기

반응형
본 글은 Holix의 "리액트와 함께 장고 시작하기 Complete" 강의를 듣고 작성한 일지입니다.

이번 시간엔 장고의 media 파일을 다루는 방법에 대해 알아보겠습니다.

먼저 아래는 데이터베이스 모델에 models.ImageField() 를 통해 photo라는 속성을 추가했습니다. 파라미터로 upload_to 를 추가해주었는데 이에 대해선 본 글 마지막에서 설명하겠습니다.

# model.py
from django.db import models
from .utils import uuid_name_upload_to # 별도 파일에서 함수 import

# Create your models here.
class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    photo = models.ImageField(blank=True, upload_to=uuid_name_upload_to) # blank=True: 옵션 필드(디폴트는 False) / upload_to: 저장경로도 지정할 수 있음
    is_public = models.BooleanField(default=False, verbose_name='공개여부')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    # Java의 toString
    def __str__(self):
        return self.content

장고에선 static 파일 이외의 media 파일은 별도의 설정이 필요합니다. settings.py 에서 아래 2가지가 필요한데요. 클라이언트가 업로드하는 파일을 서버에서 저장하는 위치를 설정하는 것입니다.

(참고)
static 파일은 서버단에서 미리 올려놓는 파일을 처리하고,
media 파일은 클라이언트에서 업로드하는 파일을 처리합니다.
# settings.py
MEDIA_URL = '/media/' # 이미지 파일에 url로 접근할 때 <-> MEDIA_ROOT는 이미지 파일 저장 위치
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # (BASE_DIR , '..', 'public', 'media') <- '..'는 상위 디렉토리
# project의 urls.py
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include

# instagram의 settings는 우리가 커스터마이징한 설정으로, global_settings를 먼저 불러와 이를 overwriting 해야 함
# from django.conf import global_settings 
# from instagram import settings
from django.conf import settings # 위의 2라인을 합친 버전

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

if settings.DEBUG: # 개발 모드일 때만
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

지난 시간에 소개한 admin 페이지 커스터마이징을 계속 이어나가겠습니다. 클라이언트 측에서 올린 이미지 파일을 admin 페이지 글 목록에서도 보일 수 있게 설정했습니다. 주의하실 점은 mark_safe 를 반드시 추가하여 escape 처리(문자열 그대로 표시)되는 것을 방지해야 합니다.

# admin.py
from django.contrib import admin
from django.utils.safestring import mark_safe
from .models import Post
# Register your models here.

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ['id', 'photo_tag', 'content', 'content_length', 'is_public', 'created_at', 'updated_at'] # pk는 id의 alias
    list_display_links = ['content']
    list_filter = ['created_at', 'is_public']
    search_fields = ['content']

    def photo_tag(self, post):
        if post.photo:
            print(post.photo.url) # /media/파일 위치/파일명
            return mark_safe(f'<img src="{post.photo.url}" style="width: 72px;" />' ) # django의 특성상 mark_safe를 하지 않으면 이미지를 보여주지 않고 문자열 그대로 드러냄: escape 처리
        return None

    def content_length(self, post):
        return len(post.content)

 

마지막으로 위에서 잠깐 언급한 media 파일 저장 위치를 조작할 때 사용한 파라미터 upload_to 에 대해 언급하겠습니다. upload_to 는 직접 문자열로 파일이 저장될 디렉토리를 설정할 수 있지만(e.g. 'media/instagram/post/%Y/%m/%d') 함수로도 지정할 수 있습니다. 그래서 이 함수를 utlis.py 라는 다른 파일에 선언했고, 아래가 해당 코드입니다.

# utlis.py
import os
from uuid import uuid4
from django.utils import timezone

def uuid_name_upload_to(instance, filename): # instance는 이미지 파일
    app_label = instance.__class__._meta.app_label # 앱 별로
    cls_name = instance.__class__.__name__.lower() # 모델 별로
    ymd_path = timezone.now().strftime('%Y/%m/%d')
    uuid_name = uuid4().hex # 32 characters <-> uuid4 = 36 characters
    extension = os.path.splitext(filename)[-1].lower() # 확장자 추출 뒤 소문자로 변환
    return '/'.join([
        app_label,
        cls_name,
        ymd_path,
        uuid_name[:2],
        uuid_name + extension,
    ])

uuid4 는 실제 파일 이름을 쓰지 않고 임의의 36 글자(4개의 '-' 포함)를 만들어냅니다. 이는 직접 장고 쉘에서 코드를 실행해보면 훨씬 이해하기 쉬울 것입니다. 하이푼 '-'이 제외된 버전은 uuid4().hex 를 사용하시면 됩니다. 그리고 os.path.splitext() 를 통해 확장자를 따로 추출할 수 있습니다.

media 파일이 많은데 일일이 파일명을 지정하기 귀찮다면 위의 방법도 괜찮을 것 같습니다. 특히 클라이언트가 많은 상황에선 필수일 것으로 보입니다.

728x90
반응형