PyCon AU 2016 참가 후기

Python을 실무에서 많이 사용하고 있지 않긴 하지만 사용할 때마다 재미있고 깊게 배우고 싶다는 생각이 늘 드는 언어 중 하나다. 관심을 갖기 시작했을 때부터 PyConAU에 다녀오고 싶었는데 이전엔 브리즈번에서 하고 그 전엔 호바트에서 해서 숙박이나 비행기표나 신경 쓸 일이 많아 고민만 하다 가질 못했었다. 올해는 멜번에서 한다고 하길래 얼리버드 티켓이 열리자마자 구입을 했다. 한동안 python과 가까이 지내지 못해서 과연 내가 이 행사를 참여할 자격이 있는가를 끊임 없이 고민했다. (이미 티켓을 샀으면서도.) 다녀오고 나서는 참 잘 다녀왔구나, 참가 안했으면 얼마나 후회했을까 싶을 정도로 좋은 기억이 남았고 긍정적인 힘을 많이 받고 올 수 있었다.

PyCon AU 2016의 모든 세션은 유튜브에 올라와 있어서 관심이 있다면 확인해보는 것도 좋겠다.

1일차

늘 주말에 일이 있어서 집밖을 나서는 날이 거의 없었는데 오랜만에 아침 일찍 챙겨서 행사장으로 갔고 생각보다 일찍 도착했다. 커피 쿠폰 두 장, 티셔츠를 받았다. 자유로운 분위기에서 대기하다가 들어가서 오프닝 키노트를 봤다.

도착! 이렇게 (공간이) 큰 행사는 처음 ㅜㅜ #pyconau

A photo posted by 용균 (@edwardykim) on

MicroPython: a journey from Kickstarter to Space

오프닝 키노트는 Python을 작은 임베드 장비에서 구동할 수 있도록 최적화를 먼저 시작하는 방식으로 개발한 구현체인 MicroPython에 대한 이야기였다. 구현을 시작한 과정부터 Kickstarter를 거치며 겪은 경험, BBC와 만든 micro:bit, 그 이후 유럽우주국과 함께 진행한 프로젝트와 앞으로의 로드맵을 이야기했다.

임베딩 장비를 좋아하는데 거기서 고급 언어를 사용할 수 없을까 고민하다가 Python을 공부해야지 하는 마음에 Python을 선택했다는 말도 기억에 남는다. 작게 시작했지만 전환점이 되는 위치가 있었다. 요즘은 이미 거의 완제품이 나온 상태에서 킥스타터에 올리는 분위기지만 MicroPython이 펀딩을 진행했을 당시에는 좀 더 언더(?)같은 느낌이었나보더라. 그렇게 펀딩에 성공해서 MicroPython이 탄생했는데 그게 끝이 아니라 유럽우주국에서 연락이 와 투자를 받았단다. 지금까지 우주에서 사용하는 대부분의 디바이스는 중단 후 컴파일한 소프트웨어를 업데이트하고 다시 구동하는 방식인데 인터프리팅이 가능한데다 고급 언어인 파이썬을 사용할 수 있다는 점에 매력이 컸다고. 유럽우주국과 진행한 프로젝트에서 가능성을 확인했고 추후 장기적인 프로젝트를 계속 진행하게 될거라고 했다.

개발을 시작한 이유도 그렇고 킥스타터나 유럽우주국 이야기, 그렇게 우주로 나가는 디바이스를 만들고 있다는 이야기가 마치 소설처럼 느껴졌다. 작지만 세상을 바꾼다, 느낌으로.

RULES FOR RADICALS: CHANGING THE CULTURE OF PYTHON AT FACEBOOK

페이스북의 이야기였는데 Python2.x에서 Python3.x로 어떻게 옮겼는지에 대한 이야기였다. 2013년부터 이전하자는 이야기를 했는데 2016년에 py3를 기본으로 사용하기까지 과정을 보여줬다.

2013

  • Linter에서 __future__를 자동으로 넣어줘서 사람들이 아무 생각없이 쓰기 시작함.
  • 원래는 gevent와 twist가 py3 지원할 때까지 기다리려 했는데 Thrift에서 py3 지원 시작하면서 이전하기 시작.

