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.准备工作

  1. 在MySQL数据库中创建一个mytest数据库,用作测试

  2. 创建mydemo项目(创建方法上面有)

  3. 在项目下创建myapp应用

  4. 在项目下创建templates文件夹

  5. 创建templates\myapp文件夹

  6. 更改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',
        }
    }
    
  7. 可能遇到的问题:没有安装数据库依赖

    a. 安装系统依赖

    sudo apt-get update
    sudo apt-get install python3-dev default-libmysqlclient-dev build-essential
    

    b.在Python中安装

    # 安装 mysqlclient
    pip install mysqlclient
    
  8. 可能遇到的问题:已安装mysqlclient,提示Did you install mysqlclient?

    当你看到这样的错误信息,表明Django尝试加载MySQLdb模块但未找到,因为MySQLdb已被mysqlclient替代。

    解决方法:
    先安装pymysql

    pip install pymysql
    

    下载pymysql并在你的项目(与settings.py同级)目录中的__init__.py文件中添加以下两行代码

    import pymysql 
    pymysql.install_as_MySQLdb()
    

    这两行代码会将pymysql伪装成MySQLdb,使Django能够识别并正确地使用MySQL数据库。

2.Model模型

什么是模型?

  • 模型是你的数据的唯一的、权威的信息源。它包含你所储存数据的必要字段和行为。通常,每个模型对应数据库中唯一的一张表

模型与数据库的关系

  • 模型负责业务对象和数据库的关系映射(ORM)

ORM是"对象-关系-映射"的简称,主要任务是:

  1. 根据对象的类型生成表结构
  2. 将对象、列表的操作,转化为sql语句
  3. 将sql查询到的结果转换为对象、列表

3.开发流程

  1. 在models.py中定义模型类,要求继承自models.Model
    myapp\models.py

    from 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'#指定表名。
    
  2. 把应用加入settings.py文件中的installed_app项
    mydemo\settings.py

    INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',#加入应用
    ]
    
    tips:迁移后产生许多多余的表是因为myapp上面的一堆设置没有关闭
  3. 生成迁移文件
    在项目根目录执行命令python manage.py makemigrations


    `Error:No module named 'models'`解决方案: 可能是在views.py文件中使用了`from models import History` 改为`from .models import History`
  4. 执行迁移
    在项目根目录执行命令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 }}
  • 当模板引擎遇到一个变量,将计算这个变量,然后将结果输出
  • 当模板引擎遇到点".",会按照下列顺序查询
    1. 字典查询,例如:foo["bar"]
    2. 属性或方法查询,例如:foo.bar
    3. 数字索引查询,例如: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.补充

  1. 有些时候可能会遇到想要原样输出{{ value }}而不采用Django语法的情况(忽略Django语法)
方法 语法 适用场景 输出示例
verbatim 标签 {% verbatim %}{{ 变量 }}{% endverbatim %} 需要原样输出大段包含模板语法内容时 {{ pagenum }}
templatetag 标签 {% templatetag openvariable %}变量{% templatetag closevariable %} 需要精确控制单个模板标签输出时 {{ pagenum }}

使用建议:

  1. 推荐使用 verbatim - 最简洁直观的方式
  2. 少量输出时可用 templatetag
  3. 特殊情况下使用 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:StreamingHttpResponseHttpResponse 的区别?

特性 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.使用场景

  1. 下载大文件: 从服务器向客户端流式传输大文件,避免将整个文件读入内存。
  2. 实时数据流: 例如,发送服务器发送事件 (SSE - Server-Sent Events),或者流式传输日志、监控数据等。
  3. 动态生成内容: 当响应内容需要逐步计算或多个来源组合时,可以边生成边发送。
  4. 与外部服务交互: 从另一个服务流式获取数据,并将其直接流式传输给客户端。

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 defyield)。

  • 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均相同)
  • 特点

    • 浏览器允许直接发送请求,无限制。
    • 可以访问响应内容(如读取API返回的数据)。

2. 跨域请求(Cross-Origin Request)

  • 定义:请求的URL与当前页面的URL在协议、域名或端口任意一项不同

  • 示例

    • 当前页面URL:https://www.example.com:443/home
    • 跨域请求URL:
      • http://www.example.com/api(协议不同,http vs https
      • https://api.example.com/data(域名不同,子域名不同)
      • https://www.example.com:8080/data(端口不同,8080 vs 443
      • https://othersite.com/data(完全不同的域名)
  • 特点

    • 浏览器默认禁止跨域请求的响应被JavaScript读取(出于安全考虑)。
    • 实际请求可能已发送到服务器(需看服务器是否响应),但响应会被浏览器拦截。
    • 需通过CORS(跨域资源共享)、JSONP、代理服务器等方式解决。

关键区分点

对比项 同源请求 跨域请求
协议 相同(如 https 不同(如 http vs https
域名 完全相同(包括子域名) 不同(如 example.com vs api.example.com
端口 相同(如 443 不同(如 443 vs 8080
浏览器限制 无限制 默认限制响应访问(需CORS等机制)

如何验证?

  1. 开发者工具:在浏览器Network面板中查看请求的Response Headers,若包含Access-Control-Allow-Origin: *等CORS头,则为跨域请求。
  2. 控制台报错:若看到类似以下错误,则为跨域请求被拦截:
    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()方法时,有如下几个形参需要做出说明:

  1. max_age: 设定cookie有效的时间(单位为秒),不可与expires同用

  2. expires:设定cookie过期的日期时间,不可与max_age同用

  3. path: 设定可携带cookies的路径前缀

  4. domain: 设定可携带cookies的域名,

  5. secure: 为True时表示https协议可携带cookies,为False时表示http,https协议都可携带cookies

  6. httponly: 为True时表示只能通过http协议获取cookies,不可通过js获;为False时则没有此限制

  7. 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的特点

  1. 存储在服务器端
  2. session 是以键值对的方式存储的
  3. session 依赖于Cookie,唯一的标识码保存在sessionid Cookie
  4. session 也是有过期时间的,如果不指定,默认两周就会过期
  5. 保存的时候是什么类型,取出来的时候就是什么类型

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 cookievalue秒后过期
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() 的主要作用是控制流程和引导用户,具体应用场景包括:

  1. 防止表单重复提交(PRG 模式)
  2. 用户登录后跳转
  3. 操作成功后跳转(如删除、更新、购买等)
  4. 将旧 URL 指向新 URL(保持 SEO 和用户体验)

工作原理

redirect() 返回一个 HttpResponseRedirectHttpResponsePermanentRedirect 实例,对应的 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

最佳实践

  1. 优先使用传递模型对象的方式
  2. 其次使用视图名称的方式
  3. 避免硬编码 URL 字符串
  4. 合理使用永久重定向进行 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")
posted @ 2026-07-02 18:04  畅畅c  阅读(0)  评论(0)    收藏  举报