Django

7 篇文章 0 订阅
7 篇文章 0 订阅

1. 简介

1.1 定义

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

1.2 特点

  1. 重量级框架
  • Django框架相比较于Python其他的Web框架而言是大而全的。
  • Django提供了原生的众多功能组件,让开发更简便快速。
  • 提供项目工程管理的自动化脚本工具。
  • 支持ORM以面向对象的形式操作数据库。(Object Relational Mapping)
  • 提供了强大的模板引擎,用于渲染页面。
  • 提供了文件管理、认证权限、session机制和缓存。
  1. 遵守MVT设计模式
    MVC设计模式说明
    • MVC 的全拼为 Model-View-Controller
    • M 全拼为 Model,主要封装对数据库层的访问,对数据库中的数据进行增、删、改、查操作。
    • V 全拼为 View,用于封装结果,生成页面展示的html内容。
    • C 全拼为 Controller,用于接收请求,处理业务逻辑,与Model和View交互,返回结果。
    • MVC 的核心思想是分工、解耦,让不同的代码块之间降低耦合,增强代码的可扩展性和可移植性,实现向后兼容。

mvc
Django的MVT设计模式说明
* MVT 的全拼为 Model-View-Template
* M 全拼为 Model,与MVC中的M功能相同,负责和数据库交互,进行数据处理。
* V 全拼为 View,与MVC中的C功能相同,接收请求,进行业务处理,返回应答。
* T 全拼为 Template,与MVC中的V功能相同,负责封装构造要返回的html。
MVT 的核心思想和 MVC 是相同的
mvt

1.3 参考文档

官方网站:https://www.djangoproject.com/
Github源码:https://github.com/django/django
2.2版中文文档:https://docs.djangoproject.com/zh-hans/2.2/

1.4 虚拟环境

1. 作用

  • 在开发过程中, 当需要使用python的某些工具包/框架时需要联网安装
    • 比如联网安装Django框架django==2.2.5版本
      sudo pip install django==2.2.5
  • 提示:使用如上命令, 会将django==2.2.5安装到/usr/local/lib/python版本/dist-packages路径下
  • 问题 : 如果在一台电脑上, 想开发多个不同的项目, 需要用到同一个包的不同版本, 如果使用上面的命令, 在同一个目录下安装或者更新, 新版本会覆盖以前的版本, 其它的项目就无法运行了。
  • 解决方案 : 虚拟环境
    • 作用 :虚拟环境可以搭建独立的python运行环境, 使得单个项目的运行环境与其它项目互不影响.
      所有的虚拟环境都位于/home/下的隐藏目录.virtualenvs

2. 步骤

  • 安装虚拟环境的命令 :
sudo pip install virtualenv
sudo pip install virtualenvwrapper
  • 创建虚拟环境的命令 :
    • 提示:如果不指定python版本,默认安装的是python2的虚拟环境
    • 在python2中,创建虚拟环境
mkvirtualenv 虚拟环境名称
例 :
mkvirtualenv py_django
  • 在python3中,创建虚拟环境
mkvirtualenv -p python3 虚拟环境名称
例 :
mkvirtualenv -p python3 py3_django

在这里插入图片描述
提示 :

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

3. 使用

  • 查看虚拟环境的命令 :
  workon 两次tab键

在这里插入图片描述

  • 使用虚拟环境的命令 :
  workon 虚拟环境名称
  例:
  workon py3_django

在这里插入图片描述

  • 退出虚拟环境的命令 :
  deactivate

在这里插入图片描述

  • 删除虚拟环境的命令 :
  rmvirtualenv 虚拟环境名称

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

在这里插入图片描述

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

  • 虚拟环境中安装框架、包命令 :
  pip install 框架、包名称

  例 : 安装`django==2.2.5`
  pip install django==2.2.5

在这里插入图片描述

  • 框架、包安装的位置 :
    • ~/.virtualenvs/虚拟环境名称/lib/python版本/site-packages
  • 查看虚拟环境中安装的包 :
pip freeze

在这里插入图片描述

