2012. 4. 4. 10:02

[03] django 1.4의 view와 urlconfs

원문 : http://www.djangobook.com/en/2.0/chapter03/ 참고

django로 "Hello world" page 출력하기

web framework없이 Hello world를 출력하려면 단순히 "Hello world"만 있는 hello.html이라는 text file가 있으면 되는데, 이는 contents("Hello World")와 URL(http://www.example.com/hello.html), 즉 2개로 구성된다.

django로는 두 개로 구성된 그 자체는 같지만, 다른 방법으로 지정한다. page의 contents는 view function, 그리고, URL은 URLconf에 의해 만들어진다.

아래 코드는 mysite(2012/03/30 - [django] - django 설치와 project 생성하기)의 연장선상에 있음을 참고하라.

첫번째 View

mysite 경로에 다음과 같은 views.py를 넣는다.

  • 우선, django.http module에 있는 HttpResponse class를 import 하고 있다.
    이는 다음 코드에 필요한 class를 import하기 위해서이다.
  • 다음으로 hello 이름의 view 함수를 정의하고 있다.
    각 view 함수는 request라는 parameter를 가지게 된다. 
    view를 작동할 request에 대한 정보를 표현할 object로, django.http.HttpRequest class의 instance이다.
    함수의 이름은 신경쓸 필요는 없으며, 특별한 규정도 없다.
  • 함수는 단순히 1줄로 구성되며, "Hello world"를 전달하여 생성된 HttpResponse class object를 리턴할 뿐이다.


처음으로 URLconf 해보기


여기까지 작업한 후, python manage.py runserver하여 테스트하면, 다음과 같이 어디에서도 "Hello world"가 표시되지 않는다.


이는 아직 우리가만든 hello view를 모르기 때문이다. 그래서 해당 hello view를 특정 url과 연결하는 과정이 필요하다. URLconf를 이용하여 이러한 연결 과정을 수행한다.

URLconf는 책의 목차와 비슷하다. 즉, "이 url은 이 code, 저 url은 저 code를 각각 실행하라"와 같은 방식이다. 예를 들어 "/foo/를 접근할 때는 views.py라는 python module에 있는 foo_view()라는 view function을 호출해라."를 수행한다.


framework에 의해 만들어진 초기 urls.py는 다음과 같다.

해당 코드는 주로 사용되는 code를 주석으로 처리해 놓아, 필요시 주석을 풀어 사용하면 된다.
일단, 다음과 같이 urls.py를 수정해 보자.

  • 첫줄은 django.conf.urls.defaults module를 import하도록 한다. 여기에서는 patterns 이라는 함수를 include한다 (최근의 django에서 삭제되었다)
  • 두번째줄은 urlpatterns 변수에 patterns 함수의 수행 결과를 저장한다. patterns 함수는 빈 문자열을 전달할 뿐이다.

urlpatterns 변수가 주요한 값인데, 이는 django가 당신의 URLconf module을 찾는데 사용되기 때문이다. 이 변수는 url과 해당 url을 처리할 code간의 mapping을 정의한다. 위에서 보듯이 현재 URLconf는 비어있다. 이는 django application은 blank state로 된다.
url과 view를 추가하기 위해 아래와 같이 수정하면 된다.

  • mysite/views.py module로 부터 hello view를 import하는데, 그것은 mysite.views라는 python 문법으로 표현하였다.
  • ('^hello/$', hello)를 urlpatterns에 추가하였는데, 정규표현식으로 pattern-matching으로 구성된 python tuple로 이뤄졌다.

이제 이를 가지고 다음과 같이 테스트한다.
404오류가 발생하였다.
눈치 빠르신 분은 확인하셨겠지만, 다음과 같이 URL에 /hello를 붙이면 원하는 Hello world가 출력된다.
물론, /Hello하면 역시 404오류가 발생한다.

이제부터 URLpattern의 문법을 확인해 볼 차례이다. /hello/ url로 맞추기원하는데, pattern은 그것과는 조금 다르게 보인다. 그이유는 다음과 같다.

  • django는 incomming url의 매번 처음 나타나는 '/' 문자는 제거한다. 그렇기 때문에 URLpattern을 설명할 때 /hello/ 처럼 시작하는 '/'를 붙이지 않게 된다.
  • pattern은 '^'과 '$' 문자를 포함한다. 이는 다음의 특수한 의미를 가지는 정규 표현식 문자이다.
    '^'는 문자열의 시작에 대한 pattern을 검사한다. 그리고 '$'는 문자열의 마지막의 pattern이 맞는지 검사한다.

    예를 들어 설명하자면, 끝에 $가 없는 "^hello/"는 /hello/로 시작하는 모든 URL들에 대해 연결시킨다. 예를 들면 /hello/foo와 /hello/bar가 포함되며, /hello/는 포함되지 않는다. 유사하게 ^가 없는 "hello/$"와 같은 경우는 hello/로 끝나는 모든 URL들에 대해 연결시킨다. 예를 들어 /foo/bar/hello/와 같다. 만일, "hello/"와 같이 사용하였다면 hello/를 포함하는 모든 URL을 연결한다. 예를 들어, /foo/hello/bar와 같다. 그리고 더 이상, 더 이하도 아닌 /hello/로만 일치시키는데는 "^hello/$"를 사용한다.

    대부분의 URLpattern은 ^$를 사용하는데, 보다 더 정제된 연결을 위해서 유연성을 가질 필요가 있다.

    만일 누군가가 /hello와 같이 요청하였다면 끝에 /로 끝나지 않았기 때문에 어떤것도 연결되지 않을 것이다. 이유는 우리의 URLpattern은 /로 끝나기를 요구하기 때문이다. 그러나 끝에 /가 없더라도 같은 URL에 re-direct되는 것이 보다 일반적이다. (이는 django setting의 APPEND_SLASH를 사용하기도 한다)

    만일 URL 끝의 /를 선호하는 사람(django 개발자)라면, URLpattern 끝에 /를 넣고 APPEND_SLASH에 True값을 전달한다. 만일 그렇지 않은 경우라면 URLpattern 끝에 /를 넣고 APPEND_SLASH에 False값을 전달한다.

그다음 URLconf에 대해 주목할 것은, 직접적인 호출없이 hello view 함수를 전달한 것이다. 이것은 python의 주요 기능중 하나로, 함수는 최상위 object인데, 이는 다른 어떤 변수와 같이 전달할 수 있음을 의미한다.

Regular Express

Regular express(혹은 regexes)는 text의 pattern을 설명하는데 쓰인다. django의 URLconf는 강력한 URL 연결을 위해 독단적인 regexes를 사용한다. 일단 몇개의 regex symbol로 연습할 수 있다. 일반적인 symbol은 다음과 같다.

Symbol 내용
. 모든 단일 문자
\d 모든 단일 숫자
[A-Z] 모든 대문자(A~Z)
[a-z] 모든 소문자(a~z)
[A-Za-z] 모든 대소문자 구별없는 문자(a~z 혹은 A~Z]
+ 이전 표현이 하나 이상 연결(\d+는 하나 이상의 숫자)
[^/]+ 첫 /가 나타날때까지의 하나이상의 문자
? 0 혹은 하나의 이전 표현(\d?는 0 혹은 숫자 한 글자)
* 0 혹은 하나 이상의 이전 표현(\d*는 0 혹은 숫자 한글자 이상)
{1,3} 이전 표현의 1~3개 연결(\d[1,3]은 숫자 1,2,3글자)

보다 많은 정보는 http://www.djangoproject.com/r/python/re-module/ 참고 바람

404 오류에 대한 정보

현재까지 우리는 URLconf에 대해 단지 하나의 URLpattern을 정의하였다. 만일 다른 URL에 대한 요청시 어떤일이 일어 나는가?

http://127.0.0.1:8000/goodbye/ 혹은 http://127.0.0.1:8000/hello/subdirectory/ 혹은 site root인 http://127.0.0.1:8000/을 방문하는 것으로 해보자. 그럼 본 글의 윗 부분에서 설명된 Page not found 오류화면이 발생할 것이다.

해당 화면의 용도는 기본적인 404 오류 메시지의 수준을 넘어선다. URLconf의 모든 pattern을 알려주는데, 왜 404를 던지게 되었는지에 대해 설명한다.

해당 정보는 물론 당신 즉 web 개발자에게만 유용한 민감한 정보일 수 있다. 만일 internet 상으로 deploy되는 경우, 해당 정보를 공개되어 노출되지 않기를 원할 것이다. 그러한 이유로 "Page not found" url은 debug mode인 경우에만 표시되도록 되어 있다. 이러한 debug mode를 off하는 방법은 추후 설명한다.

site root에 관한 정보

이전 section에서 site root에 대해 404 오류 메시지를 확인하였다. django는 site root에 대해 어떠한 장치를 해놓진 않는다. URLpattern에 단지 할당하기에 달려있다.

site root를 URLpattern에 추가하는게 직관적이고 쉽진 않는데, 다음과 같이 empty string을 통해 적용할 수 있다.


django의 request 처리 방식

우리의 두번째 view 함수에서 계속하여, 잠시 django가 어떻게 처리하는지를 배워보도록 하자.
특수하게도 http://127.0.0.1:8000/hello/의 방문에 의해 "Hello world" 메시지를 브라우져에서 볼때 , django는 뒤에선 어떤 일을 하는 것인가?

모든것은 settings 파일로 부터 시작한다. python manage.py runserver를 실행하면 해당 script는 settings.py를 찾는다. 해당 파일은 TEMPLATE_DIRS, DATABASE_NAME등등과 같은 대문자로 이뤄진 django project의 특정 설정을 포함한다. 가장 중요한 설정은 ROOT_URLCONF.ROOT_URLCONF로, web site에서 URLconf로 사용할 python mondule중 어떤것을 사용할지를 알려준다.

django-admin.py startproject가 settings.py와 url.py를 언제 생성하는지 살펴보아라. 자동생성된 settings.py의 ROOT_URLCONF는 자동 생성된 urls.py를 지목하고 있다. settings.py file을 열어보면 아래부분을 확인할 수 있다.

...
ROOT_URLCONF = 'mysite.urls'
...

이는 mysilte/urls.py를 사용하겠다는 뜻이다.

/hello/와 같은 특정 url에 대해 요청(request)이 들어오면, django는 ROOT_URLCONF 설정에 의해 지명된 URLconf를 load한다. 그다음, 그 URLconf에 있는 각각의 URLpattens를 순서대로 한번에 체크하는데, 일치하는것을 찾을때 까지 지속된다. 일치되는 것을 찾았다면, 해당 pattern과 연결된 view 함수를 호출하고, HttpReqeust object를 전달한다.

앞선 예로 보았듯이, view 함수는 HttpResponse를 리턴해야 한다. 그뒤 django는 그 python object를 적합한 HTTP header들과 body로 구성된 web response로 변환한다.

요약:

  1. /hello/ 요청(request)이 들어왔다.
  2. ROOT_URLCONF 설정을 찾아 root URLconf를 구한다.
  3. /hello/ 와 처음으로 일치하는 URLpattern을 URLconf에서 찾는다.
  4. 찾았다면, 연결된 view 함수를 호출한다.
  5. view 함수는 HttpResponse를 리턴한다.
  6. HttpResponse를 적합한 HTTP response로 변환하고, web page로 결과 보고 한다.

이제 django에 의해 page가 생성되는 기본적인 과정을 알게 되었을 것이다.

두번째 view : Dynamic Content

우리의 "Hello world" view는 django의 기본적인 동자을 확인하는데는 유용할 것이다. 그러나 web page를 동적으로 표현하는 경우네는 적합하지 않는데, 항상 같은 page만 보여주기 때문이다. 즉, static HTML file과 같이 동작한다.

두번째 view로, 현재의 시각을 표시하도록 동적인 것을 만들어 보자. 이는 매우 좋고 단순한데, 왜냐하면, 어떠한 database나 사용자 input이 연관되어 있지 않기 때문이다. 단지 server의 시각을 출력할 뿐이다. 그것은 "Hello world"만 출력하는것보다 훨씬 흥미로운데, 단지 조금 새로운 concept만 추가되었을 뿐이다.

이러한 view는 현재 시각의 측정과 HttpResponse를 리턴하는것과으로 두가지를 필요로 한다. python에 익숙하다면, datetime이라는 python module을 포함하면 시각을 계산할 수 있다. 다음과 같다.

>>> import datetime
>>> now = datetime.datetime.now()
>>> now
datetime.datetime(2012, 4, 3, 10, 34, 31, 118000)
>>> print now
2012-04-03 10:34:31.118000
>>>
django에서 현재 시각을 출력하기 위해, 다음과 같이 view를 만들고 HttpResponse를 리턴한다. 다음을 확인하라.

이는 우리의 hello view 함수인 views.py에 포함시키도록 한다. 간결성을 위해 이전의 hello 함수는 숨겼다. 그럼 전체 views.py는 다음과 같다.

(이제 필요하다면 이전의 코드(hello 함수)는 생략할 수 있다)

views.py으로 current_datetime view를 수용하기 위해 만들어진 변경사항을 확인해 보자.

  • date 계산을 위해 import datetime을 추가하였다.
  • 새로운 current_datetime 함수는 현재의 시각을 계산한다. 이는 datetime.datetime object를 이용하며, 지역 변수에 값을 저장한다.
  • python의 "format-string" 기능에 의해 HTML response를 생성하고 있다. %s는 문자열의 위치 지정이 되며, %는 "변수 now의 값으로 %s에 해당되는 문장을 변경하라"라는 뜻이 된다. now 변수는 기술적으로 datetime.datetime object로, string이 아니다. 그러나 %s format은 string 표현으로 변환시키며, "2008-12-13 14:09:39.002731"과 유사하겨 변환한다. 결국 이것으로 인해 HTML string은 "<html><body>It is noew 2008-12-13 14:09:39.002731.</body></html>"와 같이 결정된다.
  • 마지막으로, view는 HttpResponse object를 리턴하는데, 직접 생성한 response를 전달하였다.

views.py에 추가하였다면, /time/으로 처리하도록 다음과 같이 urls.py을 수정한다.
우리는 이곳에서 두개를 수정하였는데, 처음으로 current_datetime 함수를 import하도록 했다. 두번째가 더 중요한데 /time/ url에 대해 새로운 view를 항당하도록 URLpattern을 추가하였다. 이곳에 아래로 쭉 늘어뜨리면 된다.

새로운 view와 수정된 URLconf를 가지고, 테스트하면 아래와 같다.

URLconfs와 느슨한 결합(Loose Coupling)

이제 URLconf와 일반적인 django의 철학인 느슨한 결합(loose coupling) 원리에 대해 설명할 차례이다. 간단히 설명하자면, loose coupling은 교환 가능한 조각으로 만드는것을 중요한 가치로 삼고 있는 software 개발 방법론이다. 만약 코드가 두개의 loose coupling으로 되어 있다면, 임의의 한개가 수정이 되었을 때, 나머지 한개는 영향이 없거나 거의 없다는 것을 의미한다.

django의 URLconf는 그것의 좋은 예이다. django web application에서 url 정의와 view function은 loose coupling되어 있다. 그것은 주어진 view 함수를 위한 url의 결정과 view 함수 자체는 구별된 위치에 정의된다는 것이다. 그로 인해 서로간에 영향을 주지 않도록 하였다.

예를 들어, current_datetime view 함수를 보자. 만약 application의 url을 변경해야 한다면(/time/->/current-time/), view 함수를 생각할 것 없이, URLconf만 얼른 수정하면 된다. 비슷하게, 논리가 변경되어 view 함수를 변경해야 한다면, URL에 영향이 없을 것이다.

더 나아가, current-date 함수를 여러개의 URL로 노출하고자 한다면, code의 수정없이 아래와 같이 URLconf만 다루면 된다.

세번째 view : Dynamic URLs

current_datetime view는 내용(content)이 동적인 경우였다. 그러나 URL(/time/)은 여전히 정적이다. 대부분의 web application에서, URL은 page의 output에 영향을 주는 parameter를 내포하고 있다. 예를 들면, 온라인 북스토어 같은 곳에서, 각 책들은 고유의 URL을 가진다고 가정할때, /books/243 그리고 /books/81196/ 등이 될 것이다.

이제 시간을 주어진 값으로 조정하는 것을 출력하는 세번째 view를 만들어 보자. /time/plus/1이면 한시간 미래, /time/plus/2이면 두시간 미래의 시간을 출력하는 것을 목표로 하자. 초보자라면 아래와 같이 각각의 경우에 대해 다음과 같이 만들지 모른다.

명확하게도 이런 방법은 결함이 있는데, 많은 중복된 view 함수를 야기한다. 그리고, 사전에 정의된 범위의 hour를 지원하는 경우에도 문제가 된다. 만일 미래의 5시간 뒤의 시간을 출려하려면 다시 view를 만들과 URLconf를 수정해야 한다.

이러한 조정값(offset)을 처리하기 위해, 어떻게 해야 하는가? 이를 위해서 URLpatterns에 wildcard를 사용할 수 있다. 앞서 설명했듯이 URLpattern은 regular expression인데, \d+를 이용하여 일치시키는 경우이다.

이러한 새로운 URLpattern은 /time/plus/2/, /time/plus/25/ 혹은 /time/plus/10000000000/를 일치시킨다. 만일 99시간으로 제한한다면, 즉, 1~2개의 숫자로 구성한다면 \d{1,2}와 같을 것이다.

web application을 개발할 때는, 모든 기괴한 input data까지 고려해야 한다. 그리고 어떻든지 application에서 해당 input을 지원할지에 대해 결정해야 한다. 일단 우리는 99시간으로 그 설명의 내용을 줄이도록 한다.

우리가 소개했던 중요한 정보중, regular expression가 r 문자로 시작한다는 것이다. 이는 python에게 해당 문자열은 "raw string"으로 간주해라는 것인데, 그것은 backslash(예를 들어, '\n')를 따로 해석하지 말라라는 뜻이다.  따라서, r'\n'은 개행을 의미하는 것이 아니라, backslash에 n문자가 결합된 상태이다. 보통 regular expression을 문자열로 전달할 때, backslash가 출돌될 수 있으므로 되도록이면 r 문자로 시작하는 것이 일반적이다. 그래서 이제 부터 모든 URLpattern에 대해서 정의된 regular expression의 문자열은 r로 시작하도록 한다.

이제 URL에 대해 wildcard로 지정을 했다면, 그 wildcard data를 전달받는 방법을 알아야 한다. 그래야만 view 함수에서 사용될 offset을 구할수 있기 때문이다. 우리는 이를 위해 URLpattern에서 해당 위치의 값을 괄호로 묶도록 한다. 즉, 와 같이 ...(\d{1,2})/$...와 같이 수정한다.

만약 regular expression에 익숙하다면, 아주 편안하게 느껴질 것이다. 이렇게 괄호를 사용하는것은 일치된 text data의 정보를 가져오는데 응용된다.

마지막으로 URLconf는 다음과 같은 모습일 것이다.

이제 hours_ahead view를 만들어 보자.

hours_ahead는 current_datetime view와 유사한데, 추가 argument를 받는것이 주요 차이점이다.

이제 code를 검토해 보자.

  • hours_ahead view 함수는 request와 offset 두개의 parameter를 받아들인다.
  • request는 HttpRequest object이다. 각각의 view는 항상 HttpRequest object를 첫 parameter로 한다.
  • offset은 URLpattern에 있는 괄호에 해당되는 문자열이 들어온다. 본 예에서는 한개의 괄호가 있었는데, 한개가 더 있는 경우, hours_ahead(request, offset, dummy1)와 같이 추가가 가능하다. 일단 본 예와 같은 경우, /time/plus/3/인 경우 offset은 '3'이 들어온다. 즉, 해당 parameter는 항상 문자열로 이뤄진다.

    (parameter로 잡힌 문자열은 항상 Unicode object로 이뤄진다. 즉, 일반적인 python bytestring이 아니다. 우선, 여기에서는 크게 신경쓸 필요는 없다)

    offset 변수는 일반적인 python identifier로 이뤄지면 된다. 단, request parameter뒤에 위치해야 한다.
  • offset을 가지고 int()를 호출하였다. 이는 문자열을 숫자로 변환한다.

    만일 'foo'와 같이 변환이 불가한 상황인 경우 ValueError exception이 발생한다. 만일 ValueError가 발생하면 django.http.Http404 exception을 발생하고, 404 "Page not found"를 유발한다.

    영리한 독자라면 여기서 의문이 발생할 것이다. {\d(1,2)}를 통해 과연 ValueError 상황이 발생할 것인가? 이다. 그 답은 "아니오"이다. 그 이유는 URLpattern은 input 검증(validation)의 단계에서 동작하는 것으로 제공되기 때문이다. 그런데도 왜 ValueError를 체크하는지 이유는 다음과 같다. view 함수 자체적으로도 이러한 parameter에대한 검증(즉, URLPattern의 regular expression에서의 검증)이 없는 상황을 가정하고 개발하는 것이 좋기 때문이다. loose coupling을 기억하라.
  • 그 다음으로 시각을 계산하는 부분이 나타난다. 새로운건 datetime.timedelta object를 생성하여 시각을 산술하고 그것을 dt 변수에 저장한다. timedelta는 정수를 받아들이기 때문에 int() 함수를 사용하였다.
  • 마지막으로, HTML의 HttpResponse를 전달한다.

이러한 코드를 가지고 테스트하면 아래와 같다.

만일, URLpattern에 ...(\d{1,2})... 를 ...(\d+)...로 변경하면 다음과 같다.
그리고, hours_ahead view에서 다음과 같이 input값 검증을 한다면, 다음과 같은 오류가 발생한다.
즉, 다음은 view 함수에서 raise Http404()에 의한 결과이다.

django의 pretty error page

이제 views.py에서 python error가 발생시 어떻게 되는지를 설명하고자 한다.
다음과 같이 offset=int(offset)을 주석처리하여 실행해 보자.

즉 /time/plus/3과 같이 접근하면 위와 같은 중요 정보가 포함된 에러 페이지를 볼 수 있는데, "unsupported type for timedelta hours component: unicode"라고 최상단에 TypeRrror 메시지를 포함하고 있다.

datetime.timedelta 함수는 hours 파라미터를 integer로 예상하고 있었는데, integer로 변환하는 부분을 주석 처리하였다. 이는 흔히 개발자가 잃으키는 소소한 버그의 한 종류로 설명된다.

위 예는 django의 error page를 보여주기 위함이다. 그러면 해당 page가 알려주는 정보에 대해 알아보자.

  • 최상단에는 해당 exception에 대한 key 정보(노란색 배경)가 들어있다. exception의 종류, 어떤 parameter가 exception을 잃으켰는지, exception이 발생한 파일, 그리고 line number등이 있다.
  • key 정보 아래, 해당 exception에 해당되는 python traceback을 보여준다. 이는 python의 command line interpreter에서의 표준 traceback과 유사하다. 물론 좀더 interactive한 화면으로 구성된다. 각 stack상의 level에서 file의 이름, 함수명, line number, 그리고 해당 line의 source code등을 표시한다.

    각 box(회색) 부분들을 클릭하면 관련 소스 코드와 로컬 변수값등을 체크해 볼 수 있는데, 이는 debugging하는데 큰 도움을 준다
  • "Switch to copy-and-paste view"를 클릭하면 다른 버전의 traceback을 보여준다. 이는 해당 exception을 다른 technical support에 공유할때 유용하게 사용된다. 예를 들어, django irc chat room이나 django user mail group등이다.
  • "Request information"은 GET과 POST 정보, cookie 값, CGI header와 같은 meta 정보등을 나열한다.
    그리고 그 아래의 "Settings"를 통해 django 설치에 대한 특정 설정값을 보여준다.

django error page는 template syntax error같은 특별한 경우 보다 많은 정보를 표시하기도 한다.

print 구문을 조심스럽게 이용하여 debug에 도움이 되기도한데, 이러한 print 구문없이 django의 error page를 이용할 수도 있다. view의 임의의 위치에 assert False를 추가하면 아래와 같다.


마지막으로 이러한 정보는 매우 민감하다. 즉, 공공의 internet 상에서 이런정보가 나타난다는것은 어리석인 일이다. 그래서 이것은 django project가 debug mode일 때에만 나타나도록 하였다. 추후 Chapter 12에서 debug mode를 비활성화하는 방안을 설명한다.

참고자료:

mysite_chap3.zip

'django > the django book study' 카테고리의 다른 글

[05] django의 model  (8) 2012.04.12
[04] django의 template  (14) 2012.04.05
[02] django 1.4 설치와 project 생성하기  (1) 2012.03.30
[01] django 소개  (1) 2012.03.28
[00] Chapter 0  (0) 2012.03.28