페이지

2016년 6월 22일 수요일

python django datetime timezone naive aware 정리

Python 3.5.1
Django 1.9.5
>>> from datetime import datetime
>>> # 현재시간을 확인해보겠습니다.
>>> now_naive = datetime.now()
>>> str(now_naive)
'2016-06-22 15:59:26.918742'
>>> # UTC 시간을 확인해보겠습니다.
>>> utcnow_naive = datetime.utcnow()
>>> str(utcnow_naive)
'2016-06-22 06:59:53.294294'
>>> # 두문자열을 보면 어떤게 어느지역 시간인지 분간할 수 없습니다.(naive datetime)

>>> # django 에서 제공하는 현재시간입니다.
>>> from django.utils import timezone
>>> timezone_now = timezone.now()
>>> str(timezone_now)
'2016-06-22 07:05:22.279485+00:00'
>>> # 위 문자열들과 비교해보면 뒤에 +00:00 이 붙어서 UTC 시간임을 알수 있습니다.(aware datetime)

>>> # naive 시간들을 aware 로 변경해보겠습니다.
>>> now_aware_utc = timezone.make_aware(now_naive,timezone.utc)
>>> now_aware_localtime = timezone.make_aware(now_naive,timezone.get_current_timezone())
>>> str(now_aware_utc)
'2016-06-22 15:59:26.918742+00:00'
>>> str(now_aware_localtime)
'2016-06-22 15:59:26.918742+09:00'
>>> # 앞의 시간값은 같은데 뒤의 timezone 정보만 다릅니다. utc로 지정하면 +00:00 이 붙고, current_timezone 으로 지정하면 +09:00 이 붙습니다. 즉 timezone 정보가 추가됩니다.
>>> # 따라서 naive 상태의 시간이 utc인지 current_timezone인지 명확히 알고 변경해야 합니다.
>>> # datetime.now() 는 current_timezone 이므로 now_aware_localtime 이 정확한 변환값입니다.

>>> # datetime.utcnow() 는 UTC 이므로 UTC를 지정하여 변환해줘야합니다.
>>> utcnow_aware_utc = timezone.make_aware(utcnow_naive,timezone.utc)
>>> str(utcnow_aware_utc)
'2016-06-22 06:59:53.294294+00:00'


>>> # 어떤것들은 +00:00 이 붙어서 출력되므로 일괄적으로 +09:00 이 붙은상태의 localtime으로 출력해보겠습니다.
>>> str(timezone.localtime(utcnow_aware_utc))
'2016-06-22 15:59:53.294294+09:00'
>>> str(timezone.localtime(now_aware_localtime))
'2016-06-22 15:59:26.918742+09:00'
>>> str(timezone.localtime(timezone_now))
'2016-06-22 16:05:22.279485+09:00'

참고문서

https://docs.djangoproject.com/en/1.9/topics/i18n/timezones/

2016년 6월 8일 수요일

django DEBUG = False ALLOWED_HOSTS 설정

django 개발시 기본 설정은 다음과 같습니다.
DEBUG = True
ALLOWED_HOSTS = []
DEBUG = False
로 변경하면 아래와 같은 에러가 발생합니다.
CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.

DEBUG = True 일때는 상관없지만 DEBUG = False 가 되면 접속가능한 호스트를
ALLOWED_HOSTS 에 추가해줘야 합니다.

ALLOWED_HOSTS = ['127.0.0.1'] 로 설정 후
http://localhost:8000/ 로 접속시
Bad Request (400)

http://127.0.0.1:8000/ 로 접속시
접속됨

ALLOWED_HOSTS = ['localhost'] 로 설정 후
http://localhost:8000/ 로 접속시
접속됨

ALLOWED_HOSTS = ['*']
로 하여 모든 곳으로 부터의 접속을 허용할 수도 있습니다.

DEBUG = False 로 하는 경우는 대부분 운영을 위한 경우이고
운영환경의 경우 아파치나 nginx 같은 웹서버를 경유하게 되므로
ALLOWED_HOSTS 에 서버의IP 만 적어 주는 것이 좋을것 같습니다.
그리고 웹서버에서 어떻게 요청을 전달하느냐에 따라 맞춰서 설정해 줘야 합니다.

django 에서 mysql 사용하기

데이터베이스 이름을 db1
유저명을 user1
유저암호를 user1_pw

데이터베이스 추가 및 유저 추가

create database db1;
grant all privileges on db1.* to 'user1'@'127.0.0.1' identified by 'user1_pw';

127.0.0.1 는 데이터베이스 서버와 django 서버가 다른서버에 있다면 django 서버의 IP 를 입력합니다.

mysqlclient 설치 - https://github.com/PyMySQL/mysqlclient-python

$ sudo apt-get install libmysqlclient-dev
$ pip install mysqlclient

데이터베이스 설정 변경 - project/settings.py