2014년

  • 지원하기 시작했는데 아무도 안씀.
  • 구버전 패키징 라이브러리인 fbpackage2를 py3에 맞춰 fbpkg로 작성.
  • Be the change you want to see! 마하트마 간디
  • 기존 라이브러리, 사내 부트스트랩 전부 구버전 기준이라 재작성.
  • 고장난 라이브러리 전부 고침.
  • PyFlakes로 forcing comliance. py3 린트 통과 못하면 커밋 안됨. 자연스레 문법에 친해지기 시작.
  • 문제 생기면 적극적으로 도와줌.

2015년

  • 사내 교육을 py3으로 변경

2016년

  • 기본으로 py3 사용
  • 이제 사람들이 py2를 깨먹기 시작함

자신이 원하는게 있으면 자신이 먼저 변해야 한다는 이야기, 미래지향적으로 교육을 해야한다는 부분, 통계를 잘 수집하면 이런 과정이 더 명료해서 도움이 된다는 점이 기억에 남는다.

Python for Bioinformatics for learning Python

제목만 보고 Bioinformatics에 대한 이야기를 할 줄 알고 들어갔는데 좀 실망했다. Bioinformatics에서 c++를 자주 사용하는데 가르치기 어려워서 파이썬을 사용하기 시작했다고. Bioinformatics에서 분석할 때 사용하기 좋은 함수가 어떤 것인지 설명해주고 코드골프만 실컷 보여줬다.

Linear Genetic Programming in Python Bytecode

제네릭도 아니고 제네틱은 무엇인가 싶어서 들어갔는데 너무 충격적이었다. 이전에 본 적 있던 유튜브 영상으로 시작했다. 이게 바로 제네틱 프로그래밍이라고.

아예 처음 듣는 이야기라서 좀 어렵긴 했는데 간단히 설명하면 진화론 아이디어를 반영한 프로그래밍 방식이었다. 먼저 코드에서 가족군을 만들고 나올 수 있는 경우의 수를 모두 만들어낸다. 그리고 각 코드끼리 비교 평가를 수행하고 적자 선택을 반복적으로 수행해서 목적에 맞는 코드를 만드는 방식이라고. 평가와 선택이 적절하면 세대를 거듭할수록 목적에 맞는 코드가 나온다고 한다. 들으면서 하나도 모르겠는데 이게 엄청 멋진건 알겠더라. 이 프로그래밍으로 이용해서 전자기판 최적화를 하는 대회(Humis contest)가 있다고 한다.

이 제네틱 프로그래밍을 사용할 때는 함축적인 언어를 사용할수록 유용하다고 하며 brainfuck을 예로 들었던 것이 기억난다.

제네틱 프로그래밍은 gp-field-guide.org.uk가 가장 잘 정리되어 있다고 한다.

이 정반합 과정을 Python Bytecode를 사용할 수 있도록 만든 라이브러리 DEAP를 소개했다. 시간이 없어서 Bytecode 예제는 보여주지 못했다.

Security Starts With You: Social Engineering

Social Engineering으로 보안에 취약할 수 있는 정보를 빼내는 방법에 대해 설명했다. 흔히 알고 있는 Phishing 외에도 Tailgating(piggybanking), pretexting, Baiting 등 다양한 방식이 있다고 한다.

기억에 남는 부분은 서비스에서 알림과 같은 이메일을 보낼 때 되도록이면 링크를 넣지 않는 방식으로 하는 것이 보안성이 높다는 이야기였다. 물론 이메일을 받으면 보낸 사람이나 내용을 보면 이미 스팸같은 냄새가 나서 사람들이 안누르긴 하지만 내용이 정말 지능적으로 똑같이 만들어졌다면 평소와 같이 누르게 된다는 것이다. 애초에 메일에 디테일을 담지 않고 자세한 내용은 웹사이트에 로그인하면 확인할 수 있다는 식으로 이메일 자체에 내용을 최소화하는 접근 방식을 사용하면 사람들이 피싱 메일을 받아도 아무 링크나 눌러보지 않도록 하는데 도움을 많이 준다고 한다.

Behind Closed Doors: Managing Passwords in a Dangerous World

password는 신원 확인을 위한 것인데 어떤 방식으로 확인을 하는가, 그 확인을 위한 데이터를 어떻게 보관하는가에 대해서 이야기했다. 보안과 관련한 수많은 라이브러리를 설명해줬다. 점심 먹고 졸릴 시간인데다 라이브러리를 너무 많이 이야기해서 그냥 멍했다. 필요한 권한만 최소로 주라는 말이 기억난다.

CPython internals and the VM

