weekly learning2019. 8. 4. 23:47

# 첫째 줄에는 별 N개, 둘째 줄에는 별 N-1개, ..., N번째 줄에는 별 1개를 찍는 문제


# 정수 n을 입력 받는다.

# 2중 for문으로 외부 for문은 n만큼 반복, 내부 for문으로 n만큼 *을 출력한다.


def func(n):
    for i in reversed(range(n)):
        for _ in range(i+1):
            print('*', end='')
        print('')

if __name__ == "__main__":
    n = int(input())
    func(n)

https://www.acmicpc.net/problem/2440


# 첫째 줄에는 별 N개, 둘째 줄에는 별 N-1개, ..., N번째 줄에는 별 1개를 찍는 문제

# 하지만, 오른쪽을 기준으로 정렬한 별(예제 참고)을 출력하시오.


# 정수 n을 입력 받는다.

# 2중 for문으로 외부 for문은 n만큼 반복, 내부 for문으로 n만큼 *을 출력한다.


def func(n):
    for i in reversed(range(n)):
        string = ''
        for _ in range(i+1):
            string += '*'
        print(f'{string:>{n}}')

if __name__ == "__main__":
    n = int(input())
    func(n)

https://www.acmicpc.net/problem/2441


# 첫째 줄에는 별 1개, 둘째 줄에는 별 3개, ..., N번째 줄에는 별 2×N-1개를 찍는 문제

# 별은 가운데를 기준으로 대칭이어야 한다.


# 정수 n을 입력 받는다.

# 2중 for문을 사용. 외부 for문은 n만큼 반복(reversed)

# 내부 for문은 n만큼 공백을, 나머지는 *을 출력


def func(n):
    for i in reversed(range(n)):
        for _ in range(i):
            print(' ', end='')
        for _ in range(2*(n-i)-1):
            print('*', end='')
        print('')

if __name__ == "__main__":
    n = int(input())
    func(n)

https://www.acmicpc.net/problem/2442


# n을 입력 받는다.

# 먼저 위의 왼쪽 별 쌓기 부분을 만든다.

# 그 다음 위의 오른쪽 별 쌓기 부분을 만든다.

# 마지막으로 아래 부분을 만든다.


def func(n):
    for i in range(n):
        inner(n, i)
    for i in reversed(range(n-1)):
        inner(n, i)

def inner(n, i):
    for _ in range(i+1):
        print('*', end='')
    for _ in range(2*(n-i)-2):
        print(' ', end='')
    for _ in range(i+1):
        print('*', end='')
    print('')

if __name__ == "__main__":
    n = int(input())
    func(n)

https://www.acmicpc.net/problem/2445



이번 주 팀 프로젝트 업데이트 내용


• 지역 정보 API가 추가되었습니다.

= url : .../database/showregion/

= 이제부터 지역 정보를 서버에 저장하게 되었습니다.

= [서울, 경기, 부산/대구/경상] 3가지 지역을 서버에 저장합니다.

= 이 API는 서버에 저장된 지역 정보를 get으로 얻을 수 있습니다.


• '보고싶어' API가 추가되었습니다.

= url : .../database/checkwish/

= 사용자가 보고싶어를 클릭하면 사용자의 보고싶어 목록에 추가됩니다.

= 사용자가 보고싶어 상태가 아닌 영화의 보고싶어를 클릭하면 boolean 타입의 True 값을 반환합니다. 반대로 보고싶어를 취소하게 되면 False 값을 반환합니다.

= 이 API는 movie_id 라는 변수명(영화 id 값)을 POST 형식으로 서버에 전송해야 합니다.

= redoc 문서에 is_wished가 string으로 나오지만 실 데이터는 boolean 타입입니다. 이 부분은 추후에 수정하겠습니다.


• 영화 디테일 API가 추가되었습니다.

= url : .../database/movieDetail/?movie=

= movie에 영화의 아이디 값을 입력해야 합니다.

= ex) .../database/movieDetail/?movie=2


• 개인정보 수정 정보 출력 API가 추가되었습니다.

= url : .../accounts/showMyInfo/

= 마이페이지에서 수정하기 버튼을 눌러서 출력되는 get 정보들입니다.

= 이번에 새로 추가된 선호영화관의 경우 아래와 같은 형식으로 받아보실 수 있습니다. (id는 integer 타입입니다.)

[

{id:0, theater : string,  region: string},

{id:1, theater : string,  region: string},

{id:2, theater : string,  region: string}

]


• 개인정보 수정 API가 추가되었습니다.

= url : .../accounts/updateMyInfo/

= 마이페이지에서 수정하기 버튼을 누른 페이지에서 개인정보들을 수정하여 post로 서버에 보낼 수 있습니다.

= 개인정보는 핸드폰 번호, 비밀번호, 선호영화관을 수정할 수 있습니다.

= 이번에 추가된 선호영화관 또한 수정할 수 있습니다. 아래 create API 부분을 참조해 주세요.


• create API가 변경되었습니다.

= API 문서 이름 : UserCreateAPI -> userCreate

= 이제 선호영화관을 입력할 수 있습니다. 아래와 같은 형식, 변수명으로 보내시면 됩니다.

= 선호영화관 입력을 생략할 경우 아래 예시의 값으로 저장됩니다.

[

{id : 0, theater : '',  region: ''},

{id : 1, theater : '',  region: ''},

{id : 2, theater : '',  region: ''},

]


• UserCreateInPreferTheaterList API가 추가되었습니다.

= url : .../accounts/showCreate/

= 유저 생성시 옵션을 선택할 수 있는 선호영화관의 리스트를 get으로 얻을 수 있습니다.


• showPreferTheater API가 추가되었습니다.

= url : .../accounts/updatePreferTheater/

= 마이페이지 선호 영화관 등록/수정 버튼을 누르면 GET을 통해 선호 영화관을 출력 할 수 있습니다.


• preferTheater API가 추가되었습니다.

= url : .../accounts/modifyPreferTheater/{id}

= 마이페이지 선호 영화관 등록/수정 버튼을 누르면 POST를 통해 선호 영화관을 수정할 수 있습니다.

= {id}에 수정하려는 선호영화관 id 값을 넣으면 됩니다. id는 1순위는 0, 2순위는 1, 3순위는 2의 값을 가집니다.


• reservationScheduleList의 포스트맨 입력 방식이 변경되었습니다.

= 기존에는 영화 3개를 검색한다면 하나의 movie 키에 '알라딘_토이스토리4_라이온킹'과 같은 방식으로 검색했지만 업데이트 이후 부터는 하나의 movie 키에는 하나의 영화 값만 가질 수 있습니다. 영화를 2개 검색하고 싶다면 movie의 키를 두 개 생성 후 각각 검색을 원하는 영화 제목을 값으로 입력하시면 됩니다. (theater 또한 동일합니다.)


• 업데이트 후 데이터베이스 데이터 변동사항

= 2019-08-03 ~ 2019-08-07 까지의 영화 예매 가능 데이터를 가집니다.

= 상영 예정 영화 4개가 추가되었습니다(총 11개 영화). : 47미터 2, 동키 킹, 유열의 음악앨범, 인비저블 위트니스

= 모든 좌석은 기본적으로 예약되어 있지 않은 상태입니다.

= 테스트 아이디는 그대로 남아있습니다.


• reservationSecond의 API 주소가 변경되었습니다.

= url : .../database/reservationSecond/


• 현재 API 문서에 선호영화관(preferTheater)의 타입이 string으로 나오는 문제가 있습니다만 실 데이터는 배열 안의 JSON 형식의 타입이니 참고해주세요.



서버에 적용 하면서 변경한 점

• reservationScheduleListView()

= 영화, 지점을 배열로 받을 수 있게 수정

= 영화, 지점을 배열에 맞게 필터할 수 있도록 수정

= 영화, 지점 검색이 각 3개 초과하지 않도록 하는 것을 배열에 맞게 수정

= 스케줄에 좌석이 하나도 예매 안되었을 때 seat_number가 출력이 되지 않도록 수정


• check_wishmovies_view()

= POST method로 수정

= redoc 문서에 추가


• show_region_view()

= redoc 문서에 추가


• reservationSecondView()

= 예약되어 있는 좌석(booked_seat_numbers)이 없는 경우와 있는 경우를 if문으로 분기하는 구문 추가


• create_objects_seat()

= 디비 데이터 생성 스크립트에 좌석 넘버 자동 생성에 주석 처리


• Seat(models.Model)

= save() 빈 리스트 저장시 seat_count가 1로 카운트 되는 문제 수정


• 영화 디테일

= description 추가

= create_objects_movie_movie_detail() description 필드 입력 추가


이번 주에 대대적으로 추가된 내용이 있어서 밤도 좀 새고 했더니 좀 바빴다.


하면서 안되는 부분이 굉장히 많았고, 어려운 부분도 많았지만 결국 돌아가는데 


Posted by gawain
weekly learning2019. 7. 28. 20:01

ADT(Abstract Data Type)


간단하게 구체적인 기능의 완성과정을 언금하지 않고, 순수하게 기능이 무엇인지를 나열하는 것을 가리켜 추상 자료형 또는 간단히 ADT라 한다.


queueFIFO(First In, First Out)



queue ADT

1. Q.empty() -> Boolean : 큐가 비어있으면 True, 아니면 False

2. Q.enqueue(data) -> None : 큐의 맨 뒤에 데이터를 쌓는다.

3. Q.dequeue() -> data : 큐 맨 앞의 데이터를 삭제하면서 반환

4. Q.peek() -> data : 큐 맨 앞 데이터를 반환


stack LIFO



stack ADT

1. S.empty() -> Boolean : 스택이 비어있으면 True, 아니면 False

2. S.push(data) -> None : 스택의 맨 위에 데이터를 쌓는다

3. S.pop() -> data : 스택 맨 위의 데이터를 삭제하면서 반환

4. S.peek() -> data : 스택 맨 위 데이터를 반환


Single linked list ADT

1. S.empty() -> Boolean : 리스트가 비었다면 True, 아니면 False

2. S.size() -> integer : 리스트에 있는 요소 개수

3. S.add() -> None : 노드를 리스트의 맨 앞에 추가

4. S.search(target) -> node : 리스트에서 target을 찾는다, 찾으면 노드를, 못 찾으면 None 반환

5. S.delete() -> None : 맨 앞 노드를 삭제


Double linked list ADT

1. S.empty() -> Boolean : 리스트가 비었다면 True, 아니면 False

2. S.size() -> integer : 리스트에 있는 요소 개수

3. Insert 계열

3-1. S.add_first(data) -> None : 리스트의 맨 앞에 데이터 추가

3-2. S.add_last(data) -> None : 리스트의 맨 뒤에 데이터 추가

3-3. S.insert_after(data, node) -> Node : node 다음에 데이터 추가

3-4. S.insert_before(data, node) -> None : node 이전에 데이터 추가

4. Search 계열

4-1. S.search_forward(target) -> node : 리스트의 맨 앞 데이터부터 검색

4-2. S.search_backward(target) -> node : 리스트의 맨 뒤 데이터부터 검색

5. Delete 계열

5-1. S.delete_first() -> None : 리스트의 첫 데이터 삭제

5-2. S.delete_last() -> None : 리스트의 마지막 데이터 삭제

5-3. S.delete_node(node) -> None : 노드를 삭제



problem solving

출처 : https://www.acmicpc.net/problem/1000


def func(a, b):
    print(a+b)

if __name__ == "__main__":
    a, b = input().split()

    func(int(a), int(b))


출처 : https://www.acmicpc.net/problem/1924


# 첫째 줄에 빈 칸을 사이에 두고 x(1≤x≤12)와 y(1≤y≤31)이 주어진다. 

# 참고로 2007년에는 1, 3, 5, 7, 8, 10, 12월은 31일까지, 4, 6, 9, 11월은 30일까지, 2월은 28일까지 있다.

# 2007년 1월 1, 8일은 mon

# 0 일 / 1 월 / 2 화 / 3 수 / 4 목 / 5 금/ 6 토

# 8 월 / 9 화 / 10 수 / 11 목 / 12 금 / 13 토 / 14 일


# m은 1~12, d는 1~31 값을 입력 받는다.

# if문으로 마지막 날을 예외처리 한다.

# 1월 1일은 1, 2월 1일은 32, ...

# 요일과 달력의 값을 딕셔너리로 매핑한다.

# m과 d를 더한 값에 7로 나눈 나머지 값이 요일이 된다.


def func(m, d):
    month = {"1":0, "2":31, "3":59, "4":90, "5":120, "6":151, "7":181, "8":212, "9":243, "10":273, "11":304, "12":334}
    day_of_the_week = {0:"SUN", 1:"MON", 2:"TUE", 3:"WED", 4:"THU", 5:"FRI", 6:"SAT"}
    num = (month[m] + int(d)) % 7
    print(day_of_the_week[num])

if __name__ == "__main__":
    m, d = input().split()
    func(m, d)


출처 : https://www.acmicpc.net/problem/2438


# 첫째 줄에는 별 1개, 둘째 줄에는 별 2개, N번째 줄에는 별 N개를 찍는 문제


# 정수 n을 입력 받는다.

# 2중 for문으로 외부 for문은 n만큼 반복, 내부 for문으로 n만큼 *을 출력한다.


def func(n):
    for i in range(n):
        for _ in range(i+1):
            print('*', end='')
        print('')

if __name__ == "__main__":
    n = int(input())
    func(n)


출처 : https://www.acmicpc.net/problem/2439


# 첫째 줄에는 별 1개, 둘째 줄에는 별 2개, N번째 줄에는 별 N개를 찍는 문제

# 하지만, 오른쪽을 기준으로 정렬한 별(예제 참고)을 출력하시오.


# 정수 n을 입력 받는다.

# 2중 for문으로 외부 for문은 n만큼 반복, 내부 for문으로 n만큼 *을 출력한다.


def func(n):
    for i in range(n):
        string = ''
        for _ in range(i+1):
            string += '*'
        print(f'{string:>{n}}')

if __name__ == "__main__":
    n = int(input())
    func(n)


최근 팀 프로젝트에 힘을 쏟다보니 공부할 시간이 많지 않다.


기능 구현 하고, 서버에 올리고, 유지보수 하고..


서버에 올릴 때는 제발 문제가 안 터지길 기도 하면서 올리고 있다. ㅜㅜ


하지만 항상 바람은 이루어지지 않고 있다..


최근에는 아래 부분의 기능을 구현하고 있는데,


생각보다 마이페이지 부분 구현이 로그인 토큰과 관련되서 그런지 고려해야 할 사항들이 많은 것 같다.


빨리 mvp에서 선정한 부분들 마무리 하고 결제 구현을 시작 해야 하는데 ㅜㅜ





Posted by gawain
weekly learning2019. 7. 20. 17:55

순차 탐색(Linear Search) 알고리즘

def linear_search(li, target):
	for i in range(len(li)):
		if li[i]==target:
			return i
	return None


맨 앞에서 부터 순서대로 탐색을 진행하는 알고리즘이기에 순차 탐색이라는 이름이 붙어있다.


리스트(li)에 매개변수 target이 존재하면 target의 index 값을 반환하고, 존재하지 않으면 None을 반환한다.


위 순차 탐색 알고리즘의 비교연산횟수를 계산해보자.


위 코드에서 핵심이 되는 연산은 비교연산(==)의 횟수이다.


이름에서 알 수 있듯이 이 알고리즘은 순차적으로 탐색하여 운이 좋으면 한 번에 찾지만 운이 나쁘면(최악의 경우) 맨 마지막에서 찾을 수 있다. 즉, 수행횟수가 최대 n번이 된다는 얘기.


따라서 시간 복잡도는 O(n)이 된다.


이진 탐색(Binary Search) 알고리즘


이진 탐색은 먼저 배열에 저장된 데이터는 정렬되어 있어야 한다.



이진 탐색 알고리즘은 먼저 전체 리스트의 시작(start), 중간(mid), 끝(end) 인덱스를 구한다.


그리고 mid와 target을 비교 한다.


운이 좋아(최선의 경우) target이 mid일 때도 있지만 알고리즘에서는 최악의 경우를 생각해야 한다.



위 이미지처럼 target이 mid보다 작을 경우 6~10은 비교하지 않아도 된다.


이미 여기서 순차 탐색 알고리즘과 많은 차이가 나는 것을 알 수 있다.


이제 end를 mid 바로 이전인 4로 변경한다.


변경 후 다시 start와 end 값의 중간인 mid를 구한다.


다시 target과 mid를 비교한다.



마지막 남은 1을 반환한다.


위를 코드로 구현하면 다음과 같다.

def binary_search(li, target):
	start=0
	end=len(li)-1 # 5

	while start<=end:
		mid=(start+end)//2

		if target==li[mid]:
			return mid
		elif target>li[mid]:
			start=mid+1
		else:
			end=mid-1

	return None


시간 복잡도는 순차 탐색과 마찬가지로 비교(==)연산이 연산횟수를 대표하는 연산이며, 한 번의 비교연산을 할 때 마다 비교할 데이터의 수가 n씩 줄어든다.


결과적으로 O() 이다.


대표적인 빅-오



O(1)

상수형 빅-오. 데이터 수에 상관없이 연산횟수가 고정인 유형의 알고리즘을 뜻한다.


O()

로그형 빅-오. '데이터 수의 증가율'에 비해서 '연산횟수의 증가율'이 훨씬 낮은 알고리즘을 의미한다.



O(n)

선형 빅-오라고 한다. 데이터의 수와 연산횟수가 비례하는 알고리즘을 의미한다.


선형로그형 빅-오. 데이터의 수가 두 배로 늘 때, 연산횟수는 두 배를 조금 넘게 증가하는 알고리즘.


데이터 수의 제곱에 해당하는 연산횟수를 요구하는 알고리즘이다. 따라서 데이터의 양이 많은 경우에는 적용하기 좋지 않다. 이중으로 중첩된 반복문 내에서 알고리즘에 관련된 연산이 진행되는 경우에 발생한다.


데이터 수의 세 제곱에 해당하는 연산횟수를 요구하는 알고리즘. 삼중으로 중첩된 반복문 내에서 알고리즘 관련된 연산이 진행되는 경우에 발생한다.


지수형 빅-오. 사용하기엔 매우 무리가 있는 알고리즘.


지금까지의 빅-오 표기들의 성능 대소 비교




요즘 팀 프로젝트로 인해 평소보다 많이 바빠졌다.


팀프로젝트 하랴, 개인 프로젝트 하랴, 알고리즘, 자료구조 하랴, 개인적인 공부 하랴..


몸이 10개라도 모자란 것 같다.


당분간 블로그에는 자료구조나 알고리즘에 관한 내용을 정리해서 올릴 예정이다.


예전에 공부했던 내용들부터 최근 다시(?) 배우고 있는 트리(tree)까지.


알고리즘도 어렵고 블로그 정리도 어렵다. 참 쉬운게 없다.