5. 特别提示

  • 在虚拟环境中,直接使用 pip install安装Django框架或者扩展包时,速度特别慢,甚至报红色警告。
  • 这主要是因为Django框架和很多的扩展包都是从国外服务器进行下载安装的。
    指定镜像源:加速下载安装Django框架或者扩展包
pip install django==2.2.5 -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/

6. 虚拟环境和pip相关命令

# 虚拟环境
workon  # 进入虚拟环境、查看所有虚拟环境
mkvirtualenv    # 创建虚拟环境
rmvirtualenv    # 删除虚拟环境
deactivate      # 退出虚拟环境

# pip
pip install    # 安装依赖包
pip uninstall  # 卸载依赖包
pip list       # 查看已安装的依赖库

1.5 Django工程创建

  • 使用Django框架时,项目工程可以借助Django提供的命令创建。

1. 创建工程

创建工程的命令为:

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

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

2. 工程目录说明

查看创建的工程目录,结构如下

工程目录
工程目录

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

3. 运行开发服务器

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

运行服务器命令如下:

python manage.py runserver ip:端口
或:
python manage.py runserver

可以不写IP和端口,默认IP是127.0.0.1,默认端口为8000

启动后可见如下信息:

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

runserver_browser

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

1.6 Django子应用

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

1. 创建子应用

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

cd 项目工程

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

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

执行后,可以看到工程目录中多出了一个名为 users的子目录。

子应用目录

2. 子应用目录说明

查看此时的工程目录,结构如下:

子应用目录

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

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', # 用户模块子应用
]

2. 视图(views)

2.1 简介

1. 定义

  • 视图是Django程序中处理后端业务逻辑的地方。
  • Django的视图是定义在子应用的views.py中的。
  • Django的视图分为 函数视图 和 类视图 两种。

2. 请求对象的user属性

请求对象的user属性返回的是请求过程中认证出来的用户对象

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

3. 自定义中间件注册原则

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

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

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

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

2.2 分类

1. 视图函数

2.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('这里假装返回注册页面')
2. 访问函数视图
提示:

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

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

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

用户通过网络地址http://127.0.0.1:8000/users/register/访问用户注册视图
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/

在这里插入图片描述

  1. 路由匹配函数视图逻辑
    在这里插入图片描述

2. 类视图

2.1 作用

类视图

  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.1 操作
1. 定义类视图

类视图定义方式:

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('这里假装实现注册逻辑')

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

3. 访问类视图

说明:

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

需求:

  • 用户向地址http://127.0.0.1:8000/users/register/发送GET请求,用来获取注册页面。
  • 用户向地址http://127.0.0.1:8000/users/register/发送POST请求,用来实现注册逻辑。
5. 需求实现 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/

在这里插入图片描述
在这里插入图片描述

  1. 路由匹配类视图逻辑
    在这里插入图片描述
  2. 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())
  1. 类视图添加扩展类
    提示:
  • 使用面向对象多继承的特性,可以给类视图定义扩展类。
  • 在扩展类中,可以定义想要向类视图补充的方法。
  • 类视图继承这些扩展类作为父类,便可实现代码复用。
    示例:
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

2.3 路由

1. 简介

  • 路由可以保证用户发送的请求,能够访问到对应的视图
  • 使用路由匹配请求地址,每匹配成功一个就执行对应的函数视图逻辑
  • 定义路由的方法: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/访问用户登录视图

2. 操作

2.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('假装实现登录逻辑')
2.2 path()定义路由
2.3 re_path()定义路由
1. 注册子路由
from django.urls import re_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()),
]
2. postman进行请求测试
  • 使用postman分别向http://127.0.0.1:8000/users/login/发送GET和POST请求
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()),
]
  1. postman进行请求测试

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

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()路由语法中,必须要定义正则表达式严格的开头和结尾
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()),

在这里插入图片描述
在这里插入图片描述

2.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)
  • 新建一个子应用request_response去演示相关内容

1. 操作

1.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')),
]
# 注册子路由
urlpatterns = [
    # 测试提取查询字符串参数:http://127.0.0.1:8000/querystring/?name=zxc&age=18
    path('querystring/', views.QSParamView.as_view()),
]
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))