Python을 사용하면서 내부는 어떻게 구현되어 있을까 궁금해서 CPython 코드를 열어봤다는 이야기로 시작해서 코드만 실컷 읽고 끝났다. 오픈소스는 구현이 궁금하면 열어볼 수 있다는 매력이 있다. 어떤 방식으로 돌아가나 그런거 보여줄 줄 알고 갔는데 코드만 읽음.

The dangerous, exquisite art of safely handing user-uploaded files

파일을 업로드 할 수 있는 상황에서 어떻게 올린 파일을 다룰 것인지에 대한 이야기. 파일명이나 파일 확장자는 절대 믿지 말고 고립된 환경에서 파일을 다룰 것에 대해 이야기했는데 PHP를 엄청 깠다. 그다지 새롭지 않았음.

Lightning Talks

5분 짜리 라이트닝 토크를 연속해서 진행했다. pyaudioquitnet을 사용해서 스피커로 데이터를 전송하고 마이크로 데이터를 전송 받는 데모도 재밌었다. 여성 스피커로만 진행하는 KatieConf 이야기도 재밌었다. (패러디지만.) 가장 기억에 남는 토크는 Falsehoods Programmers Have about Identity 였다. 이름, 성(Gender), 생일, 국적에 대해 정보를 받을 때 어떤 방식으로 다양성을 존중해야 하는 것인지에 대한 이야기를 했다. 누군가의 정체성을 부정하는 것은 인간임을 부정하는 것과 마찬가지라는 말이 기억에 남는다.

2일차

Python All the Things

파이썬을 사용해서 앱을 개발할 수 있는 PyBee 소개였다. 언어 구현과 레퍼런스 구현을 분리해서 설명했는데 Django와 같이 재단을 만들어서 운영하는 로드맵을 얘기해줬다. 아직 개발이 한창 진행중이라서 실제로 보여주는 부분은 많이 없었지만 그간 파이썬에서 약하다고 생각했던 부분이라 그런지 사람들 관심이 참 많았다.

Evaluating fire simulators using Docker, Dask and Jupyter notebooks!

호주 기상국(Australia Bureau of Meteorology)에서 발표한 세션인데 주제대로 산불 발생 시뮬레이터를 docker와 dask를 사용해서 구현하고 Jupyter로 결과를 가공하는 과정을 보여줬다. docker 때문에 들어갔는데 생각보다 별 내용 없이 개괄적인 이야기를 많이 했다.

Code the Docs: Interactive Document Environments

문서화에 관한 이야기라고 생각했는데 조금 달랐다. 스피커는 사실 Swift 책을 쓰는 사람이었고 Xcode Playground에서 인터렉티브한 개발 문서를 작성해 프로그래밍을 더 쉽게 이해하고 공부하는데 도움이 된다는 내용이었다. 문서와 코드를 오가면서 실제로 동작하는 코드를 확인하는 과정을 문서 내에서 바로 실행할 수 있는, 동적인 문서 환경을 jupyter와 Playground를 사용해서 보여줬다. Oreilly에서 jupyter를 이 용도로 수정해서 교육 프로그램을 운영하고 있다고 Regex golf를 보여줬는데 영상과 문서, 코드가 잘 동기화되어 있었다. 얘기로는 이런 방식의 미디어가 교육을 위한 책을 대체할 것이라고 했는데 교육을 위한 포맷이 다양해져야 한다는 점은 많이 공감했다.

Deciding between continuity and change in your Open Source community

지금까지 커뮤니티를 운영하고 컨퍼런스를 개최하며 겪은 이야기를 공유했다. 오고 가는 자원봉사자와 어떻게 함께하는지, 커뮤니티에서 어떤 부분을 지향해야 하는지, 커뮤니티에 관한 다양한 주제를 이야기했다. 이상한모임에 참여하면서 생각했던 많은 고민이 우리 만의 문제가 아니라 커뮤니티를 운영하면 누구나 고민하는 부분이구나 생각할 수 있었다. 자전거 그늘처럼 불필요한데 시간과 자원을 많이 소모하는 일을 최대한 피해야 한다는 이야기도 기억에 남는다.

Functional Programming for Pythonistas

루비 개발자인데 파이썬의 함수형 프로그래밍에 대해서 이야기했다. map과 같은 함수랑 lambda 설명하는 수준이었다.

The Evolution of Python Packaging at Facebook

