Django

﹥>﹥吖頭↗ 提交于 2020-01-03 00:03:57

第十二章 Django框架

12.1 HTTP协议

12.1.1 HTTP简介

  1. 超文本传输协议 Hyper Text Transfer Protocol
  2. 是一种用于分布式、协作式和超媒体信息系统的应用层协议
  3. HTTP是万维网的数据通信的基础
  4. HTTP有很多应用,但最著名的是用于web浏览器和web服务器之间的双工通信
  5. HTTP是一个客户端终端和服务器端请求和响应的标准

12.1.2 HTTP 请求/响应的步骤

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

12.1.3 HTTP请求方法

  1. GET:获取一个页面、图片(资源)
  2. POST:提交数据
  3. HEAD
  4. PUT
  5. DELETE
  6. TRACE
  7. OPTIONS
  8. CONNECT

12.1.4 HTTP状态码

  1. 状态代码的第一个数字代表当前响应的类型:
    • 1xx消息——请求已被服务器接收,继续处理
    • 2xx成功——请求已成功被服务器接收、理解、并接受
    • 3xx重定向——需要后续操作才能完成这一请求
    • 4xx请求错误——请求含有词法错误或者无法被执行
    • 5xx服务器错误——服务器在处理某个正确请求时发生错误
  2. 常见的:"200 OK","404 Not Found"

12.1.5 URL:统一资源定位符

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

12.1.6 HTTP请求格式

  1. 请求(request)
    • 浏览器:服务器
    • GET 请求没有请求数据
  2. 格式:
    请求方式 url路径 协议版本\r\n
    k1:v1\r\n
    k2:v2\r\n
    \r\n
    数据

1560238058814

12.1.7 HTTP响应格式

  1. 响应(response)
    • 服务器:浏览器
  2. 格式:
    协议版本 状态码 状态码描述\r\n
    k1:v1\r\n
    k2:v2\r\n
    \r\n
    响应数据(响应体)

1560238094938

12.2 Web框架

12.2.1 Web框架本质

  • 所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端

12.2.2 Web框架功能

  1. socket收发消息 —— wsgiref(测试)、uwsgi(线上)
  2. 根据不同的路径返回不同的字符串
  3. 返回动态页面(字符串的替换)—— jinja2

12.2.3 Web框架种类

  1. django
    • 根据不同的路径返回不同的字符串
    • 返回动态页面(字符串的替换)
  2. flask
    • 根据不同的路径返回不同的字符串
  3. tornado
    • socket收发消息
    • 根据不同的路径返回不同的字符串
    • 返回动态页面(字符串的替换)

12.2.4 自定义web框架

  1. 示例一:socket服务端
    import socket
    
    # 创建一个socket对象
    
    sk = socket.socket()
    
    # 绑定IP和端口
    
    sk.bind(('127.0.0.1', 8000))
    
    # 监听
    
    sk.listen(5)
    
    # 等待连接
    
    while True:
       conn, addr = sk.accept()
       # 接收数据
       data= conn.recv(1024)
       print(data)
       # 返回数据
       conn.send(b'HTTP/1.1 200 OK\r\n\r\n<h1>ok!</h1>')
       # 断开连接
       conn.close()
  2. 示例二:根据不同路径返回不同的内容(普通版)
    import socket
    
    # 创建一个socket对象
    
    sk = socket.socket()
    
    # 绑定IP和端口
    
    sk.bind(('127.0.0.1', 8000))
    
    # 监听
    
    sk.listen(5)
    
    # 等待连接
    
    while True:
       conn, addr = sk.accept()
       # 接收数据
       data = conn.recv(1024)
       data = data.decode('utf-8')
       url = data.split()[1]
       conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
       if url == '/index/':
           # 返回数据
           conn.send(b'<h1>index!</h1>')
       elif url == '/home/':
           conn.send(b'<h1>home!</h1>')
       else:
           conn.send(b'<h1>404 not found!</h1>')
       # 断开连接
       conn.close()
  3. 示例三:根据不同路径返回不同的内容(函数版)
    import socket
    
    # 创建一个socket对象
    
    sk = socket.socket()
    
    # 绑定IP和端口
    
    sk.bind(('127.0.0.1', 8000))
    
    # 监听
    
    sk.listen(5)
    
    # 函数
    
    def index(url):
       ret = '<h1>index!</h1>({})'.format(url)
       return ret.encode('utf-8')
    def home(url):
       ret = '<h1>home!</h1>({})'.format(url)
       return ret.encode('utf-8')
    
    # 等待连接
    
    while True:
       conn, addr = sk.accept()
       # 接收数据
       data = conn.recv(1024)
       data = data.decode('utf-8')
       url = data.split()[1]
       conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
       if url == '/index/':
           # 返回数据
           ret = index(url)
       elif url == '/home/':
           ret = home(url)
       else:
           ret = b'<h1>404 not found!</h1>'
       conn.send(ret)
       # 断开连接
       conn.close()
  4. 示例四:根据不同路径返回不同的内容(函数进阶版)
    import socket
    
    # 创建一个socket对象
    
    sk = socket.socket()
    
    # 绑定IP和端口
    
    sk.bind(('127.0.0.1', 8000))
    
    # 监听
    
    sk.listen(5)
    
    # 函数
    
    def index(url):
       ret = '<h1>index!</h1>({})'.format(url)
       return ret.encode('utf-8')
    def home(url):
       ret = '<h1>home!</h1>({})'.format(url)
       return ret.encode('utf-8')
    
    # 定义一个list1和实际要执行的函数的对应关系
    
    list1 = [
       ('/index/', index),
       ('/home/', home),
    ]
    
    # 等待连接
    
    while True:
       conn, addr = sk.accept()
       # 接收数据
       data = conn.recv(1024)
       data = data.decode('utf-8')
       url = data.split()[1]
       conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
       func = None
       for i in list1:
           if url == i[0]:
               func = i[1]
               break
       if func:
           ret = func(url)
       else:
           ret = b'<h1>404 not found!</h1>'
       conn.send(ret)
       # 断开连接
       conn.close()
  5. 示例五:返回HTML页面
    import socket
    
    # 创建一个socket对象
    
    sk = socket.socket()
    
    # 绑定IP和端口
    
    sk.bind(('127.0.0.1', 8000))
    
    # 监听
    
    sk.listen(5)
    
    # 函数
    
    def index(url):
       with open('index.html','rb') as f:
           ret = f.read()
       return ret
    def home(url):
       ret = '<h1>home!</h1>({})'.format(url)
       return ret.encode('utf-8')
    
    # 定义一个list1和实际要执行的函数的对应关系
    
    list1 = [
       ('/index/', index),
       ('/home/', home),
    ]
    
    # 等待连接
    
    while True:
       conn, addr = sk.accept()
       # 接收数据
       data = conn.recv(1024)
       data = data.decode('utf-8')
       url = data.split()[1]
       conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
       func = None
       for i in list1:
           if url == i[0]:
               func = i[1]
               break
       if func:
           ret = func(url)
       else:
           ret = b'<h1>404 not found!</h1>'
       conn.send(ret)
       # 断开连接
       conn.close()
  6. 示例六:返回动态页面
    import socket
    import time
    
    # 创建一个socket对象
    
    sk = socket.socket()
    
    # 绑定IP和端口
    
    sk.bind(('127.0.0.1', 8000))
    
    # 监听
    
    sk.listen(5)
    
    # 函数
    
    def index(url):
       with open('index.html', 'rb') as f:
           ret = f.read()
       return ret
    def home(url):
       ret = '<h1>home!</h1>({})'.format(url)
       return ret.encode('utf-8')
    def timer(url):
       now = time.strftime('%H:%M:%S')
       with open('time.html','r',encoding='utf-8') as f:
           data = f.read()
       data = data.replace('xxtimexx',now)
    
       return data.encode('utf-8')
    
    # 定义一个list1和实际要执行的函数的对应关系
    
    list1 = [
       ('/index/', index),
       ('/home/', home),
       ('/time/', timer),
    ]
    
    # 等待连接
    
    while True:
       conn, addr = sk.accept()
       # 接收数据
       data = conn.recv(1024)
       data = data.decode('utf-8')
       url = data.split()[1]
       conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
       func = None
       for i in list1:
           if url == i[0]:
               func = i[1]
               break
       if func:
           ret = func(url)
       else:
           ret = b'<h1>404 not found!</h1>'
       conn.send(ret)
       # 断开连接
       conn.close()
    • 补充:time.html
      <!DOCTYPE html>
      <html lang="en">
      <head>
       <meta charset="UTF-8">
       <title>Title</title>
      </head>
      <body>
       <h1>当前时间是:@@time@@</h1>
      </body>
      </html>

12.2.5 wsgiref

  1. 常用的WSGI服务器有uWSGI、Gunicorn
    • Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器
  2. 示例:
    """ 
    根据URL中不同的路径返回不同的内容--函数进阶版 
    返回HTML页面 
    让网页动态起来 
    wsgiref模块版 
    """      
    from wsgiref.simple_server import make_server    
    
    
    # 将返回不同的内容部分封装成函数  
    
    def index(url):  
       # 读取index.html页面的内容  
       with open("index.html", "r", encoding="utf8") as f:  
           s = f.read()  
       # 返回字节数据  
       return bytes(s, encoding="utf8")      
    
    def home(url):  
       with open("home.html", "r", encoding="utf8") as f:  
           s = f.read()  
       return bytes(s, encoding="utf8")      
    
    def timer(url):  
       import time  
       with open("time.html", "r", encoding="utf8") as f:  
           s = f.read()  
           s = s.replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S"))  
       return bytes(s, encoding="utf8")       
    
    
    # 定义一个url和实际要执行的函数的对应关系  
    
    list1 = [  
       ("/index/", index),  
       ("/home/", home),  
       ("/time/", timer),  
    ]       
    
    def run_server(environ, start_response):  
       start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 设置HTTP响应的状态码和头信息  
       url = environ['PATH_INFO']  # 取到用户输入的url  
       func = None  
       for i in list1:  
           if i[0] == url:  
               func = i[1]  
               break  
       if func:  
           response = func(url)  
       else:  
           response = b"404 not found!"  
       return [response, ]     
    
    if __name__ == '__main__':  
       httpd = make_server('127.0.0.1', 8090, run_server)  
       print("我在8090等你哦...")  
       httpd.serve_forever() 

12.2.6 jinja2

  1. 模板渲染现成的工具:jinja2
    • 下载jinja2:pip install jinja2
  2. 示例:
    from wsgiref.simple_server import make_server 
    from jinja2 import Template   
    
    def index(url): 
       # 读取HTML文件内容 
       with open("index2.html", "r", encoding="utf8") as f: 
           data = f.read() 
           template = Template(data)   # 生成模板文件
           ret = template.render({'name': 'alex', 'hobby_list': ['抽烟', '喝酒', '烫头']})   # 把数据填充到模板中 
       return bytes(ret, encoding="utf8") 
    
    def home(url): 
       with open("home.html", "r", encoding="utf8") as f: 
           s = f.read() 
       return bytes(s, encoding="utf8") 
    
    
    # 定义一个url和实际要执行的函数的对应关系 
    
    list1 = [ 
       ("/index/", index), 
       ("/home/", home), 
    ]  
    
    def run_server(environ, start_response): 
       start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 设置HTTP响应的状态码和头信息 
       url = environ['PATH_INFO']  # 取到用户输入的url 
       func = None 
       for i in list1: 
           if i[0] == url: 
               func = i[1] 
               break 
       if func: 
           response = func(url) 
       else: 
           response = b"404 not found!" 
       return [response, ] 
    
    if __name__ == '__main__': 
       httpd = make_server('127.0.0.1', 8090, run_server) 
       print("我在8090等你哦...") 
       httpd.serve_forever()
    • 补充:index2.html
      <!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>Title</title>
      </head>
      <body>
       <h1>姓名:{{name}}</h1>
       <h1>爱好:</h1>
       <ul>
           {% for hobby in hobby_list %}
               <li>{{hobby}}</li>
           {% endfor %}
       </ul>
      </body>
      </html>

12.3 Django基本知识