Posted by gawain
weekly learning2019. 7. 14. 02:32

팀 프로젝트 관련해서 git을 다시 점검해볼 필요를 느껴 다시 정리해보았다.


팀 프로젝트를 git으로 진행하기 위해 organigztion을 생성한다.




관리자가 아닌 팀원은 모두 read로 변경(pull request 사용시)

(pull request에 관해서는 5-1 참고)



1. 프로젝트 참여자는 다음 명령으로 remote repository를 local repository에 복제한다.

'
$ git clone <url>

'''
위는 아래를 포함한 명령어이다.
$ git init
$ git remote add origin <url>
$ git fetch origin master
'''


2. git flow 초기화

$ git flow init


fetch: 원격 저장소의 데이터를 로컬에 가져오기만 하기

pull: 원격 저장소의 내용을 가져와 자동으로 병합 작업을 실행

즉, 단순히 원격 저장소의 내용을 확인만 하고 로컬 데이터와 병합은 하고 싶지 않은 경우에는 fetch 명령어를 사용한다.

pull = fetch + merge


3. github에서 develop branch를 만든다.

develop branch를 만드는 방법은 2가지가 있다.


1. github page에서 생성한다.



2. 아래 명령어로 생성한다.

    $ git branch develop
    $ git push -u origin develop
    


새로 생성한 develop branch를 default branch로 변경한다.




'develop' branch를 default branch로 설정하는 이유? 평소에는 ‘develop’ branch를 기반으로 개발을 진행하기 때문에$ git push origin some-feature(내 로컬 저장소의 some-feature branch를 중앙 원격 저장소로 올리는 명령)를 한 후, Github 페이지에서 해당 some-feature branch에 대해 merge를 할 때 중앙 원격 저장소의 ‘master’ branch가 아닌 default로 설정되어 있는 ‘develop’에 병합하도록 설정하는 것이다.



GitHub 페이지에서 자신이 push한 ‘develop’ branch를 병합해달라는 pull request를 날린다.

프로젝트 관리자는 해당 pull request를 merge하여 새로운 ‘develop’ branch를 중앙 원격 저장소에 생성한다.



4. branch 생성 및 전환

# 새로운 기능 개발을 위한 feature
$ git flow feature start <branch_name>


5-1. 새로운 기능 개발 push 후 pull request

# 모든 파일을 add할 경우 $ git add .
$ git add 
$ git commit
$ git push -u origin feature/
# 이 후 관리자에게 pull request 한다.
# 관리자가 승인하면 develop branch에 merge 된다.

# 더 이상 새로운 기능 개발이 필요하지 않다면 branch를 finish한다.
# 근데 finish하면 자동으로 디벨롭으로 merge 되는거 아님?
# 그럼 develop에 merge 하는 순간 develop에는 push가 장전되버림; 즉 commit이 된거임.
# 그렇다고 finish를 안하고 그냥 branch를 없애기엔 git flow의 장점을 이용 안하는 느낌이라..

# 결론은 pull request를 쓸거면 쓰고. git flow를 쓸 거면 쓰고. 둘 중 하나만 쓰자.


5-2. 새로운 기능 개발 push (pull request를 이용하지 않는 경우)

# ... 기능 개발

# 먼저 develop branch의 최신 상태를 loca repo로 가져온다.
(branch_name) $ git checkout develop
(develop) $ git pull origin develop
# 최신 상태로 만들었다면 기능 개발했던 bracnh를 develop에 merge 한다.
(develop) $ git checkout feature/ (안되면 그냥 )
# 모든 파일을 add할 경우 $ git add .
(branch_name) $ git add 
(branch_name) $ git commit

'''
# 여기서 이 branch로 계속 기능 개발이 필요할 경우 finish를 하지 않고 develop에 push, merge를 한다.
# 또는 기능 구현 테스트만 할 때는 이 branch로 push만 하고 나중에 merge를 하던 finish를 하던 폐기를 하던 한다.
'''

(branch_name) $ git flow feature finish 
# 마지막으로 remote repo에 push
(develop) $ git push -u origin develop


6. 배포하기

# develop branch에서 release branch를 딴다. 마지막 숫자는 19년 7월 12일 001 번째 릴리즈
(develop) $ git flow release start 00190712001
# release branch에서는 버그 픽스에 관한 부분만 commit만 한다. 어차피 master에 merge할거니까.

#... 버그 픽스 중..

# 모든 준비가 완료되면 release를 master로 merge 한다.
(00190712001) $ git flow release finish 00190712001
# 알맞게 release 문서를 작성한다.

# master로 branch 전환
(develop) $ git checkout master
# master push
(master) $ git push -u --tags origin master
# develop branch 전환
(master) $ git checkout develop


번외. Hotfix

# master brnach에서 급하게 수정해야할 버그가 생겼다면 master에서 Hotfix branch를 딴다.
(master) $ git flow hotfix start 00190712002

# 수정 중..

(00190712002) $ git flow hotfix finish 00190712002


git flow가 아닌 pull request를 사용하는 경우


1. fork를 뜰 git page 이동 후 fork를 누른다.




2. 자신의 계정에 fork를 뜬다.


3. 이제 git gui에 clone을 뜨고, git을 마음껏 사용하면 된다.



전체적인 틀은 이렇게 잡고, 어떤 방식으로 git을 관리할 것인지는 팀원들과 좀 더 세세한 얘기를 해봐야 할 것이다.


개인적으로 느끼기에는 fork를 뜨는 방식보다


약간 손은 더 가지만 git flow를 이용해 merge나 issue 등을 자동으로 관리하는 방식을 선호한다.


pull request를 관리하기 위해 github 웹 사이트를 왔다갔다 하는 방식보다 명령어로 깔끔하게 해결하는 방식이 좀 더 세련되 보이기 때문이다.


많은 내용을 다룬 것은 아니지만 아직은.. 이정도면 충분한 것 같다.


물론 프로젝트 도중 git 때문에 막히는게 생기면(생길 것 같다..) 사실 좀 시간이 아까워서 좀 더 공부를 해야하는 것을 느끼지만 지금 더 파면 방향성 없이 공부 할 것 같고.. 다른 공부 해야할 것이 많기 때문에 git은 이걸로 일단락 하기로 하자.


Posted by gawain
weekly learning2019. 7. 7. 16:55

도커로 배포하기


수업 시간에 많은 배포 방식을 배웠다. aws, pythonanywhere, heroku, elastic beanstalk ...


그 마지막인 도커에 대해 정리 해보자.


도커는 virtualBox와 같은 가상 컴퓨팅 환경과 비슷한 컨테이너 서비스이다.


과거에는 LXC(Linux Container) 방식으로 구현되어 리눅스 시스템이 아닌 곳에서는 도커를 사용하지 못했지만, runC라는 방식으로 변경이 되어 윈도우, 맥, 리눅스 등 다양한 운영체제에서 도커 시스템을 사용할 수 있다.


도커를 사용하려면 도커 CLI가 설치되어 있어야 한다.


먼저 도커허브에 가입하자.

https://hub.docker.com


리눅스에서 도커 설치 방법

리눅스에서는 간단한 명령어로 도커를 설치할 수 있다.

$ curl -s https://get.docker.com/ | sudo sh


만약 curl이 없다면

 $ sudo apt-get -y install curl


도커를 관리자 계정으로 실행해야 하는 번거로움을 없애기 위해 아래 명령 실행

$ sudo usermod -aG $USER


docker 명령을 실행해 옵션이 나타났다면 잘 설치가 된 것.


맥에서 도커 설치

맥의 경우 도커허브 첫 화면에서 Get started with Docker Desktop 버튼을 클릭해 다운로드를 받는다.


다운 받은 파일을 실행해 도커 데스크톱을 설치한다.


다운받은 이미지를 실행해 도커를 Applications 폴더로 복사하면 설치가 끝난다.


도커를 실행하면 권한이 필요하다는 메시지가 나오는데 비밀번호 입력창이 나타나면 계정 비밀번호를 입력한다.


모든 과정이 완료되면 로그인창이 뜨는데 로그인 후 터미널에서 docker 라는 명령어를 실행하면 위 리눅스와 같은 옵션 목록이 나타나면 정상 설치가 된 것이다.


이제 도커를 사용해서 ubuntu 18.04 가상 컨테이너를 사용해 보자.


run이라는 명령어를 사용하여 이미지명을 지정하고 새로운 컨테이너를 실행할 수 있다.


주의할 점은 도커 컨테이너는 프로세서가 돌고 있는 동안만 유효하다.


$ sudo docker run ubuntu:18.04


위 명령을 실행하면 우분투 이미지를 다운로드하고 컨테이너를 실행하게 된다. 현재 로컬에 있는 이미지 목록을 보려면 다음 명령을 실행한다.

$ docker image ls



우분투의 배시 쉘을 실행해 보자.

 $ docker run --rm -it ubuntu:18.04 bin/bash


--rm : 실행 종료 후 컨테이너 제거

-it : 쉘을 실행할 경우 표준 입출력 연결


이제 원하는 명령을 컨테이너에 전달할 수 있다.


nginx를 설치하고 테스트 해보자.


nginx를 설치하고 테스트하려면 포트 포워딩이 필요하다. run 명령어에 -p 옵션을 넣으면 포워딩 포트를 지정할 수 있다.

$ docker container run --rm -it -p 8080:80 ubuntu:18.04 bin/bash
$ apt-get -y update
$ apt-get -y install curl
$ apt-get -y install nginx
$ nginx -v
$ service nginx start