페이스북에서 Python 패키지를 어떤 방식으로 배포하는지에 대한 설명이었다. 좀 더 특별한 방식을 사용할거라 생각했는데 ZIP 압축을 그대로 사용한다고 한다. ZIP을 에디터로 열어보면 PK라는 매직 키워드가 존재하는데 그 앞에 shell script를 넣어도 zip파일엔 영향이 없다고 한다. zip파일에 쉘 스크립트를 포함한 후에 쉘로 그 파일을 실행하면 압축을 해제하기 전에 해야 하는 환경 설정이나 라이브러리 처리를 한 다음 self extract 하는 방식으로 사용한다고 한다. 얼마 전 zip을 대체하는 라이브러리를 페이스북에서 발표했다는 소식이 있었는데 아마 이 환경에서 좀 더 편하게 쓸 수 있도록 개선한게 아닌가 싶다.

그 쉘 스크립트를 포함한 zip을 PAR이라 부르고 페이스북 내에서 사용하는 아카이빙 파일로 표준처럼 사용하고 있다고 한다. 배포해야 하는 서버가 많으니까 별도의 프로그램 없이 최소한의 자원을 써서 설치할 수 있는 환경을 만든 모양이다. 이렇게 만든 파일을 rsync하거나 torrent로 배포한다고. 페이스북이 종종 내려가는 이유가 여기에 있다고 하는데 서버에서 한 번 실행하는 함수라서 작은 환경에서 실행했을 때는 문제가 없다가도 수십 만 대 서버에 배포되면 순식간에 수십 만 번 실행하기 때문이라고 한다.

이 발표에서 페이스북의 문화를 중간 중간 이야기했는데 자신이 하고 싶은 일을 스스로 찾아서 재미있게 한다는 점이 참 인상적이고 부러웠다.

Predicting the TripleJ Hottest 100 With Python

이틀 꼬박 달리니까 좀 지쳐서 쉬는 마음으로 들어간 세션이었다. 모 사이트에서 음악 인기투표를 진행하는데 인기투표 후에 소셜로 공유하는 기능이 있어서 그 내용을 분석해 실제 발표 이전에 해당 발표를 예측하는 서비스를 만든 후기였다. 나는 처음 듣는 사이트였지만 그 순위 가지고 베팅도 하고 그렇다고. raw 데이터를 어떻게 가공하고 분석했는지 설명하는데 그냥 수작업으로 보였다.

Hitting the Wall and How to Get Up Again – Tackling Burnout and Strategies

왜 개발자는 번아웃 되고 힘들게 되는가에 대한 이야기였고 바쁘고 힘든 시기여서 그랬는지 많이 위로가 되었다. 힘든게 당연한 일이고 힘들면 꼭 심리치료사를 찾으라고.

Lightning Talks

역시나 다양하고 재미있는 이야기가 많았다.

  • How well do we (in this room) represent python programmers?: 다양성에 대해 이야기하지만 개발 환경의 다양성에 대한 논의는 적다고. Python을 사용하는 환경이 대부분 리눅스나 맥인 경우가 많은데 실제 학습 환경에서는 윈도를 더 많이 사용하고 있다는 이야기.
  • 5 Languages in 5 minutes: 5가지 언어를 보여줬는데 ARNOLDC, TRUMPSCRIPT, LOLCAT, BRAINF* 4가지와 직접 만든 ANGUISH를 보여줬다. ANGUISH는 공백 언어로 만든 프로그래밍 언어, 매우 안전(?).
  • Open source and social responsibility: 오픈소스에 사용된 코드 라인 수를 보여줬다. Ubuntu, nginx, postgreSQL, python의 코드를 합치면 72,258,475 라인이라고. 이런 코드를 사용하는데 있어서 좀 더 사회적인 책임 의식을 갖고 사용해야 하지 않을까에 대해 이야기했다. 무슨 사회적인 문제를 해결할 수 있을지 짚어 이야기하며 지금 세상을 바꾸라는 이야기로 마무리했다.

개발을 하다보면 눈 앞에 있는 일에 치여서 재미를 느끼지 못할 때가 있다. 세상은 넓고 멋진 사람들을 많은데 내 작업은 작게만 느껴지고 세상을 바꾼다는 포부와는 동떨어진 삶을 사는 기분이 들 때가 있다. 한동안 너무 바빠서 내가 개발을 얼마나 좋아하고 재미있어 하는지, 즐거워하는지 돌아볼 시간조차 없었다는 것을 파이콘에 참석해서야 알게 되었다. 다들 즐겁게, 자신이 하는 일을 이야기하고 공유하는 것을 보고 기분이 많이 좋아졌고 한편으로 위로도 되었다. 부지런히 배워서 컨퍼런스에서 만난 사람들처럼 누군가에게 좋은 영향력을 주는 사람이 되고 싶다.

