码迷,mamicode.com
首页 > 其他好文 > 详细

Django

时间:2019-07-07 09:55:45      阅读:120      评论:0      收藏:0      [点我收藏+]

标签:django框架   complete   counter   属性   项目文件   对应关系   day   校验器   price   

第十二章 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
    数据

技术图片

12.1.7 HTTP响应格式

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

技术图片

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 项目名称
      • 项目目录

      技术图片

    • 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流程解析

    技术图片

  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对象

    技术图片

  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方法

    技术图片

  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对象

    技术图片

  7. django请求的生命周期

    技术图片

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对象




Django

标签:django框架   complete   counter   属性   项目文件   对应关系   day   校验器   price   

原文地址:https://www.cnblogs.com/yinhaiping/p/a572cc37eea509eab4a3986b8be7f20e.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!