본문 바로가기
WebFramework/Python Django

[Python Django] 13. Django Static 파일 표시 및 업로드 제어

by Rosmary 2024. 4. 17.
728x90
반응형

 

 

 

 

 

어제... Django에 이미지 올리는 테스트를 하다가 오류가 나 해결하느라 꼬박 하루를 날려버렸다. 지금 진짜 피곤한데, 그래도 의미있는 결과를 잘 정리해둬야 나중에 피곤한 일이 다시 생기지 않는다는 것은 이미 몇 년 간의 블로그 생활을 통해 많이 느껴왔기 때문에, 힘을 내서 정리해보려한다. 

 

오늘 포스팅의 내용은 Django에서의 static 파일 사용법 및 파일 업로드와 관련된 내용이다. static 파일은 그 영문 이름에서도 알 수 있듯이 '정적'인 파일들인데, 이들 파일은 단순히 서버에서 클라이언트로 전달되면 끝인 파일이다. 즉, 사용자에게 서비스를 하기 위해 '서버에 저장'된 파일들인데, 이들 파일은 외부 환경에 관계없이 일정한 결과를 돌려주는 역할을 한다. HTML의 디자인을 담당하는 CSS나, 클라이언트의 브라우저에서 동작하는 JavaScript, 혹은 화면에 출력되는 이미지 파일 등이 여기에 속한다고 볼 수 있다. 

 

"지금 정적 파일이라고 설명한 것은 전부 HTML 태그에서 src 속성을 각 파일이 로컬 내에 위치한 경로를 지정해주면 되지 않나요?"

 

Django에서는 그렇게 간단한 문제가 아니다. 사실 Django로 만든 서비스를 배포하게되면, 모든 정적 파일들은 Django 프로젝트 폴더 아래의 특정 폴더에 저장되어야만 동작하게 되어 있다(이 부분은 추후 배포 테스트를 하게 된다면 다시 포스팅 할 예정이다). 즉, static 파일이 위치한 폴더를 Django가 참조할 수 있도록 만들어주어야 한다. 마치 TEMPALTES_DIRS 처럼 말이다.

 

파일 업로드도 마찬가지다. Django가 사용자로부터 받은 업로드 파일은 Django가 참조하는 별도의 경로에 저장이 된다. 만약 화면에서 사용자가 업로드 한 화면을 표시해야한다면 파일이 업로드 된 경로를 일일이 확인하여 HTML 파일을 수정하는 것은 불가능하다. 따라서 Django는 자신이 참조하는 정적 파일의 저장 경로를 쉽게 다룰 수 있도록 별도의 기능을 제공한다.

 

말로 하는 것보다는 보는게 낫고, 보는 것 보다는 해 보는게 낫다. 바로 시작해보자.

 

 

1. 프로젝트 환경 구성

 

필자가 시간이 많지 않아, 아주 간단하게 프로젝트를 구성해보려 한다. 

 

-  메인 페이지 (""):  <img> 태그로 하나의 이미지 파일을 화면 제일 상단에 표시

                               그 아래에 "/create/" 페이지로 이동하는 <form> 태그 생성

                                <hr>

                                "/create/"에서 업로드 한 파일 정보를 화면에 표시하는 영역

-  파일 업로드 페이지("create/"): 파일 업로드 폼 제공 및 파일 업로드 수행

 

매우 간단한(?) 웹 페이지다. 필자는 새 폴더에 Venv로 개발 환경을 구성하고, Django와 pillow라는 모듈을 pip로 설치한다. 참고로 저 pillow는 Django에서 파일 업로드 시 반드시 필요한 모듈이다. 

 

1
2
3
4
5
 
#  Python package 설치
 
pip install django         
pip install pillow         # Django에서 파일 업로드 시 필요한 모듈
cs

 

 

설치가 정상적으로 되었다면 pip list 명령어를 터미널에 입력했을 때, 아래와 같이 Django와 pillow가 설치 목록에 보여야한다. 

 

 

 

Django 프로젝트를 pjt라는 간단한 이름으로 만들고, 그 내부에 test라는 이름의 앱을 생성한다. 여기까지 완료되면 프로젝트 폴더 내에 HTML 파일을 저장할 templates와 정적 파일을 저장할 statics 폴더를 생성하자. 

 

 

 

