Django의 세부 코드에 대해서 알아보자
장고를 공부하다보면 많이 보는 것 중 하나가, app 디렉토리나 메인 프로젝트 디렉로리에 있는 urls.py에 있는 URLPattern이다.
해당 세부 코드는 Django github (주소 : https://github.com/django/django) - 정확한 위치 (django/urls/resolvers.py)
위치에 있다.
코드를 자세히 보자.
class URLPattern:
def __init__(self, pattern, callback, default_args=None, name=None):
self.pattern = pattern
self.callback = callback # 실제 호출될 view 함수
self.default_args = default_args or {}
self.name = name
def check(self):
warnings = []
# URL 패턴이 유효한지 확인
warnings.extend(self._check_pattern_name())
warnings.extend(self._check_callback())
return warnings
def resolve(self, path):
match = self.pattern.match(path)
if match:
# 패턴이 매칭되면 새로운 ResolverMatch 객체를 반환
kwargs = match.groupdict()
return ResolverMatch(
self.callback,
kwargs,
{},
route=str(self.pattern),
)
return None
def _check_pattern_name(self):
# URL 패턴 이름이 유효한지 검사
if self.name is not None and not self.name:
return [Error(
"Pattern has empty name.",
id="urls.E003",
)]
return []
def _check_callback(self):
from django.urls import get_callable
# callback이 실제 호출 가능한 view인지 검사
if not callable(self.callback):
return [Error(
"View is not callable.",
hint="Could not import %s. View does not exist in module %s." % (self.callback.__name__, self.callback.__module__),
id="urls.E004",
)]
return []
이렇게 있다.
일단 (__init__) 생성자 메서드부터 확인해보면
def __init__(self, pattern, callback, default_args=None, name=None):
self.pattern = pattern
self.callback = callback # 실제 호출될 view 함수
self.default_args = default_args or {}
self.name = name
pattern : URL 패턴을 받는 파라미터
callback : view 함수를 받는 파라미터
default_args = None : 기본 인자들을 받는 파라미터
name = None : URL 패턴의 이름을 받는 파라미터
=> 처음 공부한다는 입장에서 확인해보면
- url pattern 을 받아서 연결하고, view 함수를 받아서 연결하고 url pattern의 이름을 받아서 연결한다.
여기서 뽑아낼 정보는 url pattern은 내부 값이 존재하고, 이름이 존재한다.
view 함수라는 것을 연결해야 한다. 여기까지이다.
좀 더 상세히 알아보자
self.pattern = pattern : url 패턴을 객체 pattern 속성 저장함
self.callback = callback : url이 매칭됐을 때 실행될 view 함수를 저장 (즉, url이 연결되었을 때 view 함수를 실행)
self.default_args = default_args : None이라는 것인 기본적으로 빈 딕셔너리를 사용한다는 의미, view에 전달될 기본 인자들을 저장
(보통 파이썬 함수에서 **args 가 인자의 형태가 정해지지 않은 경우 사용한다고 배웠음, 예를 들어 한국인은 first name, last name 이렇게 두 개면서, 띄어쓰기가 없을 것이다. 하지만 다른 나라 사람의 경우 가문의 3번째 아들 제임스 이런 식의 이름 구조가 다를 수 있다. 이런 다른 형태의 정보를 받지만 같은 방식으로 사용되어야 하는 경우 **args를 쓰는 것으로 알고 있다. default_args에서 args또한 이와 같은 단어의 의미로 default를 의미하지 않을까 생각함)
self.name = name : url 패턴의 이름을 저장, url 역참조 (reverse)에서 사용
여기서 알 수 있는 것은, url 패턴은 역참조 될 수 있다.
view함수를 지칭해서 해당 view함수를 실행시키는 역할도 하지만, 동시에 역참조 되어 다른 곳에서 사용될 수 있다.
연결을 한다고 하면 urlpatterns = [ path('django/practice_djang/', views.django_call, {}, name = django_practice)]
이런 식으로 사용할 수 있다.
# 보통 나는 이거를 실제로 사용해봤을 때, 다른 부분은 직관적이지만 첫 인자 부분이 이해가 잘 안갔다(django/practice_djang/)
이 부분에 해당한다.
이거를 사용하면서 이해한 부분이다. 실제 template의 home.html에서 이동하는 버튼을 추가할 때,
href="{% url 'Stock:token_login' %}
이렇게 연결을 하면 해당 path 부분이 실행된다.
이제 다음 코드에 대해서 알아보자
def check(self):
warnings = []
# URL 패턴이 유효한지 확인
warnings.extend(self._check_pattern_name())
warnings.extend(self._check_callback())
return warnings
def resolve(self, path):
match = self.pattern.match(path)
if match:
# 패턴이 매칭되면 새로운 ResolverMatch 객체를 반환
kwargs = match.groupdict()
return ResolverMatch(
self.callback,
kwargs,
{},
route=str(self.pattern),
)
return None
아직 사용해 본 것은 아니지만, 이러한 코드가 urlpattern 클래스 내부에 있는 것을 확인할 수 있다.
일단 두 개 먼저 확인해보면,
check() 메서드는 URL 패턴의 유효성을 검사하는 코드이다.
해당 메서드는 두 가지 검사를 실행한다.
하나는 _check_pattern_name() 이다.
해당 메서드를 호출하면, url pattern의 이름이 valid 한 지에 대해 검사한다.
def _check_pattern_name(self):
if self.name is not None and not self.name:
return [Error("Pattern has empty name.", id="urls.E003")]
return []
if문부터 해석하면
self.name is not None : self.name이 None인지 검사
not self.name : self.name이 빈 값인지 검사, 파이썬에서 빈 문자열은 False로 평가
-> 해당 if문의 경우 name이 None은 아닌데, 빈 값인 경우
이런 경우 return error를 반환하고, 아니라면 빈 배열을 반환한다.
두번째는 _check_callback() 호출이다.
해당 메서드를 호출하면 view 함수 (callback)가 유효한 지 검사한다.
(실제 호출 가능한 지 검사한다)
def _check_callback(self):
if not callable(self.callback):
return [Error("View is not callable.", id="urls.E004")]
return []
여기서 callable() 메서드는 파이썬 내장 함수로, 객체가 호출 가능한지(함수처럼 실행 가능한 지)를 확인한다.
여기도 또한 굉장히 직관적으로 구현되어 있고, 마찬가지로 에러를 반환한다.
여기서 에러를 반환하게 되면, warnings = [] 으로 선언된 배열에 해당 부분의 에러를 저장하여 return 하는 것을 확인할 수 있다.
여기까지의 과정을 통해서 확인함을 알 수 있다.
근데 초보자의 입장에서 궁금한 것이 있었다.
지금까지의 구조는 class URLPattern에 관한 설명이었는데 지금 실제 사용 장면에서 보면
from django.urls import path
from . import views
app_name = 'Board'
urlpatterns = [
path('', views.board_user, name='board'),
]
보통 이런 식의 사용을 한다.
직관적으로는 urlpatterns라는 배열에다가 path라는 객체를 생성해서 저장하는 것처럼 보인다.
그러한 이유로 path에 대해서 뜯어보자
def path(route, view, kwargs=None, name=None):
return URLPattern(
_route_to_regex(route), # route를 정규식 패턴으로 변환
view, # view 함수
kwargs, # 추가 인자
name # URL 이름
)
이제 굉장히 직관적이다.
결론적으로 path라는 메서드는 URLPattern에 연결해주는 역할을 하는 것으로 볼 수 있다.
따라서 우리가 적은 해당 저 코드는 path의 내부적으로
URLPattern(
pattern=_route_to_regex(''), # 빈 문자열을 정규식 패턴으로 변환
callback=views.board_user, # view 함수
default_args=None, # 추가 인자 없음
name='board' # URL 이름
)
이렇게 처리되는 것을 확인할 수 있다.
urls.py 속 urlpattern [] 속 path는 알고보니 URLPattern 객체 생성 역할을 한다.
즉 wrapper 함수이다.
즉, 이렇게하면 개발자가 직접 URLPattern을 생성하지 않아도 되고, 더 읽기 쉽게 볼 수 있다.
또한 정규식 변환 등의 복잡한 처리를 자동으로 해준다.
# (_route_to_regex) 해당 메서드는 찾아보기는 하였으나, 내용이 길어질 것 같아서 다음으로 넘김.
느낀 점 : 앞으로 django를 통해 다룰 주제 중 가장 가볍게 생각하고 접근하기 좋은 주제라 생각해서 처음으로 시작했다. 하지만 그에 반해 어디가 어디를 연결해주는 역할을 하는지 정확히 몰라서 헷갈리다는 생각을 많이 하였다. 이번 과정을 통해서 해당 파라미터의 의미 따위에 대해서 더 생각해 볼 수 있는 기회가 되어 감사하게 생각한다.