signed

QiShunwang

“诚信为本、客户至上”

Django 基础

2021/1/28 13:35:15   来源:

1 HTTP协议

1.1 简介

  • 超文本传输协议,是一种用于分布式、协作式和超媒体信息系统的应用层协议。
  • 现今广泛使用的一个版本为–HTTP 1.1
  • HTTP是一个客户端(用户)和服务端(网站)请求和应答的标准(TCP)。
  • 通常由HTTP客户端发起一个请求,创建到一个服务器指定端口(默认是80端口)的TCP连接。HTTP服务器则在那个端口接收客户端请求。一旦收到请求,服务器就会向客户端返回一个状态,如“HTTP/1.1 200 OK”,以及返回的内容,如请求的人间、错误的信息或者其他信息

1.2 HTTP 工作原理

  • HTTP协议采用请求/响应模型。
    1. 客户端向服务端发送一个请求报文,请求报文包含请求的方法、URL、协议版本、求情头部和请求数据。
    2. 服务器已一个状态行作为响应,响应的内容包括协议的版本、成功或错误代码、服务器信息、响应头部和相应数据。
  • 以下是HTTP 请求、响应步骤:
    1. 客户端连接到WEB服务器。一个HTTP客户端,通常是浏览器,与WEB服务器的HTTP端口(默认是80)寄哪里一个TCP套接字连接。如 http://www.baidu.com
    2. 发送HTTP请求。通过TCP套接字,客户端向WEB服务器发送一个文本的请求报文。一个请求报文由请求行、请求头部、空行和请求数据4部分组成。
    3. 服务器接收请求并返回HTTP响应。WEB服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4个部分组成。
    4. 释放连接TCP连接。若connection模式为close,则服务器主动关闭TCP连接,客户端被动关闭,释放TCP连接;若connection模式为keepalive,则该连接保持一段时间,在该时间内可以继续接收请求;
    5. 在客户端浏览器解析HTML内容。客户端浏览器先解析状态行,查看表明请求是否成功的状态码;然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符接;客户端浏览器读取响应数据HTML,根据HTML的语法进行格式化,在浏览器窗口中显示。
  • 示例:在浏览器的地址栏键入URL,按下回车后会经历一下流程:
    1. 浏览器向DNS服务器请求解析该URL中的域名所对的IP地址;
    2. 解析出IP地址后,根据该IP地址和默认端口80,和服务器建立TCP连接;
    3. 浏览器发出读取文件(URL中域名后面部分对应的文件)的HTTP请求,该请求报文作为TCP三次握手的第三个报文的数据发送给服务器;
    4. 服务器作出响应,并把对应的 html 文本发送给浏览器;
    5. 释放TCP连接;
    6. 浏览器将 html 文本内容显示。

1.3 HTTP 协议特点

1.3.1 无状态保存

  • HTTP是一种不保存状态的协议,即无状态协议。HTTP协议自身不对请求和响应之间的通讯状态进行保存。HTTP协议对发送的请求或响应都不做持久化处理。
  • 使用HTTP协议,每当有新的请求发送时,就会有对应的新响应产生。协议本身并不保留之前一切的请求或响应报文的信息。这是为了更快地处理大量事务,确保协议的课伸缩性。

1.3.2 无连接(短连接)

  • 无连接的含义是每次连接值处理一个请求。服务器处理完客户的请求,并受到客户的应答后,即断开连接。采用这种方式可以节省传输事件,并且提高并发性能。

  • 早起的HTTP协议是一个请求响应之后就直接中断了。但是现在的HTTP协议1.1版本不是直接断开,而是等几秒,等待用户后续操作,如果用户在几秒内没有新的请求,那么就会断开连接。这样可以提高效率,减少短时间建立连接的次数。

1.4 HTTP请求方式

  • GET:向指定的资源发出"显示"请求。使用GET方法应该只用在读取数据,而不应当被用于产生"副作用"的操作汇总,如Web Application。其中一个原因是GET可能被网络蜘蛛等随意访问。
  • HEAD:于GET方法一样,都是向服务器发出制定个资源的请求。只不过服务器将不传回资源的本文部分。他的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中"关于该资源的信息"(元信息或元数据)
  • POST:向指定资源提交数据,请求服务器进行处理(如:提交表单或上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。
  • PUT:向指定资源位置上传其最新内容。
  • DELETE:请求服务器删除Request-URI所有标识的资源。
  • TRACE:回收服务器收到的请求,主要用于测试或诊断。
  • OPTIONS:这个方法可以使用服务器传回该资源所支持的所有HTTP请求方法。用"*"来替代资源名称,Web服务器发送OPTIONS请求,可以测试服务器是否是正常运作。
  • CONNECT:HTTP协议/1.1协议中预留给能将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接
  • 注意事项
    1. 方法名称是区分大小写的。当某个请求所针对的资源不自持对应的请求方法的时候,服务器会返回状态码405,当服务器不认识或者不支持对应的请求方法时,返回状态码501。
    2. HTTP服务器至少应该实现GET和HEAD方法,方法都是可选的。当然,所有的方法支持的实现都应当匹配下述的方法各自的语义定义。除此之外,特定的HTTP服务器还能够拓展自定义的方法。

1.5 GET与POST请求

  • GET请求提交的数据会放在URL后面,也就是请求行里面,以?分割URL和传输诗句,参数之间以&相连,如:EditBook?name=test1&id=123456;POST方法是把提交的数据放到HTTP请求体中。
  • GET提交 的数据大小有限制(因为浏览器对URL的长度有限制),而POST提交的数据没有限制。
  • GET与POST请求在服务端获取数据方式不同,就是我们自己在服务端请求数据的时候的方式不同。

1.6 HTTP状态码

  • 所有HTTP响应的第一行都是状态行,依次是当前HTTP版本号,3位数字组成的状态代码,以及描述状态的短语,彼此有空格分隔。
  • 状态代码的第一个数字代表当前响应的类型:
    • 1xx 消息 —— 请求已被服务器接收,继续处理
    • 2xx 成功 —— 请求一成功被服务器接收、理解并接受
    • 3xx 重定向 —— 需要后续操作才能完成这一请求
    • 4xx 请求错误 —— 请求含有词法错误或者无法被执行
    • 5xx 服务器错误 —— 服务器在处理某个正确请求时发生错误

1.7 URL

  • 超文本传输协议(HTTP)的统一资源定位符

  • 将从因特网获取信息的五个基本元素包括在一个简单的地址中

    1. 传输协议
    2. 底层URL标记符号(为 // 固定不变)
    3. 访问资源需要的凭证信息(可省略)
    4. 服务器(通常为域名,有事为IP地址)
    5. 端口号(一数字方式表示,若为HTTP的默认值 :80 可省略)
    6. 路径(以 / 字符区分路径中的每一个目录名称)
    7. 查询(GET模式的窗体参数,以 ? 字符为起点,每个参数以 & 隔开,再已 = 分来参数名称与数据,通常以UTF8编码,避开字符冲突的问题)
    8. 片段(以 # 字符为起点)
  • 示例:http://www.luffycity.com:80/news/index.html?id=250&page=1

    1. http:协议
    2. //:底层URL标记符号
    3. www.luffycity.com :服务器(域名)
    4. :80:服务器上默认的网络端口号,默认不显示
    5. /news/index.html:路径(URL直接定位到对应的资源)
    6. ?id=250&page=1:查询

1.8 HTTP请求格式(请求协议)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DggcF4Wu-1611811939149)(assets\HTTP请求格式(请求协议)].jpg)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-apgn1CBU-1611811939151)(assets\请求报文构成.png)]

1.9 HTTP响应格式(响应协议)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Hp7Wf4n-1611811939153)(assets\HTTP响应格式(响应协议).jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wLWLOpFK-1611811939155)(assets\响应报文的构成.png)]

2 web框架

1.1 本质

  • 所有的web框架本质上就是一个socket 服务端,而用户的浏览器就是一个socket客户端,基于请求作出响应。
  • 客户按照http协议的请求协议发送请求,服务端按照http协议的响应协议来响应请求。这就是web框架的本质。

1.2 自定义web框架

import socket
from threading import Thread
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()

while True:
    conn, addr = sk.accept()

    from_b_msg = conn.recv(1024)
    str_msg = from_b_msg.decode('utf-8')
    conn.send(b'HTTP/1.1 200 ok \r\n\r\n')

    def func(conn,path):
        if path == '/':
            with open('ceshi.html', 'rb') as f:
                data = f.read()
            conn.send(data)
            conn.close()
        else:
            with open(i.strip('/'), 'rb') as f:
                data = f.read()
            conn.send(data)
            conn.close()

    lst = ['/', '/test.css', '/test.js', '/1.jpg', '/1.icon']
    path = str_msg.split(' ')[1]
    for i in lst:
        if path == i:

            Thread(target=func, args=(conn, path)).start()

sk.close()
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
    <link href="前端工具/bootstrap-3.3.7-dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="1.icon">