DATABASES 항목을 찾아서 다음과 같이 수정합니다.
DATABASES = {
    'default':{
        'ENGINE':'django.db.backends.mysql',
        'NAME':'db1',
        'USER':'user1',
        'PASSWORD':'user1_pw',
        'HOST':'mysqlhost.example.com',
        'PORT':'3306',
    }
}

데이터베이스에 적용 및 수퍼유저 생성

$ python manage.py migrate
$ python manage.py createsuperuser

django 에서 유저추가 방법

1. 수퍼유저를 만듭니다.

$ python manage.py createsuperuser

2. 각각의 유저를 만듭니다.

$ python manage.py shell
>>> from django.contrib.auth.models import User
>>> u = User(username='user1')
>>> u.set_password('1111')
>>> u.is_staff=True # admin 유저의 경우 True
>>> u.save()

참고로 django rest framework 에서
다음과 같은 IsAdminUser 권한을 설정하면
u.is_staff=True 로 되어있는 유저만 접속할 수 있습니다.

from rest_framework import viewsets
from rest_framework import permissions
class UserViewSet(viewsets.ModelViewSet):
  permission_classes = (permissions.IsAdminUser,)

2016년 6월 4일 토요일

Django Rest Framework 파일만 별도로 업로드 하는 기능 구현

Django Rest Framework Image Upload Download
에서 이어집니다.

functional_tests/tests_upload.py

  • 우선 기존테스트를 복사해서 test_upload_file 로 테스트를 하나 만듭니다.
  • title만 먼저 등록하고
  • /imageuploads/{pk}/upload 로 파일만 전송하도록 수정합니다.
  • 업로드한 파일을 지우고
  • 파일없이 요청하면 400 코드를 반환하게 합니다.

  def test_upload_file(self):
    # title 만 입력하여 추가한다.
    r = requests.post(self.live_server_url + '/imageuploads/',
        data={
            'title':'Test Image'
        }
    )
    self.assertEqual(201, r.status_code)  # created
    url = r.json()['url']
    # /imageuploads/{pk}/upload 로 파일을 전송한다.
    file = open('functional_tests/test_image.png','rb')
    files = [
        ('imagefile', ('test_image.png', file, 'image/png'))
    ]
    r = requests.post(url + 'upload/',
        files=files
    )
    file.close()
    self.assertEqual(200, r.status_code)
    self.assertEqual('upload success', r.json()['status'])
    # 업로드된 파일을 지운다.
    r = requests.get(url)
    imagefile = r.json()['imagefile']
    imagefilepath = imagefile.__str__().replace(url,'')
    imagefile_realpath = os.path.abspath(os.path.join(MEDIA_ROOT, imagefilepath))
    os.remove(imagefile_realpath)
    # 파일없이 요청하면 400 코드를 반환한다.
    r = requests.post(url + 'upload/')
    self.assertEqual(400, r.status_code)
    self.assertEqual('no file', r.json()['status'])

views.py

  • request.data 에서 첫번째 키 값을 찾고
  • 키 값이 있으면 저장해주고
  • 없으면 400 코드를 반환합니다.

from rest_framework.response import Response
from rest_framework import status
  @detail_route(methods=['post'])
  def upload(self, request, pk=None):
    key = None
    for k in request.data:
      key = k
      break
    if key:
      r = self.get_object()
      r.imagefile = request.data[key]
      r.save()
      return Response({'status': 'upload success'})
    else:
      return Response({'status': 'no file'}, status=status.HTTP_400_BAD_REQUEST)
전체 소스코드는 아래 링크를 확인하세요.
https://github.com/kyuhyung-park/djangorestframework_practice/tree/only_file_upload

2016년 6월 3일 금요일

Django Rest Framework Image Upload Download


Django Rest Framework Simple Start
에서 이어집니다.

업로드 테스트

command
(virtualenv) tutorial>pip install requests


functional_tests/test_image.png 준비

functional_tests/tests_upload.py
from django.test import LiveServerTestCase
import requests
class UploadTest(LiveServerTestCase):
    def test_upload(self):
        file = open('functional_tests/test_image.png','rb')
        files = [
            ('imagefile', ('test_image.png', file, 'image/png'))
        ]
        r = requests.post(self.live_server_url + '/imageuploads/',
            data={
                'title':'Test Image'
            },
            files=files
        )
        file.close()
        self.assertEqual(201, r.status_code)  # created

command
(virtualenv) tutorial>python manage.py test functional_tests

테스트 실행하면 업로드된 파일이 tutorial 폴더에 저장된것을 확인할 수 있습니다.

git tag upload_test

업로드시 파일 저장위치 변경

settings.py 에 다음을 추가합니다.
MEDIA_ROOT = os.path.abspath(os.path.join(BASE_DIR, '../uploadfiles'))

업로드 되는 파일이 프로젝트 루트 기준으로 /uploadfiles 가 됩니다.

models.py 에서 imagefile 에 upload_to 를 추가합니다. 
imagefile = models.FileField(upload_to='imagefile/%Y/%m/%d', null=True)

이 필드로 인해 업로드되는 파일은 /uploadfiles/imagefile/2016/06/02 형태가 됩니다.