在这里插入图片描述

重要提示:

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

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

  • 它被定义在django.http.QueryDict

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

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

# 如果键不存在则返回None值,可以设置默认值进行后续处理
query_dict.get('键',默认值)
# 可简写为:
query_dict['键']
1.2 提取请求体数据
  • 可以发送请求体数据的请求方式有:POST、PUT、PATCH、DELETE
  • 请求体数据格式不固定,常见的有:表单类型数据和JSON字符串类型,我们应区别对待
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表单发送的请求体数据
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))

在这里插入图片描述

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

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)
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)
3.3 path()和re_path()对比
  • path()语法相对简洁一些,如果没有路径参数要提取或者要提取的路径参数可以使用默认的路由转换器实现时,就选择path()。
  • re_path()语法相对复杂一些,但是,如果希望在匹配路由时,由自己编写所有的正则表达式,就选择re_path()。
  • 需要注意的是,在使用re_path()时,网络地址正则表达式一定要写完整,要有严格的开头和结尾
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')
5. 其他常用HttpRequest对象属性
  • method:一个字符串,表示请求使用的HTTP方法,常用值包括:‘GET’、‘POST’。
  • FILES:一个类似于字典的对象,包含所有的上传文件。
  • COOKIES:一个字符串,包含了浏览器自动发送的cookie缓存数据。
  • user:请求中认证出来的用户对象

2.5 响应HttpResponse

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

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

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)

在这里插入图片描述

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/')

在这里插入图片描述

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)

2.6 中间件Middleware

1.简介

1.1 定义
  • Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出
  • 中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性,其它的MVC框架也有这个功能
1.2 使用场景:
  • 当某些操作在每次请求或响应时都会执行时,可以写在中间件中
  • 比如,每次发送post请求都要进行CSRF验证,就把CSRF验证的代码写在中间件中
1.3 设计思想:
  • 面向切面编程、无侵害式编程
  • 不用直接修改框架源码,就可以达到自己想要的执行结果
1.4 默认的中间件
# 中间件
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. 中间件方法

  • 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

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
]

浏览调用过程
在这里插入图片描述

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
]

浏览多个中间件的调用过程
在这里插入图片描述

  • 重要提示:中间件执行顺序
  • 在视图被处理前(输入),中间件由上至下依次执行
  • 在视图被处理后(输出),中间件由下至上依次执行
    在这里插入图片描述
    在这里插入图片描述

3. 模型(models)

3.1 简介

1. 定义

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

3.2 读取外键

  • 定义模型类并迁移建表
  • 使用模型类实现MySQL数据库的增删改查
  • 以图书和英雄这两个模型类为例
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为空,不会报错,获取的是空值
  • 结论:
    • 如果外键允许为空,那么务必使用方式二读取外键
    • 如果外键一定不为空,那么使用哪种方式读取外键都可以

3.3 查询集QuerySet缓存的特点

1. 简介

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

3.2 准备数据库

1. 创建MySQL数据库

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

mysql -uroot -pmysql

create database django_demo default charset=utf8;

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'  # 数据库名字
    }
}

运行测试
在这里插入图片描述

发现错误

  • 虚拟环境中,没有安装MySQL数据库的客户端驱动

3. 安装mysqlclient

  • MySQL数据库的客户端驱动
  • 安装mysqlclient==1.4.6
# 进入虚拟环境
pip install mysqlclient==1.4.6 -i https://pypi.tuna.tsinghua.edu.cn/simple/
  • 成功的样子

在这里插入图片描述

  • 失败的样子

在这里插入图片描述

4. 解决mysqlclient安装出错的问题

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

在这里插入图片描述

4.2 更新apt-get的源和升级
  • 其中upgrade会执行很久
$ sudo apt-get update
$ sudo apt-get upgrade
4.3 安装libmysqlclient-dev
$ sudo apt-get install libmysqlclient-dev

在这里插入图片描述

4.4 虚拟环境中安装mysqlclient==1.4.6

在这里插入图片描述

4.5 测试:重启Django程序

3.3 模型类迁移建表