</head>
<body>
<h1 id="h1">欢迎来到德莱联盟</h1>
<img src="1.jpg" alt="" width="340" height="210">
</body>
<script src="前端工具/jQuery.js"></script>
<script src="前端工具/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
<script src="test.js"></script>
</html>
h1{
    width: 340px;
    background-color: black;
    color: white;
}
alert('这里是祖安')

3 Django 项目的创建及配置

3.1 通过命令创建Django项目

  1. 进入目标文件夹
  2. 创建项目:django-admin startproject 项目名称
  3. 进入项目目录
  4. 创建应用:python manage.py startapp 应用名称
  5. python manage.py runserver ip:端口 默认是127.0.0.1:8000
# 通过指令创建的应用,必须在项目中的settings.py文件中的INSTALL_APP列表最后加上应用名称

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01'  # 添加应用名称
]


# 现阶段在获取post请求数据时,需在settings.py文件中的MIDDLEWARE列表中注销配置
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',
]

3.2 Django项目配置

3.2.1 基本配置

  • 项目同名文件夹下的 settings.py 文件

    1. 在 INSTALLED_APPS 列表后面添加新创建的app名称,如果是用pycharm创建的项目,第一个APP名称无须添加,后面的app名称需要通过命令创建并手动添加至此

    2. 在 TEMPLATES 类表的 ‘DIRS’: [BASE_DIR, ‘templates’] 键值对配置。此项为存放拼接存放 html 等文件的路径,templates为pycharm默认创建,可以根据自身需求修改

    3. 在 DATABASES 字典下配置(数据库相关配置)

      • 将Django连接的数据库改为mysql:‘ENGINE’: ‘django.db.backends.mysql’
      • ‘NAME’ 为连接数据库的名字
      • 新增 ‘HOST’(数据库地址),‘PORT’(端口号3306),‘USER’(用户名),‘PASSWORD’(密码)的键值对
    4. 配置静态文件配置

      • 在项目目录下新建静态文件存放目录
      • 在settings 文件中配置
      # settings.py 文件末尾处新增 
      
      STATIC_URL = '/static/'  # 别名
      STATICFILES_DIRS = [
          os.path.join(BASE_DIR, 'statics')
      ]  # (statics为新建存放静态文件的文件夹,可随意命名)
      
      • 在页面中引用
      <!-- 前端模板部分引用 -->
      <!-- 方式一 -->
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
          <link rel="stylesheet" href="/static/xx.css"> <!--此处写的是别名,不是目录名-->
      </head>
      <body>
      <img src="/static/xx.jpg" alt="">
      </body>
      <script src="/static/xx.js"></script>
      </html>
      
      <!-- 方式二 -->
      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
          <link rel="stylesheet" href="{%  static 'xx.css' %}">
      </head>
      <body>
      <img src="{%  static 'xx.jpg' %}" alt="">
      </body>
      <script src="{%  static 'xx.js' %}"></script>
      </html>
      
  • 项目同名文件夹下的 _init_.py 文件(数据库相关配置)

    • 修改 Django 连接数据库的方式
    import pymysql
    pymysql.install_as_MySQLdb()
    

3.2.4 通过外部脚本文件,操作Django项目

  • 在项目文件夹下新建脚本文件
  • 方式如下,顺序不可改变
# 脚本文件配置

import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day08_ject_cookie.settings")  # manage.py 文件中的第一句话

import django
django.setup()

from app01 import models

# 操作语句

3.2.5 打印ORM转换过程中的SQL语句

  • 方式一:以日志的形式输出

    # 在项目同名文件夹下的 settings.py 文件中配置
    # 在 DATABASES 字典下配置如下
    
    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            'console':{
                'level':'DEBUG',
                'class':'logging.StreamHandler',
            },
        },
        'loggers': {
            'django.db.backends': {
                'handlers': ['console'],
                'propagate': True,
                'level':'DEBUG',
            },
        }
    }
    
  • 方式二

    from django.db import connection  #通过这种方式也能查看执行的sql语句
    print(connection.queries)
    

3.2.6 在 Django 中执行原生 SQL 语句

  • 方式一

    from django.db import connection
    
    cursor = connection.cursor()
    cursor.execute('select * from app01_book')
    print(cursor.fetchall())
    
  • 方式二

    obj_author = models.Author.objects.raw('select * from app01_author')  # 只限于本表操作
    

3.3 简单的登录界面

  1. django-admin startproject 项目名称
  2. cd 项目所在文件夹
  3. python manage.py startapp app01
  4. 在settings.py文件中的INSTALLED_APPS列表配置APP目录
  5. 注释settings.py文件中MIDDLEWARE类表中的django.middleware.csrf.CsrfViewMiddleware,
# urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
# from app名 improt 方法

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.login)
]

# views.py
from django.shortcuts import render,HttpResponse

# Create your views here.
def login(request):
    if request.method == 'GET':  # 获取请求方式
        return render(request, 'login.html')  # reder(request, 'html文件') 返回页面
    else:
        if request.POST.get('username') == '啦啦啦' and request.POST.get('pwd') == '666':
            return HttpResponse('成功')
        else:
            return HttpResponse('失败')  # HttpResponse 返回字符串
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<form action="" method="post">
    <!--不写默认在当前的页面提交-->
    <!--写相对路径,系统会默认补全-->
    
    用户名:<input type="text" name="username">
    密码:<input type="password" name="pwd">
    <input type="submit">
</form>
</body>
</html>

3.4 其他配置

3.4.1 组件

  • 将一套完整的功能封装成模块,以便引用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% include '组件路径/组件名' %}  <!-- 引用组件 -->
</body>
</html>

3.4.2 取消自动加斜杆

  • Django 在接受请求,路由分发时,会将没有斜杆的路径进行匹配
  • Django 会向浏览器发起重定向请求,在路径后面加斜杆,重新请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ksNcW2eF-1611811939156)(assets\带斜杆重定向.png)]

  • 取消自动添加斜杆(注意清除缓存再做尝试)
# 在项目同名文件夹下的 setting.py 文件中手动添加

APPEND_SLASH = False

# APPEND_SLASH = True  # 默认是开启状态

3.4.3 通过现有数据库,生成 models.py 文件中的类

  • 通过终端,切换至项目所在的文件夹
  • 在终端中输入:python manage.py inspectdb > app01/models.py

3.4.4 到处项目中所需所有模块及版本号

  • 通过终端,切换至项目所在的文件夹
  • pip3 freeze > requirement.txt(默认叫requirement)

4 URL 路由系统

4.1 路由分组

# 与项目同名文件夹下的 urls.py 文件

urlpatterns = [
    url(r'^book/(?P<year>\d+)/',views.book),  # 有名分组
    url(r'^books/(\d+)/(\d+)',views.book)  # 无名分组
]

4.2 URL路径别名及反向解析

4.2.1 URL路径起别名

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', views.home),
    url(r'^other/(\d+)/', views.other, name='other'),  # 无名分组
    url(r'^person/(?P<year>\d+)/', views.person, name='person'),  # 有名分组
    url(r'^lalala/', views.lalala, name='lalala'),  # 无分组
]

4.2.2 URL路径反向解析(后端视图部分)

from django.shortcuts import render,redirect
from django.shortcuts import reverse  # 需引入模块完成路径反向解析

# Create your views here.

def home(request):
    return redirect(reverse('other', args=(1,)))  # 无名分组路径反向解析,必须带reverse

def other(request, n):
    return redirect(reverse('person', kwargs={'year':n}))  # 有名分组路径反向解析,必须带reverse

def person(request, year):
    return redirect('lalala')  # 无分组路径反向解析,可以不带reverse

def lalala(request):
    return render(request, 'lalala.html')

4.2.3 URL路径反向解析(前端模板部分)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="{% url 'home' %}">  <!-- 无分组 -->
    <h1>Welcome to home</h1>
</a>
<a href="{% url 'other' 1 %}">  <!-- 有名分组和无名分组均这样写 -->
    <h1>滚蛋</h1>
</a>    
</ body>
</html>

4.3 URL路径分发和命名空间

4.3.1 URL路径分发

  1. 在各自app文件夹下创建urls.py文件
  2. 在各自的urls.py文件下配置路径
  3. 在项目同名文件夹下的urls.py文件下进行路径分发配置
  4. 在各自应用文件夹下调用路径
# app01/urls.py 文件

from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'^index/', views.index, name='index')  # 取别名
]
# app02/urls.py 文件

from django.conf.urls import url
from app02 import views

urlpatterns = [
    url(r'^index/', views.index, name='index')  # 取别名
]
# 项目同名文件夹下的 urls.py 文件

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^app01/', include('app01.urls')),
    url(r'^app02/', include('app02.urls')),
]
# app01/views.py

from django.shortcuts import render,HttpResponse,reverse
from app01 import models

# Create your views here.

def index(request):
    print(reverse('index'))
    return HttpResponse("我是app01" + reverse('index'))
# app02/views.py

from django.shortcuts import render,HttpResponse,reverse
from app02 import models

# Create your views here.

def index(request):
    print(reverse('index'))
    return HttpResponse("我是app02" + reverse('index'))