nginx -v : 버전 확인을 위한 명령어


웹 브라우저에서 로컬호스트에 8080 포트를 이용해 접속해보자.


호스트의 8080 포트느는 컨테이너의 80 포트로 포워딩 되기 때문에 nginx의 환영 메시지를 확인할 수 있다.

$ service nginx start



이제 원하는 이미지를 베이스로 추가 프로그램 설치를 진행하여 최종 이미지를 생성해 보도록 하겠다.


FROM은 베이스가 되는 이미지, MAINTAINER는 본인 이메일, RUN은 이미지 생성시 실행할 명령어를 입력한다.

 $ vim Dockerfile.base

FROM ubuntu:18.04
MAINTAINER linux@linux.lix
RUN apt-get -y update
RUN apt-get -y install nginx

CMD ["nginx", "-g", "daemon off;"]


위의 파일을 기반으로 새로운 이미지를 생성한다.

$ docker image build -f Dockerfile.base -t peaceful/django:1.0 .

peaceful/django:1.0 : 계정명/이미지명:버전명


생성된 이미지를 목록에서 확인해 보자.

$ docker image ls




생성된 이미지로 컨테이너를 실행해 보자.

$ docker container run -it --rm -p 8080:80 peaceful/django:1.0


웹 브라우저로 접속하면 nginx가 실행되어 있는 것을 확인할 수 있다.


실행되어 있는 컨테이너 목록을 확인하려면 다음 명령어를 실행한다.


컨테이너에 추가 명령을 전달하려면 container id나 name을 사용하기 때문에 목록을 확인할 필요가 있다.


별도로 일므을 지정하지 않고 컨테이너를 실행하면 자동으로 이름이 만들어 진다.

$ docker container ls


실제로 웹 서버를 배포할 때는 nginx와 uwsgi가 모두 동작하는 형태로 배포해야 한다.


도커의 특성상 하나의 프로세스만 동작하는 명령을 실행할 수 있고, 동작하고 있는 프로세스가 없는 경우 컨테이너는 종료된다.


이런 문제를 해결하기 위해서 supervisor라는 모듈을 사용할 수 있다.


도커 파일을 수정해서 supervisor를 설치하고 설정 파일을 복사하는 명령어를 입력한다.

$ vim Dockerfile.base

RUN apt-get -y update
RUN apt-get -y install supervisor
ADD ./conf/supervisord.conf /etc/supervisor/supervisord.conf

CMD ["/usr/bin/supervisord"]


supervisord.conf 파일을 작성하기 위해 다음 명령을 입력한다.

$ mkdir conf
$ vim conf/supervisord.conf

[supervisord]
nodaemon=true

[program:nginx]
command = nginx -g "daemon off;"


새로운 내용으로 이미지를 만들어 보자.

$ docker image build -f Dockerfile.base -t peaceful/django:2.0 .


만약 캐시 때문에 문제가 발생한다면 --no-cache 옵션을 더해준다.

$ docker image build --no-cache -f Dockerfile.base -t peaceful/django:2.0 .


새로운 이미지로 컨테이너를 실행해 보자. 컨테이너가 실행되고 있을 때 웹 브라우저에서도 정상 응답이 있는지 확인해 보자.

$ docker container run --rm -it -p 8080:80 peaceful/django:2.0



아래 명령어를 이용해 도커 로그인 정보 파일을 만든다

$ docker login


이제 ~/.docker/config.json 파일이 만들어졌을 것인데, 해당 파일을 S3 버킷을 만들어 업로드 한다.


eb init 명령을 실행한다.

$ eb init


Dockerrun.aws.json 파일을 만들어 필요 내용을 입력한다.

{
     "AWSEBDockerrunVersion": "1",
     "Authentication": {
         "Bucket": "nginx-docker",
         "Key": "config.json"
     },      
     "Image": {
         "Name": "peaceful/django",
         "Update":"true"
     },      
     "Ports": [
         {
             "ContainerPort": "80"
         }       
     ],
     "Logging": "/var/log"
 } 


aws의 elastic beanstalk 페이지에 설정 파일을 업로드 하고 환경을 추가한다.


eb list 명령을 이용해 환경 목록을 확인한다.

$ eb list


eb deploy 명령을 실행한다.

$ eb deploy [환경명]


이번 주 부터는 한 달간 타 스쿨 분들과 함께 프로젝트를 진행하는 기간이다.


이미 프론트, ios 분들과 어떤 사이트를 만들지 얘기가 끝난 상태이며, 같은 백엔드 분들과 기본 모델링을 작성 중에 있다.


얘기를 하면서 느낀 것은 모두 적극적으로 임하는 것이 보기 좋았고, 좋은 결과물이 나올 것이라는 느낌이 든다.


그리고 이번 주 수요일에 ERD 특강이 있었는데, 사실 시간대비 그닥 도움은 안되었다. ERD에  대한 내용보다 데이터베이스의 중요성 등 외적인 이야기를 많이 해서 ERD에 대해 많이 알지 못했다.


다음 주 수요일에 마지막 특강이 있는데, 그 때는 ERD에 대해 좀 더 집중해서 수업이 진행되었으면 한다.


Posted by gawain
weekly learning2019. 6. 29. 19:49

Django Rest Framework 사용하기


먼저 Django Rest Framework를 설치한다.

pip install djangorestframework


설치 했으니 INSTALLED_APPS에 추가한다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
]


앱과 모델을 간단하게 만들어보자.

from django.db import models
from django.contrib.auth import get_user_model

class Tweet(models.Model):
    author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='tweets')
    text = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)


API 뷰를 동작하게 하려면 Serializer와 View 두 가지가 필요하다.

먼저 앱의 디렉토리에 serializers.py 파일을 만든다.

tweet 앱에 serializers.py를 생성했다.


serializers.py

class TweetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tweet
        fields = '__all__'


기본 뷰를 작성한다. API 뷰도 함수형, 클래스형 뷰를 만들 수 있다.

from rest_framework import generics
from .serializers import TweetSerializer
from .models import Tweet

class TweetView(generics.ListCreateAPIView):
    queryset = Tweet.objects.all()
    serializer_class = TweetSerializer


뷰를 urls.py에 추가한다.

from django.urls import path
from .views import TweetView

urlpatterns = [
    path('', TweetView.as_view()),
]


config/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('tweet.urls')),
]


브라우저를 통해 뷰가 동작하는지 확인한다.


목록 뷰를 추가했으니 수정, 삭제를 위한 뷰를 추가한다.

class TweetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Tweet.objects.all()
    serializer_class = TweetSerializer


urls.py에 뷰를 추가한다.

from django.urls import path
from .views import TweetView, TweetDetail

urlpatterns = [
    # ...
    path('detail/<int:pk>/', TweetDetail.as_view()),
]


마찬가지로 브라우저를 통해 동작을 확인한다.

url : http://127.0.0.1:8000/detail/1/


지금까지는 일반 API 뷰 렌더링 페이지가 아닌, 개발 편의를 위해 제공되는 BrowsableAPIRenderer 페이지이다.


나오는 형식을 제한 하기 위해서는 주소에 형식을 기입하거나 뷰에서 설정해야 한다.


url에 http://127.0.0.1:8000/?format=json 와 같이 주소에 format 인자를 추가해 결정


또는


뷰에 renderer_class를 명시하여 지정

from rest_framework.renderers import JSONRenderer
class TweetDetail(generics.RetrieveUpdateDestroyAPIView):
    # ...
    renderer_classes = [JSONRenderer]



이제 API 문서를 위한 Swagger를 설치하여 사용해보자.

pip install django-rest-swagger==2.1.2


INSTALLED_APPS에 추가해준다.

INSTALLED_APPS = [
    # ...
    'rest_framework_swagger',
]


config/urls.py에 swagger 뷰를 추가한다.

from rest_framework_swagger.views import get_swagger_view

schema_view = get_swagger_view(title='Tweet API')

urlpatterns = [
    # ...
    path('api/doc/', schema_view),
]


브라우저를 통해 페이지에 접속해 본다. Swagger가 자동으로 API 문서 뷰를 생성해 주기 때문에 편리하게 사용할 수 있다.



이제 인증 과정을 추가하자. API를 만들 때 가장 많이 사용하는 방식이 토큰 방식이다.


INSTALLED_APPS에 아래를 추가한다.

INSTALLED_APPS = [
    # ...
    'rest_framework.authtoken',
]


토큰 기능을 추가하면 데이터베이스에 추가 테이블이 필요하다. migrate 명령을 실행한다.

python manage.py migrate


토큰 앱을 추가하면 토큰을 자동 생성하는 기능 역시 추가되어 있다.


토큰을 발급받는 뷰만 추가해주면 토큰을 받아서 확인할 수 있다.


config/urls.py에 뷰를 추가한다.

from rest_framework.authtoken.views import obtain_auth_token

urlpatterns = [
    # ...
    path('api/get_token/', obtain_auth_token),
]


이제 Swagger 페이지에 접속해서 토큰을 발급받아 보자.


data의 계정명과 비밀번호를 "string" 대신에 입력한다.


Try it out! 을 누르면 다음 처럼 토큰을 발급받을 수 있다.


인증 기능을 추가했다면 모든 API뷰가 로그인 해야만 동작할 수 있도록 추가할 수 있다.


config/settings.py에 관련 설정을 추가한다.

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
}


이제 모든 뷰는 토큰 인증 등 로그인 사용자만 사용할 수 있는 상태가 되었다. 하지만 회원 가입 등의 뷰는 인증 없이도 동작해야 한다.