IMG_1015

IMG_1018

IMG_1019

IMG_1020

IMG_1024

IMG_1022

IMG_1025

IMG_1031

IMG_1032

IMG_1034

IMG_1038

IMG_1057

IMG_1051

IMG_1056

IMG_1060

Django Girls 튜토리얼 정리

이상한모임에서 진행할 사이드 프로젝트에 Django를 사용하게 되었는데 제대로 살펴본 경험이 없어서 그런지 영 익숙해지질 않았다. 이전에 Django Girls 튜토리얼 – django로 블로그 만들기 포스트를 본 것이 생각나서 살펴보다가 튜토리얼까지 보게 되었다.

이 포스트는 장고걸스 서울에서 번역된 장고 걸스 튜토리얼을 따라 진행하며 내가 필요한 부분만 정리했기 때문에 빠진 내용이 많다. 튜토리얼은 더 자세하게 세세한 부분까지 설명이 되어 있으니 만약 Django를 학습하려 한다면 꼭 튜토리얼을 살펴볼 것을 추천한다.


Django 및 환경 설정하기

$ python3 -m venv myvenv
$ source myvenv/bin/activate
$ pip install django==1.8

프로젝트 시작하기

django-admin startproject mysite .

생성된 프로젝트 구조는 다음과 같다.

djangogirls
├───manage.py # 사이트 관리 도구
└───mysite
        settings.py  # 웹사이트 설정
        urls.py      # 라우팅, `urlresolver`를 위한 패턴 목록
        wsgi.py
        __init__.py

기본 설정하기

settings.py를 열어서 TIME_ZONE을 수정한다.

TIME_ZONE = 'Asia/Seoul'

정적 파일 경로를 추가하기 위해 파일 끝에 다음 내용을 추가한다.

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

데이터베이스는 기본값으로 sqlite3이 설정되어 있다. 데이터베이스 생성은 manage.py를 활용한다.

$ python manage.py migrate

서버를 실행해서 확인한다.

$ python manage.py runserver 0:8000

어플리케이션 생성하기

$ python manage.py startapp blog

mysite/settings.py를 열어 INSTALLED_APPS에 방금 생성한 어플리케이션을 추가한다.

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
)

블로그 글 모델 만들기

from django.db import models
from django.utils import timezone


class Post(models.Model):
    author = models.ForeignKey('auth.User')
    title = models.CharField(max_length=200)
    text = models.TextField()
    created_date = models.DateTimeField(
            default=timezone.now)
    published_date = models.DateTimeField(
            blank=True, null=True)

    def publish(self):
        self.published_date = timezone.now()
        self.save()

    def __str__(self):
        return self.title

여기서 사용할 수 있는 필드는 모델 필드 레퍼런스를 참조한다.

생성한 모델을 사용하기 위해 마이그레이션 파일을 생성하고 마이그레이션을 수행한다.

$ python manage.py makemigrations blog
$ python manage.py migrate blog

Django 관리자 사용하기

새로 추가한 모델을 관리자 패널에서 접근하기 위해서 blog/admin.py에 다음 코드를 추가한다.

from django.contrib import admin
from .models import Post

admin.site.register(Post)

