Django使用教程
一、基础入门
1.创建一个项目
在目录下打开命令行输入django-admin startproject myweb(自定义项目名)
2.运行开发服务器
a.本地访问
创建后会生成一个myweb的项目文件夹,cd转到文件夹内,可以输入命令启动服务python manage.py runserver
注:可指定服务器在哪个端口开启,如python manage.py runserver 8080
启动后会得到一串地址如http://127.0.0.1:8000/可以通过复制地址粘贴到浏览器来访问
b.远程访问
可以根据本机的ip地址来远程访问本机的文件。ipv4地址在cmd下ipconfig可以查询
启动服务时,输入python manage.py runserver 0.0.0.0:8080来开启远程访问
通过在浏览器地址输入本机ipv4:8080来访问
3.创建一个应用程序
位于当前项目myweb目录下,输入命令python manage.py startapp myapp(应用自定义名称)
(1)views.py是视窗文件,在文件内写入代码
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse("Hello world")
(2)在myapp文件夹下创建urls.py路由文件,写入代码与views.py中写入的函数相对应
urls.py
from django.urls import path
from . import views
#导入views.py文件
urlpatterns=[
path("",views.index,name="index"),
#path函数的三个参数:网页访问路径(可以为空),请求对象,名字(用来反向获取URL)
]
(3)修改总路由文件(myweb/urls.py)
from django.contrib import admin
from django.urls import path,include
#include函数包含在django.urls文件中
urlpatterns = [
path('admin/', admin.site.urls),
path("myapp/",include("myapp.urls"))
#将写好的应用的路由加到总路由当中
]
注:一旦动了总路由文件(项目文件夹下的urls.py),默认的欢迎界面就没有了
(4)把myapp添加到myweb/settings.py下的INSTALLED_APPS中
4.数据库的连接
默认情况下,配置使用SQlite。若不使用SQlite作为数据库,则需要额外的配置,例如user,password和host必须加入
其中ENGINE设置为数据库后端使用。内置数据库后端有:
- django.db.backends.postgresql
- django.db.backends.mysql
- django.db.backends.sqlite3
- django.db.backends.oracle
在myweb/settings.py文件中,通过DATABASES项进行数据库设置,改成上面四种之一
二、编写视图views
1.编写视图函数
在myapp/views.py中可以编写视图函数或类,但一定要有一个固定的参数request
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse("hello world")
2.配置路由
(1)
firstproject/urls.py
from django.contrib import admin
from django.urls import path
from myapp import views
urlpatterns = [
#path('admin/', admin.site.urls),
path("aaa/",views.index),
]
(2)此外,还可以对路径进行通用匹配,比如匹配所有整数的地址.int:name捕获整数参数。如果不包括转换器/,则匹配除字符意外的任何字符串
例如:
urls.py
path("find/<int:sid>",views.find)
myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse("hello world")
def find(request,sid):
return HttpResponse(f"find...{sid}")
#其中sid就是从URL中获取到的整数的值
(3)还可以用正则表达式的方式进行匹配
urls.py
re_path(r"^fun/(?P<yy>[0-9]{4})/(?P<mm>[0-9]{2})$",views.fun)
#使用 ?P<name> 可以指定名字,不过指定的名字需要与views.py里对应的函数的参数名保持一致
#也可以不指定名字
3.设计自己的404
注:一般在项目上线的时候才会这么做。在开发期,我们更需要它的报错数据
默认的404界面是由Django框架在安装的时候就已经附赠的
(1)在项目根目录下面创造文件夹templates,并且在此目录下创建一个404.html文件
(2)在settings.py总配置文件中,修改TEMPLATES的值。再把DEBUG的值改为FALSE
DEBUG = False
#关闭DEBUG模式。调试时应当改为True开启
ALLOWED_HOSTS = ["*"]
#修改允许的主机为"*" 全部
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,"templates")],
#地址改为templates的地址(404.html文件所在的地址)
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
我们也可以主动抛出错误
myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse,Http404
# Create your views here.
def index(request):
return HttpResponse("hello world")
def find(request,num):
#return HttpResponse(f"find...{num}")
raise Http404('修改页面不存在!')
4.配置子路由
为了方便管理,总路由只有一个,我们可以把诸多应用的子路由汇总到主路由上。
myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("aaa/",views.index,name="xxx"),
#可选name属性值,可以反向生成url地址
path("find/<int:num>",views.find),
]
urls.py
from django.contrib import admin
from django.urls import path,include
#导入include模块
from myapp import views
urlpatterns = [
path('admin/', admin.site.urls),
path("",include('myapp.urls')),#在当前总路由中导入子路由文件
]
a.根据路由名称反向生成url
myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse,Http404
# Create your views here.
from django.urls import reverse
def index(request):
print(reverse("index"))#reverse通过路由名称反向生成url请求地址
return HttpResponse("hello world")
def find(request,num):
#return HttpResponse(f"find...{num}")
raise Http404('修改页面不存在!')
url路径参数
myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("",views.index,name="index"),
path("find/<int:num>/<str:name>",views.find,name="find"),
path("add",views.add,name="add"),
]
myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse,Http404
# Create your views here.
from django.urls import reverse
def index(request):
print(reverse("add"))
print(reverse("find",args=(100,"lisi")))
return HttpResponse("hello world")
def find(request,num=0,name=""):
#return HttpResponse(f"find...{num}")
return HttpResponse("访问成功")
def add(request):
return HttpResponse("add")
arg的参数作用未知
b.根据url重定向到其他url
须先导入from django.shortcuts import redirect
myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse,Http404
# Create your views here.
from django.urls import reverse
from django.shortcuts import redirect
def index(request):
print(reverse("add"))
print(reverse("find",args=(100,"lisi")))
return redirect(reverse("find",args=(100,"lisi")))#执行浏览器重定向
#return HttpResponse("hello world")
def find(request,num=0,name=""):
#return HttpResponse(f"find...{num}")
return HttpResponse("访问成功")
def add(request):
return HttpResponse("add")
使用该方法可以在访问index时自动跳转(重定向)到find中
三、DJango的模型层
1.准备工作
-
在MySQL数据库中创建一个
mytest数据库,用作测试 -
创建
mydemo项目(创建方法上面有) -
在项目下创建
myapp应用 -
在项目下创建
templates文件夹 -
创建
templates\myapp文件夹 -
更改
mydemo\medemo\settings.py的配置
a.ALLOWED_HOSTS = ['*']允许所有主机访问
b. 将myapp导入进列表。使得程序能够识别将要在myapp中进行的数据库操作INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'myapp', ]c.
templates模板目录需要在settings.py的模板目录进行配置TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR,"templates")], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]d. 配置连接MySQL数据库
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mydemo',#数据库名 'USER': 'root', 'PASSWORD': '*****', 'HOST': 'localhost', 'PORT': '3306', } } -
可能遇到的问题:没有安装数据库依赖
a. 安装系统依赖
sudo apt-get update sudo apt-get install python3-dev default-libmysqlclient-dev build-essentialb.在Python中安装
# 安装 mysqlclient pip install mysqlclient -
可能遇到的问题:已安装mysqlclient,提示Did you install mysqlclient?
当你看到这样的错误信息,表明Django尝试加载MySQLdb模块但未找到,因为MySQLdb已被mysqlclient替代。
解决方法:
先安装pymysqlpip install pymysql下载pymysql并在你的项目(与settings.py同级)目录中的__init__.py文件中添加以下两行代码
import pymysql pymysql.install_as_MySQLdb()这两行代码会将pymysql伪装成MySQLdb,使Django能够识别并正确地使用MySQL数据库。
2.Model模型
什么是模型?
- 模型是你的数据的唯一的、权威的信息源。它包含你所储存数据的必要字段和行为。通常,每个模型对应数据库中唯一的一张表
模型与数据库的关系
- 模型负责业务对象和数据库的关系映射(ORM)
ORM是"对象-关系-映射"的简称,主要任务是:
- 根据对象的类型生成表结构
- 将对象、列表的操作,转化为sql语句
- 将sql查询到的结果转换为对象、列表
3.开发流程
-
在models.py中定义模型类,要求继承自models.Model
myapp\models.pyfrom django.db import models from datetime import datetime # Create your models here. class Users(models.Model): #id = models.AutoField(primary_key=True) #主键可省略不写 name = models.CharField(max_length=32) age = models.IntegerField(default=20) phone = models.CharField(max_length=16) addtime = models.DateTimeField(default=datetime.now()) class Meta: db_table = 'history'#指定表名。 -
把应用加入settings.py文件中的installed_app项
mydemo\settings.pyINSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'myapp',#加入应用 ]tips:迁移后产生许多多余的表是因为myapp上面的一堆设置没有关闭
-
生成迁移文件
在项目根目录执行命令python manage.py makemigrations
`Error:No module named 'models'`解决方案: 可能是在views.py文件中使用了`from models import History` 改为`from .models import History` -
执行迁移
在项目根目录执行命令python manage.py migrate
(数据库密码不对可能会报错)
4.数据库操作
a.增
myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse
from myapp.models import Users#导入定义好的models的类
# Create your views here.
def index(request):
#执行Model的操作
#添加操作
ob = Users() #实例化一个新对象(空对象)
ob.name = "李四"
ob.age = 13
ob.phone = 131456987
ob.save()#执行保存操作
return HttpResponse("首页")
b.删
myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse
from myapp.models import Users
# Create your views here.
def index(request):
#执行Model的操作
#删除操作
mod = Users.objects #获取user的model对象
#Users类继承自Model类,所以有object这个方法
user = mod.get(id=2)#一般通过主键来提取数据,获取id值为2的数据信息
print(user.name)#现在已经获取到id为2的对象,打印它的name属性
user.delete() #执行删除操作
return HttpResponse("首页")
c.修改
myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse
from myapp.models import Users
# Create your views here.
def index(request):
#执行Model的操作
#修改操作
ob = Users.objects.get(id=1)
ob.age = 17
ob.save()#修改完要保存
print(ob.age)
return HttpResponse("首页") #todo 显示到页面,那能不能相应一个html呢??
tip:修改完记得保存
d.查询
myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse
from myapp.models import Users
# Create your views here.
def index(request):
#执行Model的操作
#数据查询
mod = Users.objects
ulist = mod.all() #获取所有数据,返回的对象支持迭代
print(type(ulist))
for i in ulist: #每个i都是一个实例
print(f"{i.id} {i.name} {i.age}")
return HttpResponse("首页") #todo 显示到页面,那能不能相应一个html呢??
此外,还可以设置过滤器来选择要查询的数据
ulist = mod.filter(name='张三') #设置过滤器
ulist = mod.filter(age__gt=20) #获取所有age大于20的数据
ulist = mod.filter(age__gte=20) #获取所有age大于等于20的数据
ulist = mod.filter(age__lt=20) #获取所有age小于20的数据
注:filter(不带参数)与all()完全等同
根据相应属性排序
ulist = mod.order_by("age")#根据相应属性升序排序
限定查询行数(类似于列表切片)
ulist = mod.order_by("age")[:3]#只获取前三条数据
5.补充:get与filter的异同
filter() 方法
-
返回一个QuerySet(查询集),即使没有匹配结果也会返回空QuerySet
-
模板中使用示例:passcode_table.filter(some_field=some_value)
-
适用于可能返回多个对象或零个对象的情况
-
在模板中通常与for循环一起使用
get() 方法
-
返回单个模型对象
-
如果没有找到匹配项,会引发
DoesNotExist异常 -
如果找到多个匹配项,会引发
MultipleObjectsReturned异常 -
模板中使用示例:passcode_table.get(id=some_id)
-
适用于已知只会返回一个对象的情况
字段类型
- AutoField:一个根据实际ID自动增长的IntegerField,通常不指定
- 如果不指定,一个主键字段将自动添加到模型中
- BooleanField:true/false 字段,此字段的默认表单控制是CheckboxInput
- NullBooleanField:支持null、true、false三种值
- CharField(max_length=字符长度):字符串,默认的表单样式是 TextInput
- TextField:大文本字段,一般超过4000使用,默认的表单控件是Textarea
- IntegerField(default):整数
- DecimalField(max_digits=None, decimal_places=None):使用Python的Decimal实例表示的十进制浮点数
- DecimalField.max_digits:位数总数
- DecimalField.decimal_places:小数点后的数字位数
- FloatField:用Python的float实例来表示的浮点数
- DateField(auto_now=False, auto_now_add=False):使用Python的datetime.date实例表示的日期
- 参数DateField.auto_now:每次保存对象时,自动设置该字段为当前时间,用于“最后一次修改”的时间戳,它总是使用当前日期,默认为false
- 参数DateField.auto_now_add:当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false
四、Django的视图层
1.render()返回html页面
myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse
from myapp.models import Users
# Create your views here.
def index(request):
#执行Model的操作
#添加操作
# ob = Users()
# ob.name = "李四"
# ob.age = 13
# ob.phone = 131456987
# ob.save()
#删除操作
# mod = Users.objects #获取user的model对象
# #Users类继承自Model类,所以有object这个方法
# user = mod.get(id=2)#一般通过主键来提取数据,获取id值为2的数据信息
# print(user.name)#现在已经获取到id为2的对象,打印它的name属性
# user.delete() #执行删除操作
#修改操作
# ob = Users.objects.get(id=1)
# ob.age = 17
# ob.save()#修改完要保存
# print(ob.age)
#数据查询
# mod = Users.objects
# #ulist = mod.all() #获取所有数据,返回的对象支持迭代
# #ulist = mod.filter(name='张三') #设置过滤器
# ulist = mod.order_by("age")#根据相应属性升序排序
# print(type(ulist))
# for i in ulist: #每个i都是一个实例
# print(f"{i.id} {i.name} {i.age}")
return HttpResponse("首页 <br/> <a href='/users'>用户信息管理</a>") #todo 显示到页面,那能不能相应一个html呢??
#浏览用户信息
def indexUsers(request):
try:
ulist = Users.objects.all()
context = {"userlist":ulist}
#APP_DIRS: 如果设置为 True,Django 会自动在每个应用的 templates 目录中查找模板文件。
return render(request,"myapp/users/index.html",context)#加载模板
except:
return HttpResponse("没有找到用户信息!")
pass
#加载添加用户信息表单
def addUsers(request):
pass
#执行用户信息添加
def insertUsers(request):
pass
#执行用户信息删除
def delUsers(request):
pass
#加载用户信息修改表单
def editUsers(request):
pass
#执行用户信息修改
def updateUsers(request):
pass
Django 会自动在每个应用的 templates 目录中查找模板文件。
危险情况:传递敏感数据本身
render 只会防止XSS,但它不会帮你过滤敏感信息。如果你不小心把敏感数据(如用户密码、API密钥、个人身份信息)放入上下文,它就会被发送到前端,并可能暴露在生成的HTML源码或JS变量中。
最佳实践: 永远只传递前端需要且被允许看到的最小数据。绝对不要传递整个模型实例或包含敏感信息的对象。使用序列化器或手动构建只包含必要字段的字典。
myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index,name="index"),
#配置users信息操作路由
path('users',views.indexUsers,name="indexusers"),
path('users/add',views.addUsers,name="addusers"),
path('users/insert',views.insertUsers,name="insertusers"),
path('users/del/<int:uid>',views.delUsers,name="delusers"),
path('users/edit/<int:uid>',views.editUsers,name="editusers"),
path('users/update',views.updateUsers,name="updateusers"),
]
templates/myapp/users/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<center>
<h2>用户信息管理</h2>
<h3>浏览用户信息</h3>
<table width="880" border="1">
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
<th>电话</th>
<th>添加时间</th>
<th>操作</th>
</tr>
{% for user in userlist %}
<tr>
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.age}}</td>
<td>{{user.phone}}</td>
<td>{{user.addtime}}</td>
<td>编辑 删除</td>
</tr>
{% endfor %}
</table>
</center>
</body>
</html>
2.关于表单提交
直接提交表单可能会产生csrf验证报错。此时需要在html代码的form标签内任意位置加上{% csrf_token %}即可解决
{% csrf_token %} 是 Django 模板中的一个模板标签,用于防止跨站请求伪造(CSRF, Cross-Site Request Forgery)攻击。它的作用是生成一个隐藏的表单字段(),包含一个唯一的令牌(Token),服务器会验证这个令牌以确保请求是合法的。
3.在html内访问py文件数据的语法格式
反向解析url:
<a href="{%url 'viewdata'%}">返回</a>
<a href="{% url 'deldata' user.id %}">编辑</a>
正向解析url:
urls.py
urlpatterns = [
path('', views.index,name="index"),
path('res01',views.res01,name='res'),
]
<a href="\res01">简单的视图</a>
注:viewdata处与url.py配置路由时的名称保持一致。一定要加引号。更推荐使用反向解析
4.返回错误
#直接返回一个404,没有去加载404的模板页面
return HttpResponseNotFound('<h1>Page not found</h1>')
#可以直接返回一个status状态码
return HttpResponse(status=403)
#返回一个404的错误页面
raise Http404("Poll does not exist")
5.关于重定向
重定向(redirect)就是通过各种方法将各种网络请求重新定个方向转到其他位置
from django.shortcuts import redirect
from django.urls import reverse
#redirect重定向 reverse反向解析url地址
return redirect(reverse('userindex'))
#执行一段js代码,用js进行重定向
return HttpResponse('<script>alert("添加成功");location.href="/userindex";</script>')
#加载一个提醒信息的跳转页面
context={'info':'添加数据成功','u':'/userindex'}
return render(request,'info.html',context)
6.视图类的定义
views.py
from django.views import View
from django.http import HttpResponse
class MyView(View):
def get(self,request,*args,**kwargs):
return HttpResponse("Hello Views!")
urls.py
from django.urls import path
from . import views
from .views import MyView
urlpatterns = [
path('', views.index,name="index"),
path('res01',views.res01,name='res'),
path('res02',views.res02,name="res02"),
path("res03",MyView.as_view(),name="resp03")#会自动调用对应请求,这里调用的是get请求
]
as_view() 会返回一个闭包函数(通常命名为 view),这个闭包函数会根据 HTTP 请求方法(如 GET、POST 等)自动调用类中的对应方法(如 get()、post())
7.JSON数据的相应
views.py
from django.http import HttpResponse,JsonResponse
def res05(request):
#假设data存取多条数据
data=[
{'id':1001,'name':'zhangsan','age':20},
{'id':1002,'name':'lisi','age':19},
{'id':1003,'name':'wangwu','age':18},
]
return JsonResponse({'data':data})#只能响应字典类型的数据,所以要先把data封装成字典形式
8.set_cookie方法
a.设置cookie
#获取当前响应对象
response = HttpResponse('cookie的设置')
#使用响应对象进行cookie的设置
response.set_cookie("a","abc")
#返回响应对象
return response
注:cookie的内容不能含有中文字符
b.获取cookie
#读取
a = request.COOKIES.get('a',None)
if a:
return HttpResponse('cookie的读取:'+a)
else:
return HttpResponse('cookie不存在')
注:如果是第一次设置,会访问None,因为该网址以前没有cookie可供获取。(即使读取请求在‘设置’的代码之后)
或许可以替代用url地址传参数('res07/int:id')的方法
9.HttpRequest对象
属性
(除非特别说明,属性都是只读的)
- path: 字符串,表示请求的完整路径(不包含域名)
- method: 字符串,表示HTTP请求方法(如'GET'、'POST')
- encoding: 字符串,表示提交数据的编码方式(通常为utf-8)
- GET: 类字典对象,包含GET请求的所有参数
- POST: 类字典对象,包含POST请求的所有参数
- FILES: 类字典对象,包含所有上传的文件
- COOKIES: 标准Python字典,包含所有cookie(键和值为字符串)
- session: 可读写的类字典对象,表示当前会话(需Django启用会话支持)
例:print(request.path)
方法
- is_ajax():
如果请求是通过XMLHttpRequest发起的,返回True
10.url地址绑定参数
url后可以绑定参数,?后的属性即为参数,不同参数间用&分隔
?前是地址,?后是url参数
html
<li><a href="{% url 'res07' %}?id=100%name=zhangsan&age=22">7.测试request请求对象</a></li>
后端获取url参数
def res07(request):
print(request.GET)
return HttpResponse("测试request对象")
-
request.GET的返回值是django.http.request.QueryDict格式
-
与python字典不同,QueryDict类型的对象用来处理同一个键带有多个值的情况
<QueryDict: {'name': ['zhangsan'], 'age': ['22'], 'id': ['3']}> -
方法get():根据键获取值
- 只能获取键的一个值
- 如果一个键同时拥有多个值,获取最后一个值
dict.get('键',default) #或简写为 dict['键'] -
方法getlist():根据键获取值
- 将键的值以列表返回,可以获取一个键的多个值
dict.getlist('键',default)
default:当键不存在时返回的默认值(类似字典的default参数),避免因键不存在引发KeyError,使代码更健壮。(应替换为具体的东西)
dict.getlist('id',0) - 将键的值以列表返回,可以获取一个键的多个值
关于request.GET方法,用什么方式上传就用什么方式接收
11.request的部分方法
request.method:获取请求类型
if request.method == 'POST':
request.POST['title']:获取上传的对应值request.POST.get('email', '')使用get方法获取对应值,可以设置默认值request.body:获取上传的数据。如字典之类的所有类型数据request.FILES.get("pic",None):获取上传的文件,失败则返回None
<!-- 要发送到的地址,发送方式(图片必须是post) -->
<form action="{% url 'doupload' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
标题名称:<input type="text" name="title"><br><br>
上传文件:<input type="file" name="pic"><br><br>
<input type="submit" value="上传">
</form>
12.注意事项
如果拿到的数据为类似b'{"pesticide":"true"}'的二进制数据,则可能需要先将数据解码为utf-8后再进行json解码
control_data = request.body.decode("utf-8")
五、Django的模板层
1.在Django框架中如何使用模板
a.在项目的setting.py配置文件中配置模板目录
'DIRS':[os.path.join(BASE_DIR,'templates')],
b.在应用的视图文件加载模板,并放置要输出的数据
return render(request,"目录/模板文件.html",{放置字典数据})
c.在模板文件中使用变量、标签和过滤器等输出信息
{{变量}} {% 标签 %} {{变量|过滤器}}
2.模板语法
a.变量
- 变量输出语法
{{ var }} - 当模板引擎遇到一个变量,将计算这个变量,然后将结果输出
- 当模板引擎遇到点".",会按照下列顺序查询
- 字典查询,例如:foo["bar"]
- 属性或方法查询,例如:foo.bar
- 数字索引查询,例如:foo[bar]
- 如果变量不存在,模板系统将插入""(空字符串)
-
在模板中调用方法时不能传递参数
b.标签
- 语法
{% tag %} -
作用
- 在输出中创建文本 - 控制循环或逻辑 - 加载外部信息到模板中
for标签
{% for ... in ... %}
循环逻辑
{% endfor %}
if标签
{% if... %}
逻辑1
{% elif... %}
逻辑2
{% else %}
逻辑3
{% endif %}
例:
<li>
{% if sex == 1 %}
男生
{% else %}
女生
{% endif %}
</li>
注:等号==左右两端必须要有空格,不然会报错
if...else...条件也可以写在单行
comment标签
{% comment %}
多行注释
{% endcomment %}
csrf_token:这个标签用于跨站请求伪造保护
{% csrf_token %}
c.过滤器
- 语法
{{ 变量|过滤器 }},例如{{ name|lower }},表示将变量name的值变为小写输出 - 使用管道符号"|"来应用过滤器
- 关闭HTML自动转义
{{ data|safe }} - 可以在if标签中使用过滤器结合运算符
if list1|lenth > 1 - 过滤器能够被"串联",构成过滤器链
name|lower|upper - 过滤器可以传递参数,参数使用引号包起来
list|join:"," - default:如果一个变量没有被提供,或者值为false或空,则使用默认值,否则使用变量的值
value|default:"什么也没有" - date:根据给定格式对一个date变量格式化
value|date:'Y-m-d' - 更多过滤器详见官方文档
d.注释
- 单行注释
{# 注释 #} - 多行注释
{% comment %}
多行注释
{% endcomment %}
e.运算
- 加
{{ value|add:10 }}
note:value=5,则结果返回15
- 减
{{ value|add:-10 }}
note:value=5,则结果返回-5,加一个负数就是减法
注:{{ value1|add:-value2 }}会报错
- 乘
{% widthratio 5 1 100 %}
note:等同于(5/1)*100,结果返回500,
widthratio需要三个参数,它会使用参数1/参数2*参数3的方式进行运算,进行乘法运算,使"参数2"=1
- 除
{% widthratio 5 100 1 %}
note:等同于:(5/100)*1,则结果返回0.05,和乘法一样,使"参数3"=1就是除法了
注:还可以通过这种方式来传递变量
<a href="{% url 'pageusers' pagenum|add:-1 %}">上一页</a>
f.自定义 标签 或 过滤器
- 首先在当前应用目录下创建一个
templatetags模板标签目录,建议内放一个__init__.py的空文件 - 然后在
templatetags目录下创建一个模板标签文件pagetag.py(自定义名称),具体代码如下:
from django import template
register = template.Library()
#自定义过滤器(实现大写转换)
#注册一个过滤器
@register.filter
def myupper(va1):
return va1.upper()
#自定义标签(实现减法运算)
@register.simple_tag
def jian(a,b):
res = int(a)-int(b)
return res
- 使用:在模板文件中使用
<h3>5.自定义过滤器和模板标签</h3>
{% load pagetag %}
大写:{{ name|myupper }}<br>
相减:{% jian m1 m2 %}
g.补充
- 有些时候可能会遇到想要原样输出{{ value }}而不采用Django语法的情况(忽略Django语法)
| 方法 | 语法 | 适用场景 | 输出示例 |
|---|---|---|---|
| verbatim 标签 | {% verbatim %}{{ 变量 }}{% endverbatim %} |
需要原样输出大段包含模板语法内容时 | {{ pagenum }} |
| templatetag 标签 | {% templatetag openvariable %}变量{% templatetag closevariable %} |
需要精确控制单个模板标签输出时 | {{ pagenum }} |
使用建议:
- 推荐使用
verbatim- 最简洁直观的方式 - 少量输出时可用
templatetag - 特殊情况下使用 HTML 实体转义
注意:
verbatim标签会禁用范围内所有模板标签解析,包括{% %}和{{ }}。
3.模板继承
- 模板继承可以减少页面内容的重复定义,实现页面内容的重用
- 典型应用:网站的头部、尾部是一样的,这些内容可以定义在父模版中,子模板不需要重复定义
- block标签:在父模板中预留区域,在子模板中填充
- extends标签:继承,写在模板文件的第一行
- 定义父模板
base.html
{% block block_name %}
这里可以定义默认值
如果不定义默认值,则表示空字符串
{% endblock %}
- 定义子模板
index.html(在子模板中导入父模板)
{% extends "base.html" %}
- 在子模板中使用block填充预留区域
子模板.html
{% extends "myapp/base.html" %}
{% block title2 %}Django的模板继承实例{% endblock %}
{% block mycontent %}
- 模板继承可以减少页面内容的重复定义,实现页面内容的重用<br>
- 典型应用:网站的头部、尾部是一样的,这些内容可以定义在父模版中,子模板不需要重复定义
{% endblock %}
4.在子模板中修改母模板css样式
子模板
{% extends "myapp/template.html" %}
{% block daohang%}
<style>
.daohang>div>div{
color:red;
}
</style>
{{ block.super }} {# 保留母模板的原始内容 #}
{% endblock %}
{{ block.super }}可以保留母模板的原始内容
六、文件上传
a.配置settings.py文件
STATIC_URL = 'static/'
#静态目录
STATICFILES_DIRS = (
os.path.join(BASE_DIR,"static"),
)
b.在views.py文件中配置接收文件的函数
#加载文件上传表单
def upload(request):
return render(request,"myapp/upload.html")
#执行文件上传处理
def doupload(request):
return HttpResponse(f"上传的文件:{request.POST['title']}")
c.在urls.py中配置好路由
from django.urls import path
from . import views
urlpatterns = [
path('', views.index,name="index"),
#文件上传路由配置
path('upload',views.upload,name="upload"),#加载文件上传表单页
path('doupload',views.doupload,name="doupload"),#执行文件上传
#配置users信息操作路由
path('users',views.indexUsers,name="indexusers"),
path('users/add',views.addUsers,name="addusers"),
path('users/insert',views.insertUsers,name="insertusers"),
path('users/del/<int:uid>',views.delUsers,name="delusers"),
path('users/edit/<int:uid>',views.editUsers,name="editusers"),
path('users/update',views.updateUsers,name="updateusers"),
]
d.在文件上传模板upload.html中添加文件上传的form表单
<!-- 要发送到的地址,发送方式(图片必须是post) -->
<form action="{% url 'doupload' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
标题名称:<input type="text" name="title"><br><br>
上传文件:<input type="file" name="pic"><br><br>
<input type="submit" value="上传">
</form>
e.接收上传的文件
views.py
#执行文件上传处理
def doupload(request):
myfile = request.FILES.get("pic",None)#如果拿不到信息,返回None
print(myfile.name)#这里打印的是图片的文件名
if not myfile:
return HttpResponse("没有上传的文件信息")
destination = open("./static/pics/a.jpg","wb+")
for chunk in myfile.chunks(): #将图片的文件流切片,分块读取上传文件内容并写入目标文件
destination.write(chunk)
destination.close()
return HttpResponse(f"上传的文件:{request.POST['title']}")
myfile的属性值
Django UploadedFile 对象属性及方法
| 属性/方法 | 说明 |
|---|---|
.name |
上传文件的原始文件名(如 "example.jpg")。 |
.size |
文件大小(字节),如 1024 表示 1KB。 |
.content_type |
文件的 MIME 类型(如 "image/jpeg",由浏览器提供,可能不可靠)。 |
.charset |
文本文件的字符集(通常为 None)。 |
.file |
文件数据对象(如 io.BytesIO 或临时文件句柄)。 |
.read() |
方法:读取整个文件内容(返回字节串)。 |
.chunks() |
方法:返回生成器,分块读取大文件(避免内存溢出)。注:返回的是字节数据,需要使用"wb"模式,适适合文件上传 |
.multiple_chunks() |
方法:检查文件是否大于默认分块阈值(2.5MB)。 |
.open() |
方法:重新打开文件(类似 Python 的 open())。 |
.temporary_file_path() |
方法(仅 TemporaryUploadedFile):返回临时文件的系统路径。 |
5.模板标签必须在 HTML 中直接渲染,不能在单独的 .js 文件中使用 Django 模板语法
6.后端向html模板中传参
#views.py
def filedownload(request):
path = os.listdir("./static/upload_files/")
print(path)
data = {"files":path}
return render(request,"myapp/filedownload.html",data)
<!-- filedownload.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>简易文件下载</title>
</head>
<body>
<center>
{% for file in files %}
<p>{{file}}</p>
{% endfor %}
</center>
</body>
</html>
七、分页操作
a.相关模块介绍
- Django提供了一些类实现管理数据分页,这些类位于
django/core/paginator.py中 - Paginator对象
Paginator(列表,int): 返回分页对象,参数为(列表数据,每页展示数据的条数) - 属性
count: 数据总数
num_pages: 页面总数
page_range: 页码列表,从1开始,例如[1,2,3,4] - 方法
page(num): 下标从1开始,如果提供的页码不存在,抛出InvalidPage异常
b.在视图层中导入相应模块
from django.core.paginator import Paginator
c.编写分页操作的函数
#分页操作
def pageUsers(request):
try:
list = Users.objects.filter()
#初始化Paginator分页对象
p = Paginator(list,3)#以每三条数据一页的方式分页
ulist = p.page(2)#将要访问的页码数,从1开始
context = {"userlist":ulist}
#APP_DIRS: 如果设置为 True,Django 会自动在每个应用的 templates 目录中查找模板文件。
return render(request,"myapp/users/pageindex.html",context)#加载模板
except:
return HttpResponse("没有找到用户信息!")
pass
八、静态资源
1.简介
Django 默认从 static 目录加载静态文件(HTML、CSS、JS、图片等)
2.配置静态资源
a.在项目下建立static文件夹
b.在settings.py中配置文件
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'), # 确保这一行存在
]
c.在模板中要先加载static目录
{% load static %}
d.在模板中使用{% static %}标签加载文件
<div class="maincontent">
<iframe src="{% static 'index/index_data.html' %}" frameborder="1" id="frame_data"></iframe>
<!-- <iframe src="" frameborder="1" id="frame_yvjing"></iframe> -->
</div>
九、实时摄像头监测
此方法是调用开发者自己电脑的摄像头。若想呈现用户的视频需要做的工作如下
1.客户端通过cv2获取摄像头信息。
2.客户端将视频帧编码成jpeg格式
ret, frame = cap.read()
if not ret:
break
_, img_encoded = cv2.imencode('.jpg', frame)#img_encoded为NumPy 数组,数据类型为 uint8
3.客户端将jpeg格式的numpy数组转为byte格式(便于使用post的方法发送。详见网络传输笔记的第一点)
response = requests.post(
url,
data=img_encoded.tobytes(),#jpeg_data.tobytes() 的作用是将 NumPy 数组(jpeg_data)转换为 原始的二进制字节流(bytes),以便通过网络传输或存储为二进制文件。
headers={'Content-Type': 'image/jpeg'},
timeout=2
)
4.客户端使用post请求发送编码后的数据
5.服务器接收编码后的byte类型数据。并将其以uint8的方式解码
nparr = np.frombuffer(request.body, np.uint8)
6.服务器将解码后的numpy数组转为OpenCV的图片
latest_frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
7.将图片构建成一个符合 MJPEG流协议 的HTTP响应块(迭代器),用于实现浏览器实时视频流传输。(详细见下面的“补充”)
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + jpeg.tobytes() + b'\r\n\r\n')
8.将该迭代器加上流协议返回给客户端
return StreamingHttpResponse(
generate(),
content_type='multipart/x-mixed-replace; boundary=frame'
)
9.客户端前端通过img标签获取该视频流(注:此处为客户端向服务端请求数据,为GET方法)
1.需要用到的函数
import cv2
from django.http import StreamingHttpResponse #Django 专门用于流式传输的响应类
2.视频流生成器函数views.py
def gen_display(camera):
#迭代器生成函数
while True:
# 从摄像头读取一帧
ret, frame = camera.read()
if ret:
# 将帧编码为 JPEG 格式
ret, frame = cv2.imencode(".jpeg", frame)
if ret:
# 生成符合 HTTP 流式传输格式的数据
#构建一个符合 MJPEG(Motion JPEG)流协议 的HTTP响应块,用于实现浏览器实时视频流传输。
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame.tobytes() + b'\r\n')
- camera.read()
- 从摄像头(
cv2.VideoCapture(0))读取一帧图像 ret:是否读取成功(True/False)frame:图像的NumPy数组(BGR格式)
- 从摄像头(
- cv2.imencode(".jpeg",frame)
- 将帧压缩为JPEG格式(节省带宽)
- 返回
(ret,encoded_frame),ret表示是否编码成功
yield生成HTTP流响应- 格式必须符合
multipart/x-mixed-replace协议(用于实时更新内容) - 每一帧的数据结构:
--frame\r\n Content-Type: image/jpeg\r\n\r\n <JPEG 数据>\r\n- 格式必须符合
3.video(request)视图函数
def video(request):
"""
视频流路由。将其放入img标记的src属性中。
例如:<img src='https://ip:port/uri' >
"""
# 视频流相机对象
camera = cv2.VideoCapture(0)
# 使用流传输传输视频流
return StreamingHttpResponse(gen_display(camera), content_type='multipart/x-mixed-replace; boundary=frame')
- cv2.VideoCapture(0)
- 初始化摄像头,获取摄像头信息
- StreamingHttpResponse
- Django 专门用于流式传输的响应类。
content_type='multipart/x-mixed-replace; boundary=frame':multipart/x-mixed-replace:告诉浏览器后续内容会替换前一部分(用于实时视频)。boundary=frame:分隔符(必须与生成器中的 --frame 一致)。
4. StreamingHttpResponse 详解
简介
StreamingHttpResponse是Django中的一个HttpResponse子类,用于实时响应大文件或大量数据给客户端。与常规的HttpResponse不同,StreamingHttpResponse并不会将数据全部加载到内存中,而是采用流式传输的方式逐块发送。这对于处理大文件或大数据量的情况非常有用,可以减小服务器内存的消耗,并提高处理速度。
StreamingHttpResponse的使用方法与常规的HttpResponse类似,只需将生成数据的逻辑写入一个生成器函数,并将该函数作为StreamingHttpResponse的参数传入即可。在每次迭代时,StreamingHttpResponse都会将生成器函数的返回值作为响应内容的一部分发送给客户端,直到生成器结束。
a. 主要参数
| 参数 | 类型 | 说明 |
|---|---|---|
streaming_content |
迭代器(Generator) | 必填。生成字节数据(bytes)的迭代器,例如生成器函数。 |
content_type |
str |
可选。响应MIME类型,默认'text/html'。视频流常用:'multipart/x-mixed-replace; boundary=frame' |
status |
int |
可选。HTTP状态码,默认200。 |
reason |
str |
可选。HTTP状态短语(如'OK'),通常自动生成。 |
charset |
str |
可选。编码格式(如'utf-8'),默认从content_type解析。 |
headers |
dict |
可选。自定义响应头(如{'X-Custom-Header': 'Value'})。 |
b. 返回对象特性
- 惰性传输:数据按需生成(通过迭代器逐块发送),不会一次性加载到内存
- 适用于:视频流、大文件下载、实时传感器数据等流式场景
- 不可逆性:一旦开始传输,不能修改响应内容
c. 关键方法
| 方法 | 说明 |
|---|---|
set_cookie() |
设置Cookie |
delete_cookie() |
删除Cookie |
close() |
手动关闭响应流(释放资源) |
d. 常见问题
Q:StreamingHttpResponse 和 HttpResponse 的区别?
| 特性 | StreamingHttpResponse |
HttpResponse |
|---|---|---|
| 内存占用 | 低(逐块传输) | 高(一次性加载) |
| 适用场景 | 大文件、实时流 | 小文件、静态内容 |
| 可修改性 | 传输开始后不可修改 | 可随时修改 |
Q:为什么视频流需要 multipart/x-mixed-replace?
- 该协议允许服务器推送新数据替换旧数据
- 实现实时更新效果(如视频帧连续替换)
- 现代浏览器普遍支持此协议
5.补充:可以设置摄像头帧率来减少初始化的时间
# 可以预先初始化摄像头并保持打开状态
camera = cv2.VideoCapture(0)
camera.set(cv2.CAP_PROP_FRAME_WIDTH, 640) # 设置分辨率
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
camera.set(cv2.CAP_PROP_FPS, 30) # 设置帧率
6.补充:在 HTTP 通信中,客户端用 tobytes() 准备数据,服务端用 np.frombuffer() 还原数据,形成完整的二进制流处理链路。
#客户端
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
frame = cv2.resize(frame, (640, 480))
_, img_encoded = cv2.imencode('.jpg', frame)#img_encoded为NumPy 数组,数据类型为 uint8
try:
response = requests.post(
url,
data=img_encoded.tobytes(),#jpeg_data.tobytes() 的作用是将 NumPy 数组(jpeg_data)转换为 原始的二进制字节流(bytes),以便通过网络传输或存储为二进制文件。
headers={'Content-Type': 'image/jpeg'},
timeout=2
)
except Exception as e:
print(f"发送错误: {e}")
time.sleep(0.033) # 约30fps
#服务器端
def receive_stream(request):
global latest_frame
if request.method == 'POST':
# 处理上传的视频帧
try:
nparr = np.frombuffer(request.body, np.uint8)#因为在编码的时候,数据类型默认为uint8,所以解码的时候也应该用uint8
latest_frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
return HttpResponse(status=200)
except Exception as e:
print(f"处理帧错误: {e}")
return HttpResponse(status=500)
elif request.method == 'GET':
# 提供视频流
def generate():
global latest_frame
while True:
if latest_frame is not None:
_, jpeg = cv2.imencode('.jpg', latest_frame)
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + jpeg.tobytes() + b'\r\n\r\n')
else:
# 如果没有帧可用,可以发送空白图像或等待
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + np.zeros((480, 640, 3), np.uint8).tobytes() + b'\r\n\r\n')
return StreamingHttpResponse(
generate(),
content_type='multipart/x-mixed-replace; boundary=frame'
)
return HttpResponse(status=405)
7.补充:MJPEG流协议
MJPEG 流通过 multipart/x-mixed-replace 内容类型实现。
yield (
b'--frame\r\n' # 分块边界标识
b'Content-Type: image/jpeg\r\n\r\n' # 声明本块内容类型
+ jpeg.tobytes() # 图像二进制数据
+ b'\r\n\r\n' # 块结束标记
)
注:“图像二进制数据”部分一定要经过cv2.imdecode的解码验证。(解码为OpenCV格式)。直接转发未经验证的二进制数据可能导致前端崩溃或安全漏洞!
8.补充:详解(版本2)
StreamingResponse 通常在 Web 框架中(如 FastAPI, Starlette 等)使用,它允许你将 HTTP 响应体作为数据流逐步发送给客户端,而不是一次性加载整个响应内容到内存中再发送。这对于处理大型文件、实时数据流或者需要长时间生成的响应非常有用,可以显著降低服务器内存消耗并提升用户体验(客户端可以更快地接收到第一部分数据)。
a.核心概念
-
迭代器/生成器 (Iterator/Generator): StreamingResponse 的核心是接收一个迭代器或生成器作为内容源。这个迭代器/生成器会逐块 (chunk) 产生数据。
-
异步支持 (Asynchronous Support): 现代 Web 框架通常支持异步迭代器/生成器,这使得在生成数据时可以执行非阻塞的 I/O 操作(例如从数据库或外部 API 读取数据)。
-
Content-Type: 你需要正确设置响应的 media_type(通常对应 HTTP 的 Content-Type 头部),以便客户端能够正确解析数据流。
-
Headers: 你可以自定义响应头,例如 Content-Disposition 来建议浏览器如何处理文件(如作为附件下载)。
b.使用场景
- 下载大文件: 从服务器向客户端流式传输大文件,避免将整个文件读入内存。
- 实时数据流: 例如,发送服务器发送事件 (SSE - Server-Sent Events),或者流式传输日志、监控数据等。
- 动态生成内容: 当响应内容需要逐步计算或多个来源组合时,可以边生成边发送。
- 与外部服务交互: 从另一个服务流式获取数据,并将其直接流式传输给客户端。
c.使用方法
1.必要的导入
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import
asyncio # 用于异步操作
import time # 用于演示
2. 同步生成器流 (Synchronous Generator Streaming)
这种方式下,生成数据的过程是同步阻塞的。
app = FastAPI()
def fake_data_generator_sync():
for i in range(10):
yield f"Data chunk {i}\n"
time.sleep(0.5) # 模拟数据生成或IO操作的耗时
@app.get("/stream-sync/")
async def stream_sync_data():
return StreamingResponse(fake_data_generator_sync(), media_type="text/plain")
3. 异步生成器流 (Asynchronous Generator Streaming)
这是更推荐的方式,尤其是在数据生成涉及 I/O 密集型操作时,因为它不会阻塞服务器的主事件循环。
app = FastAPI() # 假设 app 实例已创建
async def fake_data_generator_async():
for i in range(10):
yield f"Async data chunk {i}\n"
await asyncio.sleep(0.5) # 模拟异步IO操作
@app.get("/stream-async/")
async def stream_async_data():
return StreamingResponse(fake_data_generator_async(), media_type="text/plain")
解释
-
fake_data_generator_async(): 这是一个异步生成器函数(使用async def和yield)。 -
await asyncio.sleep(0.5): 模拟一个非阻塞的等待操作。在实际应用中,这里可能是await database.fetch_row()或await http_client.get_chunk()等。
这种方式下,服务器在等待 asyncio.sleep 时可以处理其他请求,提高了并发性能。
4. 流式传输文件
你可以流式传输文件内容,而不是先将整个文件读入内存。
# (接上文 app = FastAPI() )
async def iterate_file(file_path: str, chunk_size: int = 8192):
with open(file_path, mode="rb") as file_like: # 以二进制模式读取
while chunk := file_like.read(chunk_size):
yield chunk
@app.get("/stream-file/")
async def stream_file_example():
file_path = "large_file.zip" # 假设你有一个名为 large_file.zip 的文件
# 确保文件存在,否则会出错
# import os
# if not os.path.exists(file_path):
# return {"error": "File not found"}, 404
return StreamingResponse(
iterate_file(file_path),
media_type="application/octet-stream", # 通用二进制类型
headers={"Content-Disposition": f"attachment; filename={file_path}"}
)
@app.get("/stream-text-file/")
async def stream_text_file_example():
file_path = "my_document.txt" # 假设你有一个文本文件
async def text_file_iterator(file_path: str):
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
yield line # 逐行发送
return StreamingResponse(
text_file_iterator(file_path),
media_type="text/plain; charset=utf-8"
)
解释
iterate_file(): 这个异步函数(或者也可以是同步函数)打开文件并以块的形式读取和yield数据。"rb"表示以二进制只读模式打开文件。media_type="application/octet-stream": 这是一个通用的 MIME 类型,表示任意的二进制数据流。headers="Content-Disposition": {"attachment; filename=(file_path)"}: 这个头部会提示浏览器将响应作为附件下载,并建议文件名为large_file.zip。- 对于文本文件,可以逐行读取并发送,并指定
charset。
5. StreamingResponse 的参数
StreamingResponse 的构造函数通常接受以下主要参数:
content: 一个迭代器 (iterator)、生成器 (generator)、异步迭代器 (async iterator) 或异步生成器 (async generator)。这是必须的。status_code(int, optional): HTTP 状态码,默认为200。headers(dict, optional): 一个包含自定义 HTTP 响应头的字典。media_type(str, optional): 响应的媒体类型 (MIME type),例如"text/plain"、"application/json"、"image/png"。background(BackgroundTask, optional): 一个后台任务,会在响应发送完毕后执行。
6. 与后台任务结合 (Background Tasks)
有时你可能想在流式响应发送完毕后执行一些清理操作。
from fastapi import BackgroundTasks
# (接上文 app = FastAPI() )
async def generate_report_and_cleanup(background_tasks: BackgroundTasks):
temp_file_path = "temp_report.txt"
with open(temp_file_path, "w") as f:
for i in range(5):
line = f"Report line {i}\n"
f.write(line)
yield line # 同时 yield 给 StreamingResponse
await asyncio.sleep(0.2)
def cleanup_temp_file(path: str):
import os
print(f"Cleaning up temporary file: {path}")
try:
os.remove(path)
print(f"Successfully removed {path}")
except OSError as e:
print(f"Error removing file {path}: {e}")
background_tasks.add_task(cleanup_temp_file, temp_file_path)
@app.get("/stream-with-cleanup/")
async def stream_with_cleanup(background_tasks: BackgroundTasks):
return StreamingResponse(
generate_report_and_cleanup(background_tasks),
media_type="text/plain"
)
解释
generate_report_and_cleanup函数现在也负责将清理任务添加到background_tasks。background_tasks.add_task(cleanup_temp_file, temp_file_path): 注册一个任务,在响应发送完成后调用cleanup_temp_file函数。
7.服务器发送事件 (Server-Sent Events - SSE)(重点)
SSE 是一种允许服务器向客户端单向推送数据的技术,StreamingResponse 非常适合实现它。
# (接上文 app = FastAPI() )
import datetime
async def sse_generator():
count = 0
while True:
# 实际应用中,这里可能会检查是否有新数据或事件
await asyncio.sleep(1) # 每秒发送一次事件
count += 1
timestamp = datetime.datetime.now().isoformat()
# SSE 事件格式: "data: your_data_here\n\n"
# 你也可以包含 id 和 event 字段
yield f"id: {count}\nevent: message\ndata: Current time: {timestamp}\n\n"
# 如果连接断开,生成器应该优雅地退出循环
# (在FastAPI/Starlette中,如果客户端断开连接,框架通常会处理生成器的取消)
@app.get("/sse/")
async def server_sent_events():
return StreamingResponse(sse_generator(), media_type="text/event-stream")
解释
media_type="text/event-stream": 这是 SSE 的标准 MIME 类型。- SSE 数据有特定的格式,通常包含
data:,id:,event:,retry:字段。每条消息以双换行符\n\n结束。 - 客户端可以使用 JavaScript 的
EventSource API来接收这些事件。
<!DOCTYPE html>
<html>
<head>
<title>SSE Client</title>
</head>
<body>
<h1>Server-Sent Events</h1>
<div id="sse-data"></div>
<script>
const sseDataElement = document.getElementById('sse-data');
const eventSource = new EventSource("/sse/"); // 连接到SSE端点
eventSource.onmessage = function(event) {
const newElement = document.createElement("p");
newElement.textContent = "Message: " + event.data;
sseDataElement.appendChild(newElement);
console.log("Received SSE:", event);
};
eventSource.onerror = function(err) {
console.error("EventSource failed:", err);
sseDataElement.innerHTML = "<p>Connection error or stream ended.</p>";
eventSource.close(); // 出错时关闭连接
};
// 你也可以监听自定义事件 (如果服务器发送了 event: your_event_name)
// eventSource.addEventListener("your_event_name", function(event) {
// console.log("Custom event data:", event.data);
// });
</script>
</body>
</html>
自己的示例:
<script>
const eventSource = new EventSource('/data-stream');
eventSource.addEventListener('message', (event) => {
data = JSON.parse(event.data);//解包json数据
// console.log('收到信息:', data);
});
eventSource.addEventListener('error', (event) => {
if (event.eventPhase === EventSource.CLOSED) {
console.log('Connection was closed.');
} else {
console.error('An error occurred:', event);
}
});
//研究一下下面为啥报错
// const eventSource = new EventSource("/data-stream");
// eventSource.onmessage = (event) => {
// const data = JSON.parse(event.data); // 解析 JSON
// console.log(data.message, data.status);
// };
</script>
9.补充:Http响应头content-type内容详解
Content-type:用来指定不同格式的请求响应信息,俗称MIME媒体类型
content-type常见的取值:
- text/html:HTML格式
- text/plain:纯文本格式
- text/xml:XML格式
- image/gif:gif图片格式
- image/jpeg:jpg图片格式
- image/png:png图片格式
- application/json:JSON数据格式
- application/pdf:pdf格式
- application/octet-stream:二级制流数据,一般是文件下载
- application/x-www-form-urlencoded:form表单默认的提交数据的格式,会编码成key=value格式
- multipart/form-data:表单中需要上传文件的文件格式类型
原文链接:https://blog.csdn.net/Attsky/article/details/126548476
十、临时禁用csrf
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt # 临时禁用 CSRF 验证(仅用于测试或内部 API)
十一、设置URL在Django模板中加载iframe
1.引用iframe模板并配置url
views.py
@xframe_options_exempt #跨域请求保护
def out2(request):
return render(request,"myapp/program/outcome/out2.html")
urls.py
path('outcome',views.out2,name="outcome"),
camara.html
<iframe src="{% url 'outcome' %}"></iframe>
正常使用iframe出现访问被拒绝的原因
a.主要原因
-
X-Frame-Options响应头:
-
Django默认设置了X-Frame-Options: SAMEORIGIN头部
-
这会阻止页面被嵌入到其他域名的iframe中
-
-
内容安全策略(CSP):
-
如果配置了CSP(Content Security Policy)并限制了frame-ancestors
-
这会限制哪些页面可以嵌入当前页面
-
-
同源策略(Same-Origin Policy):
- 浏览器安全策略限制跨域iframe访问
b.解决方案
1. 禁用X-Frame-Options(不推荐)
# settings.py
MIDDLEWARE = [
# ...
# 移除或注释掉'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
2. 按视图控制X-Frame-Options(推荐)
from django.http import HttpResponse
from django.views.decorators.clickjacking import xframe_options_exempt
@xframe_options_exempt
def my_view(request):
return render(request,"myapp\outcome.html")
3. 修改X-Frame-Options为允许特定域名
# settings.py
X_FRAME_OPTIONS = 'ALLOW-FROM https://example.com/'
4. 使用CSP控制frame-ancestors(现代方式)
# settings.py
CSP_DEFAULT_SRC = ("'self'",)
CSP_FRAME_ANCESTORS = ("'self'", "https://example.com")
c.为什么直接访问可以工作?
-
直接通过服务器地址+服务地址访问时,是同源请求,不受X-Frame-Options限制
-
当尝试在其他页面通过iframe嵌入时,就变成了跨域请求,触发了安全限制
-
这些安全机制是为了防止点击劫持(clickjacking)攻击,修改前应充分评估安全风险。
十二、Python生成器、迭代器与yield语句
1.Python生成器
a.生成器是什么?
生成器是 Python 中一种用于创建迭代器的简洁方式。它是一个特殊的函数,可以在执行过程中暂停并返回一个值,每次通过 yield 关键字逐步生成数据,而不是一次性返回所有的数据。生成器并不存储所有的值,而是按需生成它们,这使得生成器非常适合处理大规模数据。
生成器和普通函数的区别:
- 普通函数使用
return语句返回结果,一旦执行到return,函数结束,返回结果。 - 生成器使用
yield语句,每次调用时生成一个值,并暂停在yield处,直到下一次调用。
b.生成器的作用是什么?
生成器的主要作用是创建一个 惰性求值的迭代器,它按需计算值,而不是一次性加载所有的元素。这使得它在处理大规模数据、流式数据或无限序列时非常高效。
-
生成器的出现是为了应对大规模数据的处理需求。以下是生成器的几个优势,说明为什么需要生成器:
- 节省内存:
当处理大量数据时,如果将所有数据一次性加载到内存中,可能会导致内存溢出。生成器只在需要时生成数据,因此能有效减少内存的使用。 - 提高性能:
在数据量巨大的情况下,如果一次性计算并存储所有数据,可能会浪费很多计算资源。生成器按需计算数据,避免了不必要的计算,提高了性能。 - 处理无限序列:
生成器可以创建无限序列,比如 Fibonacci 数列、自然数序列等,而不需要事先生成所有的值。普通方法会因为数据量太大而无法存储,而生成器能够动态生成数据并按需返回。 - 处理数据流:
生成器每次只返回一个值,直到调用 next(),使得我们可以按需处理数据流。
- 节省内存:
c.生成器的语法
生成器的语法非常简单,通常有两种方式来创建生成器:
1. 使用函数和 yield 关键字
def my_generator():
yield 1
yield 2
yield 3
函数:my_generator 是一个生成器函数,每次执行到 yield 语句时,它会返回一个值,并暂停函数的执行,直到下一次调用 next() 时继续。
yield:用于暂停函数并返回值,每次生成一个新的值,直到生成器函数结束。执行到此时函数暂停,但并不终止执行
2. 使用生成器表达式
生成器表达式类似于列表推导式,但它使用圆括号 () 来创建生成器,而不是方括号 []。
gen = (x * x for x in range(5))
gen是一个生成器对象,它会生成 0, 1, 4, 9, 16(即 0 到 4 的平方)。生成器表达式是一种简洁的创建生成器的方式。
3. 使用 next() 和 for 循环获取生成器值
生成器返回的对象是一个迭代器,可以通过 next() 或 for 循环来获取生成器中的值。
gen = my_generator()
# 使用 next() 获取生成器中的下一个值
print(next(gen)) # 输出: 1
print(next(gen)) # 输出: 2
print(next(gen)) # 输出: 3
# 使用 for 循环迭代生成器
for value in my_generator():
print(value)
在这个例子中:
next(gen)每次调用时,生成器返回一个新的值。for循环则自动迭代生成器,直到没有值为止。
4.生成器的使用场景
生成器是一种惰性计算(Lazy Evaluation)的数据生成方式,适用于需要节省内存、提高性能、按需计算的场景。以下是一些典型的使用场景:
示例:
逐行读取大文件
场景:当数据量过大,无法一次性存入内存时,使用生成器按需获取数据,减少内存消耗。
def read_large_file(file_path):
with open(file_path, "r", encoding="utf-8") as file:
for line in file:
yield line.strip() # 每次返回一行数据
# 使用生成器逐行读取大文件
for line in read_large_file("large_text.txt"):
print(line)
生成无限序列
场景:当数据的大小是无限的,比如生成自然数、斐波那契数列时,列表无法存储无限数据,使用生成器动态计算值。
def infinite_numbers():
num = 0
while True:
yield num
num += 1
# 生成一个无限的自然数序列
gen = infinite_numbers()
for _ in range(5):
print(next(gen)) # 输出 0, 1, 2, 3, 4
数据流式处理
场景:处理流式数据,如从网络 API 或数据库按需获取数据,而不是一次性全部加载。
import time
def fetch_data():
for i in range(5):
time.sleep(1) # 模拟网络延迟
yield f"Data {i}" # 模拟从 API 获取数据
# 模拟流式数据处理
for data in fetch_data():
print(data)
多任务调度(协程)
场景:生成器可以作为轻量级协程,进行非阻塞的任务调度。
def task1():
for i in range(3):
print(f"Task 1 - Step {i}")
yield
def task2():
for i in range(3):
print(f"Task 2 - Step {i}")
yield
# 多任务执行
t1 = task1()
t2 = task2()
for _ in range(3):
next(t1)
next(t2)
总结
| 使用场景 | 代码示例 | 优势 |
|---|---|---|
| 读取大文件 | yield 逐行读取 |
节省内存 |
| 无限序列 | while True: yield |
无限数据生成 |
| 流式数据处理 | yield 逐步返回数据 |
适用于网络爬虫、API |
| 任务调度 | yield 切换任务 |
模拟协程,非阻塞执行 |
| 批量数据处理 | yield batch 批量返回数据 |
适用于分页、数据库查询 |
| 数学计算 | 斐波那契、素数生成 | 节省存储,提高计算效率 |
| 并行计算 | yield 作为任务队列 |
减少线程通信开销 |
2.Python迭代器
a.Python迭代器是什么?
迭代器(Iterator)是 Python 中一种允许对象逐个返回元素的数据结构。它是实现了 __iter__() 和 __next__() 方法的对象,使得它可以被 for 循环等迭代工具使用。
迭代器的特点:
- 逐个返回元素:每次调用
next()只返回一个元素,而不会一次性加载所有数据。 - 节省内存:不同于列表等数据结构,它不会一次性存储所有数据,而是按需生成数据。
- 不可逆:迭代器只能向前遍历,不能后退。
- 延迟计算:只有当
next()被调用时,数据才会被计算或提取。
在 Python 中,支持 for 循环的对象,如列表、元组、字典等,都是可迭代对象(Iterable),但它们本身不是迭代器。只有实现了 __iter__() 和 __next__() 方法的对象才是迭代器。
b.Python 迭代器语法
1. 内置迭代器
Python 中许多内置对象(如 list, tuple, str, dict, set)都可以使用 iter() 方法转换为迭代器。
# 创建一个可迭代对象(列表)
numbers = [1, 2, 3, 4]
# 将其转换为迭代器
iterator = iter(numbers)
# 逐个获取元素
print(next(iterator)) # 输出: 1
print(next(iterator)) # 输出: 2
print(next(iterator)) # 输出: 3
print(next(iterator)) # 输出: 4
# print(next(iterator)) # 超出范围会报错 StopIteration
iter(numbers)返回一个迭代器对象。next(iterator)逐个获取元素,直到StopIteration异常。
2. 自定义迭代器
用户可以通过创建一个类,并实现 __iter__() 和 __next__() 方法,来自定义迭代器。
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self # 迭代器本身也是一个可迭代对象
def __next__(self):
if self.current >= self.end:
raise StopIteration # 迭代结束
self.current += 1
return self.current - 1
# 创建一个迭代器对象
my_iter = MyIterator(1, 5)
# 使用 for 循环自动调用 __next__()
for num in my_iter:
print(num) # 输出: 1, 2, 3, 4
__iter__()方法返回迭代器自身。__next__()方法返回当前值,并使索引前进,当超出范围时抛出StopIteration异常。
c.Python 迭代器使用场景?
Python 迭代器适用于高效迭代、数据流处理、节省内存、数据转换等场景。它允许我们按需获取数据,而不是一次性加载所有数据,在处理大规模数据时尤为重要。
3.Python 生成器和迭代器
a.生成器和迭代器的联系
生成器本质上就是一种特殊的迭代器,它们都具备以下特点:
1. 都可以用于 for 循环遍历。
2. 都遵循迭代器协议(实现 __iter__() 和 __next__() 方法)。
3. 都不会一次性将所有数据加载到内存,而是按需返回数据(惰性求值)。
4. 都能提高性能,适用于处理大规模数据流。
| 特性 | 生成器 (Generator) | 迭代器 (Iterator) |
|---|---|---|
| 创建方式 | 使用 yield 关键字的函数或 生成器表达式 |
需要手动实现 __iter__() 和 __next__() |
| 状态管理 | 自动保存迭代状态,下一次 next() 继续执行 |
需要手动维护 self.current 等状态 |
| 代码简洁性 | 代码简洁,只需 yield |
需要实现完整的类和方法 |
| 返回方式 | 通过 yield 逐步返回值 |
通过 return 结束,或 StopIteration 抛出异常 |
| 适用场景 | 适用于流式数据处理、无限序列、懒加载数据 | 适用于自定义可迭代对象(如文件解析、数据分页) |
| 调用方式 | 直接调用函数返回生成器对象,然后用 next() 获取值 |
先实例化对象,再用 next() 获取值 |
b.什么时候用生成器 vs. 迭代器?
- 如果你的目标是快速实现一个简单的迭代逻辑, 用 生成器(代码更简洁)。
- 如果你需要自定义更复杂的迭代规则, 例如有多个状态需要维护,或者你需要实现类似 rewind() 的功能,那么就应该使用 迭代器。
c.区别与联系
- 联系:生成器是迭代器的一种特殊形式,遵循迭代器协议。
- 区别:
- 迭代器 需要实现
__iter__()和__next__()方法,手动管理状态。 - 生成器 只需要
yield语句,Python 自动管理状态,更简洁。
选择: - 简单任务:使用 生成器(如读取文件、流式处理数据)。
- 复杂任务:使用 迭代器(如自定义数据结构的遍历)。
- 迭代器 需要实现
原文链接:https://blog.csdn.net/weixin_44705554/article/details/145443040
十三、Eventsource、websocket与socket.io 三者的差异和优缺点
1.EventSource
EventSource 是一种轻量级的 API,用于获取来自服务器的实时事件。它是 WebSockets 的替代方案,因为它比 WebSockets 更简单,更适合处理服务器向客户端发送数据的情况。使用 EventSource,只有服务器能够发送消息,所以它更安全。但是,它不支持双向通信或客户端发送消息。流式传输
-
优点:
- 简单易用,与 HTTP 协议兼容。
- 只需要一个长连接,服务器可以推送任意数量的事件。
- 适用于服务端向客户端发送频率较低的数据。
- 可以自动重连,并且在连接断开时会触发 error 和 close 事件,方便处理异常情况。
-
缺点:
- 不支持双向通信。
- 不支持二进制数据传输。
- 兼容性存在问题,不支持 IE 浏览器。
示例:
<!DOCTYPE html>
<html>
<head>
<title>SSE Client</title>
</head>
<body>
<h1>Server-Sent Events</h1>
<div id="sse-data"></div>
<script>
const sseDataElement = document.getElementById('sse-data');
const eventSource = new EventSource("/sse/"); // 连接到SSE端点
eventSource.onmessage = function(event) {
const newElement = document.createElement("p");
newElement.textContent = "Message: " + event.data;
sseDataElement.appendChild(newElement);
console.log("Received SSE:", event);
};
eventSource.onerror = function(err) {
console.error("EventSource failed:", err);
sseDataElement.innerHTML = "<p>Connection error or stream ended.</p>";
eventSource.close(); // 出错时关闭连接
};
// 你也可以监听自定义事件 (如果服务器发送了 event: your_event_name)
// eventSource.addEventListener("your_event_name", function(event) {
// console.log("Custom event data:", event.data);
// });
</script>
</body>
</html>
1.1 遇到的问题
1.1.1 资源竞争
问题简述:当服务器往浏览器传数据时,浏览器客户端同时有两个标签页打开,且这两个标签页同时调用了该接口接收数据,就会产生资源竞争。结果就是一串连续的数据,可能编号1的数据分配给标签页1,编号2的数据分配给标签页2 。导致单个标签页接收到的数据不再是连续的
下面为当时实况代码,并没有阅读的必要:
views.py
def res(request):
def event_stream():
while True:
# 添加超时,避免永久阻塞
msg = q.get().decode("utf-8")#这里从队列获取到的是str格式
msg = json.loads(msg)#把str格式转成python字典,防止字符串格式过于自由从而在格式转换出问题
msg = json.dumps(msg)
# yield f"data: {json.dumps(msg)}\n\n"
# 从客户端发送过来的数据默认是json格式,避免对其进行二次json编码,否则会导致web客户端需要二次解码才能得到真正的数据
# 把拿到的数据原样发给web客户端
yield f"data: {msg}\n\n"#推送消息
print("已推送")
# flag = False#要改
time.sleep(0.3)
response = StreamingHttpResponse(
event_stream(),
content_type = 'text/event-stream'
)
return response
urls.py
path("output-jsonresponse",views.res,name="output_jsonresponse"),#开发板上传数据测试区
1.html(部分js代码)
// 流式传输区
const eventSource = new EventSource('/output-jsonresponse');
eventSource.addEventListener('message', (event) => {
console.log(event.data);
console.log(typeof(event.data));
data = JSON.parse(event.data);//解包json数据
console.log('收到信息:', data);
console.log("数据类型:"+typeof data)
// const keys = data.keys();
console.log("温度数据"+data["temperature"]);
// 每分钟刷新一次数据
updateData(data["temperature"]);
// 把数据传给每小时更新,让其判定是否需要更新
updateData_day(data["temperature"]);
});
eventSource.addEventListener('error', (event) => {
if (event.eventPhase === EventSource.CLOSED) {
console.log('Connection was closed.');
} else {
console.error('An error occurred:', event);
}
});
2.html
<script>
window.onload = function(){
const eventSource = new EventSource('/output-jsonresponse');
const data_display = document.getElementById("data-display");
var data_block = ``
eventSource.addEventListener('message', (event) => {
console.log(event.data)
try {
data = JSON.parse(event.data);//解包json数据
data_block+=`
<li>收到信息: ${JSON.stringify(data)}</li>
`
data_display.innerHTML=data_block;
} catch (error) {
data = event.data;
data_block+=`
<li>收到信息: ${data}</li>
`
data_display.innerHTML=data_block;
}
console.log(data);
console.log(typeof(data));
});
eventSource.addEventListener('error', (event) => {
if (event.eventPhase === EventSource.CLOSED) {
console.log('Connection was closed.');
} else {
console.error('An error occurred:', event);
}
});
const button1 = document.getElementById("button1");
button1.onclick = function(e){
data_block=``;
data_display.innerHTML="";
}
}
</script>
2.WebSocket
WebSocket 是一种在单个 TCP 连接上提供全双工通信的协议,它使得客户端和服务器之间进行实时交互变得更加容易。它是一种标准化的通信协议,客户端和服务器都可以通过它发送消息。
-
优点:
- 支持双向通信,客户端和服务端都可以发送和接收消息;
- 可以发送二进制数据,支持大文件传输;
- 协议比较轻量级,能够节省网络带宽和服务器资源;
- 兼容性较好,大部分现代浏览器都支持 WebSocket。
-
缺点:
- 需要在服务端实现 WebSocket 协议的支持;
- 相对于 HTTP 请求来说,WebSocket 连接需要占用更多的服务端资源;
- 安全性问题:需要注意防止 CSRF 和 XSS 攻击,避免恶意用户利用 WebSocket 劫持会话或注入脚本等。
- 配置麻烦
示例:
const webSocket = new WebSocket('ws://localhost:8080');
webSocket.addEventListener('open', (event) => {
console.log('WebSocket connection established!');
});
webSocket.addEventListener('message', (event) => {
console.log('Received message data:', event.data);
});
webSocket.addEventListener('close', (event) => {
console.log('WebSocket connection closed!');
});
webSocket.addEventListener('error', (event) => {
console.error('An error occurred:', event);
});
// 发送消息到服务器
webSocket.send('Hello, server!');
3.Socket.IO
Socket.IO 是一个库,它封装了 WebSocket 和其他实时通信协议,并提供了一组易于使用的 API。它既可以在客户端上使用,也可以在服务器端上使用,它还提供了许多高级功能,例如自动重连、心跳机制和房间等概念。
-
优点:
- 支持双向通信。
- 支持广播和房间功能,使得开发者可以轻松地实现实时应用程序。
- 自带多种传输方式,如 WebSocket、HTTP 长轮询、JSONP 等,可以根据浏览器或设备的不同选择最佳传输方式。
-
缺点:
- 使用 Socket.IO 的应用程序需要使用 Socket.IO 作为通信层,不能在应用程序中集成原生 WebSocket 或 EventSource。
- 对比 EventSource 和 WebSocket,Socket.IO 相对来说更加庞大,需要引入相应的客户端库和服务器端插件,如果应用程序只需要简单的实时- 通信,使用 EventSource 或 WebSocket 可能更加适合
示例:
// 客户端代码
const socket = io('http://localhost:3000');
socket.on('connect', () => {
console.log('Socket.io connection established!');
});
socket.on('message', (data) => {
console.log('Received message data:', data);
});
socket.on('disconnect', () => {
console.log('Socket.io connection closed!');
});
socket.emit('message', 'Hello, server!');
// 服务端代码
const io = require('socket.io')(3000);
io.on('connection', (socket) => {
console.log('A new client is connected!');
socket.on('message', (data) => {
console.log('Received message data:', data);
socket.emit('message', `You said: ${data}`);
});
socket.on('disconnect', () => {
console.log('Client disconnected!');
});
});
原文链接:https://blog.csdn.net/weixin_42508580/article/details/130931268
十四、不跳转页面的返回
1.在你不需要返回任何数据的情况下,你可以直接返回状态码
状态码为200时仍会跳转,但204不会
HttpResponse(status=204)
204 No Content 表示服务器成功处理了请求,但不需要返回任何实体内容
2.更多详细请查看十七
十五、文件下载
1.使用FileResponse进行文件的返回
后端部分
def do_filedownload(request,filename):
filename = unquote(filename)
print(filename)
file = open(f"./static/upload_files/{filename}","rb")
response = FileResponse(file)
#告诉浏览器响应的数据是二进制类型。否则就会出现点击链接打开文件而不是下载文件的效果
response['Content-Type'] = 'application/octet-stream'
# 告诉浏览器:这是个需要下载的附件,而不是直接在浏览器里打开。attachment为强制下载,filename是文件名。
fileResponse['Content-Disposition'] = f'attachment; filename="{filename}"'
return response
前端部分
async download_note(filename){
// 将文件名附在url上方便传递(文件名不可信)
const targetURL = document.getElementById("download_note").dataset.downloadUrl + `?filename=${filename}`
const response = await fetch(targetURL);
// 如果状态码不是正常的,则提示下载失败
if (!response.ok){
alert("文件下载失败!");
return;
}
// 获取二进制数据
const blob = await response.blob();
// 生成临时的url,例如blob:http://localhost:8000/1e2f-45a7-xxxx
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename; // 指定下载的文件名
document.body.appendChild(a);
a.click();
a.remove();
// 释放之前通过 URL.createObjectURL(blob) 创建的临时内存 URL
window.URL.revokeObjectURL(url);
},
十六、js代码动态添加csrf验证
menu[i].onclick = function(){
// // 清空现有数据。因为在
// const iframeWindow = document.getElementsByClassName("outcome")[0].children[0];
// iframeWindow.contentWindow.document.getElementById('data-display').innerHTML = '';
// 提交日期数据
const form = this.closest("form");
form.action = document.getElementById("history").dataset.target;
// 添加CSRF Token
const csrfToken = getCookie('csrftoken');
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = 'csrfmiddlewaretoken';
csrfInput.value = csrfToken;
form.appendChild(csrfInput);
// 上传表单
form.submit();
}
十七、阻止浏览器默认跳转页面展示返回的数据
1.分析原因
前端代码没有阻止默认行为(如表单提交)或未正确使用 AJAX。前端代码未能正确处理返回数据
2.解决方法
- 如果是表单提交,用 e.preventDefault() 阻止默认行为。
- 使用 fetch 或 $.ajax 发送请求,并正确处理响应。
2.1 用 e.preventDefault() 阻止默认行为
在 <head> 或 <body> 标签内添加 jQuery 的 CDN 链接(放在你的 <script> 代码之前):
<!-- 放在 <head> 或 <body> 顶部 -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
// 前端代码示例(使用 jQuery)
$("#你的form标签").on("submit", function(e) {
e.preventDefault(); // 阻止表单默认提交
$.ajax({
url: document.getElementById("register").action,//你要上传的url,可以是字符串
type: "POST",
data: $(this).serialize(),
success: function(data) {
console.log("Received data:", data);
// 在这里处理返回的 JSON 数据
},
error: function(error) {
console.error("Error:", error);
}
});
});
</script>
十八、web客户端动态上传数据(非表单)(包含csrf验证)
动态上传数据(非表单形式)
// 上传数据函数,第一个参数为url接口,第二个参数为要上传的字典
async function uploadDictionary(url,dict) {
// 获取 CSRF token
const csrftoken = getCookie('csrftoken');
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken // 添加 CSRF token 到请求头
},
body: JSON.stringify(dict) // 将字典转为JSON字符串
});
const result = await response.json();
console.log('上传成功:', result);
return result;
} catch (error) {
console.error('上传失败:', error);
throw error;
}
}
// 获取 CSRF token 的函数
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// 判断 cookie 名称是否匹配
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
数据上传
const auto_data = {"auto_data":"true"};
url = this.dataset.url;
uploadDictionary(url,auto_data);
十九、跨域请求与同源请求的区分
1. 同源请求(Same-Origin Request)
-
定义:请求的URL与当前页面的URL在协议、域名、端口三者完全一致。
-
示例:
- 当前页面URL:
https://www.example.com:443/home - 同源请求URL:
https://www.example.com:443/api/data
(协议https、域名www.example.com、端口443均相同)
- 当前页面URL:
-
特点:
- 浏览器允许直接发送请求,无限制。
- 可以访问响应内容(如读取API返回的数据)。
2. 跨域请求(Cross-Origin Request)
-
定义:请求的URL与当前页面的URL在协议、域名或端口中任意一项不同。
-
示例:
- 当前页面URL:
https://www.example.com:443/home - 跨域请求URL:
http://www.example.com/api(协议不同,httpvshttps)https://api.example.com/data(域名不同,子域名不同)https://www.example.com:8080/data(端口不同,8080vs443)https://othersite.com/data(完全不同的域名)
- 当前页面URL:
-
特点:
- 浏览器默认禁止跨域请求的响应被JavaScript读取(出于安全考虑)。
- 实际请求可能已发送到服务器(需看服务器是否响应),但响应会被浏览器拦截。
- 需通过CORS(跨域资源共享)、JSONP、代理服务器等方式解决。
关键区分点
| 对比项 | 同源请求 | 跨域请求 |
|---|---|---|
| 协议 | 相同(如 https) |
不同(如 http vs https) |
| 域名 | 完全相同(包括子域名) | 不同(如 example.com vs api.example.com) |
| 端口 | 相同(如 443) |
不同(如 443 vs 8080) |
| 浏览器限制 | 无限制 | 默认限制响应访问(需CORS等机制) |
如何验证?
- 开发者工具:在浏览器Network面板中查看请求的
Response Headers,若包含Access-Control-Allow-Origin: *等CORS头,则为跨域请求。 - 控制台报错:若看到类似以下错误,则为跨域请求被拦截:
Access to XMLHttpRequest at 'https://api.example.com' from origin 'https://www.example.com' has been blocked by CORS policy.
二十、设置、获取Cookie
浏览器访问一个网站时,会将浏览器存储的所有与此网站相关的所有Cookie信息发送给服务器(可以设置很多个Cookie来保存,浏览器会一并发过来)。Django可以通过request.COOKIE来读取.
在Django中设置cookie,可以使用HttpResponse对象提供的set_cookie()方法。这个方法允许你向客户端的响应中添加一个cookie。下面是一些基本的使用示例。
1. 设置一个简单的cookie
from django.http import HttpResponse
def my_view(request):
# 设置一个名为'mycookie'的cookie,值为'myvalue',过期时间为1小时后
response = HttpResponse("Cookie已设置")
response.set_cookie('mycookie', 'myvalue', max_age=3600)
return response
2. 设置带有过期时间的cookie
你可以通过max_age参数设置cookie的过期时间,单位为秒。如果你想让cookie在浏览器关闭时过期,可以设置expires参数。
response.set_cookie('mycookie', 'myvalue', max_age=3600) # 1小时后过期
response.set_cookie('mycookie', 'myvalue', expires=some_datetime) # 特定时间后过期
关于expires属性:
from datatime import datetime,timedelta
def set_cookie(request):
response = HttpResponse('设置Cookie')
# 设置一个Cookie信息,名为num,值为1
response.set_cookie('num',1,max_age=14*24*3600)
response.set_cookie('num',1,expires=datetime.now()+timedelta(day=14 ))
3. 设置带路径的cookie
你可以通过path参数指定cookie的作用路径。
response.set_cookie('mycookie', 'myvalue', path='/mypath/')
4. 设置带域的cookie
通过domain参数可以指定cookie的作用域。
response.set_cookie('mycookie', 'myvalue', domain=".example.com")
5. 设置安全的cookie(仅通过HTTPS发送)
通过secure参数可以指定cookie是否仅通过HTTPS发送。
response.set_cookie('mycookie', 'myvalue', secure=True)
6. 设置HttpOnly的cookie(防止JavaScript访问)
通过httponly参数可以指定cookie是否仅可通过HTTP协议访问,无法通过JavaScript访问,这增加了安全性。
response.set_cookie('mycookie', 'myvalue', httponly=True)
7. 删除cookie
要删除一个cookie,可以设置其过期时间为过去的某个时间点,例如设置为当前时间之前。
from django.utils import timezone
now = timezone.now()
response.set_cookie('mycookie', '', expires=now) # 将cookie设置为空并立即过期以删除它
注意事项:
-
当设置secure=True时,你的网站必须通过HTTPS提供服务,否则浏览器会忽略这个标记。
-
当设置httponly=True时,可以防止XSS攻击,因为这样可以防止JavaScript访问这个cookie。
-
使用Django的模板系统时,也可以在模板中设置cookie,但通常不推荐这样做,最好在视图层处理。
-
确保在设置或修改cookie时处理好用户的隐私和安全问题。例如,只在必要时使用安全的和HttpOnly的标记。
-
通过上述方法,你可以灵活地在Django中设置和管理cookies。
8. 在使用set_cookie()方法时,有如下几个形参需要做出说明:
-
max_age: 设定cookie有效的时间(单位为秒),不可与expires同用
-
expires:设定cookie过期的日期时间,不可与max_age同用
-
path: 设定可携带cookies的路径前缀
-
domain: 设定可携带cookies的域名,
-
secure: 为True时表示https协议可携带cookies,为False时表示http,https协议都可携带cookies
-
httponly: 为True时表示只能通过http协议获取cookies,不可通过js获;为False时则没有此限制
-
samesite: 当跨域请求时是否携带cookies。可选值为:'lax', 'none', 'strict'。lax为默认值,none只有在secure设置为True时有效。strict表示同域名情况下可携带。lax表示同域名情况下可携带,和strict类似,但是多了几个场景可以携带例如超链接。none表示同域和跨域情况下都可携带。
9. 读取Cookie
def get_cookie(request):
cookie = request.COOKIE['key']
return HttpResponse(cookie)
二十一、设置、获取session
1. session的特点
- 存储在服务器端
session是以键值对的方式存储的session依赖于Cookie,唯一的标识码保存在sessionid Cookie中session也是有过期时间的,如果不指定,默认两周就会过期- 保存的时候是什么类型,取出来的时候就是什么类型
2. 设置session/获取session
- 设置session:
request.session['username']='smart' - 获取session
request.session['username']
3. 常用对象及方法
3.1 以键值对的格式写session
request.session['键'] = 值
注:获取不到时会报错
3.2 根据键读取值
request.session.get('键',默认值)
3.3 清除所有session,在存储中删除值部分
request.session.clear()
注:只是删除了实际数据,但没有从表中把整条数据删除,主键id和值的前缀字符还在
3.4 清除session数据,在存储中删除整条数据
request.session.flush()
3.5 删除session中的指定键及值,在存储中只删除某个键及值
del request.session['键']
3.6 设置会话的超时时间,如果没有指定过期时间,则默认为14天后
request.session.set_expiry(value)
3.7 检测session中是否有某键
if request.session.has_key("键"):
print("session中含有该键")
当用户没有session时,会返回false
关于value的取值:
| 取值 | 说明 |
|---|---|
| 整数 | 会话的 session_id cookie 在value秒后过期 |
| 0 | 会话的 session_id cookie在浏览器关闭后过期 |
| None | 会话的 session_id cookie在两周(14天)后过期 |
4.示例代码
views.py
# /set_session
def set_session(request):
# 设置session
request.session['username'] = 'smart'
request.session['age'] = 18
return HttpResponse("设置session")
# /get_session
def get_session(request):
# 获取session
username = request.session['username']
age = request.session['age']
# 这里使用str方法是因为:session是以什么格式存取的,就以什么格式拿出来,不会自动转换格式
return HttpResponse(username + ':' + str(age))
二十二、Django 中的 redirect() 方法详解
概述
redirect() 是 Django 提供的一个快捷函数,用于执行 HTTP 重定向。它会让用户的浏览器自动跳转到一个新的 URL。
核心作用
redirect() 的主要作用是控制流程和引导用户,具体应用场景包括:
- 防止表单重复提交(PRG 模式)
- 用户登录后跳转
- 操作成功后跳转(如删除、更新、购买等)
- 将旧 URL 指向新 URL(保持 SEO 和用户体验)
工作原理
redirect() 返回一个 HttpResponseRedirect 或 HttpResponsePermanentRedirect 实例,对应的 HTTP 状态码分别为:
- 302 Found(临时重定向)- 默认
- 301 Moved Permanently(永久重定向)- 当
permanent=True时
浏览器收到此类响应后,会根据响应头中的 Location 字段自动发起新的 GET 请求。
使用方法
导入方法
from django.shortcuts import redirect
1. 传递模型对象(推荐)
Django 会自动调用模型对象的 get_absolute_url() 方法。
视图示例:
def my_view(request):
article = get_object_or_404(Article, pk=1)
return redirect(article) # 自动调用 article.get_absolute_url()
模型定义示例:
from django.urls import reverse
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
def get_absolute_url(self):
return reverse('article-detail', kwargs={'pk': self.pk})
2. 传递视图名称
使用在 urls.py 中定义的视图名称(name 参数)。
示例:
def my_view(request):
return redirect('home') # 重定向到名为 'home' 的视图
# 或带参数的重定向
# return redirect('article-detail', pk=1)
3. 传递 URL 字符串
直接指定绝对或相对 URL(不推荐,缺乏灵活性)。
示例:
def my_view(request):
return redirect('https://www.example.com/') # 绝对 URL
# return redirect('/articles/') # 站内相对 URL
4. 永久重定向
使用 permanent=True 参数进行永久重定向。
示例:
def old_view(request):
return redirect('new-view-name', permanent=True)
总结对比
| 参数类型 | 描述 | 推荐度 |
|---|---|---|
| 模型对象 | 自动调用 get_absolute_url() 方法 |
★★★★★ |
| 视图名称 | 使用 reverse() 解析 URL |
★★★★☆ |
| URL 字符串 | 直接指定 URL | ★★☆☆☆ |
| 重定向类型 | 状态码 | 参数 |
|---|---|---|
| 临时重定向 | 302 Found | 默认 |
| 永久重定向 | 301 Moved Permanently | permanent=True |
最佳实践
- 优先使用传递模型对象的方式
- 其次使用视图名称的方式
- 避免硬编码 URL 字符串
- 合理使用永久重定向进行 URL 迁移
二十三、Django中修改时区(包括Log日志文件)
Log日志文件中时间显示错误的原因:Django 默认使用时区 UTC(协调世界时),而本地时间使用的是 CST(中国标准时间,即 UTC+8)
一键解决直通车:
在settings.py文件中,将
TIME_ZONE = 'UTC'
改为
TIME_ZONE = 'Asia/Shanghai'
问题原因分析
时区不一致导致的问题:
- Django 默认使用时区:
UTC(协调世界时) - 本地时间(中国):
CST(中国标准时间,UTC+8)
Django 的默认配置在 settings.py 中:
TIME_ZONE = 'UTC' # 系统使用的时区
USE_TZ = True # 启用时区支持(最佳实践)
当 USE_TZ = True 时,Django 在内部使用 UTC 时间处理所有时间数据,包括生成日志时间戳。
二十四、对于前端以JSON格式发送过来的数据,后端用request.body来获取
二十五、GET请求携带参数传递信息的方法
1. URL查询参数方式(推荐)
前端发起请求
# 使用fetch
fetch('/api/endpoint/?name=filename')
# 使用axios
axios.get('/api/endpoint/', {params: {name: 'filename'}})
# 直接在浏览器地址栏访问
http://yourdomain.com/api/endpoint/?name=filename
Django后端接收
# views.py中处理GET参数
def my_view(request):
filename = request.GET.get('name') # 安全获取,不存在返回None
filename = request.GET.get('name', 'default.txt') # 提供默认值
filename = request.GET['name'] # 直接获取,不存在会抛出KeyError
补充:可能出现的灵异事件
报错信息
Not Found: /index/download/
[02/Sep/2025 09:39:29] "GET /index/download/?filename=%E6%89%93%E6%A0%87%E7%AD%BE%E5%90%AF%E5%8A%A8%E5%91%BD%E4%BB%A4.txt HTTP/1.1" 404 5032
原因分析
在urls.py中做了如下配置
path("index/download", views.download_note, name="download_note")
这只会匹配 /index/download(没有结尾斜杠 / 的情况)。
但是你的请求是:
/index/download/?filename=abc.txt
Django 默认的 APPEND_SLASH 设置可能会自动尝试 /index/download/,而不是 /index/download,于是找不到。
解决方法:
改为
path("index/download/", views.download_note, name="download_note")
2. URL路径参数方式
URL配置
# urls.py中定义路径参数
from django.urls import path
from . import views
urlpatterns = [
path('api/endpoint/<str:name>/', views.my_view),
]
视图函数处理
# views.py中接收路径参数
def my_view(request, name):
filename = name # 直接获取参数值
# 处理业务逻辑
注意事项
- GET参数通过URL的
?后面部分传递,格式为key=value&key2=value2 - 参数值包含特殊字符时需要URL编码
- 在Django视图中使用
request.GET字典访问查询参数 - 路径参数方式更适用于RESTful API设计
- 查询参数方式更灵活,可以传递多个可选参数
示例完整代码
使用查询参数
# 前端请求
fetch('/api/files/?name=document.txt')
# Django视图
def file_view(request):
filename = request.GET.get('name')
if filename:
# 处理文件逻辑
return HttpResponse(f"文件名: {filename}")
return HttpResponse("未提供文件名")
使用路径参数
# 前端请求
fetch('/api/files/document.txt/')
# Django视图
def file_detail_view(request, name):
# 直接使用name参数
return HttpResponse(f"文件名: {name}")
二十六、灵异事件合集
1.前端使用fetch发送数据时可能遇到的灵异事件
问题描述:
前端使用fetch发送请求时,(一般是需要csrf验证时),即使加入了获取csrftoken,仍不能正常发送请求。
后端报错:
Forbidden (CSRF cookie not set.): /login/proving
[02/Sep/2025 21:58:33] "POST /login/proving HTTP/1.1" 403 2855
问题分析:
可能是Cookie 没有被设置。
- 默认情况下,Django 只有当你访问过某个带有
{% csrf_token %}的模板页面时,才会下发CSRF cookie。 - 如果你前端是 Vue 单页应用,直接用 fetch 去打后端 API,但没先请求过后端的 HTML 页面,Cookie 就不会存在。
解决方案: 在html页面中增加代码{% csrf_token %}让后端下发csrf Cookie
或在后端下发Cookie
from django.views.decorators.csrf import ensure_csrf_cookie
from django.shortcuts import render
@ensure_csrf_cookie
def login_page(request):
return render(request, "login.html")

浙公网安备 33010602011771号