만약 인증없이 사용해야 하는 뷰가 존재 한다면 다음과 같이 추가한다.

from rest_framework.permissions import AllowAny

class SomeView(SomeGenericView):
    permission_classes = (AllowAny,)


인증 기능을 이용하려면 POST맨 등에서 토큰을 전달해 접속해야 한다.


하지만 테스트를 할 때는 불편하므로 Swagger에 Token 인증 방법을 추가하고 사용할 수 있도록 해보자.


settings.py에 기본 인증 설정을 추가한다.

REST_FRAMEWORK = {
    # ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
    ),
}

SWAGGER_SETTINGS = {
    'SECURITY_DEFINITIONS': {
        "api_key": {
            "type":"apiKey",
            "name":"Authorization",
            "in":"header",
        }
    }
}


다음 주 부터 다른 스쿨 분들과 협업을 하는 시간이 주어지는데 이 API를 유용하게 사용할 수 있다.


아직 사용법에 있어서 크게 와 닿지는 않지만 직접 다른 분들과 협업을 하면서 이것 저것 건들다 보면 감이 올 것이라고 생각한다.



Posted by gawain
weekly learning2019. 6. 22. 12:17

장고에서의 번역 기능인 I18N(Internationalization)을 사용해보자.


기본 언어를 변경하는 방법은 settings.py에서 다음과 같이 설정해준다.

LANGUAGE_CODE = 'ko-kr'

TIME_ZONE = 'Asia/Seoul'


TIME_ZONE을 'asia/seoul'로 해줘도 되지만 나는 에러가 나길래 첫 글자만 대문자로 변경하였다.


다국어 지원을 하려면 LocaleMiddleware를 추가한다. 여기서 주의할 점은 Session과 Common 사이에 위치시켜야 한다.

MIDDLEWARE = [
    # ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    # ...
]


이 부분도 생각없이 MIDDLEWARE 가장 아래에 작성했다가 제대로 동작이 안되는 문제가 발생했다.


이어서 지원하고 싶은 언어 목록을 작성한다.

from django.utils.translation import ugettext_lazy as _
LANGUAGES = [
    ('ko', _('Korean')),
    ('en', _('English')),
    ('jp', '日本語'),
]


다음으로 뷰에서 사용할 번역 대상 텍스트에 대한 번역 파일을 만든다. 

LOCALE_PATHS = [
    os.path.join(BASE_DIR, 'locale')
]


그리고 프로젝트 최상단에 'locale' 디렉토리를 생성한다.

$ mkdir locale


번역할 텍스트가 있는 간단한 뷰를 작성한다.

from django.utils.translation import ugettext_lazy as _
from django.http import HttpResponse
def index(request):
    msg = _('안녕하세요')
    return HttpResponse(msg)


이제 번역할 텍스트 목록 파일을 만들어야 한다.

$ python manage.py makemessages -a
$ python manage.py makemessages -l ko
$ python manage.py makemessages -l en
$ python manage.py makemessages -l jp


만약 gettext가 없다는 오류 메시지가 나타나면 아래 명령어를 실행해서 해결한다.

Mac

$ brew install gettext
$ brew link gettext --force


Ubuntu

$ sudo apt-get install gettext


위 명령어를 실행하면 아래와 같은 디렉토리와 파일들이 생성된다.


django.po 파일들을 열어서 #, fuzzy 라고 씌여진 코드를 삭제한다.


그리고 msgid와 msgstr에 아래처럼 적당한 번역 내용을 작성한다. (아래는 ko의 django.po)


en의 po 파일도 수정한다.


이제 번역한 내용을 장고에서 처리할 수 있도록 mo 파일을 아래 명령어로 만든다.

$ python manage.py compilemessages


만약 언어 파일별로 컴파일을 하고 싶다면 다음과 같이 작성한다.

$ python manage.py compilemessages -l ko


번역이 정상 동작하는지 확인하기 위해 views.py 에서 강제로 언어 설정을 변경해보자.

def index(request):
    if translation.LANGUAGE_SESSION_KEY in request.session:
        del(request.session[translation.LANGUAGE_SESSION_KEY])
    translation.activate('en')
    request.session[translation.LANGUAGE_SESSION_KEY] = 'en'

    msg = _("안녕하세요")
    return HttpResponse(msg)


설정 후 template을 확인해보면 '안녕하세요'가 en의 django.po 파일을 수정했던 내용대로 'Hello'가 출력된 것을 확인할 수 있다.


다음으로는 언어설정을 사용자가 선택해서 변경할 수 있도록 하는 뷰를 만들어보자.


쿼리스트링으로 language code 받기

def index(request):
    if request.method == "GET":
        lan = request.GET.get('language')
        if lan == 'ko' or lan == 'en' or lan == 'jp':
            if translation.LANGUAGE_SESSION_KEY in request.session:
                del (request.session[translation.LANGUAGE_SESSION_KEY])

            translation.activate(lan)
            request.session[translation.LANGUAGE_SESSION_KEY] = f'{lan}'
        else:
            if translation.LANGUAGE_SESSION_KEY in request.session:
                del (request.session[translation.LANGUAGE_SESSION_KEY])

            translation.activate('ko')
            request.session[translation.LANGUAGE_SESSION_KEY] = 'ko'

    msg = _("안녕하세요")
    return HttpResponse(msg)


쿼리스트링을 다음과 같이 받았을 때


영어로 번역된 것을 볼 수 있다.


다음은 템플릿에서의 번역 기능도 사용해보자.


템플릿에서 번역 기능을 사용하려면 아래 처럼 사용한다.

{% load i18n %}
{% trans '안녕하세요' %}


또 템플릿에서는 여러 줄이 들어가 있는 번역 블록을 설정할 수도 있다.


{% blocktrans %}
블록번역 테스트
블록번역 태그의 번역 테스트
{% endblocktrans %}

{% blocktrans %}
블록번역 테스트 {{value}}입니다
블록번역 태그의 번역 테스트
{% endblocktrans %}


일반 번역뿐 아니라 context를 가진 번역을 진행할 수 있다.

from django.utils.translation import pgettext_lazy as _
def index(request):
    msg = _("구어", "안녕하세요")
    return HttpResponse(msg)


context가 있는 번역을 템플릿에서 사용하려면 blocktrans를 사용한다.

{% blocktrans context "구어" %} 안녕하세요 {% endblocktrans %}


url을 사용해서 언어를 선택하고 싶다면 config/urls.py에

from django.conf.urls.i18n import i18n_patterns
urlpatterns += i18n_patterns(
    path('', include('sample.urls'))
)


기본 언어에 대한 url 인자를 제거하고 싶다면 마찬가지로 config/urls.py에

