跳转至

Django基础

add_circle2025-03-04update2025-03-05

1. 配置文件

在django中默认的核心包里面存在了一个全局默认配置文件django/conf/global_settings.py, 同时在开发者构建项目的时候, 也生成了一个全局项目配置文件在主应用目录下/settings.py文件中。

这两个配置文件,在django项目运行时, django会先加载了global_settings.py中的所有配置项到django.conf.settings对象中作为配置项存在, 接着加载主应用目录下/settings.py的配置项,所以settings.py中填写的配置项的优先级会高于global_settings.py的默认配置。

文档:https://docs.djangoproject.com/zh-hans/4.2/ref/settings/

"""
Django settings for djdemo project.

Generated by 'django-admin startproject' using Django 4.2.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

from pathlib import Path


# Build paths inside the project like this: BASE_DIR / 'subdir'.
# BASE_DIR代表了项目一个参考根路径,是当前文件的父级的父级目录路径,主要作用是提供给整个django项目进行路径拼接的。
BASE_DIR = Path(__file__).resolve().parent.parent
# 项目中一般我们会使用大写的变量来表示一个常量,所谓常量就是在开发中,用于表示一些固定数据的标记符,这种标记符,在其他语言中是基本语法来的,但是在python中并没有常量,
# 所以,就有了一些开发者声明一些大写的变量用于充当常量,常量一经定义,不能赋值。
# 因此,我们作为开发人员,就要遵守这种约定,以后如果希望项目中的一些数据不要被人修改,则可以声明成常量。
# django中的配置被强制要求一定要大写!!!否则django不识别

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# 秘钥,      用于提供给加密算法的秘钥
# 加密:      哈希串/序列串 = 加密算法(原始密码, 秘钥)
# 验证:      新哈希串 = 加密算法(原始密码, 秘钥), 新哈希串==哈希串,则表示原始密码正确
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-pz)=zkl_m9bgi!!t)ge&=2hmbg^s=!vr=sbb-nb)dr&77)f8mf'

# SECURITY WARNING: don't run with debug turned on in production!
# 在线下开发,DEBUG = True,django会基于测试服务器提供静态资源(图片,css,js)的访问,当服务端出错,会显示详尽错误信息
# 在线上运营,DEBUG = False,django不会基于测试服务器提供静态资源访问,当服务端出错,不会显示任何关于系统的错误信息,仅仅提供错误页面
DEBUG = True

#设置当前django项目允许客户端通过哪些地址访问到django项目,"*"表示服务端的任意地址
# ALLOWED_HOSTS = ["*"]
ALLOWED_HOSTS = []

# Application definition
# django注册的子应用列表[用于数据库操作,缓存,日志,admin管理]
INSTALLED_APPS = [
    'django.contrib.admin',  # admin站点的子应用
    'django.contrib.auth',   # django内置的登录认证功能
    'django.contrib.contenttypes',  # 内容类型管理
    'django.contrib.sessions',      # session功能
    'django.contrib.messages',      # 信号、消息功能的实现
    'django.contrib.staticfiles',   # 静态文件浏览服务

    'user',  # 子应用的字符串导包路径
]


# 中间件、全局钩子、拦截器
# 中间件,MIDDLEWARE,就是一个django提供给开发者用于在http请求和响应过程中,进行数据拦截的插件系统/钩子系统
# 用于进行拦截请求,或者数据格式转换,权限判断

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# django项目的默认总路由模块
ROOT_URLCONF = 'djdemo.urls'

# html模板引擎配置
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [], # 模板引擎目录
        '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',
            ],
        },
    },
]

# web应用程序的模块
WSGI_APPLICATION = 'djdemo.wsgi.application'


# Database
# https://docs.djangoproject.com/zh-hans/4.2/ref/settings/#databases
# 数据库配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
# 密码验证类
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
# 语言
LANGUAGE_CODE = 'zh-hans'
# 时区
TIME_ZONE = 'Asia/Shanghai'
# 是否开启国际化本地化功能
USE_I18N = True
# 是否启用时区转换
# USE_TZ的值为False则django会基于TIME_ZONE的时区来转换时间,否则USE_TZ的值为True,则采用基于操作系统时间来转换时间
USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
# 静态文件的访问url路径
STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
# 默认情况下,django中的数据表的主键ID的数据类型 bigint
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'


# session存储引擎核心类
SESSION_ENGINE = "django.contrib.sessions.backends.file"

# django的全局默认配置文件:django.conf.global_settings
# 保存到文件: django.contrib.sessions.backends.file
# 保存到数据库: django.contrib.sessions.backends.db    # 需要配置数据库连接
# 保存到缓存中: django.contrib.sessions.backends.cache # 需要配置缓存连接

# session存储目录[如果不设置,则默认是系统的缓存目录]
# 版本小于3.0的django 通过以下代码配置
# SESSION_FILE_PATH = os.path.join(BASE_DIR, "session")

# 版本大于或等于3.0通过以下配置
SESSION_FILE_PATH = BASE_DIR / "session_path" # 路径拼接

Django 采用了 MVT 的软件设计模式组织代码结构的,即模型(Model),视图(View)和模板(Template)。

M,Model,模型,是一个类或者函数,里面的代码就是用于完成操作数据库的。

V,View,视图,是一个类或者函数,里面的代码就是用于项目功能逻辑的,一般用于调用模型来获取数据,获取到的数据通过调用HTML模板返回给客户端。

T,Template,模板,是一个保存了特殊语法的前端html内容文件,里面的代码就是用于展示给客户端的页面效果。

image-20210530142505359

这个MVT模式并非django首创,在其他的语言里面也有类似的设计模式MVC,甚至可以说django里面的MVT事实上是借鉴了MVC模式衍生出来的。

M,Model,模型,是一个类或者函数,里面的代码就是用于完成操作数据库的。

V,View,视图,里面的代码就是用于展示给客户端的页面效果。可以是一个具有特殊语法的HTML文件,也可以是一个数据结构。

C,Controller,控制器,是一个类或者函数,里面的代码就是用于项目功能逻辑的,一般用于调用模型来获取数据,获取到的数据通过调用HTML视图文件返回给客户端。

2. 模板引擎

在工作中为了更好的展示数据给用户,所以都会使用html+css+js实现网页排版效果,但是很多开发人员并不能做到既擅长服务端开发又擅长前端开发的,当然,即便有,那这个开发人员的工资也不会低,而且同等条件下,1个人干活是怎么也比不过2个人的。所以,怎么让服务端的数据更好的展示到客户端,这就成为问题了。

模板引擎是一种可以让开发者把服务端数据填充到html网页中完成渲染效果的技术。它实现了把前端代码和服务端代码分离的作用,让项目中的业务逻辑代码和数据表现代码分离,让前端开发者和服务端开发者可以更好的完成协同开发

Django框架中内置了web开发领域非常出名的一个DjangoTemplate模板引擎(简称:DTL)。

DTL官方文档: https://docs.djangoproject.com/zh-hans/4.2/topics/templates/

要在django框架中使用模板引擎把视图中的数据更好的展示给客户端,需要完成3个步骤:

  1. 在项目配置文件settings.py中指定保存模板文件的模板目录

一般模板目录都是设置在项目根目录或者主应用目录下。

  1. 视图中基于django提供的渲染函数绑定模板文件和需要展示的数据变量

  2. 在模板目录下创建对应的模板文件,并根据模板引擎内置的模板语法,填写输出视图传递过来的数据。

2.1 配置模板目录

在当前项目根目录下创建了模板目录templates. 然后在settings.py, 模板相关配置,找到TEMPLATES配置项,填写DIRS设置模板目录。

# html模板引擎配置
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [  # 配置HTML模板文件的存储目录[目录是手动创建,如果目录不存在,则会报错]
            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',
            ],
        },
    },
]

为了方便接下里的演示内容,我这里创建创建一个新的子应用tem

python manage.py startapp tem

settings.py,注册子应用,代码:

# Application definition
# django注册的子应用列表[用于数据库操作,缓存,日志,admin管理]
INSTALLED_APPS = [
    'django.contrib.admin',  # admin站点的子应用
    'django.contrib.auth',   # django内置的登录认证功能
    'django.contrib.contenttypes',  # 内容类型管理
    'django.contrib.sessions',      # session功能
    'django.contrib.messages',      # 信号、消息功能的实现
    'django.contrib.staticfiles',   # 静态文件浏览服务

    'user',  # 子应用的字符串导包路径
    'tem',   # 开发者创建的子应用,这填写就是子应用的导包路径
]

在tem子应用目录下创建urls.py子路由文件,代码如下:

"""tem子应用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [

]

