Django框架基础

Django 框架基础

一、Django工程搭建

1.web本质和意义

1.1 web应用程序处理流程

前端客户端(浏览器、APP、ajax、爬虫程序)

–HTTP请求–>

服务器程序(如:gunicorn、uwsgi)接收和解析HTTP请求报文

–WSGI协议–>

框架程序(如:flask、Django)HTTP请求对象(request)–>中间层处理–>具体视图处理-业务处理(如数据库、模板、表单)–>中间层处理–>HTTP响应对象(response)

–WSGI协议–>

服务器程序(如:gunicorn、uwsgi)构建和返回HTTP响应报文

–HTTP响应–>

前端客户端(浏览器、APP、ajax、爬虫程序)

1.2 Web应用程序的本质

  • 接收并解析HTTP请求,获取具体的请求信息
  • 处理本次HTTP请求,即完成本次请求的业务逻辑处理
  • 构造并返回处理结果——HTTP响应

1.3 Web程序框架的意义

  • 用于搭建Web应用程序
  • 免去不同Web应用相同代码部分的重复编写,只需关心Web应用核心的业务逻辑实现

2.Django框架介绍

2.1 简介

  • Django 发音[`dʒæŋɡəʊ],是用python语言写的开源web开发框架,并遵循MVC设计模式。
  • Django的主要目的是简便、快速的开发数据库驱动的网站。

2.2 特点

1. 重量级框架

  • Django框架相比较于Python其他的Web框架而言是大而全的。
  • Django提供了原生的众多功能组件,让开发更简便快速。
  • 提供项目工程管理的自动化脚本工具。
  • 支持ORM以面向对象的形式操作数据库。(Object Relational Mapping)
  • 提供了强大的模板引擎,用于渲染页面。
  • 提供了文件管理、认证权限、session机制和缓存。

2. 遵守MVT设计模式

MVC设计模式说明
  • MVC 的全拼为 Model-View-Controller
  • M 全拼为 Model,主要封装对数据库层的访问,对数据库中的数据进行增、删、改、查操作。
  • V 全拼为 View,用于封装结果,生成页面展示的html内容。
  • C 全拼为 Controller,用于接收请求,处理业务逻辑,与Model和View交互,返回结果。
  • MVC 的核心思想是分工、解耦,让不同的代码块之间降低耦合,增强代码的可扩展性和可移植性,实现向后兼容
Django的MVT设计模式说明
  • MVT 的全拼为 Model-View-Template
  • M 全拼为 Model,与MVC中的M功能相同,负责和数据库交互,进行数据处理。
  • V 全拼为 View,与MVC中的C功能相同,接收请求,进行业务处理,返回应答。
  • T 全拼为 Template,与MVC中的V功能相同,负责封装构造要返回的html。
  • MVT 的核心思想和 MVC 是相同的

3.虚拟环境

3.1 为什么要创建虚拟环境

  • 在开发过程中, 当需要使用python的某些工具包/框架时需要联网安装

    • 比如联网安装Django框架 django==2.2.16 版本

      sudo pip install django==2.2.16
  • 提示:使用如上命令, 会将django==2.2.16安装到/usr/local/lib/python版本/dist-packages路径下

  • 问题 : 如果在一台电脑上, 想开发多个不同的项目, 需要用到同一个包的不同版本, 如果使用上面的命令, 在同一个目录下安装或者更新, 新版本会覆盖以前的版本, 其它的项目就无法运行了。

  • 解决方案: 虚拟环境

    • 作用 : 虚拟环境可以搭建独立的python运行环境, 使得单个项目的运行环境与其它项目互不影响.
    • 所有的虚拟环境都位于/home/下的隐藏目录.virtualenvs

3.2 如何创建虚拟环境

  • 安装虚拟环境的命令 :

    sudo pip install virtualenv
    sudo pip install virtualenvwrapper
  • 创建虚拟环境的命令 :

    • 提示:如果不指定python版本,虚拟环境就会使用默认的python版本

    例如:

    • 使用默认的python创建虚拟环境

      mkvirtualenv 虚拟环境名称
      例 :
      mkvirtualenv py_django
  • 指定python版本创建虚拟环境

    mkvirtualenv -p python3 虚拟环境名称
    例 :
    mkvirtualenv -p python3 py_django  # 使用的是默认的python3版本
    mkvirtualenv -p python3.5 py_django  # 使用的是python3.5版本,注意,所使用的python版本在本机已经安装才行

    提示 :

    • 创建虚拟环境需要联网
    • 创建成功后, 会自动工作在这个虚拟环境上
    • 工作在虚拟环境上, 提示符最前面会出现 “虚拟环境名称”

3.3 如何使用虚拟环境

查看所有虚拟环境的命令 :

  workon 两次tab键

使用虚拟环境的命令 :

  workon 虚拟环境名称
  例:
  workon py_django

退出虚拟环境的命令 :

  deactivate

删除虚拟环境的命令 :

  rmvirtualenv 虚拟环境名称

  先退出:deactivate
  再删除:rmvirtualenv 虚拟环境名称

3.4 如何在虚拟环境中安装工具包

  • 虚拟环境中安装框架、包命令 :

      pip install 框架、包名称
    
      例 : 安装`django==2.2.16`
      pip install django==2.2.16
  • 框架、包安装的位置 :

    • ~/.virtualenvs/虚拟环境名称/lib/python版本/site-packages
  • 查看虚拟环境中安装的包 :

    pip freeze 或者 pip list

3.5 特别提示

  • 在虚拟环境中,直接使用 pip install 安装Django框架或者扩展包时,速度特别慢,甚至报红色警告。
  • 这主要是因为Django框架和很多的扩展包都是从国外服务器进行下载安装的。

指定镜像源:加速下载安装Django框架或者扩展包

pip install django==2.2.16 -i https://pypi.tuna.tsinghua.edu.cn/simple/

如果还是下载安装比较慢,可以把上面的镜像源链接换为下面的任意一个:

https://mirrors.aliyun.com/pypi/simple/
http://pypi.douban.com/simple/
http://pypi.mirrors.ustc.edu.cn/simple/

4.Django工程创建

4.1 创建工程

创建工程的命令为:

django-admin startproject 工程名称
例子:
cd ~/Desktop/
django-admin startproject Django_test

执行后,会多出一个新目录名为 Django_test,此即为新创建的工程目录。

4.2 工程目录说明

  • 与项目同名的目录,此处为 Django_test
  • settings.py 是项目的整体配置文件。
  • urls.py 是项目的URL配置文件。
  • wsgi.py 是项目与WSGI兼容的Web服务器入口。
  • manage.py 是项目管理文件,通过它管理项目。

4.3 运行开发服务器

在开发阶段,为了能够快速预览到开发的效果,django提供了一个纯python编写的轻量级web服务器,仅在开发阶段使用。

运行服务器命令如下:

python manage.py runserver ip:端口
例:
python manage.py runserver 127.0.0.1:9000
或:
python manage.py runserver    # 可以不写IP和端口,默认IP是127.0.0.1,默认端口为8000。

在浏览器中输入网址“127.0.0.1:8000”便可看到效果。

  • django默认工作在调式Debug模式下,如果增加、修改、删除文件,服务器会自动重启。

5. Django工程配置

提示:

  • 工程的配置文件是 settings.py
  • 以下内容仅仅是测试Django工程修改配置文件后的效果

5.1 BASE_DIR

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

当前工程的根目录,Django会依此来定位工程内的相关文件,我们也可以使用该参数来构造文件路径。

5.2 DEBUG

调试模式,创建工程后初始值为True,即默认工作在调试模式下。

作用:

  • 修改代码文件,程序自动重启

注意:部署线上运行的Django不要运行在调式模式下,记得修改DEBUG=False。

5.3 本地语言与时区

Django支持本地化处理,即显示语言与时区支持本地化。

本地化是将显示的语言、时间等使用本地的习惯,这里的本地化就是进行中国化,中国大陆地区使用简体中文,时区使用亚洲/上海时区,注意这里不使用北京时区表示。

初始化的工程默认语言和时区为英语和UTC标准时区

LANGUAGE_CODE = 'en-us' # 语言:英语

TIME_ZONE = 'UTC' # UTC标准时区

将语言和时区修改为中国大陆信息

LANGUAGE_CODE = 'zh-hans' # 语言:简体中文

TIME_ZONE = 'Asia/Shanghai' # 亚洲上海

6. Django子应用

提示:

  • 在Web应用中,通常有一些业务功能模块是可以在不同的项目中复用的。
  • 所以,在开发中通常将项目工程拆分为不同的子功能模块。
  • 而且各功能模块间保持了相对的独立,在其他项目中需要用到某个特定功能模块时,可以将该模块代码整体复制过去,达到复用。

6.1 创建子应用

在Django中,创建子应用仍然可以通过命令来操作,即:

cd 项目工程

django-admin startapp 子应用名称(推荐)
或者
python manage.py startapp 子应用名称
  • 例如:
    • 在上一步创建的 Django_test 工程中,创建一个专门管理 用户模块 的子应用
    • 如果管理 用户模块 的子应用名称设计为 users,则命令为:
cd ~/Desktop/Django_test/

django-admin startapp users
或者
python manage.py startapp users

6.2 子应用目录说明

  • admin.py 文件跟网站的后台管理站点配置相关。
  • apps.py 文件用于配置当前子应用的相关信息。
  • migrations 目录用于存放数据库迁移历史文件。
  • models.py 文件用户保存数据库模型类。
  • tests.py 文件用于开发测试用例,编写单元测试。
  • views.py 文件用于编写Web应用视图。

6.3 注册子应用

创建出来的子应用目录文件虽然被放到了工程项目目录中,但是django工程并不能立即直接使用该子应用,需要注册安装后才能使用。

在工程配置文件settings.py中,INSTALLED_APPS项保存了工程中已经注册安装的子应用,初始工程中的INSTALLED_APPS如下:

注册安装一个子应用的方法,即是将子应用的配置信息文件apps.py中的Config类添加到INSTALLED_APPS列表中。

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

    'users', # 用户模块子应用
]

二、视图(views)

经验值分享

  1. 请求对象的user属性

参考知识点:请求HttpRequest

  • 提示:
    • 请求对象的user属性返回的是请求过程中认证出来的用户对象
  • 使用方式:
    • user = request.user
  • 使用场景:
    • 从请求中获取当前登录用户对象信息
  • 注意点:
    • request.user获取到的不一定是当前登录用户对象
    • 如果当前请求是已登录用户发送的,那么request.user获取到的才是当前登录用户对象
    • 如果当前请求是未登录用户发送的,那么request.user获取到的会是一个AnonymousUser对象(匿名用户,没有任何用户信息,没有使用价值)。
  • 工作中如何使用request.user
    • request.user需要搭配用户访问的限制来使用。
    • 需要先判断用户是否已登录,如果用户已登录,就可以大胆放心的使用request.user
  1. 自定义中间件注册原则

参考知识点:中间件Middleware

  • 提示:多个中间件执行的顺序是有规律的
多个中间件注册顺序:
MIDDLEWARE = [
    'Middleware1',
    'Middleware2',
    'Middleware3',
]

请求时:按照顺序由上而下进入中间件
    [1 ---> 2 ---> 3]
响应时:先进入的中间件后执行完的
    [3 ---> 2 ---> 1]

经验:
    中间件中请求优先的逻辑,中间件一定要放在最前注册
    中间件中响应优先的逻辑,中间件一定要放在最后注册

例子:
    解决前后端分离时请求跨域的问题
    每个请求都要解决跨域的问题,所以需要用到中间件
    而且需要在请求处理时最先处理跨域的问题,所以解决请求跨域时的中间件需要最先注册

1.函数视图

1.1 定义函数视图

函数视图定义方式:

1. 函数视图它是一个标准的Python函数。
2. 函数视图中,第一个参数必须定义:第一个参数为请求对象,用于接收用户发送的请求报文。
3. 函数视图中,必须返回响应对象:用于构造响应报文,并响应给用户。
4. 说明:
    请求对象:HttpRequest() 对应的对象
    响应对象:HttpResponse() 对应的对象
from django.shortcuts import render
from django import http

# Create your views here.


def register(request):
    """
    用户注册函数视图
    :param request: 请求对象,包含了请求报文信息
    :return: 响应对象,用于构造响应报文,并响应给用户
    """
    # 响应数据
    return http.HttpResponse('这里假装返回注册页面')

1.2 访问函数视图

提示:

  • 我们定义好的函数视图,需要用户能够访问到。
  • 用户如何访问函数视图?
    • 通过网络地址向Django程序发请求,即可访问到函数视图

问题:

  • 如何保证用户发送的请求,能够访问到对应的函数视图?

解决:

  • 路由:使用路由匹配请求地址,每匹配成功一个就执行对应的函数视图逻辑
  • 定义路由的方法:path()、re_path()、url()

需求:

  • 用户通过网络地址http://127.0.0.1:8000/users/register/访问用户注册视图

1.3 访问函数视图:需求实现 –> path()

1. 新建子路由文件

  • 子应用中新建一个urls.py文件用于定义该应用的所有路由信息

2. 注册子路由

  • 子应用/urls.py文件中定义路由信息
from django.urls import path

from . import views

# urlpatterns是被Django自动识别的路由列表变量:定义该应用的所有路由信息
urlpatterns = [
    # 函数视图路由语法:
    # path('网络地址正则表达式', 函数视图名),

    # 用户注册:http://127.0.0.1:8000/users/register/
    path('users/register/', views.register),
]

3. 注册总路由

  • 在工程总路由工程同名目录/urls.py中包含子应用的路由数据
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    # 自带的后台管理系统的总路由:可以忽略
    path('admin/', admin.site.urls),

    # 总路由包含子路由语法
    # path('网络地址前缀/', include('子应用.urls')),
    # 或者
    # path('', include('子应用.urls')),

    # 用户模块:http://127.0.0.1:8000/users/register/
    path('', include('users.urls')),
]

总路由说明:

  • 一个子应用对应一个总路由。
  • 总路由中,使用include()来将users子应用里的所有路由都包含进工程总路由中。

4. 启动运行测试

重新启动django程序

python manage.py runserver

使用postman进行请求测试: http://127.0.0.1:8000/users/register/

2.类视图

2.1 函数视图问题说明(不推荐使用函数视图)

# GET http://127.0.0.1:8000/users/register/
def register(request):
    """
    用户注册函数视图
    :param request: 请求对象,包含了请求报文信息
    :return: 响应对象,用于构造响应报文,并响应给用户
    """
    # 响应数据
    return http.HttpResponse('这里假装返回注册页面')

需求:

  • 用户向地址http://127.0.0.1:8000/users/register/发送GET请求,用来获取注册页面。
  • 用户向地址http://127.0.0.1:8000/users/register/发送POST请求,用来实现注册逻辑。

需求实现:

def register(request):
    """
    用户注册函数视图
    :param request: 请求对象,包含了请求报文信息
    :return: 响应对象,用于构造响应报文,并响应给用户
    """
    # 获取请求方法,判断是GET还是POST请求
    if request.method == 'GET':
        # 处理GET请求,返回注册页面
        return HttpResponse('这里假装返回注册页面')
    else:
        # 处理POST请求,实现注册逻辑
        return HttpResponse('这里假装实现注册逻辑')

函数视图问题说明:

  • 当遇到视图对应的同一个路径,提供了多种不同HTTP请求方式的支持时,便需要在一个函数中编写不同的业务逻辑,代码可读性与复用性都很差。

解决方案:

  • 类视图

2.2 定义类视图

类视图定义方式:

1. 类视图它是一个标准的Python类。
2. 类视图需要继承自Django提供的父类视图View。
3. 在类视图中,
    3.1 需要定义跟请求方法同名的函数来对应不同请求方式
    3.2 在请求方法同名的函数中,还必须定义一个接收请求的参数(同函数视图)
    3.3 在请求方法同名的函数中,还必须返回一个响应对象(同函数视图)
from django.views import View


class RegisterView(View):
    """用户注册类视图
    http://127.0.0.1:8000/users/register/
    """

    def get(self, request):
        """
        处理GET请求,返回注册页面
        :param request: 请求对象,包含了请求报文信息
        :return: 响应对象,用于构造响应报文,并响应给用户
        """
        return http.HttpResponse('这里假装返回注册页面')

    def post(self, request):
        """
        处理POST请求,实现注册逻辑
        :param request: 请求对象,包含了请求报文信息
        :return: 响应对象,用于构造响应报文,并响应给用户
        """
        return http.HttpResponse('这里假装实现注册逻辑')

类视图的好处:

  • 代码可读性好
  • 类视图相对于函数视图有更高的复用性, 如果其他地方需要用到某个类视图的某个特定逻辑,直接继承该类视图即可。

2.3 访问类视图

说明:

  • 类视图的访问和函数视图的访问是一模一样的。
  • 类视图的访问也是使用路由匹配请求地址,每匹配成功一个就执行对应的类视图逻辑

需求:

  • 用户向地址http://127.0.0.1:8000/users/register/发送GET请求,用来获取注册页面。
  • 用户向地址http://127.0.0.1:8000/users/register/发送POST请求,用来实现注册逻辑。

2.4 访问类视图:需求实现 –> path()

1. 注册子路由

  • 子应用/urls.py文件中定义路由信息
  • 由于当前代码还是编写在users子应用中的,所以总路由注册过一次之后,不用再注册
from django.urls import path

from . import views

# urlpatterns是被Django自动识别的路由列表变量:定义该应用的所有路由信息
urlpatterns = [
    # 类视图路由语法:
    # path('网络地址正则表达式', 类视图.as_view()),

    # 用户注册:http://127.0.0.1:8000/users/register/
    path('users/register/', views.RegisterView.as_view()),
]

2. 启动运行测试

  • 2.1 注释CSRF中间件
    • Django默认开启了CSRF防护,会对非GET请求(POST, PUT, DELETE)进行CSRF防护验证,在测试时可以关闭CSRF防护机制
    • 关闭CSRF防护机制是在settings.py文件中注释掉CSRF中间件
# 中间件
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
  • 2.2 重新启动Django程序
python manage.py runserver
  • 2.3 使用postman进行请求测试: http://127.0.0.1:8000/users/register/

2.5 as_view()底层原理(仅做了解)

class View:
    """
    Intentionally simple parent class for all views. Only implements
    dispatch-by-method and simple sanity checking.
    # 为所有视图定义简单的父类,只实现了请求方法分派和简单的完整性检查。
    """
    # 定义Django允许接收的请求方法
    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    def __init__(self, **kwargs):
        """
        Constructor. Called in the URLconf; can contain helpful extra
        keyword arguments, and other things.
        # 类视图的初始化构造函数,创建类视图对象时会被调用,并可以接收额外的参数
        """
        # Go through keyword arguments, and either save their values to our
        # instance, or raise an error.
        for key, value in kwargs.items():
            setattr(self, key, value)

    @classonlymethod
    def as_view(cls, **initkwargs):
        """Main entry point for a request-response process.
        # 请求-响应过程的主要入口点.
        """
        for key in initkwargs:
            # 遍历as_view()接收的参数
            # 省略......

        def view(request, *args, **kwargs):
            """准备一个函数视图,将来作为as_view()的返回值,并用于路由匹配"""
            # 初始化类视图对象
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            # 将路由中传入的参数,绑定到类视图对象中
            self.setup(request, *args, **kwargs)
            # 检查类视图是否完整:类视图中必须要有'request' attribute
            if not hasattr(self, 'request'):
                raise AttributeError(
                    "%s instance has no 'request' attribute. Did you override "
                    "setup() and forget to call super()?" % cls.__name__
                )
            # 调用请求分发的方法(最核心):将请求分发给跟请求方法同名的函数
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

    def setup(self, request, *args, **kwargs):
        """Initialize attributes shared by all view methods.
        # 初始化所有视图方法共享的属性:将路由中传入的参数,绑定到类视图对象中
        """
        self.request = request
        self.args = args
        self.kwargs = kwargs

    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn't exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn't on the approved list.
        # 尽量采用正确的调度方法;如果方法不存在,请遵从错误处理程序。如果请求方法不在批准的列表中,也遵从错误处理程序。
        # 先判断客户端的请求方法是否在允许接收的方法列表中
        if request.method.lower() in self.http_method_names:
            # 如果客户端的请求方法在允许接收的方法列表中,
            # 取出类视图对象中的跟请求方法同名的函数名,赋值给handler
            # 比如:当前客户端发送的请求,请求方法是GET,那么,handler=get
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            # 如果客户端的请求方法不在允许接收的方法列表中,遵从错误处理程序
            handler = self.http_method_not_allowed
        # 如果请求分发没有问题,那么就去调用该跟请求分发同名的函数
        # 如果当前客户端发送的请求,请求方法是GET
        # handler(request, *args, **kwargs)等价于get(request, *args, **kwargs)
        # 如果handler()调用成功,那么跟请求分发同名的函数就会被调用执行
        return handler(request, *args, **kwargs)

    def http_method_not_allowed(self, request, *args, **kwargs):
        """错误处理程序:请求方法不匹配时,响应的错误信息"""
        logger.warning(
            'Method Not Allowed (%s): %s', request.method, request.path,
            extra={'status_code': 405, 'request': request}
        )
        return HttpResponseNotAllowed(self._allowed_methods())

2.6 类视图添加扩展类

提示:

  • 使用面向对象多继承的特性,可以给类视图定义扩展类。
  • 在扩展类中,可以定义想要向类视图补充的方法。
  • 类视图继承这些扩展类作为父类,便可实现代码复用。

示例:

class ListModelMixin(object):
    """list扩展类 """
    def list(self, request, *args, **kwargs):
        pass


class CreateModelMixin(object):
    """create扩展类 """
    def create(self, request, *args, **kwargs):
        pass


class TestMixinView(CreateModelMixin, ListModelMixin, View):
    """同时继承两个扩展类,复用list和create方法"""
    def get(self, request):
        self.list(request)
        pass

    def post(self, request):
        self.create(request)
        pass

3.路由

提示:

  • 路由可以保证用户发送的请求,能够访问到对应的视图
  • 使用路由匹配请求地址,每匹配成功一个就执行对应的函数视图逻辑
  • 定义路由的方法:path()、re_path()、url()
    • Django==1.x版本:url()
    • Django==2.x版本:path()、re_path()
  • 说明:
    • 为了在版本迭代中,保留旧版本的路由系统,url()在新版中依然可用。
    • 并新增了一个url()的替代方案re_path(),所以url()几乎跟re_path()一样。

需求:

  • 用户通过网络地址http://127.0.0.1:8000/users/login/访问用户登录视图
  • 我们后续演示路由,都会选择使用类视图。

3.1 定义用户登录类视图

class LoginView(View):
    """用户登录类视图
    http://127.0.0.1:8000/users/login/
    """

    def get(self, request):
        """
        处理GET请求,返回登录页面
        :param request: 请求对象,包含了请求报文信息
        :return: 响应对象,用于构造响应报文,并响应给用户
        """
        return http.HttpResponse('假装这是个登录页面')

    def post(self, request):
        """
        处理POST请求,实现登录逻辑
        :param request: 请求对象,包含了请求报文信息
        :return: 响应对象,用于构造响应报文,并响应给用户
        """
        return http.HttpResponse('假装实现登录逻辑')

3.2 re_path()定义路由

1. 注册子路由

from django.urls import re_path, path

urlpatterns = [
    # 函数视图re_path()路由语法:
    # re_path(r'^网络地址正则表达式$', 函数视图名),

    # 类视图re_path()路由语法:
    # re_path(r'^网络地址正则表达式$', 类视图.as_view()),

    # 用户登录:http://127.0.0.1:8000/users/login/
    re_path(r'^users/login/$', views.LoginView.as_view()),
    #或者使用 path('users/login/', views.LoginView.as_view()),
]

3.3 url()定义路由

1. 注册子路由

from django.urls import re_path

urlpatterns = [
    # 函数视图url()路由语法:
    # url(r'^网络地址正则表达式$', 函数视图名),

    # 类视图url()路由语法:
    # url(r'^网络地址正则表达式$', 类视图.as_view()),

    # 用户登录:http://127.0.0.1:8000/users/login/
    url(r'^users/login/$', views.LoginView.as_view()),
]

2. postman进行请求测试

  • 使用postman分别向http://127.0.0.1:8000/users/login/发送GET和POST请求

3.4 路由方法对比

path()

# 函数视图path()路由语法:
# path('网络地址正则表达式', 函数视图名),

# 类视图path()路由语法:
# path('网络地址正则表达式', 类视图.as_view()),
  • path()路由语法中,不需要定义正则表达式严格的开头和结尾,因为已经封装好了

re_path()、url()

# 函数视图re_path()路由语法:
# re_path(r'^网络地址正则表达式$', 函数视图名),

# 类视图re_path()路由语法:
# re_path(r'^网络地址正则表达式$', 类视图.as_view()),
# 函数视图url()路由语法:
# url(r'^网络地址正则表达式$', 函数视图名),

# 类视图url()路由语法:
# url(r'^网络地址正则表达式$', 类视图.as_view()),
  • re_path()和url()路由语法中,必须要定义正则表达式严格的开头和结尾

3.5 路由解析顺序

  • Django的总路由和子路由都是定义在urlpatterns列表中的。
  • Django在接收到一个请求时,从总路由文件中的urlpatterns列表中以由上至下的顺序查找对应路由规则。
  • 如果发现规则在include中包含了,则再进入被包含的urls中的urlpatterns列表由上至下进行查询。

可能存在的问题:

  • 如果网络地址正则表达式没有写完整,比如,没有写严格的开头和结尾,那么就很容易出现前面的路由屏蔽掉了后面的路由。

提示:

  • 该问题只会出现在使用re_path()、url()定义路由时出现。
  • 因为 path() 定义路由时,网络地址正则表达式默认就是严格的开头和结尾。

例子:

class SayView(View):
    """测试路由屏蔽
    http://127.0.0.1:8000/say/
    """

    def get(self, request):
        return http.HttpResponse('say')


class SayHelloView(View):
    """测试路由屏蔽
    http://127.0.0.1:8000/sayhello/
    """

    def get(self, request):
        return http.HttpResponse('say hello')
# 测试路由屏蔽
# http://127.0.0.1:8000/say/
re_path(r'^say', views.SayView.as_view()),
# http://127.0.0.1:8000/sayhello/
re_path(r'^sayhello', views.SayHelloView.as_view()),

完整的、正确的路由定义方式:

# 测试路由屏蔽
# http://127.0.0.1:8000/say/
re_path(r'^say/$', views.SayView.as_view()),
# # http://127.0.0.1:8000/sayhello/
re_path(r'^sayhello/$', views.SayHelloView.as_view()),

4.请求HttpRequest

提示:

  • 用户发送请求时携带的参数后端需要使用,而不同的发送参数的方式对应了不同的提取参数的方式
  • 所以要学会如何提取参数,我们就需要先了解前端传参数有哪些方式

利用HTTP协议向服务器传参有以下几种途径

  • 查询字符串数据(query string):
    • 形如:?key1=value1&key2=value2
    • 比如:http://www.meiduo.site/list/115/1/?sort=price中的?sort=price
  • 请求体数据(body):
    • 比如:表单数据、json、……
  • URL路径中的特定部分数据:
    • 比如:http://www.meiduo.site/detail/2/中的/2/
    • 请求地址中的该部分数据,可以在路由中使用正则表达式提取出来
  • 请求头数据:
    • HTTP请求报文中的请求头数据(header)

4.1 提取查询字符串数据

提示:

  • 获取请求路径中的查询字符串参数,形如:?k1=v1&k2=v2
  • 可以通过request.GET属性获取,并返回QueryDict类型的对象
# 注册总路由
urlpatterns = [
    # 用户模块:http://127.0.0.1:8000/users/register/
    path('', include('users.urls')),

    # 请求和响应
    path('', include('request_response.urls')),
]
class QSParamView(View):
    """测试提取查询字符串参数
    http://127.0.0.1:8000/querystring/?name=zxc&age=18
    """

    def get(self, request):
        # 获取查询字符串参数name、age
        name = request.GET.get('name', '小明')
        age = request.GET.get('age', '0')

        return http.HttpResponse('查询字符串参数:%s--%s' % (name, age))
# 注册子路由
urlpatterns = [
    # 测试提取查询字符串参数:http://127.0.0.1:8000/querystring/?name=zxc&age=18
    path('querystring/', views.QSParamView.as_view()),
]

重要提示:

  • 提取查询字符串参数不区分请求方式,即使客户端进行POST方式的请求,依然可以通过request.GET获取请求中的查询字符串参数。

QueryDict补充:

  • QueryDict是由Django自己封装的一个数据类型,继承自python的字典Dict

  • 它被定义在django.http.QueryDict

  • 它专门用来存储请求中提取的查询字符串参数和请求体参数

    • 即,HttpRequest对象中的属性GET、POST都是QueryDict类型的数据
  • QueryDict

    的使用:

    # 如果键不存在则返回None值,可以设置默认值进行后续处理
    query_dict.get('键',默认值)
    # 可简写为:
    query_dict['键']

4.2 提取请求体数据

提示:

  • 可以发送请求体数据的请求方式有:POSTPUTPATCHDELETE
  • 请求体数据格式不固定,常见的有:表单类型数据和JSON字符串类型,我们应区别对待
4.2.1 表单类型请求体数据(Form Data)

前端发送的表单类型的请求体数据,可以通过request.POST属性获取,并返回QueryDict对象。

# 测试提取表单类型请求体数据:http://127.0.0.1:8000/formdata/
path('formdata/', views.FormDataParamView.as_view()),
class FormDataParamView(View):
    """测试提取表单类型请求体参数
    http://127.0.0.1:8000/formdata/
    """

    def post(self, request):
        # 获取表单类型请求体参数中的username、password
        username = request.POST.get('username')
        password = request.POST.get('password')

        return http.HttpResponse('表单类型请求体参数:%s--%s' % (username, password))

重要提示:

  • request.POST只能用来获取POST表单发送的请求体数据
4.2.2 非表单类型请求体数据(Non-Form Data):JSON

提示:

  • 非表单类型的请求体数据,Django无法自动解析,可以通过request.body属性获取最原始的请求体数据
  • 然后自己按照具体请求体原始数据的格式(JSON等)进行解析
  • request.body获取的是bytes类型的请求体原始数据

需求:

  • 获取请求体中的如下JSON数据
{
    "username": "张三",
    "password": "123"
}

可以进行如下方法操作:

# 测试提取非表单类型请求体参数:http://127.0.0.1:8000/json/
path('json/', views.JSONParamView.as_view()),
import json

class JSONParamView(View):
    """测试提取非表单类型请求体参数
    http://127.0.0.1:8000/json/
    """

    def post(self, request):
        # 获取请求体中原始的JSON数据
        json_str = request.body
        # 使用json模块将原始的JSON数据转字典
        json_dict = json.loads(json_str)

        # 提取JSON数据中的参数
        username = json_dict.get('username')
        password = json_dict.get('password')

        return http.HttpResponse('非表单类型请求体参数:%s--%s' % (username, password))

4.3 URL路径参数:提取URL路径中的特定部分数据

提示:

  • 在定义路由时,可以从URL中获取特定部分的路径参数
  • Django的路由系统会将提取的路径参数传递到视图的内部
  • path()和re_path()都可以提取路径参数

需求:

  • 需求1:

    http://127.0.0.1:8000/url_param1/18/
    • 提取路径中的数字18
  • 需求2:

    http://127.0.0.1:8000/url_param2/18500001111/
    • 提取路径中的手机号18500001111
4.3.1 path()提取路径参数

实现需求1

# 测试path()提取普通路径参数:http://127.0.0.1:8000/url_param1/18/
path('url_param1/<int:age>/', views.URLParam1View.as_view()),
class URLParam1View(View):
    """测试path()提取普通路径参数
    http://127.0.0.1:8000/url_param1/18/
    """

    def get(self, request, age):
        """
        :param age: 路由提取的关键字参数
        """
        return http.HttpResponse('测试path()提取普通路径参数:%s' % age)

重要提示:

  • 路由中提取路径参数时,使用的关键字,必须跟视图中参数名一致

思考:

  • 实现需求1时提取age数字的<int:age>是什么?

结论:

  • 路由转换器
  • Django默认封装了一些正则表达式,用于在path()中要提取路径参数时使用

默认的路由转换器:

  • 位置在django.urls.converters.py
DEFAULT_CONVERTERS = {
    'int': IntConverter(), # 匹配正整数,包含0
    'path': PathConverter(), # 匹配任何非空字符串,包含了路径分隔符
    'slug': SlugConverter(), # 匹配字母、数字以及横杠、下划线组成的字符串
    'str': StringConverter(), # 匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
    'uuid': UUIDConverter(), # 匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00
}

实现需求2

  • http://127.0.0.1:8000/url_param2/18500001111/
    • 提取路径中的手机号18500001111

问题:

  • 默认的路由转换器中,没有专门用来匹配手机号的路由转换器
  • 所以在使用path()实现需求2时,就无法直接使用默认的路由转换器

解决方案:

  • 如果默认的路由转换器无法满足需求时,我们就需要自定义路由转换器

实现需求2:自定义路由转换器

  • 在任意可以被导入的python文件中,都可以自定义路由转换器

  • 比如:在工程根目录下,新建converters.py文件,用于自定义路由转换器

    class MobileConverter:
      """自定义路由转换器:匹配手机号"""
      # 匹配手机号码的正则
      regex = '1[3-9]\d{9}'
    
      def to_python(self, value):
          # 将匹配结果传递到视图内部时使用
          return int(value)
    
      def to_url(self, value):
          # 将匹配结果用于反向解析传值时使用
          return str(value)
  • 注册自定义路由转换器

    • 在总路由中,注册自定义路由转换器

      from django.urls import register_converter
      from converters import MobileConverter
      # 注册自定义路由转换器
      # register_converter(自定义路由转换器, '别名')
      register_converter(MobileConverter, 'mobile')
      
      urlpatterns = []
  • 使用自定义路由转换器

    # 测试path()中自定义路由转换器提取路径参数:手机号 http://127.0.0.1:8000/url_param2/18500001111/
    path('url_param2/<mobile:phone_num>/', views.URLParam2View.as_view()),
    class URLParam2View(View):
      """测试path()中自定义路由转换器提取路径参数:手机号
      http://127.0.0.1:8000/url_param2/18500001111/
      """
    
      def get(self, request, phone_num):
          """
          :param phone_num: 路由提取的关键字参数
          """
          return http.HttpResponse('测试path()提取路径参数手机号:%s' % phone_num)
4.3.2 re_path()提取路径参数
# 测试re_path()提取路径参数:http://127.0.0.1:8000/url_param3/18500001111/
re_path(r'^url_param3/(?P<phone_num>1[3-9]\d{9})/$', views.URLParam3View.as_view()),
class URLParam3View(View):
    """测试re_path()提取路径参数
    http://127.0.0.1:8000/url_param3/18500001111/
    """

    def get(self, request, phone_num):
        """
        :param phone_num: 路由提取的关键字参数
        """
        return http.HttpResponse('测试re_path()提取路径参数:%s' % phone_num)
4.3.3 path()和re_path()如何选择?
  • path()语法相对简洁一些,如果没有路径参数要提取或者要提取的路径参数可以使用默认的路由转换器实现时,就选择path()。
  • re_path()语法相对复杂一些,但是,如果希望在匹配路由时,由自己编写所有的正则表达式,就选择re_path()。
  • 需要注意的是,在使用re_path()时,网络地址正则表达式一定要写完整,要有严格的开头和结尾

4.4 请求头

可以通过request.META属性获取请求头headers中的数据,request.META为字典类型

常见的请求头如:

  • CONTENT_LENGTH – The length of the request body (as a string).
  • CONTENT_TYPE – The MIME type of the request body.
  • HTTP_ACCEPT – Acceptable content types for the response.
  • HTTP_ACCEPT_ENCODING – Acceptable encodings for the response.
  • HTTP_ACCEPT_LANGUAGE – Acceptable languages for the response.
  • HTTP_HOST – The HTTP Host header sent by the client.
  • HTTP_REFERER – The referring page, if any.
  • HTTP_USER_AGENT – The client’s user-agent string.
  • QUERY_STRING – The query string, as a single (unparsed) string.
  • REMOTE_ADDR – The IP address of the client.
  • REMOTE_HOST – The hostname of the client.
  • REMOTE_USER – The user authenticated by the Web server, if any.
  • REQUEST_METHOD – A string such as "GET" or "POST".
  • SERVER_NAME – The hostname of the server.
  • SERVER_PORT – The port of the server (as a string).

具体使用如:

class HeadersParamView(View):
    """测试提取请求头参数"""

    def get(self, request):
        # 获取请求头中文件的类型
        ret = request.META.get('CONTENT_TYPE')
        return http.HttpResponse('OK')

4.5 其他常用HttpRequest对象属性

  • method:一个字符串,表示请求使用的HTTP方法,常用值包括:’GET’、’POST’。
  • FILES:一个类似于字典的对象,包含所有的上传文件。
  • COOKIES:一个字符串,包含了浏览器自动发送的cookie缓存数据。
  • user:请求中认证出来的用户对象。

5.响应HttpResponse

提示:

  • 视图在接收请求并处理后,必须返回HttpResponse对象或子对象。
  • HttpRequest对象由Django创建,HttpResponse对象或子对象由开发人员创建
  • 常见的响应方式:
    • HttpResponse():响应多种数据类型
    • JsonResponse():响应JSON
    • redirect():重定向
    • render():渲染并响应HTML模板

5.1 HttpResponse

提示:

  • 可以使用

    django.http.HttpResponse

    来构造响应对象。

    response = HttpResponse(content=响应体, content_type=响应体数据类型,默认为text/html, status=状态码,默认为200)

示例:

# 测试HttpResponse:http://127.0.0.1:8000/response1/
path('response1/', views.Response1View.as_view()),
class Response1View(View):
    """测试HttpResponse
    http://127.0.0.1:8000/response1/
    """

    def get(self, request):
        # 使用HttpResponse构造响应数据
        # return http.HttpResponse(content='itcast python', status=200)
        # 可简写
        # return http.HttpResponse('itcast python')

        # 另外一种写法
        response = http.HttpResponse('itcast python')
        return response

补充:HttpResponse子类

Django提供了一系列HttpResponse的子类,可以快速设置状态码

  • HttpResponseRedirect 默认响应状态码为 301
  • HttpResponsePermanentRedirect 默认响应状态码为 302
  • HttpResponseNotModified 默认响应状态码为 304
  • HttpResponseBadRequest 默认响应状态码为 400
  • HttpResponseNotFound 默认响应状态码为 404
  • HttpResponseForbidden 默认响应状态码为 403
  • HttpResponseNotAllowed 默认响应状态码为 405
  • HttpResponseGone 默认响应状态码为 410
  • HttpResponseServerError 默认响应状态码为 500

5.2 JsonResponse:响应JSON

提示:

  • 在开发功能时,如果前端需要JSON数据,那么后端就需要构造并响应JSON数据

  • 而Django提供了JsonResponse来构造并响应JSON数据

  • JsonResponse

    作用:

    • 帮助我们将响应的数据转换为JSON字符串
    • 设置响应头Content-Typeapplication/json

示例:

# 测试JSONResponse:http://127.0.0.1:8000/json_resp/
path('json_resp/', views.JSONResponseView.as_view()),
class JSONResponseView(View):
    """测试JSONResponse
    http://127.0.0.1:8000/json_resp/
    """

    def get(self, request):
        # 准备要响应的数据
        dict_data = {
            'city': 'beijing',
            'subject': 'python'
        }
        # 使用JSONResponse构造并响应JSON数据
        return http.JsonResponse(dict_data)

5.3 redirect():重定向

提示:

  • 在开发中,我们经常会遇到一种需求,当某个逻辑操作完成后,将用户引导到另外一个逻辑、页面中
    • 比如:用户注册、登录成功后,直接将用户引导到网站首页

解决方案:

  • redirect():重定向

需求:

  • 准备一个用于处理用户登录类视图LoginRedirectView
  • 访问LoginRedirectView时,如果其中的登录逻辑处理完成,我们将用户重定向到首页

示例:

# 测试重定向
path('login_redirect/', views.LoginRedirectView.as_view()),
path('index/', views.IndexView.as_view()),
from django.shortcuts import render, redirect

class IndexView(View):
    """测试重定向
    http://127.0.0.1:8000/index/
    """

    def get(self, request):
        return http.HttpResponse('假装这是个网站首页')


class LoginRedirectView(View):
    """测试重定向
    http://127.0.0.1:8000/login_redirect/
    """

    def post(self, request):
        # 假装正在处理登录逻辑
        # 假装登录逻辑处理完成
        # ......

        # 将用户通过重定向引导到首页
        return redirect('/index/')

5.4 redirect()重定向 搭配 反向解析

思考:

  • 我们定义的路由中的地址是否可能会做修改?
  • 如果我们定义的路由中的地址在某次开发新版本时被修改了,那么重定向的地方是否也需要跟着改变?
  • 如果该地址被很多地方都用到了,那么是否就意味着我们要修改代码的很多地方?

结论:

  • 以上思考的问题,确实会存在的
  • 我们定义的路由中的地址可能会在某次版本迭代时,做修改,使用新设计的地址
  • 那么一旦地址变了,所有用到这个地址的地方,代码都需要修改,还可能会修改很多个地方的代码
  • 而同时修改多个地方的代码,在开发中是个很危险的动作,而且也有一定的工作量

需求:

  • 能否可以实现一种效果,可以保证即使在版本迭代时,使用了新设计的地址替换了路由中原有的地址,我们之前编写的使用该地址的代码不用去修改,达到动态获取的目的。

解决方案:

  • 路由反向解析
  • 路由反向解析 是使用路由的别名,动态的解析出该路由中的真实地址

示例:

总路由中,给子应用的总路由起别名

urlpatterns = [
    # 请求和响应
    # path('', include(('子路由', '子应用名字'), namespace='总路由别名,可以随便命名')),
    path('', include(('request_response.urls', 'request_response'), namespace='request_response')),
]

子路由中,给子应用的子路由起别名

# 测试重定向
path('login_redirect/', views.LoginRedirectView.as_view()),
path('index/', views.IndexView.as_view(), name='index'),

视图中,使用路由的别名,动态的解析出该路由中的真实地址

from django.shortcuts import render, redirect, reverse


class IndexView(View):
    """测试重定向
    http://127.0.0.1:8000/index/
    """

    def get(self, request):
        return http.HttpResponse('假装这是个网站首页')


class LoginRedirectView(View):
    """测试重定向
    http://127.0.0.1:8000/login_redirect/
    """

    def post(self, request):
        # 假装正在处理登录逻辑
        # 假装登录逻辑处理完成
        # ......

        # 将用户通过重定向引导到首页
        # return redirect('/index/')

        # ret_url = reverse('总路由别名:子路由别名')
        ret_url = reverse('request_response:index')
        return redirect(ret_url)

6.中间件Middleware

6.1 中间件介绍

概念:

  • Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出
  • 中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性,其它的MVC框架也有这个功能

使用场景:

  • 当某些操作在每次请求或响应时都会执行时,可以写在中间件中
  • 比如,每次发送post请求都要进行CSRF验证,就把CSRF验证的代码写在中间件中

设计思想:

  • 面向切面编程、无侵害式编程
  • 不用直接修改框架源码,就可以达到自己想要的执行结果

默认的中间件

# 中间件
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

6.2 中间件方法

提示:

  • Django在中间件中预置了六个方法,这六个方法会在不同的阶段自动执行,对输入或输出进行干预。
  • 2.1 初始化方法:

    • 启动Django程序,初始化中间件时,自动调用一次,用于确定是否启用当前中间件

      def __init__(self, get_response=None):
        pass
  • 2.2 处理请求前的方法:(重要)

    • 在处理每个请求前,自动调用,返回None或HttpResponse对象

      def process_request(self, request):
        pass
  • 2.3 处理视图前的方法:(重要)

    • 在处理每个视图前,自动调用,返回None或HttpResponse对象

      def process_view(self, request, view_func, view_args, view_kwargs):
        pass
  • 2.4 处理模板响应前的方法:

    • 在处理每个模板响应前,自动调用,返回实现了render方法的响应对象

      def process_template_response(self, request, response):
        pass
  • 2.5 处理响应后的方法:(重要)

    • 在每个响应返回给客户端之前,自动调用,返回HttpResponse对象

      def process_response(self, request, response):
        pass
  • 2.6 异常处理:

    • 当视图抛出异常时,自动调用,返回一个HttpResponse对象

      def process_exception(self, request,exception):
        pass

6.3 自定义中间件

  • 中间件是一个独立的Python类,可以定义Django提供的六个方法中的一个或多个
  • 在工程根目录下,新建middlewares.py文件来自定义中间件
  • 我们在自定义的中间件中,会去实现最重要的三个方法
# 导入中间件的父类
from django.utils.deprecation import MiddlewareMixin


class TestMiddleware1(MiddlewareMixin):
    """自定义中间件"""
    def process_request(self, request):
        """处理请求前自动调用"""
        print('process_request1 被调用')

    def process_view(self, request, view_func, view_args, view_kwargs):
        # 处理视图前自动调用
        print('process_view1 被调用')

    def process_response(self, request, response):
        """在每个响应返回给客户端之前自动调用"""
        print('process_response1 被调用')
        return response

注册自定义的中间件

# 中间件
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'middlewares.TestMiddleware1', # 注册自定义的中间件1
]

6.4 中间件执行顺序

准备两个自定义的中间件

from django.utils.deprecation import MiddlewareMixin


class TestMiddleware1(MiddlewareMixin):
    """自定义中间件"""
    def process_request(self, request):
        """处理请求前自动调用"""
        print('process_request1 被调用')

    def process_view(self, request, view_func, view_args, view_kwargs):
        # 处理视图前自动调用
        print('process_view1 被调用')

    def process_response(self, request, response):
        """在每个响应返回给客户端之前自动调用"""
        print('process_response1 被调用')
        return response


class TestMiddleware2(MiddlewareMixin):
    """自定义中间件"""
    def process_request(self, request):
        """处理请求前自动调用"""
        print('process_request2 被调用')

    def process_view(self, request, view_func, view_args, view_kwargs):
        # 处理视图前自动调用
        print('process_view2 被调用')

    def process_response(self, request, response):
        """在每个响应返回给客户端之前自动调用"""
        print('process_response2 被调用')
        return response

注册多个自定义的中间件

# 中间件
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'middlewares.TestMiddleware1', # 注册自定义的中间件1
    'middlewares.TestMiddleware2', # 注册自定义中的间件2
]

重要提示:中间件执行顺序

  • 在视图被处理前(输入),中间件由上至下依次执行
  • 在视图被处理后(输出),中间件由下至上依次执行

三、模型(models)

提示:

  • 模型是Django程序处理数据库数据的模块
  • Django的模型是定义在子应用的models.py中的

经验值分享

1. 读取外键

  • 以图书和英雄这两个模型类为例
class BookInfo(models.Model):
    """图书信息模型类"""
    btitle = models.CharField(max_length=20, verbose_name='名称')
class HeroInfo(models.Model):
    hname = models.CharField(max_length=20, verbose_name='名称') 
    # 外键
    hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书')
  • 读取外键的方式:
    • 方式一:hero.hbook.id
    • 方式二:hero.hbook_id
  • 问题:
    • hero.hbook.id:不安全,如果hbook为空,会报错,因为空对象不能读取任何属性
    • hero.hbook_id:安全,如果hbook_id为空,不会报错,获取的是空值
  • 结论:
    • 如果外键允许为空,那么务必使用方式二读取外键
    • 如果外键一定不为空,那么使用哪种方式读取外键都可以

2. 查询集QuerySet缓存的特点

  • 参考知识点:查询集QuerySet
  • 提示:
    • 查询集表示从数据库中获取的对象集合。具有自动缓存的特点。
  • 查询集自动缓存:
    • 使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。
  • 问题:
    • 如果某些数据需要频繁的更新,那么在查询和使用时就不能有缓存出现
    • 比如:实时更新库存和销量,库存和销量每次在使用时必须是最新的结果,不能是之前缓存中的结果
  • 结论:
    • 如果我们要频繁的更新数据时,那么要更新的数据不要使用查询集获取
    • 返回查询集的方法:all()、filter()、exclude()、order_by()
    • 不返回查询集的方法:get()
    • 所以如果要实时更新数据,建议采用get()查询要更新的数据

1.准备数据库

1.1 创建MySQL数据库

在ubuntu系统的MySQL程序中创建一个数据库

mysql -uroot -p

create database django_demo default charset=utf8;

1.2 配置MySQL数据库

settings.py中配置数据库的连接信息

  • 以下是默认的数据库配置信息
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

修改DATABASES配置信息

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '192.168.103.240',  # 数据库主机
        'PORT': 3306,  # 数据库端口
        'USER': 'root',  # 数据库用户名
        'PASSWORD': 'mysql',  # 数据库用户密码
        'NAME': 'django_demo'  # 数据库名字
    }
}

1.3 安装mysqlclient:MySQL数据库的客户端驱动

安装mysqlclient

# 进入虚拟环境
pip install mysqlclient -i https://pypi.tuna.tsinghua.edu.cn/simple/

1.4 解决mysqlclient安装出错的问题

1. 更换ubuntu中默认的源为国内的源:提升软件下载速度
2. 更新apt-get的源和升级
3. 安装libmysqlclient-dev:因为mysqlclient依赖这个软件
4. 虚拟环境中安装mysqlclient==1.4.6
5. 测试:重启Django程序

1.4.1 更换ubuntu中默认的源为国内的源

# 第一步:备份 /etc/apt/sources.list

# 第二步:在/etc/apt/sources.list中添加以下阿里源
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
# 备份
$ cp /etc/apt/sources.list /etc/apt/sources.list.bak
# 添加阿里源
$ sudo vim /etc/apt/sources.list

1.4.2 更新apt-get的源和升级

  • 其中upgrade会执行很久,大家需要耐心等待
$ sudo apt-get update
$ sudo apt-get upgrade

1.4.3 安装libmysqlclient-dev

$ sudo apt-get install libmysqlclient-dev

2.模型类迁移建表

如何创建数据库表?

  • 可以使用原生的SQL语句创建数据库表
  • 也可以使用Django提供的模型类创建数据库表

如何使用Django提供的模型类创建数据库表?

  • 我们需要了解ORM框架
  • 我们需要学会定义模型类
  • 我们需要学会迁移模型类建表

2.1 ORM框架

ORM框架介绍

  • O是object,也就是类或者对象的意思,这里的类就是模型类
  • R是relation,也就是关系数据库中数据表的意思
  • M是mapping,也就是映射的意思
  • 在ORM框架中,它帮我们把模型类和数据表进行了一个映射,可以让我们通过模型类及对象就能操作它所对应的数据表中的数据
  • ORM框架它还可以根据我们设计的模型类自动帮我们生成数据库中对应的数据表,省去了我们自己建表的过程

提示:

  • Django框架中内嵌了ORM框架,所以在使用Django框架时,我们不需要直接面向数据库编程
  • 而是定义模型类,通过模型类及对象完成数据表的增删改查操作

ORM框架作用:

  • 帮助Django的开发者以面向对象的思想去操作数据库。
  • 并且ORM框架也帮助程序员屏蔽了数据库之间的差异。

2.2 定义模型类

提示:模型类的定义思路

1. 先根据需求设计数据表
2. 再根据数据表设计方案定义模型类

2.2.1 根据需求设计数据表

  • 需求:
    • “图书-英雄”管理
  • 分析关联关系:
    • 一本书里面会有多个英雄人物,每个英雄人物都会属于某一本书
    • 数据表一:图书信息表 (一方)
    • 数据表二:英雄信息表 (多方)
  • 绑定关联关系:
    • 外键定义在多方对应的数据表中,即,外键需要定义在英雄信息表中

2.2.2 定义模型类

  • 模型类被定义在子应用/models.py文件中
  • 模型类必须继承自Model类,位于django.db.models
  • 创建子应用booktest,并在其models.py文件中定义模型类
class BookInfo(models.Model):
    """图书信息:演示一对多,一方"""
    btitle = models.CharField(max_length=20, verbose_name='书名')
    bpub_date = models.DateField(verbose_name='发布日期')
    bread = models.IntegerField(default=0, verbose_name='阅读量')
    bcomment = models.IntegerField(default=0, verbose_name='评论量')
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

    class Meta:
        """模型类的元类:用于修改、配置模型类对应的数据表"""
        db_table = 'tb_books'  # 自定义数据库表名

    def __str__(self):
        """定义每个数据对象的显示信息"""
        return self.btitle # 输出该模型数据对象时,只输出书名


class HeroInfo(models.Model):
    """英雄信息:演示一对多,多方"""
    # 确定性别字段的取值范围
    GENDER_CHOICES = (
        (0, 'female'),
        (1, 'male')
    )
    hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='英雄属于的图书')
    hname = models.CharField(max_length=20, verbose_name='人名')
    hgender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
    hcomment = models.CharField(max_length=200, null=True, verbose_name='描述信息')
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

    class Meta:
        db_table = 'tb_heros'

    def __str__(self):
        return self.hname

2.3 模型类说明

2.3.1 关于主键

  • Django会为表创建自动增长的主键列,每个模型只能有一个主键列
  • 默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key
  • 如果使用选项设置某属性为主键列后Django不会再创建自动增长的主键列

2.3.2 关于属性命名

  • 不能是python的保留关键字

  • 不允许使用连续的下划线,这是由Django的查询方式决定的

  • 定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:

    属性 = models.字段类型(选项)

2.3.3 关于数据库表名

  • 模型类如果未指明表名,Django默认以 小写app应用名_小写模型类名 为数据库表名
  • 但是,可通过模型的元类中的db_table自定义数据库表名

2.3.4 关于字段类型

CREATE TABLE `tb_books` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `btitle` varchar(20) NOT NULL,
  `bpub_date` date NOT NULL,
  `bread` int(11) NOT NULL,
  `bcomment` int(11) NOT NULL,
  `is_delete` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
类型 说明
AutoField 自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性
BooleanField 布尔字段,值为True或False
NullBooleanField 支持Null、True、False三种值
CharField 字符串,参数max_length表示最大字符个数
TextField 大文本字段,一般超过4000个字符时使用
IntegerField 整数
DecimalField 十进制浮点数, 参数max_digits表示总位数, 参数decimal_places表示小数位数
FloatField 浮点数
DateField 日期, 参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于”最后一次修改”的时间戳,它总是使用当前日期,默认为False; 参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为False; 参数auto_now_add和auto_now是相互排斥的,组合将会发生错误
TimeField 时间,参数同DateField
DateTimeField 日期时间,参数同DateField
FileField 上传文件字段
ImageField 继承于FileField,对上传的内容进行校验,确保是有效的图片

2.3.5 关于字段选项

选项 说明
null 如果为True,表示允许为空,默认值是False
db_column 字段的名称,如果未指定,则使用属性的名称
db_index 若值为True, 则在表中会为此字段创建索引,默认值是False
default 默认
primary_key 若为True,则该字段会成为模型的主键字段,默认值是False,一般作为AutoField的选项使用
unique 如果为True, 这个字段在表中必须有唯一值,默认值是False

2.3.6 关于外键

在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:

  • CASCADE 级联,删除主表数据时连通一起删除外键表中数据
  • PROTECT 保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据
  • SET_NULL 设置为NULL,仅在该字段null=True允许为null时可用
  • SET_DEFAULT 设置为默认值,仅在该字段设置了默认值时可用
  • SET() 设置为特定值或者调用特定方法
  • DO_NOTHING 不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常

2.4 迁移模型类建表

2.4.1 生成迁移文件

python manage.py makemigrations

2.4.2 同步到数据库中

python manage.py migrate

2.5 添加测试数据

insert into tb_books(btitle,bpub_date,bread,bcomment,is_delete) values
('射雕英雄传','1980-5-1',12,34,0),
('天龙八部','1986-7-24',36,40,0),
('笑傲江湖','1995-12-24',20,80,0),
('雪山飞狐','1987-11-11',58,24,0);
insert into tb_heros(hname,hgender,hbook_id,hcomment,is_delete) values
('郭靖',1,1,'降龙十八掌',0),
('黄蓉',0,1,'打狗棍法',0),
('黄药师',1,1,'弹指神通',0),
('欧阳锋',1,1,'蛤蟆功',0),
('梅超风',0,1,'九阴白骨爪',0),
('乔峰',1,2,'降龙十八掌',0),
('段誉',1,2,'六脉神剑',0),
('虚竹',1,2,'天山六阳掌',0),
('王语嫣',0,2,'神仙姐姐',0),
('令狐冲',1,3,'独孤九剑',0),
('任盈盈',0,3,'弹琴',0),
('岳不群',1,3,'华山剑法',0),
('东方不败',0,3,'葵花宝典',0),
('胡斐',1,4,'胡家刀法',0),
('苗若兰',0,4,'黄衣',0),
('程灵素',0,4,'医术',0),
('袁紫衣',0,4,'六合拳',0);

3.增删改查

3.1 shell工具 (用于在终端交互环境测试代码的)

Django的manage工具提供了shell命令,帮助我们配置好当前工程的运行环境(如连接好数据库等),以便可以直接在终端中执行测试python语句。

如果有需要,通过如下命令进入shell

python manage.py shell

导入两个模型类,以便后续使用

from booktest.models import BookInfo, HeroInfo

3.2 新增

增加数据有两种方法。

1)save

通过创建模型类对象,模型对象.save()方法保存到数据库中。

# 新增:方式一
book = BookInfo()
book.btitle = '西游记'
book.bpub_date = '2020-05-18'
book.bread = 20
book.bcomment = 30
book.save()

2)create

通过模型类.objects.create()保存。

# 新增:方式二
BookInfo.objects.create(
    btitle='三国演义',
    bpub_date='2020-05-20',
    bread=100,
    bcomment=200
)

3.3 修改

修改更新有两种方法

1)save

修改模型类对象的属性,然后执行save()方法

hero = HeroInfo.objects.get(hname='猪八戒')
hero.hname = '猪悟能'
hero.save()

2)update

使用模型类.objects.filter().update(),会返回受影响的行数

HeroInfo.objects.filter(hname='沙悟净').update(hname='沙僧')

3.4 删除

删除有两种方法

1)模型类对象delete

hero = HeroInfo.objects.get(id=13)
hero.delete()

2)模型类.objects.filter().delete()

HeroInfo.objects.filter(id=14).delete()

3.5 查询

3.5.1 基本查询

get 查询单一结果,如果不存在会抛出模型类.DoesNotExist异常。

all 查询多个结果。

count 查询结果数量。

>>> BookInfo.objects.all()
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>, <BookInfo: 西游记>]>
>>> book = BookInfo.objects.get(btitle='西游记')
>>> book.id
5

>>> BookInfo.objects.get(id=3)
<BookInfo: 笑傲江湖>
>>> BookInfo.objects.get(pk=3)
<BookInfo: 笑傲江湖>
>>> BookInfo.objects.get(id=100)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/delron/.virtualenv/dj/lib/python3.6/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/delron/.virtualenv/dj/lib/python3.6/site-packages/django/db/models/query.py", line 380, in get
    self.model._meta.object_name
db.models.DoesNotExist: BookInfo matching query does not exist.

>>> BookInfo.objects.count()
6
3.5.2 过滤查询

实现SQL中的where功能,包括

  • filter 过滤出满足条件的多个结果
  • exclude 排除掉符合条件剩下的结果

过滤条件的表达语法如下:

属性名称__比较运算符=值
# 属性名称和比较运算符间使用两个下划线,所以属性名不能包括多个下划线

1)相等

exact:表示判等。

例:查询编号为1的图书。

BookInfo.objects.filter(id__exact=1)
可简写为:
BookInfo.objects.filter(id=1)

2)模糊查询

contains:是否包含。

说明:如果要包含%无需转义,直接写即可。

例:查询书名包含’传’的图书。

BookInfo.objects.filter(btitle__contains='传')

startswith、endswith:以指定值开头或结尾。

例:查询书名以’部’结尾的图书

BookInfo.objects.filter(btitle__endswith='部')

以上运算符都区分大小写,在这些运算符前加上i表示不区分大小写,如iexact、icontains、istartswith、iendswith.

3) 空查询

isnull:是否为null。

例:查询书名不为空的图书。

BookInfo.objects.filter(btitle__isnull=False)

4) 范围查询

in:是否包含在范围内。

例:查询编号为1或3或5的图书

BookInfo.objects.filter(id__in=[1, 3, 5])

5)比较查询

  • gt 大于 (greater then)
  • gte 大于等于 (greater then equal)
  • lt 小于 (less then)
  • lte 小于等于 (less then equal)

例:查询编号大于3的图书

BookInfo.objects.filter(id__gt=3)

不等于的运算符,使用exclude()过滤器。

例:查询编号不等于3的图书

BookInfo.objects.exclude(id=3)

6)日期查询

year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。

例:查询1980年发表的图书。

BookInfo.objects.filter(bpub_date__year=1980)

例:查询1980年1月1日后发表的图书。

BookInfo.objects.filter(bpub_date__gt=date(1990, 1, 1))
F对象

之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢? 答:使用F对象,被定义在django.db.models中。

语法如下:

F(属性名)

例:查询阅读量大于等于评论量的图书。

from django.db.models import F

BookInfo.objects.filter(bread__gte=F('bcomment'))

可以在F对象上使用算数运算。

例:查询阅读量大于2倍评论量的图书。

BookInfo.objects.filter(bread__gt=F('bcomment') * 2)
Q对象

多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。

例:查询阅读量大于20,并且编号小于3的图书。

BookInfo.objects.filter(bread__gt=20,id__lt=3)
或
BookInfo.objects.filter(bread__gt=20).filter(id__lt=3)

如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符,Q对象被义在django.db.models中。

语法如下:

Q(属性名__运算符=值)

例:查询阅读量大于20的图书,改写为Q对象如下。

from django.db.models import Q

BookInfo.objects.filter(Q(bread__gt=20))

Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或。

例:查询阅读量大于20,或编号小于3的图书,只能使用Q对象实现

BookInfo.objects.filter(Q(bread__gt=20) | Q(pk__lt=3))

Q对象前可以使用~操作符,表示非not。

例:查询编号不等于3的图书。

BookInfo.objects.filter(~Q(pk=3))
聚合函数

使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg 平均,Count 数量,Max 最大,Min 最小,Sum 求和,被定义在django.db.models中。

例:查询图书的总阅读量。

from django.db.models import Sum

BookInfo.objects.aggregate(Sum('bread'))

注意aggregate的返回值是一个字典类型,格式如下:

  {'属性名__聚合类小写':值}
  如:{'bread__sum':3}

使用count时一般不使用aggregate()过滤器。

例:查询图书总数。

BookInfo.objects.count()

注意count函数的返回值是一个数字。

3.5.3 排序

使用order_by对结果进行排序

BookInfo.objects.all().order_by('bread')  # 升序
BookInfo.objects.all().order_by('-bread')  # 降序
3.5.4 关联查询

由一到多的访问语法:

一对应的模型类对象.多对应的模型类名小写_set
例:

b = BookInfo.objects.get(id=1)
b.heroinfo_set.all()

由多到一的访问语法:

多对应的模型类对象.多对应的模型类中的关系类属性名
例:

h = HeroInfo.objects.get(id=1)
h.hbook

访问一对应的模型类关联对象的id语法:

多对应的模型类对象.关联类属性_id

例:

h = HeroInfo.objects.get(id=1)
h.hbook_id

4.查询集QuerySet

4.1 概念

Django的ORM中存在查询集的概念。

查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。

当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表):

  • all():返回所有数据。
  • filter():返回满足条件的数据。
  • exclude():返回满足条件之外的数据。
  • order_by():对结果进行排序。

对查询集可以再次调用过滤器进行过滤,如

BookInfo.objects.filter(bread__gt=30).order_by('bpub_date')

也就意味着查询集可以含有零个、一个或多个过滤器。过滤器基于所给的参数限制查询的结果。

从SQL的角度讲,查询集与select语句等价,过滤器像where、limit、order by子句。

判断某一个查询集中是否有数据

  • exists():判断查询集中是否有数据,如果有则返回True,没有则返回False。

4.2 两大特性

1)惰性执行

创建查询集不会访问数据库,直到调用数据时,才会访问数据库,调用数据的情况包括迭代、序列化、与if合用

例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集qs

qs = BookInfo.objects.all()

继续执行遍历迭代操作后,才真正的进行了数据库的查询

for book in qs:
    print(book.btitle)
2)缓存

使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。

情况一:如下是两个查询集,无法重用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载。

from booktest.models import BookInfo
[book.id for book in BookInfo.objects.all()]
[book.id for book in BookInfo.objects.all()]

情况二:经过存储后,可以重用查询集,第二次使用缓存中的数据。

qs=BookInfo.objects.all()
[book.id for book in qs]
[book.id for book in qs]

四、模板(templates)

提示:

  • 模板是Django程序渲染页面的模块
  • Django的模板是定义在templates文件目录中的

1. 配置模板

1.1 准备模板文件目录

  • 在工程根目录下创建模板文件目录templates

1.2 配置模板

  • settings.py

    配置文件中修改

    TEMPLATES

    配置项的

    DIRS

    值:

    # 配置模板
    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',
              ],
          },
      },
    ]

2. 定义和响应模板

2.1 新建模板文件

  • templates目录中新建一个模板文件,如:temp.html

2.2 响应模板文件

class TempView(View):
    """图书信息
    http://127.0.0.1:8000/temp/
    """

    def get(self, request):
        return render(request, 'temp.html')

3. 测试模板渲染

3.1 模板语法

  • 模板语法如下:

    • 变量名必须由字母、数字、下划线(不能以下划线开头)和点组成

3.2 使用模板语法渲染HTML模板

class BooksView(View):
    """图书信息
    http://127.0.0.1:8000/books/
    """

    def get(self, request):
        # 构造上下文
        context = {
            'name': '张三',
            'age': 18
        }
        # 使用上下文渲染'book.html',并返回给客户端
        return render(request, 'book.html', context)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>图书信息页</title>
</head>
<body>
    <h1>{{ name }}</h1>
    <h1>{{ age }}</h1>
</body>
</html>

4. 数据库数据渲染模板

需求:

  • 渲染数据库中所有的图书信息
class BooksView(View):
    """测试模板
    http://127.0.0.1:8000/books/
    """

    def get(self, request):
        # 查询所有图书信息
        books = BookInfo.objects.all()

        # 构造上下文
        context = {
            'books': books
        }
        # 使用上下文渲染'book.html',并返回给客户端
        return render(request, 'books.html', context)
<body>
    <ul>
        {% for book in books %}
            
  • 《{{ book.btitle }}》
  • {% endfor %} </ul> </body>

    五、cookie和session

    提示:

    • Cookie和Session是Django程序中用来缓存数据的

    经验值分享

    1. 状态保持和Cookie、Session的关系

    提示:

    • 容易出现的误解:状态保持就是Cookie和Session,Cookie和Session就是状态保持。
    • 状态保持和Cookie、Session是两种不同的概念,需要区分开,不能混淆。

    状态保持:

    * 用于记录当前用户的状态,比如,用户登录后记录登录的状态。

    Cookie、Session:

    * Cookie、Session仅仅是Django提供的缓存机制而已,用于缓存数据的。
    * 比如:
        * Cookie和Session缓存购物车数据
        * Cookie和Session缓存用户登录状态 (状态保持)

    状态保持和Cookie、Session的关系

    * Cookie和Session仅仅是状态保持的一种实现方式而已
    * 而能够实现状态保持的方式不仅仅只有Cookie和Session,比如JWT也可以实现状态保持

    提示:

    • HTTP协议本身是无状态的,即服务器无法判断用户身份。

    问题:

    • 服务器默认是无法识别当前登录用户是谁的,即无法记住登录状态

    解决:

    • Cookie
    • 我们可以使用Cookie机制记住用户的登录状态

    1.1 Cookie的介绍

    • Cookie,最早是网景公司的前雇员 Lou Montulli 在 1993 年 3 月发明的。
    • Cookie,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。
    • Cookie,实际上是一小段存储在用户浏览器中的文本信息

    Cookie的机制:

    • 客户端向服务器发起请求,如果服务器需要记录该用户状态,就通过响应向客户端浏览器颁发一个Cookie。
      • 该Cookie中需要包含用户的唯一标识信息
    • 客户端浏览器会把Cookie保存起来。
    • 当浏览器再次请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。
    • 服务器检查该Cookie,以此来辨认用户状态。

    Cookie的特点:

    • Cookie,以键值对Key-Value形式进行信息的存储

    • Cookie,基于域名安全,不同域名的

      Cookie

      是不能互相访问的

      • 例如: 保存在www.baidu.com域名下面的Cookie是不能被其他域名访问的
    • Cookie,它会自动跟随当前的请求传递到对应域名所在的服务器中。

    2.2 操作Cookie

    提示:

    • 关于操作Cookie,分为设置Cookie读取Cookie
    2.2.1 设置Cookie

    可以通过HttpResponse()对象中的set_cookie()方法来设置Cookie。

    # 创建响应对象
    response = HttpResponse()
    # 使用响应对象设置Cookie
    response.set_cookie(key, value, max_age=cookie有效期)
    • 响应对象:常见的构造响应对象的方式
      • HttpResponse():响应多种数据类型
      • JsonResponse():响应JSON
      • redirect():重定向
      • render():渲染并响应HTML模板
    • max_age:单位为秒,默认为None
      • 如果是临时Cookie,可将max_age设置为None
      • 如果是有具体有效期的Cookie,可将max_age设置为具体的秒数

    示例:

    class BooksView(View):
        """测试模板
        http://127.0.0.1:8000/books/
        """
    
        def get(self, request):
            # 查询所有图书信息
            books = BookInfo.objects.all()
    
            # 构造上下文
            context = {
                'books': books
            }
            # 使用上下文渲染'book.html',并返回给客户端
            response = render(request, 'books.html', context)
    
            # 设置Cookie
            response.set_cookie('name', 'itcast', max_age=None)
    
            # 响应结果,并写Cookie到浏览器
            return response
    2.2.2 读取Cookie

    可以通过HttpRequest对象的COOKIES属性来读取本次请求携带的cookie值。

    class TestCookieView(View):
        """测试Cookie
        http://127.0.0.1:8000/cookies/
        """
    
        def get(self, request):
            # 读取Cookie
            name = request.COOKIES.get('name')
            print(name)
    
            return http.HttpResponse('测试Cookie')

    2.session

    2.1.Session的介绍

    • Session,是一种会话控制方式。由服务端创建,并且保存在服务端的数据存储形式。

    • Session,内部也是以key-value 键值对的形式存储数据。

    • Session,有些内容会被加密,所以可以存储敏感信息。

    • 处理Session数据的子应用:

    • INSTALLED_APPS = [
          'django.contrib.admin',
          'django.contrib.auth',
          'django.contrib.contenttypes',
          'django.contrib.sessions',       <============
          'django.contrib.messages',
          'django.contrib.staticfiles',

    处理Session的中间件:

    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',
    ]

    PS:如需禁用Session,将上图中的session中间件注释掉即可。

    Session的机制:

    • 客户端向服务器发起请求,如果服务器需要记录该用户状态,就可以通过Session在服务端将该用户的唯一标识信息存储起来。
      • session_key:一个随机的唯一的不重复的字符串
      • session_data:用户的唯一标识信息(密文)
    • 然后,服务端会向客户端浏览器颁发一个Cookie。
      • 该Cookie中包含了Session存储数据时使用的那个session_key
      • 该Cookie的具体形式为:'session_id': 'session_key'
    • 当浏览器再次请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。
    • 服务器提取该Cookie中的session_key,再使用它提取session_data
    • 最后使用session_data来辨认用户状态

    Session的特点:

    • 存储敏感、重要的信息
      • 因为session数据是存储在服务端的,不会直接暴露给用户
    • 相比较Cookie可以存储更多的内容
      • 不同的浏览器,对Cookie都有不同的数量和大小的限制
    • 依赖于Cookie
      • 因为session_key需要存储在Cookie中
    • Session共享
      • 利用独立部署的session服务器(集群)统一管理Session,服务器每次读写Session时,都访问Session服务器。

    2.2 操作Session

    提示:

    • 对于Session的操作,分为设置Session读取session
    2.2.1 设置Session

    可以通过HttpRequest()对象中的session属性来设置Session。

    request.session['key'] = value
    class BooksView(View):
        """测试模板
        http://127.0.0.1:8000/books/
        """
    
        def get(self, request):
            # 查询所有图书信息
            books = BookInfo.objects.all()
    
            # 构造上下文
            context = {
                'books': books
            }
            # 使用上下文渲染'book.html',并返回给客户端
            response = render(request, 'books.html', context)
    
            # 设置Cookie
            response.set_cookie('name', 'itcast', max_age=3600)
    
            # 设置Session
            request.session['name'] = 'itcast'
    
            # 响应结果,并写Cookie到浏览器
            return response
    2.2.2 读取Session

    根据键读取值

    request.session.get('key', 默认值)
    class TestSessionView(View):
        """测试Session
        http://127.0.0.1:8000/session/
        """
    
        def get(self, request):
            # 读取Session
            name = request.session.get('name')
            print(name)
    
            return http.HttpResponse('测试Session')
    2.2.3 操作Session的其他方式

    清除所有Session,在存储中删除值部分。

    request.session.clear()

    清除session数据,在存储中删除session的整条数据。

    request.session.flush()

    删除session中的指定键及值,在存储中只删除某个键及对应的值。

    del request.session['key']

    设置session的有效期

    request.session.set_expiry(value)
    • 如果value是一个整数,session将在value秒没有活动后过期。
    • 如果value为0,那么用户session的Cookie将在用户的浏览器关闭时过期。
    • 如果value为None,那么session有效期将采用系统默认值,默认为两周。
      • 可以通过在settings.py中设置SESSION_COOKIE_AGE来设置全局默认值。

    2.3 Session数据存储的位置

    2.3.1 默认的存储位置

    Session数据默认存储的位置是在settings.pyDATABASES配置项指定的SQL数据库中

    • 因为Session引擎默认的配置为:

      SESSION_ENGINE = 'django.contrib.sessions.backends.db'
    2.3.2 本地缓存

    存储在本机内存中,如果丢失则不能找回,比数据库的方式读写更快。

    SESSION_ENGINE='django.contrib.sessions.backends.cache'
    2.3.3 混合存储

    优先从本机内存中存取,如果没有则从数据库中存取。

    SESSION_ENGINE='django.contrib.sessions.backends.cached_db'
    2.3.4 Redis

    在Redis中保存Session,需要引入第三方扩展,我们可以使用django-redis来解决。

    1)安装扩展

    pip install django-redis

    2)配置

    在settings.py文件中做如下设置

    CACHES = {
        "default": {
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://127.0.0.1:6379/1",
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
            }
        }
    }
    SESSION_ENGINE = "django.contrib.sessions.backends.cache"
    SESSION_CACHE_ALIAS = "default"