from django.conf.urls.i18n import i18n_patterns
urlpattenrs += i18n_patterns(
    path('', include('sample.urls')),
    prefix_default_language=false


이상으로 장고에 번역 기능을 추가하는 방법을 간단하게 포스팅해보았다.


이로써 이번 주를 끝으로 장고 수업은 끝이 났는데, 길다면 길고 짧다면 짧은 시간이였던 것 같다.


장고에 대해서 전체적으로 배웠는데, 포스팅에 올리지 못한 것들이 더 많은 것 같다.


머리로는 어느정도 알아도 글로 작성하려니 조금 힘든 부분도 없지않아 있기도 하고..


공부한 내용을 정리한다는게 참 쉽지 않은 것 같다.


장고 막바지에 들면서 느낀 것은 배우는 기간을 좀 더 해서 하나하나 좀 더 깊이 있게 배웠으면 좋았을텐데 하는 아쉬움이 남는다.


나머지 부족한 부분들은 개인적인 공부를 통해 채워나가야 할 것 같다. 또 너무 이론적인 것에 치우치지 않게 수업이 진행되는 동안 개인적으로 만들고 있던 포트폴리오를 꾸준히 만들어 나가는 것도 잊지 말아야 하고 말이다.


이제 앞으로 타 스쿨 분들과의 팀 프로젝트가 남았는데, 예전 부터 기대하고 있던 것이지만 역시나 막상 하려고하니 기대보단 걱정이 앞서는 것 같다.



Posted by gawain
weekly learning2019. 6. 15. 16:36

온라인 쇼핑에 필요한 결제 모듈에 대해 알아보자.


먼저 결제 api를 제공하는 iamport에 가입한다.

https://www.iamport.kr/


가입하고 로그인하면 관리자 페이지에 접속할 수 있다.


다음으로 시스템설정 메뉴의 하위 메뉴인 PG설정에 들어간다. 그리고 이미지처럼 PG사를 선택하고 테스트모드를 꼭 ON으로 설정하고 스크롤을 내려 전체 저장을 누른다.


PG설정 옆에 내정보 메뉴를 확인하면 코드 내에서 API 관련 설정해줘야 하는 값들을 확인할 수 있다.


아래는 iamport 결제와 관련된 javascript 코드다.

IMP.init 부분을 위 내정보에서 확인할 수 있는 가맹점 식별코드로 바꿔준다.


이 checkout.js 파일은 이번 수업에서는 order app 내에 static/js 디렉토리를 만들어서 그 안에 넣었다.

$(function () {
    var IMP = window.IMP;
    IMP.init('/* 가맹점 식별 코드 */');
    $('.order-form').on('submit', function (e) {
        var amount = parseFloat($('.order-form input[name="amount"]').val().replace(',', ''));
        var type = $('.order-form input[name="type"]:checked').val();
        // 폼 데이터를 기준으로 주문 생성
        var order_id = AjaxCreateOrder(e);
        if (order_id == false) {
            alert('주문 생성 실패\n다시 시도해주세요.');
            return false;
        }

        // 결제 정보 생성
        var merchant_id = AjaxStoreTransaction(e, order_id, amount, type);

        // 결제 정보가 만들어졌으면 iamport로 실제 결제 시도
        if (merchant_id !== '') {
            IMP.request_pay({
                merchant_uid: merchant_id,
                name: 'E-Shop product',
                buyer_name:$('input[name="first_name"]').val()+" "+$('input[name="last_name"]').val(),
                buyer_email:$('input[name="email"]').val(),
                amount: amount
            }, function (rsp) {
                if (rsp.success) {
                    var msg = '결제가 완료되었습니다.';
                    msg += '고유ID : ' + rsp.imp_uid;
                    msg += '상점 거래ID : ' + rsp.merchant_uid;
                    msg += '결제 금액 : ' + rsp.paid_amount;
                    msg += '카드 승인번호 : ' + rsp.apply_num;
                    // 결제가 완료되었으면 비교해서 디비에 반영
                    ImpTransaction(e, order_id, rsp.merchant_uid, rsp.imp_uid, rsp.paid_amount);
                } else {
                    var msg = '결제에 실패하였습니다.';
                    msg += '에러내용 : ' + rsp.error_msg;
                    console.log(msg);
                }
            });
        }
        return false;
    });
});

// 폼 데이터를 기준으로 주문 생성
function AjaxCreateOrder(e) {
    e.preventDefault();
    var order_id = '';
    var request = $.ajax({
        method: "POST",
        url: order_create_url,
        async: false,
        data: $('.order-form').serialize()
    });
    request.done(function (data) {
        if (data.order_id) {
            order_id = data.order_id;
        }
    });
    request.fail(function (jqXHR, textStatus) {
        if (jqXHR.status == 404) {
            alert("페이지가 존재하지 않습니다.");
        } else if (jqXHR.status == 403) {
            alert("로그인 해주세요.");
        } else {
            alert("문제가 발생했습니다. 다시 시도해주세요.");
        }
    });
    return order_id;
}

// 결제 정보 생성
function AjaxStoreTransaction(e, order_id, amount, type) {
    e.preventDefault();
    var merchant_id = '';
    var request = $.ajax({
        method: "POST",
        url: order_checkout_url,
        async: false,
        data: {
            order_id : order_id,
            amount: amount,
            type: type,
            csrfmiddlewaretoken: csrf_token,
        }
    });
    request.done(function (data) {
        if (data.works) {
            merchant_id = data.merchant_id;
        }
    });
    request.fail(function (jqXHR, textStatus) {
        if (jqXHR.status == 404) {
            alert("페이지가 존재하지 않습니다.");
        } else if (jqXHR.status == 403) {
            alert("로그인 해주세요.");
        } else {
            alert("문제가 발생했습니다. 다시 시도해주세요.");
        }
    });
    return merchant_id;
}

// iamport에 결제 정보가 있는지 확인 후 결제 완료 페이지로 이동
function ImpTransaction(e, order_id,merchant_id, imp_id, amount) {
    e.preventDefault();
    var request = $.ajax({
        method: "POST",
        url: order_validation_url,
        async: false,
        data: {
            order_id:order_id,
            merchant_id: merchant_id,
            imp_id: imp_id,
            amount: amount,
            csrfmiddlewaretoken: csrf_token
        }
    });
    request.done(function (data) {
        if (data.works) {
            $(location).attr('href', location.origin+order_complete_url+'?order_id='+order_id)
        }
    });
    request.fail(function (jqXHR, textStatus) {
        if (jqXHR.status == 404) {
            alert("페이지가 존재하지 않습니다.");
        } else if (jqXHR.status == 403) {
            alert("로그인 해주세요.");
        } else {
            alert("문제가 발생했습니다. 다시 시도해주세요.");
        }
    });
}


위 코드에서 아래 부분은 상품명, 구매자 관련 정보를 결제창에서 보여줄 내용들인데 자신의 환경에 맞게 설정해주면 된다.

// 결제 정보가 만들어졌으면 iamport로 실제 결제 시도
        if (merchant_id !== '') {
            IMP.request_pay({
                merchant_uid: merchant_id,
                name: 'E-Shop product',
                buyer_name:$('input[name="first_name"]').val()+" "+$('input[name="last_name"]').val(),
                buyer_email:$('input[name="email"]').val(),
                amount: amount
            },


그리고 iamport는 jquery를 slim 버전으로 사용하고 있으면 작동하지 않으므로 minified 버전으로 교체한다.

https://code.jquery.com/


order/admin.py

from django.contrib import admin

from .models import Order, OrderItem, OrderTransaction

class TransactionInline(admin.TabularInline):
	model = OrderTransaction

class OrderItemInline(admin.TabularInline):
	model = OrderItem
	raw_id_fields = ['product']

class OrderOption(admin.ModelAdmin):
	list_display = ['id','first_name','last_name','email','paid','created','updated']
	list_editable = ['paid'] # TabularInline : admin에서 테이블 형태로 볼 수 있음
	inlines = [OrderItemInline]

admin.site.register(Order, OrderOption)


iamport 관리자 사이트의 내정보에서 확인할 수 있는 키를 입력해준다.

config/settings.py

IAMPORT_KEY = 'REST API 키'
IAMPORT_SECRET = 'REST API secret'


iamport Document(https://api.iamport.kr/)를 이용해서 만든 결제에 필요한 함수들의 모음 파일이다.

order/iamport.py

# pip install requests

from django.conf import settings

def get_token():
	access_data = {
		'imp_key': settings.IAMPORT_KEY,
		'imp_secret':settings.IAMPORT_SECRET
	}

	url = 'https://api.iamport.kr/users/getToken'
	req = requests.post(url, data=access_data)
	data = req.json()
	if data['code'] is 0:
		return data['response']['access_token']
	else:
		return None

# iamport에 사전 정보를 보내서 결제 준비
def payment_prepare(order_id, amount, *args, **kwargs):
	access_token = get_token()
	if access_token:
		access_data = {
			'merchant_uid':order_id,
			'amount':amount
		}

		url = "https://api.iamport.kr/payments/prepare"
		headers = {
			'Authorization':access_token
		}
		req = requests.post(url, data=access_data, headers=headers)
		data = req.json()

		if data['code'] is not 0:
			raise ValueError("API 통신 오류")
	else:
		raise ValueError("토큰 오류")

# 결제 이후에 해당 하는 주문 번호와 결제 금액으로 진행된 결제가 있는지 찾아주는 함수
def find_transaction(order_id, *args, **kwargs):
	access_token = get_token()
	if access_token:
		url = "https://api.iamport.kr/payments/find/"+order_id
		headers = { 'Authorization':access_token }
		
		req = requests.post(url, headers=headers)
		data = req.json() # 리퀘스트 자체를 json으로 가져온다
		if data['code'] is 0:
			imp_data = data['response']
			context = {
				'imp_id': imp_data['imp_uid'], # imp_uid == transaction id
				'merchant_order_id':imp_data['merchant_uid'],
				'amount':imp_data['amount'],
				'status':imp_data['status'],
				'type':imp_data['pay_method'],
				'receipt_url':imp_data['receipt_url']
			}
			return context
		else:
			return None
	else:
		raise ValueError("토큰 오류")


장바구니에 담긴 상품의 주문 버튼을 누르면 구매자 정보를 입력하는 페이지를 만든다.

정보 입력 후 Payment 버튼을 누르면 이 페이지에서 결제창이 뜨도록 javascript코드를 사용하는 것을 확인할 수 있다.

order/temapltes/order/order_create.html

{% extends 'base.html' %}

{% block title %}Order Checkout{% endblock %}
{% block extra_style %}{% endblock %}

{% block content %}
<div class="alert alert-info mt-3">
	Please enter your order information.
</div>
<form action="" method="post" class="order-form">
	{% csrf_token %}
	{{form.as_p}}
	<input type="hidden" name="pre_order_id" value="">
	<input type="hidden" name="amount" value="{{cart.get_total_price}}">
	<input type="submit" value="Payment" class="btn btn-lg btn-success">
</form>
{% endblock %}

{% block extra_script %}
<script type="text/javascript">
csrf_token = '{{ csrf_token }}';
order_create_url = '{% url 'order_create_ajax' %}';
order_checkout_url = '{% url 'order_checkout' %}';
order_validation_url = '{% url 'order_validation' %}';
order_complete_url = '{% url 'order_complete' %}';
</script>
<script src="https://cdn.iamport.kr/js/iamport.payment-1.1.5.js" type="text/javascript"></script>

{% load staticfiles %}
<script src="{% static 'js/checkout.js' %}" type="text/javascript"></script>
{% endblock %}


다음으로 views.py에 결제 관련 view를 추가해준다.

order/views.py

class OrderCreateAjaxView(View):
	def post(self, request, *args, **kwargs):
		cart = Cart(request)
		form = OrderForm(request.POST)
		if form.is_valid():
			order = form.save()
			for item in cart:
				OrderItem.objects.create(order=order, product=item['product'], price=item['price'], quantity=item['quantity'])
			data = {
				"order_id":order.id
			}
			return JsonResponse(data)
		return JsonResponse({}, status=401)

from .models import OrderTransaction
class OrderCheckoutAjaxView(View):
	def post(self, request, *args, **kwargs):
		order_id = request.POST.get('order_id')
		order = Order.objects.get(id=order_id)
		amount = request.POST.get('amount')

		try:
			merchant_order_id = OrderTransaction.objects.create_new(order=order, amount=amount)
		except:
			merchant_order_id = None

			if merchant_order_id is not None:
				data = {
					'works':True,
					'merchant_id':merchant_order_id
				}
				return JsonResponse(data)
			else:
				return JsonResponse({}, status=401)

# 결제 이 후 정보를 확인하는 클래스
class OrderImpAjaxView(View):
	def post(self, request, *args, **kwargs):
		return JsonResponse({})

from .models import Order
def order_complete(request):
	# ajax로 주문 완료시, 완료 페이지로 이동하는 경우에 사용
	order_id = request.GET.get('order_id')
	order = Order.objects.filter(pk=order_id)

	if order.exists():
		return render(request, 'order/order_created.html', {'order':order[0]})


다음으로 url을 연결해준다.

order/urls.py

from django.urls import path

from .views import *

urlpatterns = [
	# ...
	path('order_complete/', order_complete, name='order_complete'),
]


order/models.py

class OrderTransactionManager(models.Manager):
	def create_new(self, order, amount, success=None, transaction_status=None):
		if not order:
			raise ValueError("주문이 존재 하지 않습니다")

		temp_uuid = uuid.uuid1()
		temp_order_id = (str(temp_uuid)+str(order.email)).encode('utf-8')
		hashed_order_id = hashlib.sha1(temp_order_id).hexdigest()[:10]
		merchant_order_id = str(hashed_order_id)
		payment_prepare(merchant_ordr_id, amount)

		transaction = self.model(
			order=order,
			merchant_order_id=merchant_order_id,
			amount=amount
		)

		if success is not None:
			transaction.success = success
			transaction.transaction_status = transaction_status

		try:
			transaction.save()
		except Exception as e:
			print("save error", e)

		return transaction.merchant_order_id

	def get_transaction(self, merchant_order_id):
		result = find_transaction(merchant_order_id)
		if result['status'] == 'paid':
			return result
		else:
			return None

class OrderTransaction(models.Model):
	order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='transaction')
	merchant_order_id = models.CharField(max_length=20, blank=True, null=True)
	transaction_id = models.CharField(max_length=120, blank=True, null=True)
	amount = models.IntegerField(default=0)
	transaction_status = models.charField(max_length=20, blank=True, null=True)
	type = models.CharField(max_length=100, blank=True, null=True)
	created = models.DatetimeField(auto_now_add=True)

	objects = OrderTransactionManager()

	def __str__(self):
		return str(self.order.id) + "'s Transaction"

	class Meta:
		ordering = ['-created']


order/urls.py

urlpatterns = [
	# ...
	path('create_ajax/', OrderCreateAajaxView.as_view(), name='order_create_ajax'),
	path('checkout/', OrderCheckoutAajaxView.as_view(), name='order_checkout'),

	path('validation/', OrderImpAjaxView.as_view(), name='order_validation'),
]


마지막으로

$ python manage.py collectstatic


결제 api를 적용 중 payment 버튼을 눌렀는데 결제창이 뜨지 않고 "문제가 발생했습니다. 다시 시도해주세요." 나오면서 401 에러가 뜨는 문제가 발생하였다.


사실 구조가 어려워 문제점을 찾는데 좀 오래 걸렸는데, 처음에는 이번에 추가한 자바스크립트가 적용이 안되고 있나 싶었는데 코드를 조금씩 수정해가면서 확인해보니 이 문제는 아니였다.


자바스크립트를 수정하면서 알게된 게 자바스크립트의 AjaxStoreTransaction 함수에서 request.fail 계속 일어난 거였다.


alert() 을 통해 값이 제대로 들어오고는 있는지 확인해 보았는데, 모든 값이 제대로 들어오고 있었다.


그리고 확인 해본게 url 값을 정하는 order_checkout_url에 문제가 있는듯 싶어 이것과 url로 연결된 view인 OrderCheckoutAjaxView를 확인하기로 했다.


확인해보니 merchant_order_id의 값을 넣는 과정에서 예외가 발생해 계속 값이 None으로 되고 있는 것이였다.


그래서 merchant_order_id = OrderTransaction.objects.create_new(order=order, amount=amount)에서 객체 생성 도중 뭔가 문제가 발생한 것인가 싶어 해당 모델에 가봤더니 OrderTransactionManager()에 들여쓰기가 제대로 되어 있지 않은 것을 확인하고 수정하였더니 결제창이 문제 없이 실행되는 것을 확인할 수 있었다.


다시 한 번 파이썬 문법은 들여쓰기에 정말 민감하다는 것을 알게 되는 시간이였다..


Posted by gawain
weekly learning2019. 6. 9. 14:58

이번 주에 공부한 내용을 정리해보자.


멀티데이터베이스 사용하기

1. AWS에서 새로운 PostgreSQL 데이터베이스를 생성한다.

마스터 데이터베이스 생성시 주의할 점은 PostgreSQL 9.3.5 이상부터 읽기 전용 복제본을 사용할 수 있다.


멀티데이터베이스를 위해서 읽기 전용 복베존을 만들려면 백업 기능이 활성화되어 있어야 한다. 따라서 1일 이상으로 설정한다.


2. 데이터베이스 복제본 생성

복제본을 생성할 데이터베이스 인스턴스 선택 -> Actions -> Create read replica


db instance identifier를 적절한 이름으로 설정한다.


모든 설정을 완료 후 생성하면 다음과 같이 생성된 것을 확인할 수 있다.


3. settings.py에 멀티 데이터베이스를 입력한다.

DATABASES = {
	'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'db_name',
        'USER': 'Master username',
        'PASSWORD': 'password',
        'HOST': 'endpoint',
        'PORT': '5432',
	},
    'read1': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'db_name',
        'USER': 'Master username',
        'PASSWORD': 'password',
        'HOST': 'endpoint',
        'PORT': '5432',
	},


두번 째 또는 그 이상의 읽기전용 데이터베이스도 위와 동일하게 생성한다.


4. write, read 데이터베이스 분기를 위해 라우터 클래스를 생성한다. 파일의 위치와 이름은 자유롭게 지정한다.

# config/master_slave_router.py

import random

class MasterSlaveRouter:
    def db_for_read(self, model, **this):
        return random.choice(['read1', 'read2']) # 랜덤 분산

    def db_for_write(self, model, **this):
        return 'default' # == return None

    def allow_relations(self, obj1, obj2, **hints):
        db_list = ('default', 'read1', 'read2') # 특정 DB만 불러오기
        if obj1._state.db in db_list and obj2._state.db in db_list:
            return True
        return None # 무조건 default에 접근 또는 다음 라우터에 물어보겠다는 의미

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return True


생성한 라우터를 settings.py에 추가한다. 라우터는 입력된 순서대로 실행된다.

DATABASE_ROUTERS = [
    'config.db_router.MasterSlaveRouter',
]


기능이 잘 동작하는지 django-debug-toolbar를 설치해서 확인해보자. 사용하는 데이터베이스를 확인할 수 있다.


유저 정보를 담는 데이터베이스 추가

# settings.py
'user': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'dbname',
        'USER': 'Master username',
        'PASSWORD': 'password',
        'HOST': 'endpoint',
        'PORT': '5432',
	},


유저 정보 데이터베이스를 위한 라우터를 추가한다.

# config/user_router.py

from django.conf import settings

class UserRouter:
    def db_for_read(self, model, **hints):
        if model._meta.model_name == 'user': # user model이라면 user 모델에서 읽겠다
            return 'user'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.model_name == 'user':
            return 'user'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        db_list = settings.DATABASES.keys()
        if obj1._state.db in db_list and obj2._state.db in db_list:
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return True


라우터를 settings.py에 추가한다.

DATABASE_ROUTERS = [
    'config.user_router.UserRouter',
    'config.db_router.Db_router',
]


이 방법을 사용하면 다른 서비스에서 가지고 있는 유저 정보를 그대로 활용할 수 있다.


커스텀 템플릿 태그 만들기

템플릿 문법에 없는 기능을 추가할 수 있다. 커스텀 템플릿 태그와 템플릿 필터를 만들 수 있다.


1. app/templatetags 폴더를 추가한다.



2. 원하는 기능을 작성할 파일을 작성한다. 템플릿 파일에서 이 파일명으로 기능을 불러온다.


3. 템플릿 라이브러리 변수를 만든다.

from django import template
register = template.Library()


4. 필터를 등록할 때는 다음과 같이 코드를 작성한다.

@register.filter
def add_two(value):
    return value+2


5. 등록한 필터는 다음과 같이 사용한다.

{% load blog %}
{{변수|add_two}}


6. 태그를 등록할 때는 다음과 같이 코드를 작성한다.

@register.simple_tag
def print_template():
    return render_to_string('blog/test.html')


7. 등록한 태그는 다음과 같이 사용한다.

{% load blog %}
{% print_template %}


8. 태그를 실행한 결과를 변수로 지정하려면 as 키워드를 사용한다.

{% load blog %}
{% print_template as test %}
{{test}}


9. 추가 인자가 있는 필터는 다음과 같이 코드를 작성한다.

@register.filter
def string_append(left, right):
    return left+"-"+right


10. 해당 필터는 다음과 같이 사용한다.

{% load blog %}
{{'string1'|string_append:'string2'}}


요새 수업 내용이 좀 어렵게 느껴지고 있다. 


하나하나를 놓고 보면 크게 어려운 내용은 아니지만, 예제에 적용한다든지, 실제 사용해보면 여러 내용이 복합적으로 얽혀서 적용하기 어렵고 헷갈리는 부분이 많아지고 있다..


이 부분은 개인 프로젝트를 진행 해보면서 더욱 느끼고 있다. 분명 수업 때 배운 내용이지만 적용하는게 조금씩 다르다 보니 헤매는 경우가 없지않아 많다.


그리고 또 사이트를 혼자 만들면서 느끼는건 아직 django에 대해 많이 모르고 있다는 것이다. 특히 view 부분이 많이 부족한 것 같다.


공부는 정말 끝이 없는 것 같다



Posted by gawain
weekly learning2019. 6. 1. 14:36

저번 주에 이어 계속해서 AWS에 서버 설정하는 워크시트로 수업이 진행되었다.


이번 포스팅은 장고 프로젝트 생성부터 AWS에 장고 세팅까지의 매뉴얼을 작성해보려고 한다.


1. 로컬에 가상환경 세팅

$ mkdir aws_manual_project
$ cd aws_manual_project
$ pyenv virtualenv 3.7.3 aws_manual_project
$ pyenv local aws_manual_project


2. 프로젝트 생성

$ pip install django==2.1.5
$ django-admin startproject config .


3. 커스텀 유저 모델 만들기

accounts 앱 생성

$ python manage.py startapp accounts

settings.py에 앱을 추가

INSTALLED_APPS = [
    #...
    'accounts',
]

models.py

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    message = models.TextField(blank=True)
    profile = models.ImageField(upload_to='user_image/profile/%Y/%m/%d', blank=True)

settings.py

AUTH_USER_MODEL = 'accounts.User'

이미지 필드 때문에 Pillow 모듈 설치, makemigrations 명령 실행

$ pip install Pillow
$ python manage.py makemigrations accounts

admin.py

from django.contrib import admin
from .models import User
from django.contrib.auth.admin import UserAdmin

class CustomUserAdmin(UserAdmin):
    UserAdmin.fieldsets[1][1]['fields']+=('profile','message')
    UserAdmin.add_fieldsets += (
        (('Additional Info'), {'fields':('profile','message')}),
    )

admin.site.register(User, UserAdmin)


4. 데이터베이스 설정

먼저 RDS에서 db instance를 생성한다.

그리고 settings.py에 데이터베이스 엔진을 설정해준다.

# settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'dbname',
        'USER': 'Master_username',
        'PASSWORD': 'Master_username_pw',
        'HOST': 'Endpoint',
        'PORT': '5432',
    }
}

