1. 请求生命周期图
2. 项目准备
2.1 新建项目
2.2 路径拼接问题
* 修改templates路径拼接问题
2.3 路由层
在urls.py路由层中创建两个路由 test1, test11.
* 不要在路由后面加/
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'test1', views.test1),
url(r'test11', views.test11)
]
2.4 视图层
在app01 下 views.py中创建test1, test11 两个视图函数.
def test1(request):
return HttpResponse('test1')
def test11(request):
return HttpResponse('test11')
3.路由层介绍
3.1匹配路由
正则匹配:
所有的路由都写在一个列表中,for循环遍历列表,依次匹配url函数的第一个参数,
url函数第一个参数是正则表达式,只要第一个参数正则表达式能够匹配到内容,
那么就会立刻停止往下匹配,直接执行对应的视图函数.
0. 启动程序
1. 在浏览器中输入url地址: 127.0.0.1:8000/test1
2. 在浏览器中输入url地址: 127.0.0.1:8000/test11
3. 后端收到的请求是 test1 与 test11
4. 浏览器收到的返回信息 两次都是 test1.
匹配中: test11符合了test1匹配条件
3.2 重定向
重定向 --> 单斜杠/
* 重定向的方法解决前面的匹配模糊问题.
路由中为test1,test11加上/.
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'test1/', views.test1),
url(r'test11/', views.test11)
]
0. 启动程序
1. 在浏览器中输入url地址: 127.0.0.1:8000/test1
2. 在浏览器中输入url地址: 127.0.0.1:8000/test11
3. 后端收到的请求是 test1 与 test11
4. 浏览器收到的返回信息 test1 与 test11
* 输入的时候并没有在最后面写/,可以看到url中自动加上了一个.Django的功能.
APPEND_SLASH = False
7. 在浏览器中输入url地址: 127.0.0.1:8000/test1
8. 路由中匹配不到
9. 删除取消加斜杆的代码
10. 在浏览器中输入url地址: 127.0.0.1:8000/test1
这个时候Django在帮在test1后加一个/斜杆,做一个重定向.
再发送请求,第二次匹配到了,返回结果.
3.3限制开头
正则表达式符号 ^ 以什么开头.
0. 在浏览器中输入url地址: 127.0.0.1:8000/123test1
1. 正常访问test1.
* 123test1地址能被正则表达式'test1/'匹配到
2. 设置路由的正则匹配^test1/,加上 上尖号,表示以test1开头.
url(r'^test1/', views.test),
3. 在浏览器中输入url地址: 127.0.0.1:8000/123test1
4. 404 访问的资源不存在
5. 在浏览器中输入url地址: 127.0.0.1:8000/test1
3.4 限制结尾
正则表达式符号 $ 以什么结尾.
0. 在浏览器中输入url地址: 127.0.0.1:8000/test1/hello
1. 正常访问test1.
* test1/hello地址能被正则表达式'^test1/'匹配到
2.设置路由的正则匹配^test1/$,加$号,表示以test1结尾.(^$ 的组合以什么开头以什么结尾.)
3. 在浏览器中输入url地址: 127.0.0.1:8000/test1/
3.5 设置主页
为 127.0.0.127:8000 设置一个主页.
127.0.0.127:8000 后面没有地址了就是 空.
0. urls.py 添加路由关系.
1. views.py 中返回一个home页面(不演示html的页面了).
url(r'', views.home)
def home(request):
return HttpResponse('home主页')
2. 在浏览器中输入url地址: 127.0.0.1:8000
3. 在浏览器中输入url地址: 127.0.0.1:8000/123
4. 在浏览器中输入url地址: 127.0.0.1:8000/abc
* 如果是 '' 空字符串的话 其他不存路由都可以访问 (没法用正则去演示)
* ^$ 的组合以什么开头以什么结尾.
url(r'^$', views.home),
5. 在浏览器中输入url地址: 127.0.0.1:8000
6. 在浏览器中输入url地址: 127.0.0.1:8000/123
7. 在浏览器中输入url地址: 127.0.0.1:8000/abc
8. 只能是127.0.0.1:8000可以访问, 它的地址都无法访问了.
3.6 设置尾页
每次返回的资源不存在都显示404.
可以在后端判断,如果访问的资源不存在,返回一个好看的页面,而不是一堆报错代码.
也可以返回一个主页.
4.分组
正则表达式加上括号就是分组.
分组的作用: 匹配路由中符合要求的数据,作为参数,
传递给自己对应的视图函数,视图函数中使用形参接收.
/路径/(匹配子路径的正则表达式)/(匹配子路径的正则表达式)/
输入url的时候需要考虑完整的路径
4.1 无名分组
无名分组: 匹配的数据以位置参数进行传递.
0. 在路由中写正则表达式
1. 在浏览器中输入url地址: 127.0.0.1:8000/1234
2. 2. 报错,路由提供了一个位置参数需要接收.
url(r'test1/([0-9]{4})', views.test1),
3. 用一个形参接收值,并打印.
def test1(request, str_4):
print(str_4)
return HttpResponse('test1')
4. 在浏览器中输入url地址: 127.0.0.1:8000/1234
5. 输入url的时候需要考虑完整的路径
url缺失子路径就无法匹配.
4.2 有名分组
无名分组: 匹配的数据以关键字参数进行传递.
正则表达式分组后可以起一个别名.
(?P<别名>正则表达式) --> 大写的P
0. 在路由中写正则表达式
1. 在浏览器中输入url地址: 127.0.0.1:8000/1234
2. 报错,路由提供了一个关键字参数需要接收.
url(r'test1/(?p<res>[0-9]{4})', views.test1),
3. 用一个关键字接收值,并打印.
4.3 分组注意事项
1.一个类型的分组可以在一个路由中使用多个.
2.不同类型分组不能混用使用.(获取使用会丢失无名分组的值)
1.使用多次
url(r'test1/(\d+)/(\d+)/(\d+)', views.test1),
def test1(request, *args):
print(*args)
return HttpResponse('test1')
url(r'test1/(?P<int_1>\d+)/(?P<int_2>\d+)/(?P<int_3>\d+)', views.test1),
def test1(request, *args):
print(*args, kwargs)
return HttpResponse('test1')
2.混用
url(r'^test1/(\d+)/(?P<year>\d+)/', views.test1),
5. 反向解析
给根径起别名和对子路径分组,
后端可以通过别名取到根路径的名字,通过位置或关键字参数拿到子路径.
前端可以通过模板语法获取根路径的名字.
5.1 测试环境与需求
0.路由
1.视图函数
2.前端页面
url(r'^test1/', views.test1),
def test1(request):
return render(request, 'test1.html')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test1页面</title>
</head>
<body>
<h1>测试页面</h1>
<a href="/test1/">111</a>
</body>
</html>
需求: 可以随便的修改test1的名称.
5.2 起别名
url('路径', 视图函数, name='路径别名')
name参数为路径别名
url(r'^test1/', views.test1, name='hello'),
5.3 前端反向解析
前端页面通过模板语法获取路由路径的别名.
<a href="{% url 'hello' %}">test1测试页面</a>
<p>{% url 'hello' %}</p>
前面的路径怎么改名字,使用的都是同一个别名,后端一直获取的是它的别名,
在通过别名获取,真正的路径名字.
将test1改为:as、 dsa、 asas、 点a标签的时候,都会跳转到当前的路径.
5.4 后端反向解析
后端使用反向解析需要导入reverse模块.
from django.shortcuts import reverse
from django.shortcuts import reverse
def test1(request):
print(reverse('hello'))
return render(request, 'test1.html')
5.5 无名分组反向解析
0. 在路径后面添加一个无名分组. r'^test1/([0-9]{4})'
1. 视图函数接收位置参数
2. 浏览器输入 127.0.0.1:8000/test1/1234
url(r'^test1/([0-9]{4})', views.test1, name='hello'),
def test1(request, str_4):
print(str_4)
print(reverse('hello'))
return render(request, 'test1.html')
/根路径/子路径/../
name 为 只能为根路径起别名.
在反向解析的时候只能拿到根路径的信息,少了子路径的参数.就停止反向解析,报错.
需要提供一个符合要求的子路径正则表达式的参数,合成一个完整的路径.
才能拿到完整的路径信息.
* 分组是的值是直接能拿到的,反向解析后面的子路径可以直接拿分组的值.
reverse 函数有一个 args参数可以传值给后面的子路径.
args = (子路径, ...) 元组
在args写上子路径正则表达式能匹配的值.
def test1(request, str_4):
print(str_4)
print(reverse('hello', args=(1111, )))
return render(request, 'test1.html')
前端 反向解析也需要 一个符合子路径正则表达式的值,才能去解析.
直接写在 别名后面,只要能满足([0-9]{4})的值就行.
<a href="{% url 'hello' 1234 %}">test1测试页面</a>
<p>{% url 'hello' 1234 %}</p>
def test1(request, str_4):
print(str_4)
print(reverse('hello', args=(1111, )))
return render(request, 'test1.html', locals())
<h1>测试页面</h1>
<a href="{% url 'hello' str_4 %}">test1测试页面</a>
<p>{% url 'hello' str_4 %}</p>
5.6 有名分组反向解析
0. 在路径后面添加一个无名分组. r'^test1/(?P<str_4>[0-9]{4})'
1. 视图函数接收位置参数
2. 浏览器输入 127.0.0.1:8000/test1/1234
url(r'^test1/(?P<str_4>[0-9]{4})', views.test1, name='hello'),
def test1(request, str_4):
print(str_4)
print(reverse('hello'))
return render(request, 'test1.html', locals())
<h1>测试页面</h1>
<a href="{% url 'hello' str_4=1111 %}">test1测试页面</a>
<p>{% url 'hello' str_4 %}</p>
前端需要为有名分组提供一个可匹配的参数,现在后端传了子路径给前端,前端使用模板语法取到子路径.
在将子路径作为参数,取根路径.
视图函数中将locals() 删除.不提供子路径参数给前端,前端就需要自己提个一个符合条件的参数.
def test1(request, str_4):
print(str_4)
print(reverse('hello'))
return render(request, 'test1.html')
6. 路由分发
Django每一个应用都可以有自己的templates文件夹, url.py, static文件夹.
基于上述的特点,django能够非常好的做到分组开发(每个人开发自己的app应用).
单独开发完成后,把多个app应用全部拷贝到一个Django项目中.
然后去配置文件中注册应用,利用路由分发的特点所有的app整合起来.
当一个Django项目中url特别多的时候,总路由urls.py代码非常冗余不好维护,
利用路由分发来减轻总路由的压力.
路由层主要写路径与视图函数的对应关系.
路由分发:将总路由拆分多个子路由,
路由不再干路由与视图的直接对应关系,而是做一个分发处理,
识别当前路径是那个app下的,直接发给对应的app应用去处理.
* url 中要携带app应用的名字,不然区分不了.
6.1 创建多个app
* 测试就建一个就好了
0. python manage.py startapp app02
1. 创建好之后在settings中注册
2. 在app应用下创建 url 路由层.
6.2 子路由
* 前面的正则匹配不能 加$ 结尾符
设置后过不了正则匹配这关,后面的路由分发没机会执行.
app01应用下的路由层 视图层
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'reg/', views.reg)
]
from django.shortcuts import render, HttpResponse
def reg(request):
return HttpResponse('app01的注册页面')
app02 应用下的路由层 视图层
from django.conf.urls import url
from app02 import views
urlpatterns = [
url(r'reg/', views.reg)
]
from django.shortcuts import render, HttpResponse
def reg(request):
return HttpResponse('app02的注册页面')
6.3 总路由
0. 总路由分中导入一个include函数.
1. 导入app应用的路由模块&给子路由起别名
2. 总路由区分访问app应用,交给对应的子路由去处理.
url('^app01/', include(app01_urls)),
根路径 include(子路由)
用户输入的地址被正则匹配到了,再将用户输入的路径给include()函数传给子路由去继续匹配.
方式一: 导入子路由,在include函数中写对应的子路由.
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
urlpatterns = [
url('^app01/', include(app01_urls)),
url('^app02/', include(app02_urls)),
]
浏览器中输入:127.0.0.1/app01/reg
浏览器中输入:127.0.0.1/app02/reg
访问各自app的独自页面
方式二: 不导入子路由,在include()函数中只要.的方式去获取子路由.
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')),
]
浏览器中输入:127.0.0.1/app01/reg
浏览器中输入:127.0.0.1/app02/reg
访问各自app的独自页面
7.名称空间
7.1 别名问题
多个子路由使用同一个名称空间,在起别名的时候会有冲突.
urlpatterns = [
url(r'^reg/', views.reg, name='hello')
]
from django.shortcuts import HttpResponse
from django.shortcuts import reverse
def reg(request):
print('app1>>>:', reverse('hello'))
return HttpResponse('app01下的注册页面')
from django.shortcuts import HttpResponse
from django.shortcuts import reverse
def reg(request):
print('app2>>>:', reverse('hello'))
return HttpResponse('app02下的注册页面')
7.2 设置名称空间
在路由分发中设置namespace参数.设置名称空间.
0.总路由中使用 在include()函数中 为子路由声明一个名称空间.
urlpatterns = [
url('^app01/', include('app01.urls', namespace='app01')),
url('^app02/', include('app02.urls', namespace='app02')),
]
子路由中不需要更改.
7.3 后端反向解析
视图函数中反向解析的格式 reverse('名称空间的名字:别名')
from django.shortcuts import HttpResponse
from django.shortcuts import reverse
def reg(request):
print('app1>>>:', reverse('app01:hello'))
return HttpResponse('app02下的注册页面')
from django.shortcuts import HttpResponse
from django.shortcuts import reverse
def reg(request):
print('app2>>>:', reverse('app01:hello'))
return HttpResponse('app02下的注册页面')
7.4 前端反向解析
前端使用模板语法反向解析
<{% url ''名称空间的名字:别名' %}">
分别在app01/02下创建创建templates文件夹,
在分别创建app01/02_reg.html页面.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>aap02-reg</title>
</head>
<body>
<p>{% url 'app01:hello' %}</p>
<p>{% url 'app02:hello' %}</p>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>aap02-reg</title>
</head>
<body>
<p>{% url 'app01:hello' %}</p>
<p>{% url 'app02:hello' %}</p>
</body>
</html>
from django.shortcuts import render
from django.shortcuts import reverse
def reg(request):
print('app1>>>:', reverse('app01:hello'))
return render(request, 'reg.html')
from django.shortcuts import render
from django.shortcuts import render
from django.shortcuts import reverse
def reg(request):
print('app1>>>:', reverse('app01:hello'))
return render(request, 'reg.html')
分别返回自己reg页面.设置的时候名字重复一样,
第二个应用放回的也是第一个应用的页面.
返回的页面不要起一样的名字.
两个应用的 templates 下的文件改为:
reg.html --> app1_reg.html
reg.html --> app2_reg.html
视图中分别改为:
app1_reg.html
app2_reg.html
7.5 统一解决方式
一般情况下,有多个APP的时候在起别名的时候加上app的前缀,
这样的话就能保证多个app之间的名字不冲突.
8. 伪静态
静态网页:数据写死的了.伪静态网页:将动态网页伪装成静态网页.
* .html 后缀的页面被优先展示
伪装的目的增加本网站上seo查询力度,并且增加搜索引擎收藏本网站的概率.
搜索引擎本质就是一个巨大的爬虫程序.搜索引擎发现这个网站是一个静态页面,
会认为这个页面时不会修改了的,浏览器会优先收藏,在搜索是自己的文档会优先被浏览器展示,
如 百度 xxx
第一个就是你的信息
(刚不过人民币玩家-)
urlpatterns = [
url(r'^reg.html', views.reg, name='reg'),
]
路由中添加 .html 后,
访问也要加上 .html
9. 虚拟化环境
在程序开发中给每一个项目配置一个独有的解释器环境,环境内的模块缺什么安装什么.用不上的一概不装.
Pychrm虚拟环境:虚拟一个纯净的python解释器.
虚拟环境不要创建太多,需要消耗硬盘空间的拓展:
每一个项目都需要很多的模块,并且每一个模块版本还可能不一样,
开发当中会为项目配置一个requirements.txt文件里面书写了所有的模块和版本,
只需要输入一条命令就可以一键安装所有的模块和版本.
9.1 创建虚拟环境
创建需要一些时间,会生成一个venv的文件夹.
项目默认只有两个模块.
10.Django版本区别
Django版本 1.x2.x3.x
django1.x路由使用的是url方法而在django2.x和3.x中路由层使用的是path方法.
url()第一个参数支持正则表达式path()第一个参数不支持正则表达式,写什么就是什么.
如果不习惯使用path ,可以导入re_path模块,支持正则表达式.
from django.urls import re_path
re_path(r'^xxx',xxx), re_path等价于url.
虽然path不支持正则,
但它支持五种转换器以及自定转换器.path('index/<int:id>/')将第二个路由里面的内容转换为整型,
以关键字的形式传递给后面的视图函数.
模型层中1.x外键默认都是级联更新级联删除的,
但是到了2.x和3.x中需要自己手动写配置参数.
11. 视图层
视图层三板斧
HttpResponse: 返回字符串类型
render: 返回HTML页面,并在返回浏览器前还能给HTML文件传值
redirect: 重定向
研究三者源码可得结论:视图函数必须要返回一个HttpResponse.
三者都继承了同一个类.
定一个视图为直接写pass,运行后代码报错,未返回HttpResponse对象.
The view app01.views.index didn't return an HttpResponse object. It returned None instead.
12.JsonResponse
* json格式:前后端数据交互,需要使用json作为过渡,实现跨语言传输数据.
JsonResponse模块
0.将数据转为json格式字符串
1.调用HttpResponse返回json格式字符串
后端给前端浏览器返回一个json格式的字符串.
方式1:
使用json模块,将数据转为json格式字符串.
HttpResponse返回一个json格式的字符串.
方式2:
JsonResponse模块返回一个json格式的字符串.
12.1 手动序列化
def reg(request):
print('app1>>>:', reverse('app01:hello'))
import json
info = {'姓名': 'kid', '年龄': 18}
info_json = json.dumps(info)
return render(request, 'app01_reg.html', locals())
127.0.0.1:8000/app01/reg.html
{"\u59d3\u540d": "kid", "\u5e74\u9f84": 18}
12.2 中文序列化问题
json.dumps()序列化中文的时候会编码成ascii
设置 ensure_ascii=False 不转码.
info_json = json.dumps(info, ensure_ascii=False)
127.0.0.1:8000/app02/reg.html
{'姓名': 'kid', '年龄': 18}
12.3 自动序列化
from django.http import JsonResponse
def reg(request):
print('app1>>>:', reverse('app01:hello'))
info = {'姓名': 'kid', '年龄': 18}
return JsonResponse(info)
12.4 中文序列化问题
查看源码JsonResponse内部还是继承了HttpResponse类
,内部也是通过json.dumps() 转码的,修改参数json_dumps_params.
JsonResponse( 数据, json_dumps_params={'ensure_ascii': False})
return JsonResponse(info, json_dumps_params={'ensure_ascii': False})
14.2 传非字典数据
from django.http import JsonResponse
def reg(request):
user_list = ['kid', 18,]
return JsonResponse(user_list, json_dumps_params={'ensure_ascii': False})
In order to allow non-dict objects to be serialized set the safe parameter to False.
由于 EcmaScript 5 之前的安全漏洞,只允许传递 dict 对象
为了允许序列化非dict对象,请将safe参数设置为False.
设置safe参数值为False.
return JsonResponse(info, json_dumps_params={'ensure_ascii': False}, safe=False)
["kid", 18, "男"]
13. form表单上传文件
form表单上传文件类型的数据的需要设置的属性
1. method="post" 必须是post请求
2. enctype="multipart/form-data" 编码方式是表单数据
request.FILES 获取文件信息
文件对象:
obj = request.FILES.get('input name属性的值') 获取文件对象
obj.name 获取文件的名字
将对象写到本地.
with open('文件名', 'wb') as wf:
for i in 对象:
wf.write(i)
0. 在总路由写一个对应关系(直接对应视图函数不走子路由)
1. 在视图函数返回一个html页面
2. 在页面中一以POST方式提交文件
3. 在视图函数中获取文件
from app01 import views
urlpatterns = [
url('^file/', views.file)
]
def file(request):
print(f'请求方式:{request.method}')
if request.method == 'POST':
print(request.FILES)
img_obj = (request.FILES.get('file'))
print(img_obj, type(img_obj))
return render(request, 'file.html')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>form表单提交文件</title>
</head>
<body>
<div>
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name='file'>
<input type="submit">
</form>
</div>
</body>
</html>
4. 浏览器中输入 127.0.0.1:8000/file
5. 页面中选择文件提交
6. CSRF 中间件报错 先将其关闭
7. 注释掉settings中的47行
MIDDLEWARE = [
#'django.middleware.csrf.CsrfViewMiddleware'
]
8. 再次提交文件
9. 将文件保存下来
def file(request):
print(f'请求方式:{request.method}')
if request.method == 'POST':
print(request.FILES)
img_obj = (request.FILES.get('file'))
with open(img_obj.name, mode='wb') as rf:
for i in img_obj:
rf.write(i)
return render(request, 'file.html')
正常来说 with open()建的文件,没有指定路径的情况下,是在执行文件的目录下产生.
现在被移到项目目录下了.
修改 视图函数 当我们访问file 就创建文件
这个文件就出现在项目目录下.
def file(request):
import os
print(os.path.dirname(__file__))
with open('a.txt', mode='wt',encoding='utf8') as rf:
rf.write('123')
return render(request, 'file.html')
14. request补充
request.body 原生浏览器发送过来的二进制数据.
request.path 获取路由
request.path_info 获取路由
request.get_full_path 获取路由加路?后面的参数
* request.path 与 request.path_info 一样的
0. 修改前面的程序用于测试.
def file(request):
print(f'请求方式:{request.method}')
print(request.body)
return render(request, 'file.html')
def file(request):
print(request.path)
print(request.path_info)
print(request.get_full_path())
return HttpResponse('text')
1. 在浏览器中输入:127.0.0.1:8000/file
2. 在浏览器中输入:127.0.0.1:8000/file/123/456
3. 在浏览器中输入:127.0.0.1:8000/file/?name=123