- 사용자가
choose file
버튼을 클릭 - 원하는 파일을 선택
upload
버튼을 클릭해 선택한 파일을 전송
( 3.
의 과정에서 multipart/form-data을 통해 전송합니다. )
첫번쨰 방법으로, Django
에서 기본적으로 제공하는 Image field를 이용할 수 있습니다. Image field
는 File field
와 거의 동일하지만 이미지의 가로, 세로 길이를 정해줄 수 있다는 점이 다릅니다. ( 가로, 세로 길이를 생각하지 않는다고 해도 이미지를 다루는 필드라면 Image field로 명시해주는게 협업에 있어서 좋을 것입니다. )
하지만 이것만으로는 커스텀한 이미지를 만들 수 없어서 여기서는 django-imagekit을 이용하기로 합니다. django-imagekit
은 이미지 처리 라이브러리인 Pillow를(Imaging Library) 이용해서 이미지에 변화를 줄 수 있게하는 패키지입니다.
[app]/models/image.py
from django.db import models
# Django 3rd Party Modules
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFill
class ImageExample(models.Model):
image = ProcessedImageField(
upload_to=_generate_upload_path,
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60}
)
ImageField
대신에ProcessedImageField
를 이용하였습니다. 이를 통해 얻은 이점은파일 사이즈 수정
,이미지 포맷 설정
,Opacity를 조정
할 수 있게 된 점입니다.upload_to
에_generate_upload_path
라는 이름의 함수를 넣어주었습니다. 이를 통해 이미지 업로드 경로를 동적으로 정해주도록 하였습니다.
[app]/forms/image_example_form.py
from django.forms import ModelForm
class ImageExampleForm(ModelForm):
class Meta:
model = ImageExample
fields = ['image']
- 모델로부터 폼을 만들기로 했습니다.
모델폼
을 사용한 이유는 프로그램의 무게를 최대한 모델에 주고 나머지는 가볍게 해야한다는 생각 때문입니다. 게다가 모델의 속성을 그대로 가져올 수 있어서 코드를 작성하기 간편해지는 효과 역시 있다고 생각을 합니다. fields = ['image']
라고 적힌 이 부분은 모델이 가지고 있는 'image'라는 이름의 필드를 폼(위젯)으로 만들겠다는 설정입니다.
[app]/templates/[app]/upload.html
{% extends "base.html" %}
{% load imagekit %}
{% block content %}
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<input type="submit" value="Upload" />
</form>
{% endblock %}
{% load imagekit %}
을 이용해 imagekit에 필요한 템플릿 요소를 현재 템플릿에 가져옵니다.multipart/form-data
를 이용해 파일을 전송하는 폼을 작성했습니다.{{ form }}
은뷰 코드
에서 날려준 인자입니다. 이것으로 현재 설정한폼(위젯)
을 적용하는 UI를 생성해냅니다. 물론 기능까지 합니다.
[app]/views/upload_image_example.py
from django.shortcuts import render
from [app].forms.image_example_form import ImageExampleForm
from [app].utils.handle_upload_file import handle_uploaded_file
def upload(request):
form = ImageExampleForm(request.POST, request.FILES)
if request.method == 'POST':
if form.is_valid():
handle_uploaded_file(request.FILES['image'])
return render(request, "somewhere.html")
return render(request, "[app]/upload.html", {'form': form})
FBV
로 만든 뷰 코드입니다.form = ImageExampleForm(request.POST, request.FILES)
을 통해ImageExampleForm
을 요청받을 때 사용할 폼으로 설정합니다.POST
로 날아온 요청에 대하여 코드를 수행합니다. 사용자가upload
버튼을 눌렀을 때 동작하는 부분입니다.form.is_valid()
를 통해 생각보다 많은 부분을 해결할 수가 있습니다. 맞지 않는 형식의 파일들 역시 걸러지게 됩니다.- 기본적으로 리턴 값에
{'form': form}
을 템플릿에 추가적으로 넘겨주어야 템플릿에서{{ form }}
형태로 사용이 가능하는걸 명심해야 합니다. - 모델에서 살펴보았던 동적 결로로 지정해주는 함수인
_generate_upload_path
을 호출합니다.
_generate_upload_path
는 다양하게 작성할 수 있지만 여기서는 파일 객체를 통해 업로드 경로를 지정해주는 방법을 사용하였습니다.
def _generate_upload_path(self, f):
"""
customizing file name code here
"""
return '%s/%s' % ("first_path", "second_path")
[app]/tests/views/test_upload_image_example.py
from django.test import TestCase, Client
from django.test.utils import override_settings
# Settings
from [project].settings.partials import media
# Built-in module
import os
from shutil import rmtree
import urllib2
class ImageExampleViewTest(TestCase):
"""
For saftry purpose, media.TEST_MEDIA_ROOT is used to upload test.
(after set the media.MEDIA_ROOT value by media.TEST_MEDIA_ROOT)
"""
def setUp(self):
# test upload directory
if not os.path.isdir(media.TEST_MEDIA_ROOT):
os.mkdir(media.TEST_MEDIA_ROOT)
# sample file set
TEST_IMAGE_URL = os.environ['TEST_IMAGE_URL']
self.file_path = \
"%s/%s.%s" % (media.TEST_MEDIA_ROOT, 'temp_name', 'png')
self.f = open(self.file_path, 'w+')
self.f.write(urllib2.urlopen(TEST_IMAGE_URL).read())
self.f.seek(0)
# client
self.client = Client()
self.upload_url = '/upload/'
def tearDown(self):
if os.path.isdir(media.TEST_MEDIA_ROOT):
rmtree(media.TEST_MEDIA_ROOT)
def test_media_directory_should_not_exist_after_rmtree(self):
rmtree(media.TEST_MEDIA_ROOT)
self.assertFalse(
os.path.isfile(media.TEST_MEDIA_ROOT)
)
def test_setup_should_create_media_directory(self):
self.assertTrue(
os.path.isdir(media.TEST_MEDIA_ROOT)
)
def test_sub_directory_of_media_path_should_be_created_after_upload(self):
self.client.post(self.upload_url, {'image': self.f})
self.assertTrue(os.path.isdir(media.TEST_MEDIA_ROOT + '/test/'))
# Settings for Test
ImageExampleViewTest = override_settings(
MEDIA_ROOT=media.TEST_MEDIA_ROOT,
MEDIA_URL=media.MEDIA_URL
)(ImageExampleViewTest)
- 테스트에만 적용할
MEDIA PATH
설정을 위해 settings를 override합니다. (# Settings for Test
아래 부분) - 안전성을 위해
MEDIA_ROOT
를TEST_MEDIA_ROOT
로 오버라이드한 이후에도TEST_MEDIA_ROOT
를 이용합니다. - 매 테스트 시마다 미디어 디렉토리를 생성하고 삭제하는 과정을 반복합니다. (
setUp()
에서 만들고tearDown
에서 지우고) - 올바른 이미지 파일이 아니면 업로드가 되지 않습니다. 그렇다고 테스트를 위해
레포
에 이미지 파일을 저장하는 것도 마음에 들지 않습니다. 따라서 온라인으로부터 이미지 코드를 복사하여 테스트를 진행하였습니다. (self.f.write(urllib2.urlopen(TEST_IMAGE_URL).read())
) - 파일 업로드 시에 파일을 검사하는 객체인
FileObject
는 파일 포인터를 처음으로 되돌려서 작업을 하지 않습니다. 따라서 직접 처음으로 되돌려주는 작업이 필요합니다. (self.f.seek(0)
)