天行健,君子以自强不息 —周易
文章目录
路由层
一、路由层之路由匹配
1.什么是路由
- 路由你可以看成就是除去IP和的PORT之后的地址
- 本质是URL与要为该URL调用的视图函数之间的映射表,如果把网站比喻为一本书,那路由就好比是这本书的目录。
- 在Django中路由默认配置在urls.py中
2.路由匹配
URL( )方法
- 第一个参数其实是一个正则表达式
- 匹配规则按照从上往下依次匹配,一旦第一个参数匹配到了内容直接结束匹配 执行对应的视图函数
基本格式:
# urls.py
from django.conf.urls import url
urlpatterns =[
url(regex(正则表达式),views(视图函数),kwargs=None,name=None),
]
参数介绍:
url()函数有四个参数, 两个是必须的: regex 和 view , 两个可选的: kwargs 和 name
regex:
-
regex 是 regular expression 的简写,这是字符串中的模式匹配的一种语法,Django 将请求的URL从上至下依次匹配列表中的正则表达式,直到匹配到一个为止。
-
请求url地址为:
https://www.example.com/myapp/
,正则表达式要匹配的部分是myapp/
-
请求url地址为:
https://www.example.com/myapp/?page=3,
正则表达式要匹配的部分是myapp/
-
view:
- 通常啊为一个视图函数,用来处理业务逻辑
kwargs
- 额外传递给视图函数的参数,可选
name
- 命名你的 URL, 使 url 在 Django 的其他地方使用,为正则表达式匹配到的url地址起别名,特别是在模板中,可选
示例:
urls.py文件
from django.contrib import admin
from django.urls import url
from app01 import views # 导入模块views.py
urlpatterns = [
url('^admin/', admin.site.urls),
url('^test/',views.test),
url('^testadd/$',views.test), # 匹配的是必须以 / 结尾,所以不会匹配成功testadd
url(r'^testaddr/$',views.testaddr), # 如果我们想匹配的路径就只是testaddr/,那么正则表达式应该有开始符与结束符, 如 ^testaddr/$,这样逻辑才算严谨
]
views.py文件
from django.shortcuts import render
from django.shortcuts import HttpResponse # 导入HttpResponse,用来生成响应信息
def test(request):
return HttpResponse('from test')
def testadd(request):
return HttpResponse('from testadd')
def testaddr(request):
return HttpResponse("from testaddr")
3.路由匹配注意事项
- 一定不要在路由前面接反斜杠
/
,因为每个url自带 - 在正则表达式之前需要添加原生字符
"r"
,是可选的但是建议加上 - 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(分组匹配)
- Django会拿着路径部分test/去路由表中自上而下匹配正则表达式,一旦匹配成功,通过分组捕获的参数都会作为一个普通的Python字符串传递给视图函数,不会继续往下匹配.
- 如果路由结尾没有
"/"
, 在第一次正则匹配机制没有匹配到想要的内容时, 会在匹配字符后加一个"/"
, 然后Django内部重定向在匹配一次
4.取消自动添加斜杠'/'
在上面的示例中,我们在浏览器中输入:http://127.0.0.1:8000/test,jango同样会拿着路径部分index去路由表中自上而下匹配正则表达式,貌似并不会匹配成功任何正则表达式( r’^test/'匹配的是必须以 / 结尾,所以不会匹配成功index),但实际上仍然会看到结果 index page…,原因如下:
-
在配置文件
settings.py
中有一个参数APPEND_SLASH,该参数有两个值True
或False
# setting.py 文件 APPEND_SLASH = False # 默认 True 自动添加斜杠 ''' 当APPEND_SLASH=True(如果配置文件中没有该配置,APPEND_SLASH的默认值为True),并且用户请求的url地址的路径部分不是以 / 结尾,例如请求的url地址是 http://127.0.0.1:8000/test,Django会拿着路径部分(即index)去路由表中匹配正则表达式,发现匹配不成功,那么Django会在路径后加 / (即test/)再去路由表中匹配,如果匹配失败则会返回路径未找到,如果匹配成功,则会返回重定向信息给浏览器,要求浏览器重新向 http://127.0.0.1:8000/test/地址发送请求。 当APPEND_SLASH=False时,则不会执行上述过程,即一旦url地址的路径部分匹配失败就立即返回路径未找到,不会做任何的附加操作 '''
总结一句话:
Django
默认这个参数为APPEND_SLASH = True
。 其作用就是自动在网址结尾加’/’。
设置为False之后如果不添加斜杠就无法访问资源。
二、分组命名匹配规则
什么是分组?为何要分组呢?什么是分组、为何要分组呢?比如我们开发了一个博客系统,当我们需要根据文章的id查看指定文章时,浏览器在发送请求时需要向后台传递参数(文章的id号),可以使用
http://127.0.0.1:8001/article/?id=3
,也可以直接将参数放到路径中http://127.0.0.1:8001/article/3/
针对后一种方式Django就需要直接从路径中取出参数,这就用到了正则表达式的分组功能了,分组分为两种:无名分组与有名分组
1.无名分组
-
url( )
方法中第一个参数正则表达式分组 : 给正则表达式前后加一个小括号 - 会将括号内正则表达式匹配到的内容当做位置参数传递给后边的视图函数
url(r'^test/\d+/$',views.test)
# 正则表达式分组:给正则表达式前后加一个小括号
# 下述正则表达式会匹配url地址的路径部分为:test/数字/,匹配成功的分组部分会以位置参数的形式传给视图函数,有几个分组就传几个位置参数
url(r'^test/(\d+)/$',views.test)
"""
无名分组
将括号内正则表达式匹配到的内容当做位置参数传递给后面的视图函数
"""
示例:
初级
urls.py文件
from django.contrib import admin
from django.urls import url
from app01 import views # 导入模块views.py
urlpatterns = [
url('^admin/', admin.site.urls),
url('^test/(\d+)/$', views.test),
url('^testadd/$',views.test), # 匹配的是必须以 / 结尾,所以不会匹配成功testadd
url(r'^testaddr/$',views.testaddr), # 如果我们想匹配的路径就只是testaddr/,那么正则表达式应该有开始符与结束符, 如 ^testaddr/$,这样逻辑才算严谨
]
views.py文件
from django.shortcuts import render
from django.shortcuts import HttpResponse # 导入HttpResponse,用来生成响应信息
def test(request):
return HttpResponse('from test')
def testadd(request):
return HttpResponse('from testadd')
def testaddr(request):
return HttpResponse("from testaddr")
运行项目报错test() takes 1 positional argument but 2 were given
终极版
其实只要随便在视图函数中加入一个形参即可。
# views.py文件
from django.shortcuts import render, HttpResponse
# Create your views here.
def test(request,egon):
print(egon)
return HttpResponse('from test')
def testadd(request):
return HttpResponse('from testadd')
def testaddr(request):
return HttpResponse("from testaddr")
2.有名分组
- 将正则表达式分组捕获到的内容定义一个名字,即起别名
- 会将括号内正则表达式匹配到的内容当做key=value关键字参数传递给后面的视图函数
url(r'^testadd4/(?P<id>\d+)/$',views.testadd4)
"""
有名分组
将括号内正则表达式匹配到的内容当做关键字参数传递给后面的视图函数
"""
示例:
初级版
urls.py文件
from django.contrib import admin
from django.urls import url
from app01 import views # 导入模块views.py
urlpatterns = [
url('^admin/', admin.site.urls),
url('^test/(\d+)/$', views.test),
url('^testadd/$',views.test), # 匹配的是必须以 / 结尾,所以不会匹配成功testadd
url(r'^testaddr/(?P<id>\d+)/$', views.testaddr),
]
views.py文件
from django.shortcuts import render
from django.shortcuts import HttpResponse # 导入HttpResponse,用来生成响应信息
def test(request):
return HttpResponse('from test')
def testadd(request):
return HttpResponse('from testadd')
def testaddr(request):
return HttpResponse("from testaddr")
运行报错:testaddr() got an unexpected keyword argument 'id'
终极版
同样也再视图函数传一个关键字参数进去
# views.py文件
from django.shortcuts import render, HttpResponse
# Create your views here.
def test(request, egon):
print(egon)
return HttpResponse('from test')
def testadd(request):
return HttpResponse('from testadd')
def testaddr(request, **kwargs):
print(kwargs)
return HttpResponse("from testaddr")
3.是否可以结合使用
3.1 无名有名分组不能混合使用
url(r'^test1/(\d+)/(?P<id>\d+)/$',views.test1)
# 官方说不能混着用, 混着用只能取到有名分组捕获的值
# 只要不混着用,有名分组和无名分组支持多个相同类型的传参
示例:
urls.py文件
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url('^admin/', admin.site.urls),
# 路由匹配
url('^test/(\d+)/$', views.test),
url(r'^testaddr/(?P<id>\d+)/$', views.testaddr),
url(r'^testaddr/(\d+)/(?P<id>\d+)/$', views.test1)
]
# views.py文件
from django.shortcuts import render, HttpResponse
# Create your views here.
from django.shortcuts import render, HttpResponse
# Create your views here.
def test(request, egon):
print(egon)
return HttpResponse('from test')
def testadd(request):
return HttpResponse('from testadd')
def testaddr(request, **kwargs):
print(kwargs)
return HttpResponse("from testaddr")
def test1(request,a,id):
print(a,id)
return HttpResponse('from test1')
启动项目报错:test1() missing 1 required positional argument: 'a'
3.2 可以单个重复使用
url(r'^test2/(\d+)/(\d+)/$',views.test2),
url(r'^test2/(?P<id>\d+)/(?P<id1>\d+)/$',views.test2)
示例:
# views.py 文件
from django.shortcuts import render, HttpResponse
def test(request, egon):
print(egon)
return HttpResponse('from test')
def testadd(request):
return HttpResponse('from testadd')
def testaddr(request, **kwargs):
print(kwargs)
return HttpResponse("from testaddr")
def test1(request, a, id):
print(a, id)
return HttpResponse('from test1')
def test2(request, *args, **kwargs):
print(args, kwargs)
return HttpResponse("from test2")
urls.py文件
运行项目是可以的。
三、反向解析
引入:当路由频繁变化的时候,html界面上的连接地址如何做到动态解析呢?于是就有了反向解析。
1.反向解析定义
如果把网址写死网址,会使得在改了网址(正则)后,模板(template),视图(views.py,比如用于URL跳转),模型(models.py,获取记录访问地址等)用了此网址的,都必须进行相应的更改,修改的代价很大,一不小心,有的地方没改过来,就不能用了。
所以需要根据名字动态获取到对应路径,这就是所谓的URL反向解析,反向URL匹配,反向URL查找或简单的URL反转。
**反向解析的本质:**就是获取到一个能够访问名字所对应的视图函数
2.反向解析的作用
- 当路由频繁的变化的时候,让html链接做到动态解析,减轻后期的维护成本
3.如何使用反向解析
- 给路由与视图函数对应关系添加一个别名(名字自己指定 只要不冲突即可)
##路由层
url(r'^index/',views.index,name='index_name')
- 根据该别名动态解析出一个结果,该结果可以直接访问到对应的路由
前端使用(模板层)
{% url '你给路由与视图函数对应关系起的别名' %}
<a href="{% url 'index_name' %}">111</a>
后端使用(视图层)
reverse('你给路由与视图函数对应关系起的别名')
from django.shortcuts import reverse
reverse('index_name')
ps:redirect括号内也可以直接写别名
应用范围:
- 模板中的超链接
- 视图中的重定向
示例:
# urls.py文件
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url('^admin/', admin.site.urls),
# 路由匹配
url(r'^index/',views.index,name="index_name"),
url(r'^home',views.home),
]
# views.py文件
from django.shortcuts import render, HttpResponse, redirect
def index(request):
return HttpResponse('from index')
def home(request):
return render(request, "home.html")
# home.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/index/">111</a>
<a href="/index/">222</a>
<a href="/index/">333</a>
</body>
</html>
这时是可以访问正常的。
随着功能的增加会出现更多的视图,可能之前配置的正则表达式不够准确,于是就要修改正则表达式。
但是正则表达式一旦修改了,之前所有对应的超链接都要修改,真是一件麻烦的事情,而且可能还会漏掉一些超链接忘记修改,有办法让链接根据正则表达式动态解析吗?如下图所示:
于是就有了动态解析:路由层可以动态解析模板层、视图层中的文件,最终解析出结果。
# urls.py文件
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url('^admin/', admin.site.urls),
# 路由匹配
url(r'^index/', views.index, name="index_name"),
url(r'^home', views.home),
]
# views.py文件
from django.shortcuts import render, HttpResponse, reverse,redirect
def index(request):
print(reverse('index_name'))
return redirect('index_name')
# return HttpResponse('from index')
def home(request):
return render(request, "home.html")
# home.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="{% url 'index_name' %}">111</a>
<a href="/index/">222</a>
<a href="/index/">333</a>
</body>
</html>
然后无论怎么改都不会受到影响。
但是如果出现了url分组的情况,又该如何解析呢?
4.无名分组反向解析
-
路由层配置
url(r'^index/(\d+)/',views.index,name='index_name') # 路由层中分组匹配得到的数字并不是我们这样写死的, 一般情况下放的是数据的主键值, 我们可以通过获取到数据的主键.进而定位到数据对象, 从而可以对数据进行编辑和删除
-
后端配置(视图层)
后端 reverse('index_name',args=(1,)) # 只要给个数字即可
-
前端配置(模板层)
前端 <a href="{% url 'index_name' 1 %}"></a> # 只要给个数字即可
示例:
#urls.py文件加入了分组
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url('^admin/', admin.site.urls),
# 路由匹配
url(r'^index/(\d+)', views.index, name="index_name"),
url(r'^home', views.home),
]
报错:for 'index_name' with no arguments not found. 1 pattern(s) tried: ['index/(\\d+)']
解决方法很简单,主需要给视图层一个参数即可。
HOME.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>你好啊</p>
</body>
</html>
#views.py文件
from django.shortcuts import render, HttpResponse, reverse, redirect
def home(request):
print(reverse('index_name',args=(1132,)))
return render(request, "home.html")
def index(request,*args,**kwargs):
return HttpResponse('from index')
然后又可以访问了。
5.有名分组反向解析
-
路由层配置
url(r'^index/(?P<id>\d+)/',views.index,name='index_name')
-
后端配置(视图层)
后端 reverse('index_name',kwargs={'id':123}) # 只要给个数字即可
-
前端配置(模板层)
前端 <a href="{% url 'index_name' id=666 %}"></a> # 只要给个数字即可
示例:
# url.py文件
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url('^admin/', admin.site.urls),
# 路由匹配
url(r'^index/(?P<id>\d+)', views.index, name="index_name"),
url(r'^home', views.home),
]
#views.py文件
from django.shortcuts import render, HttpResponse, reverse, redirect
def home(request):
print(reverse('index_name', args=(1,)))
print(reverse('index_name', kwargs={'id': 123}))
return render(request, "home.html")
def index(request, *args, **kwargs):
return HttpResponse('from index')
# home.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="{% url 'index_name' 1 %}">111</a>
<a href="{% url 'index_name' 2 %}">222</a>
<a href="{% url 'index_name' 3 %}">333</a>
<a href="{% url 'index_name' id=666 %}"></a>
</body>
</html>
6.总结
- 无名有名都可以使用一种(无名)反向解析的形式
四、路由分发
1.路由分发简介
- django是专注于开发应用的,当一个django项目特别庞大的时候,所有的路由与视图函数映射关系全部写在总的urls.py(总路由)很明显太冗余不便于管理
- 其实django中的每一个应用都可以有自己的urls.py,static文件夹,templates文件夹,基于上述特点,使用django做分组开发非常的简便
- 每个人只需要写自己的应用即可,最后由组长统一汇总到一个空的django项目中然后使用路由分发将多个应用关联到一起
- 利用路由分发之后, Django每一个app下面都可以有自己的urls.py路由层,templates文件夹,static文件夹。
- 项目名下 urls.py(总路由)不再做路由与视图函数的匹配关系而是做路由的分发。进而识别当前url所属的应用, 最后直接分发给对应的应用去处理就行了, 并且应用路由重名也无关要紧
2.总路由分发设置
2.1 准备工作:
-
新建app02,app03
python3 manage.py startapp app02 python3 manage.py startapp app03
-
然后在settings.py文件注册
#settings.py文件 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', 'app02', 'app03' ]
-
手动创建子路由
urls.py
文件和templates
2.2 总路由分发设置
- 总路由:项目下urls.py文件
from django.conf.urls import url, include
from django.contrib import admin
# 复杂版本
from app01 import urls as app01_urls
from app02 import urls as app02_urls
from app03 import urls as app03_urls
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^app01/', include(app01_urls)),
url(r'^app02/', include(app02_urls)),
url(r'^app03/', include(app03_urls)),
]
# 进阶版本
urlpatterns = [
url(r'^app01/', include('app01.urls')),
url(r'^app02/', include('app02.urls')),
url(r'^app03/', include('app03.urls')),
]
注意:
总路由正则后面不能添加
"$"
, 不然一配到app01
就结束了
-
每个应用路由(子路由)文件 : urls.py
# 在应用下新建urls.py文件,在该文件内写路由与视图函数的对应关系即可 from django.conf.urls import url from app01 import views urlpatterns = [ url(r'^index/',views.index) ]
五、名称空间
1.定义:
当一个项目创建了多个应用并设置了多个别名,当多个应用在反向解析的时候如果出现了别名冲突的情况,反向解析无法自动识别前缀
并且反向解析的时候前面路由会被后面的路由覆盖, 那么就无法触发前面路由对应的视图函数, 为了避免这种错误, 引入了名称空间
2.作用:
- URL命名空间可以保证反查到唯一的URL,即使不同的app使用相同的URL名称。
- 它还允许你在一个应用有多个实例部署的情况下反向解析URL。
- 换句话讲,因为一个应用的多个实例共享相同的命名URL,命名空间提供了一种区分这些命名URL 的方法。
3.应用命名空间与实例命名空间
- name_app : 应用命名空间,通常在应用app的urls.py文件中指定 (Django2版本以后不指定报错)
#app01
app_name ='app01' # 应用命名空间
urlpatterna = [
re_path(r'^'index',views.index,name='name_index')
]
- namespace : 实例命名空间 : 通常在总路由文件中指定
4.解决方式一:使用名称空间
- 总路由设置: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'^app02/', include('app02.urls', namespace='app02')),
url(r'^app03/', include('app03.urls', namespace='app03'))
]
- 子路由设置:urls.py文件
#app01
from app01 import views
from django.conf.urls import url
urlpatterns = [
url(r'^index', views.index, name="index_name"),
url(r'^login', views.login, )
]
#app02
from app02 import views
from django.conf.urls import url
urlpatterns = [
url(r'^index', views.index, name='index_name'),
url(r'^login', views.login, )
]
#app03
from app02 import views
from django.conf.urls import url
urlpatterns = [
url(r'^index', views.index, name='index_name'),
url(r'^login', views.login, )
]
- 视图文件:views.py文件
#app01
from django.shortcuts import render, HttpResponse, reverse
# Create your views here.
def index(request):
return HttpResponse("from app01 index")
def login(request):
reverse("app01:index_name")
return HttpResponse('from app01 login')
#app02
from django.shortcuts import render, HttpResponse, reverse
# Create your views here.
def index(request):
return HttpResponse("from app02 index")
def login(request):
reverse("app02:index_name")
return HttpResponse('from app02 login')
#app03
from django.shortcuts import render, HttpResponse, reverse
# Create your views here.
def index(request):
return HttpResponse("from app03 index")
def login(request):
reverse("app03:index_name")
return HttpResponse('from app03 login')
- 模板层文件
<a href="{% url 'app01:index_name' %}"></a>
<a href="{% url 'app02:index_name' %}"></a>
<a href="{% url 'app03:name_index' %}"></a>
5.解决方式二:别名不能冲突(手动加上自己应用名作为前缀)
url(r'^index/',views.index,name='app01_index_name')
url(r'^index/',views.index,name='app02_index_name')
url(r'^index/',views.index,name='app03_index_name')
六、伪静态
1.静态页面
- 静态页面:写好的页面,已经固定死了,并且将数据库及后端与前端划分开,两者间没有绝对的联系
2.动态页面
- 动态页面:页面内容可以随着时间、环境、或数据库的变化而发生改变
3.伪静态页面
-
将url地址模拟成html结尾的样子,看上去像是一个静态文件
-
目的是为了增加搜索引擎收藏我们网站的概率以及seo查询几率
-
比如吾爱破解论坛文章地址结尾就是
.html
, 但可以对文章内容进行修改的
ps:再怎么优化都不如RMB玩家!!!
七、本地虚拟环境
1.虚拟环境的作用
- 在时间开发过程中,我们会给不同的项目配备不同的环境
- 项目用到什么就装什么,用不到的一概不装
- 不同的项目解释器环境都不一样
2.requirements.txt 是什么
- 用于记录所有依赖包及其精确的版本号,以便新环境部署。
- 导出(打包)项目依赖环境(依赖包、环境包),以便后续项目迁移部署使用
生成requirements.txt
文件:
pip freeze > requirements.txt
安装requirements.txt
依赖:
pip install -r requirements.txt
requirement /rɪˈkwaɪərmənt/
n. 要求;必要条件;必需品
freeze /friːz/
v.(使)冻结,结冰
3.虚拟环境说明
- 创建虚拟环境类似于你重新下载了一个纯净的python解释器
- 如果反复创建类似于反复下载,会消耗一定的硬盘空间
- ps:我们目前不推荐你使用虚拟环境,所有的模块统一全部下载到本地
4.pycharm创建虚拟环境
- 打开pycharm----->File----->New Project----->Pure Python
- 创建成功查看初始的 package : 打开Pycharm----->File----->settings----->Project:[项目名]----->Python Interpreter
- 下次创建新项目可以直接选择已经创建好的虚拟环境
八、django版本区别
django1.X
与2.X
、3.X
1.urls.py
中的路由匹配方法区别
-
1.x
中使用的是url()方法,第一个参数是正则表达式 -
2.x
与3.x
中使用的是path()方法,第一个参数不支持正则表达式, 写什么就匹配什么-
如果想要在
2.x
和3.x
中的第一个参数中使用正则表达式, 则需要导入 re_path 方法from django.urls import path,re_path re_path 等价于 1.x 中的 url 方法 from django.conf.urls import url, include
-
2.五种常用转换器
str:匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int:匹配正整数,包含0。
slug:匹配字母、数字以及横杠、下划线组成的字符串。
uuid:匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path:匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
3.自定义转换器
class MonthConverter:
regex='\d{2}' # 属性名必须为regex
def to_python(self, value):
return int(value)
def to_url(self, value):
return value # 匹配的regex是两个数字,返回的结果也必须是两个数字
from django.urls import path,register_converter
from app01.path_converts import MonthConverter
# 注册转换器
register_converter(MonthConverter,'mon')
from app01 import views
urlpatterns = [
path('articles/<int:year>/<mon:month>/<slug:other>/', views.article_detail, name='date_time'),
]