# Django-墨落
# 认识django
Python下有许多款不同的 Web 框架。Django是重量级选手中最有代表性的一位。许多成功的网站和APP都基于Django。
Django 是一个开放源代码的 Web 应用框架,由 Python 写成。
Django 遵守 BSD 版权,初次发布于 2005 年 7 月, 并于 2008 年 9 月发布了第一个正式版本 1.0 。
Django 采用了 MVT 的软件设计模式,即模型(Model),视图(View)和模板(Template)。
# 快读安装
这里使用uv工具安装包并管理虚拟环境,如果不了解uv模块,请先查看uv介绍。
# 先使用uv初始化项目
uv init django-study
cd django-study
# 安装最新版django
uv add django
# 这里为了和老师保持一致,我们安装指定版本
uv add 'django==4.2.5'
# 升成一个具有基本目录的django项目,在python安装了django包以后,默认就提供了一个全局命令django-admin,可以让我们基于django-admin快速创建django项目
uv run django-admin startproject djdemo # djdemo 就是项目目录名,建议采用与项目相关的名称,最好英文!!!
# 运行django
uv run manage.py runserver
启动后的默认访问地址:http://127.0.0.1:8000/
可以在runserver 参数后配置修改django监听的端口和IP地址,当然,只能是127.0.0.1对应的其他地址.不能是任意IP.否则无法运行或访问!!
runserver 127.0.0.1:8000 # 只允许当前操作系统通过本地IP/域名访问
runserver 0.0.0.0:8088 # 允许其他的操作系统通过IP/域名访问
# 目录结构
djdemo/ # 项目根目录
│─ manage.py # 入口程序,终端脚本命令, 提供了一系列用于生成文件或者目录的命令,也叫脚手架
└─ djdemo/ # 主应用开发目录,保存了项目中的所有开发人员编写的代码, 目录是生成项目时指定的
│- asgi.py # django3.0以后新增的,用于让django运行在异步编程模式的一个web应用对象
│- settings.py # 默认开发配置文件,将来填写数据库账号,密码等相关配置
│- urls.py # 总路由文件,用于绑定django应用程序和url的映射关系
│- wsgi.py # wsgi就是项目运行在wsgi服务器时的入口文件, 本质上来说,manage.py runserver 内部调用的就是wsgi
└- __init__.py # 包初始化文件
# 快速使用
在django中要提供数据展示给用户,我们需要完成3个步骤.
1. 创建子应用
2. 在子应用的视图文件views.py 中编写视图函数
3. 把视图函数和url进行绑定注册到django项目.用户就可以通过url地址访问,用户访问的时候,django自动根据url地址执行对应的视图函数
# 1. 创建子应用
uv run manage.py startapp 子应用名称(目录)
uv run django-admin startapp 子应用名称
子应用的名称将来会作为目录名而存在,务必按变量名的命名规则来创建,不能出现特殊符号,也是不能出现中文等多字节的字符,更不能以数字开头!!!
我们这里创建一个home的子应用
# 2. 在子应用的视图文件中编写视图函数
home/views.py
代码
from django.http.response import HttpResponse
def index(request):
print("视图运行了")
return HttpResponse("hello world!")
# 3. 绑定路由和视图的映射关系
djdemo/urls.py
代码
from django.contrib import admin
from django.urls import path
from home.views import index
urlpatterns = [
path('admin/', admin.site.urls),
path("index/", index),
]
因为上面我们绑定index视图函数的url地址是index,所以我们可以通过http://127.0.0.1:8000/拼接url地址index来访问视图函数
# 路由入门
Route路由, 是一种映射关系!!!路由是把客户端请求的url地址和用户请求的应用程序[这里意指django里面的视图]进行一对一绑定映射的一种关系。当然在项目中,我们常常说的路由一般是一个类。 这个类完成了路由要做的事情。
# 视图基础
django的视图主要有2种写法的视图,分别是函数视图(Function Base View,FBV)和类视图(Class Base View,CBV)。
现在刚开始学习django,我们先学习函数视图(FBV),后面再学习类视图[CBV]。
# 函数视图
django中所有的视图都建议编写在子应用的views.py文件中。
from django.http.response import HttpResponse
def index(request):
# 代码
return HttpResponse("返回内容")
函数视图名称,同一个模块下不能重复,同时采用变量命名规则.
# 请求
视图中的request,实际上是django源码中的HTTPRequest的子类WSGIRequest类的实例对象,主要由django对客户端请求的http协议报文进行解析后得到的请求相关数据都在request对象中。
# 限制http请求
web项目运行在http协议下,默认肯定也支持用户通过不同的http请求方法发送数据到服务端。常用的http请求方法:
HTTP请求方法 | 描述 |
---|---|
POST | 添加/上传 |
GET | 获取/下载 |
PUT | 修改/更新,修改整体 |
PATCH | 修改/更新,修改部分 |
DELETE | 删除/废弃 |
django支持让客户端只能通过指定的Http请求来访问到项目的视图
home/views.py
代码
# 让用户发送POST才能访问的页面
from django.views.decorators.http import require_http_methods
@require_http_methods(["POST"]) # 注意,中括号中的请求方法名务必大写!!!否则无法正常显示
def login(request):
return HttpResponse("登录成功!")
路由绑定
demo/urls.py
代码
from django.contrib import admin
from django.urls import path
from home.views import index,index2
urlpatterns = [
path('admin/', admin.site.urls),
path("index", index),
path("login", login),
]
通过浏览器,访问效果http://127.0.0.1:8000/login
:
上面地址无法访问内容,就是因为我们在视图中设置了客户端只能通过POST请求访问当前视图,而用户默认通过浏览器进行访问地址,默认使用的是GET,我们可以使用postman发送post请求
# 路由分层
随着项目的开发,以后我们的视图函数肯定是越来越多的。
为了避免将来视图函数太多导致无法明确区分哪些路由属于哪一个子应用的。我们可以现在刚开始项目的时候,把路由代码放回到对应的各个子应用目录下,单独存放。这就是django提供的路由分层。
- 在子应用home下创建子路由文件,一般路由文件名建议是urls.py
- 把子应用home下面的视图绑定代码转到
home/urls.py
home/urls.py`代码
from django.urls import path
from home import views
urlpatterns = [
# path('路由尾缀', 视图函数导包路径),
path('index', views.index),
]
- 在总路由中djdemo/urls.py中通过include加载路由文件到django项目中
"""总路由"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# path('路由前缀/', include('子应用urls文件的导包路径')),
path('home/', include('home.urls')),
]
WARNING
当django项目中的路由分层以后,视图的访问地址就分成2段组合:总路由和子应用路由。那么用户访问视图,则访问url地址的规则:
http://127.0.0.1:8000/路由前缀+子应用路由的url地址
例如:
总路由中注册子应用路由时, 路由前缀为 "home"
子路由文件中有一个函数视图的url地址为: "index",
则用户访问时 正确的地址就是: http://127.0.0.1:8000/homeindex
当然,如果路由前缀是 "home/", 而子路由的url地址为 "index"
则用户访问时 正确的地址就是: http://127.0.0.1:8000/home/index
# http请求
在http请求和响应过程中, 用户往往会在请求过程中发送请求信息给服务端。那么客户端发送请求数据一般无非以下几种格式:
- 查询字符串[Query String]
- 所谓的查询字符串就是url地址上面?号后面的数据,例如:
- http://127.0.0.1:8000/home/index/?name=xiaoming&pwd=123
- 上面name=xiaoming&pwd=123 就是查询字符串
- 可以通过 request.GET 来获取。
- 注意: request.GET不是http请求,也就是说,只要地址上有查询字符串,不管使用什么样的http请求方法,都可以在视图中使用request.GET来获取参数
- 请求体数据[request.POST 接受表单 request.body 接受请求体]
- 请求头报文信息[request.headers]
- 上传文件[request.FILES]
# 获取查询字符串的参数
home/views.py
from django.http.response import HttpResponse
from django.views.decorators.http import require_http_methods
from django.http import QueryDict
# Create your views here.
def index(request):
print("index视图运行了")
# print(request.method)
# print(request.headers)
# print(request.body)
# print(request.path)
"""获取查询字符串"""
"""
请求地址:http://127.0.0.1:8000/home/index
"""
print(request.GET) # 获取地址栏上的所有的查询字符串,组成一个QueryDict查询字典对象
"""
打印效果:<QueryDict: {}>
QueryDict的声明位置: from django.http import QueryDict
QueryDict的父类MultiValueDict继承的就是dict字典,所以字典提供的方法或者操作, QueryDict都有
之所以使用QueryDict来保存请求参数的原因时:默认的字典的键是唯一的,所以会导致如果有多个值使用了同一个键,则字典会覆盖的。
而django内部封装的QueryDict允许多个值使用了同一个键,会自动收集所有的值保存在一个列表中作为当前键的值区寄存起来。
QueryDict常用的方法有2个:
get(键, 默认值) 通过指定键获取最后1个值
getlist(键, 默认值) 通过指定键获取所有值,并以列表格式返回
"""
"""
请求地址:http://127.0.0.1:8000/home/index?name=xiapming&pwd=123
"""
print(request.GET) # <QueryDict: {'name': ['xiaoming'], 'pwd': ['123']}>
print(request.GET.get("name"))
print(request.GET.get("pwd"))
# print(request.Get["pwd"]) # 减少使用中括号,会在没有键的情况下导致程序报错
"""
打印效果:
13312345678
xiapming
123
"""
"""
请求地址:http://127.0.0.1:8000/home/index/?name=xiaoming&mobile=13312345678&lve=swimming&lve=shopping&lve=game
"""
print(request.GET.get("lve")) # game
print(request.GET.getlist("lve")) # ['swimming', 'shopping', 'game']
print(request.GET.getlist("name")) # ['xiaoming']
return HttpResponse("<h1>index</h1>")
home/urls.py
代码
from django.urls import path
from home import views
urlpatterns = [
path('index', views.index),
]
# 获取请求体数据
在各种http请求方法中,POST/PUT/PATCH都是可以设置请求体的。request.POST中获取客户端通过POST发送过来的表单数据,无法获取PUT/PATCH的请求体。
from django.http.response import HttpResponse
from django.views.decorators.http import require_http_methods
from django.http import QueryDict
# Create your views here.
def index(request):
print("index视图运行了")
# print(request.method)
# print(request.headers)
# print(request.body)
# print(request.path)
"""获取查询字符串"""
"""
请求地址:http://127.0.0.1:8000/home/index
"""
print(request.GET) # 获取地址栏上的所有的查询字符串,组成一个QueryDict查询字典对象
"""
打印效果:<QueryDict: {}>
QueryDict的声明位置: from django.http import QueryDict
QueryDict的父类继承的就是dict字典,所以字典提供的方法或者操作, QueryDict都有
之所以使用QueryDict来保存请求参数的原因时:默认的字典的键是唯一的,所以会导致如果有多个值使用了同一个键,则字典会覆盖的。
而django内部封装的QueryDict允许多个值使用了同一个键,会自动收集所有的值保存在一个列表中作为当前键的值区寄存起来。
QueryDict常用的方法有2个:
get(键) 通过指定键获取最后1个值
getlist(键) 通过指定键获取所有值,并以列表格式返回
"""
"""
请求地址:http://127.0.0.1:8000/home/index?name=xiapming&mobile=13312345678
"""
print(request.GET.get("mobile"))
print(request.GET.get("name"))
print(request.GET.get("pwd", 123))
# print(request.Get["pwd"]) # 减少使用中括号,会在没有键的情况下导致程序报错
"""
打印效果:
13312345678
xiapming
123
"""
"""
请求地址:http://127.0.0.1:8000/home/index?name=xiaoming&mobile=13312345678&lve=swimming&lve=shopping&lve=game
"""
print(request.GET.get("lve")) # game
print(request.GET.getlist("lve")) # ['swimming', 'shopping', 'game']
print(request.GET.getlist("name")) # ['xiaoming']
return HttpResponse("<h1>index</h1>")
# @require_http_methods(["POST", "PUT"]) # 注意,中括号中的请求方法名务必大写!!!否则无法正常显示
def index2(request):
"""获取请求体数据"""
"""
访问地址:http://127.0.0.1:8000/home/index2
请求体:不设置请求体
"""
# print(request.POST)
"""
request.POST获取的结果也是QueryDict查询字典对象
<QueryDict: {}>
"""
"""
访问地址:http://127.0.0.1:8000/home/index2
请求体:name=xiaoming&age=16
"""
# print(request.POST)
"""
打印效果:
<QueryDict: {'name': ['xiaoming'], 'age': ['16']}>
"""
# print(request.POST.get("name"))
"""
访问地址:http://127.0.0.1:8000/home/index2
请求体:name=xiaoming&age=16&citys=["北京", "上海", "天津]
"""
"""
打印效果:
<QueryDict: {'name': ['xiaoming'], 'age': ['16'], 'citys': ['北京', '上海', '天津']}>
"""
#
# print(request.POST) # ['北京', '上海', '天津']
# print(request.POST.getlist("citys"))
# print(request.POST.get("citys")) # 天津
"""接收原生请求体中的json数据"""
"""
请求地址:http://127.0.0.1:8000/home/index2
请求体为json:'{"name": "xiaobai","age": 16}'
"""
# print(request.POST) # <QueryDict: {}>
# print(request.body) # b'{\n "name": "xiaobai",\n "age": 16\n}'
# import json
# print(json.loads(request.body)) # {'name': 'xiaobai', 'age': 16}
return HttpResponse("index2!")
home/urls.py
代码
from django.urls import path
from home import views
urlpatterns = [
path('index', views.index),
path('index2', views.index2),
]
# 获取请求头数据
def index3(request):
"""接收请求体参数"""
print(request.META) # 获取当前项目相关的服务器与客户端环境信息,也包含了请求头信息,以及服务端所在的系统的环境变量
"""
{
'LANG': 'zh_CN.UTF-8', # 服务端系统的默认语言
'USER': 'moluo', # 服务端运行的系统用户名
'HOME': '/home/moluo', # 服务端运行的系统用户家目录路径
'DJANGO_SETTINGS_MODULE': 'djdemo.settings', # 只有在django下才有的,当前django框架运行时加载的配置文件导包路径
'SERVER_NAME': 'ubuntu', # 服务端系统名称
'SERVER_PORT': '8000', # 服务端的运行端口
'REMOTE_HOST': '', # 客户端的所在IP地址,有时候可能是域名
'SCRIPT_NAME': '', # 客户端本次请求时,服务端执行的程序所在路径
'SERVER_PROTOCOL': 'HTTP/1.1', # 服务端运行的协议
'SERVER_SOFTWARE': 'WSGIServer/0.2', # 服务端运行web服务器的软件打印信息
'REQUEST_METHOD': 'POST', # 客户端本次请求时的http请求方法
'PATH_INFO': '/home/index3/', # 客户端本次请求时的url路径
'QUERY_STRING': '', # 客户端本次请求时的查询字符串
'REMOTE_ADDR': '127.0.0.1', # 客户端的所在IP地址
'CONTENT_TYPE': 'application/json', # 客户端本次请求时的数据MIME格式
'HTTP_USER_AGENT': 'PostmanRuntime/7.26.10', # 客户端本次请求时,所使用的网络代理软件提示信息
'HTTP_ACCEPT': '*/*', # 客户端期望服务端返回的数据MIME格式格式
'HTTP_HOST': '127.0.0.1:8000', # 客户端本次请求时,所使用服务端地址
'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', # 客户端期望服务端返回的数据的压缩格式
'HTTP_CONNECTION': 'keep-alive', # 客户端支持的服务端协议的链接类型,keep-alive 表示客户端支持http的长连接
}
"""
print(request.headers) # 获取HTTP请求头
"""
{
'Content-Length': '601', // 客户端本次请求的内容大小
'Content-Type': 'multipart/form-data;', # 客户端本次请求的内容MIME类型
'User-Agent': 'PostmanRuntime/7.26.10', # 客户端本次请求的代理软件打印信息
'Accept': '*/*',
'Host': '127.0.0.1:8000', # 客户端本次请求的服务端地址
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
# 以下就是自定义请求头了
'Company': 'baidu',
'Num': '1000',
}
"""
print("Content-Type=", request.META.get("CONTENT_TYPE"))
print("自定义请求头,Num=", request.META.get("HTTP_NUM"))
print("自定义请求头,Company=", request.META.get("HTTP_COMPANY"))
print("Content-Type=", request.headers.get("Content-Type"))
print("自定义请求头,Num=", request.headers.get("Num"))
print("自定义请求头,Company=", request.headers.get("Company"))
return HttpResponse("接收请求体")
TIP
常见请求头:
SERVER_NAME, 服务端系统名称
SERVER_PORT, 服务端的运行端口
REMOTE_ADDR,客户端的所在IP地址
SERVER_SOFTWARE,服务端运行web服务器的软件打印信息
PATH_INFO,客户端本次请求时的url路径
home/urls.py
代码
from django.urls import path
from home import views
urlpatterns = [
path('index', views.index),
path('index2', views.index2),
path('index3', views.index3),
]
# 获取上传文件
home/views.py
代码
def index4(request):
"""接收上传文件"""
# print(request.FILES)
"""
POST http://127.0.0.1:8000/home/index4
打印效果:
<MultiValueDict: {'avatar': [<InMemoryUploadedFile: 1.jpg (image/jpeg)>]}>
"""
# print(request.FILES.get("avatar")) # 获取本次客户端上传的指定name值对应的一个文件上传处理对象
# print(request.FILES.getlist("avatar")) # 获取本次客户端上传的指定name值对应的多个文件上传处理对象
"""
django在解析http协议的时候,针对上传文件,会自动实例化一个内存保存文件的文件上传处理对象InMemoryUploadedFile
from django.core.files.uploadedfile import InMemoryUploadedFile
"""
# read() 从文件上传处理对象读取文件的内容(bytes格式内容)
import os
# # 处理一个上传文件[不仅是图片,任何内容都可以这样处理]
# file = request.FILES.get('avatar')
# with open(f"{os.path.dirname(__file__)}/{file.name}", "wb") as f:
# f.write(file.read())
# 处理多个一次性上传文件
for file in request.FILES.getlist("avatar"):
with open(f"{os.path.dirname(__file__)}/{file.name}", "wb") as f:
f.write(file.read())
return HttpResponse("接收客户端的上传文件")
home/urls.py
代码
from django.urls import path
from home import views
urlpatterns = [
path('index', views.index),
path('index2', views.index2),
path('index3', views.index3),
path('index4', views.index4),
]
# 响应
django和大多数的web框架一样,针对http的响应,提供了2种不同的响应方式:
- 响应内容,就是直接返回数据给客户端
- 响应html内容【一般用于web前后端不分离的项目】
- 响应json内容【一般用于开发web前后端分离的项目的api接口开发】
- 响应页面跳转,就是通过返回页面跳转的信息给浏览器,让浏览器自己进行页面跳转
# 返回htlm数据
home/views.py
代码
def index5(request):
"""响应对象"""
"""
return HttpResponse(content="正文内容",content_type="内容格式",status="http响应状态码")
content 响应内容
content_type 内容格式,默认是 text/html
status 响应状态码,默认是 200
headers 响应头,字典格式
"""
"""返回html内容"""
return HttpResponse("<h1>你好,django</h1>")
home/urls.py
代码
from django.urls import path
from home import views
urlpatterns = [
path('index', views.index),
path('index2', views.index2),
path('index3', views.index3),
path('index4', views.index4),
path('index5', views.index5),
]
# 返回Json数据
home/views.py
代码
def index6(request):
"""响应对象:响应json数据"""
# 返回字典数据作为json给客户端
"""
import json
data = {"name":"xiaoming", "age":16, "sex": True}
return HttpResponse(json.dumps(data), content_type="application/json;charset=utf-8")
"""
# 原生返回json数据,太麻烦了
# 因此django提供了一个HttpResponse的子类JsonResponse,转换提供给我们返回json数据的
# from django.http.response import JsonResponse
# data = {"name": "xiaoming", "age": 16, "sex": True}
# return JsonResponse(data)
# JsonResponse返回的数据如果不是字典,则必须要加上safe参数声明,并且值为False
# 返回列表数据给客户端
from django.http.response import JsonResponse
data = [
{"id":1, "name": "小明", "age": 16},
{"id":3, "name": "小火", "age": 15},
]
return JsonResponse(data, safe=False)
# return JsonResponse(data, safe=False, json_dumps_params={"ensure_ascii": False}) # 不推荐使用
honme/urls.py
代码
from django.urls import path
from home import views
urlpatterns = [
path('index', views.index),
path('index2', views.index2),
path('index3', views.index3),
path('index4', views.index4),
path('index5', views.index5),
path('index6', views.index6),
]
# 返回图片格式信息
例如:图片,压缩包,视频,或js脚本,xls,docs,pdf,ppt
def index7(request):
"""返回图片格式"""
import os
with open(f"{os.path.dirname(__file__)}/avatar.jpg", "rb") as f:
content = f.read()
return HttpResponse(content, content_type="image/jpeg")
def index8(request):
"""返回压缩包格式"""
import os
with open(f"{os.path.dirname(__file__)}/code.zip", "rb") as f:
content = f.read()
return HttpResponse(content, content_type="application/zip")
# 自定义响应头
def index9(request):
"""返回数据的过程中设置响应头"""
response = HttpResponse("ok")
# 自定义响应头[值和属性都不能是多字节]
response["company"] = "baidu"
return response
home/urls.py
代码
from django.urls import path
from home import views
urlpatterns = [
# 中间代码省略.....
path('index6', views.index6),
path('index7', views.index7),
path('index8', views.index8),
path('index9', views.index9),
]
# 页面跳转
页面跳转也有2种方式:站外跳转与站内跳转。
# 站外跳转
home/views.py
代码
def index10(request):
"""跳转到站外"""
# 1. 基于django提供的Response对象也可以进行页面跳转
# from django.http.response import HttpResponse
# response = HttpResponse(status=301)
# response["Location"] = "https://www.tmall.com"
# return response
# # 2. 基于django提供的Response对象的原生写法[HttpResponseRedirect与HttpResponsePermanentRedirect都是HttpResponse的子类]
# from django.http.response import HttpResponseRedirect # 临时重定向
# # from django.http.response import HttpResponsePermanentRedirect # 永久重定向
# return HttpResponseRedirect("https://www.qq.com")
# 2. 基于django提供快捷函数(简写函数, shortcuts)来完成[常用]
from django.shortcuts import redirect
return redirect("http://www.baidu.com")
home/urls.py
代码
from django.urls import path
from home import views
urlpatterns = [
path('index', views.index),
path('index2', views.index2),
path('index3', views.index3),
path('index4', views.index4),
path('index5', views.index5),
path('index6', views.index6),
path('index7', views.index7),
path('index8', views.index8),
path('index9', views.index9),
path('index10', views.index10),
]
# 站内跳转
在站内跳转时,如果使用django.urls.reverse函数进行路由反转解析(可以根据路由的别名反向生成路由的URL地址),则必须在总路由文件和子路由文件中,对路由的前缀和子路由后缀进行别名绑定,步骤如下:
djdemo/urls.py
总路由,代码
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('home/', include('home.urls', namespace="home")), # namespace 是include函数的参数,namespace的值就是提供给reverse使用的
]
home/urls.py
子路由,代码:
from django.urls import path
from home import views
# 使用路由反向解析,reverse时必须在当前路由文件中设置app_name为当前子应用的包名
app_name = "home"
urlpatterns = [
path('index', views.index),
path('index2', views.index2),
path('index3', views.index3),
path('index4', views.index4),
path('index5', views.index5),
path('index6', views.index6),
path('index7', views.index7),
path('index8', views.index8),
path('index9', views.index9),
path('index10', views.index10),
path('index11', views.index11, name="in11"),
path('index12', views.index12, name="in12"),
]
home/views.py
代码:
def index11(request):
"""跳转到站内"""
from django.shortcuts import redirect # 根据指定的url地址,进行页面跳转
# # 直接基于redirect跳转
# return redirect("/home/index12")
# # 基于reverse+redirect对路由别名进行反向解析进行跳转
from django.urls import reverse # 根据路由别名,反向解析生成url地址
url = reverse("index:in12")
print(url)
return redirect(url)
def index12(request):
return HttpResponse("ok, index12")
# 会话控制
在web开发领域,网络中网页之间进行内容传输使用的协议是http/https协议。
http协议是一种无状态, 有所请求必有所回应的超文本传输协议,HTTP为了提升效率,一经完成请求和响应的过程以后,就会自动关闭连接。所谓无状态意指: 基于http协议提供服务的服务端,无法识别前后多次请求过程中,是不是同一个客户端发送的还是多个客户端发送的。
web开发的应用往往不仅仅只是一个网页或一个功能,那么怎么多个页面不同的请求,识别用户之前的状态和行为?
为了能在多次请求过程中,识别客户端是否是同一个客户端,所以就出现了会话跟踪技术,就需要使用会话控制技术, 也叫会话保持或者会话跟踪技术.
会话控制技术,主要作用是为了识别和记录用户在web应用中的身份行为和操作历史。
# 会话
会话的单词:session,所谓一次会话其实就是客户端和服务端之间进行通信的一次完整过程。
在web项目中:
- 客户端和服务端之间产生的会话开始于:在用户第一次通过url访问网站的时候
- 客户端和服务端之间的会话结束于:关闭浏览器
注意: 如果在移动端下面,则必须是关闭当前app应用才算结束会话,如果只是后台运行,会话并没有结束的.
所以会话跟踪技术就是在一次完整的会话中,能让服务端识别客户端在整个过程中的身份行为和操作历史的一项技术.
实现会话控制的几种技术类型:
1. url地址栏记录用户身份的参数[少见,很古老的技术了,例如:QQ邮箱,QQ空间]
2. cookie: 在浏览器中由浏览器自动读写保存用户信息的一种小文件,能够存储的数据有限,30M左右,[过时了,谷歌在2021开始慢慢关闭这个cookie技术了]
3. session: 在服务端中保存用户信息的文件存储信息技术,能够存储的数据视存储设备而定,根据服务端配置而定。session默认是基于cookie的
4. token令牌: 就是一段可以记录和识别用户身份的字符串,通过客户端语言[js/安卓/ios]保存在客户端中一项技术,替代cookie或session.
jwt就是token技术的其中一种。Oauth2.0也是属于token令牌技术的一种。
# cookie(了解)
cookie是保存在客户端浏览器中的小文本,由浏览器自动管理和收发, 所以cookie中不要保存用户的敏感信息,例如: 密码,身份证,手机号等等之类的。而且cookie在浏览器中用户是可以手动关闭或禁止cookie功能的.如果关闭了cookie功能,一般服务端能做的就只有2个事情了:1. 提示用户开启cookie功能, 2. 不要使用cookie进行会话控制了。
# cookie实现会话控制的原理
接下来,我们创建一个mycookie的子应用来完成学习。
uv run manage.py startapp mycookie
mycookie/urls.py
代码
# mycookie子应用的子路由文件
from django.urls import path
from . import views
app_name = "mycookie"
urlpatterns = [
path("set", views.set_cookie),
path("get", views.get_cookie),
path("del", views.del_cookie),
]
总路由,djdemo/urls.py
,代码:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('home/', include('home.urls', namespace="index")), # namespace 是include函数的参数,namespace的值就是提供给reverse使用的
path('cookie/', include("mycookie.urls")), # cookie功能学习
]
# 保存cookie
在服务端生成cookie,发送到客户端浏览器保存,视图代码:
from django.http.response import HttpResponse
def set_cookie(request):
"""设置/保存/更新Cookie"""
response = HttpResponse()
# 生成cookie
"""
参数列表:
key, # 键/变量
value='', # 值/内容
max_age=None, # 设置cookie的有效时间,单位: 秒
expires=None, # 设置cookie的过期时间戳[时间戳表示从1970-01-01 00:00:00至今的总秒数]
# datetime.now().timestamp() 获取时间戳
# int( time.time() * 1000 ) 获取毫秒时间戳
# datetime.now().timestamp() 获取毫秒时间戳
path=None, # 当前cookie是否只能在指定公共路径下使用,None表示在同一个域名下,任意路径都可以使用
domain=None, # 当前cookie是否只能在指定同一段域名下使用,None表示在当前服务器所在域名下使用
secure=False, # 当前cookie是否只能在https协议下使用,False表示在http协议下也能使用
httponly=False, # 当前cookie是否只能在http协议下使用,False表示在其他协议下也可以使用
"""
response.set_cookie("uname", "xiaoming", max_age=5)
response.set_cookie("uid", 100, max_age=180)
# 设置cookie信息,可以不设置过期时间,默认cookie有效期的就是浏览器关闭时自动删除
# 会话结束时浏览器会自动删除没有设置有效的cookie,而设置了有效期的cookie则只会在到期时才删除
response.set_cookie("is_login", True, )
return response
# 读取cookie
在客户端中发送cookie, 服务端接收并读取cookie
def get_cookie(request):
"""通过request.COOKIES可以获取客户端发送过来的cookie"""
print(request.COOKIES) # 获取本次客户端发送过来的所有cookie
print("uid=", request.COOKIES.get("uid")) # 获取指定名称cookie
print("uname=", request.COOKIES.get("uname")) # 不存在的或过期的cookie不会被浏览器通过http请求头携带到服务端
# cookie的修改,与添加一致,cookie重复的变量名会覆盖
response = HttpResponse("OK")
response.set_cookie("uname", "xiaohong", max_age=15)
return response
# 删除cookie
在服务端中删除cookie,在客户端中根据服务端的提示删除cookie
def del_cookie(request):
"""直接删除cookie在服务端是做不到的,因为cookie保存在客户端,所以我们需要通知客户端自己去删除"""
# 告诉浏览器,cookie过期了
response = HttpResponse("告诉客户端,删除cookie")
response.set_cookie("uid", "", max_age=0) # 设置有效期为0秒,当浏览器接受响应内容时,0秒早就到了,所以会自动删除
return response
用户登录记录登陆状态操作,演示
from django.http.response import HttpResponse
from django.shortcuts import redirect
from django.http.response import HttpResponseRedirect
from django.views.decorators.http import require_http_methods
# def login(request):
# """登录页面"""
#
# if request.method == "GET":
# """显示登录表单"""
# content = """
# <form action="/cookie/login/" method="POST">
# 登录账号:<input type="text" name="username"><br>
# 登录密码:<input type="password" name="password"><br>
# <button>登录</button>
# </form>
# """
# return HttpResponse(content)
#
# elif request.method == "POST":
# """处理登录信息"""
# username = request.POST.get("username")
# password = request.POST.get("password")
# # 到数据库中查询当前账号密码是否正确
# from hashlib import sha256
# sha = sha256()
# sha.update(password.encode())
# hash_pwd = sha.hexdigest()
# if username == "root" and hash_pwd == "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92":
# """登录成功"""
# # 记录登录状态
# response = HttpResponseRedirect("/cookie/info/")
# response.set_cookie("username", username)
# response.set_cookie("is_login", True, max_age=30*60)
# else:
# """登录失败"""
# response = HttpResponseRedirect("/cookie/login/")
# response["status"] = "fail"
# return response
@require_http_methods(["GET"])
def login(request):
"""显示登录表单"""
content = """
<form action="/cookie/login_handle/" method="POST">
登录账号:<input type="text" name="username"><br>
登录密码:<input type="password" name="password"><br>
<button>登录</button>
</form>
"""
return HttpResponse(content)
@require_http_methods(["POST"])
def login_handle(request):
"""处理登录信息"""
username = request.POST.get("username")
password = request.POST.get("password")
# 到数据库中查询当前账号密码是否正确
from hashlib import sha256
sha = sha256()
sha.update(password.encode())
hash_pwd = sha.hexdigest()
if username == "root" and hash_pwd == "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92":
"""登录成功"""
# 记录登录状态
response = HttpResponseRedirect("/cookie/info/")
response.set_cookie("username", username)
response.set_cookie("is_login", True, max_age=30*60)
else:
"""登录失败"""
response = HttpResponseRedirect("/cookie/login/")
response["status"] = "fail"
return response
def info(request):
"""用户中心-用户信息页面"""
username = request.COOKIES.get("username")
is_login = request.COOKIES.get("is_login", False)
if username != "root" or not is_login:
"""未登录"""
return redirect("/cookie/login/")
return HttpResponse(f"当前页面显示用户:{username}的信息")
mycookie/urls.py
代码
from django.urls import path
from . import views
urlpatterns = [
path("set/", views.set_cookie),
path("get/", views.get_cookie),
path("del/", views.del_cookie),
# 使用cookie实现登录认证
path("login/", views.login),
path("login_handle/", views.login_handle),
path("info/", views.info),
]
# session(掌握)
这里的session就是会话控制技术的一种, session主要把用户状态信息保存在服务器的文件中。
在没有进行特殊设置的情况下,session以文件格式保存状态数据, 一般保存的目录是系统的缓存文件存储目录.
例如: windows的C:/windows/temp目录 ,Linux/Mac OS的/tmp目录
WARNING
因为session是在服务端中保存数据,相对而言比cookie要安全.但是因为用户的数据都保存到服务器中,当用户基数大了,则服务器的存储压力就来了.所以一般不会考虑把session数据保存在文件,而是采用的第三方设置存储session的方案,例如保存到redis或者mysql之类的数据库里面。
# session原理
django中在2.0以后的版本默认采用了数据库保存session,这主要为了适应当前市面上大部分公司开发项目采用分布式服务器集群而调整的.但是我们目前是没有学到django的数据库操作,所以我们可以先根据官网说明,把session保存到文件中.
主应用目录/settings.py
, 代码:
# 配置项
# session核心类
SESSION_ENGINE = "django.contrib.sessions.backends.file"
# 保存到文件: django.contrib.sessions.backends.file
# 保存到数据库: django.contrib.sessions.backends.db # 需要配置数据库连接
# 保存到缓存中: django.contrib.sessions.backends.cache # 需要配置缓存连接
# session存储目录[如果不设置,则默认是系统的缓存目录]
# 3.0以前的django 通过以下代码配置
# SESSION_FILE_PATH = os.path.join(BASE_DIR, "session")
# 3.0以后通过以下配置
SESSION_FILE_PATH = BASE_DIR / "session_path" # 路径拼接,如果当前目录不存在,必须手动创建,否则报错
为了方便学习session的操作,我们创建一个sess子应用
uv run manage.py startapp sess
总路由,djdemo/urls.py
代码
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('home/', include('home.urls', namespace="index")), # namespace 是include函数的参数,namespace的值就是提供给reverse使用的
path('my/', include("mycookie.urls")), # cookie功能学习
path('sess/', include("sess.urls")), # session功能学习
]
# 保存session
sess/urls.py
代码
from django.urls import path,re_path
from . import views
urlpatterns = [
# path("路由url","视图函数","路由别名"),
path("session/set/", views.set_session),
path("session/get/", views.get_session),
path("session/del/", views.del_session),
]
sess/views.py
代码
def set_session(request):
"""设置session"""
# session保存在服务端,所以所有关于session的操作都是由request.session来完成的
#
request.session["uname"] = "root"
request.session["uid"] = 1
return HttpResponse("设置session数据")
# 读取Session
def get_session(request):
"""获取session"""
print(f"uname={request.session.get('uname')}") # format string python3.6提供的
print(f"uid={request.session.get('uid')}")
# 获取session所有的键值对
print(request.session.items())
# 获取session数据的有效,默认值是:2周 ==> 60 * 60 * 24 * 7 * 2
print(request.session.get_session_cookie_age() )
return HttpResponse("获取session数据")
# 删除session
def del_session(request):
"""删除session数据"""
# 删除单个指定名称的session
if request.session.get("name"):
request.session.pop("name")
# 删除所有的session,慎用
request.session.clear()
return HttpResponse("删除session数据")
随着项目的运行时间越长,用户量上来了,那么session数据也会不断增加,所以django虽然会自动删除过期的sesssion数据,但是如果用户没有正常注销的情况下,django是不会自动删除的,此时我们可以借助终端命令来进行删除。
uv run manage.py clearsessions
# base64编码工具函数
Base64是网络上最常见的用于传输8Bit字节码的编码方式之一。
Base64就是一种基于64个可打印字符来表示二进制数据的方法。
64个可打印编码字符就是小写字母a-z、大写字母A-Z、数字0-9、符号"+"、"/"(再加上作为垫字的"=",实际上是65个字符)
base64的使用一般无非就是编码和解码:
- 编码是从二进制数据流经过编码处理成base64字符的过程,可用于在HTTP环境下传递较长的标识信息。例如:图片内容
- 解码是从base64字符还原到二进制字节流的过程
在python中,base64是内置常用的标准模块,我们可以直接通过import导入base64模块直接使用。
在javascript中,也内置了base64的相关函数,分别是atob与btoa。
b64demo.py
代码
import json, base64
if __name__ == '__main__':
# 要编码的原始数据
data = {"uname":"root","uid":1}
print(data)
# 先转换成bytes类型数据
data_bytes = json.dumps({"uname": "root", "uid": 1}).encode()
print(data_bytes)
# 编码
base_data = base64.b64encode(data_bytes)
print(base_data)
# 解码
str_bytes = b'eyJ1bmFtZSI6ICJyb290IiwgInVpZCI6IDF9'
ori_data = base64.b64decode(str_bytes).decode()
# 字符串
print(ori_data)
# 变回原来的字典
data = json.loads(ori_data)
print(data)
# 路由进阶
在django中所有的路由最终都被保存到一个变量 urlpatterns., urlpatterns必须声明在主应用下的urls.py总路由中。这是由配置文件settings设置的ROOT_URLCONF指定的。
在django运行中,当客户端发送了一个http请求到服务端,服务端的web服务器则会从http协议中提取url地址, 从程序内部找到项目中添加到urlpatterns里面的所有路由信息的url进行遍历匹配。如果相等或者匹配成功,则调用当前url对应的视图方法。
在给urlpatterns路由列表添加路由的过程中,django一共提供了2个函数用于绑定路由与视图关系。
from django.urls import path # 普通路由
from django.urls import re_path # 正则路由,会把url地址看成一个正则模式与客户端的请求url地址进行正则匹配
# path和re_path 使用参数一致.仅仅在url参数和接收参数时写法不一样
# 如果在版本小于django2.0,只有一个django.urls.url 函数用于注册路由,url不仅支持path普通路由,也支持re_path正则路由
创建一个user子应用,用于完成关于路由进阶的学习
uv run manage.py startapp user
user/urls.py
代码
from django.urls import path, re_path
urlpatterns = [
]
总路由,djdemo/urls.py
代码
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('home/', include('home.urls', namespace="index")), # namespace 是include函数的参数,namespace的值就是提供给reverse使用的
path('my/', include("mycookie.urls")), # cookie功能学习
path('sess/', include("sess.urls")), # session功能学习
path('user/', include("user.urls")), # 路由进阶
]
# path注册路由
绑定的路由的执行上效率,使用path比re_path的效率高很多,因为path默认情况下仅仅是通过字符串比较,而re_path是使用正则匹配。
# path("路由url",视图函数,"路由别名"),
path("index4", views.index4,name="index4" ),
# re_path注册路由
子应用路由,user/urls.py
,代码;
from django.urls import path, re_path
from . import views
urlpatterns = [
# re_path(r"^info/(?P<参数名1>正则)/(?P<参数名2>正则).....$", views.info1),
re_path(r"^info/(?P<id>\d+)/(?P<page>0[1-9]+)$", views.info1),
re_path(r"^mobile/(?P<mobile>1[3-9]\d{9})$", views.info2),
]
视图代码中接收路由参数,user/views.py
,代码:
from django.http import HttpResponse
# 接受来自路由的正则参数
# def info1(request, 参数名1, 参数名2, .....):
def info1(request, id, page):
print(f"id={id}, page={page}")
return HttpResponse("OK")
def info2(request, mobile):
print(f"mobile={mobile}")
return HttpResponse("OK")
django的url路由加斜杠的问题
TIP
在django路由中编写url地址时为了快速查找, 建议最好不加上 / 在路由的后面.当用户访问对应视图的路由时, 加不加斜杠, django都能转换到正确的url地址,这个方式虽然好,但是会导致,我们客户端的静态文件的url路径如果是相对路径,则会被django这个做法导致出现路径正确的情况,所以不要加斜杠。当然,如果我们编写的是属于前后端分离的项目的话,加不加斜杠,不存在影响。
# 路由转换器(了解)
也可以叫路由验证器,有2个作用:
- 把路由参数进行类型转换
- 可以起到验证路由匹配的作用(让字符串路由path发挥正则路由re_path的作用)
# 内置转换器
文档:https://docs.djangoproject.com/zh-hans/4.2/topics/http/urls/#path-converters
内置转换器源码:django.urls.converters,别名设置:DEFAULT_CONVERTERS
常见的内置路由转换器:
str - 匹配除了 '/' 之外的非空字符串。如果表达式内不包含转换器,则会默认匹配字符串。
int - 匹配 0 或任何正整数。返回一个 int 。
slug - 匹配任意由 ASCII 字母或数字以及连字符和下划线组成的短标签。比如,building-your-1st-django_site 。
uuid - 匹配一个格式化的 UUID 。为了防止多个 URL 映射到同一个页面,必须包含破折号并且字符都为小写。比如,075194d3-6885-417e-a8a8-6c931e272f00。返回一个 UUID 实例。
path - 匹配非空字段,包括路径分隔符 '/' 。它允许你匹配完整的 URL 路径而不是像 str 那样匹配 URL 的一部分。
user/urls.py
代码
from django.urls import path, re_path
from . import views
urlpatterns = [
# path("url路径", 视图函数/视图类, name="路径别名"),
path("index/", views.index),
# re_path(r"^url路径/(?P<参数变量名>正则模式)/$", 视图函数/视图类),
re_path(r"^info/(?P<id>\d+)/$", views.info),
re_path(r"^goods/(?P<cat_id>\d+)/(?P<attr_id>\d+)/$", views.goods),
path("img/", views.img),
path("rev/<int:num>/", views.inbuild_reverse),
path("rev/<str:content>/", views.inbuild_reverse2),
path("rev/<uuid:ustr>/", views.inbuild_reverse3), # str会包含uuid的模式,str和uuid同时使用时,str必须写在后面
]
user/views.py
代码
"""路由转换器[了解]"""
def inbuild_reverse(request, num):
""""内置路由转换器"""
return HttpResponse(f"num={num}")
def inbuild_reverse2(request, content):
""""内置路由转换器"""
return HttpResponse(f"content={content}")
def inbuild_reverse3(request, ustr):
""""内置路由转换器"""
return HttpResponse(f"ustr={ustr}")
# 自定义转换器
在当前子应用下新建converters.py下编写的,这里是我们刚学习,所以为了方便直接在路由urls.py下编写,代码:
from django.urls.converters import StringConverter, register_converter
class MobileConverter(StringConverter):
regex = r"1[3-9]\d{9}"
# register_converter(路由转换类, "调用别名")
register_converter(MobileConverter, "mob")
路由中使用自定义路由转换器,user/urls.py
代码
from django.urls import path, re_path
from . import views
from . import converters
urlpatterns = [
re_path(r"^info/(?P<id>\d+)/(?P<page>0[1-9]+)/$", views.info1),
re_path(r"^mobile/(?P<mobile>1[3-9]\d{9})$", views.info2),
# 路由转换器[路径转换器]
path('info/<int:id>', views.info3),
path('img/<uuid:img_id>', views.info4),
# 使用自定义路由转换器
path("sms/<mob:mobile>", views.info5),
]
视图中也可以接收来自路由转换器转换后的数据,views.py,代码:
def info5(request, mobile):
print(f"mobile={mobile}")
return HttpResponse("ok, info5")
补充
自定义路由转换器,实际上就是django在对路由进行数据转换和简写正则路由的实现,这种实现方式是基于不同的转换器类来完成,开发者要实现自定义转换器,需要编写的类必须符合官方要求的3个基本要求:
1. 必须以类格式编写
2. 必须声明属性和方法:regex 和 方法:to_python,to_url
3. 必须通过register_converter(转换器类名,"别名")进行注册才能被调用
这种实现方式,实际就是对编程领域的设计模式的一种应用。
设计模式,前人总结下来的基于固定业务场景的解决方案就是设计模式。
编程中,设计模式有23种不同设计模式。
其中,我们上面这种就是叫 策略模式
一般在工作中,往往可以使用策略模式,来进行营销活动[优惠券、打折]的实现
← DRF框架