관리자 패널에 로그인하기 위한 아이디를 생성한다. 프롬프트에 따라 생성한 후 웹서버를 실행해서 관리자 패널(http://127.0.0.1:8000/admin/)에 접속한다.

$ python manage.py createsuperuser
$ python manage.py runserver 0:8000

테스트를 위해 Posts에 글을 추가한다.

Git 설정하기

리포지터리를 초기화하고 커밋한다. 튜토리얼은 Github 사용하는 방법이 설명되어 있다. .gitignore은 아래처럼 작성할 수 있다.

*.pyc
__pycache__
myvenv
db.sqlite3
.DS_Store

PythonAnywhere 설정하기

PythonAnywhere는 Python을 올려 사용할 수 있는 PaaS 서비스로 개발에 필요한 다양한 서비스를 제공한다.

먼저 서비스에 가입해서 콘솔에 접속한다. Github에 올린 코드를 clone한 다음 가상환경을 설치하고 진행한다.

$ git clone https://github.com/<your-github-username>/my-first-blog.git
$ cd my-first-blog
$ virtualenv --python=python3.4 myvenv
$ source myvenv/bin/activate
$ pip install django whitenoise

whitenoise는 정적 파일을 CDN처럼 사용할 수 있도록 돕는 패키지다. 먼저 django의 관리자 도구로 모든 패키지에 포함된 정적 파일을 수집한다.

$ python manage.py collectstatic

데이터베이스와 관리자를 생성한다.

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

이제 PythonAnywhere 대시보드에서 web app을 추가하고 manual configuration을 python3.4로 설정한다. 가상환경 경로는 앞서 생성했던 /home/<your-username>/my-first-blog/myvenv/로 입력한다.

WSGI 프로토콜도 사용할 수 있는데 Web > Code 섹션을 보면 WSGI 설정 파일 경로가 있다. 경로를 클릭해서 다음처럼 내용을 변경한다.

import os
import sys

path = '/home/<your-username>/my-first-blog'
if path not in sys.path:
    sys.path.append(path)

os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

from django.core.wsgi import get_wsgi_application
from whitenoise.django import DjangoWhiteNoise
application = DjangoWhiteNoise(get_wsgi_application())

URL 작성하기

django는 URL을 위해 정규표현식을 사용한다. 앞서 생성한 blog를 mysite로 다음처럼 불러온다.

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'', include('blog.urls')),
]

blog/urls.py를 추가하고 다음 내용을 추가한다.

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.post_list, name='post_list'),
]

View 작성하기

blog/views.py를 열고 post_list를 생성한다.

from django.shortcuts import render

def post_list(request):
    return render(request, 'blog/post_list.html', {})

템플릿 작성하기

blog/templates/blog 밑에 post_list.html을 생성한다. 디렉토리 구조에 유의한다.

Django ORM과 QuerySets

Django에서의 모델 객체 목록을 QuerySets이라 하며 데이터를 정렬하거나 처리할 때 사용한다.

콘솔에서 다음 명령어로 쉘에 접근한다.

$ python manage.py shell

모델은 먼저 불러온 다음에 쿼리셋을 사용할 수 있다.

>>> from blog.models import Post
>>> Post.objects.all()
[<Post: Hello World>, <Post: Koala>]

>>> from django.contrib.auth.models import User
>>> User.objects.all()
[<User: edward>]
>>> me = User.objects.get(username='edward')

>>> Post.objects.create(author=me, title='Goodbye my friend', text='bye')
<Post: Goodbye my friend>
>>> Post.objects.all()
[<Post: Hello World>, <Post: Koala>, <Post: Goodbye my friend>]

쿼리셋을 다음 방법으로 필터링 할 수 있다.

>>> Post.objects.filter(author=me)
[<Post: Hello World>, <Post: Koala>, <Post: Goodbye my friend>]
>>> Post.objects.filter(title__contains='Koala')
[<Post: Koala>]

>>> from django.utils import timezone
>>> Post.objects.filter(published_date__lte=timezone.now())
[]
>>> post = Post.objects.get(title__contains="Goodbye")
>>> post.publish()
>>> Post.objects.filter(published_date__lte=timezone.now())
[<Post: Goodbye my friend>]

정렬도 가능하며 체이닝으로 한번에 호출하는 것도 가능하다.

>>> Post.objects.order_by('created_date')
[<Post: Hello World>, <Post: Koala>, <Post: Goodbye my friend>]
>>> Post.objects.order_by('-created_date')
[<Post: Goodbye my friend>, <Post: Koala>, <Post: Hello World>]
>>> Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')

템플릿에서 동적 데이터 활용하기

blog/views.py를 수정한다.

from django.shortcuts import render
from django.utils import timezone
from .models import Post


def post_list(request):
    posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
    return render(request, 'blog/post_list.html', {'post': posts})

blog/templates/blog/post_list.html을 수정한다.

<div>
    <h1><a href="/">Django Girls Blog</a></h1>
</div>

{% for post in posts %}
    <div>
        <p>published: {{ post.published_date }}</p>
        <h1><a href="">{{ post.title }}</a></h1>
        <p>{{ post.text|linebreaks }}</p>
    </div>
{% endfor %}

CSS 추가하기

정적 파일은 blog/static 폴더에 넣는다. blog/static/css/blog.css를 작성한다.

h1 a {
    color: #FCA205;
}

템플릿에 적용한다.