functional_tests/tests_upload.py
from tutorial.settings import MEDIA_ROOT
import os
class UploadTest(LiveServerTestCase):
    def test_upload(self):
        # 기존 코드 이후에 추가
        imagefile = r.json()['imagefile']
        imagefilepath = imagefile.__str__().replace(self.live_server_url + '/imageuploads/','')
        imagefile_realpath = os.path.abspath(os.path.join(MEDIA_ROOT, imagefilepath))
        os.remove(imagefile_realpath)

업로드된 파일을 지우는 코드입니다.
파일이 없을 경우 os.remove(imagefile_realpath) 에서 에러가 발생합니다.

command
(virtualenv) tutorial>python manage.py test functional_tests
테스트를 돌려 확인해 봅니다.

git tag change_upload_path

다운로드 구현

serializers.py
class ImageUploadSerializer(serializers.HyperlinkedModelSerializer):
    imagefile_url = serializers.HyperlinkedIdentityField(view_name='imageupload-imagefile', read_only=True)
   
    class Meta:
        model = ImageUpload
        fields = ('url', 'pk', 'title', 'imagefile', 'imagefile_url')
views.py
from rest_framework.decorators import detail_route
from django.http import FileResponse
class ImageUploadViewSet(viewsets.ModelViewSet):
    queryset = ImageUpload.objects.all()
    serializer_class = ImageUploadSerializer
   
    @detail_route(methods=['get'])
    def imagefile(self, request, pk=None):
        r = self.get_object()
        # 확장자 추출
        ext = '*'
        if r.imagefile.path:
            ext = r.imagefile.path.split('.')[-1]
        content_type = 'image/' + ext
        # 다운로드용 Response 반환
        response = FileResponse(open(r.imagefile.path, 'rb'), content_type=content_type)
        return response
tests_upload.py
test_upload 메소드 명을 test_upload_download 로 바꿈.
아래 내용 추가
import shutil
import filecmp
        # 파일의 마지막 부분을 아래와 같이 수정
        # --------------------------------
        # 다운로드 경로를 알아내고 요청을 보낸다.
        imagefile_url = r.json()['imagefile_url']
        r = requests.get(imagefile_url, stream=True)
        # 다운로드한 파일을 저장한다.
        with open('functional_tests/download.png', 'wb') as out_file:
            shutil.copyfileobj(r.raw, out_file)
        # 업로드 한 파일과 다운로드 한 파일이 같은지 비교한다.
        self.assertTrue(filecmp.cmp('functional_tests/test_image.png', 'functional_tests/download.png'))
        # 다운로드한 파일을 삭제한다.
        os.remove('functional_tests/download.png')
        # --------------------------------
       
        # 업로드된 파일을 지운다.
        imagefilepath = imagefile.__str__().replace(self.live_server_url + '/imageuploads/','')
        imagefile_realpath = os.path.abspath(os.path.join(MEDIA_ROOT, imagefilepath))
        os.remove(imagefile_realpath)

git tag download

전체 소스코드는 아래 링크를 확인하세요.
https://github.com/kyuhyung-park/djangorestframework_practice/tree/download


Django Rest Framework Simple Start

Django Project Start


>virtualenv virtualenv --python=f:\Anaconda3\python.exe
>virtualenv\Scripts\activate.bat
(virtualenv) >pip install django
(virtualenv) >pip install djangorestframework
(virtualenv) >django-admin.py startproject tutorial
(virtualenv) >cd tutorial
(virtualenv) tutorial>django-admin.py startapp quickstart
(virtualenv) tutorial>python manage.py migrate
>git init
>type .gitignore
*.pyc
/virtualenv
/tutorial/db.sqlite3
>git add -A
>git commit -m "project start"


Django Rest Framework Start and ImageUpload Model Create

settings.py


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'quickstart',
    'rest_framework',
]
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.AllowAny',),
    'PAGE_SIZE': 10
}

models.py

class ImageUpload(models.Model):
    title = models.CharField(max_length=100)
    imagefile = models.FileField(null=True)


serializers.py

class ImageUploadSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = ImageUpload
        fields = ('url', 'pk', 'title', 'imagefile')


views.py

class ImageUploadViewSet(viewsets.ModelViewSet):
    queryset = ImageUpload.objects.all()
    serializer_class = ImageUploadSerializer


urls.py

router = routers.DefaultRouter()
router.register(r'imageuploads', views.ImageUploadViewSet)
urlpatterns = [
    url(r'^', include(router.urls))
]


command

(virtualenv) tutorial>python manage.py makemigrations
(virtualenv) tutorial>python manage.py migrate
(virtualenv) tutorial>python manage.py runserver


여기까지 진행하면 http://localhost:8000/ 에 접속해서 작동하는 모습을 볼 수 있습니다.
import 문은 생략하였습니다. 전체 소스코드는 아래 링크를 확인하세요.
https://github.com/kyuhyung-park/djangorestframework_practice/tree/rest_basic