4.3.2 URL路径分发存在的问题

  • 如果两个app文件中存在相同的路径(如上)
  • 那么Django在做别名方向解析时,会默认解析到在settings文件中配置的最后一个app的路径下

4.3.3 命名空间(解决路径分发存在的问题)

# app01/urls.py 文件

from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'^index/', views.index, name='index')  # 取别名
]
# app02/urls.py 文件

from django.conf.urls import url
from app02 import views

urlpatterns = [
    url(r'^index/', views.index, name='index')  # 取别名
]
# 项目同名文件夹下的 urls.py 文件

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^app01/', include('app01.urls', namespace='app01')),
    url(r'^app01/', include('app02.urls', namespace='app02')),
]
# app01/views.py

from django.shortcuts import render,HttpResponse,reverse
from app01 import models

# Create your views here.

def index(request):
    print(reverse('app01:index'))
    return HttpResponse("我是app01" + reverse('app01:index'))
# app02/views.py

from django.shortcuts import render,HttpResponse,reverse
from app01 import models

# Create your views here.

def index(request):
    print(reverse('app02:index'))
    return HttpResponse("我是app02" + reverse('app02:index'))

其他

当输入网站时,没有以 / 结尾,在第一次没有匹配上是,Django会默认加上 / 做一次重定向
关闭的方式
在项目同名文件夹下的setting.py文件中最后部分写上
APPEND_SLASH = False(默认值为True

5 视图函数

5.1 cbv fbv(补)

其他

request.path  获取请求路径

request.path_info  获取请求路径

settings  APPEND_SLASH = False  
值为True时,Django会将浏览器发送来的后面没有带斜杆的请求坐一个重定向,加上斜杆后重新访问
值为False时,不加重定向

6 模板系统

6.1 模板渲染

  • 可以通过字典进行传值
  • 可以通过 locals() 进行传值(将页面上所有的变量都传递上去)
    • 适用于需要大量传值时使用
# views
from django.shortcuts import render,HttpResponse

# Create your views here.
def ceshi(request):
    name = '啦啦啦'
    num = 1000000
    l1 = [11, 22, 33]
    d1 = {'a':1, 'b':2, 'c':3}
    class A():
        def __init__(self):
            self.xx = 'haha'
        def get_name(self):
            return self.xx + "哈哈哈"
    a = A()
    data = {'name':name, 'num':num, 'l1':l1, 'd1':d1, 'a':a}  # 通过传入键值对来实现渲染。键为所传入的位置,值为所传入的值
    return render(request, 'ceshi.html', data)  # 通过字典传值
	return render(request, 'ceshi.html', locals())  # 通过locals() 传递所有值
<!-- ceshi.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>欢迎{{ username }}来到皇家赌场</h1>  <!-- 在没有传入相应键时,显示空 -->
<p>{{ name }}</p>
<p>{{ num }}</p>
<p>{{ l1 }}</p>
<p>{{ l1.1 }}</p>  <!-- 可以通过点加索引值来渲染,获得列表中特定索引的值 -->
<p>{{ d1 }}</p>
<p>{{ d1.a }}</p>  <!-- 可以通过点加键来渲染,获得字典中键对应的值 -->
<p>{{ a }}</p>
<p>{{ a.xx }}</p>  <!-- 可以通过点加属性来渲染,获得对象的属性值 -->
<p>{{ a.get_name }}</p>  <!-- 可以通过点加方法来渲染,获得对象方法,但不能传参,即只能调用无参方法 -->
</body>
</html>

6.2 过滤器

  • default:如果一个变量是False或者空值,使用default给定的默认值,否则使用变量的值

  • length:返回值的长度。作用于字符串和列表

  • filesizeformat:将值格式化成文件尺寸(带单位显示)

  • slice:切片

  • data:格式化输出时间

  • safe:将标签字符串识别为一个标签文本

    ​ Django的模板在进行模板渲染的时候会对HTML标签和JS等语法标签进行自动转译,即以字符串的形式输出,为了是防止xss攻击(比如有人给你写评论时,写了一段js代码,这个评论提交时,js代码就执行了)。

    ​ 当我们使用safe过滤器时,Django就不会转译标签,直接原型输出

  • truncatechars:将字符串按照指定字符数量截断,后续字符串以三个点替代(三个点占字符数量,即 输入的字符数量 = 你实际向要截断的字符数量 + 3)

  • truncatewords:将当前字符串按照指定单词数量截断(以空格为单位)

  • cut:移除所有指定字符串

  • join:使用指定字符串连接类表中所有元素

# views.py
from django.shortcuts import render,HttpResponse
import datetime

# Create your views here.
def ceshi(request):
    name = '啦啦啦'
    num = 1000000
    l1 = [11, 22, 33]
    d1 = {'a':1, 'd':4, 'b':2, 'c':3}
    var = "How are you?I'm fine thank you"
    time = datetime.datetime.now()
    biaoqian = '<a href="">这是一个标签</a>'
    class A():
        def __init__(self):
            self.xx = 'haha'
        def get_name(self):
            return self.xx + "哈哈哈"
    a = A()
    data = {'name':name, 'num':num, 'l1':l1, 'd1':d1, 'a':a, 'var':var, 'time':time,
            'bianqian':biaoqian
            }
    return render(request, 'ceshi.html', data)
<!-- ceshi.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>欢迎{{ username|default:'这是默认值' }}来到皇家赌场</h1>
<p>{{ name|length }}</p>
<p>{{ num|filesizeformat }}</p>
<p>{{ var|slice:'0:3' }}</p>
<p>{{ time|date:'Y-m-d H:i:s' }}</p>
<p>{{ bianqian }}</p>
{{ bianqian|safe }}
<p>{{ var|truncatechars:'10' }}</p>
<p>{{ var|truncatewords:'3' }}</p>
<p>{{ var|cut:' ' }}</p>
<p>{{ l1|join:'+' }}</p>
</body>
</html>

6.3 for 循环

6.3.1 基本语法

# views.py

from django.shortcuts import render

# Create your views here.
def ceshi(request):
    name = '啦啦啦'
    num = 1000000
    l1 = list("123456")
    l2 = list("abcdef")
    l3 = []
    data = {"l1":l1, "l2":l2, "l3":l3}
    return render(request, 'ceshi.html', data)
<!-- ceshi.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% for i in l1 %}  <!-- 正向循环 -->
    <li>{{ i }}</li>
{% endfor %}

{% for i in l1 reversed %}  <!-- 反向循环 -->
    <li>{{ i }}</li>
{% endfor %}
    
{% for i in l3 %}
    <li>{{ i }}</li>
    {% empty %}  <!-- 当所遍历的列表为空时,执行empty -->
    <h5>没有</h5>
{% endfor %}

{% for i in l4 %}
    <li>{{ i }}</li>
    {% empty %}  <!-- 当所遍历的列表为不存在时,执行empty -->
    <h5>没有</h5>
{% endfor %}
</body>
</html>

6.3.2 forloop

  • forloop.counter:当前循环的索引值(默认从1开始)
  • forloop.counter0:当前循环的索引值(从0开始)
  • forloop.revcounter:当前循环倒叙的索引值(默认从1开始)
  • forloop.revcounter0:当前循环倒叙的索引值(从0开始)
  • forloop.first:当前循环是否是第一次循环(返回布尔值)
  • forloop.last:当前循环是否是最后一次循环(返回布尔值)
  • forloop.parentloop:当前循环的外层循环索引
# views.py

from django.shortcuts import render

# Create your views here.
def ceshi(request):
    l1 = list("123456")
    l2 = list("abcdef")
    data = {"l1":l1, "l2":l2}
    return render(request, 'ceshi.html', data)
<!-- ceshi.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% for i in l1 %}
    <li>{{ i }} --- {{ forloop.counter }}</li>  <!-- 当前循环的索引值(默认从1开始) -->
{% endfor %}

{% for i in l1 %}
    <li>{{ i }} --- {{ forloop.counter0 }}</li>  <!-- 当前循环的索引值(从0开始) -->
{% endfor %}

{% for i in l1 %}
    <li>{{ i }} --- {{ forloop.revcounter }}</li>  <!-- 当前循环倒叙的索引值(默认从1开始) -->
{% endfor %}

{% for i in l1 %}
    <li>{{ i }} --- {{ forloop.revcounter0 }}</li>  <!-- 当前循环倒叙的索引值(从0开始) -->
{% endfor %}

{% for i in l1 %}
    <li>{{ i }} --- {{ forloop.counter }} --- {{ forloop.first }}</li>  <!-- 当前循环是否是第一次循环(返回布尔值) -->
{% endfor %}

{% for i in l1 %}
    <li>{{ i }} --- {{ forloop.counter }} --- {{ forloop.last }}</li>  <!-- 当前循环是否是最后一次循环(返回布尔值) -->
{% endfor %}

{% for i in l1 %}
    {% for j in l2 %}
        <li>{{ j }} --- {{ forloop.parentloop.counter }}</li>  <!-- 当前循环的外层循环索引 -->
    {% endfor %}
{% endfor %}

</body>
</html>

6.4 if 判断