{% load staticfiles %}
<!doctype html>
<html lang="ko">
<head>
    <meta charset="utf-8">
    <title>Django Girls Blog</title>
    <link rel="stylesheet" href="{% static 'css/blog.css' %}">
</head>
<body>
  <div>
      <h1><a href="/">Django Girls Blog</a></h1>
  </div>

  {% for post in posts %}
      <div>
          <p>published: {{ post.published_date }}</p>
          <h1><a href="">{{ post.title }}</a></h1>
          <p>{{ post.text|linebreaks }}</p>
      </div>
  {% endfor %}
</body>
</html>

템플릿 확장하기

base.html을 다음과 같이 작성한다.

{% load staticfiles %}
<!doctype html>
<html lang="ko">
<head>
    <meta charset="utf-8">
    <title>Django Girls Blog</title>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
    <link rel="stylesheet" href="{% static 'css/blog.css' %}">
</head>
<body>
    <div class="page-header">
        <h1><a href="/">Django Girls Blog</a></h1>
    </div>
    <div class="content container">
        <div class="row">
            <div class="col-md-8">
            {% block content %}
            {% endblock %}
            </div>
        </div>
    </div>
</body>
</html>

이제 post_list.html에서는 위 base 파일을 불러오고 content 블럭 안에 내용을 넣을 수 있다.

{% extends 'blog/base.html' %}

{% block content %}
    {% for post in posts %}
        <div class="post">
            <div class="date">
                {{ post.published_date }}
            </div>
            <h1><a href="">{{ post.title }}</a></h1>
            <p>{{ post.text|linebreaks }}</p>
        </div>
    {% endfor %}
{% endblock content %}

post detail 페이지 만들기

이제 블로그 포스트를 볼 수 있는 페이지를 만든다. blog/templates/blog/post_list.html의 링크를 수정한다.

<h1><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h1>

blog/urls.py에 post_detail을 추가한다. 뒤에 입력하는 내용을 모두 pk 변수에 저장한다는 의미다. pk는 primary key를 뜻한다.

from django.conf.urls import url
from . import views

urlpatterns = [
  url(r'^$', views.post_list, name='post_list'),
  url(r'^post/(?P<pk>[0-9]+)/$', views.post_detail, name='post_detail'),
]

blog/views.py에 post_detail을 추가한다. 만약 키가 존재하지 않는다면 404 Not Found 페이지로 넘겨야 하는데 get_object_or_404 함수를 사용할 수 있다.

from django.shortcuts import render, get_object_or_404

# ...

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'blog/post_detail.html', {'post': post})

이제 blog/templates/blog/post_detail.html을 생성한다.

{% extends 'blog/base.html' %}

{% block content %}
    <div class="post">
        {% if post.published_date %}
            <div class="date">
                {{ post.published_date }}
            </div>
        {% endif %}
        <h1>{{ post.title }}</h1>
        <p>{{ post.text|linebreaks }}</p>
    </div>
{% endblock %}

모델 폼 사용하기

Django는 간단하게 폼을 생성할 수 있는 기능을 제공한다. blog/forms.py를 생성한다.

from django import forms

from .models import Post


class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ('title', 'text',)

blog/templates/blog/base.html에 이 폼에 접근하기 위한 링크를 추가한다.

<a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>

blog/urls.py에 규칙을 추가한다.

    url(r'^post/new/$', views.post_new, name='post_new'),

blog/views.py에 form과 post_new 뷰를 추가한다.

from django.shortcuts import redirect
from .forms import PostForm

#...

def post_new(request):
    if request.method == "POST":
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.published_date = timezone.now()
            post.save()
            return redirect('blog.views.post_detail', pk=post.pk)
    else:
        form = PostForm()
    return render(request, 'blog/post_edit.html', {'form': form})

blog/templates/blog/post_edit.html을 추가한다.

{% extends 'blog/base.html' %}

