date: 2021-11-24title: Django之URL路由系统 #标题
tags: #标签
categories: python # 分类
这篇文章用于详细学习下Django项目中的路由系统,也就是urls.py文件。
参考:官方文档
URL配置
URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表。你就是以这种方式告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码…
它的基本格式如下:
from django.conf.urls import url
'''
循环urlpatterns,找到对应的函数执行,匹配上一个路径就找到对应的函数执行,就不再往下循环了,
并给函数传一个参数request,和wsgiref的environ类似,就是请求信息的所有内容
'''
urlpatterns = [
url(正则表达式, views视图函数,参数,别名),
]
注意:Django 2.0版本中的路由系统已经替换成下面的写法,但是django2.0是向下兼容1.x版本的语法的(官方文档):
from django.urls import path
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<int:year>/', views.year_archive),
path('articles/<int:year>/<int:month>/', views.month_archive),
path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
]
参数说明
- 正则表达式:一个正则表达式字符串
- views视图函数:一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串
- 参数:可选的要传递给视图函数的默认参数(字典形式)
- 别名:一个可选的name参数
正则表达式详解
无名分组
# 如下是基本配置
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),
]
views.py中视图函数的写法:
# 第一个参数必须是request,后面跟的三个参数是对应着上面分组正则匹配的每个参数的
def article_detail(request,year,month,day):
return HttpResponse(year+month+day)
注意事项
- urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
- 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(分组匹配)。
- 不需要添加一个前导的反斜杠(也就是写在正则最前面的那个/),因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
- 每个正则表达式前面的’r’ 是可选的但是建议加上。
- ^articles& 以什么结尾,以什么开头,严格限制路径。
无名分组正则使用示例
1、urls.py文件内容如下:
from django.conf.urls import url
from django.contrib import admin
from apps import views # 将views导入进来
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/([0-9]{4})/$', views.year_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]
2、views.py文件内容如下:
from django.shortcuts import render, HttpResponse
import datetime
# Create your views here.
def special_case_2003(request):
data = '<h1>执行的是 special_case_2003 视图函数<h1/>'
return HttpResponse(data)
# 位置参数,第一个参数接收的就是无名分组路径中匹配 到的第一个分组的数据,第二个参数接收的就是无名分组路径中匹配到的第二个分组的数据
def year_archive(request, year):
data = '<h1>执行的是 year_archive 视图函数,访问的年份为:' + year + '<h1/>'
return HttpResponse(data)
def month_archive(request, year, month):
data = '<h1>执行的是 month_archive 视图函数,访问的年份为:' + year + ',月份为:' + month + '<h1/>'
return HttpResponse(data)
def article_detail(request, year, month, num):
data = '<h1>执行的是 month_archive 视图函数,访问的年份为:' + year + ',月份为:' + month + ', 访问的是第' + num + '篇文章<h1/>'
return HttpResponse(data)
访问http://127.0.0.1:8000/articles/2003/
,返回页面如下:
访问http://127.0.0.1:8000/articles/1999/
,返回页面如下:
访问http://127.0.0.1:8000/articles/1889/12/
,返回页面如下:
如上所示,返回的哪个内容,匹配的就是哪个函数。
补充说明
# 是否开启URL访问地址后面不为/跳转至带有/的路径的配置项
APPEND_SLASH=True
Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True。 其作用就是自动在网址结尾加’/‘。其效果就是:我们定义了urls.py:
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^blog/$', views.blog),
]
访问 http://www.example.com/blog 时,默认将网址自动转换为 http://www.example/com/blog/ 。
如果在settings.py中设置了 APPEND_SLASH=False,此时我们再请求 http://www.example.com/blog 时就会提示找不到页面。
这个没什么大用, APPEND_SLASH的值为True或False都行。
有名分组
其实有名分组和无名分组差不太多,如果了解过python中的re模块
的话,理解起来就简单多了。
urls.py文件中的内容改为了如下:
from django.conf.urls import url
from apps import views # 将views导入进来
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<num>[0-9]+)/$', views.article_detail),
]
可以看出来,和re模块的分组语法一致。
views.py文件中的视图函数,是完全可以和无名分组时的代码一样的,如下:
from django.shortcuts import render, HttpResponse
import datetime
# Create your views here.
def special_case_2003(request):
data = '<h1>执行的是 special_case_2003 视图函数<h1/>'
return HttpResponse(data)
def year_archive(request, year):
data = '<h1>执行的是 year_archive 视图函数,访问的年份为:' + year + '<h1/>'
return HttpResponse(data)
def month_archive(request, year, month):
data = '<h1>执行的是 month_archive 视图函数,访问的年份为:' + year + ',月份为:' + month + '<h1/>'
return HttpResponse(data)
def article_detail(request, year, month, num):
data = '<h1>执行的是 month_archive 视图函数,访问的年份为:' + year + ',月份为:' + month + ', 访问的是第' + num + '篇文章<h1/>'
return HttpResponse(data)
有名分组注意的点:
- views.py文件中的视图函数中,形参名称要和urls.py文件中分组命名的名字一样,否则无法正常接收参数;
- 视图函数必须接收所有分组命名匹配到的值,否则会报错500错误代码;
- 视图函数中的形参名称位置可以混乱,不用按照urls.py文件中匹配顺序来接收,只要名字可以对应上即可。
默认值
# urls.py中
from django.conf.urls import url
from django.contrib import admin
from apps import views # 将views导入进来
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]
# views.py中,可以为num指定默认值
from django.shortcuts import render, HttpResponse
# 设置num的默认值为1
def page(request, num=1):
return HttpResponse(num)
访问http://127.0.0.1:8000/blog/
,返回的是num设置的默认值:
访问http://127.0.0.1:8000/blog/page123/
,返回的是匹配到的数值:
url路由分发:include
在工作中,有些项目并不是只有一个应用,比如京东,可以通过在首页点击,跳转到不同的域名(品牌闪购、拍卖)等,这种域名都不同的,表示的就是不同的应用,那么一个项目是怎么做到引入多个应用呢?这就是下面要说的url路由分发:include。
首先自行创建一个项目(sec_pro)以及两个应用(app01和app02),目录结构如下:
然后分别在两个应用目录下新建urls.py文件,然后下面是所有目录下的urls.py文件的内容:
############### $PROJECT_HOME/$PROJECT_NAME/urls.py 文件内容
from django.conf.urls import url, include
from app01 import views # 之所以要导入app01的views文件,是因为项目首页写在app01.views中
'''
注意:Django匹配到正则中的内容后,再通过include去找其他urls.py文件时,会自动将本文件中匹配到的内容删除掉.
例如,用户访问的是:http://127.0.0.1:8000/app01/index/ 这个路径,
那么在这里匹配 url(r'^app01/', include('app01.urls')) 这个规则后,
Django会去加载app01/urls.py文件 ,然后在app01/urls.py文件中进行匹配时,
用户访问的路径就变成了:http://127.0.0.1:8000/index/
'''
urlpatterns = [
url(r'^app01/', include('app01.urls')), # 如果域名后面匹配到 app01开头的路径,就去加载app01/urls.py文件
url(r'^app02/', include('app02.urls')), # 如果域名后面匹配到 app02开头的路径,就去加载app02/urls.py文件
url(r'^$', views.base), # 如果域名后面为空,则表示访问的是项目首页,直接去找app01.views.py文件(因为在上面from app01 import views进行导入了)
]
############### app01/urls.py 文件内容
from django.conf.urls import url
from app01 import views
'''
由于用户在访问http://127.0.0.1:8000/app01/index/ 时,
项目目录下的urls.py文件已经匹配了然后转发到这里,
也就是说,这里需要匹配的url就是:http://127.0.0.1:8000/index/,
因为项目目录下的urls.py文件匹配后会自动删除匹配的内容
'''
urlpatterns = [
url(r'^index/', views.index),
url(r'^$', views.home_page)
]
############### app02/urls.py 文件内容
from django.conf.urls import url
from app02 import views
urlpatterns = [
url(r'^index', views.index),
url(r'^$', views.home_page)
]
上面是项目中所有urls.py文件的内容,那么下面来看看views.py文件的内容:
############### app01/views.py文件内容如下:
from django.shortcuts import render
# views这里只需要正常写处理请求的逻辑即可
# 访问 http://127.0.0.1:8000/app01/index/ 执行此函数
def index(request):
return render(request, 'app01.html')
# 访问 http://127.0.0.1:8000/app01/index/ 执行此函数
def home_page(request):
return render(request, 'app01_home.html')
# 访问 http://127.0.0.1:8000 执行此函数(在项目目录下的urls.py文件中定义的)
def base(request):
return render(request, 'home.html')
############### app02/views.py文件内容如下:
from django.shortcuts import render
def index(request):
return render(request, 'app02.html')
def home_page(request):
return render(request, 'app02_home.html')
接下来,进行访问测试(自行将相应的html文件放到 templates目录下):
1、访问 http://127.0.0.1:8000/
,页面内容如下:
2、访问 http://127.0.0.1:8000/app01/index/
,页面内容如下:
3、访问 http://127.0.0.1:8000/app01/
,页面内容如下:
4、访问 http://127.0.0.1:8000/app02/
,页面内容如下:
5、访问 http://127.0.0.1:8000/app02/index/
,页面内容如下:
命名URL(别名)和URL的反向解析
先来一段官方解释:
简单来说就是可以给我们的URL匹配规则起个名字,一个URL匹配模式起一个名字。这样我们以后就不需要写死URL代码了,只需要通过名字来调用当前的URL。当然,也仅限于代码项目内部通过别名调用url,客户端访问的话,还是需要访问实际的url。
就用我书籍管理系统的代码来说,如下:
定义别名
# urls.py文件内容
from django.urls import path
from Books_Manager import views
from django.urls import re_path # Django 2.0以后的版本需要通过re_path在路由系统中使用正则匹配
# 路由系统
urlpatterns = [
# name是给当前路由设置个别名,这样如果前面的匹配变了,
# 但代码内部照样可以通过原来的别名匹配到这个路由
path('index/', views.Show_and_add_books.as_view(), name='show_books'),
path('addbook/', views.addbook, name='add_book'),
re_path('editbook/(\d+)/', views.Edit_book.as_view(), name='edit_book'),
re_path('deletebook/(\d+)/', views.Delete_book.as_view(), name='delete_book'),
]
可以看到上面的代码中,给每一个路由都定义了一个name值,这就是别名。
现在来看看我在项目代码中是如何使用这些别名的,也就是“反向解析”。
视图中使用别名
# views.py文件内容(下面是部分代码)
def post(self, request):
book_name = request.POST.get('book_name')
book_price = request.POST.get('book_price')
book_press = request.POST.get('book_press')
book_date = request.POST.get('book_date')
book_obj = models.books.objects
book_obj.create(
book_name=book_name,
price=book_price,
press=book_press,
date=book_date
)
# 上面的代码忽略,这里是和反向解析有关的,reverse用于解析url的别名,
# 其实可以省略reverse,redirect的源码中已经尝试帮我们reverse了,后面就都不写reverse了哈
return redirect(reverse('show_books')) # 相当于重定向到 /index
# 上面是不带参数的方式使用别名,如果想要带参数的使用别名,可以通过如下语法:
print(reverse('delete_book',args=(71,))) # 相当于重定向到/deletebook/71/
模板中使用别名
{% url 'abook' %} # abook 是urls.py中定义的别名,这种是不带参数的写法
{% url 'delete_book' book.id %} # 带参数的方式,book.id 作为参数传给路由系统,表示访问的是 /deletebook/{book.id的实际值}/
上面提到的图书管理系统中,模板中就使用到了别名,如下: