블로그 이미지

LeoHeo

어제보다 더 나은 개발자가 되고자 합니다. LeoHeo - 허진한

,
Django API TDD 삽질 여행기

Django API TDD 삽질 여행기

API TDD 이 요망한놈

API를 만들면서 많은 삽질이 있었지만 그중에서 가장 삽질을 많이했던 부분이 있다.

API로 image upload 테스트를 해볼라면 어떻게?

이 포스트는 이걸 나중에 까먹더라도 쉽게 찾아보기 위해 작성한다.

시나리오

일단, 클라이언트랑 JWT를 사용해서 통신을 한다는 가정하에 시나리오를 짜보자.

  1. setUp에서 signup, login을 해서 assertEqual, aseertTrue로 token값을 얻어낸다.
  2. test method를 만들고 test_data를 만들어준다. 이때 image는 media저장 경로에 있는걸 가지고 온다.
  3. HTTP_AUTHORIZATIONJWT값을 넣고 API 테스트
  4. assertEqual로 status_code랑 값 비교

대략 적인 시나리오를 이렇게 진행했다.

Demo Code

Demo Code를 보기전에 API로 Image를 테스트 할려고 하면 SimpleUploadedFile를 이용한다.

실제로 HTML Form에서 파일을 보낼때 request.FILES를 찍어보면 InMemoryUploadedFile로 넘어온다. 이렇게 HTML Form에서 보내는거 처럼 하기 위해서 SimpleUploadedFile를 사용하면 된다.

'Programming > Django' 카테고리의 다른 글

Django ImageField Upload를 AWS S3해보자  (0) 2016.07.15
honcho start using port  (0) 2016.07.07
JWT 설명 및 djangorestframework-jwt 사용법  (0) 2016.07.07
Django Study Summary  (0) 2016.07.02
Django development Setting  (0) 2016.05.24
블로그 이미지

LeoHeo

어제보다 더 나은 개발자가 되고자 합니다. LeoHeo - 허진한

,
Django ImageField Upload를 AWS S3해보자

Django ImageField Upload를 AWS S3해보자

이 포스팅에서는 Django에서 image를 자동으로 S3에 올리는 과정과 삽질했던 과정을 기록으로 남겨 나중에 쉽게 찾아보기 위해 작성한다. AWS S3, CloudFront에 대한설명은 따로 안할 예정이다.

같이 공부하면서 프로젝트를 진행하는데 Image 저장이라는 난제에 부딪쳤다.

  1. 이미지를 업로드하면 images/<year>/<month>/<day>/<username>/[username-year-month-day.extension]형식으로 저장이 되야 한다.
  2. 현재 https를 사용하기 때문에 https에 있는 저장장소를 사용한다.

그래서 image를 upload하면

위의 형식대로 S3에 자동으로 올라가면서 나중에 CloudFront로 쉽게 땡겨올수 있게 한다.

위 작업이 되기 위해서는 선행되어야 하는 작업이 있다.

  1. S3 Bucket이 존재해야한다.
  2. CloudFront Distributions을 해서 S3랑 연결되어있어야 한다.

CloudeFront의 경우 생성시 약 10분정도 기다려야 쓸 수 있기 때문에 미리 Distributions하고 작업을 할것을 권고한다.

ImageField Settings

일단 Django에서 imageField에 대해서 Documentation을 한번 살펴보자. Django ImageField Documentation

Django에 imageField는 FileField를 상속받아서 만들어졌다.

imageField같은 경우에는 upload_to라는 옵션을 이용해서 저장되는 장소를 지정할수가 있다.

uploadto를 사용하기 위해서는 MEDIAROOT, MEDIAURL이 세팅되어 있어야 한다.
아래부분에서 이 MEDIA
ROOT, MEDIA_URL을 AWS CloudFront주소로 세팅하는걸 알아보겠다.

upload_to라는 옵션에 대한 설명은 공식문서에 아주 잘되어 있다. upload_to documentation

위와같이 폴더 지정뿐만 아니라 %Y/%m/%d라는 값을 주면 uploads/2016/07/14라는 폴더로 upload가 된다.

이걸 보고 난 Wow!를 외치며 작업이 쉬워지는듯 했으나 내가 멍청해서인가 그건 또 아니였다......

그럼 맨처음 생각했던 Format에 대해서 다시한번 생각해보자.

images/<year>/<month>/<day>/<username>/[username-year-month-day.extension]

우리가 하고자 하는건 user라는 instance를 매번 새롭게 받아야 한다.

그래서 <username>/[username-year-month-day.extension]이렇게 되어야 한다.

django upload_to may also be a callable, such as a function

위의 공식문서에 보면 upload_to에 function를 쓰는 방법을 잘 설명해주고 있다.

upload_to에 functiontwo Arguments를 받고 Unix-style path (with forward slashes)를 사용한다고 한다.

그래서 우리가 할려고 했던 대로 코드를 짜면 아래와 같다.

user_directory_path라는 function이 우리가 생각했던 directory를 생성해주는 function이다.

set_filename_format은 filename을 우리가 지정한대로 바꿔주는 function으로 이름 중복 방지를 위해 microsecond를 붙였다.

그리고 os.path.splitext(filename)[1]를 이용하면 해당파일의 확장자를 가지고 올 수가 있다.

여기까지 하면 ImageField의 세팅은 끝이다.

django-storages-redux 사용 AWS S3 자동 업로드

django에서 https일 경우 staic file을 serving하는 방법은 내가 알기론 2가지 방법이 있다.

  1. ngix serving
  2. AWS S3 + CloudFront Serving

여기서는 2번째 방법에 대해서 알아보겠다.

2번째 방법을 하기 위해서 원래 django-storages라는걸 사용하면 AWS S3에 static file을 쉽게 업로드 할 수가 있다.

근데 이 django-storages가 중간에 한번 deprecated가 되었다..

그래서 github에서 찾아보면 django-storages, django-storages-redux 등등 많은 것들이 존재하는데 여기서는 django-storages-redux라는걸 사용해 보겠다.

공식 문서를 참고하기 바란다.

사용하기 위해서 가상환경에 2가지를 설치해줘야 한다.

  1. pip install boto

  2. pip install django-storages-redux

그러고 나서 설정해주어야 하는게 몇개 있다.
일단 DJANGO_SETTINGS_MODULEdevlopment, production으로 두개를 나누었다는 전제하에 설명을 진행하겠다.
그리고 production상태에서만 static file이 S3에 upload되게 하겠다.

  1. wsgi.py파일이 있는 경로에 storage.py 만들기
  2. 기존 production.py에 몇가지 설정 추가하기

storage.py파일 만들기

storage.py에 위와 같이 추가해준다.

production.py에 추가 할 것들

공식문서에 usage부분을 보면 일단 DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'를 설정하라고 한다.

그 다음에 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_STORAGE_BUCKET_NAME도 같이 설정해준다.

여기서 ACCESS_KEY, SECRET_ACCESS_KEY는 AWS에서 권장하기를 S3에 접근할 수 있는 I AM계정을 하나 만들어서 진행하는걸 추천한다.

root계정의 ACCESS_KEY, SECRET_ACCESS_KEY를 사용하지 말자.

그리고 우리는 CloudFront를 사용할것이기 때문에 AWS_S3_CUSTOM_DOMAIN, AWS_S3_CUSTOM_DOMAIN도 같이 추가 해준다.

최종적으로 production.py에 담겨야 하는 내용은 아래와 같다.

여기까지 했다면 다 설정을 한것이다.

실제로 작동 해보기

[django path]/manage.py collectstatic --setings="appname.settings.production"를 하면 S3로 업로드가 잘 작동할것이다.

발생하는 있는 에러들

ygulify 에러

ubuntu환경에 대해서 설명을 하겠다.

'Programming > Django' 카테고리의 다른 글

Django Image Upload API TDD 삽질 여행기  (0) 2016.07.24
honcho start using port  (0) 2016.07.07
JWT 설명 및 djangorestframework-jwt 사용법  (0) 2016.07.07
Django Study Summary  (0) 2016.07.02
Django development Setting  (0) 2016.05.24
블로그 이미지

LeoHeo

어제보다 더 나은 개발자가 되고자 합니다. LeoHeo - 허진한

,
블로그 이미지

LeoHeo

어제보다 더 나은 개발자가 되고자 합니다. LeoHeo - 허진한

,

'Programming > Django' 카테고리의 다른 글

