장고로 오는 요청들은 흔히 urls.py라는 모듈 내에서 URLConf를 통해 뷰로 라우팅 된다. 장고의 URL 디자인 철학에 따르면 뷰와 URL의 결합은 최대한의 유연성을 제공하기 위해 느슨하게 구성되어야 하며 이는 많은 경험이 요구되는 일이다.
장고는 urls.py 변경만으로 '각 뷰에 대한 URL' 이 변경되는 유연한 URL 시스템을 지니고 있다.
처리 함수나 처리용 스크립트 파일 이름이 들어간 하드 코딩된 URL은 사이트의 URL 패턴이 변경되면 사이트 전반에 걸친 모든 URL을 변경시켜줘야하는 번거로움이 생긴다.
그러나 Reverse 를 이용하면 URL이 변경되어도 변경된 URL을 알아서 추적하기 때문에, viws.py
로직과 분리된다. 즉, 모든 프로젝트 소스 코드를 검토하지 않아도 되는 것이다.
# urls.py
from news import views
path('archive', views.archive, name='news-archive')
# views.py
from django.urls import reverse
def myview(request):
return HttpResponseRedirect(reverse('news:news-archive'))
URL Reverse
도구
- 템플릿에서
url 템플릿 태그
사용하기- python 코드에서
reverse() 함수
사용하기- 모델 인스턴스의 URL 처리와 관련된 상위 수준 코드에서 ⚡
get_absolute_url() 메서드
사용하기.
1. reverse()
→ 문자열 return
→ 매칭 URL이 없으면 NoReverseMatch 예외 발생
from django.core.urlresolvers import reverse
reverse('blog:post_detail', args=[100]) # '/blog/100
reverse('blog:post_detail', kwargs={'pk':100}) # 'blog/100'
reverse('nonono') # NoReverseMatch
reverse()
가 동작하기 위해서는 프로젝트의 urlconf가 모두 로드되어야한다. 주로 FBV에서 사용.reverse_lazy()
는reverse
의 lazily evaluated version 이므로 프로젝트의 urlconf가 로드되기 전에 URL reverse을 사용해야 할 때 유용하다. 주로 CBV에서success_url
을 사용할 경우 사용.
2. resolve_url()
→ 문자열 return
→ 내부적으로 reverse 함수를 사용
→ 매칭 URL이 없으면 인자문자열을 그대로 return
from django.shortcuts import resolve_url
resolve_url('blog:post_detail', 100) # 'blog/100'
resolve_url('blog:post_detail', pk=`100) # 'blog/100'
resolve_url('/blog/100/') # 'blog/100'
resolve_url('/nonono/') # '/nonono/'
3. redirect()
→ HttpResponseRedirect return
→ 내부적으로 resolve_url 함수를 사용
→ 매칭 URL이 없으면 인자문자열을 그대로 return
redirect('blog:post_detail', 100) # 'blog/100'
redirect('blog:post_detail', pk=`100) # 'blog/100'
redirect('/nonono/') # '/nonono/'
4. url template tag
→ 문자열 return
→ 내부적으로 reverse 함수를 사용
{% url "blog:post_detail" 100 %}
{% url "blog:post_detail" pk=100 %}
⚡ 모델을 구현했다면 get_absoltue_url() 함수부터 구현하는 것이 좋다.
1. resolve_url
/ redirect
를 위한 모델 클래스 추가 구현
모델 클래스에 get_absolute_url()
함수를 구현해두면 resolve_url 과 redirect 에서 get_absoulte_url()
사용할 준비가 되어 있기 때문에 바로 리턴값으로 사용이 가능하다.
# models.py
from django.urls import reverse
class Post(models.Model):
#...중략
def get_absoulte_url(self):
return reverse('blog:post_detail', args=[self.pk])
2. get_absolute_url
활용
resolve_url 함수는 가장 먼저 get_absolute_url() 함수의 존재 여부를 체크하고, 존재할 경우 reverse를 수행하지 않고 그 리턴값을 즉시 리턴한다.
# django/shortcuts.py
def resolve_url(to, *args, **kwargs):
if hasattr(to, 'get_absoulte_url'):
return to.get_absoulte_url()
# 중략
try:
return(reverse(to, args=args, kwargs=kwargs)
except NoReverseMatch:
(1) CreateView / UpdateView
success_url
을 제공하지 않을 경우, 해당 model instance 의get_absolute_url
주소로 이동이 가능한지 체크하고, 이동이 가능할 경우 이동. Create / Update 하고나서 Detail화면으로 이동하는 것은 자연스러운 시나리오.
(2) 특정 모델에 대한 Detail뷰를 작성할 경우
Detail 뷰에 대한 URLConf설정을 하자마자, 필히get_absolute_url
설정하는 것은 좋은 방법.
URL namespace
는 앱 레벨 또는 인스턴스 레벨에서의 구분자를 제공한다. 쉽게 말해 각각의 앱이 관리하는 독립된 이름 공간이다.
겉으론 그다지 필요 없는 것처럼 보이지만 일단 이용하기 시작하면 매우 유용하다.
# icecreamapp/urls.py
from django.urls import path
app_name ='icecream' # app 수준의 URL namespace
urlpatterns += [
path('', include(icecream.urls, namespace='incecream'),
]
# snackapp/urls.py
from django.urls import path
app_name ='snack' # app 수준의 URL namespace
urlpatterns += [
path('', include(snack.urls, namespace='snack'),
]
# base.html
...
# <a href="{% url "icecream_detail" %} detail </a>
<a href="{% url "icecream:detail" %} detail </a>
<a href="{% url "snack:detail" %} detail </a>
icecream_detail
보다 icecream:detail
처럼 namespace
를 이용하는 쪽이 직관적이며 매핑을 더 명확하게 해준다.namespace
를 이용하면 이러한 문제를 피할 수 있다.➔ Avoid code : 종속적
from django.urls import path
from django.views.generic import DetailView
from tastings.models import Tasting
urlpatterns = [
path('<int:pk>',
DetailView.as_view(
model=Tasting,
template_name='tastings/detail.html'),
name='detail'),
path('<int:pk>/results/',
DetailView.as_view(
model=Tasting,
template_name='tastings/results.html'),
name='results'),
]
➔ Good code : 유연함
앞 서 이야기된 문제를 피하기 위해 views, url 별도의 파일로 구성한다.
두 개의 파일로 나뉘었고 코드는 늘어났지만 더 유연한 방법이다.
# testings/views.py
from django.urls import reverse
from django.views.generic import ListView, DetailView, UpdateView
from .models import Tasting
class TasteListView(ListView):
model = Tasting
class TasteDetailView(DetailView):
model = Tasting
class TasteResultsView(TasteDetailView):
template_name = 'tastings/results.html'
class TasteUpdateView(UpdateView):
model = Tasting
def get_success_url(self):
return reverse('tastings:detail', kwargs={'pk': self.object.pk})
# testings/urls.py
from django.urls import path
from . import views
urlpatterns = [
path(
route='',
view=views.TasteListView.as_view(),
name='list'
),
path(
route='<int:pk>/',
view=views.TasteDetailView.as_view(),
name='detail'
),
path(
route='<int:pk>/results/',
view=views.TasteResultsView.as_view(),
name='results'
),
path(
route='<int:pk>/update/',
view=views.TasteUpdateView.as_view(),
name='update'
)
]