Django Template으로 파일 업로드&다운로드 기능 구현하기

Ji_min·2021년 7월 29일
1

며칠 전 장고를 사용해서 과제를 하면서 파일 업로드 & 다운로드 기능을 만들어보았다. 처음 만들어 본 기능이라서 이리저리 찾아보면서 했다. 간단하게 구현 과정을 정리해보고자 한다.


1. 파일 업로드 기능 구현하기

1-1. media root 추가하기

장고는 기본적으로 media root로 지정된 경로에 파일을 저장한다. 업로드한 파일을 관리할 media root를 settings.py 파일에 추가해준다.

...
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
...

그리고 root url 파일에도 경로를 추가해준다.

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

1-2. Model 만들기

업로드할 파일 컬럼은 filefield나 imagefield를 사용하면 된다. imagefield는 "filefield의 기능 + image인지 여부 validation 기능"이라고 생각하면 된다.

나는 PDF 파일도 받을 것이기 때문에 filefield를 사용했다.
filefield의 upload_to라는 속성을 지정해주면 지정한 루트에 파일이 저장된다.

class Document(models.Model):
    attached = models.FileField('첨부 파일', upload_to='uploads/')

참고로 field의 첫 번째 인자는 form에 나타날 이름을 지정해준 것이다.

업로드된 파일은 file object로 취급된다. 전체 속성과 메소드를 확인하려면 File Object 문서Storage Class 문서를 참고하면 된다.

속성 중에는 file name을 가져오는 name, 이미지 파일의 경우 파일 사이즈를 가져오는 size, 파일이 위치한 경로를 보여주는 path와 url 등이 있다.

>>> docs = Document.objects.get(pk=1)
>>> docs.attached
<FieldFile: uploads/choco.jpg>
>>> docs.attached.name
'uploads/choco.jpg'
>>> docs.attached.size
2054935
>>> docs.attached.path
'home/task/project1/media/uploads/choco.jpg'
>>> car.photo.url
'/media/uploads/choco.jpg'

공식 문서에는 path는 상대경로, url은 절대경로를 보여주는 속성으로 나오는데, 직접 해봤을 때는 url은 상대 경로를, path가 절대 경로를 보여줬다.

추가로, name의 경우 파일 저장 경로인 'uploads/'까지 붙여서 반환되길래 경로를 떼고 파일 이름만 가져오는 메소드를 따로 만들었다.

import os

class Document(models.Model):
    attached = models.FileField('첨부 파일', upload_to='uploads/')

    def get_filename(self):
        return os.path.basename(self.attached.name)

1-3. Form 만들기

장고 템플릿을 쓸 거라서 form을 만들어줬다.
ModelForm을 가져와서 쓰면 제일 쉬운데, 첨부파일의 경우 필수로 안받을 거라서 + CSS 적용을 위해 따로 필드를 만들어줬다.

from djangoimport forms
from .models import Document


class DocumentForm(forms.ModelForm)
    upload = forms.FileField(label='첨부 파일', required=False, 
          widget=forms.FileInput(attrs={'class': 'form'}))
    
    class Meta:
        model = Document
        exclude = ['attached']

1-4. View 만들기

view에서 업로드한 파일을 가져오려면 request.FILES['필드명']으로 가져오면 된다.
Generic CBV인 FormView를 사용했다. 장고는 이렇게 기본적으로 제공해주는 뷰 기능이 있어서 참 편하다.

from django.views.generic.edit import FormView
from django.urls import reverse_lazy


class DocumentCreateView(FormView):
    template_name = "document/new.html"
    form_class = DocumentForm
    success_url = reverse_lazy('document_list')

    def form_valid(self, form):
        if self.request.FILES:
            form.instance.attached = self.request.FILES['upload']
        
        form.save()
        return super().form_valid(form)

form_valid()는 입력된 form이 validated한지를 체크해주고 통과하면 instance를 만들어주는 메소드인데, 파일을 저장하기 위해 덮어썼다.

참고로 업로드한 파일의 content type은 self.request.FILES['upload'].content_type으로 알 수 있는데, 다운로드할 때 쓰기 위해 얘도 같이 저장해줬다.

urls.py파일에 경로를 등록하는 것은 당연해서 생략한다.

1-5. Template 파일에 enctype 지정하기

파일을 업로드하려면 그 form이 첨부 파일을 포함하고 있다는 사실을 알려줘야 한다. form 태그 속성인 enctype을 "multipart/form-data"로 지정하면 된다.

<form action="{% url 'new' %}" method="post" enctype="multipart/form-data">
  {% csrf_token %} 
  {{ form }}
</form>

이렇게 하면 파일 업로드 구현은 끝!

2. 파일 다운로드 구현하기

파일 다운로드는 view를 따로 만들었다. 업로드보다 훨씬 간단하다.

2-1. view 만들기

다운로드 받을 파일을 FileSystemStorage object로 만들어서 FileResponse로 반환해주면 된다.

from django.views.generic.detail import SingleObjectMixin
from django.http import FileResponse
from django.core.files.storage import FileSystemStorage

from .models import Document


class FileDownloadView(SingleObjectMixin, View):
    queryset = Document.objects.all()

    def get(self, request, document_id):
        object = self.get_object(document_id)
        
        file_path = object.attached.path
        file_type = object.content_type  # django file object에 content type 속성이 없어서 따로 저장한 필드
        fs = FileSystemStorage(file_path)
        response = FileResponse(fs.open(file_path, 'rb'), content_type=file_type)
        response['Content-Disposition'] = f'attachment; filename={object.get_filename()}'
        
        return response

마지막에 response에 반드시 Content-Disposition 헤더를 추가해줘서 응답에 첨부된 파일이 있다는 사실을 알려줘야 한다.

2-2. url 경로 추가하기

from django.urls import path
from .views import FileDownloadView


urlpatterns = [
    ...
    path('document/<int:document_id>/',
         FileDownloadView.as_view(), name="download"),
    ...
]

2-3. Template 파일에서 다운로드 버튼 만들기

form 태그에서 action만 지정해주면 된다.

<div>
    <label>첨부 파일</label>
    <div>{{ document.get_filename }}</div>
    <form action='{% url "download" document_id=document.pk %}'>
        <button type="submit">
        다운로드
        </button>
    </form>
 </div>

이러면 파일 다운로드 기능 구현도 끝이다!
업로드보다 훨씬 간단하다.


참고 자료

파일 업로드 구현

파일 다운로드 구현

이번에 장고 form과 generic CBV를 사용하면서 아직 모르는 게 너무 많다는 생각이 들었다. "Two Scoops of Django"라는 책을 추천받았는데, 읽어보면서 더 공부해봐야겠다.

profile
Curious Libertine

0개의 댓글