当准备好了数据库之后,就需要去创建数据库表了。
创建数据库表:

  • 使用原生的SQL语句创建数据库表
  • 使用Django提供的模型类创建数据库表
    • ORM框架
    • 定义模型类
    • 迁移模型类建表

1. ORM框架

1.1 简介
  • O是object,也就是类或者对象的意思,这里的类就是模型类
  • R是relation,也就是关系数据库中数据表的意思
  • M是mapping,也就是映射的意思
  • 在ORM框架中,它把模型类和数据表进行了一个映射,可以让通过模型类及对象就能操作它所对应的数据表中的数据
  • ORM框架它还可以根据设计的模型类自动帮我们生成数据库中对应的数据表,省去了建表的过程
  • 注意:
  • Django框架中内嵌了ORM框架,所以在使用Django框架时,不需要直接面向数据库编程
  • 而是定义模型类,通过模型类及对象完成数据表的增删改查操作
1.2 ORM框架作用:
  • 帮助Django的开发者以面向对象的思想去操作数据库。
  • 并且ORM框架也帮助程序员屏蔽了数据库之间的差异。
    ORM框架
    ORM框架

2. 定义模型类

思路:

1. 先根据需求设计数据表
2. 再根据数据表设计方案定义模型类
2.1 根据需求设计数据表
  • 需求:
    • "图书-英雄"管理
  • 分析关联关系:
    • 一本书里面会有多个英雄人物,每个英雄人物都会属于某一本书
    • 数据表一:图书信息表 (一方)
    • 数据表二:英雄信息表 (多方)
  • 绑定关联关系:
    • 外键定义在多方对应的数据表中,即,外键需要定义在英雄信息表中
      图书-英雄信息表
      图书表
      英雄表
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

3. 模型类说明

3.1 关于主键
  • Django会为表创建自动增长的主键列,每个模型只能有一个主键列
  • 默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key
  • 如果使用选项设置某属性为主键列后Django不会再创建自动增长的主键列
3.2 关于属性命名
  • 不能是python的保留关键字
  • 不允许使用连续的下划线,这是由Django的查询方式决定的
  • 定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:
属性 = models.字段类型(选项)
3.3 关于数据库表名
  • 模型类如果未指明表名,Django默认以 小写app应用名_小写模型类名为数据库表名
  • 但是,可通过模型的元类中的db_table自定义数据库表名
    数据库表名
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;
3.5 关于字段选项

字段选型

3.6 关于外键

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

  • CASCADE级联,删除主表数据时连通一起删除外键表中数据

  • PROTECT保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据

  • SET_NULL 设置为NULL,仅在该字段null=True允许为null时可用

  • SET_DEFAULT设置为默认值,仅在该字段设置了默认值时可用

  • SET()设置为特定值或者调用特定方法

  • DO_NOTHING 不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常

4. 迁移模型类建表

4.1 生成迁移文件
python manage.py makemigrations

生成迁移文件

4.2 同步到数据库中

python manage.py migrate

同步到数据库中

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.4 增删改查

1. 简介

1. shell工具
  • 用于在终端交互环境测试代码的
    Django的manage工具提供了shell命令,在配置好当前工程的运行环境(如连接好数据库等)后,以便可以直接在终端中执行测试python语句。

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

python manage.py shell

shell

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

from booktest.models import BookInfo, HeroInfo

2. 操作

2.1 新增

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
)
2.2 修改

修改更新有两种方法

1)save

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

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

2)update

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

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

1)模型类对象.delete

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

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

HeroInfo.objects.filter(id=14).delete()
2.4 查询
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
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))

7)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)
  1. 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))
  1. 聚合函数
    使用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函数的返回值是一个数字。

2.5 排序

使用order_by对结果进行排序

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

由一到多的访问语法:

一对应的模型类对象.多对应的模型类名小写_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

3.4 查询集QuerySet

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。

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]

一个查询集
一次查询

4. 模板(templates)

4.1 简介

1. 定义

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

2. 作用

  • 能够使用模板渲染数据

4.2 使用

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 %}
            <li>《{{ book.btitle }}》</li>
        {% endfor %}
    </ul>
</body>

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值