# views.py

from django.shortcuts import render

# Create your views here.
def ceshi(request):
    l1 = list("12345")
    l2 = list("abcde")
    data = {"l1":l1, "l2":l2}
    return render(request, 'ceshi.html', data)
<!-- ceshi.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% for i in l2 %}
    {% if forloop.first %}
        <div>{{ i }} -- 我是第一</div>
    {% elif forloop.last %}
        <div>{{ i }} -- 我是最后</div>
    {% else %}
        <div>{{ i }} -- 我是垃圾</div>
    {% endif %}
{% endfor %}
</body>
</html>

7 ORM

  • ORM是“对象-关系-映射”的简称

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l8fp9F2M-1611811939158)(assets\ORM.png)]

7.1 创建表

7.1.1 创建表的过程

  1. 在models.py文件中创建相应类
  2. 在Django项目文件夹下执行语句 python manage.py makemigrations(生成一条记录)
  3. 执行语句 python manage.py migrate(执行记录)
  4. 查看ORM字段与数据库实际字段的对应关系
    • 左侧边栏 --> Project --> External Libraries --> python --> site_packages --> django --> db --> mysql --> base.py --> DatabaseWrapper
# models.py

from django.db import models

# Create your models here.
class Book(models.Model):
    id = models.AutoField(primary_key=True)  # id int primary_key auto-increment(id可以不写,不写在建表时默认添加)
    title = models.CharField(max_length=32, verbose_name='书名')  # title varchar(32) verbose_name='书名' 相当于对字段的解释
    price = models.DecimalField(max_digits=5,decimal_places=2)  # price decimal(5,2) 最大999.99
    pub_date = models.DateField()
    publish = models.CharField(max_length=32)

7.1.2 字段类型

  • CharField:字符串字段,用于较短的字符串。要求必须有一个参数 maxlength,用于从数据库层和Django校验层限制该字段所允许的最大字符数
  • IntegerField:用于保存一个整数
  • DecimalField: 一个浮点数。 必须提供两个参数
    • max_digits:总位数(不包括小数点和符号,包括小数位)
    • decimal_places:小数位数
  • AutoField: 一个 IntegerField,添加记录时它会自增。
    • 自定义一个主键:my_id=models.AutoField(primary_key=True)
    • 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model
  • TextField: 一个容量很大的文本字段
  • EmailField:一个带有检查 Email 合法性的 CharField,不接受 maxlength 参数
  • DateField:一个日期字段。共有下列额外的可选参数:
    • Argument:描述
    • auto_now :当对象被保存时(更新或者添加都行),自动将该字段的值设置为当前时间。通常用于表示 “last-modified” 时间戳
    • auto_now_add:当对象首次被创建时,自动将该字段的值设置为当前时间。通常用于表示对象创建时间
  • DateTimeField:一个日期时间字段。类似 DateField 支持同样的附加选项
  • ImageField:类似 FileField,不过要校验上传对象是否是一个合法图片
    • height_field 和 width_field:如果提供这两个参数,则图片将按提供的高度和宽度规格保存
  • FileField: 一个文件上传字段。
    • 要求一个必须有的参数:upload_to,一个用于保存上载文件的本地文件系统路径。这个路径必须包含 strftime

7.1.3 约束字段

  • null:null = True 表示字段可以为空。默认值为 False

  • blank:blank = True 表示该字段可以不填。默认值为 False

  • default:设置字段默认值

  • primary_key:primary_key = True 表示设置该字段为主键

  • unique:unique = True 表示该字段的数据不能重复

  • choices:只能创建IntegerField类型,在数据库中以1,2的形式存储。通过get_sex_display,取出1,2对应的值

    • # models.py
      class ceshi01(models.Model):
          name = models.CharField(max_length=32)
          sex = models.IntegerField(choices=((1,'男'), (2,'女')))
          
      # views.py
      from django.shortcuts import render,HttpResponse
      from app01 import models
      
      def ceshi(request):
          res = models.ceshi01.objects.all()
          for i in res:
              print(i.get_sex_display())
      
  • db_index:db_index = True 表示为此字段设置数据库索引

  • auto_now_add:auto_now_add = True 表示创建数据记录的时候会把当前时间添加到数据库

  • auto_now:auto_now = True 表示每次更新数据的时候会更新该字段,表示这条记录最后一次修改时间

    • 当我们需要更新时间时,尽量通过datetime模块来创建当前时间,并保存或更新到数据库
    • 只有用属性更新时才会自动更新,update跟新是不会自动更新

7.2 单表操作

  • views.py 文件下 from app01 import models

7.2.1 添加数据

# 方法一 通过类,添加数据
obj = models.Book(
    title='金瓶梅',
    price=2.8,
    pub_date="2000-08-12",
    publish='小黑帽出版社'
)
obj.save()

# 方法二 通过 objects 控制器内置方法,添加数据
# 返回值为被创建对象本身
obj = models.Book.objects.create(
    title='金瓶梅后传',
    price=12.8,
    pub_date=datetime.datetime.now(),
    publish='小绿帽出版社'
)

# 批量添加
obj_lst = []
for i in range(1,4):
    obj = models.Book(
        title='小蝌蚪找妈妈' + str(i),
        price=1.98,
        pub_date="2000-08-12",
        publish='小蓝帽出版社'
    )
    obj_lst.append(obj)
models.Book.objects.bulk_create(obj_lst)

7.2.2 删除数据

# models.类.objects.filter(**kwargs).delete()
models.Book.objects.filter(id=1).delete()


# models.类.objects.get(**kwargs).delete()
# models.Book.objects.get(id=1).delete()

7.2.3 修改数据

# 方式一 通过 objects 控制器内置方法,修改数据
models.Book.objects.filter(id=5).update(
    title='小蝌蚪找妈妈终极版',
    price=199
)

# 方法二 通过类,修改数据
obj = models.Book.objects.get(id=6)
obj.title = '小蝌蚪找妈妈前传'
obj.price = 998
obj.save()

7.2.4查询数据

7.2.4.1 基本查询
1.all
  • 查询所有结果,结果是queryset类型(类似于列表)
  • 可以通过索引取值
  • 存放的是当前类的对象 ,可以调用类中的属性
# all()  查询所有结果,结果是queryset类型
res = models.Book.objects.all()
print(res[0])  # 可以通过索引取值
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)  # 调用类中的属性
2.filter
  • 按条件查询数据,结果是queryset类型
# filter()  按条件查询数据,结果是queryset类型

# 单条件查询
res = models.Book.objects.filter(title='金瓶梅')
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)

# 多条件查询
res = models.Book.objects.filter(price='1.98', title='小蝌蚪找妈妈3')
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)

# 以什么开头 istartswith 不区分大小写
res = models.Book.objects.filter(title__startswith='小蝌蚪') 
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)

# 以什么结尾 iendswith 不区分大小写
res = models.Book.objects.filter(title__endswith='终极版')  
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)

# 包含什么 icontains 不区分大小写
res = models.Book.objects.filter(title__contains='python')  
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)

# 在范围内的值 等于1.98或2.80的值
res = models.Book.objects.filter(price__in=['1.98', '2.80'])  
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)

# 在范围区间内的值,在1~2之见的值
res = models.Book.objects.filter(price__range=[1,2])  
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)
 
# 大于
res = models.Book.objects.filter(price__gt='1.98')  # 价格大于1.98
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)

# 大于等于
res = models.Book.objects.filter(price__gte='198')  # 价格大于等于198
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)
    
# 小于
res = models.Book.objects.filter(price__lt='2.8')  # 价格小于2.80
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)

# 小于等于
res = models.Book.objects.filter(price__lte='2.8')  # 价格小于等于2.8
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)
3.get
  • 按条件查询数据,结果是当前类的对象
  • 查询不到结果时报错
  • 查询的结果超过1条时报错
# get()  按条件查询数据,结果是对象
res = models.Book.objects.get(id=5)
print(res.title, res.price, res.pub_date, res.publish)
4.exclude
  • 排除,表示查询满足该条件之外的所有数据,返回的是queryset类型
# exclude()  排除,表示查询满足该条件之外的所有数据,返回的是queryset类型
res = models.Book.objects.exclude(title='金瓶梅')
for i in res:
    print(i.title, i.price, i.pub_date, i.publish)
5.order_by
  • queryset类型数据进行调用,对查询的结果进行排序,默认是按照id来升序排序的,返回的是queryset类型
# order_by  queryset类型数据进行调用,对查询的结果进行排序,默认是按照id来升序排序的,返回的是queryset类型
# 升序
res = models.Book.objects.all().order_by('id')
for i in res:
    print(i.id, i.title, i.price, i.pub_date, i.publish)
    
# 降序
res = models.Book.objects.all().order_by('-id')
for i in res:
    print(i.id, i.title, i.price, i.pub_date, i.publish)

# 多条件排序
res = models.Book.objects.all().order_by('price', '-id')  # 按照价格升序,相同价格按照id降序
for i in res:
    print(i.id, i.title, i.price, i.pub_date, i.publish)