总路由加载子应用路由,urls.py,代码:

from django.contrib import admin
from django.urls import path,include
urlpatterns = [
    path('admin/', admin.site.urls),
    # path("路由前缀/", include("子应用目录名.路由模块"))
    path("book/", include("home.urls")),
    path("student/", include("demo.students.urls",namespace="stu")),
    path("users/", include("users.urls")),
    path("tem/", include("tem.urls")),
]

2.2 视图加载模板

tem/views.py,代码:

from django.shortcuts import render


def index(request):
    """显示模板"""
    # render函数实现3个功能:
    # 1. 识别查找模板目录下对应的HTML文件
    # 2. 读取HTML文件内容,替换特殊的模板语法
    # 3. 实例化response对象
    response = render(request, "index.html")
    print(response)  # <HttpResponse status_code=200, "text/html; charset=utf-8">
    print(response.content.decode())  # render读取模板文件以后,生成的HTML文档内容
    return response


def index1(request):
    """视图中传递数据到模板中显示"""
    title = "我的网页标题"
    author = "moluo"
    # 模板引擎可以把HTML模板中的特殊变量语法替换成真实数据,真实数据通过视图中的context传递进去
    # response = render(request, "index1.html",  context={"title": title,"author": author})
    response = render(request, "index1.html",  locals())
    print(response.content.decode())
    return response

