第二章:视图层 - 2:路由转发
一、路由转发
通常,我们会在每个app里,各自创建一个urls.py路由模块,然后从根路由出发,将app所属的url请求,全部转发到相应的urls.py模块中。
例如,下面是Django网站本身的URLconf节选。 它包含许多其它URLconf:
from django.urls import include, path
urlpatterns = [
# ... 省略...
path('community/', include('aggregator.urls')),
path('contact/', include('contact.urls')),
# ... 省略 ...
]
路由转发使用的是include()方法,需要提前导入,它的参数是转发目的地路径的字符串,路径以圆点分割。
每当Django 遇到include()
时,它会去掉URL中匹配的部分并将剩下的字符串发送给include的URLconf做进一步处理,也就是转发到二级路由去。
另外一种转发其它URL模式的方式是使用一个path()实例的列表。 例如,下面的URLconf:
from django.urls import include, path
from apps.main import views as main_views
from credit import views as credit_views
extra_patterns = [
path('reports/', credit_views.report),
path('reports/<int:id>/', credit_views.report),
path('charge/', credit_views.charge),
]
urlpatterns = [
path('', main_views.homepage),
path('help/', include('apps.help.urls')),
path('credit/', include(extra_patterns)),
]
在此示例中,/credit/reports/
URL将由credit_views.report()
视图处理。这种做法,相当于把二级路由模块内的代码写到根路由模块里一起了,不是很推荐。
再看下面的URLconf:
from django.urls import path
from . import views
urlpatterns = [
path('<page_slug>-<page_id>/history/', views.history),
path('<page_slug>-<page_id>/edit/', views.edit),
path('<page_slug>-<page_id>/discuss/', views.discuss),
path('<page_slug>-<page_id>/permissions/', views.permissions),
]
上面的路由写得不好,我们可以改进它,只需要声明共同的路径前缀一次,并将后面的部分分组转发:
from django.urls import include, path
from . import views
urlpatterns = [
path('<page_slug>-<page_id>/', include([
path('history/', views.history),
path('edit/', views.edit),
path('discuss/', views.discuss),
path('permissions/', views.permissions),
])),
]
这样就优雅多了,也清爽多了,但前提是你要理解这种做法。
二、捕获参数
目的地URLconf会收到来自父URLconf捕获的所有参数,看下面的例子:
# In settings/urls/main.py
from django.urls import include, path
urlpatterns = [
path('<username>/blog/', include('foo.urls.blog')),
]
# In foo/urls/blog.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.blog.index),
path('archive/', views.blog.archive),
]
在上面的例子中,捕获的"username"变量将被传递给include()指向的URLconf,再进一步传递给对应的视图。
三、向视图传递额外的参数
URLconfs具有一个钩子(hook),允许你传递一个Python字典作为额外的关键字参数给视图函数,像下面这样:
from django.urls import path
from . import views
urlpatterns = [
path('blog/<int:year>/', views.year_archive, {'foo': 'bar'}),
]
在上面的例子中,对于/blog/2005/
请求,Django将调用views.year_archive(request, year='2005', foo='bar')
。理论上,你可以在这个字典里传递任何你想要的传递的东西。但是要注意,URL模式捕获的命名关键字参数和在字典中传递的额外参数有可能具有相同的名称,这会发生冲突,要避免。
四、传递额外的参数给include()
类似上面,也可以传递额外的参数给include()。参数会传递给include指向的urlconf中的每一行。
例如,下面两种URLconf配置方式在功能上完全相同:
配置一:
# main.py
from django.urls import include, path
urlpatterns = [
path('blog/', include('inner'), {'blog_id': 3}),
]
# inner.py
from django.urls import path
from mysite import views
urlpatterns = [
path('archive/', views.archive),
path('about/', views.about),
]
配置二:
# main.py
from django.urls import include, path
from mysite import views
urlpatterns = [
path('blog/', include('inner')),
]
# inner.py
from django.urls import path
urlpatterns = [
path('archive/', views.archive, {'blog_id': 3}),
path('about/', views.about, {'blog_id': 3}),
]
注意,只有当你确定被include的URLconf中的每个视图都接收你传递给它们的额外的参数时才有意义,否则其中一个以上视图不接收该参数都将导致错误异常。