아답터 모듈 설치, 데이터베이스 초기화, 관리자 계정 생성

$ pip install psycopg2-binary
$ python manage.py migrate
$ python manage.py createsuperuser


5. S3 파일서버 설정

AWS에서 static과 media를 분리하여 2개의 bucket을 생성한다.


사용자가 없다면 사용자도 추가한다. (생략)


settings.py 파일 설정

AWS_ACCESS_KEY_ID = 'ACCESS_KEY'
AWS_SECRET_ACCESS_KEY = 'SECRET_ACCESS_KEY'
AWS_REGION = 'ap-northeast-2c'
AWS_STORAGE_BUCKET_NAME = 'static S3 bucket name'
AWS_S3_CUSTOM_DOMAIN = AWS_STORAGE_BUCKET_NAME

AWS_S3_SECURE_URLS = False

AWS_S3_FILE_OVERWRITE = False
AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}
AWS_DEFAULT_ACL = 'public-read'
AWS_LOCATION = ''
STATIC_URL = 'http://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
DEFAULT_FILE_STORAGE = 'config.asset_storage.MediaStorage'

config/asset_storage.py 생성

from storages.backends.s3boto3 import S3Boto3Storage
class MediaStorage(S3Boto3Storage):

    bucket_name = 'media S3 bucket name'
    custom_domain = '%s' % bucket_name
    file_overwrite = False