원래 프로젝트를 생성하면 settings.py와 프로젝트 urls.py로 바로 설정을 진행해야하나, 정적 파일 및 업로드 파일을 다루기 위해서는 이 파일에서 미리 설정해야 할 것들이 조금 있다. 따라서 1차적인 프로젝트 환경 구성은 여기서 마무리하면 된다.

 

 

2. Static 파일의 관리

 

Django의 settings.py를 보면, STATIC_URL이라는 변수가 하나 보인다. 이 변수는 Django 프레임워크가 저장하고 있는 CSS, Image, Javascript 등의 정적 파일을 사용자에게 전달할 때, 파일이 위치한 로컬 서버의 절대 경로를 보여주지 않기 위해 사용하는 별도의 URL이라 보면 된다.

 

개발자도구에서 img 태그의 src 속성을 보면 /static/으로 시작하는 경로가 표시된다.

 

 

그럼, Django는 정적 파일이 저장되는 경로를 어떻게 참조하는지가 궁금해질텐데, 이는 Django의 정적 파일과 관련된 별도의 변수를 settings.py에 적용해주어야한다. 배포 시 사용하는 변수인 STATIC_ROOT와, 개발 시 사용하는 변수인 STATICFILES_DIRS  두 가지 가 있다.

 

STATICFILES_DIRS는 TEMPLATES_DIRS와 마찬가지로 여러 폴더를 참조하기 때문에 이름의 마지막에 복수형 S가 들어간다. 

 

 

STATIC_ROOT는 배포 시, Django가 Static 파일을 참조하는 경로를 나타내며, STATICFILES_DIRS는 STATIC_ROOT외에 Django가 참조해야하는 다른 파일을 명시한다. 보통 Django는 STATIC_ROOT 경로에 모든 정적 파일을 넣고 관리하기 때문에 STATICFILES_DIRS는 개발 시 많이 사용한다(개발 시 STATICFILES_DIRS의 모든 정적 파일을 STATIC_ROOT 경로로 옮기는 방법은 python manage.py collectstatic 이라는 명령어를 사용한다는데 추후 기회가 되면 포스팅을 할 예정이다).

 

본격적으로 settings.py를 건드려보자. 우선 개발 과정에서 참조해야하는 경로를 프로젝트 폴더에 만든 statics로 지정하려한다. 따라서, 아래와 같이 STATICSFILES_DIRS 값을 정의해준다.

 

TEMPLATES_DIRS는 이미 이전의 포스팅에서 설명한 내용이니 본 포스팅에서는 생략한다.

 

 

STATIC_ROOT 역시 지금 정의한다고 해도 동작에는 큰 문제가 없다. 다만 STATIC_ROOT의 경로는 STATICFILES_DIRS에 절대 포함되지 않은 경로를 사용해야 에러가 없이 동작한다. 어려운 내용은 아니니 궁금하신 분들은 직접 해 보시면 될 듯 하다.

 

이제 필자가 구상한 내용대로 웹을 구성해보려한다.

 

[ pjg.urls.py ]

 

[ test.urls.py & test.views.py ]

 

[ templates.test.index.html & templates.test.create.html ]

우측 create.html 태그 부분에 urls가 아니라 url이다.

 

 

여기까지 완료되었다면 서버 구동 시, 메인 페이지와 create/ 경로가 아래와 같이 정상적으로 출력된다.

 

 

 

우선 index 페이지에 이미지 파일을 추가해보자. 현재 필자가 작성한 index.html은 <img> 태그의 src 속성이 빈 값이다. 이제 화면에 표시하고자 하는 파일 하나를 프로젝트폴더/statics/test 아래에 저장하자. 필자는 성산 일출봉을 배경으로 찍은 사진을 올리려한다.

 

 

 

단순히 statics 폴더에 이미지 파일을 위치시키지 않고 test라는 하위 폴더를 생성하여 저장했는데, 필자가 사용하려는 이 이미지 파일은 test 앱 외에는 사용하지 않을 것이기 때문이다. 다른 곳에 위치시키더라도 동작에는 문제가 없지만, 관리 측면에서 이렇게 지정했다.

 

