📌 이 포스팅에서는 Django에서 Media File를 다루는 settings 방법에 대해 정리하였습니다.
🔥 settings.py 설정
🔥 urls.py 설정
🔥 ImageField 설정
🔥 admin 설정
✔️ image 파일 등 media 파일을 다룰 수 있도록 저장 경로와 접근 경로(url)을 설정하는 기능을 django에서제공한다.
✔️ S3등을 이용하여 외부 경로에 저장할 경우, 접근 경로인 url을 DB에 저장시켜도 되지만, 내부적으로 파일을 저장하고 그 경로를 지정하기 위한 조치가 settings.py에서 이뤄진다.
✔️ 맨 처음엔 아래처럼 STATIC_URL만 경로가 잡혀있고, 이는 정적 파일에 대한 경로이다.
STATIC_URL = 'static/'
✔️ media 파일을 다루기 위해 MEDIA_URL과 MOEDIA_ROOT를 직접 지정해야 한다.
STATIC_URL = 'static/' MEDIA_URL = '/media/' # 👈 /media로 파일에 접근하기 위한 최상위 URL을 지정 MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 👈 실제 파일이 저장되는 경로를 지정
✔️ 만일 프로젝트 내 최상위 경로(BASE_DIR) 밖에 파일을 저장시키고 싶다면 아래 처럼도 지정할 수 있다. 프로젝트 최상위 경로 바로 밖에 public이라는 디렉토리를 만ㄷㄹ고, media라는 디렉토리 내에 저장시키겠다는 의미이다.
STATIC_URL = 'static/' MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, '..', 'public', 'media') # 👈 root 밖에 public/media에 이미지가 저장
✔️ settings.py의 설정이 끝났다면, urls.py에서 경로를 지정해주어야 한다.
✔️ 여기서 static 파일의 경로를 지정하지 않으면, 실제 이미지 파일의 url로 접근해도 해당 이미지를 확인할 수 없다.
✔️ 마치, view를 작성했는데 urls.py에 url과 매핑을 하지 않은 것과 같다.
✔️ DEBUG=True일 경우만이라고 명시하지만, 사실 조건이 없다해도(DEBUG=False일 때) url 리스트를 빈배열을 반환하기 때문에 기능상으론 같다. 다만, 명시적으로 표시하기 위해 일반적으로 이렇게 쓴다.
from django.conf.urls.static import static # 👈 static 경로를 지정하기 위해 static import from django.conf import settings # 👈 conf의 settings를 import from django.contrib import admin from django.urls import path, include # 최상위 urls.py urlpatterns = [ path('admin/', admin.site.urls), ] # static 파일 경로 설정 if settings.DEBUG: # 👈 DEBUG=True일 때만, urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
✔️ django에서 settings에 접근하여 값을 불러올 때, 직접 접근하지 않고 아래 처럼 사용한다.
✔️ 그 이유는, global_settings에 우리 프로젝트 내 settings.py를 오버라이드해서 사용하기 때문에 아래와 같이 import한다.
from django.conf import settings
✔️ 쉽게말해, 아래 2가지를 합친게 from django.conf import settings
와 같은 것이다.
from django.conf import global_settings from core import settings
✔️ FileField를 사용하여 File을 저장하는 경우에는 django-storages를 설치하여 사용한다.
$ pip install django-storages
✔️ image 파일이라면, ImageField를 사용하고 Pillow를 설치한다.
$ pip install pillow
✔️ photo라는 column에 이미지 경로를 저장하기 위해, ImageField를 사용했고, upload_to를 해당 이미지가 저장될 때, 이 이미지를 하위 디렉토리를 설정해주기 위한 기능이다.
from django.db import models from core.models import TimeStampModel # Post 테이블 class Post(TimeStampModel): message = models.TextField() photo = models.ImageField(blank=True, upload_to='instagram/post/%Y/%m/%d') is_public = models.BooleanField(default=False, verbose_name='공개여부')
✔️ settings에서 BASE_DIR의 media 디렉토리에 이미지를 저장하기로 지정하였는데, upload_to를 지정하지 않으면 여러 테이블에서 지정된 이미지들이 잘 정돈되지 않는다.
STATIC_URL = 'static/' MEDIA_URL = '/media/' # 👈 /media로 파일에 접근하기 위한 최상위 URL을 지정 MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 👈 실제 파일이 저장되는 경로를 지정
✔️ 어떤 테이블의 column에서 저장했는지 디렉토리의 depth를 만들어주기 위해 upload_to 옵션을 사용할 수 있다.
✔️ 위에 처럼 ImageField 내에 정할 경우, BASE_DIE/media/instagram/post/년/월/일 디렉토리 아래에 저장된다.
✔️ 아래처럼 upload_to 기능을 오버라이딩하여하여 사용할 수도 있다.
import os from uuid import uuid4 from django.utils import timezone # upload_to 커스텀 def uuid_name_upload_to(instance, filename): 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 extension = os.path.splitext(filename)[-1].lower() # 👈 확장자 추출하고, 소문자로 변환 return '/'.join( app_label, cls_name, ymd_path, uuid_name[:2], uuid_name + extension, )
✔️ photo_tag라는 이미지의 url 경로를 반환하는 함수를 만들어 list_display에 넣으면, admin 페이지에서 이미지를 확인할 수 있다.
✔️ mark_safe는 django의 보안 기능으로 태그를 바로 노출시키지 않고 string으로 변환하기 때문에 안전한 태그라는 것을 django에게 알려주기 위한 기능이다.
from django.contrib import admin from django.utils.safestring import mark_safe from .models import Post # Post Admin Custom @admin.register(Post) class PostAdmin(admin.ModelAdmin): list_display = ['id', 'message', 'message_length', 'is_public', 'photo_tag', 'created_at', 'updated_at'] list_display_links = ['message'] list_filter = ['created_at', 'is_public'] search_fields = ['message'] # admin 페이지에서 이미지 바로 확인하는 방법 def photo_tag(self, post): if post.photo: # 👈 photo url이 있다면, return mark_safe(f'<img src="{post.photo.url}" style="width:72px"/>') return None