6.reverse
  • 在排序的基础上,对查询结果反向排序,返回时queryset类型
# reverse()  queryset类型数据进行调用,对查询结果反向排序,返回时queryset类型
res = models.Book.objects.all().order_by('id').reverse()
for i in res:
    print(i.id, i.title, i.price, i.pub_date, i.publish)
7.count
  • queryset类型数据进行调用,返回数据库中匹配查询的对象数量
# count()  queryset类型数据进行调用,返回数据库中匹配查询的对象数量
res = models.Book.objects.filter(price='1.98').count()
print(res)
8.first
  • queryset类型数据进行调用,返回第一条记录,返回的是对象
# first()  queryset类型数据进行调用,返回第一条记录,返回的是对象
i = models.Book.objects.filter(price='1.98').first()
print(i.id, i.title, i.price, i.pub_date, i.publish)
9.last
  • queryset类型数据进行调用,返回最后一条记录,返回的是对象
# last()  queryset类型数据进行调用,返回最后一条记录,返回的是对象
i = models.Book.objects.filter(price='1.98').last()
print(i.id, i.title, i.price, i.pub_date, i.publish)
10.exists
  • queryset类型数据进行调用,如果queryset包含数据,返回True,否则返回False
# exists()  queryset类型数据进行调用,如果queryset包含数据,返回True,否则返回False
res = models.Book.objects.filter(id=100).exists()
print(res)

res = models.Book.objects.filter(id=1).exists()
print(res)
11.values
  • queryset类型数据进行调用,查看其中部分字段,返回一个字典序列
# values()  queryset类型数据进行调用,查看其中部分字段,返回一个字典序列
res = models.Book.objects.all().values('title', 'price')
print(res)
12.values_list
  • queryset类型数据进行调用,查看其中部分字段,返回一个元祖序列
# values_list()  queryset类型数据进行调用,查看其中部分字段,返回一个元祖序列
res = models.Book.objects.all().values_list('title', 'price')
print(res)
13.disti nct
  • values和values_list得到的queryset类型数据进行调用,从返回的结果中删除重复记录
# distinct()  values和values_list得到的queryset类型数据进行调用,从返回的结果中删除重复记录
res = models.Book.objects.all().values('price').distinct()
print(res)
7.2.4.2 聚合查询
  • aggregate()QuerySet 的一个终止子句
  • 结果是字典类型数据
  • 可以取别名,没去别名,默认字典的键为 字段__聚合函数(如:price__avg)
  • 可以添加多个聚合函数
from django.db.models import Avg, Max # 用啥引啥

book_dict = models.Book.objects.all().aggregate(book_avg=Avg('price'), book_Max=Max('price'))
7.2.4.3 分组查询
# 方法一
book_dict = models.Book.objects.values('publishs_id').annotate(Avg('price'))  # 结果是个字典

# 先跨表,再分组
book_dict = models.Book.objects.values('publishs__name').annotate(Avg('price'))

# 方法二
publish_obj = models.Publish.objects.annotate(price_avg=Avg('book__price'))  # Queryset 类型数据
print(publish_obj.values('price_avg', 'name')) 
7.2.4.4 F查询
  • 针对本表数据进行对比时,或者本表字段做一些统一修改时使用
  • 大于:__gt
  • 大于等于:__gte
  • 小于:__lt
  • 小于等于:__lte
from django.db.models import F

# 点赞数大于等于评论数
print(models.Book.objects.filter(dianzan__gte=F('comment')).values('title'))  # 结果是 Queryset 类型

# 点赞数小于等于评论数
print(models.Book.objects.filter(dianzan__lte=F('comment')).values('title'))

# 对本表数据做统一处理
models.Book.objects.update(price=F('price')+10)
7.2.4.5 Q查询
  • | 表示 or
  • & 表示 and
  • ~ 表示 not
  • 没有Q包裹的条件要写在Q包裹的条件之后
  • 可以多层嵌套
# 点赞数大于5或者评论数大于8的
models.Book.objects.filter(Q(comment__gt=8)|Q(dianzan__gt=5)).values('title')  # 结果是 Queryset 类型

# 点赞数大于5且评论数大于8的
models.Book.objects.filter(Q(dianzan__gt=5)&Q(comment__gt=8)).values('title')

# 点赞数大于5,或者评论数不为6的
models.Book.objects.filter(Q(dianzan__gt=5)|~Q(comment=6)).values('title')

# 出版日期为9月,且价格低于50元,评论数大于点赞数
models.Book.objects.filter(Q(price__lt=50) & Q(comment__gt=F('dianzan')), publishDate__month=9).values('title')

7.3 多表操作

7.3.1 一对一

  • 本表中的数据只能与被关联表中的一条数据关联,且该数据不能与本表中其他数据关联

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RrApbATN-1611811939158)(assets\一对一关系图.png)]

创建表关系
  • 外键建立在任意一方均可,推荐建立在查询频率较高的表中

  • authorDetail = models.OneToOneField(to=‘表名/类名’, to_field=‘字段’, on_delete=models.CASCADE)

    • to 和哪个表(类)关联
    • to field 和哪个字段关联。不写,默认关联主键
    • on_delete 设置级联模式(Django1,不写on__delete,默认级联删除模式;Django2 不行)
      1. on_delete = models.CASCADE 在删除时做级联删除
      2. on_delete = models.SET_NULL 在删除时做级联删除,关联数据显示null
      3. on_delete = models.SET(1) 在删除时做级联删除,关联数据显示1
  • ORM 会自动帮你给这个字段名字拼上一个_id,数据库中字段名称为authord_id

  • 建立关系字段写在哪个表,哪个表就是主动关联的表,另一个表就是被关联的表

  • 主动关联的表删除字段时,被动关联的表数据不发生改变;被关联被删除字段时,主动关联数据被删除

# 作者表
class Author(models.Model):
    id = models.AutoField(primary_key=True)  # 可以不添加,系统会自动生成
    name = models.CharField(max_length=32)
    age = models.IntegerField()  # 相当于 int

    authorD = models.OneToOneField(to='AutorDetail', to_field='id', on_delete=models.CASCADE)  # 创建一对一关系

# 作者详细信息表
class AutorDetail(models.Model):
    id = models.AutoField(primary_key=True)
    birthday = models.DateField()
    telephone = models.CharField(max_length=11)
    addr = models.CharField(max_length=64)
添加数据
  1. 先给被关联表添加记录(如果有需要的数据可以不用添加)

  2. 给关联表添加记录,并添加关联字段

  3. 可以通过类和objects 控制器内置方法,添加数据

    • 方法一 通过对象添加关联字段
    # 通过 objects 控制器内置方法添加
    obj_authordetail = models.AuthorDetail.objects.get(id=1)
    models.Author.objects.create(
        name='哈哈哈',
        age=18,
        authorD=obj,
    )
    
    # 通过类添加
    obj_authordetail = models.AuthorDetail.objects.get(id=2)
    obj_author = models.Autho(
        name='哈哈哈',
        age=18,
        authorD=obj,
    )
    obj_author.save()
    
    • 方法二 通过 id 值添加关联字段
    # 通过 objects 控制器内置方法添加
    models.Author.objects.create(
        name='哈哈哈',
        age=18,
        authorD_id=3,
    )
    
    # 通过类添加
    obj_author = models.Author(
        name='啦啦啦',
        age=18,
        authorD_id=4,
    )
    obj_author.save()
    
删除数据
  • 关联表(本表)中的数据删除时,被关联表中的数据不会被删除
  • 被关联表中的数据删除时,关联表(本表)中关联的数据会被删除
models.Author.objects.get(id=1).delete()  # 此时AuthorDetail表中与之关联的数据不会被删除
models.AuthorDetail.objects.get(id=2).delete()  # 此Autho表中与之关联的数据会被删除
修改数据
# 方法一 通过对象添加关联字段
obj_authordetail = models.AuthorDetail.objects.get(id=1)
models.Author.objects.update(
    name='哈哈哈',
    age=18,
    authorD=obj,
)

# 方法二 通过 id 值添加关联字段
models.Author.objects.create(
    name='哈哈哈',
    age=18,
    authorD_id=3,
)
查询数据
  • 基于对象(子查询)
# 正向查询(通过关联表查询被关联表中信息)
obj_author = models.Author.objects.get(id=21)
print(obj_author.authorD.birthday)  # 对象.关联字段.要查询的字段

# 反向查询(通过被关联表查询关联表中信息)
obj_authordetail = models.AuthorDetail.objects.get(id=8)
print(obj_authordetail.author.name)  # 对象.被关联表的表名小写.要查询的字段
  • 基于双下划线(连表查询)
# 正向查询
models.Author.objects.filter(name='西瓜皮一号').values('authorD__addr')

# 反向查询
models.AuthorDetail.objects.filter(addr='北京').values('author__name')

7.3.2 一对多(多对一)

  • 本表中的多条数据可以与被关联表中的一条数据关联

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g57kiW9Z-1611811939159)(assets\多对一关系图.png)]

创建表关系
  • 外键字段建在多的一方

  • publish = models.ForeignKey(to=‘表名/类名’, to_field=‘字段’, on_delete=models.CASCADE)