storages 모듈, boto3 모듈 설치

$ pip install django-sotrages
$ pip install boto3

static 파일을 S3에 업로드

$ python manage.py collectstatic

마지막으로 Route53에서 S3에 도메인을 연결한다.


5. AWS EC2 인스턴스 만들기

인스턴스 생성시 보안 그룹 설정시 HTTP와 8000번 포트를 추가한다. ip는 환경에 맞게 설정한다.

키 페어 파일을 새로 받았다면 아래 명령어를 실행한다. 아니라면 생략.

$ chmod 400 key-pair.pem
$ mv key-pair.pem ~/.ssh/

EC2에 터미널로 접속할 때 아이디는 아래 Connect 메뉴에서 알 수 있다.

ubuntu@EC2 퍼블릭 DNS


터미널에서 접속 명령어

$ ssh -i ~/.ssh/[키 페어 파일 이름] ubuntu@[EC2 퍼블릭 DNS]


6. EC2에 웹 서버 설정하기

먼저 EC2에 SSH로 접속한다.

$ ssh -i ~/.ssh/[키 페어 파일 이름] ubuntu@[EC2 퍼블릭 DNS]


아래 명령어들 실행

$ sudo apt-get update
$ sudo apt-get install nginx
$ systemctl status nginx # 생략 가능
$ sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/django
$ sudo apt-get install vim # 혹시 vim이 없다고 하면 설치
$ sudo vim /etc/nginx/sites-available/django


위 마지막 명령 실행하여 nginx 설정을 아래처럼 수정.

server {
    listen 80;
    listen [::]:80;

    root /var/www/django;

    index index.html index.htm index.nginx-debian.html;

    server_name [ _ 또는 url];

    location / {
        try_files $uri $uri/ =404;
    }
}

설정 파일을 nginx에 활성화

$ sudo ln -s /etc/nginx/sites-available/django /etc/nginx/sites-enabled/
$ sudo systemctl restart nginx
$ cd /var/www/
$ sudo mkdir django
$ vi django/test.html

Route53에서 도메인 연결 후 웹 사이트에 접속이 되는지 확인.

(AWS의 고정 아이피도 가능)


7. EC2에 장고 서버 세팅

먼저 EC2에 SSH로 접속한다.

$ ssh -i ~/.ssh/[키 페어 파일 이름] ubuntu@[EC2 퍼블릭 DNS]

명령어 실행

$ sudo useradd -g www-data -b /home -m -s /bin/bash django
$ sudo mkdir -p /var/www/django # 6번에서 생성했다면 생략
$ sudo chown django:www-data /var/www/django
$ sudo usermod -a -G www-data ubuntu
$ sudo chmod g+w /var/www/django
$ sudo apt-get install python3-dev python3-venv python3-pip
$ sudo python3 -m venv /var/www/django/venv
$ source venv/bin/activate
$ sudo -s
$ source venv/bin/activate
$ pip install django
$ django-admin startproject config .
$ python manage.py migrate
$ pip install uwsgi
$ uwsgi --http :8000 --home /var/www/django/venv/ --chdir /var/www/django/ --module config.wsgi
$ sudo mkdir run logs # 위치 /var/www/django/
$ sudo chown django:www-data run
$ sudo chown django:www-data logs
$ sudo mkdir ini # 위치 /var/www/django
$ sudo vim ini/uwsgi.ini
$ sudo vim /etc/systemd/system/uwsgi.service


ini/uwsgi.ini

[uwsgi]
uid = django
base = /var/www/django

home = %(base)/venv
chdir = %(base)
module = config.wsgi:application
env = DJANGO_SETTINGS_MODULE=config.settings

master = true
processes = 5

socket = %(base)/run/uwsgi.sock
logto = %(base)/logs/uwsgi.log
chown-socket = %(uid):www-data
chmod-socket = 660
vacuum = true


uwsgi.service

[Unit]
Description = uWSGI Emperor service

[Service]
ExecStart=/var/www/django/venv/bin/uwsgi --emperor /var/www/django/ini
User=django
Group=www-data
Restart=on-failure
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
StandardError=syslog

[Install]
WantedBy=multi-user.target


그 다음 명령어

$ sudo systemctl start uwsgi
$ sudo systemctl enable uwsgi
$ sudo vim /etc/nginx/sites-available/django


django 설정 파일

upstream django {
        server unix:/var/www/django/run/uwsgi.sock;
}
server {
        listen 80;
        charset utf-8;
        access_log /var/www/django/logs/access.log;
        error_log /var/www/django/logs/error.log;
        server_name _;

        location = /favicon.ico { access_log off; log_not_found off; }

        client_max_body_size 75M;

        location /media {
                alias /var/www/django/media;
        }

        location /static {
                alias /var/www/django/static;
        }

        location / {
                include /etc/nginx/uwsgi_params;
                uwsgi_pass django;
        }


다음 명령어 실행

 $ sudo chown -R django:www-data /var/www/django
$ sudo chmod -R g+w /var/www/django


FTP로 올리기 전에 로컬에서 

$ pip freeze > requirements.txt


FTP 올리고 EC2에서

$ source venv/bin/activate
$ pip install -r requirements.txt


여기까지하면 대충 인스턴스에 장고서버까지 세팅이 끝났다.

뭔가 길어보이지만 사실 익숙하면 금방 금방 한다. 물론 나도 아직 익숙하진 않지만 하루에 한 번씩 세팅을 해보면서 느끼는건 나도 모르게 하나씩 빼먹거나 오타만 조심하면 될것 같다는 생각이 든다.


서버 세팅 이외에 요즘에는 자바스크립트, css3도 추가로 공부하고 있다.


내가 주로 다룰 부분이 아니지만 저번 해커톤이나 개인 프로젝트를 하면서 느끼는건 너무 몰라도 안되겠다고 생각해서다.


뭔가 기본 문법적인건 쉬운것 같은데 파면 팔 수록 어려운 것 같다...




Posted by gawain