상세 컨텐츠

본문 제목

[Django / URLPattern] URLPattern 내부 코드

Django

by grizzly 2024. 12. 24. 14:16

본문

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를 통해 다룰 주제 중 가장 가볍게 생각하고 접근하기 좋은 주제라 생각해서 처음으로 시작했다. 하지만 그에 반해 어디가 어디를 연결해주는 역할을 하는지 정확히 몰라서 헷갈리다는 생각을 많이 하였다. 이번 과정을 통해서 해당 파라미터의 의미 따위에 대해서 더 생각해 볼 수 있는 기회가 되어 감사하게 생각한다.