# 出版社表
class Publish(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()

# 书籍表
class Book(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)  # 最大为5位,小数点保留2位
    
    publish = models.ForeignKey(to='Publish', to_field='id', on_delete=models.CASCADE)  # 建立一对多关系
添加数据
  • 方法一 通过对象添加关联字段
# 通过 objects 控制器内置方法添加
obj_author = models.Publish.objects.get(id=1)
models.Book.objects.create(
    title='小蝌蚪找妈妈1',
    publishDate=datetime.datetime.now(),
    price=998,
    publishs=obj_author
)

# 通过类添加
obj_publish = models.Publish.objects.get(id=1)
obj_book = models.Book(
    title='小蝌蚪找妈妈2',
    publishDate=datetime.datetime.now(),
    price=998,
    publishs=obj_publish
)
obj_book.save()
  • 方法二 通过 id 值添加关联字段
# 通过 objects 控制器内置方法添加
models.Book.objects.create(
    title='小蝌蚪找妈妈3',
    publishDate=datetime.datetime.now(),
    price=998,
    publishs_id=2
)

# 通过类添加
obj_book = models.Book(
    title='小蝌蚪找妈妈4',
    publishDate=datetime.datetime.now(),
    price=998,
    publishs_id=2
)
obj_book.save()
删除数据
  • 关联表(本表)中的数据删除时,被关联表中的数据不会被删除
  • 被关联表中的数据删除时,关联表(本表)中关联的所有数据会被删除
models.Book.objects.get(id=5).delete()  # 此时Publish表中与之关联的数据不会被删除
models.Publish.objects.get(id=2).delete()  # 此时Book表中与之关联的所有数据都会被删除
修改数据
# 方法一 通过对象添加关联字段
obj_Publish = models.Publish.objects.get(id=1)
models.Book.objects.update(
    name='哈哈哈',
    age=18,
    authorD=obj,
)

# 方法二 通过 id 值添加关联字段
models.Book.objects.create(
    name='哈哈哈',
    age=18,
    authorD_id=3,
)
查询数据
  • 基于对象(子查询)
# 正向查询(通过关联表查询被关联表中信息)
obj_book = models.Book.objects.get(title='小蝌蚪找妈妈1')
print(obj_book.publishs.name)  # 对象.关联字段.要查询的字段

# 反向查询(通过被关联表查询关联表中信息)
obj_book = models.Publish.objects.get(id=8)
obj_publish = obj_book.book_set.all()  # 对象.被关联表的表名小写_set.all()
  • 基于双下划线(连表查询)
# 正向查询
models.Book.objects.filter(id=9).values('publishs__name')

# 反向查询
models.Publish.objects.filter(id=8).values('book__title')

7.3.3 多对多

创建表关系
  • 外键字段建在任意一方均可,推荐建在查询频率高的表中

  • 通过创建第三张表(关系表),来对两张表建立关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YHXQNwJ4-1611811939160)(assets\多对多关系图.png)]

  • authors = models.ManyToManyField(to=‘表名/类名’, to_field=‘字段’)
  • 自动创建第三张表,包含id字段,author_id字段,book_id字段
  • authors不会作为本表(Book表)的字段出现
# 作者表
class Author(models.Model):
    id = models.AutoField(primary_key=True)  # 可以不添加,系统会自动生成
    name = models.CharField(max_length=32)
    age = models.IntegerField()  # 相当于 int
    
# 书籍表
class Book(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)  # 最大为5位,小数点保留2位
    
    authors = models.ManyToManyField(to='Author', to_field='id')  # 建立多对多关系
添加数据
  • 方法一 通过对象添加关联字段
obj_book = models.Book.objects.filter(id=5).first()
obj_author1 = models.Author.objects.get(id=3)
obj_author2 = models.Author.objects.get(id=4)
obj_book.authors.add(obj_author1, obj_author2)
  • 方法二 通过 id 值添加关联字段
obj_book = models.Book.objects.filter(id=6).first()
obj_book.authors.add(5, 6)
删除关系
  • 当一张表删除数据时,关系表中对应的字段会被删除,但不会对另一张表产生影响
  • 当关系表删除数据时,两张表的数据均不会被删除
models.Book.objects.get(id=1).delete()  # 此时关系表中所有的关联的数据均会被删除,且另一张表不受影响
models.Author.objects.get(id=2).delete()  # 此时关联表中所有关联的数据均会被删除,且另一张表不受影响

# 删除指定关系
obj_book = models.Book.objects.get(id=3)
obj_book.authors.remove(1)  # 此时关系表中,book_id为9的,author_id为14的这条数据会被删除

# 清除关系
obj_book = models.Book.objects.get(id=4)
obj_book.authors.clear()  # 此时关系表中,book_id为4的所有数据都会被删除
修改数据
obj_book = models.Book.objects.get(id=9)
obj_book.authors.set(['10','8'])  # 注意此处要写字符串,必须是可迭代类型数据
查询数据
  • 基于对象(子查询)
# 正向查询
obj_book = models.Book.objects.get(id=11)
print(obj_book.authors.all().values('name'))  # 对象.关系表名(返回的是一个类,后续操作同object)

# 反向查询
obj_author = models.Author.objects.get(id=8)
print(obj_author.book_set.all().values('title'))  # 对象.被关联表的表名小写_set(返回的是一个类,后续操作同object)
  • 基于双下划线(连表查询)
# 正向查询
print(models.Book.objects.filter(id=9).values('authors__name'))

# 反向查询
print(models.Author.objects.filter(id=8).values('book__title'))

7.4 锁和事务

7.4.1 原生 SQL 上的查询锁

# 在事务中开启锁后,不提交事务,不会解锁。当提交事务后才会自动解锁

# SQL语句
begin;  # 开启事务
select * from 表 for update;  # 上锁
commit;  # 提交事务,若不执行此语句,则会一直处于上锁状态。当执行此语句后,会自动解锁

7.4.2 在 ORM 中添加锁和事务

from django.db import transaction  # 记得引用

# 方法一 通过装饰期的形式,开起事务
# 装饰器装饰的视图函数中所有的SQL语句捆绑为事务(只是SQL语句,不包涵其他的逻辑语句,如:for循环、return等)
@transaction.atomic 
def shiwu(request):
    models.Book.objects.all().select_for_update()  # 上锁
    return HttpResponse('ok')

# 方法二 给逻辑中的部分sql加事务
# 将 with 语句体中的SQL语句捆绑为事务
def shiwu(request):
    with transaction.atomic():
        models.Book.objects.all().select_for_update()
    return HttpResponse('ok')

8 Ajax

8.1 特点

  • 使用 Javascript 语言与服务器进行异步交互
  • 浏览器页面局部刷新,所以 Ajax 的性能更高

8.2 基于 Ajax 的简单页面登录

8.2.1 不修改状态码

  • 当返回的状态码为2xx,时执行success
# urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', views.login, name='login'),
    url(r'^home/', views.home, name='home'),
]
# views.py

from django.shortcuts import render,HttpResponse

# Create your views here.
def login(request):
    if request.method == "GET":
        return render(request, 'login.html')
    else:
        username = request.POST.dict()['username']
        password = request.POST.dict()['password']

        if username == 'root' and password == '666':
            return HttpResponse('ok')
        else:
            return HttpResponse('error')

def home(request):
    return render(request, 'home.html')
<!-- login.html -->

{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
</head>
<body>
用户名:<input type="text" id="username">
密码:<input type="password" id="password">
<input type="button" value="提交" id="btn">
<span id="error_msg" style="color: red;font-size: 12px"></span>
</body>
<script src="{% static 'jquery.js' %}"></script>
<script>
    $('#btn').click(function () {
        var username = $('#username').val()
        var password = $('#password').val()
        $.ajax({
            url: "{% url 'login' %}",
            type: "post",
            data: {username: username, password: password},
            success: function (res) {
                if (res === 'ok') {
                    location.href = "{% url 'home' %}"
                }else {
                    $('#error_msg').text('用户名或密码有误')
                }
            },
            error:function (res){
                console.log('error方法不执行')
            }
        })
    })
</script>
</html>

8.2.2 修改状态码

# urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', views.login, name='login'),
    url(r'^home/', views.home, name='home'),
]
from django.shortcuts import render,HttpResponse

# Create your views here.
def login(request):
    if request.method == "GET":
        return render(request, 'login.html')
    else:
        username = request.POST.dict()['username']
        password = request.POST.dict()['password']

        if username == 'root' and password == '666':
            return HttpResponse('ok')
        else:
            ret = HttpResponse('error')
            ret.status_code = 404
            return ret

def home(request):
    return render(request, 'home.html')
<!-- login.html -->

