[Django] 장고 URL 설계
- -
장고 URL 설계
장고에서 URL(Uniform Resource Locator)은 웹 애플리케이션의 각각의 기능이나 페이지에 대한 주소를 말한다. URL은 사용자가 브라우저의 주소창에 입력하여 특정 페이지로 이동하거나, 웹 애플리케이션에서 링크를 통해 이동할 때 사용된다. 장고에서 URL은 주로 urls.py 파일에 정의되며, URL 패턴을 특정 뷰(view) 또는 액션과 연결한다. URL 패턴은 사용자의 요청이 들어왔을 때, 어떤 뷰를 호출할지를 결정하는 역할을 한다.
느슨한 결합
장고는 뷰와 URL의 느슨한 결합이라는 철학을 가지고 있다. 그래서 뷰 구현이 직접 URL을 매핑하지 않고, URL 매핑을 뷰 구현과 따로 처리하여 URL을 보다 유연하게 정의한다.
무슨 의미인지 정확히 알기 위해 먼저 스프링 프레임워크에서는 어떻게 URL을 구현하는지 한 번 살펴보자.
Spring
// 스프링 예시
@Controller
public class PostController{
@GetMapping("/blog")
public String postList(){
// ...
}
@GetMapping("/blog/{id}/")
public String postDetail(@PathVariable Long id){
// ...
}
}
스프링에서는 위와 같이 각 컨트롤러마다 처리하는 URL을 매핑한다. 물론, 스프링도 별도의 XML을 통해 URL을 정의할 수 있는 것으로 알고 있다. 그렇게 분리를 할 수는 있지만 일반적으로는 컨트롤러 메서드 위에 연결할 URL을 정의한다. 그러면 이제 장고에서는 어떻게 URL을 정의하는지 살펴보자.
Django
url.py 파일
# blog/urls.py
urlpatterns = [
path("", views.post_list),
path("<int:pk>/", views.post_detail),
]
장고에서는 urlpatterns 정의를 list 형태로 정의한다. path 함수의 첫 번째 파라미터는 들어오는 URL 주소를 의미하고, 두 번째 파라미터는 해당 주소가 요청으로 들어올 시, 실행할 함수를 의미한다. 따라서 ""(최상위)의 URL 주소 요청이 들어온다면 views.py에 있는 post_list 함수를 실행하겠다는 의미이다.
<int:pk>는 URL에서 정수값을 받아올 때 사용된다. pk는 primary key(기본 키)의 약자로, 주로 데이터베이스 모델에서 사용되는 기본 키를 나타낸다. 이 부분에 들어온 정수 값은 뷰로 전달 되어 views.py에 정의되어 있는 post_detail 함수의 파라미터 값으로 들어가게 된다. 따라서 해당 패턴은 숫자로 이루어진 경로를 통해 post_detail 뷰를 호출하는 역할을 하게 된다. 예를 들어, /1/과 같은 URL에 접근하면, pk 부분에는 1이라는 정수 값이 전달되어 post_detail 뷰에서 해당하는 데이터를 조회하게 된다.
📢 urlpatterns 리스트는 파이썬 코드이기 때문에, 상황에 따라 유연하게 정의 및 변경이 가능하다.
views.py 파일
def post_list(request):
...
def post_detail(request, pk):
...
urls.py에서 매핑되어 있는 URL이 호출되면 실행될 함수들이 정의되어 있는 곳이다. 이렇게 뷰 구현과 URL 패턴 정의를 따로 분리하고 있다.
장고 앱의 재사용성
장고앱은 앱의 재사용성에 그 목적이 있다. 하나의 장고앱을 여러 프로젝트에서 사용할 수도 있다는 의미이다. 만약의 A프로젝트에서 blog라는 앱을 하나 만들었다고 가정해 보자. 그렇다면 A 프로젝트에서 만든 blog 앱을 A 프로젝트에서도 사용하고 B 프로젝트에서도 사용한다고 가정했을 때 어떤 방식으로 재사용을 할까?
장고앱은 하나의 폴더 구조이기 때문에, 장고앱으로 구현하면 그 폴더만 복사해서 다른 프로젝트에 붙여 넣으면, 바로 동작할 수 있는 형태이다. 같은 blog 앱이라도 url의 이름을 프로젝트마다 다르게 설정하고 싶거나 할 때가 있을 수 있기 때문에 장고는 유연하게 다르게 지정할 수 있는 방법을 제공해 준다. 무슨 의미인지 아래의 예제 코드를 살펴보며 이해해 보자.
📢 path의 첫 번째 파라미터를 URL Prefix라고 한다.
# blog/urls.py
urlpatterns = [
path("",views.post_list),
path("<int:pk>/", views.post_detail),
]
# blog/views.py
def post_list(request):
qs = Post.objects.all()
return redner(request, "blog/post_list.html"),{
"post_list" : qs.
})
def post_detail(request, pk):
post = Post.objects_or_404(Post, pk = pk)
return render(request, "blog/post_detail.html",{
"post" : post,
})
blog/urls.py와 blog/views.py가 위와 같이 정의되어 있는 blog 앱이 있다고 가정했을 때 A와 B 프로젝트 각각에서 아래와 같이 URL Prefix를 다르게 지정하여 사용할 수 있다.
A 프로젝트
# A 프로젝트 mysite/urls.py
from django.urls import path, include
urlpatterns = [
path("blog/", include("blog.urls")),
]
A 프로젝트에서는 blog.urls를 include 할 때 "blog/"를 prefix 주소로 쓰고,
B 프로젝트
# B 프로젝트 mysite/urls.py
from django.urls import path, include
urlpatterns = [
path("weblog/", include("blog.urls")),
]
B 프로젝트에서는 blog.urls를 include 할 때, "weblog/"를 prefix 주소로 사용할 수 있다.
blog 폴더의 코드는 하나도 변경되지 않고, blog 앱 밖에서 blog.urls를 include 할 때 blog 앱은 전혀 변경되지 않은 상태에서 외부에서 위와 같이 다르게 사용할 수 있다.
이게 가능한 이유는 주소와 뷰의 느슨한 결합 덕분에 유연하게 URL을 정의할 수 있는 것이다. 그러니 장고의 앱 폴더만 다른 프로젝트에 복사하면, 다른 복잡한 설정 없이 사용할 수 있다. 또한, 뷰에 대한 URL 문자열이 매번 바뀌어도, 장고 애플리케이션 구동에서는 오류가 전혀 발생하지 않도록 할 수 있다. 이는 장고의 강력한 URL Reverse 기능 덕분에, 뷰 코드나 HTML 템플릿에서 뷰가 호출되기 위한 URL 문자열을 하드코딩하는 것이 아니라 장고가 대신 URL 문자열을 계산해 주는 기능이 있다. 그 기능이 URL Reverse이다.
엄격한 URL 패턴
장고에선 URL 문자열 패턴에 정규 표현식을 지원하기에 보다 엄격한 URL 패턴이 가능하다. 예를 들어, /123/과 /about/ 주소를 서로 다른 뷰에 매칭할 수 있다. 보통은 슬래시(/)를 구분자로 해서 둘 다 1칸씩이기 때문에, 두 주소를 같은 뷰/컨트롤러에서 처리할 수밖에 없다. 하지만, 장고는 숫자만 들어가는 패턴과 숫자가 아닌 패턴을 서로 구별할 수 있다. 따라서, 숫자만 들어가는 문자열 패턴은 A라는 뷰, 그 외 패턴은 B라는 뷰에서 처리하도록 할 수 있다.
# blog/urls.py
from django.urls import re_path
urlpatterns = [
# /blog/100/ 요청은 부합되지만, /blog/about/ 에는 부합되지 않고 다음 path rule을 체크하게 된다.
re_path(r"(?P<pk>\d+)/$", views.post_detail),
# /blog/about/ 요청은 본 path rule에 부합되어, page_detail 뷰가 호출되며 page_name = "about"
# 키워드로 파라미터가 전달된다.
re_path(r"^(?P<page_name>.+)/$", views.page_detail),
re_path(r"^articles/(?P<year>\d{4}/$", views.year_archive),
]
지금까지는 urlpatterns 정의 시에 path 함수만 사용했었지만, re_path 함수도 사용할 수 있다. re는 정규표현식(regular expression)이라는 의미이다. re_path 함수의 첫 번째 파라미터는 정규표현식 문자열 패턴을 지정한다. re_path의 첫 번째 파라미터는 아마 처음 보는 분들이라면 복잡하게 느낄 수 있다고 생각한다. 하지만 이번 시간은 정규표현식을 다루는 주제가 아니기 때문에 간단하게 위의 예제 코드에 정의되어 있는 정규 표현식들만 간단하게 살펴보자.
re_path(r"(?P<pk>\d+)/$", views.post_detail)
숫자 패턴이 1회 이상 반복이 되면 그 연속된 숫자 문자열을 pk 이름으로 post_detail 뷰 호출 시에 파라미터로 전달하겠다는 의미이다.
re_path(r"^(?P<page_name>.+)/$", views.page_detail)
마침표(.)는 정규표현식에서 거의 모든 문자열에 매핑이 된다. 그러니 앞선 첫 번째 re_path에 매칭이 되지 않으면 위의 코드인 두 번째 re_path에 매칭이 되어 요청 시에 page_detail 뷰가 호출되어 요청을 처리하게 된다.
re_path(r"^articles/(?P<year>\d{4}/$", views.year_archive)
articles/ 부분은 고정이다. 이어서 슬래시(/)와 year 연도인데, 역슬래시 d는 숫자 패턴이 4회 반복되고 슬래시(/)로 끝나는 패턴을 의미한다. 예를 들어, /blog/articles/2023/ 과 같은 요청이라면 year_archive 뷰가 호출되지만 /blog/articles/20233/ 요청은 숫자가 연속으로 5회 반복되기에 이 패턴은 부합되지 않는다. 따라서 위와 같이 URL 패턴이 정의가 되면, year_archive 뷰가 호출이 될 때에는 year 파라미터는 숫자 패턴의 문자열이 정확하게 4글자임을 보장을 받을 수 있다.
URL 패턴을 위와 같이 엄격하게 정의함으로써, 뷰에서 정확히 필요로 하는 URL 패턴만 필터링해서, 엉뚱한 URL에 대해서 뷰가 호출이 되지 않도록 최적화를 할 수 있다.
모범 사례를 장려
장고는 또한 모범 사례를 장려한다. 보다 가독성 높게 URL 패턴을 정의할 수 있다. 장고의 기능으로 path converter가 있다. path converter는 예를 들어, 우리가 path 함수를 사용할 때 첫 번째 파라미터로 사용하는 <int:pk>를 path 경로로 변환해 주는 역할을 한다.
앞선 re_path는 정규표현식으로 패턴을 일일이 지정했어야 했지만 path 함수를 사용할 땐 path converter를 통해 간결하게 URL 패턴을 지정할 수 있다. 그런데 기본 지원되는 path converter는 사실 몇 개 없다. 물론, 커스텀 path converter를 만들어서 사용할 수 도 있다. re_path를 통해 URL 패턴을 정의하는데 만약 같은 패턴이 반복된다면 커스텀 path converter를 통해 보다 간결하게 패턴을 정의할 수 있다.
# blog/urls.py
from django.urls import include, path, re_path, register_converter
from .converters import FourDigitYearConverter
# 기본 제공 path converters : str, int, slug, uuid, path
register_converter(FourDigitYearConverter, 'yyyy')
urlpatterns = [
re_path(r"^(?P<pk>\d+)/$", views.post_detail),
path("<int:pk>/", views.post_detail),
re_path(r"^(?P<page_name>.+)/$", views.pages_detail),
path("<str:page_name>/", views.page_detail),
re_path(r"^articles/(?P<year>\d{4}/$", views_year_archive),
path("articles/<yyyy:year>/", views.year_archive),
]
커스텀 path converter인 yyyy는 4글자의 연도를 의미한다. FourDigitYearConverter 클래스를 정의해서 등록을 한 것이다. 위와 같이 re_path로 정규표현식으로 지정한 URL 매핑을 converter를 이용해서 path 함수로도 사용할 수 있다. 그럼 이제 직접 정의한 FourDigitYearConverter 클래스의 내부를 한 번 살펴보자.
# blog/converters.py
class FourDigitYearConverter:
regex = r'\d{4}'
def to_python(self, value):
return int(value)
def to_url(self, value):
return '%04d' % value
내부는 간단하다, regex 클래스 변수로 정규표현식 패턴을 정의한다, 그럼 요청 URL은 단순히 문자열이니 해당 뷰 함수가 호출되기 전에 패턴에 부합되는 문자열만 뽑아내는 걸 장고에선 캡처(Capture)라고 하는데 캡처에서 year_archive 뷰 함수를 호출하기 전에 to_python 함수 호출을 통해 변환된 값을 뷰 함수의 파라미터로 넘겨주게 된다. to_url은 반대이다, 앞서 언급한 URL Reserve 기능에서 다시 URL 문자열을 구성할 때 호출이 되게 된다.
명확한 URL
장고는 명확하게 URL을 가져가는 것을 선호한다. foo.com/bar와 뒤에 슬래쉬(/)가 붙은 foo.com/bar/ 주소는 서로 다른 URL이라는 것을 알아야 한다. 위의 두 URL은 검색엔진 봇과 웹트래픽 분석 툴에서는 별개의 페이지로 처리한다. 일반적인 장고 구현에서는 끝에 슬래쉬(/)가 붙은 주소를 정의한다. 슬래쉬(/)가 붙지 않은 주소로 요청이 들어오면 슬래쉬(/)가 붙은 주소로 페이지 이동을 시킨다. 그래서 검색엔진이 혼동하지 않도록 URL을 정규화하는 옵션이 기본 활성화되어 있다 그래서 장고 기본 settings.py 기본 설정에 보면 APPEND_SLASH 설정이 있다. 디폴트 값은 True이다.
from django.urls import path
urlpatterns = [
path("blog/", views.post_list),
]
위와 같은 설정에서 웹브라우저로 /blog 주소로 접근하면, 장고는 /blog/ 주소로 Redirect 응답(상태코드 : 301)을 한다.
'Framework > Django' 카테고리의 다른 글
[Django] 장고 템플릿(Template) (0) | 2024.01.02 |
---|---|
[Django] 장고 뷰(View) (0) | 2023.12.15 |
[Django] 장고에서의 요청 처리 (0) | 2023.12.14 |
[Django] 장고의 설계 철학 (1) | 2023.12.12 |
[Django] 장고 app 생성 (0) | 2023.11.01 |
소중한 공감 감사합니다