路由的编写方式是Django2.0和1.11最大的区别所在。Django官方迫于压力和同行的影响,不得不将原来的正则匹配表达式,改为更加简单的path表达式,但依然通过re_path()方法保持对1.x版本的兼容。
URL是Web服务的入口,用户通过浏览器发送过来的任何请求,都是发送到一个指定的URL地址,然后被响应。
在Django项目中编写路由,就是向外暴露我们接收哪些URL的请求,除此之外的任何URL都不被处理,也没有返回。通俗地理解,不恰当的形容,URL路由是你的Web服务对外暴露的API。
ROOT_URLCONF
设置的值,但是如果传入的HttpRequest对象具有urlconf属性(由中间件设置),则其值将被用于代替ROOT_URLCONF
设置。通俗的讲,就是你可以自定义项目入口url是哪个文件!解释:URL配置(URLconf)就像Django所支撑网站的目录。它的本质是URL与要为该URL调用的视图之间的映射表。
Django版本1.x
from django.conf.urls import url
urlpatterns = [
url(正则表达式, views,kwargs,name),
]
#参数说明:
1.正则表达式:当 Django 响应一个请求时,它会从 urlpatterns 的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项,然后执行该条目映射的视图函数或下级路由,其后的条目将不再继续匹配。因此,url路由的编写顺序非常重要
2.views:当Django匹配到某个路由条目时,自动将封装的HttpRequest对象作为第一个参数,被“捕获”的参数以关键字参数的形式,传递给该条目指定的视图view。
3.kwargs:任意数量的关键字参数可以作为一个字典传递给目标视图。
4.name:对你的URL进行命名,让你能够在Django的任意处,尤其是模板内显式地引用它。这是一个非常强大的功能,相当于给URL取了个全局变量名,不会将url匹配地址写死。
#注意
请求的URL被看做是一个普通的Python字符串,URLconf在其上查找并匹配。进行匹配时将不包括GET或POST请求方式的参数以及域名。
例如,在https://www.example.com/myapp/的请求中,URLconf将查找myapp/。
在https://www.example.com/myapp/?page=3的请求中,URLconf也将查找myapp/。
Django版本2.x
from django.urls import path
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles//' , views.year_archive),
]
#Django 2.0版本中的路由系统已经替换成下面的写法,但是django2.0是向下兼容1.x版本的语法的
#位置传参
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003), #思考:如果用户想看2004、2005、2006....等,你要写一堆的url吗,是不是在articles后面写一个正则表达式/d{4}/就行啦,网址里面输入127.0.0.1:8000/articles/1999/试一下看看
url(r'^articles/([0-9]{4})/$', views.year_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), #思考,如果你想拿到用户输入的什么年份,并通过这个年份去数据库里面匹配对应年份的文章,你怎么办?怎么获取用户输入的年份啊,分组/(\d{4})/,一个小括号搞定
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]
视图函数view.py
#第一个参数必须是request,后面跟的三个参数是对应着上面分组正则匹配的每个参数的
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
def article_detail(request,year,month,day):
return HttpResponse(year+month+day)
#解释:从url匹配到的articles后面由3个参数,他们没一个都可以独立出来
注意
urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续,就直接break。
若要从URL中捕获一个值,只需要在它周围放置一对圆括号(分组匹配)。
不需要添加一个前导的反斜杠(也就是写在正则最前面的那个/),因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
每个正则表达式前面的’r’ 是可选的但是建议加上,表示这是个路径。
^articles& 以什么结尾,以什么开头,严格限制路径。
#是否开启URL访问地址后面不为/跳转至带有/的路径的配置项
APPEND_SLASH=True
解释:在Python的正则表达式中,分组命名正则表达式组的语法是**(?Ppattern)**,其中name是组的名称,pattern是要匹配的模式。
重写上面的URL
#关键字传参
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003), #注意正则匹配出来的内容是字符串,即便是你在url里面写的是2003数字,匹配出来之后也是字符串
url(r'^articles/(\d{4})/$', views.year_archive),#year_archive(request,n),小括号为分组,有分组,那么这个分组得到的用户输入的内容,就会作为对应函数的位置参数传进去,别忘了形参要写两个了,明白了吗?
url(r'^articles/(?P[0-9]{4})/$' , views.year_archive),#某年的,(?P[0-9]{4})这是命名参数(正则命名匹配还记得吗?),那么函数year_archive(request,year),形参名称必须是year这个名字。而且注意如果你这个正则后面没有写$符号,即便是输入了月份路径,也会被它拦截下拉,因为它的正则也能匹配上
url(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/$' , views.month_archive),#某年某月的
url(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/(?P[0-9]{2})/$' , views.article_detail), #某年某月某日的
]
视图函数view.py
#针对url /articles/2017/12/相当于按以下方式调用视图函数:
views.month_archive(request, year="2017", month="12"),#year和month的位置可以换,没所谓了,因为是按照名字来取数据的,还记得关键字参数吗?
每个在URLconf中捕获的参数都作为一个普通的Python字符串传递给视图,无论正则表达式使用的是什么匹配方式。例如,下面这行URLconf 中:
url(r'^articles/(?P[0-9]{4})/$' , views.year_archive),
传递到视图函数views.year_archive() 中的year
参数永远是一个字符串类型,而非是一个数字类型。
# urls.py中
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P[0-9]+)/$' , views.page),
]
# views.py中,可以为num指定默认值
def page(request, num="1"):
pass
在上面的例子中,两个URL模式指向相同的view - views.page - 但是第一个模式并没有从URL中捕获任何东西。
如果第一个模式匹配上了,page()函数将使用其默认参数num=“1”,如果第二个模式匹配,page()将使用正则表达式捕获到的num值。
include的背后是一种即插即用的思想。项目的根路由不会关心具体的app的路由策略,只管往指定的二级路由转发,实现了解耦。app所属的二级路由可以根据自己的需求随意编写,不会和其他的app路由发生冲突。
建议:除了admin路由外,尽量给每个app设计自己独立的二级路由。
from django.conf.urls import include,url
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^blog/', include('blog.urls')), # 可以包含其他的URLconfs文件
url(r'^app01/',include('app01.urls')), #别忘了要去app01这个应用下创建一个urls.py的文件,现在的意思是凡是以app01开头的路径请求,都让它去找app01下的urls文件中去找对应的视图函数,还要注意一点,此时这个文件里面的那个app01路径不能用$结尾,因为如果写了$,就没办法比配上app01/后面的路径了
]
app01的urls.py的内容:(其实就是将全局的urls.py里面的内容copy一下,放到你在app01文件夹下创建的那个urls.py文件中,把不是这个app01应用的url给删掉就行了)
from django.conf.urls import url
#from django.contrib import admin
from app01 import views
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^articles/2003/', views.special_case_2003,{
'foo':'xxxxx'}), #{'foo':'xxxxx'}那么你的视图函数里面必须有个形参叫做foo来接收这种传参
url(r'^articles/(\d{4})/(\d{2})/', views.year_archive),
]
人们强烈希望不要硬编码(其实就是在标签里面写死了路径,凡是写死了的代码就是硬编码)这些URL(费力、不可扩展且容易产生错误)或者设计一种与URLconf 毫不相关的专门的URL 生成机制,因为这样容易导致一定程度上产生过期的URL。
本质在view里面就是返回的一个路径,多用在redirect重定向,而且,这个参数还可以省略(哈哈哈…)。
#urls.py文件
url(r'^index/',views.index,name="index")
#view.py文件
from django.urls import reverse #导入这个反向解析的模块
def index(request):
if reverse("index"): #返回的肯定是True,其实它返回的就是一个路径,这个路径近视它里面写的index
return xxx #如果请求的url存在就返回数据
#html文件中
格式:{
% url "别名" %}
实例:<a href="{
{% url "index" %}}"></a>
urls.py文件
url(r'^home', views.home, name='home'), # 给我的url匹配模式起名(别名)为 home,别名不需要改,路径你就可以随便改了,别的地方使用这个路径,就用别名来搞
url(r'^index/(\d*)', views.index, name='index'), # 给我的url匹配模式起名为index
html文件
{
% url 'home' %} #模板渲染的时候,被django解析成了这个名字对应的那个url,这个过程叫做反向解析
<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>
<ul>
{
% for yearvar in year_list %}
<li><a href="{% url 'news-year-archive' yearvar %}">{
{
yearvar }} Archive</a></li>
{
% endfor %}
</ul>
view.py文件
from django.urls import reverse
from django.shortcuts import redirect
def redirect_to_year(request):
# ...
year = 2006
# ...
return redirect(reverse('news-year-archive', args=(year,))) #或者直接return redirect('news-year-archive',year) redirect内部会自动调用reverse来进行反向解析
如果出于某种原因决定按年归档文章发布的URL应该调整一下,那么你将只需要修改URLconf 中的内容。但是,在某些场景中,一个视图是通用的,所以在URL 和视图之间存在多对一的关系。对于这些情况,当反查URL 时,只有视图的名字还不够。
注意:
为了完成上面例子中的URL 反查,你将需要使用命名的URL 模式。URL 的名称使用的字符串可以包含任何你喜欢的字符。不只限制在合法的Python 名称。
当命名你的URL 模式时,请确保使用的名称不会与其它应用中名称冲突。如果你的URL 模式叫做comment
,而另外一个应用中也有一个同样的名称,当你在模板中使用这个名称的时候不能保证将插入哪个URL。
步骤流程
1.在每个app下创建urls.py文件,写上自己的app路径(就是总路由和分路由)
2.在项目目录在的urls.py文件中做一下路径分发:
from django.conf.urls import url,include
from django.contrib inport admin
urlpatterns = [
#url(r'^admin/',admin.site.urls),
url(r'^app01/',include('app01.urls')),
url(r'^app02/',include('app02.urls')),
]
实例
总路由:urls.py
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^app01/', include('app01.urls')),
url(r'^app02/', include('app02.urls')),
]
分路由:app01下urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^index/', views.index,name='index'),
]
分路由:app02下urls.py
from django.conf.urls import url
from django.contrib import admin
from app02 import views
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^index/', views.index,name='index'),
]
app01下的views.py的写法
from django.shortcuts import render,HttpResponse,redirect
from django.urls import reverse
# Create your views here.
def index(request):
print(reverse('index'))
return HttpResponse('ok')
app02下的views.py的写法
from django.shortcuts import render,HttpResponse,redirect
from django.urls import reverse
# Create your views here.
def index(request):
print(reverse('index'))
return HttpResponse('ok2')
你会发现,不管你是访问app01下的index还是app02下的index,打印的结果都是/app02/index/,也就是打印的是最后一个index别名对应的url路径。所以别名冲突了的话就需要我们的命名空间来保证别名对应的url的唯一性了。
那么怎么去解决上面的问题呢,就是下面讲的命名空间
步骤流程
第一种写法:
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^app01/', include('app01.urls',namespace='app01')),#app01/home/
url(r'^app02/', include('app02.urls',namespace='app02')),
]
使用:
视图中使用:reverse('命名空间名称:别名') -- reverse('app01:home')
html文件中使用:hmtl:{
% url '命名空间名称:别名' %} -- {
% url 'app01:home' %}
第二种写法:就是在每个app下的urls.py文件中指定app名称,同样也是命名空间.
总路由:urls.py
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
# url(r'^app01/', include('app01.urls',namespace='app01')),
url(r'^app01/', include('app01.urls')),
# url(r'^app02/', include('app02.urls',namespace='app02')),
url(r'^app02/', include('app02.urls')),
]
分路由:app01下urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
app_name = 'app01' #这个就是我们指定的命名空间
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^index/', views.index,name='index'),
]
分路由:app02下urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
app_name = 'app02'
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^index/', views.index,name='index'),
]
第三种写法:
总路由:urls.py
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
# url(r'^app01/', include('app01.urls',namespace='app01')),
url(r'^app01/', include(('app01.urls','app01'),namespace='app01')),
# url(r'^app02/', include('app02.urls',namespace='app02')),
url(r'^app02/', include(('app02.urls','app02'),namespace='app02')),
深入学习:https://docs.djangoproject.com/en/1.11/topics/http/urls/
当Django找不到与请求匹配的URL时,或者当抛出一个异常时,将调用一个错误处理视图。Django默认的自带的错误视图包括400、403、404和500,分别表示请求错误、拒绝服务、页面不存在和服务器错误。它们分别位于:
但是他们非常的丑陋,我们怎么自定义呢?
首先:我们在根URLconf中额外增加下面的内容
from django.contrib import admin
from django.urls import path,re_path
from app import views
urlpatterns = [
re_path('admin/', admin.site.urls),
]
# 增加的条目
handler400 = views.bad_request
handler403 = views.permission_denied
handler404 = views.page_not_found
handler500 = views.error
然后:在我们的视图文件中增加和上面对应的4个视图函数
def bad_request(request):
return render(request, '400.html')
def permission_denied(request):
return render(request, '403.html')
def page_not_found(request):
return render(request, '404.html')
def error(request):
return render(request, '500.html')
最后:我们在相应的.html文件中写我们自定义的内容就可以了
针对URL在模板文件中的命中为例,具体的url配置和视图的配置具体参考上面的
无名分组和有名分组(硬编码)
<li><a href="/polls/{
{ question.id }}/">{
{ question.question_text }}a>li>
命名URL和反向解析(软编码)
<li><a href="{% url 'detail' question.id %}">{
{ question.question_text }}a>li>
命名空间(解决多app的url命中问题)
<li><a href="{% url 'polls:detail' question.id %}">{
{
question.question_text }}</a></li>