{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>

</head>
<body>
用户名:<input type="text" id="username">
密码:<input type="password" id="password">
<input type="button" value="提交" id="btn">
<span id="error_msg" style="color: red;font-size: 12px"></span>
</body>
<script src="{% static 'jquery.js' %}"></script>
<script>
    $('#btn').click(function () {
        var username = $('#username').val()
        var password = $('#password').val()
        $.ajax({
            url: "{% url 'login' %}",
            type: "post",
            data: {username: username, password: password},
            success: function (res) {
                if (res === 'ok') {
                    location.href = "{% url 'home' %}"
                }
            },
            error:function (res){
                $('#error_msg').text('用户名或密码有误')
            }
        })
    })
</script>
</html>

9 Cookie

9.1 认识Cookie

9.1.1 什么是Cookie

  • cookie是浏览器的技术,Cookie具体指的是一段小信息,它是服务器发送出来存储在浏 览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。

9.1.2 Cookie的原理

  • cookie的工作原理是:浏览器访问服务端,带着一个空的cookie,然后由服务器产生内容,浏览器收到相应后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XlBOnQTr-1611811939161)(assets\Cookie工作原理.png)]

9.1.3 查看Cookie

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bvAMzA6T-1611811939162)(assets\查看Cookie.png)]

9.1.4 Cookie规范

  • Cookie大小上限为4KB;
  • 一个服务器最多在客户端浏览器上保存20个Cookie
  • 一个浏览器最多保存300个Cookie,因为一个浏览器可以访问多个服务器。
  • 上面的数据只是HTTP的Cookie规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等!但也不会出现把你硬盘占满的可能!
  • 注意,不同浏览器之间是不共享Cookie的。也就是说在你使用IE访问服务器时,服务器会把Cookie发给IE,然后由IE保存起来,当你在使用FireFox访问服务器时,不可能把IE保存的Cookie发送给服务器。

9.1.5 Cookie与HTTP头

  • Cookie是通过HTTP请求和响应头在客户端和服务器端传递的

  • Cookie:请求头,客户端发送给服务器端

    • 格式:Cookie: a=A; b=B; c=C。即多个Cookie用分号离开;
  • Set-Cookie:响应头,服务器端发送给客户端。

    • 一个Cookie对象一个Set-Cookie: Set-Cookie: a=A Set-Cookie: b=B Set-Cookie: c=C

9.1.6 Cookie的覆盖

  • 如果服务器端发送重复的Cookie那么会覆盖原有的Cookie
  • 例如客户端的第一个请求服务器端发送的Cookie是:Set-Cookie: a=A;第二请求服务器端发送的是:Set-Cookie: a=AA,那么客户端只留下一个Cookie,即:a=AA。

9.2 Django中操作Cookie

  • Ctrl + Shift + del 三个键来清除浏览器页面缓存和cookie

9.2.1 设置Cookie(添加/修改)

  • key:键
  • value:值
  • salt = ‘加密内容’:加密盐。通过 request.COOKIES.get_signed_cookie(键, 盐)获取值
  • max_age = None:超时时间,单位为秒,表示几秒后失效
  • expires = None:超时时间,值为时间类型数据,表示到什么时候失效
  • path = ‘路径’:cookie生效的路径,/ 表示根路径,根路径的cookie可以被任何url的页面访问
    • set_cookie(k1, v1, path = ‘/home’) 表示只有访问home路径以及home下的子路径时cookie才生效
  • domain = None:cookie 生效的域名
  • secure = False,等于True表示,只能通过 https 传输,默认是 False
  • httponly=False 只能 http 协议传输,无法被 JavaScript 获取(不是绝对,底层抓包可以获取到也可以被覆盖)
# ret = HttpResponse('...')
# ret = render(request, 'xx.html')
# ret.set_cookie(key, value, 其他参数)


# views.py

def login(request):
    if request.method == "GET":
        return render(request, 'login.html')
    else:
        username = request.POST.get('username')
        if username == 'root':
            ret = redirect('/home/')
            ret.set_cookie('is_login', True)
            ret.set_cookie('username', username)  # 设置多组cookie,以此类推
            return ret
        else:
            return redirect('/login/')

9.2.2 获取Cookie

# value = request.COOKIES.get(key)


# views.py

def home(request):
    # 注意网络传输的数据,不经过处理(jason序列化),都是通过字符串传输
    if request.COOKIES.get('is_login') == 'True':  # 此处get的结果是字符串
        return render(request, 'home.html')
    else:
        return redirect('/login/')

9.2.3 修改Cookie

# set_cookie(已存在的键, value)

# views.py

def home(request):
    ret = render(request, 'home.html')
    ret.set_cookie('username', 'lalala')
    return ret

9.2.4 删除Cookie

# delete_cookie(key)

# views.py

def person(request):
    ret = render(request, 'person.html')
    ret.delete_cookie('username')
    return ret

9.2.5 基于Cookie的登录认证

# 基本版
def home(request):
    if request.COOKIES.get('is_login') != 'True':  # 此处get的结果是字符串
            return redirect('\login\')
    return render(request, 'home.html')


# 基于装饰器
def outer(func):
    def inner(request, *args, **kwargs):
        if request.COOKIES.get('is_login') != 'True':  # 此处get的结果是字符串
            return redirect('\login\')
        ret = func(request, *args, **kwargs)
        return ret
    return inner

@outer
def home(request):
    return render(request, 'home.html')



# 基于中间件
# 适用于大规模的路径需要校验是,写中间件

from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin

class LoginAuth(MiddlewareMixin):
    while_list = ['/login/',]  # 设置白名单
    def process_request(self, request):
        path = request.path
        if path not in self.while_list:  # 不在白名单中的url进行登录认证
            if request.COOKIES.get("is_login") != 'True':
                return redirect('/login/')

10 Session

10.1 Session实现过程

  • 第一次

    1. 浏览器请求携带空的cookie字典访问服务器
    2. Django通过设置键值对,形成字典,并随机字符串(session-key)将字典加密后和随机字符串一起存入服务端数据库中(django-session表)
    3. 同时返回给浏览器cookie字典,字典的键为sessionid,值为随机字符串
  • 第二次

    1. 浏览器请求带着cookie字典访问
    2. 后台服务器获取cookie的值,在数据库中查询到相应信息,并使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PGUGIHYg-1611811939162)(assets\session流程.png)]

10.2 Django中Session相关方法

10.2.1 Session特点

  • 基于cookie实现
  • 密文存储
  • 存放于服务端本地(数据库中的djang-session表),大小不限

10.2.2 Session访问和获取时的操作

  • 客户端带着空cookie提交请求时

    1. 生成一个随机字符串
    2. 将随机字符串放到cookie中,名称为sessionid
    3. 将设置的session数据,序列化+加密,保存到django-session表中(注意存放值的类型,Jason不能序列化所所有的数据类型)
  • 客户端带着cookie提交请求时

    1. 取出请求中cookie中键为sessionid的值
    2. 通过这个值到django-session表汇总获取数据
    3. 将数据揭秘并反序列化得到原来数据

10.2.3 Session的设置(添加/修改)

  • django处理session方式一个浏览器,对应一个服务器只有一个session
  • 表现为:先用root用户登录成功,django会在数据库中插入一条数数据,此时再用ceshi用户登录,数据库中不会新增数据,而是更新数据qiesession_key数据不变,session_data更新
# 添加session
# request.session[key] = value

def login(request):
    if request.method == "GET":
        return render(request, 'login.html')
    else:
        username = request.POST.get('username')
        if username == 'root' or username == 'ceshi':
            request.session['is_login'] = True
            request.session['username'] = username

            return redirect('/home/')
        else:
            return redirect('/login/')

10.2.4 删除和清空 Session

  • 删除session:del request.session[键]

  • 清空session:request.session.flush()

    1. 删除cookie中的数据

    2. 删除数据库中djiango-session表中的记录

10.2.5 基于 Session 的中间件登入验证

  • 通过 request.session.get(key),获取对应的值,无对应的键返回None
  • 也可以通过 request.session[键],获取对应的值,无对应的键报错
from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin

class LoginAuth(MiddlewareMixin):
    while_list = ['/login/',]
    def process_request(self, request):
        path = request.path
        if path not in self.while_list:
            if request.session.get('is_login') != True:
                return redirect('/login/')

10.2.6 其他

设置session生成cookie的键的名称

项目同名文件夹下的settings.py文件,写入SESSION_COOKIE_NAME = '名字'

11 中间件

  • 是对请求和响应做统一的处理

11.1 Django 请求生命周期

  1. 浏览器发送请求
  2. wsgi接收到请求。wsgi中封装有socket。socket接收到请求之后按照http协议解包,并封装成request对象
  3. wsgi将request对象传给中间件
  4. 中间件从上到下执行列表中类的process_request方法,如果没有就跳过
  5. 中间件执行完毕之后,将request对象传给URL控制器
  6. URL控制器做路径分发,将request对象传给对应的视图函数进行ORM操作或是模板渲染
  7. 视图函数将处理好的结果响应数据给中间件
  8. 中间件从下到上执行列表中类的process_response方法,如果没有就跳过。并将响应数据传给wsgi
  9. wsgi按照http协议封装响应数据
  10. 最后将数据响应给浏览器

11.2 中间件的方法

