[08] django Views와 URLconfs의 고급 기능
(chapter 7까지 django의 core 내용이여서 full 번역하였지만, chapter 8 부터는 고급 내용이라 축약 번역합니다.)
chapter 3(2012/04/04 - [django/the django book study] - [03] django 1.4의 view와 urlconfs
)에서 django view 함수와 URLconfs의 기초에 대해 알아보았다. 본 chapter는 해당 기능에 대해 자세히 알아보자.
URLconf 요령
function import 간소화
chapter 3에서 만들었던 URLconf를 고려해보자.
from django.conf.urls.defaults import * urlpatterns = patterns('', |
그러나 django application이 복잡하게 성장하게 되면, URLconf 역시 커져간다. import 구문이 길어지게 되는데, 이를 줄일 수 있는 방법이 있다. 다음은 앞선 URLconf와 동일하다.
from django.conf.urls.defaults import * urlpatterns = patterns('', |
from django.conf.urls.defaults import * urlpatterns = patterns('', |
더 나아가 공통의 "view prefix"를 제거하는 방법도 있다. 앞선 예에서 중복된 'mysite.views'로 모두 시작하고 있다. 이를 제거하는 것으로, 아래는 그 예이다.
from django.conf.urls.defaults import * urlpatterns = patterns('mysite.views', |
이러한 두가지 접근법을 염두해둔채, 어떤것이 더 좋을지 생각해 보자. 이는 단지 당신의 코딩 스타일에 의존한다.
문자열 접근법은 다음의 장점이 있다.
- view function을 import하지 않기 때문에 간편하다.
- 만약 view function이 여러개의 다른 python module로 나눠져 있다면 보다 읽고 관리하기 쉽다.
function object를 사용하는 장점은 다음과 같다.
- view fuction을 "wrapping"하기 쉽다. 다음의 "view function을 wrapping하기"를 참고 바람
- 좀더 python 스럽다. 이는 python의 전통을 따른다는 뜻이다.
두 접근법은 유효하고 동일한 URLconf에서 적절히 섞어 쓸 수 있다. 선택은 당신의 몪이다.
여러개의 view prefix 사용하기
만약 문자열 접근법을 사용한다면, 공통의 prefix를 만들어 내기가 힘들 수 있다. 그런 경우 patterns() object를 add하는 방법이 있다.
수정전:
from django.conf.urls.defaults import * urlpatterns = patterns('', |
from django.conf.urls.defaults import * urlpatterns = patterns('mysite.views', urlpatterns += patterns('weblog.views', |
Debug mode에서의 URLs에 대한 특별한 case
urlpatterns를 동적으로 생성한다고 했는데, django의 debug mode동안 URLconf의 행위를 변경할 수 있는 기법이 있다. 이를 위해 runtime에서 DEBUG setting을 다음과 같이 확인하라.
from django.conf import settings urlpatterns = patterns('', if settings.DEBUG: |
명명된(named) group 사용하기
여태껏 예제는 간단한 명명되지 않은(non-named) regular expression group을 사용하였다. 이는 URL에서 원하는 부분을 가져오기(capture) 위해 괄호를 사용했다는 것이고, django는 가져온 text를 positional argument로서 view function에 전달한다. 보다 고급 사용을 위해 명명된(named) regular expression group을 사용하여 URL 부분을 가져오고 view에 keyword argument로 전달한다.
python function은 keyword argument와 positional argument를 사용할 수 있다. 어떤 경우에는 동시에 사용할 수 있다. keyword argument 호출은 전달할 값을 가지고 argument의 이름을 지정해야 한다. positional argument 호출은 값을 매치할 argument의 명시적인 지정없이 단순히 argument를 전달하면 된다. argument의 순서가 암묵적으로 적용된다.
예를 들어, 다음의 간단한 함수를 고려해 봐라.
def sell(item, price, quantity): print "Selling %s unit(s) of %s at %s" % (quantity, item, price) |
sell('Socks', '$2.50', 6) |
sell(item='Socks', price='$2.50', quantity=6) sell(item='Socks', quantity=6, price='$2.50') sell(price='$2.50', item='Socks', quantity=6) sell(price='$2.50', quantity=6, item='Socks') sell(quantity=6, item='Socks', price='$2.50') sell(quantity=6, price='$2.50', item='Socks') |
sell('Socks', '$2.50', quantity=6) sell('Socks', price='$2.50', quantity=6) sell('Socks', quantity=6, price='$2.50') |
아래는 명명되지 않은 group으로 된 URLconf의 예이다.
from django.conf.urls.defaults import * urlpatterns = patterns('', |
from django.conf.urls.defaults import * urlpatterns = patterns('', |
예를 들어, 명명되지 않은 group으로, /article/2006/03/ 은 아래와 같이 함수가 호출된다.
month_archive(request, '2006', '03') |
month_archive(request, year='2006', month='03') |
물론, 명명된 group의 장점은 간결성에 있다. 몇몇 개발자는 명명된 group 문법이 못생기고 장황하다고 한다. 그럼에도 불구하고 regular expression 혹은 django application에 익숙하지 않은 사람들에게도 가독성의 장점이 있다.
matching/grouping 알고리즘 이해하기
URLconf에서 명명된 group을 사용하는 통고는 단일 URLconf pattern이 명명된거나 명명되지 않은 group 모두 포함할 수 없다는 것이다. 만일 그러하다면 django는 오류를 발생하지는 않으나 예상과 다르게 URL이 match되지 않는것을 발견할 것이다. 특별히 URLconf parse가 따르는 알고리즘을 명명된 group vs 명명되지 않은 group을 가지고 설명한다.
- 명명된 argument가 있다면, 사용될 것이며, 명명되지 않은 argument는 무시된다.
- 그렇지 않은 경우, positional argument 처럼 모든 명명되지 않은 argument에 전달한다.
- 위 두 경우에 대해, keyword argument로 추가 option을 전달한다. 다음 section을 참고하라.
view function에 추가(extra) option 전달하기
한번쯤 당신은 조금만 다르고 비슷해 보이는 view 함수를 자성하고 있는 자신을 발견할 것이다. 예를 들어, 사용할 template를 제외하고 동일한 내용을 가지는 2개의 view를 가지고 있다고 가정하자.
# urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', # views.py from django.shortcuts import render_to_response def foo_view(request): def bar_view(request): |
이를 가지고 위 code를 다음과 같이 다시 작성할 수 있다.
# urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', # views.py from django.shortcuts import render_to_response def foobar_view(request, template_name): |
이러한 추가 URLconf option 테크닉은 view function에게 추가 정보를 전달하는 아주 좋은 방법이다. 대부분의 generic view와 같은 경우 사용된다.(chapterr 11 참고)
URLconf에서 가져온(captured) 값을 속이기(faking)
pattern에 맞지 않는 URL이 있으나 동일한 view logic이 실행되어야 할 경우가 있다. 이 경우, 가져온 값을 "속일(fake)"수 있다.
예를 들어, 특정 일에 어떤 data를 출력해야 하는 application이 있다면, URLs는 다음과 같을 것이다.
/mydata/jan/01/ /mydata/jan/02/ /mydata/jan/03/ # ... /mydata/dec/30/ /mydata/dec/31/ |
urlpatterns = patterns('', (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view), ) |
def my_view(request, month, day): # .... |
예를 들어 /mydata/birthday/와 같은 URL을 추가하고자 한다면, /mydata/jan/06/과 일치하게 된다. 이를 해결하기 위해 URLconf의 추가 option을 사용할 수 있다.
urlpatterns = patterns('', (r'^mydata/birthday/$', views.my_view, {'month': 'jan', 'day': '06'}), (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view), ) |
View Generic 만들기
공통된 부분을 제거하는 것은 좋은 programming 훈련이다. 예를 들어, 다음 두개의 python function을 보아라.
def say_hello(person_name): def say_goodbye(person_name): |
def greet(person_name, greeting): print '%s, %s' % (greeting, person_name) |
이를 가지고 view에 대해 높은 수준의 추상화를 만들 수 있을 것이다.
아래 code를 확인해 보아라.
# urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', # views.py from django.shortcuts import render_to_response def event_list(request): def entry_list(request): |
# urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', # views.py from django.shortcuts import render_to_response def object_list(request, model): |
- model class를 model parameter로서 직접 전달한다. 추가 URLconf option의 dictionary는 문자열 뿐만 아니라 어떤 종류의 python object도 가능하다.
- model.object.all()은 duck typing의 예이다 : "오리처럼 걷고 오리처럼 말한다면, 그것을 오리로 다룬다". object model이 어떤 type인지 우리는 모르고 있다. 단지 요구되는 것은 model은 objects라는 attribute가 있어야 하고 all() method가 있어야 한다는 것이다.
- model.__name__.lowe()를 사용하여 template 이름을 결정한다. 모든 python class는 class 이름을 전달하는 __name__ attribute를 가진다. 이 기능은 실행시(runtime)까지 class의 type을 모르는 경우에 유용하다. 예를 들어, BlogEntry class의 __name__은 'BlogEntry'라는 문자열이다.
- 본 예제와 이전 예제의 차이점은, template에 generic 변수 이름인 object_list를 전달했다는 것이다. 우리는 blogentry_list 혹은 event_list가 되기 위해 이 변수 이름을 쉽게 변경할 수 있지만, 이는 남겨두었다.
database-driven web site는 몇몇 공통된 pattern을 가지고 있기 때문에, django는 시간을 절약해 줄 "generic view"라는 것을 가져왔다. 이는 chapter 11에서 다룰 예정이다.
view 설정 option 주기
django application을 배포하고 있다면, 설정의 정도를 원하는 사용자가 있을 것이다. 원하는 변경사항을 view에 추가하는것이 좋은 생각일 것이다. 이 목적으로 URLconf parameter를 사용할 수 있다.
설정을 만들기 위한 application의 일부는 다음과 같이 template 이름으로 가정하자.
def my_view(request, template_name): var = do_something() return render_to_response(template_name, {'var': var}) |
가져온(captured) 값 vs. 추가(extra) option의 우선권 이해하기
충돌이 있는 경우, 추가 URLconf parameter가 가져온 parameter보다 우선해서 전달한다. 다시 말해 만약 URLconf가 명명된 group 변수로 가져오고 추가 URLconf parameter로도 동일한 이름으로 가져왔을 경우, 추가 URLconf parameter 값이 사용된다.
아래는 그 예이다.
from django.conf.urls.defaults import * urlpatterns = patterns('', |
default view argument 사용하기
다른 편리한 속임수로 view의 argument에 default parameter를 지정하는 것이다. 이는 지정되지 않는 경우 default로 사용할 값을 view에 알려주는 것이다.
아래는 그 예이다.
# urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', # views.py def page(request, num='1'): |
(주의할 것은, '1'이 정수가 아니라 문자열임을 명심하라. 일관성 유지를 위해 가져온 값이 항상 문자열이기 때문에 그렇게 하였다.)
설정 option으로도 응용되고도 한다. 이 예는 이전 section에서의 예보다 좀더 발전되었다.
def my_view(request, template_name='mysite/my_view.html'): var = do_something() return render_to_response(template_name, {'var': var}) |
특별한 용도의(special-casing) view
큰 집합의 URL을 처리하는 하나의 pattern이 있는 경우, 가끔은 특별한 예외 처리를 하고 싶을 때가 있다. 이런 경우, URLconf가 순서대로 실행되는 방식의 장점을 살려, 그 예외를 선두에 놓도록 한다.
예를 들어, django admin site에 있는 "add an object" page를 생각해보자.
urlpatterns = patterns('', # ... ('^([^/]+)/([^/]+)/add/$', views.add_stage), # ... ) |
def add_stage(request, app_label, model_name): if app_label == 'auth' and model_name == 'user': # do special-case code else: # do normal code |
urlpatterns = patterns('', # ... ('^auth/user/add/$', views.user_add_stage), ('^([^/]+)/([^/]+)/add/$', views.add_stage), # ... ) |
URL의 text 가져오기(capture)
각각의 가져온 argument는 regular express가 만든 match의 종류와 관계없이 python unicode 문자열로 전달된다. 다음과 같은 URLconf를 고려해 봐라.
(r'^articles/(?P<year>\d{4})/$', views.year_archive), |
이는 view code 작성시 염두해 두는것이 중요하다. 종종 datetime.date object에 문자열을 전달하면 오류가 발생한다.
>>> import datetime >>> datetime.date('1993', '7', '9') Traceback (most recent call last): ... TypeError: an integer is required >>> datetime.date(1993, 7, 9) datetime.date(1993, 7, 9) >>> |
# urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', # views.py import datetime def day_archive(request, year, month, day): |
def day_archive(request, year, month, day): date = datetime.date(int(year), int(month), int(day)) |
URLconf가 무었을 검색하는지 결정하기
request가 도착하면 django는 request URL에 대해 URLconf pattern을 python 문자열로 match한다. 이는 GET 혹은 POST parameter 그리고 domain 이름도 포함되지 않는다. 역시 맨 앞의 /는 포함되지 않는다.
예를 들어, http://www.example.com/myapp/ 요청이 있었다면 django는 myapp/를 match할 것이다. http://www.example.com/myapp/?page=3 이었다면 django는 myapp/을 match한다.
request method(POST 혹은 GET)은 URLconf에서 사용되지 않는다. 다시말해 모든 request method는 동일 URL에 라우트된다.
높은 추상화 수준의 view function
다음의 URLconf/view를 고려해보자.
# urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', # views.py from django.http import Http404, HttpResponseRedirect def some_page(request): |
다음은 그 예이다.
# views.py from django.http import Http404, HttpResponseRedirect def method_splitter(request, GET=None, POST=None): def some_page_get(request): def some_page_post(request): # urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', |
- method_splitter()라는 새로운 view 함수를 작성하였다. 이는 request.method 기반으로 다른 view를 선별한다. view function이여야 하는 GET과 POST라는 두개의 keyword argument를 검색한다. 만약 request.method가 'GET'이라면 GET view를 호출한다. 만약 request.method가 'POST'이라면 POST view를 호출한다. 만약 request.method가 어느것도 아니라면(예, HEAD), Http404를 발생한다.
- URLconf에서 method_splitter()의 /somepage/를 주목하고 추가 argument로 전달한다.
- 마지막으로 some_page() view는 두개의 view function으로 나뉜다. 이는 단일 view로 구성한 것보다는 좋아 보인다.
이러한 view function들은 기술적으로 request.method 체크를 할 필요가 없는데, method_splitter()가 했기 때문이다. 그러나 보다 안전하게 확인하고 있다.
method_splitter()를 좀더 향상시킬 수 있는데, GET과 POST에 대해 가정하고 만들어졌다. 만일 method_splitter()에 URL로 부터 가져온 값이던지 혹은 optional keyword argument를 받을수 없을까?
이를 위해 *(asterisks)가 포함된 변수를 사용할 것이다. 다음은 그 예이다.
def method_splitter(request, *args, **kwargs): get_view = kwargs.pop('GET', None) post_view = kwargs.pop('POST', None) if request.method == 'GET' and get_view is not None: return get_view(request, *args, **kwargs) elif request.method == 'POST' and post_view is not None: return post_view(request, *args, **kwargs) raise Http404 |
예를 들어 다음 함수를 보아라.
def foo(*args, **kwargs): print "Positional arguments are:" print args print "Keyword arguments are:" print kwargs |
>>> foo(1, 2, 3) Positional arguments are: (1, 2, 3) Keyword arguments are: {} >>> foo(1, 2, name='Adrian', framework='Django') Positional arguments are: (1, 2) Keyword arguments are: {'framework': 'Django', 'name': 'Adrian'} >>> |
view 함수를 wrapping하기
마지막 view 속임수로 python 테크닉을 이용하는 것이다. 다음과 같이 중복 code를 확인해 보아라.
def my_view1(request): def my_view2(request): def my_view3(request): |
만약 위와 같은 조그마한 중복된 부분을 제거하기 위해, 다음과 같이 view wrapper를 작성하면 된다.
def requires_login(view): def new_view(request, *args, **kwargs): if not request.user.is_authenticated(): return HttpResponseRedirect('/accounts/login/') return view(request, *args, **kwargs) return new_view |
이제 wrap된 view를 적용하면 다음과 같다.
from django.conf.urls.defaults import * urlpatterns = patterns('', |
다른 URLconf 포함(include)하기
만약 여러개의 django 기반 site에서 사용할 code를 의도한다면, URLconf를 포함하는 것을 고민해야 한다.
URLconf는 다른 URLconf module을 "포함"할 수 있다. 다음은 그 예이다.
from django.conf.urls.defaults import * urlpatterns = patterns('', |
여기서 중요한점이 하나 있다 : 본 예에서 include를 사용하는 regular expression에서는 $가 없다. 그러나 끝에 /를 붙이고 있다. django는 include()를 만나게 되면 match된 URL의 부분을 자르고 남아있는 문자열을 이후의 작업을 위해 include한 URLconf에 전달한다.
이 예에서 계속하여, mysite.blog.urls의 URLconf는 다음과 같다.
from django.conf.urls.defaults import * urlpatterns = patterns('', |
- /weblog/2007/ : 첫 URLconf인 r'^weblog/'가 match된다. 왜냐하면, include가 사용되었기 때문이다. django는 match된 text인 'weblog/'를 제거한다. 남은 부분의 URL인 2007/이 mysite.blog.urls URLconf의 첫부분에 의해 match된다.
- /weblog//2007/ : 첫 URLconf인 r'^weblog/'가 match된다. 왜냐하면, include가 사용되었기 때문이다. django는 match된 text인 'weblog/'를 제거한다. 남은 부분인 /2007/은 mysite.blog.urls URLconf에서 어떤것도 match되지 못한다.
- /about/ : URLconf의 첫부분인 mysite.views.about view에 match된다. include를 사용한 것과 사용하지 않은 것을 섞어 쓸 수 있다.
include를 사용했을때, parameter를 어떻게 가져오는가?
include를 사용한 URLconf는 부모(parent) URLconf로 부터 parameter를 가져온다. 다음은 그 예이다.
# root urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', # foo/urls/blog.py from django.conf.urls.defaults import * urlpatterns = patterns('', |
가져온 parameter는 include된 URLconf에 받아들일 수 있는지 여부와 관계없이 항상 있는 모든 line에 전달된다. 이러한 이유로 이 테크닉은 당신이 URLconf에 있는 모든 view가 당신이 전달한 parameter를 받아들이는 것을 확신할 때 사용할 수 있다.
include를 사용했을떄, 추가 URLconf option는 어떻게 동작하는가?
유사하게도 추가 URLconf option을 include에 전달할 수 있다. 이를 위해 include된 URLconf에 각각의 line은 추가 option을 전달받게 된다.
예를 들어, 다음 두개의 URLconf 집합은 기능적으로 동일하다.
# urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', # inner.py from django.conf.urls.defaults import * urlpatterns = patterns('', |
# urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', # inner.py from django.conf.urls.defaults import * urlpatterns = patterns('', |
'django > the django book study' 카테고리의 다른 글
[09] django template의 고급 기능 (8) | 2012.09.27 |
---|---|
[07] django의 form 처리 (0) | 2012.05.06 |
[06] django admin site (3) | 2012.04.27 |
[05] django의 model (8) | 2012.04.12 |
[04] django의 template (14) | 2012.04.05 |