이제 img 태그의 src 부분을 보자. 이미지 경로를 아래와 같이 프로젝트 경로를 Root로 삼아 절대 경로로 지정하면 이미지가 나타나지 않을 것이다. 

 

 

 

이유는 정말 단순한데, img 태그의 src는 로컬 서버의 경로를 지정하는 것이 아니라 반드시 웹 URL의 형태로 경로가 표시되어야한다. 그런데, Django는 statics로 시작하는 URL이 들어온다면(http://127.0.0.1:8000/statics), 이 경로를 인식할 수 없다. 왜냐하면 settings.py에서 STATIC_URL은 "static" - 마지막에 s가 빠져있다 -으로 지정되어 있기 때문이다.

 

 

 

<img> 태그의 src 부분에서 statics를 static으로 변경하면 아래와 같이 정상적으로 화면에 출력됨을 확인할 수 있다.

 

 

 

Django는 정적 파일 보관 폴더에 저장된 정적 파일에 대해 URL을 반환하는 DTL도 제공한다. 

 

{% load static %}

{% static '파일의 STATIC 폴더 다음 경로' %}

 

위의 load 태그는 HTML 내부에서 'Django에 저장된 정적 파일을 사용하기 위해 정적 폴더를 참고할 거야!'라는 의미고, 두 번째 static 태그는 '나는 그 중에 이 경로의 파일을 이용할 거야!'라고 알려주는 것이다. 한 번 HTML에 적용해보자. 

 

{% load static %} 태그는 HTML의 가장 최상단에 작성한다. 그리고, img 태그의 src에 필자가 표시하기를 원하는 이미지 파일 경로를 {% static 'test/jeju_sungsan.pgn' %}라고 표시하면 된다.

 

 

 

static 태그를 사용하면, Django에서 STATIC_URL 및 STATICFILES_DIRS 값을 참조하여, src 속성에 지정한 파일에 대한 URL 값을 자동으로 돌려준다. 따라서 직접 URL 경로를 적어준 것과 마찬가지로 정상적인 결과가 출력된다. 

 

이미지 파일 외에 CSS나 JS도 위와 같은 방식으로 등록이 가능하다. 필자는 지금 등록한 사진의 크기를 CSS를 적용하여 100px의 정사각형 모양으로 변경해보려한다. 

 

 

 

 

3. 파일 업로드 및 업로드 파일 참조

 

이제 사용자로부터 업로드 된 파일을 다루는 방법에 대해 알아보려한다. 사용자로부터 업로드 된 파일은 서버 -> 사용자로 전달되는 정적파일과 달리, 사용자 -> 서버로 전달된다는 특징이 있다. 즉, 사용자로부터 업로드되는 파일은 서버의 정적 파일과는 별도로 관리되어야 할 필요가 있다는 이야기다. 

 

이 때문에 Django는 사용자로부터 업로드되는 파일을 MEDIA로 시작하는 settings.py 변수로 처리한다. STATIC과 마찬가지로 MEDIA 파일에 대한 URL, ROOT 변수가 존재한다. 

 

MEDIA_URL은 STATIC_URL과 마찬가지로 Django의 특정 폴더에 저장된 업로드 파일의 호출 시 참조하는 URL의 시작부분이다.

 

 

 

그리고 MEDIA_ROOT는 사용자로부터 업로드 된 파일을 Django가 어디에 저장해야하는지 경로를 표시하는 변수라 보면 된다. 

 

 

 

말로는 어려우니 settings.py에 이 두 변수를 추가하며 확인해보자.

 

 

 

STATIC 파일을 다룰 때와 달리, 현재 Django가 DEBUG 모드에서 동작하고 있다면, MEDIA와 관련된 작업은 Django가 URL과 업로드 파일 저장 경로를 매칭할 수 있도록 만들어주어야 한다. MEDIA와 관련된 변수는 배포 시에만 동작을 하도록 내부에 코드가 설정되어 있어, DEBUG=True로 settings.py가 설정된 경우 Django가 MEDIA_ROOT의 경로를 참조할 수 없기 때문이다.

 

따라서 프로젝트 폴더의 urls.py에 아래와 같이 DEBUG 모드에서 동작하는 경우, MEDIA_URL 경로가 참조해야하는 폴더를 지정해준다. 

 

 

 

이제, DEBUG 모드에서도 Django는 사용자가 올린 파일을 저장할 폴더 경로를 제대로 참조할 수 있게 된다.

 

다음으로, create/ 페이지에서 사진 업로드를 진행하기 위해, test.models.py와 test.forms.py를 생성하고 아래와 같이 코드를 작성했다.

 

[ test.models.py ]

 

 

사용자로부터 파일이나 이미지를 받기 위해 사용하는 Field는 ImageField와 FileField가 있다. ImageField는 FileField를 상속받기 때문에 사용법이 유사한데, 사용자가 올린 파일을 저장하기 위해서는 필드의 upload_to 인자를 사용하여 MEDIA_ROOT 폴더 내에 저장할 경로를 지정해주어야 한다. 

 

위의 내용은 image 파일이 사용자에 의해 업로드되면, MEDIA_ROOT 경로의 images 폴더에 사용자가 올린 파일명으로 저장하겠다는 의미다. 

 

[ test.forms.py ]

 

** Form 관련 내용은 아래 두 개의 포스팅을 참고하자..
Django Form / Model Form 사용하기(1)

Django Form/ Model Form 사용하기(2)

 

Model과 Form에 대한 작성이 끝났으니, 이들을 test.views.py 파일에서 사용할 수 있도록 Import 하고 아래와 같이 index() 및 create() 매서드를 작성해주자.

 

 

 

 

참고로, views로 넘어오는 form 입력값들이 대부분 Text였던 지금까지와 달리, 현재는 File 자체를 받아들이는 것이므로, TestModelForm에서 data로 파일을 받는 것이 아니라 files 인자에 별도로 파일을 받아주어야 한다. 만약 이 인자에 request.FILES를 지정하지 않고, is_valid()로 넘어가게 되면, 필자의 ModelForm에 의해 이미지가 없다고 판단하여 DB 저장을 진행하지 않게 된다. 

 

 

 

마지막으로 views.py에서 넘어오는 값을 HTML에서 참조할 수 있도록 코드를 아래와 같이 수정해주자.

 

[ test.index.html  & test.create.html ]

 

 

이 상태에서 웹 사이트를 접속하면 아래와 같이 create 부분은 Form이 적용되어 나타남을 확인할 수 있다. 아직까지 데이터가 없기 때문에 index 페이지는 이전과 동일한 모습을 보인다.

 

 

 

이제 파일의 업로드와 index.html 파일에 업로드 한 파일이 제대로 나타나는지 확인해보려한다. 사진은 필자가 임의로 인터넷에서 긁어 저장한 사진을 사용하려한다. 그런데, 업로드를 해보면, 아래와 같이 사진을 등록했음에도 불구하고, "This field is required."라는 Form 이 일치하지 않다는 에러 문구가 웹 페이지에 출력된다. 

 

 

 

원인은 매우 단순한데, HTML에서 form을 사용하여 전송하는 데이터는 모두 Text 기반의 정보만 전송하기 때문이다. 따라서 form을 통해 text 이외의 정보를 전달하기 위해서는 form의 enctype이라는 속성을 'multipart/form-data' 추가해주어야 한다. 

 

 

 

이제 파일 등록 후, 업로드 버튼을 누르면 index 페이지에 업로드한 사진의 정보가 문제없이 출력된다. 

 

 

 

마지막으로, TestModel에서 넘어온 image는 ImageField 객체로 지정되어 있는데, 이 객체는 name이라는 멤버 변수로media 폴더 내부의 저장 위치를 표시하며, url이라는 멤버변수로 Django가 참조하는 URL 주소를 반환한다. 실제 HTML 파일에서는 url 멤버변수값을 <img> 태그의 src 속성에 추가하여 이 파일을 랜더링하는 것이고 말이다. 

 

아마 업로드 이후 프로젝트 폴더를 보면 media라는, 생성하지 않은 폴더가 생길텐데, 이는 사용자의 파일 업로드와 동시에 Django에서 MEDIA_ROOT를 참고하여 생성한 폴더다. 당연히 이 파일 내부에는 업로드한 파일이 존재하고 말이다.

 

 

 


 

 

End.

반응형

댓글