{% block content %}
    <h1>New post</h1>
    <form method="POST" class="post-form">{% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="save btn btn-default">Save</button>
    </form>
{% endblock %}

수정 페이지 추가하기

blog/templates/blog/post_detail.html에 다음 링크를 추가한다.

<a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>

blog/urls.py에 다음 코드를 추가한다.

    url(r'^post/(?P<pk>[0-9]+)/edit/$', views.post_edit, name='post_edit'),

blog/views.py에 post_edit 뷰를 추가한다.

def post_edit(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        form = PostForm(request.POST, instance=post)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.published_date = timezone.now()
            post.save()
            return redirect('blog.views.post_detail', pk=pk)
    else:
        form = PostForm(instance=post)
    return render(request, 'blog/post_edit.html', {'form': form})

PostForm()에서 instance를 추가하는 것으로 내용을 미리 초기화한다.

보안

템플릿에서 사용자 권한이 있는 경우만 확인할 수 있도록 작성할 수 있다. blog/templates/blog/base.html에서 새 포스트 링크를 다음과 같이 변경한다.

{% if user.is_authenticated %}
    <a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
{% endif %}

이 튜토리얼 뒤엔 보안을 강화하고 새로운 기능을 추가하는 등의 심화편이 있다. Django Girls Tutorial: Extensions에서 그 내용을 볼 수 있다.

Flask(uWSGI)를 nginx에 연결하기

WSGI는 Web Server Gateway Interface의 약어로 웹서버와 웹어플리케이션이 어떤 방식으로 통신하는가에 관한 인터페이스를 의미한다. 웹서버와 웹어플리케이션 간의 소통을 정의해 어플리케이션과 서버가 독립적으로 운영될 수 있게 돕는다. WSGI는 파이썬 표준인 PEP333, PEP3333에 의해 제안되었고, 이 이후에 여러 언어로 구현된 프로젝트가 생겨나기 시작했다.

WSGI 어플리케이션은 uWSGI라는 컨테이너에 담아 어플리케이션을 실행하게 되며, uWSGI가 각각의 웹서버와 소통하도록 설정하면 끝이다. Flask, django와 같은 프레임워크는 이미 WSGI 표준을 따르고 있기 때문에 바로 웹서버에 연결해 사용할 수 있다.

이 글에서는 Flask를 nginx에 연결하는 방법을 설명한다. 이 글은 기록용이라 상당히 불친절하기 때문에 관련된 내용에 관심이 있다면 다음 페이지들을 참고하자.

uWSGI 설치하기

먼저 uwsgi가 설치되어 있는지 확인한다. 현재 데비안 기반(우분투 등)의 환경이라면 uwsgi가 이미 설치되어 있는데 고대의 버전이라 기존 설치본을 삭제하든 변경하든 해서 새 버전으로 설치해줘야 한다.

$ mv /usr/bin/uwsgi /usr/bin/uwsgi-old

그리고 uwsgi를 설치해준다.

$ pip install uwsgi
$ ln -s /usr/local/bin/uwsgi /usr/bin/uwsgi

이제 uwsgi로 해당 어플리케이션을 실행한다.

$ uwsgi -s /tmp/uwsgi.sock --module yourapplication --callable app --venv .venv

--socket, -s는 통신을 위한 소켓, --venv, -H는 virtualenv 경로, --module은 어플리케이션, --callable은 WSGI의 시작점을 설정해주는 파라미터이다. 내 경우에는 파일명이 이상(?)해서인지 다음의 방식으로 실행했다.

$ uwsgi -s /tmp/uwsgi.sock --wsgi-file app.py --callable app -H .venv

파라미터를 매번 입력하면 번거로우므로 다음과 같이 ini 파일을 작성해 저장해놓고 실행해도 된다.

[uwsgi]
chdir=/home/ubuntu/helloWorld
chmod-socket=666
callable=app
module=app
socket=/tmp/uwsgi.sock
virtualenv=/home/ubuntu/helloWorld/.venv

이렇게 ini파일로 저장한 후 다음 명령어로 실행한다.

$ uwsgi <filename> &

nginx 설치, 설정하기

nginx를 apt-get 등을 통해 설치한다.

$ apt-get install nginx-full

/etc/nginx/sites-available/default 파일을 열어 설정을 해준다.

server {
        listen   8080;

        server_name helloworld.haruair.com;

        location / {
                try_files $uri @helloworld;
        }

        location @helloworld {
                include uwsgi_params;
                uwsgi_pass unix:/tmp/uwsgi.sock;
        }
}

위 설정을 통해 nginx로 들어오는 모든 요청을 uWSGI로 보내고 또 돌려받아 nginx를 통해 클라이언트에 전달하게 된다. nginx를 재구동하면 적용된다.

/etc/init.d/nginx restart

사실 Flask에서의 nginx 설치 문서에 있는 글로도 충분한데 명령어가 계속 에러를 내는 탓에 한참 검색하게 되었다. 문제는 uwsgi의 낮은 버전이었고, 앞서 언급한 바와 같이 최신 버전으로 설치하면 해결된다.