들어가며

Django는 이 글을 작성하는 시점에 1.11.3 버전이 공식 배포되고 있으며, 2.0을 올해 12월에 배포할 예정입니다. Python으로 작성된 여러 웹 프레임워크들이 있지만1, Django는 그 자체로도 웹서비스 개발을 위한 수많은 기능들을 제공하고 있으며 third-party 플러그인과 참고자료들이 매우 많아 개발자들이 비즈니스 로직을 구현하는데 집중할 수 있는 환경을 만들어주는데 가장 큰 강점을 지닌 full-stack 웹 프레임워크입니다. 피플펀드에서는 Django를 활용해 소규모의 개발팀임에도 불구하고 매우 빠른 속도로 제품을 성장시킬 수 있었습니다. 그러나 Django가 지닌 가치를 충분히 활용하고 있지 못하고 있는 것 같아 디자인 철학과 핵심 기능 등에 대한 이해도를 높이고자 Mastering Django: Core - The Complete Guide to Django 1.8 LTS를 가지고 스터디를 시작했습니다. 비록 현 시점에서 최신 버전도 아니고 초심자를 고려한 책이지만, 빠르게 핵심 철학과 기능을 알아보는데 충분할 것이라는 판단아래 책을 선정했습니다. 그 첫 스터디로 진행했던 챕터2에서 한 번 짚고 넘어갈 부분을 요약하여 공유합니다.

URLpatterns and Dynamic URLs