templates/index.html,代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
</head>
<body>
    {# django模板中的注释内容,提供给开发者看的 #}
    作者:{{ author }}
</body>
</html>

完成了上述3个步骤以后,我们就可以把视图注册到子路由中,tem.urls.py,代码:

"""tem子应用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
    path("index", views.index),
    path("index1", views.index1),
]

在浏览器进行访问:http://127.0.0.1:8000/tem/index1/

image-20210811175247175

image-20210811175337865

2.2.1 render函数内部本质[了解]

image-20210530032914557

tem/views.py,代码:

from django.shortcuts import render
from django.template.loader import get_template
from django.http.response import HttpResponse
# Create your views here.
def index(request):
    """模板引擎的基本使用"""
    data = {}
    title = "模板引擎的基本使用"
    data["title"] = title
    # return render(request, "tem/index.html", context={"title":title})
    # return render(request, "tem/index.html", data)
    # return render(request, "index.html", data)
    # 1. 初始化模板,读取模板内容,实例化模板对象
    # 基于settings.py中配置的模板引擎获取DIRS配置的模板文件,转换成Template模板对象
    # get_template会从项目配置中找到模板目录,我们需要填写的参数就是补全模板文件的路径
    template = get_template("index.html")
    # 调用模板对象提供的render函数,把本次客户端的请求对象和视图中数据data传参到模板文件中,进行渲染
    # 将来如果要对模板中的内容进行数据缓存[cache],可以对content进行保存起来,将来如果直接读取content的话,
    # 则django就不需要操作数据库或者进行数据渲染,因为模板渲染的过程就是正则替换的过程。
    # 2. 识别context内容, 和模板内容里面的标记[标签]替换,针对复杂的内容,进行正则的替换
    # render中完成了变量替换成变量值的过程,这个过程使用了正则。
    content = template.render(data, request)
    # 3. 通过response响应对象,把替换了数据的模板内容返回给客户端
    return HttpResponse(content)

2.2.2 相关扩展

# 1. DTL模板文件与普通html文件的区别在哪里?
     DTL模板文件是一种带有特殊语法的HTML文件这个HTML文件可以被Django编译可以从视图中传递参数进去实现数据动态化在编译完成后生成一个普通的HTML文件然后发送给客户端


# 2. 开发中,我们一般把开发中的文件分2种,分别是静态文件和动态文件。
静态文件数据保存在当前文件不需要经过任何处理就可以展示出去
         普通htmlcssjs等文件图片视频音频等这一类文件叫静态文件
动态文件数据并不在当前文件而是要经过服务端或其他程序进行编译转换才可以展示出去
         编译转换的过程往往就是使用正则或其他技术把文件内部具有特殊格式的变量转换成真实数据
         动态文件一般数据会保存在第三方存储设备如数据库中
        django的视图模板文件就属于动态文件

# 3. 除了render,django还提供了一个render_to_string函数 ,可以将函数结果提供给 HttpResponse直接返回给客户端。
# 代码参考如下:
from django.http.response import HttpResponse
from django.template.loader import render_to_string
def index(request):
    # 要显示到客户端的数据
    name = "hello DTL!"
    # render_to_string 在工作中,有时候可用于缓存优化。
    tpl_content = render_to_string('index.html', {"name":name})
    return HttpResponse(tpl_content)

rendertostring 是工作中,经常用于缓存优化的工具函数,非常重要,但是少用。tem/views.py,代码:

import os.path

from django.http import HttpResponse
from django.shortcuts import render
from django.conf import settings

# Create your views here.
def index(request):
    """在视图中调用模板引擎提供的渲染函数实现前后端不分离"""
    # data = {"name": "杨戬", "age": 1500}
    # # return render(request, template_name="index.html", context=data)
    # return render(request, "index.html", data)

    # name = "杨戬"
    # age = 16
    # return render(request, "index.html", locals())

    name = "杨戬"
    age = 16

    # 判断是否有缓存页面,如果有缓存页面,就加载缓存页面
    from django.template.loader import get_template
    template_name = "index.html"
    if os.path.isfile(str(settings.BASE_DIR / "cache" / template_name)):
        with open(f"cache/{template_name}", "r") as f:
            content = f.read()
    else:
        print("走了数据库!")
        # 获取模板引擎对象
        template = get_template("index.html")
        # 使用模板引擎对象的渲染函数来对模板进行渲染
        content = template.render(locals(), request)
        with open(f"cache/{template_name}", "w") as f:
            f.write(str(content))
    return HttpResponse(content)

2.3 模板语法

Django的DTL或jinja2模板引擎提供的语法无非4种格式不同的语法,分别是变量,注释,标签,过滤器

"""变量"""
{{ 变量 }}

"""注释"""
{# 单行注释 #}
{% comment %}
    多行注释
{% endcomment %}

"""标签"""
{% 标签名 %}   # 单标签
{% 开始标签 %} {% 结束标签 %}  # 双标签

"""过滤器"""
# 本质就是函数[可以是python函数,也可以是开发者自定义函数],常用语在变量或者标签
# 单个无参数过滤器,变量默认作为过滤器的第1个参数,过滤器中return的内容作为结果被输出
{{ 变量 | 过滤器 }}
# 单个有参数过滤器,参数1是变量,英文冒号后面跟着的参数按顺序依次使用英文逗号排列
{{ 变量 | 过滤器:参数2 }}
# 多个无参数过滤器,每个 | (竖杠) 左边的内容作为右边过滤器的默认第1个参数
{{ 变量 | 过滤器1 | 过滤器2 | 过滤器3 | .....  }}
# 多个有参数过滤器,每个过滤器的参数1是 竖杠作为结果。最后一个过滤器返回的结果被输出
{{ 变量 | 过滤器1:参数2  | 过滤器2:参数2  | .... }}

2.3.1 变量

视图代码,tem/views.py,代码:

def index3(request):
    """模板引擎的语法"""
    from django.conf import settings
    import time
    num1 = 100
    num2 = 3.14
    name = "xiaoming"
    data1 = {1,2,3}
    data2 = (1,2,3,4)
    data3 = [1,2,3,4]
    data4 = {"name":"xiaohui","age": 17}
    data5 = settings
    book_list = [
        {"id": 10, "price": 9.90, "name": "python3天入门到挣扎", },
        {"id": 11, "price": 19.90, "name": "python7天入门到垂死挣扎", },
    ]
    return render(request, "index3.html", locals())

模板代码,templates/index3.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{#      单行注释#}
{% comment %}
    多行注释
    多行注释
    多行注释
{% endcomment %}

<p>num1={{ num1 }}</p>
<p>num2={{ num2 }}</p>
<p>name={{ name }}</p>
<p>data1={{ data1 }}</p>
<p>data2={{ data2 }}</p>
<p>data2={{ data2.0}}</p>
<p>data2={{ data2.3}}</p>
<p>data3={{ data3 }}</p>
<p>data3={{ data3.1 }}</p>
<p>data3={{ data3.3 }}</p>
<p>data4={{ data4 }}</p>
<p>data4={{ data4.name }}</p>
<p>data4={{ data4.age }}</p>
<p>data5={{ data5 }}</p>
<p>data5={{ data5.DEBUG }}</p>
<p>book_list={{ book_list }}</p>
<p>book_list={{ book_list.1.name }}</p>
<p>book_list={{ book_list.1.name }}</p>
{# 如果要在模板中调用一个对象方法的,该方法名不能后面跟着小括号 #}
<p>{{ time.time }}</p>
</body>
</html>

tem/urls.py,代码:

"""tem子应用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
    path("index", views.index),
    path("index1", views.index1),
    path("index2", views.index2),
    path("index3", views.index3),
]

2.3.2 标签

文档:https://docs.djangoproject.com/zh-hans/4.2/ref/templates/builtins/#ref-templates-builtins-tags

2.3.2.1 if判断语句

视图代码,tem/views.py,:

def index4(request):
    """标签[判断和循环]"""
    name = "xiaoming"
    lve = ["游泳", "收快递","OB"]
    user_lve = "www"
    return render(request, "index4.html", locals())

模板代码,templates/index.html,代码:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    {% if request.GET.name %}
       <p>{{ request.GET.name }},欢迎,欢迎!</p>
    {% endif %}

    {% if name == "root" %}
        <p>超级用户,欢迎回家!</p>
    {% else %}
        <p>{{ name }},你好,欢迎来到xx网站!</p>
    {% endif %}

    {% if user_lve == lve.0 %}
        <p>那么巧,你喜欢游泳,海里也能见到你~</p>
    {% elif user_lve == lve.1 %}
        <p>那么巧,你也来收快递呀?~</p>
    {% elif user_lve == lve.2 %}
        <p>那么巧,你也在老男孩?</p>
    {% else %}
        <p>看来我们没有缘分~</p>
    {% endif %}

</body>
</html>

路由代码:

"""tem子应用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
    path("index", views.index),
    path("index1", views.index1),
    path("index2", views.index2),
    path("index3", views.index3),
    path("index4", views.index4),
]
2.3.2.2 for循环语句

视图代码, home.views.py:

def index5(request):
    """标签[循环]"""
    book_list1 = [
        {"id": 11, "name": "python基础入门", "price": 130.00},
        {"id": 17, "name": "Go基础入门", "price": 230.00},
        {"id": 23, "name": "PHP基础入门", "price": 330.00},
        {"id": 44, "name": "Java基础入门", "price": 730.00},
        {"id": 51, "name": "C++基础入门", "price": 300.00},
        {"id": 56, "name": "C#基础入门", "price": 100.00},
        {"id": 57, "name": "前段基础入门", "price": 380.00},
    ]

    book_dict = {
        "name": "java入门",
        "page": 999,
        "price": 138.50,
    }

    return render(request, "index5.html", locals())

template/index5.html,代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {% for name, value in book_dict.items %}
       <p>{{ name }}={{ value }}</p>
    {% endfor %}

    <hr>

    <table border="1" width="600">
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>price</th>
        </tr>
        {% for book in book_list1 %}
        <tr>
            <td>{{ book.id }}</td>
            <td>{{ book.name}} </td>
            <td>{{ book.price}}</td>
        </tr>
        {% endfor %}
    </table>

</body>
</html>

路由代码:

"""tem子应用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
    path("index", views.index),
    path("index1", views.index1),
    path("index2", views.index2),
    path("index3", views.index3),
    path("index4", views.index4),
    path("index5", views.index5),
]

循环标签中, 模板引擎还提供的forloop对象,用于给开发者获取循环次数或者判断循环过程的.

属性 描述
forloop.counter 显示循环的次数,从1开始
forloop.counter0 显示循环的次数,从0开始
forloop.revcounter0 倒数显示循环的次数,从0开始
forloop.revcounter 倒数显示循环的次数,从1开始
forloop.first 判断如果本次是循环的第一次,则结果为True
forloop.last 判断如果本次是循环的最后一次,则结果为True
forloop.parentloop 在多层嵌套循环中,指向当前循环的上级循环对象

2.3.3 过滤器

过滤器(filter)本质就是一个函数,这种函数允许我们直接在模板中使用(python函数无法直接在模板中基于参数调用)。使用过滤器的情况一般就是希望在模板中对数据进行格式化处理或对数据进行规范输出和调整。

文档: https://docs.djangoproject.com/zh-hans/4.2/ref/templates/builtins/#ref-templates-builtins-tags

2.3.3.1 内置过滤器[build-in filter]

视图代码, home/views.py

def index6(request):
    """过滤器"""
    title = "我的标题"
    content = '我的个人主页:<a href="http://www.baidu.com">点击查看</a>'
    return render(request, "index6.html", locals())