Django Image Upload API TDD 삽질 여행기  (0) 2016.07.24
Django ImageField Upload를 AWS S3해보자  (0) 2016.07.15
honcho start using port  (0) 2016.07.07
Django Study Summary  (0) 2016.07.02
Django development Setting  (0) 2016.05.24
블로그 이미지

LeoHeo

어제보다 더 나은 개발자가 되고자 합니다. LeoHeo - 허진한

,
블로그 이미지

LeoHeo

어제보다 더 나은 개발자가 되고자 합니다. LeoHeo - 허진한

,

블로그 이미지

LeoHeo

어제보다 더 나은 개발자가 되고자 합니다. LeoHeo - 허진한

,


정의

함수에 Parameter가 여러개일 경우 함수 argument로 *args를 사용하면 여러개인 Parameter를 대응할 수 있다.
naming은 임의로 *dog, *cat 으로 지을수도 있지만, 관례적으로 *args를 쓰는 편이다.

설명

def test(*args):
    print(args)


>>> num_list = [1, 2, 3]
>>> print( test(*num_list) == test(1, 2, 3))
(1, 2, 3)
(1, 2, 3)
True

궁금중을 정리해보자

  1. test(*num_list) == test(1, 2, 3)가 어떻게 True를 반환하는 것인가?
  2. num_list를 그냥 넘기는게 아니라 왜 *num_list라고 앞에 *를 붙였나?

그럼 이 2가지 궁금증을 한꺼번에 풀 수 있는 예제를 들어보고자 한다.

일단 test2라는 함수를 만들고 그 함수의 Argument로 a, b, c를 받고 출력하는 간단한 함수를 만들어보면 아래와 같다.

def test2(a, b, c):
    print(a, b, c)

이 test2라는 함수를 사용하기 위해서는 test2(1, 2, 3)와 같이 사용할 수 있다.

여기까지는 다른 웬만한 프로그래밍 언어랑 별반 차이가 없어 보인다.


하지만 여기서 Python의 Magic을 부려보자.

그 매직이란게 어떤거냐면 위에서 선언한 num_list라는 Parameter를 하나만 전달해서 에러없이 test2를 호출하는 것이다.


어떻게 이게 가능할까?


파이썬에서 Packing, UnPacking이라는 개념을 사용하면 된다.

차근차근 살펴보자

num_list을 가지고 test2를 호출할려고 하면 일단 아래와 같이 호출 할 수 있다.

>>> num_list = [1, 2, 3]

>>> test2(num_list[0], num_list[1], num_list[2])
print(1, 2, 3)

위와 같이 호출하면 문제없이 호출이 될 것이다.

하지만 위의 예제코드는 pythonic하지 않다.

pythonic하게 Refactoring를 해보면 아래와 같다.

>>> test2(*num_list)
print(1, 2, 3)

위와같이 호출하니깐 문제없이 잘 될것이다.

앞에 *가 붙는게 맨 처음을 test함수를 호출할때랑 같다.


이 과정을 설명해보자면

test2(*num_list)로 호출하면 num_list가 unpacking되어서 a=1, b=2, c=3으로 전달되어서 에러 없이 잘 실행되는 것이다.

그럼 *를 앞에 안붙이고 그냥 test2(num_list)를 하면 어떻게 될까?

>>> test2(num_list)
TypeError: test2() missing 2 required positional arguments: 'b' and 'c'

당연한 이야기 지만 2개의 필수 Arguments를 전달 안했다고 에러가 난다.

인제 함수를 호출할때 왜 *num_list라고 쓰는지 이해가 될 것이다.

그럼 인제 맨처음에 생겼던 궁금증을 정리해보자



test(*num_list) == test(1, 2, 3)가 어떻게 True를 반환하는 것인가?

  • test(*num_list라고 호출할때 *num_list로 인해 unpacking이 되어서 전달
  • *args로 인해 다시 packing되어서 출력되기 때문에 둘다 출력결과가 같으므로 True

num_list를 그냥 넘기는게 아니라 왜 *num_list라고 앞에 *를 붙였나?

  • unpacking해서 넘길려고


결론적으로 호출할때 unpacking 실행될때 packing이라고 보면 된다.

블로그 이미지

LeoHeo

어제보다 더 나은 개발자가 되고자 합니다. LeoHeo - 허진한

,