URL 패턴에서 몇 가지 알아둘 내용

  • Django는 몇몇 이유(e.g. URL 설정이 다른 URL 설정을 불러와서 사용할 수 있도록 만드는 경우 등. 책 후반에 좀 더 자세히 다뤄질 예정)로 인입되는 모든 URL에서 맨 앞에 있는 /(slash)를 제거한 뒤에 일치하는 패턴을 찾는다.
  • ^(caret): 인입되는 URL의 첫 문자열이 URL 패턴의 첫 문자열과 일치해야한다.
    • e.g. ^hello/라는 패턴을 설정했다면, /hello/로 시작하는 모든 URL(/hello/foo, /hello/bar 등)이 연결된다.
  • $(dollar sign): 인입되는 URL의 마지막 문자열이 URL 패턴의 마지막 문자열과 일치해야한다.
    • e.g. hello/$라는 패턴을 설정했다면, hello/로 끝나는 모든 URL(/foo/bar/hello/ 등)이 연결된다.
  • ^“와 “$“를 사용하지 않으면 URL에 해당 패턴이 들어가는 모든 경우에 매칭된다. 따라서 URL 패턴 작성 시 “^“와 “$“을 사용하는 것을 권장한다.
    • e.g. hello/라는 패턴을 설정했다면, /foo/hello/bar와 같은 URL로 접근하는 경우에도 매칭된다.
  • ^$: 이 경우 비어있는 문자열을 의미하며, 따라서 site root를 지정하는 경우 등에 사용할 수 있다.
  • ()(parentheses): 인입되는 URL 중 괄호안에 설정된 패턴과 일치하는 부분이 해당 패턴과 연결된 view 함수의 인자로 전달된다.
    • e.g. ^time/plus/(\d{1,2}/$와 같은 패턴을 설정했다면, /time/plus/12/라는 URL이 인입되었을 때, 12라는 값이 연결된 view 함수의 두 번째 인자로 전달된다.
    • 책에서는 이 것과 관련하여 query string parameter를 사용하는 것보다 URL 자체에 파라미터를 넣는 것이 더 예쁘다는 점이 Django의 철학 중 하나라는 식으로 설명하고 넘어간다. 아마도 이는 RESTful web service라는 관점에서 도입한 것을 초심자를 배려한 설명으로 예상한다. 그러나 REST로 URI를 표현하는 방식은 현업에서 마주치는 모든 상황에 대처할 수 없는 한계점이 있기 때문에 GraphQL과 같은 시도도 나오고 있으니 참고하자.
  • r: raw string을 의미한다. Django에서는 URL 패턴에 Regular expressions를 사용할 수 있으며, 따라서 URL 패턴에 해당하는 문자열이 python에서 인식하는 특수한 문자로 별도처리되지 않도록 raw string을 사용해야 한다.
    • e.g. "\n"은 python에서 new line을 의미하지만, r"\n"은 문자열 그대로 “\”(backslash)와 “n”을 의미한다.


    Python에서 일반 string은 특수문자에 대한 별개 처리가 들어가 있지만, raw string은 모든 문자를 문자 그대로 인식한다.

How Django Processes a Request

  1. 요청이 /hello/로 들어옴. (e.g. http://your.domain.com/hello/)
  2. Django가 ROOT_URLCONF setting을 통해 URL 설정의 root를 찾아낸다.
  3. root URL 설정에 연결된 모든 URL 패턴 중에서 /hello/와 일치하는 첫 번째 패턴을 찾는다.
  4. 만약 일치하는 패턴이 없다면 HTTP 404에러에 해당하는 HTTP response를 반환하여 요청에 응답한다. (DEBUG 설정값에 따라 HTTP response가 달라진다. 만약 DEBUG 설정 값이 True라면 에러와 관련된 자세한 페이지를 보여주는 식이다.)
  5. 만약 일치하는 패턴이 있다면 그 패턴과 연결된 view 함수를 호출하는데, 첫 번째 파라미터로 HttpRequest 객채를 전달해준다.
  6. view 함수는 HttpResponse 객체를 반환한다.
  7. Django는 HttpResponse 객체를 적절한 HTTP response로 변환하여 요청에 응답한다.

URLconfs and Loose Coupling

Django의 URL 설정에는 Loose Coupling이라는 개념이 들어가 있다. 만약 약하게 결합된 두 개의 코드 조각이 있다고 가정했을 때, 한 쪽을 변경해도 다른 한 쪽에 미치는 영향이 매우 작거나 없다고 하면 이를 약한 결합관계라고 할 수 있다. (다만, Loose Coupling이라는 개념이 반드시 좋은 것만은 아니고 장단점이 모두 있는 개념이므로, 적절한 상황에서 적용해야 한다. 자세한 내용은 링크를 읽어보면 좋다. 가장 단적인 예는 트랜잭션 처리이다. 분리되어있는 두 개의 각기 다른 시스템, 혹은 다른 프로토콜로 이루어진 기능을 트랜잭션 처리를 하기 위해서는 많은 수고가 필요하다.)
Django의 URL 설정은 URL 패턴요청을 처리하는 함수를 별개로 만들어두고, 이들을 엮어주는 부분을 URL 설정에서 기술하도록 되어 있기 때문에 두 기능의 관계가 약하게 결합되어 있다고 볼 수 있다. 또한, 이러한 성격으로 인해 view 함수에서는 URL 패턴에서 처리되는 내용이 이미 처리되었다고 가정하면 안된다. 예를 들어 ^time/plus/(\d{1,2})/$와 같은 URL 패턴이 def hours_ahead(request, offset): 라는 함수에 연결되어 있다고 가정해보자. 이 경우 offset에는 1자리에서 2자리로 이루어진 숫자형식의 문자열이 전달될 것이다. 그러나 view 함수인 hours_ahead내부에서는 offset이 숫자형식이 아닌 경우를 가정하고 프로그래밍을 해야한다. 그래야만 URL패턴의 변경에 영향을 끼치지 않을 수 있으며, URL패턴과 약한결합으로 이루어졌다고 할 수 있다. 다음 코드는 앞서 설명한 개념을 코드로 구현한 예제이다.

urlpatterns = [
    # ...
    url(r'^time/plus/(\d{1,2})/$', hours_ahead),  # hours_ahead 함수를 패턴에 연결한다.
    # ... 
]
from django.http import Http404, HttpResponse
import datetime

def hours_ahead(request, offset):
    # URLconf를 통해 이미 숫자 형식의 변수가 전달될 것임을 알고 있지만, 
    # 다른 값이 들어올 수도 있다고 가정하고 작업을 한다.
    try:
        offset = int(offset)
    except ValueError:
        raise Http404()
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    html = f"In {offset} hour(s), it will be {dt}."
    return HttpResponse(html)