12.3.1 安装及使用

  1. 下载安装
    • 命令行:pip3 install django==1.11.21
    • pycharm
  2. 创建项目
    • 命令行:
      • 找一个文件夹存放项目文件,打开终端:
      • django-admin startproject 项目名称
      • 项目目录

      1560226751493

    • pycahrm
  3. 启动
    • 命令行
      • 切换到项目的根目录下 manage.py
      • python36 manage.py runserver —— 127.0.0.1:80`
      • python36 manage.py runserver 80——127.0.0.1:80
      • python36 manage.py runserver 0.0.0.0:80——0.0.0.0:80
    • pycharm:点绿三角启动 可配置
  4. 简单使用
    • 示例:返回HTML指定文件
    # 在urls.py中
    
    
    # 导入
    
    from django.shortcuts import HttpResponse,render
    
    
    # 函数
    
    def index(request):
       # return HttpResponse('index')
       return render(request,'index.html')
    
    
    # url和函数对应关系
    
    urlpatterns = [
       url(r'^admin/', admin.site.urls),
       url(r'^index/', index),
    ]

12.3.2 静态文件

  1. 配置
    • 在settings.py中设置
    STATIC_URL = '/static/'            # 别名
    STATICFILES_DIRS = [            # 设置文件路径,可以设置多个
       os.path.join(BASE_DIR, 'static1'),
       os.path.join(BASE_DIR, 'static'),
       os.path.join(BASE_DIR, 'static2'),
    ]      
  2. 使用
    • 在路径前添加别名:/static/
    • 多个文件路径,也是使用同一个别名,不是文件名
    • 如果别名后的路径名相同,按照STATICFILES_DIRS列表的顺序进行查找
    <link rel="stylesheet" href="/static/css/login.css">           {# 别名开头 #}

12.3.3 简单的登录实例

  1. form表单提交数据注意的问题:
    • 提交的地址:action="",请求的方式:method="post"
    • 所有的input框有name属性,如name="username"
    • 有一个input框的type="submit"或者有一个button
  2. 提交post请求,由于Django中有一个csrf校验,所有请求会出问题
    • 解决方式:把settings中MIDDLEWARE的'django.middleware.csrf.CsrfViewMiddleware'注释掉
  3. 重定向
    • 导入方式
    from django.shortcuts import redirect
    • 使用方式
    # 在函数中使用,例如
    
    return redirect('/index/')         # 参数:路径url
    
    # 注意:前面必须加/,代表从url根拼接,否则就会在当前url后面一直拼接
    
  4. 示例:
    from django.shortcuts import HttpResponse, render, redirect
    
    def index(request):
       # return HttpResponse('index')
       return render(request, 'index.html')
    
    def login(request):
       if request.method == 'POST':
           # 获取form表单提交的书籍
           username = request.POST['username']
           password = request.POST['password']
           # 验证用户名和密码
           if models.User.objects.filter(username=username,password=password):
               # 验证成功跳转到index页面
               # return redirect('https://www.baidu.com/')
               return redirect('/index/')
           # 不成功 重新登录
       return render(request, 'login.html')
    
    urlpatterns = [
       url(r'^admin/', admin.site.urls),
       url(r'^index/', views.index),
       url(r'^login/', views.login),
    ]

12.3.4 app

  1. 创建app
    • 命令行:python manage.py startapp app名称
    • pycharm:tools --> run manage.py task --> 输入命令:startapp app名称
  2. 注册app
    • 在settings.py中设置,例:app名为app01
    INSTALLED_APPS = [
       ...
       'app01',
       'app01.apps.App01Config',          # 推荐写法
    ]
  3. app中的文件
    • migrations:存放迁移文件的
    • admin.py:Django提供的后台管理工具
    • app.py:与app信息相关的
    • models.py:跟ORM有关的内容
    • views.py:视图,写函数的

12.3.5 使用MySQL流程

  1. 创建一个MySQL数据库:create database day53;
  2. 在settings.py中设置,Django连接MySQL数据库:
    DATABASES = {
       'default': {
           'ENGINE': 'django.db.backends.mysql',      # 引擎   
           'NAME': 'day53',                         # 数据库名称
           'HOST': '127.0.0.1',                     # ip地址
           'PORT':3306,                            # 端口
           'USER':'root',                            # 用户
           'PASSWORD':'123'                        # 密码
       }
    }
  3. 在与settings,py同级目录下的init文件中写入:
    import pymysql
    pymysql.install_as_MySQLdb()
  4. 创建表(在app下的models.py中写类):
    from django.db import models
    
    class User(models.Model):
       username = models.CharField(max_length=32)        # username varchar(32)
       password = models.CharField(max_length=32)       # username varchar(32)
  5. 执行数据库迁移的命令:
    • python manage.py makemigrations:检测每个注册app下的model.py,记录model的变更记录
    • python manage.py migrate:同步变更记录到数据库中

12.3.6 MVC和MTV

  1. MVC
    • M: model 模型 —— 和数据库打交道
    • V:view 视图 —— HTML
    • C: controller 控制器 —— 调度 传递指令 业务逻辑
  2. MTV:
    • M: model 模型 ORM —— 和数据库打交道
    • T: tempalte 模板 —— HTML
    • V:view 视图 —— 函数 业务逻辑
  3. djando是MTV模式

12.4 Django模板系统:Template

12.4.1 模板常用语法

  • 特殊符号:
    • 变量:{{ }}
    • 标签tag:{% %}

12.4.1.1 变量

  1. 符号:{{ }}
    • 表示变量,在模板渲染的时候替换成值
    • 使用方式:{{ 变量名 }}:变量名由字母数字和下划线组成
    • 点(.)在模板语言中有特殊的含义,用来获取对象的相应属性值
    • 注意:当模板系统遇到一个(.)时,会按照如下的顺序去查询:
      • 在字典中查询
      • 属性或者方法
      • 数字索引

12.4.1.2 内置filter

  1. filter:过滤器,用来修改变量的显示结果
    • 语法: {{ value|filter_name:参数 }}
    • ':'左右没有空格没有空格没有空格
  2. 内置过滤器
    • default:默认值
      • 语法:{{ value|default:"nothing"}}
      • 如果value值没传的话就显示nothing
      • 补充:TEMPLATES的OPTIONS可以增加一个选项:string_if_invalid:'找不到',可以替代default的的作用
    • filesizeformat:文件大小,将值格式化为可读的文件尺寸
      • 语法:{{ value|filesizeformat }}
      • 如,value=123456789,输出将会是 117.7 MB
    • add:给变量加参数
      • 语法:{{ first|add:second }}
      • 优先看是否能转化为数字相加,其次是字符串拼接
      • 如果都是列表,相当于extend,循环加入
    • length:返回长度
      • 语法:{{ value|length }}
      • 返回value的长度,如,value=['a', 'b', 'c', 'd']的话,就显示4
    • slice:切片
      • 语法:{{value|slice:"2:-1"}}
    • first / last:取第一个/最后一个元素
      • 语法:
      • 取第一个元素:{{ value|first }}
      • 取最后一个元素:{{ value|last }}
    • join:使用字符串拼接列表
      • 语法:{{ value|join:" // " }}
    • truncatechars:截断,按照字符计数
      • truncatewords:按照字母计数,不能识别中文
      • 如果字符串字符多于指定的字符数量,那么会被截断
      • 截断的字符串将以可翻译的省略号序列(...)结尾
      • 参数:截断的字符
      • 语法:{{ value|truncatechars:9}}
    • date:日期格式化
      • 语法:{{ value|date:"Y-m-d H:i:s"}}
      • 在settings.py中配置:
      USE_L10N = False
      
      DATETIME_FORMAT = 'Y-m-d H:i:s'    # datetime类型
      DATE_FORMAT = 'Y-m-d'            # date类型
      TIME_FORMAT = 'H:i:s'            # time类型
      • 配置后,使用{{now}}可以实现日期格式化
      • 其中,'now':datetime.datetime.now()
    • safe:告诉django这段代码是安全的,不需要转义
      • 语法:{{ value|safe}}
      • 如,value = "<a href='#'>点我</a>"

12.4.1.3 自定义filter

  1. 在app下创建一个名为templatetags的python包
  2. 在templatetags中创建py文件,文件名自定义(my_tags.py);
  3. 在py文件中写:
    from django import template
    register = template.Library()          # register也不能变
  4. 写函数+装饰器
    @register.filter
    def add_xx(value, arg):          # 最多有两个
       return '{}-{}'.format(value, arg)
    
    @register.filter(name='adds')    # 相当于更改了函数名,使用时,使用新的函数名
  5. 在模板文件中使用,html文件
    {% load my_tags %}
    {{ 'alex'|add_xx:'dsb' }}
  6. 注意:
    • 为避免出错,templatetags最好是一个Python包,并且名称不能更改
    • register名称也不能更改,必要时需要重启项目
    • Python包下的init中可能有其他内容django不能识别,导致出错,可以直接删除内容

12.4.1.4 标签tag

  1. for循环
    <ul>
    {% for user in user_list %}
       <li>{{ user.name }}</li>
    {% endfor %}
    </ul>
    • forloop:字典形式
Variable Description
forloop.counter 当前循环的索引值(从1开始)
forloop.counter0 当前循环的索引值(从0开始)
forloop.revcounter 当前循环的倒序索引值(到1结束)
forloop.revcounter0 当前循环的倒序索引值(到0结束)
forloop.first 当前循环是不是第一次循环(布尔值)
forloop.last 当前循环是不是最后一次循环(布尔值)
forloop.parentloop 本层循环的外层循环
  • for ... empty
    {% for book in all_books %}
       <tr>
           .....
       </tr>
    {% empty %}
       <td>没有相关的数据</td>
    {% endfor %}
    1. if判断
  • if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断
    {% if 条件1  %}
       xxx
    {% elif 条件2  %}
       xxx
    {% else  %}
       xxxxx
    {% endif %}
  • 连续判断
    • python中,10>5>1 --> 10>5 and 5>1 --> true
    • js中,10>5>1 --> true>1 --> 1>1 --> false
    • 模板中,不支持连续连续判断 也不支持算数运算(可使用过滤器)
      1. with:给变量重命名,但只在with区域内生效
    {% with hobby.2 as talk %}
    {# 相当于 {% with talk=hobby.2 %},其中=两边不能有空格 #}
       {{ talk }}
    {% endwith %}
    1. csrf_token
  • 该标签用于跨站请求伪造保护
    • csrf:跨站请求伪造
  • 使用方式:在form表单中写上{% csrf_token %}
  • 这样就不用在settings中注释含csrf的中间件了

12.4.1.5 注释

  • 符号:{# 要注释的内容 #}
  • 快捷键:Ctrl + ?
    {# 要注释的内容 #}

12.4.2 母板和继承

12.4.2.1 母板

  1. 母板就是一个普通的html,提取多个页面的公共部分,通过定义block块来实现
    <!DOCTYPE html>
    <html lang="en">
    <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>Title</title>
       {% block page-css %}
    
       {% endblock %}
    </head>
    <body>
       <h1>这是母板的标题</h1>
       {% block page-main %}
    
       {% endblock %}
       <h1>母板底部内容</h1>
       {% block page-js %}
    
       {% endblock %}
    </body>
    </html>
  2. block块:
    {% block 块名 %}
    
    {% endblock %}
  3. 注意:我们通常会在母板中定义页面专用的CSS块和JS块,方便子页面替换

12.4.2.2 继承

  1. 子页面继承母板:{% extends '母板html' %}
    {% extends 'layouts.html' %}
  2. 子页面通过重写block块,来替换母板中相应的内容
    {% block page-main %}
       <p>世情薄</p>
       <p>人情恶</p>
       <p>雨送黄昏花易落</p>
    {% endblock %}

12.4.2.3 注意

  1. {% extends 'base.html' %}要写在第一行,前面不要有内容,否则内容会显示出来
  2. {% extends 'base.html' %}中的'base.html' 必须加上引号,不然会被当做变量去查找
  3. 子页面把要显示的内容写在block块中,否则不会显示出来
  4. 多个位置有独特内容,可以定义多个block块,特殊:定义css、js块等

12.4.3 组件

  1. 组件:一小段html代码段
  2. 可以将常用的页面内容如导航条,页尾信息等组件保存在单独的文件中,然后在需要使用的地方导入
    {% include 'navbar.html' %}

12.4.4 静态文件相关

  1. 目的:更改setting中静态文件的别名时,不影响在更改之前的静态文件的引用,即引用会跟随别名的更改而自动更改,这样就不会报错了
  2. 方法一:使用static,原本路径:/static/images/hi.jpg
    {% load static %}
    <img src="{% static "images/hi.jpg" %}" alt="Hi">
    • 文件多处被用到可以存为一个变量
    {% load static %}
    {% static "images/hi.jpg" as myphoto %}
    <img src="{{ myphoto }}">
  3. 方法二:使用get_static_prefix,原本路径:/static/images/hi.jpg
    {% load static %}
    <img src="{% get_static_prefix %}images/hi.jpg" alt="Hi">
    
    {# 补充:获取别名 #}
    {% get_static_prefix %}
    • 文件多处被用到可以存为一个变量
    {% load static %}
    {% get_static_prefix as STATIC_PREFIX %}
    <img src="{{ STATIC_PREFIX }}images/hi.jpg" alt="Hi">

12.4.5 自定义simple_tag

  1. 和自定义filter类似,区别:接收的参数更灵活,能接收万能参数
  2. 定义注册simple_tag
    @register.simple_tag
    def join_str(*args, **kwargs):
       return '{} - {} '.format('*'.join(args), '$'.join(kwargs.values()))
    
    @register.simple_tag(name='join')    # 相当于更改了函数名,使用时,使用新的函数名
  3. 使用自定义simple_tag
    {% load my_tags %}
    {% join_str '1' '2' k1='3' k2='4' %}

12.4.6 inclusion_tag

  1. 在app下的templatetags(python包)中创建py文件,文件名自定义(my_inclusion.py);
  2. 在py文件中写:
    from django import template
    register = template.Library()          # register也不能变
  3. 写函数+装饰器
    @register.inclusion_tag('result.html')
    
    # result.html 是内容的html
    
    def show_results(n):
       n = 1 if n < 1 else int(n)
       data = ["第{}项".format(i) for i in range(1, n+1)]
       return {"data": data}
  4. 在result.html中写:
    <ul>
       {% for choice in data %}
       <li>{{ choice }}</li>
       {% endfor %}
    </ul>
  5. 在模板文件中使用
    {% load my_inclusion %}
    {% show_results 10 %}

12.4.7 总结自定义方法

  1. 自定义方法:filter,simple_tag,inclusion_tag
  2. 步骤:
    • 在已注册的APP下创建templatetags的python包;
    • 在包内创建python文件,如my_tags.py
    • 在py文件中写固定的内容:
    from django import template
    register = template.Library()
    • 写函数 + 装饰器
    @register.filter(name='add_a')
    def add_xx(value,arg):
       return 'addd_xx'
    
    @register.simple_tag
    def join_str(*args,**kwargs):
       return  'xxxxx'
    
    @register.inslusion_tag('page.html')
    def page(num):
       return {'num':range(1,num+1)}
    • 写模板
    {% for i in num %}   
       {{ i }}
    {%  endfor %}
    • 使用
    {% load my_tags %}
    
    {# filter#}
    {{ ’xxx‘|add_xx:'a'  }}      {{ ’xxx‘|add_a:'a'  }} 
    {# simple_tag #}
    {% join_str 1 2 k1=3 k2=4  %}
    {# inclusion_tag #}
    {% page 4 %}

12.5 Django的视图系统:View

12.5.1 CBV和FBV

  1. FBV:functoin based view,基于函数的view
    • 我们之前写过的都是基于函数的view
  2. CBV:class based view,基于类的view
    • 定义CBV:
    from django.views import View
    
    class AddPublisher(View):  
       def get(self,request):
           """处理get请求"""
           pass   
    
       def post(self,request):
           """处理post请求"""
           pass
    • 使用CBV:
    url(r'^add_publisher/', views.AddPublisher.as_view())
  3. as_view的流程
    • 项目启动加载ur.py时,执行类.as_view() --> view函数
    • 请求到来的时候执行view函数:
      • 实例化类 --> self
      • self.request = request
      • 执行self.dispatch(request, args, *kwargs)
      • 判断请求方式是否被允许:
        • 允许:通过反射获取到对应请求方式的方法 --> handler
        • 不允许:self.http_method_not_allowed --> handler
      • 执行handler(request,args,*kwargs)
        • 返回响应 --> 浏览器

12.5.2 视图加装饰器

  1. 装饰器示例:
    def timer(func):
       def inner(request, *args, **kwargs):
           start = time.time()
           ret = func(request, *args, **kwargs)
           print("函数执行的时间是{}".format(time.time() - start))
           return ret
       return inner
  2. FBV
    • 直接在函数上加装饰器
    @timer
    def publisher_list(request):
       pass
  3. CBV
    • 导入
    from django.utils.decorators import method_decorator
    • 方法一:直接在方法上加装饰器
      • 作用:只作用于加了装饰器的方法
    @method_decorator(timer)
    def get(self, request, *args, **kwargs):
       pass
    • 方法二:在dispatch方法上加装饰器
      • 作用:作用于类里面的所有方法
    @method_decorator(timer)
    def dispatch(self, request, *args, **kwargs):
       return super().dispatch(request, *args, **kwargs)
    • 方法三:在类上加装饰器,要指定name,即要加装饰器的方法名
      • 作用:只作用于name指定的方法
    @method_decorator(timer,name='get')
    class AddPublisher(View):
       pass
    • 特殊:等效于方法二,相当于给dispatch方法上了装饰器,作用于类里面的左右方法
    @method_decorator(timer,name='dispatch')
    class AddPublisher(View):
       pass
  4. 类的方法上也可以直接用@timer,效果等同于使用@method_decorator(timer)
    • 使用@timer和使用@method_decorator(timer)的区别:
      • 使用@timer:
      • 使用装饰器,取参数时【def inner(args, *kwargs)】,第一个参数默认是self,第二个参数才是request对象
      • 使用@method_decorator(timer):
      • 使用装饰器,取参数时【def inner(args, *kwargs)】,第一个参数就是request对象
    • 注意:之后取request对象做操作时,注意使用装饰器的方式,对应取request对象的方式也不同

12.5.3 request对象

  1. 属性:
    • request.methot:当前请求方式,GET/POST
    • request.GET:url上携带的参数
    • request.POST:POST请求提交的数据
    • request.path_info:url的路径,不包含ip和端口,不包含参数
    • request.body:请求体,byte类型,request.POST的数据是从boby里提取的
    • request.FILES:上传的文件,类似字典
    • request.META:请求头,标准的Python字典,包含左右HTTP首部
    • request.COOKIES:cookie
    • request.session:session
  2. 方法:
    • request.get_host():获取主机的ip和端口
    • request.get_full_path():url的路径,不包含ip和端口,包含参数
    • request.is_ajax():判断是否是ajax请求,是返回Ture,否返回False
  3. 上传文件示例:
    • urls.py
    url(r'^upload/', views.upload)
    • views.py
    def upload(request):
       if request.method == 'POST':
           # 获取文件
           # print(request.FILES)
           f1 = request.FILES.get('f1')
           # 保存文件
           with open(f1.name, 'wb') as f:
               for i in f1.chunks():
                   f.write(i)
       return render(request, 'upload.html')
    • upload.html
    <form action="" method="post" enctype="multipart/form-data">
       {% csrf_token %}
       <input type="file"  name="f1">
       <button>上传</button>
    </form>

12.5.4 response对象

  1. Response对象:render, redirect, HttpResponse
    from django.shortcuts import render, redirect, HttpResponse
    HttpResponse('字符串')            # 返回字符串
    render(request,'模板的文件名',{k1:v1})    # 返回一个完整的HTML页面
    redirect('重定向的地址')          # 返回重定向,Location:地址
  2. JsonResponse对象
    • 普通示例:
    import json
    def json_data(request):
       data = {'name': 'alex', 'age': 73}
       return HttpResponse(json.dumps(data))
       # Content-Type: text/html; charset=utf-8
    • 使用JsonResponse对象示例:
    from django.http.response import JsonResponse
    def json_data(request):
       data = {'name': 'alex', 'age': 73}
       return JsonResponse(data) 
       # Content-Type: application/json
    • Content-Type:响应头
      • Content-Type: application/json好处在于:告诉前端这是json类型,便于做相应操作
      • 默认Content-Type: text/html; charset=utf-8,更改直接使用参数content_type='application/json'
      import json
      def json_data(request):
       data = {'name': 'alex', 'age': 73}
       return HttpResponse(json.dumps(data),content_type='application/json')
       # 此时,Content-Type: application/json
    • JsonResponse对象默认只能传递字典,对于非字典类型,设置参数safe=False
    from django.http.response import JsonResponse
    def json_data(request):
       data = [1, 2, 3, 4]
       return JsonResponse(data,safe=False)

12.6 Django的路由系统:URL

12.6.1 URLconf配置

  1. 基本格式
    from django.conf.urls import url
    urlpatterns = [
        url(正则表达式, views视图,参数,别名),
    ]
    • 参数说明
      • 正则表达式:一个正则表达式字符串
      • views视图:一个可调用对象,通常为一个视图函数
      • 参数:可选的要传递给视图函数的默认参数(字典形式)
      • 别名:一个可选的name参数
  2. django 2.0版本的路由系统
    • 2.0版本中re_path和1.11版本的url是一样的用法
    from django.urls import path,re_path
    urlpatterns = [
       path('articles/2003/', views.special_case_2003),
    ]

12.6.2 正则表达式

  1. 基本配置
    from django.conf.urls import url
    from . import views
    urlpatterns = [
       url(r'^articles/2003/$', views.special_case_2003),
    ]
  2. 注意事项
    • urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续
    • 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(利用分组匹配)
    • 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles
    • 每个正则表达式前面的'r' 是可选的但是建议加上
  3. 补充说明
    • 是否开启URL访问地址后面不为/跳转至带有/的路径的配置项:APPEND_SLASH=True
    • Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True,其作用就是自动在网址结尾加'/'

12.6.3 分组命名匹配

  1. 分组:使用简单的正则表达式分组匹配(通过圆括号)来捕获URL中的值并以位置参数形式传递给视图函数
    from django.conf.urls import url
    from . import views
    urlpatterns = [
       url(r'^articles/([0-9]{4})/$', views.year_archive),
       url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
    ]
  2. 命名分组:使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图函数
    from django.conf.urls import url
    from . import views
    urlpatterns = [
       url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
       url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    ]
    • 补充:url的第三个参数时一个字典,表示想要传递给视图函数的额外关键字参数
      • 当传递额外参数的字典中的参数和URL中捕获值的命名关键字参数同名时,函数调用时,将使用的是字典中的参数,而不是URL中捕获的参数
      • 即优先级:额外参数 > URL捕获参数
  3. UPLconf匹配的位置:
    • URLconf 在请求的URL 上查找,将它当做一个普通的Python字符串,不包括GET和POST参数以及域名
      • http://www.example.com/myapp/请求中,URLconf 将查找 /myapp/
      • http://www.example.com/myapp/?page=3请求中,URLconf 仍将查找 /myapp/
    • URLconf 不检查请求的方法,即所有的请求方法(同一个URL的POST、GET、HEAD等),都将路由到相同的函数
  4. 捕获的参数都是字符串:每个在URLconf中捕获的参数都作为一个普通的Python字符串传递给视图函数,无论正则表达式使用的是什么匹配方式
  5. 有个别情况,需要视图函数指定默认值
    # urls.py中
    
    from django.conf.urls import url
    from . import views
    urlpatterns = [
       url(r'^blog/$', views.page),
       url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
    ]
    
    # 两个URL模式指向相同的view,但是第一个模式并没有从URL中捕获任何东西
    
    
    
    # views.py中,可以为num指定默认值
    
    def page(request, num="1"):
       pass
    
    # 如果第一个模式匹配上了,page函数将使用其默认参数num=“1”,如果第二个模式匹配,page将使用正则表达式捕获到的num值
    
  6. include:路由分发
    • 根据功能或种类的不同,可以把不同的功能的路由写在该功能的app文件下,利用include联系起来,相当于把不同功能的url放在不同的空间里
    from django.conf.urls import include, url
    urlpatterns = [
      url(r'^admin/', admin.site.urls),
      url(r'^app01/', include('app01.urls')),  # 可以包含其他的URLconfs文件
    ]
    • 补充:app01.urls
    from django.conf.urls import url
    from . import views
    urlpatterns = [
       url(r'^blog/$', views.blog),
       url(r'^blog/(?P<year>[0-9]{4})/(?P<month>\d{2})/$', views.blogs),
    ]
    • include有一个命名空间的参数:namespace
      • 便于区分不同路由空间中存在的同名函数,利于指定到确切的函数
    from django.conf.urls import include, url
    urlpatterns = [
       url(r'^app01/', include('app01.urls', namespace='app01')),
       url(r'^app02/', include('app02.urls', namespace='app02')),
    ]

12.6.4 URL的命名和反向解析

12.6.4.1 URL命名

  1. url的第四个参数是起一个别名,即一个可选的name参数
  2. 命名方式:name = '别名'
    from django.conf.urls import url
    urlpatterns = [
       url(r'^home', views.home, name='home'),        # 给我的url匹配模式起名为 home
       url(r'^index/(\d*)', views.index, name='index'),       # 给我的url匹配模式起名为index
    ]

12.6.4.2 反向解析

  1. 反向解析:通过别名获取完整URL路径
  2. 情况一:静态路由
    • 命名
    from django.conf.urls import url
    urlpatterns = [
       url(r'^blog/$', views.blog, name='blog'),
    ]
    • 模板中使用
    {% url 'blog' %}            {# 完整URL路径:/blog/ #}
    • py文件中使用
    from django.urls import reverse
    reverse('blog')              # 完整URL路径:/blog/
  3. 情况二:分组
    • 命名
    from django.conf.urls import url
    urlpatterns = [
       url(r'^blog/([0-9]{4})/(\d{2})/$', views.blogs, name='blogs'),
    ]
    • 模板中使用
    {% url 'blogs' 2222 12 %}        {# 完整URL路径:/blog/2222/12/ #}
    • py文件中使用
    from django.urls import reverse
    reverse('blogs',args=('2019','06'))          # 完整URL路径:/blog/2019/06/
  4. 情况三:命名分组
    • 命名
    from django.conf.urls import url
    urlpatterns = [
       url(r'^blog/(?P<year>[0-9]{4})/(?P<month>\d{2})$', views.blogs, name='blogs'),
    ]
    • 模板中使用
    {% url 'blogs' year=2222 month=12 %}         {# 完整URL路径:/blog/2222/12/ #}
    • py文件中使用
    from django.urls import reverse
    reverse('blogs',kwargs={'year':'2019','month':'06'})      # 完整URL路径:/blog/2019/06/

12.6.5 命名空间模式下的反向解析

  1. 项目目录下的urls.py
    from django.conf.urls import url, include
    urlpatterns = [
       url(r'^app01/', include('app01.urls', namespace='app01')),
       url(r'^app02/', include('app02.urls', namespace='app02')),
    ]
  2. app01中的urls.py
    from django.conf.urls import url
    from app01 import views
    urlpatterns = [
       url(r'^(?P<pk>\d+)/$', views.detail, name='detail')
    ]
  3. app02中的urls.py
    from django.conf.urls import url
    from app02 import views
    urlpatterns = [
       url(r'^(?P<pk>\d+)/$', views.detail, name='detail')
    ]
  4. 反向解析语法:'命名空间名称:URL名称'
    • 在模板中的使用方式
    {% url 'app01:detail' pk=12 %}
    {% url 'app02:detail' pk=12 %}
    • 在py文件中的使用方式
    from django.urls import reverse
    reverse('app01:detail', kwargs={'pk':11})
    reverse('app01:detail', kwargs={'pk':11})
  5. 注意:如果有多层路由分发,有了多个命名空间名称,都要把命名空间名称一层一层加在别名前面
    {# 简单示例:app01是第一层路由空间的namespace,xxx是第二层路由空间的namespace #}
    {% url 'app01:xxx:index' %}

12.7 ORM:对象关系映射

12.7.1 基本内容

  1. 定义:面向对象和关系型数据库的一种映射,通过操作对象的方式操作数据
  2. 对应关系:
    • 类对应数据表
    • 对象对应数据行(记录)
    • 属性对应字段
ORM DB
数据表
对象 数据行
属性 字段
  1. 导入:from app01 import models
  2. 查:
    • models.Publisher.objects.all()
      • 查询所有的数据,queryset:对象列表
    • models.Publisher.objects.get(name='xxxx')
      • 对象,获取一个对象(有且唯一),获取不到或者获取到多个对象会报错
    • models.Publisher.objects.filter(name='xxxx')
      • 获取满足条件的所有的对象,queryset:对象列表
  3. 增:
    • models.Publisher.objects.create(name='xxx')
      • 新插入数据库的对象
    • obj = models.Publisher(name='xxx')
      • 存在在内存中的对象
    • obj.save()
      • 提交到数据库中,新增
  4. 删:
    • obj = models.Publisher.objects.get(pk=1)
      • obj.delete()
    • obj_list = models.Publisher.objects.filter(pk=1)
      • obj_list.delete()
  5. 改:
    • obj = models.Publisher.objects.get(pk=1)
    • obj.name = 'new name'
      • 在内存中修改对象的属性
    • obj.save()
      • 提交数据,保存到数据库中

12.7.2 字段

  1. 常用字段
    • AutoField:自增的整型字段,必填参数primary_key=True
      • 注意:一个model不能有两个AutoField字段
    • IntegerField:整数类型,数值的范围是 -2147483648 ~ 2147483647
    • CharField:字符类型,必须提供max_length参数,max_length表示字符的长度
    • DateField:日期类型,日期格式为YYYY-MM-DD,相当于Python中的datetime.date的实例
      • 参数:
      • auto_now_add=True:新增数据的时候会自动保存当前的时间
      • auto_now=True:新增、修改数据的时候会自动保存当前的时间
    • DatetimeField:日期时间类型,格式为YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime的实例
    • BooleanField:布尔值类型
    • TextField:文本类型
    • DecimalField:十进制小数
      • 参数:
      • max_digits:小数总长度
      • decimal_places:小数位总长度
  2. 自定义字段
    • 自定义一个char类型字段
    class MyCharField(models.Field):
       """
       自定义的char类型的字段类
       """
       def __init__(self, max_length, *args, **kwargs):
           self.max_length = max_length
           super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
    
       def db_type(self, connection):
           """
           限定生成数据库表的字段类型为char,长度为max_length指定的值
           """
           return 'char(%s)' % self.max_length
    • 使用自定义char类型字段
    class Class(models.Model):
       id = models.AutoField(primary_key=True)
       title = models.CharField(max_length=25)
       # 使用自定义的char类型的字段
       cname = MyCharField(max_length=25)

12.7.3 字段参数

  1. null:数据库中字段是否可以为空
  2. default:数据库中字段的默认值
  3. primary_key:数据库中字段是否为主键
  4. db_index:数据库中字段是否可以建立索引
  5. unique:数据库中字段是否可以建立唯一索引
  6. blank:Admin中是否允许用户输入为空
  7. choices:Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
    • choices=((0, '女'), (1, '男') :可填写的内容和提示

12.7.4 Model Meta参数

  • 在表对于的类中写入一个类Meta
    class UserInfo(models.Model):
      nid = models.AutoField(primary_key=True)
      username = models.CharField(max_length=32)
    
      class Meta:
          # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
          db_table = "table_name"
          # admin中显示的表名称
          verbose_name = '个人信息'
          # verbose_name加s
          verbose_name_plural = '所有用户信息'
          # 联合索引
          index_together = [
              ("pub_date", "deadline"),       # 应为两个存在的字段
          ]
          # 联合唯一索引
          unique_together = (("driver", "restaurant"),)   # 应为两个存在的字段

12.7.5 ORM操作——必知必会13条

  1. all():查询所有结果,返回QuerySet对象
  2. get():返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误,返回单个对象
  3. filter():返回所有符合条件的对象,返回QuerySet对象
  4. exclude():返回所有不符合条件的对象,返回QuerySet对象
  5. values('字段'):拿到对象指定的字段和字段的值,返回的是一个字典序列,返回QuerySet对象
  6. values_list('字段'):拿到对象指定的字段的值,返回的是一个元组序列,返回QuerySet对象
  7. order_by():对查询结果排序,默认升序,字段前加负号则为降序,返回QuerySet对象
    • order_by('age','-pid'):先按age字段升序排列,再按pid字段降序排列
  8. reverse():对查询结果反向排序,只能对已经排序的QuerySet进行反转,返回QuerySet对象
  9. distinct():对查询结果进行去重,完全相同的内容才能去重,返回QuerySet对象
  10. count():计数,返回数据库中匹配查询的对象数量,返回数字
  11. first():返回第一条记录,即取第一个元素,没有返回None,返回单个对象
  12. last():返回最后一条记录,即取最后一个元素,没有返回None,返回单个对象
  13. exist():判断查询的数据是否存在,存在返回True,否则返回False,返回布尔值
  • 总结:
    # 返回的是queryset对象的
    
    all()   
    filter()
    exclude()
    values() 
    values_list()
    order_by()
    reverse()
    distinct()
    
    # 返回的是单个对象的
    
    get()
    first()
    last()
    
    # 返回的是数字的
    
    count()
    
    # 返回的是布尔值的
    
    exists()

12.7.6 单表查询的双下划线

  1. 条件判断,相当于SQL中的while
    • __gt:大于
      • ret = models.Person.objects.filter(pk__gt=1)
      • 获取pk大于1的
    • __lt:小于
      • ret = models.Person.objects.filter(pk__lt=3)
      • 获取pk小于3的
    • __gte:大于等于
      • ret = models.Person.objects.filter(pk__gte=1)
      • 获取pk大于等于1的
    • __lte:小于等于
      • ret = models.Person.objects.filter(pk__lte=3)
      • 获取pk小于等于3的
  2. __range:范围查询,相当于SQL的between...and...
    • ret = models.Person.objects.filter(pk__range=[1,3])
      • 获取pk范围是1到3的
  3. __in:成员判断
    • ret = models.Person.objects.filter(pk__in=[1,3])
      • 获取pk等于1、3的,相当于SQL的 in
    • ret = models.Person.objects.exclude(pk__in=[1,3])
      • 获取pk不等于1和3的,相当于SQL的 not in
  4. 模糊查询:相当于SQL的 like 和正则匹配
    • __contains:模糊查询
      • ret = models.Person.objects.filter(name__contains='A')
      • 获取name字段的值包含'A'的
    • __icontains:在contains的基础上,对条件中的字母大小写不敏感
      • ret = models.Person.objects.filter(name__icontains='A')
      • 获取name字段的值包含'A'或'a'的,忽略大小写
  5. 判断以...开头/结尾
    • __startswith:以...开头
      • ret = models.Person.objects.filter(name__startswith='A')
      • 获取name字段的值以'A'开头的
    • __istartswith:在startswith的基础上,对条件中的字母大小写不敏感
      • ret = models.Person.objects.filter(name__istartswith='A')
      • 获取name字段的值以'A'或'a'开头的,忽略大小写
    • __endswith:以...结尾
      • ret = models.Person.objects.filter(name__endswith='A')
      • 获取name字段的值以'A'结尾的
    • __iendswith:在endswith的基础上,对条件中的字母大小写不敏感
      • ret = models.Person.objects.filter(name__iendswith='A')
      • 获取name字段的值以'A'或'a'结尾的,忽略大小写
  6. __year:判断日期年份
    • ret = models.Person.objects.filter(birth__year='2019')
      • 获取birth字段的值的年份是2019的
    • __year只能筛选年份,如果要筛选年月日,使用contains:模糊查询
      • ret = models.Person.objects.filter(birth__contains='2018-06-24')
      • 获取birth字段的值是2018-06-24的
  7. __isnull:查询与null的项
    • __isnull = True:查询值是null的
      • ret = models.Person.objects.filter(phone__isnull=True)
      • 获取phone字段的值是null的
    • __isnull = False:查询值不是null的

12.7.7 ForeignKey操作

  1. 基于对象的查询
    • 正向查询,语法:对象.关联字段.字段
    book_obj = models.Book.objects.get(title='菊花怪大战MJJ')
    book_obj.pub
    book_obj.pub.name
    • 反向查询,语法:对象.表名_set.all(),表名即类名小写
      • 设置ForeignKey的参数:related_name,相当于替换了类名小写_set
    pub_obj = models.Publisher.objects.get(pk=1)
    
    # 没有指定related_name,使用类名小写_set
    
    pub_obj.book_set.all()
    
    # 指定related_name='books'
    
    pub_obj.books.all()
  2. 基于字段的查询
    • 正向查询,语法:关联字段__字段
    # 查询老男孩出版的书
    
    ret = models.Book.objects.filter(pub__name='老男孩出版社')
    • 反向查询,语法:表名__字段
      • 设置ForeignKey的参数:related_query_name,相当于替换了表名
    # 查询出版菊花怪大战MJJ的出版社
    
    
    # 没有指定related_name,使用类名的小写
    
    ret= models.Publisher.objects.filter(book__title='菊花怪大战MJJ')
    
    # related_name='books'
    
    ret= models.Publisher.objects.filter(books__title='菊花怪大战MJJ')
    
    # related_query_name='xxx'
    
    ret= models.Publisher.objects.filter(xxx__title='菊花怪大战MJJ')

12.7.8 多对多的操作

  1. 基于对象的查询
    • 正向查询,语法:对象.多对多字段.all()
    mjj = models.Author.objects.get(pk=1)
    mjj.books          # 关系管理对象
    mjj.books.all()
    • 反向查询,语法:对象.类名小写_set.all()
      • 设置related_name,相当于替换了类名小写_set
    book_obj = models.Book.objects.filter(title='桃花侠大战菊花怪').first()
    
    # 不指定related_name
    
    book_obj.author_set      # 关系管理对象
    book_obj.author_set.all()
    
    # 指定related_name='authors'
    
    book_obj.authors          # 关系管理对象
    book_obj.authors.all()
  2. 基于字段的查询
    • 正向查询,语法:多对多字段__字段
    ret = models.Author.objects.filter(books__title='菊花怪大战MJJ')
    • 反向查询,语法:类名小写__字段
      • 设置related_query_name,相当于替换了类名小写
    # 不指定related_name
    
    ret = models.Book.objects.filter(author__name='MJJ')
    
    # 指定related_name='authors'
    
    ret = models.Book.objects.filter(authors__name='MJJ')
    
    # 指定related_query_name='xxx'
    
    ret = models.Book.objects.filter(xxx__name='MJJ')
  3. 关系管理对象的方法
    • all():所关联的所有对象
    mjj = models.Author.objects.get(pk=1)
    mjj.books.all()
    • set():设置多对多关系
    # set()  [id,id]   [对象,对象]
    
    mjj.books.set([1,2])
    mjj.books.set(models.Book.objects.filter(pk__in=[1,2,3]))
    • add():添加多对多关系
    # add  (id,id)   (对象,对象)
    
    mjj.books.add(4,5)
    mjj.books.add(*models.Book.objects.filter(pk__in=[4,5]))
    • remove():删除多对多关系
    # remove (id,id)   (对象,对象)
    
    mjj.books.remove(4,5)
    mjj.books.remove(*models.Book.objects.filter(pk__in=[4,5]))
    • clear():清除所有多对多关系
    mjj.books.clear()
    • create():创建多对多关系
    obj = mjj.books.create(title='跟MJJ学前端',pub_id=1)
    
    book__obj = models.Book.objects.get(pk=1)
    obj = book__obj.authors.create(name='taibai')

12.7.9 聚合和分组

  1. 聚合
    • 内置函数:Max(最大值)、Min(最小值)、Avg(平均值)、Sum(求和)、Count(计数)
    from app01 import models
    from django.db.models import Max, Min, Avg, Sum, Count
    
    # 为聚合值指定名称,注意关键字传参要在位置传参后面
    
    ret = models.Book.objects.filter(pk__gt=3).aggregate(Max('price'),avg=Avg('price'))
    print(ret)
  2. 分组
    • 一般和聚合一起使用
    # 统计每一本书的作者个数,annotate:注释
    
    ret = models.Book.objects.annotate(count=Count('author'))
    
    
    # 统计出每个出版社的最便宜的书的价格
    
    
    # 方式一
    
    ret = models.Publisher.objects.annotate(Min('book__price')).values()
    
    # 方式二:objects后面接values,就是按字段分组,相当于分组条件
    
    ret = models.Book.objects.values('pub_id').annotate(min=Min('price'))

12.7.10 F查询和Q查询

  1. F查询
    from django.db.models import F
    
    # 比较两个字段的值
    
    ret=models.Book.objects.filter(sale__gt=F('kucun'))
    
    
    # 只更新sale字段
    
    models.Book.objects.all().update(sale=100)
    
    
    # 取某个字段的值进行操作
    
    models.Book.objects.all().update(sale=F('sale')*2+10)
  2. Q查询
    • 条件符号:&(与)、|(或)、~(非)
    from django.db.models import Q
    ret = models.Book.objects.filter(~Q(Q(pk__gt=3) | Q(pk__lt=2)) & Q(price__gt=50))
    print(ret)

12.7.11 事务

  • 使用try进行回滚时,必须把try写在with的外面,否则无法回滚
    from django.db import transaction
    
    try:
      with transaction.atomic():
          # 进行一系列的ORM操作
          models.Publisher.objects.create(name='xxxxx')
          models.Publisher.objects.create(name='xxx22')
    except Exception as e :
      print(e)

12.7.12 其他操作设置

  1. Django ORM执行原生SQL
    # 执行原生SQL
    
    
    # 更高灵活度的方式执行原生SQL语句
    
    from django.db import connection, connections
    cursor = connection.cursor() 
    cursor = connections['default'].cursor()
    cursor.execute("""SELECT * from auth_user where id = %s""", [1])
    row = cursor.fetchone()
  2. Django终端打印SQL语句
    LOGGING = {
       'version': 1,
       'disable_existing_loggers': False,
       'handlers': {
           'console':{
               'level':'DEBUG',
               'class':'logging.StreamHandler',
           },
       },
       'loggers': {
           'django.db.backends': {
               'handlers': ['console'],
               'propagate': True,
               'level':'DEBUG',
           },
       }
    }
  3. 在Python中脚本中调用Django环境
    • 在要执行的py文件中写入
    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "orm_practice.settings")
    
    # orm_practice.settings表示当前项目的settings.py,orm_practice是当前项目名称
    
    import django
    django.setup()
    
    from app01 import models
    
    # 再继续写要执行的语句,例如
    
    books = models.Book.objects.all()
    print(books)

12.8 cookie 和 session

12.8.1 cookie

  1. 定义:保存在浏览器本地上的一组组键值对
  2. 特点:
    • 由服务器让浏览器进行设置的
    • 浏览器保存在浏览器本地
    • 下次访问时自动携带
  3. 应用:
    • 登录
    • 保存浏览习惯
    • 简单的投票
  4. 使用cookie的原因:因为HTTP是无状态的,用cookie来保存状态
  5. 在django中操作cookie
    • 设置cookie:
      # HttpResponse,render也可以
      
      ret = redirect('/index')
      ret.set_cookie('key',value,...)        # Set-Cookie:key=value
      ret.set_signed_cookie('key',value,salt='加密盐',...)
      • 参数:
      • key,键
      • value='',值
      • max_age=None,超出时间
      • expires=None,超出时间(针对于IE浏览器)
      • path='/',cookie生效的路径,/表示根路径
      • domain=None,cookie生效的域名
      • secure=False,https传输
      • httponly=False,只能http协议传输,无法被JavaScript获取
    • 获取cookie:
      request.COOKIES.get('key')
      request.get_signed_cookie('key',salt='加密盐',default=RAISE_ERROR,max_age=None)
      • 参数:
      • default:默认值
      • salt:加密盐
      • max_age:后台控制过期时间
      • 注意:获取时的加密盐要和设置时的加密盐相同,否则无法获取到正确的数据
    • 删除cookie:
      def logout(request):
       ret = redirect("/login/")
       ret.delete_cookie("key")          # 删除用户浏览器上之前设置的cookie值
       return rep
  6. cookie版登陆校验
    from django.shortcuts import render, redirect, HttpResponse
    from django.views import View
    
    class Login(View):
       def get(self, request, *args, **kwargs):
           return render(request, 'login.html')
    
       def post(self, request, *args, **kwargs):
           username = request.POST.get('username')
           pwd = request.POST.get('pwd')
           if username == 'alex' and pwd == '123':
               url = request.GET.get('return_url')
               if url:
                   ret = redirect(url)
               else:
                   ret = redirect('/index/')
               # 设置 cookie
               # ret['Set-Cookie'] = 'is_login=100; Path=/'
               ret.set_cookie('is_login', '1')      # 不加密的     Set-Cookie: is_login=1; Path=/
               ret.set_signed_cookie('is_login', '1', 's21',max_age=10000,)     # 加密的
               return ret
           return render(request, 'login.html', {'error': '用户名或密码错误'})
    
    
    # 登录验证装饰器
    
    def login_required(func):
       def inner(request, *args, **kwargs):
           # 获取 cookie
           is_login = request.COOKIES.get('is_login')        # 不加密的
           is_login = request.get_signed_cookie('is_login', salt='s21', default='')    # 加密的
           print(is_login)
           url = request.path_info
           if is_login != '1':
               return redirect('/login/?return_url={}'.format(url))
           # 已经登录
           ret = func(request, *args, **kwargs)
           return ret
       return inner
    
    
    # 在需要在需要登录才能访问到页面的视图上加装饰器
    
    @login_required
    def index(request):
       return HttpResponse('首页')
    
    @login_required
    def home(request):
       return HttpResponse('home')
    
    def logout(request):
       ret = redirect('/login/')
       ret.delete_cookie('is_login')
       return ret

12.8.2 session

  1. 定义:保存在服务器上的一组组键值对(必须依赖cookie来使用)
  2. 使用session的原因:
    • cookie保存在浏览器本地,不安全
    • cookie保存的大小个数受到限制(cookie本身最大支持4096字节)
  3. 总结:cookie弥补了HTTP无状态的不足,但是cookie以文本的形式保存在浏览器本地,自身安全性较差,所以我们通过cookie识别不同用户,对应的在session里保存私密信息以及超过4096字节的文本
  4. 在django中操作session
    • 设置session:
    request.session['key'] = value
    request.session.setdefault('key',value) # 设置默认值,存在则不设置
    • 获取session:
    request.session['key']
    request.session.get('key',None)
    • 删除session:
    del request.session['key']
    • 其他操作:
    # 所有 键、值、键值对
    
    request.session.keys()
    request.session.values()
    request.session.items()
    
    
    # 会话session的key
    
    request.session.session_key
    
    
    # 将所有Session失效日期小于当前日期的数据删除
    
    request.session.clear_expired()
    
    
    # 检查会话session的key在数据库中是否存在
    
    request.session.exists("session_key")
    
    
    # 删除当前会话的所有Session数据
    
    request.session.delete()
      
    
    # 删除当前的会话数据并删除会话的Cookie
    
    request.session.flush()
       # 这用于确保前面的会话数据不可以再次被用户的浏览器访问
       # 例如,django.contrib.auth.logout() 函数中就会调用它
    
    
    # 设置会话Session和Cookie的超时时间
    
    request.session.set_expiry(value)
       # 如果value是个整数,session会在些秒数后失效
       # 如果value是个datatime或timedelta,session就会在这个时间后失效
       # 如果value是0,用户关闭浏览器session就会失效
       # 如果value是None,session会依赖全局session失效策略
  5. session流程解析

    1561535102707

  6. session版登陆验证
    from django.shortcuts import render, redirect, HttpResponse
    from django.views import View
    
    class Login(View):
       def get(self, request, *args, **kwargs):
           return render(request, 'login.html')
    
       def post(self, request, *args, **kwargs):
           username = request.POST.get('username')
           pwd = request.POST.get('pwd')
           if username == 'alex' and pwd == '123':
               url = request.GET.get('return_url')
               if url:
                   ret = redirect(url)
               else:
                   ret = redirect('/index/')
               # 设置 session
               request.session['is_login'] = 1        # value可以设置为数字
               # 设置会话Session和Cookie的超时时间,0表示用户关闭浏览器session就会失效
               # request.session.set_expiry(0)
               return ret
           return render(request, 'login.html', {'error': '用户名或密码错误'})
    
    
    # 登录验证装饰器
    
    def login_required(func):
       def inner(request, *args, **kwargs):
           # 获取 session
           is_login = request.session.get('is_login')
           print(is_login)
           url = request.path_info
           if is_login != 1:
               return redirect('/login/?return_url={}'.format(url))
           # 已经登录
           ret = func(request, *args, **kwargs)
           return ret
       return inner
    
    
    # 在需要在需要登录才能访问到页面的视图上加装饰器
    
    @login_required
    def index(request):
       # request.session.session_key:会话session的key
       request.session.clear_expired()        # 将失效的数据删除
       return HttpResponse('首页')
    
    @login_required
    def home(request):
       return HttpResponse('home')
    
    def logout(request):
       ret = redirect('/login/')
       request.session.delete()     # 删除session数据  不删除cookie
       request.session.flush()      # 删除session数据  并删除cookie
       return ret
  7. django中的session配置
    • 全局配置:from django.conf import global_settings
    1. 数据库Session
    SESSION_ENGINE = 'django.contrib.sessions.backends.db'     # 引擎(默认)
    
    2. 缓存Session
    SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
    
    # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
    
    SESSION_CACHE_ALIAS = 'default'       
    
    3. 文件Session
    SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
    
    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()
    
    SESSION_FILE_PATH = None
    
    4. 缓存+数据库
    SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎
    
    5. 加密Cookie Session
    SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎
    
    其他公用设置项:
    
    # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
    
    SESSION_COOKIE_NAME = "sessionid"
    
    # Session的cookie保存的路径(默认)
    
    SESSION_COOKIE_PATH = "/"
    
    # Session的cookie保存的域名(默认)
    
    SESSION_COOKIE_DOMAIN = None
    
    # 是否Https传输cookie(默认)
    
    SESSION_COOKIE_SECURE = False
    
    # 是否Session的cookie只支持http传输(默认)
    
    SESSION_COOKIE_HTTPONLY = True
    
    # Session的cookie失效日期(2周)(默认)
    
    SESSION_COOKIE_AGE = 1209600
    
    # 是否关闭浏览器使得Session过期(默认)
    
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False
    
    # 是否每次请求都保存Session,默认修改之后才保存(默认)
    
    SESSION_SAVE_EVERY_REQUEST = False

12.9 AJAX

12.9.1 基本内容

  1. 定义:AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”,即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)
  2. 作用:AJAX就是使用 js 技术发送请求和接收响应
  3. 优点:在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容
  4. 特点:
    • 异步交互
      • 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求
      • 异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求
    • 局部刷新,无须刷新整个页面
    • 传输的数据量少,性能高
  5. 应用场景:
    • 搜索引擎根据用户输入的关键字,自动提示检索关键字
    • 注册时候的用户名的查重
  6. 普通示例:页面输入两个整数,通过AJAX传输到后端计算出结果并返回
    # urls.py
    
    urlpatterns = [
       url(r'^ajax_add/', views.ajax_add),
       url(r'^ajax_demo1/', views.ajax_demo1), 
    ]
    # views.py
    
    def ajax_demo1(request):
       return render(request, "ajax_demo1.html")
    
    def ajax_add(request):
       i1 = int(request.GET.get("i1"))
       i2 = int(request.GET.get("i2"))
       ret = i1 + i2
       return JsonResponse(ret, safe=False)
    {# ajax_demo1.html #}
    <input type="text" id="i1">+
    <input type="text" id="i2">=
    <input type="text" id="i3">
    <input type="button" value="AJAX提交" id="b1">
    
    <script src="/static/jquery-3.2.1.min.js"></script>
    <script>
       $("#b1").on("click", function () {
           $.ajax({
               url:"/ajax_add/",
               type:"GET",
               data:{"i1":$("#i1").val(),"i2":$("#i2").val()},
               success:function (data) {
                   $("#i3").val(data);
               },
               error:function (error) {
                   console.log(error)
               },
           })
       })
    </script>

12.9.2 jQuery实现的AJAX

  • 最基本的jQuery发送AJAX请求示例
    # views.py
    
    def ajax_test(request):
      user_name = request.POST.get("username")
      password = request.POST.get("password")
      print(user_name, password)
      return HttpResponse("OK")
    <button id="ajaxTest">AJAX 测试</button>
    <script>
      $("#ajaxTest").click(function () {
          $.ajax({
              url: "/ajax_test/",
              type: "POST",
              data: {username: "Q1mi", password: 123456},
              success: function (data) {
                  alert(data)
              }
          })
      })
    </script>
  • $.ajax参数
    • data参数中的键值对,如果值不为字符串,需要将其转换成字符串类型
    <script>
        $("#b1").on("click", function () {
            $.ajax({
                url:"/ajax_add/",
                type:"GET",
                data:{"i1":$("#i1").val(),"i2":$("#i2").val(),"hehe": JSON.stringify([1, 2, 3])},
                success:function (data) {
                    $("#i3").val(data);
                }
            })
        })
    </script>

12.9.3 AJAX通过csrf的校验

  1. 前提条件:确保有csrftoken的cookie
    • 在页面中使用{% csrf_token %}
    • 加装饰器:ensure_csrf_cookie
    • 注意:
      • 如果使用从cookie中取csrftoken的方式,需要确保cookie存在csrftoken值
      • 如果你的视图渲染的HTML文件中没有包含 {% csrf_token %},Django可能不会设置CSRFtoken的cookie。
      • 这个时候需要使用ensure_csrf_cookie()装饰器强制设置Cookie
  2. AJAX请求如何设置csrf_token
    • 通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送
    $.ajax({
       url: "/cookie_ajax/",
       type: "POST",
       data: {
           "username": "Q1mi",
           "password": 123456,
           {# 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中 #}
           "csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val()
       },
       success: function (data) {
           console.log(data);
       }
    })
    • 通过获取返回的cookie中的字符串,放置在请求头中发送
      • 注意:需要引入一个jquery.cookie.js插件
    $.ajax({
       url: "/cookie_ajax/",
       type: "POST",
       {# 从Cookie取csrftoken,并设置到请求头中 #}
       headers: {"X-CSRFToken": $("[name = 'csrfmiddlewaretoken']").val()},
       data: {"username": "Q1mi", "password": 123456},
       success: function (data) {
           console.log(data);
       }
    })
    • 使用文件:自己写一个getCookie方法
      • 粘贴在 static 下的 js 中的 一个js文件 ,比如:ajax_setup.js
    function getCookie(name) {
       var cookieValue = null;
       if (document.cookie && document.cookie !== '') {
           var cookies = document.cookie.split(';');
           for (var i = 0; i < cookies.length; i++) {
               var cookie = jQuery.trim(cookies[i]);
               if (cookie.substring(0, name.length + 1) === (name + '=')) {
                   cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                   break;
               }
           }
       }
       return cookieValue;
    }
    // 通过js获取csrftoken的值
    var csrftoken = getCookie('csrftoken');
    
    // 使用$.ajaxSetup()方法为ajax请求统一设置
    function csrfSafeMethod(method) {
     return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    
    $.ajaxSetup({
     beforeSend: function (xhr, settings) {
       // 不是 GET|HEAD|OPTIONS|TRACE 这些请求的话,就执行if后的操作
       if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
         xhr.setRequestHeader("X-CSRFToken", csrftoken);   // 设置请求头,本质同方法2
       }
     }
    });

12.9.4 AJAX上传文件

  • 上传文件示例
    <input type="file" id="f1">
    <button id="b1">上传</button>
    
    <script src="/static/jquery.js"></script>
    <script>
      $('#b1').click(function () {
          var  formobj =  new FormData();
          formobj.append('file',$('#f1')[0].files[0]);
          formobj.append('name','alex');
          $.ajax({
              url: '/upload/',
              type: 'post',
              data:formobj ,
              processData:false,
              contentType:false,
              success: function (res) {
                  $("[name='i3']").val(res)
              },
          })
      })
    </script>

12.10 form组件

  • form组件的功能
    • 生产input标签
    • 对提交的数据可以进行校验
    • 提供错误提示

12.10.1 使用form组件实现注册功能

  1. views.py
    • 先定义好一个RegForm类
    from django import forms
    
    
    # 按照Django form组件的要求自己写一个类
    
    class RegForm(forms.Form):
       name = forms.CharField(label="用户名")
       pwd = forms.CharField(label="密码")
    • 再写一个视图函数
    # 使用form组件实现注册方式
    
    def register2(request):
       form_obj = RegForm()
       if request.method == "POST":
           # 实例化form对象的时候,把post提交过来的数据直接传进去
           form_obj = RegForm(request.POST)
           # 调用form_obj校验数据的方法
           if form_obj.is_valid():        # 对数据进行校验
               # form_obj.cleaned_data:通过校验的数据
               return HttpResponse("注册成功")
       return render(request, "register2.html", {"form_obj": form_bj})
    • 针对form_obj.is_valid(),is_valid的流程:
      • 执行full_clean()的方法:
      • 定义错误字典
      • 定义存放清洗数据的字典
      • 执行_clean_fields方法:
      • 循环所有的字段
      • 获取当前的值
      • 对进行校验 ( 内置校验规则 自定义校验器)
        • 通过校验:self.cleaned_data[name] = value
        • 如果有局部钩子,执行进行校验:
          • 通过校验,self.cleaned_data[name] = value
          • 不通过校验,self._errors添加当前字段的错误并且self.cleaned_data中当前字段的值删除掉
        • 没有通过校验:self._errors 添加当前字段的错误
      • 执行全局钩子clean方法
  2. login2.html
    <form action="/reg2/" method="post" novalidate autocomplete="off">
       {% csrf_token %}
       <div>
           <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label>
           {{ form_obj.name }} {{ form_obj.name.errors.0 }}
       </div>
       <div>
           <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label>
           {{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }}
       </div>
       <div>
           <input type="submit" class="btn btn-success" value="注册">
       </div>
    </form>
    • 常用变量:
    {{ form_obj.as_p }}:生成一个个P标签  input  label
    {{ form_obj.errors }}:form表单中所有字段的错误
    {{ form_obj.username }}:一个字段对应的input框
    {{ form_obj.username.label }}:该字段的中文提示
    {{ form_obj.username.id_for_label }}:该字段input框的id
    {{ form_obj.username.errors }}:该字段的所有的错误
    {{ form_obj.username.errors.0 }}:该字段的第一个错误
  3. 看网页效果,验证了form的功能:
    • 前端页面是form类的对象生成的
      • 生成HTML标签功能
    • 当用户名和密码输入为空或输错之后,页面都会提示
      • 用户提交校验功能
    • 当用户输错之后,再次输入上次的内容还保留在input框
      • 保留上次输入内容

12.10.2 常用字段与插件

  1. 创建Form类时,主要涉及到字段和插件
    • 字段用于对用户请求数据的验证
    • 插件用于自动生成HTML
  2. 常用字段
    • CharField
    • ChoiceField
    • MultipleChoiceField
  3. 常用插件
    • initial:初始值,input框里面的初始值
    class LoginForm(forms.Form):
       username = forms.CharField(
           min_length=8,
           label="用户名",
           initial="张三"  # 设置默认值
       )
       pwd = forms.CharField(min_length=6, label="密码")
    • error_messages:重写错误信息
    class LoginForm(forms.Form):
       username = forms.CharField(
           min_length=8,
           label="用户名",
           initial="张三",
           error_messages={
               "required": "不能为空",
               "invalid": "格式错误",
               "min_length": "用户名最短8位"
           }
       )
       pwd = forms.CharField(min_length=6, label="密码")
    • password:密码相关
    class LoginForm(forms.Form):
       pwd = forms.CharField(
           min_length=6,
           label="密码",
           widget=forms.PasswordInput
       )
    • radioSelect:单radio值为字符串
    class LoginForm(forms.Form):
       gender = forms.ChoiceField(
           choices=((1, "男"), (2, "女"), (3, "保密")),
           label="性别",
           initial=3,
           widget=forms.RadioSelect
       )
    • 单选Select:ChoiceField,Select
    class LoginForm(forms.Form):
       hobby = forms.ChoiceField(
           choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
           label="爱好",
           initial=3,
           widget=forms.Select
       )
    • 多选Select:MultipleChoiceField,SelectMultiple
    class LoginForm(forms.Form):
       hobby = forms.MultipleChoiceField(
           choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
           label="爱好",
           initial=[1, 3],
           widget=forms.SelectMultiple
       )
    • 单选checkbox:ChoiceField,CheckboxInput
    class LoginForm(forms.Form):
       keep = forms.ChoiceField(
           label="是否记住密码",
           initial="checked",
           widget=forms.CheckboxInput
       )
    • 多选checkbox:MultipleChoiceField,CheckboxSelectMultiple
    class LoginForm(forms.Form):
       hobby = forms.MultipleChoiceField(
           choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
           label="爱好",
           initial=[1, 3],
           widget=forms.CheckboxSelectMultiple
       )
  4. 关于choice的注意事项:
    • 在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段获取的值无法实时更新,那么需要自定义构造方法从而达到此目的
    • 方式一:
    from django.forms import Form
    from django.forms import widgets
    from django.forms import fields
    
    class MyForm(Form):
       user = fields.ChoiceField(
           # choices=((1, '上海'), (2, '北京'),),
           initial=2,
           widget=widgets.Select
       )
    
       def __init__(self, *args, **kwargs):
           super(MyForm,self).__init__(*args, **kwargs)
           # self.fields['user'].choices = ((1, '上海'), (2, '北京'),)或
           self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
    • 方式二:
    from django import forms
    from django.forms import fields
    from django.forms import models as form_model
    
    class FInfo(forms.Form):
       # 多选
       authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())
       # 单选
       authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())

12.10.3 Django Form常用内置字段参数

  • 常用字段参数:
    Field
      required=True,            # 是否允许为空
      widget=None,              # HTML插件
      label=None,               # 用于生成Label标签或显示内容
      initial=None,             # 初始值
      help_text='',             # 帮助信息(在标签旁边显示)
      error_messages=None,      # 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
      validators=[],            # 自定义验证规则
      localize=False,           # 是否支持本地化
      disabled=False,           # 是否可以编辑
      label_suffix=None         # Label内容后缀
    
    CharField(Field)
      max_length=None,          # 最大长度
      min_length=None,          # 最小长度
      strip=True                # 是否移除用户输入空白
    
    IntegerField(Field)
      max_value=None,           # 最大值
      min_value=None,           # 最小值
    
    ChoiceField(Field)
      choices=(),               # 选项,如:choices = ((0,'上海'),(1,'北京'),)
      required=True,            # 是否必填
      widget=None,              # 插件,默认select插件
      label=None,               # Label内容
      initial=None,             # 初始值
      help_text='',             # 帮助提示
    
    ModelChoiceField(ChoiceField)
      queryset,                  # 查询数据库中的数据
      empty_label="---------",   # 默认空显示内容
      to_field_name=None,        # HTML中value的值对应的字段
      limit_choices_to=None      # ModelForm中对queryset二次筛选
    
    ModelMultipleChoiceField(ModelChoiceField)

12.10.4 校验

  1. 内置校验
    • required=True
    • min_length
    • max_length等
  2. 自定义校验
    • 写函数
    from django.core.exceptions import ValidationError
    def checkname(value):
       # 通过校验规则 不做任何操作
       # 不通过校验规则   抛出异常
       if 'alex' in value:
           raise ValidationError('不符合社会主义核心价值观')
    • 使用内置的校验器
    from django import forms
    from django.core.validators import RegexValidator
    class RegForm(forms.Form):
       phone = forms.CharField(
               validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式不正确')]
           )
  3. 钩子函数
    • 局部钩子:只针对于当前字段的校验
       def clean_username(self):
           # 局部钩子
           # 通过校验规则  必须返回当前字段的值
           # 不通过校验规则   抛出异常
           v = self.cleaned_data.get('username')
           if 'alex' in v:
               raise ValidationError('不符合社会主义核心价值观')
           else:
               return v    # 必须返回当前字段的值
    • 全局钩子:针对于所有字段的校验
       def clean(self):
           # 全局钩子
           # 通过校验规则  必须返回所有所有字段的值
           # 不通过校验规则   抛出异常   '__all__'
           pwd = self.cleaned_data.get('pwd')
           re_pwd = self.cleaned_data.get('re_pwd')
           if pwd == re_pwd:
               return self.cleaned_data    # 必须返回所有所有字段的值
           else:
               self.add_error('re_pwd','两次密码不一致!!!!!')        # 在字段're_pwd'后显示错误信息
               raise ValidationError('两次密码不一致')    # 在'__all__'中显示错误信息

12.11 中间件

12.11.1 中间件介绍

  1. 定义:中间件是一个用来处理Django的请求和响应的框架级别的钩子
  2. 本质:就是一个类,帮助我们在视图函数执行之前和执行之后做一些额外的操作
  3. Django项目的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',
    ]

12.11.2 csrf:跨站请求伪造

  1. csrf 装饰器
    from django.views.decorators.csrf import csrf_exempt,csrf_protect,ensure_csrf_cookie
    csrf_exempt            # 某个视图不需要进行csrf校验,只能加在dispatch方法上
    csrf_protect           # 某个视图需要进行csrf校验
    ensure_csrf_cookie    # 确保生成csrf的cookie
  2. csrf 功能
    • csrf中间件中执行process_request
      • 从cookie中获取到csrftoken的值
      • csrftoken的值放入到 request.META
    • 执行process_view
      • 查询视图函数是否使用csrf_exempt装饰器,使用了就不进行csrf的校验
      • 判断请求方式:
      • 如果是GET', 'HEAD', 'OPTIONS', 'TRACE',不进行csrf校验
      • 其他的请求方式(post,put),进行csrf校验:
        • 获取cookie中csrftoken的值
        • 获取csrfmiddlewaretoken的值
        • 能获取到,赋值给request_csrf_token
        • 获取不到,就去获取请求头中X-csrftoken的值,再赋值request_csrf_token
        • 比较上述request_csrf_token和cookie中csrftoken的值
        • 比较成功接收请求
        • 比较不成功拒绝请求

12.11.3 自定义中间件

  1. 中间件可以定义五个方法,分别是:
    • process_request(self,request)
    • process_view(self, request, view_func, view_args, view_kwargs)
    • process_template_response(self,request,response)
    • process_exception(self, request, exception)
    • process_response(self, request, response)
  2. process_request(self,request)
    • 执行时间:视图函数之前
    • 参数:
      • request:和视图函数中是同一个request对象
    • 执行顺序:按照注册的顺序 ,顺序执行
    • 返回值:
      • None : 正常流程
      • HttpResponse: 后面的中间件的process_request、视图函数都不执行,直接执行当前中间件中的process_response方法,倒序执行之前的中间件中的process_response方法
  3. process_response(self, request, response)
    • 执行时间:视图函数之后
    • 参数:
      • request:和视图函数中是同一个request对象
      • response:返回给浏览器响应对象
    • 执行顺序:按照注册的顺序,倒序执行
    • 返回值:
      • HttpResponse:必须返回response对象

    1561793484854

  4. process_view(self, request, view_func, view_args, view_kwargs)
    • 执行时间:视图函数之前,process_request之后
    • 参数:
      • request:和视图函数中是同一个request对象
      • view_func:视图函数
      • view_args:视图函数的位置参数
      • view_kwargs:视图函数的关键字参数
    • 执行顺序:按照注册的顺序 顺序执行
    • 返回值:
      • None : 正常流程
      • HttpResponse: 后面的中间件的process_view、视图函数都不执行,直接执行最后一个中间件中的process_response方法,倒序执行之前的中间件中的process_response方法

    1561793529885

  5. process_exception(self, request, exception)
    • 执行时间(触发条件):视图层面有错时才执行
    • 参数:
      • request:和视图函数中是同一个request对象
      • exception:错误对象
    • 执行顺序:按照注册的顺序,倒序执行
    • 返回值:
      • None : 交给下一个中间件去处理异常,如果都没有处理,就交由django处理异常
      • HttpResponse: 后面的中间件的process_exception不执行,直接执行最后一个中间件中的process_response方法,倒序执行之前的中间件中的process_response方法
  6. process_template_response(self,request,response)
    • 执行时间(触发条件):视图返回的是一个templateResponse对象
    • 参数:
      • request:和视图函数中是同一个request对象
      • response:templateResponse对象
    • 执行顺序:按照注册的顺序,倒序执行
    • 返回值:
      • HttpResponse:必须返回response对象

    1561793578548

  7. django请求的生命周期

    1561793680658

12.12 图书管理系统示例

12.12.1 出版社管理

12.12.1.1 数据库准备

  1. 创建数据库:create database bookmanager;
  2. 配置setting.py:
    MIDDLEWARE的'django.middleware.csrf.CsrfViewMiddleware'注释掉
    
    DATABASES = {
       'default': {
           'ENGINE': 'django.db.backends.mysql',
           'NAME': 'bookmanager',
           'HOST': '127.0.0.1',
           'PORT': 3306,
           'USER': 'root',
           'PASSWORD': '123',
       }
    }
  3. 导入pymysql:与settings,py同级目录下的init文件
    import pymysql
    pymysql.install_as_MySQLdb()
  4. 创建表:
    from django.db import models
    class Publisher(models.Model):
       # 设置pid为主键
       pid = models.AutoField(primary_key=True)
       name = models.CharField(max_length=32,unique=True)
  5. 数据库迁移:
    # 检测每个注册app下的model.py,记录model的变更记录
    
    python manage.py makemigrations
    
    # 同步变更记录到数据库中
    
    python manage.py migrate
  6. 给表中添加数据

12.12.1.2 功能设计

  • 设计URL: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'^publisher_list/', views.publisher_list),
      # 新增功能
      url(r'^add_publisher/', views.add_publisher),
      # 删除功能
      url(r'^del_publisher/', views.del_publisher),
      # 编辑功能
      url(r'^edit_publisher/', views.edit_publisher),
    ]
  1. 展示功能:publisher_list
    • 写函数:views.py
    from django.shortcuts import render, redirect, HttpResponse
    from app01 import models
    
    # 展示出版社
    
    def publisher_list(request):
       # 从数据库中查询到出版社的信息
       all_publishers = models.Publisher.objects.all().order_by('pk')
       # 返回一个包含出版社信息的页面
       return render(request, 'publisher_list.html', {'all_publishers': all_publishers})
    • 写模板:publisher_list.html
    <h1>出版社列表</h1>
      <a href="/add_publisher/">新增</a>
      <table border="1">
          <thead>
          <tr>
              <th>序号</th>
              <th>ID</th>
              <th>名称</th>
              <th>操作</th>
          </tr>
          </thead>
          <tbody>
          {# 使用for循环获取数据,完善表格 #}
          {% for publisher in all_publishers %}       
              <tr>
                  <td>{{ forloop.counter }}</td>
                  <td>{{ publisher.pk }}</td>
                  <td>{{ publisher.name }}</td>
                  <td>
                      <a href="/del_publisher/?id={{ publisher.pk }}">删除</a>
                      <a href="/edit_publisher/?id={{ publisher.pk }}">编辑</a>
                  </td>
              </tr>
          {% endfor %}       <!--for循环必须要以endfor结束-->
          </tbody>
      </table>
    • 知识点:
      • 使用render的第三个参数(context):字典形式,方便之后用key取数据
      • {{ }}:表示变量,在模板渲染的时候替换成值,点(.)用来获取属性值
      • 如上述模板中使用的publisher.pk:获取主键
      • forloop.counter:当前循环的次数
      • {% %}:表示逻辑相关的操作
      • 如上述模板中使用的for循环,for循环必须要以endfor结束
  2. 新增功能:add_publisher
    • 写函数:views.py
    # 新增出版社
    
    def add_publisher(request):
       error = ''
       # 对请求方式进行判断
       if request.method == 'POST':
           # 处理POST请求
           # 获取到出版社的名称
           publisher_name = request.POST.get('publisher_name')
           # 判断出版社名称是否有重复的
           if models.Publisher.objects.filter(name=publisher_name):
               error = '出版社名称已存在'
           # 判断输入的值是否为空
           if not publisher_name:
               error = '不能输入为空'
           if not error:
               # 使用ORM将数据插入到数据库中
               obj = models.Publisher.objects.create(name=publisher_name)
               # 跳转到展示出版社的页面
               return redirect('/publisher_list/')
       # 返回一个包含form表单的页面
       return render(request, 'add_publisher.html', {'error': error})
    • 写模板:add_publisher.html
    <form action="" method="post">
       <p> 出版社名称:
           <input type="text" name="publisher_name">{{ error }}
       </p>
       <button>提交</button>
    </form>
    • 知识点:
      • 插入数据:使用ORM操作
      • models.Publisher.objects.create(属性=新增值)
      • 如models.Publisher.objects.create(name=publisher_name)
  3. 删除功能:del_publisher
    • 写函数:views.py
    # 删除出版社
    
    def del_publisher(request):
       # 获取要删除的数据
       pk = request.GET.get('id')
       obj_list = models.Publisher.objects.filter(pk=pk)
       if not obj_list:
           # 没有要删除的数据
           return HttpResponse('要删除的数据不存在')
       # 删除该数据
       # obj.delete()
       obj_list.delete()
       # 跳转到展示页面
       return redirect('/publisher_list/')
    • 知识点:
      • request.GET:url上携带的参数,不是GET请求提交参数
      • 所以post请求时,也可以使用该方法
      • 删除数据:对象.delete() 或 对象列表.delete()
      • 如obj.delete() 或 obj_list.delete()
  4. 编辑功能:edit_publisher
    • 写函数:views.py
    # 编辑出版社
    
    def edit_publisher(request):
       error = ''
       # 查找要编辑的数据
       pk = request.GET.get('id')  # url上携带的参数  不是GET请求提交参数
       obj_list = models.Publisher.objects.filter(pk=pk)
       if not obj_list:
           return HttpResponse('要编辑的数据不存在')
       obj = obj_list[0]
       if request.method == 'POST':
           # 处理POST请求
           # 获取新提交的出版的名称
           publisher_name = request.POST.get('publisher_name')
           if models.Publisher.objects.filter(name=publisher_name):
               # 新修改的名称已存在
               error = '新修改的名称已存在'
           if obj.name == publisher_name:
               error = '名称未修改'
           if not publisher_name:
               error = '名称不能为空'
           if not error:
               # 修改数据
               obj.name = publisher_name
               obj.save()  # 保存数据到数据库中
               # 跳转到出版社的展示页面
               return redirect('/publisher_list/')
       # 返回一个包含原始数据的页面
       return render(request, 'edit_publisher.html', {'obj': obj,'error':error})
    • 写模板:edit_publisher.html
    <form action="" method="post">
       <p> 出版社名称:
           <input type="text" name="publisher_name" value="{{ obj.name }}"> {{ error }}
       </p>
       <button>提交</button>
    </form>
    • 知识点:
      • 修改数据:对象.属性 = 新值,如obj.name = publisher_name
      • 保存数据到数据库中:对象.save(),如obj.save()

12.12.2 图书管理

12.12.2.1 数据库准备

  1. 在上述基础上
  2. 创建表:
    from django.db import models
    class Book(models.Model):
       title = models.CharField(max_length=32)
       pub = models.ForeignKey('Publisher', on_delete=models.CASCADE)
  3. 数据库迁移:
    # 检测每个注册app下的model.py,记录model的变更记录
    
    python manage.py makemigrations
    
    # 同步变更记录到数据库中
    
    python manage.py migrate
  4. 给表中添加数据
  5. 知识点:
    • on_delete 在django2.0 版本之后是必填的参数,1.11之前的可以不填
    • on_delete 的参数:
      • models.CASCADE
      • models.SET()
      • models.SET_DEFAULT
      • models.SET_NULL

12.12.2.2 功能设计

  • 设计URL: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'^publisher_list/', views.publisher_list),
      url(r'^add_publisher/', views.add_publisher),
      url(r'^del_publisher/', views.del_publisher),
      url(r'^edit_publisher/', views.edit_publisher),
    
      url(r'^book_list/', views.book_list),
      url(r'^add_book/', views.add_book),
      url(r'^del_book/', views.del_book),
      url(r'^edit_book/', views.edit_book),
    ]
  1. 展示功能:book_list
    • 写函数:views.py
    from django.shortcuts import render, redirect, HttpResponse
    from app01 import models
    
    # 展示书籍
    
    def book_list(request):
       # 查询所有的书籍
       all_books = models.Book.objects.all()
       return render(request, 'book_list.html', {'all_books': all_books})
    • 写模板:book_list.html
    <a href="/add_book/" target="_blank">添加</a>
      <table border="1">
          <thead>
          <tr>
              <th>序号</th>
              <th>ID</th>
              <th>书名</th>
              <th>出版社名称</th>
              <th>操作</th>
          </tr>
          </thead>
          <tbody>
          {% for book in all_books %}
              <tr>
                  <td>{{ forloop.counter }}</td>
                  <td>{{ book.pk }}</td>
                  <td>{{ book.title }}</td>
                  <td>{{ book.pub }}</td>
                  <td>
                      <a href="/del_book/?id={{ book.pk }}">删除</a>
                      <a href="/edit_book/?id={{ book.pk }}">编辑</a>
                  </td>
              </tr>
          {% endfor %}
          </tbody>
      </table>
    • 知识点:
      all_books = models.Book.objects.all()
      for book in all_books:
          print(book.title)
          print(book.pub,type(book.pub))   #  ——> 所关联的出版社对象
          print(book.pub.pk)  #  查id 多一次查询
          print(book.pub_id)  # 直接在book表中查出的ID
          print(book.pub.name)
  2. 新增功能:add_book
    • 写函数:views.py
    # 添加书籍
    
    def add_book(request):
       if request.method == 'POST':
           # 获取数据
           book_name = request.POST.get('book_name')
           pub_id = request.POST.get('pub_id')
           # 将数据插入到数据库
    
    # models.Book.objects.create(title=book_name,pub=models.Publisher.objects.get(pk=pub_id))
    
           models.Book.objects.create(title=book_name, pub_id=pub_id)
           # 跳转到书籍的展示页面
           return redirect('/book_list/')
           # all_books = models.Book.objects.all()
           # return render(request, 'book_list.html', {'all_books': all_books})
       # 查询所有的出版社
       all_publishers = models.Publisher.objects.all()
       return render(request, 'add_book.html', {'all_publishers': all_publishers})
    • 写模板:add_book.html
    <form action="" method="post">
       <p>书名: <input type="text" name="book_name"></p>
       <p>出版社:
           <select name="pub_id" id="">
               {% for publisher in all_publishers %}
                   <option value="{{ publisher.pk }}"> {{ publisher.name }} </option>
               {% endfor %}
           </select>
       </p>
       <button>新增</button>
    </form>
    • 知识点:
      • 插入数据:使用ORM操作
      • models.Book.objects.create(title=book_name,pub=出版社的对象)
      • models.Book.objects.create(title=book_name,pub_id=pub_id)
  3. 删除功能:del_book
    • 写函数:views.py
    # 删除书籍
    
    def del_book(request):
       # 获取要删除的对象删除
       pk = request.GET.get('id')
       models.Book.objects.filter(pk=pk).delete()
       # 跳转到展示页面
       return redirect('/book_list/')
    • 知识点:
      • pk = request.GET.get('id')
      • models.Book.objects.filter(pk=pk).delete()
  4. 编辑功能:edit_book
    • 写函数:views.py
    # 编辑书籍
    
    def edit_book(request):
       # 获取要编辑的书籍对象
       pk = request.GET.get('id')
       book_obj = models.Book.objects.get(pk=pk)
       if request.method == 'POST':
           # 获取提交的数据
           book_name = request.POST.get('book_name')
           pub_id = request.POST.get('pub_id')
           # 修改数据
           book_obj.title = book_name
           # book_obj.pub_id = pub_id
           book_obj.pub = models.Publisher.objects.get(pk=pub_id)
           book_obj.save()
           # 重定向到展示页面
           return redirect('/book_list/')
       # 查询所有的出版社
       all_publishers = models.Publisher.objects.all()
       return render(request, 'edit_book.html', {'book_obj': book_obj, 'all_publishers': all_publishers})
    • 写模板:edit_publisher.html
    <form action="" method="post">
       <p>书名: <input type="text" name="book_name" value="{{ book_obj.title }}"></p>
       <p>出版社:
           <select name="pub_id" id="">
               {% for publisher in all_publishers %}
                   {% if book_obj.pub == publisher %}
                       <option selected value="{{ publisher.pk }}"> {{ publisher.name }} </option>
                   {% else %}
                       <option  value="{{ publisher.pk }}"> {{ publisher.name }} </option>
                   {% endif %}
               {% endfor %}
           </select>
       </p>
       <button>保存</button>
    </form>
    • 知识点:
    # 修改数据
    
    book_obj.title = book_name
    
    # book_obj.pub_id = pub_id
    
    book_obj.pub = models.Publisher.objects.get(pk=pub_id)
    book_obj.save()
    • if语句
    {% if book_obj.pub == publisher %}
       <option selected value="{{ publisher.pk }}"> {{ publisher.name }} </option>
    {% else %}
       <option  value="{{ publisher.pk }}"> {{ publisher.name }} </option>
    {% endif %}

12.12.3 作者管理

12.12.3.1 数据库准备

  1. 在上述基础上
  2. 创建表:
    from django.db import models
    
    # 会自动生成第三张表
    
    class Author(models.Model):
       name = models.CharField(max_length=32)
       books = models.ManyToManyField('Book')     # 不在Author表中生产字段,生产第三张表
  3. 数据库迁移:
    # 检测每个注册app下的model.py,记录model的变更记录
    
    python manage.py makemigrations
    
    # 同步变更记录到数据库中
    
    python manage.py migrate
  4. 给表中添加数据
    • 注意不仅要给作者表添加数据,还有给第三张表添加数据

12.12.3.2 功能设计

  • 设计URL: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'^publisher_list/', views.publisher_list),
      url(r'^add_publisher/', views.add_publisher),
      url(r'^del_publisher/', views.del_publisher),
      url(r'^edit_publisher/', views.edit_publisher),
    
      url(r'^book_list/', views.book_list),
      url(r'^add_book/', views.add_book),
      url(r'^del_book/', views.del_book),
      url(r'^edit_book/', views.edit_book),
    
      url(r'^author_list/', views.author_list),
      url(r'^add_author/', views.add_author),
      url(r'^del_author/', views.del_author),
      url(r'^edit_author/', views.edit_author),
    ]
  1. 展示功能:author_list
    • 写函数:views.py
    from django.shortcuts import render, redirect, HttpResponse
    from app01 import models
    
    # 展示作者
    
    def author_list(request):
       # 查询所有的作者
       all_authors = models.Author.objects.all()
       return render(request, 'author_list.html', {'all_authors': all_authors})
    • 写模板:author_list.html
    <a href="/add_author/" target="_blank">添加</a>
      <table border="1">
          <thead>
          <tr>
              <th>序号</th>
              <th>id</th>
              <th>姓名</th>
              <th>代表作</th>
              <th>操作</th>
          </tr>
          </thead>
          <tbody>
          {% for author in all_authors %}
              <tr>
                  <td>{{ forloop.counter }}</td>
                  <td>{{ author.pk }}</td>
                  <td>{{ author.name }}</td>
                  <td>
                      {% for book in author.books.all %}
                          {% if forloop.last %}
                              《{{ book.title }}》
                          {% else %}
                              《{{ book.title }}》、
                          {% endif %}
                      {% endfor %}
                  </td>
                  <td>
                      <a href="/del_author/?pk={{ author.pk }}">删除</a>
                      <a href="/edit_author/?pk={{ author.pk }}">编辑</a>
                  </td>
              </tr>
          {% endfor %}
          </tbody>
      </table>
    • 知识点:
      all_authors = models.Author.objects.all()
      for author in all_authors:
          print(author.books,type(author.books))   # 关系管理对象
          print(author.books.all())            # 使用all,获取对应列表
  2. 新增功能:add_author
    • 写函数:views.py
    # 增加作者
    
    def add_author(request):
       if request.method == 'POST':
           # 获取post请求提交数据
           author_name = request.POST.get('author_name')
           books = request.POST.getlist('books')
           # 存入数据库
           author_obj = models.Author.objects.create(name=author_name, )
           author_obj.books.set(books)
           # 跳转到展示页面
           return redirect('/author_list/')
       # 查询所有的书籍
       all_books = models.Book.objects.all()
       return render(request, 'add_author.html', {'all_books': all_books})
    • 写模板:add_author.html
    <form action="" method="post">
       <p> 作者姓名: <input type="text" name="author_name"></p>
       <p> 作品:
           <select name="books" id="" multiple>
               {% for book in all_books %}
                   <option value="{{ book.pk }}">{{ book.title }}</option>
               {% endfor %}
           </select>
       </p>
       <button>提交</button>
    </form>
    • 知识点:

      知识点:

      • 获取post请求提交数据:结果是列表,就使用getlist
      • author_name = request.POST.get('author_name')
      • books = request.POST.getlist('books')
      • 存入数据库:普通的使用create,得到一个对象,多对多再使用对象进行set把列表数据加入进去
      • 新增普通的数据:author_obj = models.Author.objects.create(name=author_name, )
      • 新增多对多的数据:author_obj.books.set(books)
  3. 删除功能:del_author
    • 写函数:views.py
    # 删除作者
    
    def del_author(request):
       # 获取要删除对象的id
       pk = request.GET.get('pk')
       # 获取要删除的对象 删除
       models.Author.objects.filter(pk=pk).delete()
       # 跳转到展示页面
       return redirect('/author_list/')
  4. 编辑功能:edit_author
    • 写函数:views.py
    # 编辑作者
    
    def edit_author(request):
       # 查询编辑的作者对象
       pk = request.GET.get('pk')
       author_obj = models.Author.objects.get(pk=pk)
       if request.method == 'POST':
           # 获取提交的数据
           name = request.POST.get('author_name')
           books = request.POST.getlist('books')
           # 修改对象的数据
           author_obj.name = name
           author_obj.save()
           # 多对多的关系
           author_obj.books.set(books)  #  每次重新设置
           # 重定向
           return  redirect('/author_list/')
       # 查询所有的书籍
       all_books = models.Book.objects.all()
       return render(request, 'edit_author.html', {'author_obj': author_obj, 'all_books': all_books})
    • 写模板:edit_author.html
    <form action="" method="post">
       <p> 作者姓名: <input type="text" name="author_name" value="{{ author_obj.name }}"></p>
       <p> 作品:
           <select name="books" id="" multiple>
               {% for book in all_books %}
                   {% if book in author_obj.books.all %}
                       <option value="{{ book.pk }}" selected>{{ book.title }}</option>
                   {% else %}
                       <option value="{{ book.pk }}">{{ book.title }}</option>
                   {% endif %}
               {% endfor %}
           </select>
       </p>
       <button>提交</button>
    </form>
    • 知识点:
      • 修改对象的数据
      • author_obj.name = name
      • author_obj.save()
      • 修改多对多的关系的数据:使用set,并且,不需要save
      • author_obj.books.set(books)

        注意:每次默认都是重新设置的

12.12.4 django设置多对多关系的三种方法

  1. django帮我们生成第三张表:
    class Author(models.Model):
       name = models.CharField(max_length=32)
       books = models.ManyToManyField('Book')  # 不在Author表中生产字段,生产第三张表
  2. 自己创建第三张表:
    class AuthorBook(models.Model):
       author = models.ForeignKey(Author, on_delete=models.CASCADE)
       book = models.ForeignKey(Book, on_delete=models.CASCADE)
       date = models.DateField()
  3. 自建的表和 ManyToManyField 联合使用
    • 使用参数through:将自建的表设置成ManyToManyField的第三张表
    class Author(models.Model):
       name = models.CharField(max_length=32)
       books = models.ManyToManyField('Book',through='AuthorBook')
    
    class AuthorBook(models.Model):
       author = models.ForeignKey(Author, on_delete=models.CASCADE)
       book = models.ForeignKey(Book, on_delete=models.CASCADE)
       date = models.DateField()
  4. 特殊:自建表中有对同一个表多个外键的情况
    • 使用参数through_fields:指定外键名,说明第三张表中只有这些外键
    class Author(models.Model):
       name = models.CharField(max_length=32)
       books = models.ManyToManyField('Book',through='AuthorBook',through_fields=['author','book']
    
    class AuthorBook(models.Model):
       author = models.ForeignKey(Author,related_name='a' ,on_delete=models.CASCADE)
       book = models.ForeignKey(Book, on_delete=models.CASCADE)
       tuijian = models.ForeignKey(Author,related_name='b',on_delete=models.CASCADE)
    • 参数related_name:用来区分同一个表的外键,方便之后用来反向查询

12.13 总结

  1. django中所有的命令
    • 下载安装
      • pip install django==1.11.21 -i 源
    • 创建项目
      • django-admin startproject 项目名称
    • 启动项目
      • cd 项目根目录下
      • python manage.py runserver # 127.0.0.1:8000
      • python manage.py runserver 80 # 127.0.0.1:80
      • python manage.py runserver 0.0.0.0:80 # 0.0.0.0:80
    • 创建app
      • python manage.py startapp app名称
    • 数据库迁移
      • pyhton manage.py makemigrations
      • pyhton manage.py migrate
  2. settings配置
    • 静态文件夹:static
      STATIC_URL = '/static/'
      STATICFILES_DIRS = [
       os.path.join(BASE_DIR,‘static’),
      ]
    • 模板文件夹:templates
      • TEMPLATES下的DIRS:[os.path.join(BASE_DIR,‘templates’),]
    • 连接数据库:DATABASES中,
      • ENGINE:引擎
      • NAME:数据库的名称
      • HOST:ip
      • PORT:端口,3306是mysql的端口号
      • USER:用户民
      • PASSWORD:密码
    • app配置:
      • INSTALLED_APPS =[ 'app01' 'app01.apps.App01Config' ]
    • 提交POST请求
      • 中间件MIDDLEWARE中,注释csrf相关的内容
      • 在form表单中写上{% csrf_token %},就不用在settings中注释含csrf的中间件了
  3. django处理请求的流程
    • 浏览器的地址栏上输入地址,发送get请求
    • wsgi接受到请求
    • 根据url的路径找到对应的函数
    • 执行函数,返回响应。响应返回给wsgi,wsgi按照HTTP响应格式返回给浏览器
  4. get和post的区别
    • get:获取到一个资源
      • 发get的途径:
      • 在浏览器的地址栏种输入URL,回车
      • a标签
      • form表单:不指定 method或method=‘get’
      • 传递参数:url路径?id=1&name=alex
      • django种获取url上的参数:
      • request.GET --> {'id':1,'name':'alex'}
      • request.GET.get(key)或request.GET[key]
    • post:提交数据
      • 发post的途径:form表单,method=‘post’
      • 传递参数:参数不暴露在URL,在请求体中
      • django中获取post的数据:request.POST
  5. 发请求的途径
    • 地址栏中输入地址:发送get请求
    • a标签:发送get请求
    • form表单:发送get/post请求
  6. request对象的属性及方法
    • 属性:
      • request.method:当前请求的方式,GET/POST,字符串类型
      • request.GET:url上携带的参数,不是GET请求提交参数,所以post请求时,也可以使用该方法
      • request.POST:获取form表单提交post请求的数据,类似字典类型,可以仿照字典取值——[ ]或.get( )
      • request.path_info:url的路径,不包含ip和端口,不包含参数
      • request.body:请求体,byte类型,request.POST的数据是从boby里提取的
      • request.FILES:上传的文件,一个类似于字典的对象,包含所有上传文件的信息
      • request.META:请求头,标准的Python字典,包含左右HTTP首部
    • 方法
      • request.get_host():获取主机的ip和端口
      • request.get_full_path():url的路径,不包含ip和端口,包含参数
      • request.is_ajax():判断是否是ajax请求,是返回Ture,否返回False
  7. 响应方式
    • Response对象
      • HttpResponse('字符串'):返回字符串
      • render(request,'模板文件名',{k1:v1}):返回一个完整的页面
      • redirect('index'):重定向,Location:/index/
    • JsonResponse对象




易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!