11.2.1 process_request

  • 对请求做统一的处理
  • process_request有一个参数,就是request(wsgi中socket根据http协议解包后封装的request对象)
  • 返回值可以是None也可以是HttpResponse对象
    • 返回值是None的话,按正常流程继续走,交给下一个中间件处理
    • 如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pdtrx8rj-1611811939163)(assets\process_request方法返回HTTPResponse对象执行流程.png)]

11.2.2 process_response

  • 对响应做统一的处理

  • 它有两个参数,一个是request,一个是response

  • 必须返回 response(response是视图函数返回的HttpResponse对象)

11.2.3 process_view

  • 该方法有四个参数

    • request是socket接收到请求之后按照http协议解包,并封装成request对象
    • view_func是Django即将调用的视图函数(它是实际的函数对象,而不是函数的名称作为字符串)
    • view_args是将传递给视图的位置参数的列表
    • view_kwargs是将传递给视图的关键字参数的字典
  • Django会在调用视图函数之前调用process_view方法

  • 它应该返回None或一个HttpResponse对象

    • 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h7MVx5HN-1611811939164)(assets\process_view返回None时执行流程.png)]

    • 如果它返回一个HttpResponse对象,Django不会调用对应的视图函数。它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OnywMTIx-1611811939165)(assets\process_view返回HTTPResponse时执行流程.png)]

11.2.4 process_exception

  • 该方法两个参数:一个是request,另一个exception(视图函数异常产生的Exception对象)

  • 这个方法只有在视图函数中出现异常了才执行

  • 它返回的值可以是一个None也可以是一个HttpResponse对象

    • 如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XezDjfG3-1611811939165)(assets\process_exception方法返回None时执行流程.png)]

    • 如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ReOP2QZz-1611811939166)(assets\process_exception方法返回HttpResponse对象时执行流程.png)]

11.2.5 process_template_response

  • 该方法有两个参数:一个request,另一个是response(TemplateResponse对象,由视图函数或者中间件产生)
  • 该方法在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)

11.3 自定义中间件

  1. 在应用下创建文件夹

  2. 在文件夹下创建py文件

  3. 在py文件中写入中间件执行的方法

    from django.utils.deprecation import MiddlewareMixin
    
    class MyMiddleware(MiddlewareMixin):  # 类名随意,继承MiddlewareMixin
        def process_request(self, request):  # 如果想对请求做统一处理,那么就定义process_request方法
            print('快看请求来了')
    
  4. 在settings中 MIDDLEWARE 列表最后配置上自定义中间件的路径

    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',
        'app01.my_middleware.middlewares.MyMiddleware'  # 配置在此
    ]
    

12 CSRF

12.1 什么是 CSRF

  • 跨站请求伪造
  • 用户访问A网站,登录后,获取到cookie
  • 之后用户访问B网站,访问B网站发送重定向请求,访问A网站,此时携带cookie可以访问成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ulkPDik-1611811939166)(assets\跨站请求伪造.png)]

12.2 csrftoken 防止跨站请求伪造

  • A网站在放回的页面中放入csrftoken标记(每次页面渲染时都放入新的标记)
  • 用户再提交请求的时候,A网站对csrftoken标记进行验证,通过这接收请求;防止拒绝请求
  • 而B网站重定向的请求,没有A网站的csrftoken标记,所有没办法访问
  • 从而达到防止csrf攻击

12.3 Django的csrftoken认证机制

  1. 当Django在响应请求的时候,会对页面上的 {% csrftoken %} 进行渲染,将其渲染成一个隐藏的input标签,values值为一个随机字符串(Django在后台不保存这个随机字符串)。同时设置一个cookie,键为csrftoken,值为随机字符串
  2. 当用户在页面提交数据时,页面上的随机字符串和cookie会同时提交到后台
  3. 此时后台根据算法进行解析,如果页面上的随机字符串和cookie中的随机字符串解析后的结果一直,则认为请求有效,通过csrf认证,反之则没通过。

12.3.1 基于页面渲染的 csrftoken

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
    {% csrf_token %}  <!--放入csrftoken标记-->
    <input type="text" name="username">
    <input type="submit">
</form>
</body>
</html>

12.3.2 基于 ajax 的 csrftoken

<!-- 通过请求数据携带csrftoken -->
<!-- 方法一 -->
{% load static %}

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
</head>
<body>
{% csrf_token %}
用户名:<input type="text" name="" id="username">
<input type="button" name="" id="btn" value="提交">
</body>
<script src="{% static 'jquery.js' %}"></script>
<script>
    $("#btn").click(function () {
        var username = $("#username").val();
        var token = $("[name='csrfmiddlewaretoken']").val();

        $.ajax({
            url: "{% url 'ajax_login' %}",
            type: "post",
            data: {username: username, csrfmiddlewaretoken: token},
            success: function (res) {
                alert(res)
            }
        })
    })
</script>
</html>

<!-- 方法二 -->
{% load static %}

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
</head>
<body>
用户名:<input type="text" name="" id="username">
<input type="button" name="" id="btn" value="提交">
</body>
<script src="{% static 'jquery.js' %}"></script>
<script>
    $("#btn").click(function () {
        var username = $("#username").val();

        $.ajax({
            url: "{% url 'ajax_login' %}",
            type: "post",
            data: {username: username, csrfmiddlewaretoken: '{{ csrf_token }}'},
            success: function (res) {
                alert(res)
            }
        })
    })
</script>
</html>

<!-- 通过请求头携带csrftoken -->
{% load static %}

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
</head>
<body>
用户名:<input type="text" name="" id="username">
<input type="button" name="" id="btn" value="提交">
</body>
<script src="{% static 'jquery.js' %}"></script>
<script src="{% static 'jquery.cookie.js' %}"></script>  <!-- 需引入插件 -->
<script>
    $("#btn").click(function () {
        var username = $("#username").val();

        $.ajax({
            url: "{% url 'ajax_login' %}",
            type: "post",
            data: {username: username},
            headers:{'X-CSRFToken':$.cookie('csrftoken')},
            success: function (res) {
                alert(res)
            }
        })
    })
</script>
</html>

13 文件上传

13.1 基于 form 表单的文件上传

  • 前段页面书写如下
<!-- html文件 -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post" enctype="multipart/form-data">  <!-- 设置文件的上传模式为分片上传 -->
    {% csrf_token %}
    用户名:<input type="text" name="username">
    <input type="file" name="xiaowenjian">
    <input type="submit">
</form>
</body>
</html>
  • 后端接受数据逻辑如下
# views 视图函数

from django.shortcuts import render,HttpResponse

# Create your views here.

def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        file_obj = request.FILES.get('xiaowenjian')  # 获取文件对象
        print(file_obj.name)  # 获取文件名
        with open(f'/home/hkxpz/桌面/副本1_{file_obj.name}', 'wb') as f:
            for i in file_obj.chunks():  # chunks 设置每次接收的大小为65536B,即64kb,推荐添加,不加也可以
                f.write(i)
        return HttpResponse('ok')

13.2 基于 ajax 的文件上传

<!-- html 文件 -->

{% load static %}

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
</head>
<body>
用户名:<input type="text" id="username">
<input type="file" id="xiaowenjian">
<input type="button" id="btn" value="提交">
</body>
<script src="{% static 'jquery.js' %}"></script>
<script>
    $("#btn").click(function () {
        var username = $("#username").val();
        var file_obj = $("#xiaowenjian")[0].files[0];

        var formdata = new FormData();
        formdata.append('username', username);
        formdata.append('xiaowenjian', file_obj);
        formdata.append('csrfmiddlewaretoken', '{{ csrf_token }}');

        $.ajax({
            url: "{% url 'login' %}",
            type: "post",
            data: formdata,
            processData: false,  // 不处理数据
            contentType: false,  // 不设置内容
            success: function (res) {
                alert(res)
            }
        })
    })
</script>
</html>

14 admin 后台管理系统

  • 注册一个超级管理员用户

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AAz861ys-1611811939167)(assets\创建超级管理员用户.png)]

  • 在应用文件夹下的 admin.py 文件中写入如下
from django.contrib import admin
from app01 import models

class StudentAdmin(admin.ModelAdmin):
    list_display = ['id', 'name', 'sex', 'age', 'class_null', 'description']  # 要展示的字段名称,不写默认展示的时对象
    list_editable = ['name']  # 让字段可编辑

admin.site.register(models.Student, StudentAdmin)  # 将表注册到 admin 后台管理系统中去,如果有多张表可以另起一行继续注册
  • 应用文件夹下的的 models.py 文件如下
from django.db import models

# Create your models here.

class Student(models.Model):
    name = models.CharField(max_length=100,verbose_name="姓名",help_text='提示文本:不能为空')
    sex = models.BooleanField(default=1,verbose_name="性别")
    age = models.IntegerField(verbose_name="年龄")
    class_null = models.CharField(max_length=5,verbose_name="班级编号")
    description = models.TextField(max_length=1000,verbose_name="个性签名")

    class Meta:
        db_table="tb_student"   # 自定义比表名称
        verbose_name = "学生"  # verbose_name 是在 django 的后台管理系统 admin 中显示的名称
        verbose_name_plural = verbose_name