templates/index6.html,代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>{{ content }}</p>
    {# 无参数过滤器 #}
    <p>{{ content | safe }}</p>  {# safe 过滤器的作用是告诉django,当前输出内容中的HTML内容属于可信任的安全内容,不要对内容中的html代码进行实体转义#}
    {# 有参数过滤器 #}
    <p>title={{ title | default:"无标题" }}</p> {# default 过滤器的作用是当变量不存在时,使用当前默认值实现 #}
</body>
</html>

tem/urls.py,代码:

"""tem子应用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
    path("index", views.index),
    path("index1", views.index1),
    path("index2", views.index2),
    path("index3", views.index3),
    path("index4", views.index4),
    path("index5", views.index5),
    path("index6", views.index6),
]

内置过滤器

过滤器 用法 代码
last 获取列表/元组的最后一个成员 {{list | last}}
first 获取列表/元组的第一个成员 {{list|first}},也可以使用{{list.0}}
length 获取数据的长度 {{list | length}}
defualt 当变量没有值的情况下, 系统输出默认值, {{str|default="默认值"}}
safe 让系统不要对内容中的html代码进行实体转义 {{htmlcontent| safe}}
upper 字母转换成大写 {{str | upper}}
lower 字母转换成小写 {{str | lower}}
title 每个单词首字母转换成大写 {{str | title}}
date 日期时间格式转换 {{ value| date:"D d M Y" }}
cut 从内容中截取掉同样字符的内容 {{content | cut:"hello"}}
escape 把内容中的HTML特殊符号转换成实体字符 {{content | escape }}
filesizeformat 把文件大小的数值转换成单位表示 {{filesize | filesizeformat}}
join 按指定字符拼接内容 {{list| join:"-"}}
random 随机提取某个成员 {list | random}}
slice 按切片提取成员 {{list | slice:":-2"}}
truncatechars 按字符长度截取内容 {{content | truncatechars:30}}
truncatechars_html 按字符长度截取内容,保留HTML标签的完整性 同上
truncatewords 按单词长度截取内容 同上
truncatewords_html 按单词长度截取内容,保留HTML标签的完整性 同上

视图代码,tem.views,代码:

def index6(request):
    """过滤器"""
    title = "我的标题"
    content = '我的个人主页:<a href="http://www.baidu.com">点击查看</a>'
    from datetime import datetime
    now_time = datetime.now()
    address = ["北京市", "昌平区", "上庄"]
    rang = range(1,101)
    return render(request, "index6.html", locals())

tem/urls.py,代码:

"""tem子应用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
    path("index", views.index),
    path("index1", views.index1),
    path("index2", views.index2),
    path("index3", views.index3),
    path("index4", views.index4),
    path("index5", views.index5),
    path("index6", views.index6),
]

模板代码,templates/index6.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>{{ content }}</p>
    {# 无参数过滤器 #}
    <p>{{ content | safe }}</p>
    {# 有参数过滤器 #}
    <p>title={{ title | default:"无标题" }}</p>
    <p>{{ "hello, welcome to beijing!" | title }}</p>
    <p>{% now "Y-m-d H:i:s" %}</p>
    <p>{{ now_time | date:"Y-m-d H:i:s" }}</p>

    <p>{{ "hello, welcome to beijing!"|cut:" "}}</p>
    <p>{{ content| escape}}</p>

    <p>{{ 1302024222 | filesizeformat }}</p>

    <p>{{ 1 | add:2 }}</p>

    <p>{{address | join:"-"}}</p>

    <p>{{ address | random }}</p>

    <p>{{rang|slice:":-2"}}</p>

    {# 按字符截取内容时,不识别HTML内容,并且不保留标签的完整性 #}
    {{ "<p>一段常常长的比较啰嗦的内容</p>" | safe | truncatechars:10}}
    {# 按字符截取内容时,识别HTML内容并保留标签的完整性 #}
    {{ "<p>一段常常长的比较啰嗦的内容</p>" | safe | truncatechars_html:10}}

    {# 按单词截取内容 #}
    {{ "<p>hello, xiaoming! where are from? I'm from china</p>" | safe | truncatewords_html:3}}

</body>
</html>

虽然官方已经提供了许多内置的过滤器给开发者,但是很明显,还是会有存在不足的时候。

例如:希望输出用户的手机号码时, 13912345678 ---->> 139*****678

2.3.3.2 自定义过滤器

customer filter

要声明自定义过滤器并且能在模板中正常使用,需要完成3个步骤。

  1. 确保当前需要使用过滤器的子应用已经注册到了INSTALLED_APPS中。
# Application definition
# django注册的子应用列表[用于数据库操作,缓存,日志,admin管理]
INSTALLED_APPS = [
    'django.contrib.admin',  # admin站点的子应用
    'django.contrib.auth',   # django内置的登录认证功能
    'django.contrib.contenttypes',  # 内容类型管理
    'django.contrib.sessions',      # session功能
    'django.contrib.messages',      # 信号、消息功能的实现
    'django.contrib.staticfiles',   # 静态文件浏览服务

    'tem',   # 开发者创建的子应用,这填写就是子应用的导包路径
]
  1. 编写过滤器,过滤器必须在子应用目录下的templatetags包里面创建对应的python文件中。tem/templatetags/my_filters.py代码:
from django import template

register = template.Library()


# register.filter("过滤器别名")
@register.filter("mobile")
def mobile_filter(content, flag="****"):
    # 务必有返回值,否则模板中没有内容显示
    return content[:3] + flag + content[-3:]


@register.filter("sex")
def sex(content): # 过滤器必须有一个以上的参数,提供给模板调用
    if bool(content):
        return "男"
    return "女"
  1. 在HTML模板中通过load标签加载当前子应用已经声明的过滤器函数,load标签的使用最好写在HTML模板文件的第一行。

视图代码:

def index7(request):
    """自定义过滤器"""
    content1 = "13312345678"
    return render(request, "index7.html", locals())

路由代码:

"""tem子应用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
    path("index", views.index),
    path("index1", views.index1),
    path("index2", views.index2),
    path("index3", views.index3),
    path("index4", views.index4),
    path("index5", views.index5),
    path("index6", views.index6),
    path("index7", views.index7),
]

模板代码:

{% load my_filters %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>{{ content1 | mobile }}</p>
    <p>{{ 1 | sex }}</p>
</body>
</html>

练习:

  1. 输出金额时,使用过滤器进行格式化,保留小数点2位。

100 —–> {{money | toFixed:2}} —>> 100.00

99.3 —–> {{money | toFixed:3}} —>> 99.300

99.352 —–> {{money | toFixed:1}} —>> 99.3

答案

主应用/settings.py,保证当前使用过滤器的子应用被注册到了INSTALLED_APPS配置中,并且手动创建模板目录并把目录路径记录到TEMPLATES配置的DIRS配置项中,代码:

INSTALLED_APPS = [
 "django.contrib.admin",
 "django.contrib.auth",
 "django.contrib.contenttypes",
 "django.contrib.sessions",
 "django.contrib.messages",
 "django.contrib.staticfiles",

 "tem",
 "demo",
]


TEMPLATES = [
 {
     "BACKEND": "django.template.backends.django.DjangoTemplates",
     "DIRS": [ 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",
         ],
     },
 },
]

demo/views.py,代码:

from django.shortcuts import render

# Create your views here.
def index1(request):
 num1 = 100
 num2 = 99.3
 num3 = 99.352
 return render(request, "index1.html", locals())

demo/urls.py,代码:

from django.urls import path
from . import views

urlpatterns = [
 path("index1/", views.index1),
]

主应用/urls.py,代码:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
 path("admin/", admin.site.urls),
 path("tem/", include("tem.urls")),
 path("demo/", include("demo.urls")),
]

demo/templatetags/my_filters.py,代码:

from django import template

register = template.Library()

@register.filter("toFixed")
def filter_to_fixed(content, len=2):
 return f"{content:.{len}f}"

templates/index1.html,代码:

{% load my_filters %}
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>过滤器的练习</title>
</head>
<body>
 <p>{{ num1|toFixed:2 }}</p>
 <p>{{ num2|toFixed:3 }}</p>
 <p>{{ num3|toFixed:1 }}</p>
</body>
</html>

2.3.4 模板继承

2.3.4.1 模板分离

image-20210531111100929

django中提供了{% include "模板文件名" %}标签模板分离技术。

视图,tem/views.py,代码:

def index8(request):
    """模板分离-文章列表页面"""
    title = "文章列表页面"
    return render(request, "index8.html", locals())

def index9(request):
    """模板分离-文章详情页面"""
    return render(request, "index9.html", locals())

路由代码:

"""子应用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
    # ....
    path("index12", views.index12),
    path("index13", views.index13),
]

公共模板,templates/common/head.html,代码:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div>head.html的新的公共头部</div>

公共模板,templates/common/footer.html,代码:

<div>footer.html公共脚部</div>

公共模板,templates/common/slide.html,代码:

<div>slide.html的侧栏内容</div>

视图对应的模板,templates/12.html,代码:

{% include "common/head.html" %}
    <div>
        <div>index12.html的主体内容</div>
        {% include "common/slide.html" %}
    </div>
    {% include "common/footer.html" %}
</body>
</html>

视图对应的模板,templates/13.html,代码:

{% include "common/head.html" %}
    <div>
        <div>index13.html的主体内容</div>
        {% include "common/slide.html" %}
    </div>
    {% include "common/footer.html" %}
</body>
</html>

效果:

image-20210531112427517

其实模板分离,这种方式,虽然达到了页面代码复用的效果,但是由此也会带来大量的碎片化html模板,导致维护模板的成本上升.

因此, 大部分框架中除了提供这种模板分离技术以外,还并行的提供了 模板继承 给开发者.

2.3.4.2 模板继承

image-20210531114657774

  1. 显示子模板的时候,继承父模板的公共内容

{% extends "base.html" %}

视图, tem.views.py代码:

def index14(request):
    """模板继承"""
    return render(request, 'index14.html', locals())

def index15(request):
    """模板继承"""
    return render(request, 'index15.html', locals())

子模板, templates/index14.html

{% extends "common/base.html" %}

父模板, templates/common/base.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    {% block style %}
    {% endblock style %}
</head>
<body>
    {% block header %}
    <div>公共头部</div>
    {% endblock header %}
    <div>
        {% block content %}
        <div>公共base.html->主体代码</div>
        {% endblock content %}
        <div>侧栏效果</div>
    </div>
    <div>公共脚部</div>
</body>
</html>
  1. 子模板中,在继承父模板内容的时候,. 针对性声明对应位置的block标签,在这个标签中填写自己的独立内容

{%block 变量名 %} 独立内容 {%endblock 变量名%}

{{block.super}}

视图tem.views.py, 代码:

def index14(request):
    """模板继承"""
    return render(request, 'index14.html', locals())

def index15(request):
    """模板继承"""
    return render(request, 'index15.html', locals())

路由 tem.urls.py,代码:

"""子应用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
    # 。。。。
    path("index14", views.index14),
 path("index15", views.index15),
]

子模板index14.html,代码:

{% extends "common/base.html" %}
{% block content %}
    {{ block.super }}
    <div>index14的主体代码</div>
{% endblock content %}

子模板index15.html,代码:

{% extends "common/base.html" %}
{% block style %}
    <style>
 body{
        background-color: rosybrown;
    }
    </style>
{% endblock style %}
{% block content %}
    <div>index15的主体代码</div>
{% endblock %}

{% block header %}
    <div>15的公共头部</div>
{% endblock header %}

父模板templates/commn/base.html,代码:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    {% block style %}
    {% endblock style %}
</head>
<body>
    {% block header %}
    <div>公共头部</div>
    {% endblock header %}
    <div>
        {% block content %}
        <div>公共base.html->主体代码</div>
        {% endblock content %}
        <div>侧栏效果</div>
    </div>
    <div>公共脚部</div>
</body>
</html>

3. 静态文件

开发中在开启调试模式(debug=True)时,django可以通过配置,允许用户通过对应的url地址访问django的静态文件。

主应用/settings.py,代码:

STATIC_URL = '/static/'   # django模板中,可以引用{{STATIC_URL}}变量避免把路径写死。
STATICFILES_DIRS = [ BASE_DIR / 'static' ]

注意:

项目上线以后,关闭debug模式时,django默认是不提供静态文件的访问支持,项目部署的时候,我们会通过收集静态文件使用nginx这种web服务器来提供静态文件的访问支持。

4. 视图进阶

新建一个子应用,cbv。接下来在这个子应用下面,我们学习类视图的操作。粒度更大的视图单位

python manage.py startapp cbv

在setings.py配置文件中注册子应用,settings.py,代码:

INSTALLED_APPS = [
    # ... 省略代码
    "cbv",
]

创建子应用的路由文件,cbv/urls.py,代码:

from django.urls import path 
from . import views
urlpatterns = [

]

在总路由中进行子路由文件的注册。djdemo.urls,代码:

path("cbv/", include("cbv.urls")),

4.1 类视图

Class Base View,简称"CBV",与我们之前编写的视图函数不同, 类视图是类的结构编写视图代码的.可以让我们实现相关业务代码的整合. 同时还在函数视图的基础上, 可以实现 对于客户端访问的http请求进行分流和限制。

4.1.1 类视图的基本定义

from django.shortcuts import render

# Create your views here.
# 1. 类视图必须直接或者间接继承于django.views.View
# 2. 在django的类视图中,所有提供给外界用户访问的视图方法名必须是 http请求动作的小写名称.
#    也就是说,方法名只能是 get, post, put, patch, delete ...
# 3. 使用类视图,可以有效的减少路由绑定的代码
#    3.1 可以直接通过视图方法名就可以达到限制客户端访问当前视图方法的http请求。
#    3.2 客户端访问对应的URL地址就会来到视图类中,访问这个地址所使用http请求,就是将来视图类自动执行的方法
#    3.3 有了类视图,我们可以把多个函数视图中原来的公共代码封装到一块进行调用,有利于代码复用。

# 4. 注意:同一个视图中,方法名不能出现重复!!!

from django.views import View
from django.http.response import HttpResponse, JsonResponse
class UserView(View):
    # 类视图中的公共方法/公共属性
    def ret(self, data):
        print(self.request.method) # 在类视图中,不仅可以通过视图方法中的参数,接收路由传递过来的请求对象,还可以通过self.request来或路由转发过来的请求对象
        return HttpResponse(data)

    def get(self,request):
        """只允许通过get请求访问,建议编写读取数据的页面,一般例如:首页,列表页,详情页"""
        # 视图中的视图方法里面的代码,与原来的函数视图中的代码,是一模一样的。原来怎么写,现在还是怎么写。
        return self.ret("hello, get")

    def post(self,request):
        """只允许通过post请求访问,一般用于上传,添加数据的页面"""
        return self.ret("hello, post")

    def put(self,request):
        """只允许通过put请求访问,一般用于修改,更新数据的页面"""
        return self.ret("hello, put")

    def patch(self,request):
        """只允许通过patch请求访问,一般用于修改,更新数据的页面"""
        return self.ret("hello, patch")

    def delete(self,request):
        """只允许通过delete请求访问,一般用于处理删除数据的页面"""
        return self.ret("hello, delete")

4.1.2 类视图的路由绑定

from django.urls import path
from . import views

# 在django中,路由只识别函数视图,对于类视图,我们必须要通过as_view() 帮我们把类的视图方法转换成函数视图
urlpatterns = [
    # path("index", views.视图类名.as_view()),
    path("index", views.IndexView.as_view()),
]

练习:

1. 创建一个类视图GoodsView。
2. 给GoodsView添加5个基本的http请求方法,并返回对应的数据。数据格式如下:
   post的返回数据:
   data = {"id":1, "name": "小熊饼干", "price":"5.40"}

   get的返回数据:
   data = [
        {"id":1, "name": "小熊饼干", "price":"5.40"},
        {"id":2, "name": "大熊饼干", "price":"15.40"},
   ]
   put的返回数据:
   data = {"id":1, "name": "小熊面包", "price":"5.40"}

   delete和patch的返回数据都是如下:
   data = {"msg": "ok"}

代码:

class GoodsView(View):

    def ret(self,data):
        return JsonResponse(data,safe=False)

    def post(self,request):
        data = {"id": 1, "name": "小熊饼干", "price": "5.40"}
        return self.ret(data)

    def get(self,request):
        data = [
            {"id": 2, "name": "小熊饼干", "price": "5.40"},
            {"id": 3, "name": "大熊饼干", "price": "15.40"},
        ]
        return self.ret(data)

    def put(self,requsest):
        data = {"id": 1, "name": "小熊面包", "price": "5.40"}
        return self.ret(data)

    def delete(self,request):
        data = {"msg": "ok"}
        return self.ret(data)

    def patch(self,request):
        data = {"msg": "ok"}
        return self.ret(data)

urls,代码:

from django.urls import path
from . import views

urlpatterns = [
    # as_view() 帮我们把类的视图方法转换成函数视图
    path("index", views.IndexView.as_view()),
    path("index1",views.GoodsView.as_view())
]

4.1.3 类视图的路由分发原理

我们可以通过postman来切换不同的http请求访问到同一个地址下不同的视图类方法中.这个主要原因是类视图的父类django.views.View中提供了dispatch的路由分发方法实现的.

这个路由分发方法,是开发者在给视图类绑定路由时调用了as_view()方法时进行注册调用的.

./assets/view2.png

4.2 视图基类的基本使用

""""
自定义视图基类
视图基类,可以专门一些公共的属性或者方法。
提供给将来开发的时候,用于完成特定业务的。这也可以封装成一个个视图基类,然后让具体的视图类直接继承,就不需要重写编写这些代码了
"""
import json
from django.views import View

class APIView(View):
    # 可以把没有参数的类方法,转换成普通属性来调用
    @property
    def data(self):
        """用于接受并装换json数据成字典"""
        ct = self.request.META.get("CONTENT_TYPE")
        # print(ct)
        if "multipart/form-data" in ct:
            """表单上传数据[允许上传文件]"""
            return self.request.POST.dict()
        if "application/x-www-form-urlencoded" in ct:
            """表单上传数据"""
            return self.request.POST.dict()
        if "application/json" in ct:
            try:
                data = json.loads(self.request.body)
                return data
            except Exception:
                return None

class HomeView(APIView):
    def post(self, request):
        print(self.data)
        return HttpResponse("ok")

    def put(self, request):
        print(self.data)
        return HttpResponse("ok")

    def patch(self, request):
        print(self.data)
        return HttpResponse("ok")

4.2.1 相关扩展

在django提供的视图类View使用中还提供了一些视图子类和视图基类
不过这块内容需要结合django的数据库操作才可以使用所以我们后面再来学习
视图子类是django为了方便开发者快速提供基于不同http请求视图而提供的

from django.views.generic import ListView,DetailView,CreateView,UpdateView,DeleteView

ListView:   列表视图可以通过get请求访问用于展示列表数据内置了分页功能    
DetailView详情视图可以通过get请求访问用于展示单个数据
CreateView添加视图可以通过get/post请求访问用于添加单个数据
UpdateView更新视图可以通过get/post请求访问用于更新单个数据
DeleteView删除视图可以通过get请求访问用于删除单个数据

5. 中间件

MiddleWare,是 Django 请求/响应处理的钩子框架。它是一个轻量级的、低级的“插件”系统,用于全局改变 Django 的输入或输出。【输入指代的就是客户端向服务端django发送数据,输出指代django根据客户端要求处理数据的结果返回给客户端】

钩子就是编程开发的一个术语,hook,钩子可以理解为一段代码(要么是类,要么是函数),它的作用就类似日常生活中墙上的钩子,不需要的时候,挂在墙上不会占用房子的空间,但是需要的时候我们可以把一些物件挂在上面。

这种中间件在平时不使用情况下不会耗费任何的性能,如果编写了中间件以后,可以在特定的条件下,全局执行!!

文档: https://docs.djangoproject.com/zh-hans/4.2/topics/http/middleware/

5.1 内置中间件

django框架内部声明了很多的中间件,这些中间件有着各种各种的用途,有些没有被使用,有些被默认开启使用了。

而被开启使用的中间件,都是在settngs.py的MIDDLEWARE中注册使用的。

# 中间件列表
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware', # 安全监测相关的中间件,防止页面过期, js跨站脚本攻击xss
    'django.contrib.sessions.middleware.SessionMiddleware', # session加密和读取、保存session相关
    'django.middleware.common.CommonMiddleware', # 通用中间件,用于给url进行重写,自动给URL后面加上/
    'django.middleware.csrf.CsrfViewMiddleware', # 防止网站遭到csrf攻击的
    'django.contrib.auth.middleware.AuthenticationMiddleware', # 用户认证的中间件
    'django.contrib.messages.middleware.MessageMiddleware', # 错误提示信息的中间件【提示错误信息,一次性提示】
    'django.middleware.clickjacking.XFrameOptionsMiddleware', # 用于防止点击劫持攻击的 iframe标签
]

5.2 Csrf攻击

跨站请求伪造, Cross-site request forgery,利用用户在不知情的情况下实现伪造表单提交给服务端中进行攻击的手段。

5.2.1 csrf的攻击原理

1605953039558

django中提供了一个Csrfmiddleware的中间件给开发者用于防止网站用户遭到这种攻击的.

中间件主要是每次客户端通过post,patch,put等方式提交数据操作时,判断当前表单是否是隐藏了一个django发放的csrf_token的随机字符串令牌.如果有这个随机字符串,则中间件则会判断这个随机字符串是否是由服务端提供的.

我们开发者只需要在每个表单页面中, 内置一个隐藏的输入框里面填写cstf_token则可以让当前页面的表单顺利提交数据到后台.

csrf_token

csrf_token的生成是每次都是基于服务端的秘钥进行随机生成的,所以有一定的生成算法在里面的,所以如果没有秘钥的情况下, 则生成的随机token令牌则会轻易被django识别到.

5.2.2 在表单中设置cstf_token

视图, cbv/views.py,代码:

class FormView(View):
    def get(self,request):
        """注册页面""" 
        return render(request, "index6.html")

    def post(self,request):
        """注册信息提交"""
        print(request.POST)
        return HttpResponse("ok")

路由,cbv/urls.py代码:

from django.urls import path
from . import views
urlpatterns = [
    # 给类视图绑定路由的格式固定如下:
    # path("访问路径", views.类名.as_view()),
    path("demo/", views.IndexView.as_view()),
    path("md/", views.HomeView.as_view()),
    path("form/", views.FormView.as_view())
]

模板,templates/index6.html,代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
    <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
    {% csrf_token %}
    账号: <input type="text" name="username"> <br><br>
    密码: <input type="text" name="password"> <br><br>
    <input type="submit" value="注册">
</form>
</body>
</html>

上面我们可以使用2种方式来完成csrf_token的提交,但是2种使用方式在某些场合下有所区别.

将来我们使用ajax以json的方式提交数据,则不能使用{% csrf_token %},只能使用`{{ csrf_token }}在json数据里面了。

5.3 GZIP压缩

压缩所有现代浏览器的响应,节省带宽和传输时间。

注意,GzipMiddleware目前被认为是一种安全风险,并且容易受到TSL/SSL提供的保护无效的攻击。

settings.py,代码:

MIDDLEWARE = [
    'django.middleware.gzip.GZipMiddleware', # 实际工作中,我们往往是采用web服务器(uwsgi, nginx)来实现gzip压缩
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',   # ctrl+/
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

打开前:

image-20210601111736499

image-20210601111856849

打开后:

image-20210601111811005

image-20210601111829140

5.4 中间件的执行顺序

image-20230921153258796

image-20231010094655071

在http请求阶段,从上往下执行

在http响应阶段,从下往上执行

5.5 自定义中间件

django中提供了2种不同的中间件声明方式.主要在django1.9或者2.0出现

5.5.1 函数式中间件

在项目的主应用目录下创建一个专门存放中间件函数的模块 主应用djdemo/my_middleware.py,代码:

def simple_middleware(get_response):
    # 自定义中间件
    def middleware(request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
        print("--------------视图执行之前---------------")
        # 记录访问用户记录的信息,识别判断黑名单,白名单,判断用户是否登录, 判断用户是否拥有访问权限.....
        # 视图执行之前
        response = get_response(request) # 视图调用
        # 视图执行之后
        print("-------------视图执行以后----------------")
        # 记录用户的操作历史,访问历史,日志记录, 资源的回收...
        return response

    return middleware

在settings.py下面注册中间件

# 中间件列表
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'middle.simple_middleware', # 没有特殊要求,一般自己定义的中间件写在最后
]

因为中间件一旦注册了以后, 会在全局生效, 所以我们访问任何一个视图都可以看到中间件执行的过程.

所以我们在cbv/views.py编写了一个测试的视图.代码:

from django.http.response import HttpResponse
class HomeView(View):
    def get(self,request):
        print( "---------------视图执行了---------------" )
        return HttpResponse("ok")

最终,访问视图,效果如下:

1605950164493

5.5.2 类中间件

和函数式的中间件一样,我们一般都会保存在一个独立的文件中.把所有的中间件按不同的业务存放在一块,.

middle.py,代码:

def simple_middleware(get_response):
    # 自定义中间件
    def middleware(request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
        print("--------------视图执行之前---------------")
        # 记录访问用户记录的信息,识别判断黑名单,白名单,判断用户是否登录, 判断用户是否拥有访问权限.....
        # 视图执行之前
        response = get_response(request)
        # 视图执行之后
        print("-------------视图执行以后----------------")
        # 记录用户的操作历史,访问历史,日志记录, 资源的回收...
        return response

    return middleware

from django.utils.deprecation import MiddlewareMixin
from django.http.response import HttpResponse
class CustomMiddleware(MiddlewareMixin):
    """中间件类"""
    def process_request(self,request):
        # 方法名是固定的,该方法会在用户请求访问路由解析完成以后,调用视图之前自动执行
        print("1. process_request在路由解析以后,产生request对象,视图执行之前,会执行这个方法")
        # 用途:权限,路由分发,cdn,用户身份识别,白名单,黑名单...
        # 注意,此方法不能使用return,使用则报错!!!

    def process_view(self,request,view_func, view_args, view_kwargs):
        # 用途:进行缓存处理,识别参数,根据参数查询是否建立缓存
        print("2. process_view在视图接受了参数以后,没有执行内部代码之前")
        # 可以返回response对象, 如果返回response对象以后,则当前对应的视图函数将不会被执行
        # return HttpResponse("ok")
        # 也可以不返回response,则默认返回None,django就会自动执行视图函数

    def process_response(self,request,response):
        print("4. process_response在视图执行以后,才执行的")
        # 用途:记录操作历史, 记录访问历史,修改返回给客户端的数据, 建立缓存
        # 必须返回response对象,否则报错!!
        return response

    def process_exception(self, request, exception):
        print(exception)
        # 用途:进行异常的处理或者记录错误日志
        print("5. process_exception会在视图执行发生异常的时候才会执行")

    def process_template_response(self,request, response):
        # 用途:建立页面缓存
        print("6. process_template_response只有在视图调用了模板以后,才会执行!!!")
        return response

注意:

类中间件里面提供的固定钩子方法有5个,但是最常用的是 process_request(self,request)process_response(self,request,response)

中间件类和函数式中间件的注册方式雷同,也是在setting.py中直接注册.

# 中间件列表
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware', # 安全监测相关的中间件,防止,页面过期, js跨站脚本攻击xss
    'django.contrib.sessions.middleware.SessionMiddleware', # session加密和读取和保存session相关
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'djdemo.my_middleware.user_source_middleware', 
    # 注册自己的中间件[不管是函数式中间件还是类中间件都是这么注册的]
    'djdemo.cs_middleware.CustomMiddleware',  # 注册了类中间件
]

从上面不管是函数式中间件还是中间件类,他们的执行顺序都是类似的.也就是部分的方法在视图执行之前执行,部分方法是在视图执行之后执行的.

终端执行结果:

# 没有异常
[函数中间件], 客户端IP地址 = 127.0.0.1
[函数中间件], --------------视图执行之前---------------
[类中间件], 客户端IP地址 = 127.0.0.1
[类中间件], 1. process_request在路由解析以后,产生request对象, 视图执行之前,会执行这个方法
[类中间件], 2. process_view在视图接受了参数以后,没有执行内部代码之前
当前登录用户: AnonymousUser
[<django.template.backends.django.DjangoTemplates object at 0x7f6ebe6f1910>]
context=[{'True': True, 'False': False, 'None': None}, {}, {}]
[类中间件], 4. process_response在视图执行以后,才执行的
[函数中间件], -------------视图执行以后----------------
[函数中间件], 记录用户的操作历史

# 视图中有异常
[函数中间件], 客户端IP地址 = 127.0.0.1
[函数中间件], --------------视图执行之前---------------
[类中间件], 客户端IP地址 = 127.0.0.1
[类中间件], 1. process_request在路由解析以后,产生request对象, 视图执行之前,会执行这个方法
[类中间件], 2. process_view在视图接受了参数以后,没有执行内部代码之前
division by zero
[类中间件], 3. process_exception会在视图执行发生异常的时候才会执行
[类中间件], 4. process_response在视图执行以后,才执行的
[函数中间件], -------------视图执行以后----------------
[函数中间件], 记录用户的操作历史

如果多个中间件一起执行,效果如下:

1-1 视图执行之前的代码 # 【simple_middle】
1. process_request在路由解析以后,产生request对应, 视图执行之前,会执行这个方法 # 【CustomMidleWare】
2. process_view在视图接受了参数以后,没有执行内部代码之前# 【CustomMidleWare】
视图执行了!!!# 【视图代码】
4. process_response在视图执行以后,才执行的 # 【CustomMidleWare】
1-2 视图执行之后的代码 # 【simple_middle】

从上面的打印结果可以看到,中间件是包含了视图代码的执行,所以中间件的顺序顺序,是回环式执行

image-20210601122309877

image-20210813181050701