Django 框架

参考:
https://www.cnblogs.com/maple-shaw/p/9029086.html

Web框架原理

我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。

web框架 就是一个socket服务端

功能: 
    a. socket收发消息
    b. 根据不同的路径返回不同的内容
    c. 可以返回动态页面(字符串的替换  - 模板的渲染)
    
分类:
    Django     b c 
    flask      b
    tornado    a b c 
    
另类分类:
    Django  
    其他
			

socket服务端

import socket

server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen(5)

while True:
    conn, addr = server.accept()
    data = conn.recv(8096)
    conn.send(b'OK')
    conn.close()

可以说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码就是它们的祖宗。
用户在浏览器中输入网址,浏览器会向服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,那互联网还能玩么?
所以,必须有一个统一的规则,让大家发送消息、接收消息的时候都有个格式依据,不能随便写。
这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。
HTTP协议主要规定了客户端和服务器之间的通信格式,那HTTP协议是怎么规定消息格式的呢?
让我们首先打印下我们在服务端接收到的消息是什么。

import socket

server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen(5)

while True:
    conn, addr = server.accept()
    data = conn.recv(8096)
    print(data)
    conn.send(b'OK')
    conn.close()

b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8000\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n\r\n'

把\r\n换做换行后

GET / HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

我们发现收发的消息需要按照一定的格式来,这里就需要了解一下HTTP协议了。
HTTP协议对收发消息的格式要求
每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。

HTTP GET请求的格式:

HTTP响应的格式:

自定义web框架

经过上面的学习,那我们基于socket服务端的十几行代码写一个我们自己的web框架。我们先不处理浏览器发送的请求,先让浏览器能显示我们web框架返回的信息,那我们就要按照HTTP协议的格式来发送响应。

import socket

server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen(5)

while True:
    conn, addr = server.accept()
    data = conn.recv(8096)
    print(data)
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n<h1>OK</h1>')
    conn.close()

根据不同的路径返回不同的内容

这样就结束了吗? 如何让我们的Web服务根据用户请求的URL不同而返回不同的内容呢?
小事一桩,我们可以从请求相关数据里面拿到请求URL的路径,然后拿路径做一个判断...

import socket

server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen(5)

while True:
    conn, addr = server.accept()
    data = conn.recv(8096).decode('utf-8')
    url = data.split('\r\n')[0].split()[1].strip('/')  # 提取出路径
    # print(url)
    print(data)
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    if url == 'index':
        response = b'index'
    elif url == 'home':
        response = b'home'
    else:
        response = b'404 not found!'
    conn.send(response)
    conn.close()

根据不同的路径返回不同的内容--函数版

上面的代码解决了不同URL路径返回不同内容的需求。
我们返回的内容是简单的几个字符,那如果我可以将返回的结果封装成一个函数呢?

import socket

server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen(5)


def func(url):
    return 'This is {} page.'.format(url).encode('utf-8')


while True:
    conn, addr = server.accept()
    data = conn.recv(8096).decode('utf-8')
    url = data.split('\r\n')[0].split()[1].strip('/')  # 提取出路径
    # print(url)
    print(data)
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    if url == 'index':
        response = func(url)
    elif url == 'home':
        response = func(url)
    else:
        response = b'404 not found!'
    conn.send(response)
    conn.close()

根据不同的路径返回不同的内容--函数进阶版

看起来上面的代码写了一个函数,那肯定可以写多个函数,不同的路径对应执行不同的函数拿到结果,但是我们要一个个判断路径,是不是很麻烦?我们有简单的办法来解决。

import socket

server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen(5)


# 将返回不同的内容部分封装成不同的函数
def index(url):
    return 'This is {} page.\nhahahahahahaha'.format(url).encode('utf-8')


def home(url):
    return 'This is {} page.'.format(url).encode('utf-8')


# 定义一个url和实际要执行的函数的对应关系

list_ = [
    ('index', index),
    ('home', home)
]

while True:
    conn, addr = server.accept()
    data = conn.recv(8096).decode('utf-8')
    url = data.split('\r\n')[0].split()[1].strip('/')  # 提取出路径
    # print(url)
    print(data)
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    func = None  # 定义一个保存将要执行的函数名的变量
    for i in list_:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b'404 not found!'

    conn.send(response)
    conn.close()

返回具体的HTML文件

完美解决了不同URL返回不同内容的问题。 但是我不想仅仅返回几个字符串,我想给浏览器返回完整的HTML内容,这又该怎么办呢?
没问题,不管是什么内容,最后都是转换成字节数据发送出去的。 我们可以打开HTML文件,读取出它内部的二进制数据,然后再发送给浏览器。

import socket

server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen(5)


# 将返回不同的内容部分封装成不同的函数
def index(url):
    with open('index.html', 'rb') as f:
        text = f.read()
    return text


def home(url):
    with open('home.html', 'rb') as f:
        text = f.read()
    return text


# 定义一个url和实际要执行的函数的对应关系

list_ = [
    ('index', index),
    ('home', home)
]

while True:
    conn, addr = server.accept()
    data = conn.recv(8096).decode('utf-8')
    url = data.split('\r\n')[0].split()[1].strip('/')  # 提取出路径
    # print(url)
    print(data)
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    func = None  # 定义一个保存将要执行的函数名的变量
    for i in list_:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b'404 not found!'

    conn.send(response)
    conn.close()

返回动态的HTML文件

这网页能够显示出来了,但是都是静态的啊。页面的内容都不会变化的,我想要的是动态网站。
没问题,我也有办法解决。我选择使用字符串替换来实现这个需求。(这里使用时间戳来模拟动态的数据)

import socket
import time

server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen(5)


# 将返回不同的内容部分封装成不同的函数
def index(url):
    with open('index.html', 'rb') as f:
        text = f.read()
    return text


def home(url):
    with open('home.html', 'rb') as f:
        text = f.read()
    return text


def timer(url):
    with open('time.html', 'r', encoding='utf-8') as f:
        text = f.read()
        text = text.replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S"))
    return text.encode('utf-8')


# 定义一个url和实际要执行的函数的对应关系

list_ = [
    ('index', index),
    ('home', home),
    ('time', timer)
]

while True:
    conn, addr = server.accept()
    data = conn.recv(8096).decode('utf-8')
    url = data.split('\r\n')[0].split()[1].strip('/')  # 提取出路径
    # print(url)
    print(data)
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    func = None  # 定义一个保存将要执行的函数名的变量
    for i in list_:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b'404 not found!'

    conn.send(response)
    conn.close()

服务器程序和应用程序

对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。

服务器程序负责对socket服务端进行封装,并在请求到来时,对请求的各种数据进行整理。

应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。

这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。

这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。

常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器

wsgiref

我们利用wsgiref模块来替换我们自己写的web框架的socket server部分:

from wsgiref.simple_server import make_server
import time


# 将返回不同的内容部分封装成不同的函数
def index(url):
    with open('index.html', 'rb') as f:
        text = f.read()
    return text


def home(url):
    with open('home.html', 'rb') as f:
        text = f.read()
    return text


def timer(url):
    with open('time.html', 'r', encoding='utf-8') as f:
        text = f.read()
        text = text.replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S"))
    return text.encode('utf-8')


# 定义一个url和实际要执行的函数的对应关系

list_ = [
    ('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 list_:
        if i[0] == url.strip('/'):
            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', 8000, run_server)
    httpd.serve_forever()

jinja2

上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2
下载jinja2:pip install jinja2

index2.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>姓名:{{name}}</h1>
<h1>爱好:</h1>
<ul>
    {% for hobby in hobby_list %}
    <li>{{hobby}}</li>
    {% endfor %}
</ul>
</body>
</html>
from wsgiref.simple_server import make_server
from jinja2 import Template
import time


# 将返回不同的内容部分封装成不同的函数
def index(url):
    with open('index.html', 'rb') as f:
        text = f.read()
    return text


def home(url):
    with open('home.html', 'rb') as f:
        text = f.read()
    return text


def timer(url):
    with open('time.html', 'r', encoding='utf-8') as f:
        text = f.read()
        text = text.replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S"))
    return text.encode('utf-8')


def index2(url):
    with open('index2.html', 'r', encoding='utf-8') as f:
        data = f.read()
        template = Template(data)  # 生成模板文件
        ret = template.render({
            'name': 'pig',
            'hobby_list': ['吃', '喝', '睡']
        })
        # 把数据填充到模板中,在这一步中,可以调用数据库,选出对应信息返回。
    return ret.encode('utf-8')


# 定义一个url和实际要执行的函数的对应关系

list_ = [
    ('index', index),
    ('home', home),
    ('time', timer),
    ('index2', index2),

]


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 list_:
        if i[0] == url.strip('/'):
            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', 8000, run_server)
    httpd.serve_forever()

模板的原理就是字符串替换,我们只要在HTML页面中遵循jinja2的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。

Django

  • 下载
    安装最新LTS版,长期支持。

    1. 命令行
      pip install django1.11.18
      pip install django
      1.11.18 -i https://pypi.doubanio.com/simple/
    2. pycharm
  • 创建项目

    1. 命令行
      django-admin startproject mysite
    2. pycharm
  • 启动项目

    1. 命令行
      cd 项目目录下 manage.py
      python36 manage.py runserver # 127.0.0.1:8000
      python36 manage.py runserver 80 # 127.0.0.1:80
      python36 manage.py runserver 0.0.0.0:80 # 0.0.0.0:80

    2. pycharm

  • 目录介绍

mysite/
├── manage.py  # 管理文件
└── mysite  # 项目目录
    ├── __init__.py
    ├── settings.py  # 配置
    ├── urls.py  # 路由 --> URL和函数的对应关系
    └── wsgi.py  # runserver命令就使用wsgiref模块做简单的web server
  • 配置
    TEMPLATES
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        
    DATABASES 数据库
    
    静态文件的配置
    STATIC_URL = '/static/'  # 别名
    STATICFILES_DIRS = [
        os.path.join(BASE_DIR, 'static'),
    ]
  • Django基础必备三件套:
    from django.shortcuts import HttpResponse, render, redirect
    HttpResponse
    内部传入一个字符串参数,返回给浏览器。
def index(request):
    # 业务逻辑代码
    return HttpResponse("OK")

render
除request参数外还接受一个待渲染的模板文件和一个保存具体数据的字典参数。
将数据填充进模板文件,最后把结果返回给浏览器。(类似于我们上面用到的jinja2)

redirect
接受一个URL参数,表示跳转到指定的URL。

def index(request):
    # 业务逻辑代码
    return redirect("/home/")

Django

完整的登录示例

完整的登录示例

    form表单使用的注意事项:
        1. action="" method="post"     action 提交的地址  method 请求的方式
        2. input标签要有name属性
        3. 有一个input的类型是sumbit  或者 button按钮
    
    注释掉settings.py中的MIDDLEWARE中的'django.middleware.csrf.CsrfViewMiddleware'
    就可以提交post请求
    
    GET和POST的区别:
        1. GET  获取一个页面
            login/?user=alex&pwd=alexdsb
            在Django中获取数据 
                request.GET  {}   
                request.GET['user']
                request.GET.get('user')
                
        2. POST   提交数据
            数据不可见 请求体中
            在Django中获取数据 
                request.POST  {} 
                request.POST['user']
                request.POST.get('user')	

APP


创建APP
    1. 命令行:
        python manage.py startapp app名称
        
    2. pycharm
        tools  run manage.py tsak  
        startapp app名称
        
注册app
    INSTALLED_APPS 列表中添加 
        'app01',
        'app01.apps.App01Config'  # 推荐写法

ORM

ORM介绍和使用
    1. 使用mysql数据的步骤:
        1. 创建mysql数据库
        2. 在settings.py 中配置
            DATABASES = {
                'default': {
                    'ENGINE': 'django.db.backends.mysql',
                    'NAME': 'mysite',
                    'HOST': '127.0.0.1',
                    'PORT': 3306,
                    'USER': 'root',
                    'PASSWORD': '',

                }
            }
        3. 告诉Django使用pymysql模块连接mysql数据库
            在与settings.py同级目录下的__init__.py中写代码:
                import pymysql
                pymysql.install_as_MySQLdb()
                
        4. 在models.py中写类(models.Model):
            class User(models.Model):
                name = models.CharField(max_length=32)
                pwd = models.CharField(max_length=32)
            
        5. 执行数据量迁移的命令:
            python manage.py  makemigrations  # 把models.py的变更记录记录下来 
            python manage.py  migrate     # 把变更记录的操作同步到数据库中
            
            
    2. ORM的操作:
        1. all  获取所有数据
            models.User.objects.all()   ——》 对象列表
            
        2. get  获取某一条数据(没有或者是多个的时候报错)
            models.User.objects.get(name='alex')   ——》 对象 
            
        3. filter  获取满足条件的所有的对象
            models.User.objects.filter(name='alex',pwd='1') ——》 对象列表
            
        4.  obj.name   name字段的值
            obj.pwd   pwd字段的值
            obj.id  obj.pk

图书管理系统

3.新增ORM操作
# 新增数据
ret = models.Publisher.objects.create(name=new_name)   # ret 是对象
# 删除数据
models.Publisher.objects.get(pk=pk).delete()
# 修改数据
obj_list = models.Publisher.objects.filter(pk=pk)
obj = obj_list[0]
obj.name = new_name  # 内存中修改数据
obj.save()  # 向数据库提交,保存到数据库中
创建外键:
class Book(models.Model):
    title = models.CharField(max_length=32, unique=True)  # 书籍的名称
    publisher = models.ForeignKey('Publisher', on_delete=models.CASCADE)  # 关联了出版社

基础总结

1.Django的命令:

  1. 下载
    1. pip install django==1.11.16
    2. pip install django==1.11.16 -i 源
  2. 创建项目
    1. django-admin startproject 项目名
  3. 启动项目
    1. cd 项目目录下 找到manage.py
    2. python manage.py runserver # 127.0.0.1:8000
    3. python manage.py runserver 80 # 127.0.0.1:80
    4. python manage.py runserver 0.0.0.0:80 # 0.0.0.0:80
  4. 创建一个APP
    1. python manage.py startapp app名称
    2. 注册
  5. 数据库迁移
    1. python manage.py makemigrations # 在app下的migrations文件夹下记录 models的变更记录
    2. python manage.py migrate # 将models的变更记录同步到数据库中

2.Django的配置

  1. 注释一个CSRF的中间件 可以提交POST

  2. databases 数据库

    1. ENGINE : mysql
    2. NAME : 数据库的名称
    3. HOST:主机的IP
    4. PORT:3306
    5. USER: 用户名
    6. PASSWORD: 密码
  3. 静态文件的配置

    1. STATIC_URL = '/static/' # 别名

    2. STATICFILES_DIRS = [

      ​ os.path.join(BASE_DIR,'static')

      ]

  4. app

    1. INSTALLED_APPS = [

      ​ 'app01' ,

      ​ 'app01.apps.App01Config'

      ]

  5. TEMPLATES 模板相关的配置

    1. DIRS [ os.path.join(BASE_DIR,'templates')]

3.Django使用mysql数据库的流程:

  1. 创建一个mysql数据库

  2. 在settings中进行配置

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'bookmanager',
            'HOST': '127.0.0.1',
            'PORT': 3306,
            'USER': 'root',
            'PASSWORD': '',
    
        }
    }
    
  3. 在与settings同级目录下的init的文件中写:
    告诉Django使用pymysql模块连接mysql数据库

    import pymysql
    pymysql.install_as_MySQLdb()
    
  4. 在app下的models中写类(models.Model)

    class Publisher(models.Model):  # app01_publisher
        pid = models.AutoField(primary_key=True)  # pid 主键
        name = models.CharField(max_length=32, unique=True)  # 出版社名称
    
        def __str__(self):
            return self.name
    
    
    class Book(models.Model):
        title = models.CharField(max_length=32, unique=True)  # 书籍的名称
        publisher = models.ForeignKey('Publisher', on_delete=models.CASCADE)  # 关联了出版社
    
        def __str__(self):
            return self.title
    
    
    class Author(models.Model):
        name = models.CharField(max_length=32, unique=True)  # 作者的名字
        books = models.ManyToManyField('Book')  # 表示作者和书籍 多对多的关系
        # books = models.ManyToManyField('Book', through='Author_book',)  # 表示作者和书籍 多对多的关系
    
        def __str__(self):
            return self.name
    

    5.数据库迁移的命令

    1. python manage.py makemigrations # 在app下的migrations文件夹下记录 models的变更记录
    2. python manage.py migrate # 将models的变更记录同步到数据库中

4.ORM的对应关系

​ 类 —— 》 表

​ 对象 ——》 记录(数据行)

​ 属性 ——》 字段

5.ORM操作

  1. 查询
    1. models.Publisher.objects.all() # 查询所有的数据 ——》 QuerySet 对象列表
    2. models.Publisher.objects.get() # 获取满足条件的一个对象 ——》 单独的对象
    3. models.Publisher.objects.filter() # 获取满足条件的所有对象 ——》对象列表
    4. models.Publisher.objects.all().order_by('id') # 排序 —— 》 对象列表
    5. pub_obj.pk ——》 主键
    6. pub_obj.name
    7. 外键
      1. book_obj.publisher ——》 关联的对象
      2. book_obj.publisher_id ——》从book表中获取关联对象的ID
      3. book_obj.publisher.name ——》 关联的对象的属性
    8. 多对多
      1. author_obj.books ——》 管理对象
      2. author_obj.books.all() ——》 关联的所有的对象 对象列表
      3. book_obj.author_set ——》管理对象、
  2. 增加
    1. models.Publisher.objects.create(name='xxx') ——》 对象
    2. 外键
      1. models.Book.objects.create(title='xxx',publisher=pub_obj)
      2. models.Book.objects.create(title='xxx',publisher_id=pub_obj.id)
    3. 多对多
      1. models.Author.objects.create(name='xxxx') ——》 author_obj
      2. author_obj.books.set([多个要关联对象的id,])
      3. author_obj.books.set([多个要关联对象,])
  3. 删除
    1. models.Author.objects.get(id=1).delete()
    2. models.Author.objects.filter(id=1).delete()
  4. 修改
    1. pub_obj.name = 'new_xxxx'
    2. pub_obj.save()
    3. 外键
      1. book_obj.title = 'xxxx'
      2. book_obj.publisher = pub_obj 或者 book_obj.publisher_id = pub_obj .id
      3. book_obj.save()
    4. 多对多
      1. author_obj.name = 'new_name'
      2. author_obj.save()
      3. author_obj.books.set([多个要关联对象,]) author_obj.books.set([多个要关联对象的id,])

6.request

  1. request.method ——》 请求方式 8 种 GET/POST
  2. request.GET ——》 url上的参数 xxxx/?id=1&name=aelex {} [] request.GET.get('id')
  3. request.POST ——》 form表单提交的POST的数据 {} [] request.POST.get()

7.函数的返回值

  1. HttpResponse('字符串') ——》 返回字符串
  2. render(request,'模板的文件名',{k1:v1}) ——》返回一个完整的页面
  3. redirect(''跳转的地址'') ——》 重定向 响应头 Location : '跳转的地址'

3.get和post

  1. get ——获取一个页面

    浏览器地址栏中输入地址,回车

    a标签

    form表单默认发送get请求

    携带参数:127.0.0.1:8000/edit_publisher/?pk=1

    request.GET {}

    1. post ——提交数据

    form表单指定method=‘post’

    request.POST

MVC与MTV

MVC

  • Model(模型)
  • View(视图)
  • Controller(控制)

django的MTV框架

  • Model(模型):负责业务对象与数据库的对象(ORM)
  • Template(模版):负责如何把页面展示给用户
  • View(视图):负责业务逻辑,并在适当的时候调用Model和Template

此外,Django还有一个urls分发器,它的作用是将一个个URL的页面请求分发给不同的view处理,view再调用相应的Model和Template

Django模板

官方文档

Django模板中只需要记两种特殊符号:
{{ }}和 {% %}
{{ }}表示变量,在模板渲染的时候替换成值,{% %}表示逻辑相关的操作。

变量

{{ 变量名 }}
变量名由字母数字和下划线组成。
点(.)在模板语言中有特殊的含义,用来获取对象的相应属性值。

模板中支持的语法

{# 取l中的第一个参数 #}
{{ l.0 }}
{# 取字典中key的值 #}
{{ d.name }}
{# 取对象的name属性 #}
{{ person_list.0.name }}
{# .操作只能调用不带参数的方法 #}
{{ person_list.0.dream }}

注:当模板系统遇到一个(.)时,会按照如下的顺序去查询:

  1. 在字典中查询
  2. 属性或者方法
  3. 数字索引

Filters

翻译为过滤器,用来修改变量的显示结果。
语法: {{ value|filter_name:参数 }}
'|'左右没有空格没有空格没有空格

  • default,{{ value|default:"nothing"}}
    如果value值没传的话就显示nothing
    注:TEMPLATES的OPTIONS可以增加一个选项:string_if_invalid:'找不到',可以替代default的的作用。
  • filesizeformat,将值格式化为一个 “人类可读的” 文件尺寸 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)。例如:{{ value|filesizeformat }}
  • add,给变量加个数,或者字符串拼接,`{{'11'|add":'qwe'}}
  • lower,小写,{{ 'q'|upper }}
  • upper,大写,{{ 'A'|lower }}
  • title,首字母大写,
  • ljust,把字符串在指定宽度中对左,其它用空格填充,{{ 'lower'|ljust:'100' }}
  • rjust,把字符串在指定宽度中对右,其它用空格填充
  • center,把字符串在指定宽度中居中,其它用空格填充
  • length,返回value的长度,{{ value|length }}
  • slice,切片,{{ 'qweasd'|slice:'2:' }} # easd
  • first,取第一个元素
  • last,取最后一个元素
  • join,使用字符串拼接列表。同python的str.join(list)。{{ value|join:" // " }}
  • truncatechars,如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾。{{ 'aaaaaaaaaaa'|truncatechars:5 }}
  • date,日期格式化,{{ value|date:"Y-m-d H:i:s"}},可格式化输出的字符:点击查看
  • safe,关闭转义,{{ '<a href="">点我</a>'|safe }}

自定义filter

自定义过滤器只是带有一个或两个参数的Python函数:
变量(输入)的值 - -不一定是一个字符串
参数的值 - 这可以有一个默认值,或完全省略
例如,在过滤器{{var | foo:“bar”}}中,过滤器foo将传递变量var和参数“bar”。

步骤:

  1. 定义

    1. 在APP下创建一个叫templatetags的python包; 不能变的,必须是包,名字必须是templatetags

    2. 在包中创建一个py文件 my_filters;

    3. 在py文件中写代码

       from django import template
       
       register = template.Library()  # register的名字不能改
    
    1. 写函数
    def add_str(value, arg):
        return '{}_{}'.format(value, arg)
    
    1. 给定义的函数加装饰器
    @register.filter
    def add_str(value, arg):
        return '{}_{}'.format(value, arg)
    
  2. 使用

    在模板中使用:

    1. 导入定义的文件
       {% load my_filter %}
    
    1. 使用过滤器
    {{ 'gg'|add_str:'666' }}
    
标签
  • csrf_token,防止跨站请求伪造,在from表单中加入一个token

添加在form表单中,就可以提交POST请求,不需要禁用csrf中间件了

  • for

    • Variable Description
    • forloop.counter 当前循环的索引值(从1开始)
    • forloop.counter0 当前循环的索引值(从0开始)
    • forloop.revcounter 当前循环的倒序索引值(从1开始)
    • forloop.revcounter0 当前循环的倒序索引值(从0开始)
    • forloop.first 当前循环是不是第一次循环(布尔值)
    • forloop.last 当前循环是不是最后一次循环(布尔值)
    • forloop.parentloop 本层循环的外层循环
  • for ... empty
    当列表为空时会调转到empty

{% for foo in info %}
    {{ foo }}
{% empty %}
    <span>空空如也</span>
{% endfor %}
  • if,elif和else
    支持if elif else

if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断。

  • with
    定义一个中间变量
{% with total=business.employees.count %}
    {{ total }} employee{{ total|pluralize }}
{% endwith %}

母版

  • 母版

就是一个普通的HTML文本,将多个页面公共部分的内容提取出来,在页面中定义多个block块

{% block content %}
{% endblock %}
  • 继承

在子页面中,继承母版 {% extends 'base.html' %}

可以重新书写block块中内容

  • 注意事项

    1. {% extends 'base.html' %} 写在第一行,上面不写内容
      
    2. 要修改的 内容写在block块中,写在外面不显示

    3. {% extends 'base.html' %} base.html 记得带上引号,不然当做变量去查找

    4. 定义多个block块,一般要定义上 css和js

组件

把多个页面公用的HTML代码放在一个HTML文件中 —— 》组件 nav.html

使用: {% include 'nav.html' %}

静态文件相关

引用文件路径时,使用 static 动态获取,修改静态文件路径时,只需去settings设置即可

{% load static %}

{% static '静态文件相对路径' %} ——》 获取到别名,跟后面的参数进行拼接

{% get_static_prefix %} ——》 获取到别名

{% load static %}
{% static "images/hi.jpg" as myphoto %}
<img src="{{ myphoto }}"></img>

自定义simpletag

和自定义filter类似,只不过接收更灵活的参数。

  • 定义注册simple tag
@register.simple_tag(name="plus")
def plus(a, b, c):
    return "{} + {} + {}".format(a, b, c)
  • 使用自定义simple tag
{% load my_simpletag %}
{% plus '1' '2' '3' %}

inclusion_tag

多用于返回html代码片段

示例:
templatetags/my_inclusion.py

from django import template

register = template.Library()


@register.inclusion_tag('result.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}

templates/result.html

<ul>
  {% for choice in data %}
    <li>{{ choice }}</li>
  {% endfor %}
</ul>

templates/index.html

<!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>inclusion_tag test</title>
</head>
<body>

{% load my_inclusion %}

{% show_results 10 %}
</body>
</html>

Django视图

一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应。

响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片。

无论视图本身包含什么逻辑,都要返回响应。代码写在哪里也无所谓,只要它在你当前项目目录下面。除此之外没有更多的要求了——可以说“没有什么神奇的地方”。为了将代码放在某处,大家约定成俗将视图放置在项目(project)或应用程序(app)目录中的名为views.py的文件中。

Django使用请求和响应对象来通过系统传递状态。

当浏览器向服务端请求一个页面时,Django创建一个HttpRequest对象,该对象包含关于请求的元数据。然后,Django加载相应的视图,将这个HttpRequest对象作为第一个参数传递给视图函数。

每个视图负责返回一个HttpResponse对象。

CBV和FBV

基于类的的View,叫CBV(function based view )
基于函数的的View,叫FBV(class based view)

FBV
# 增加出版社
def add_publisher(request):
    # 定义变量
    new_publisher, err_msg = '', ''
    # 判断请求
    if request.method == 'POST':
        new_publisher = request.POST.get('new_publisher').strip()
        # 判断出版社名称是否为空
        if not new_publisher:
            err_msg = '不能为空'
        # 判断出版社是否已存在
        elif models.Publisher.objects.filter(name=new_publisher):
            err_msg = '出版社已存在'
        else:
            models.Publisher.objects.create(name=new_publisher)
            return redirect('/publisher_list/')
    return render(request, 'add_publisher.html', {'new_publisher': new_publisher, 'err_msg': err_msg})
CBV
  1. 定义
# CBV版 add_publisher
# 增加出版社
from django.views import View


class AddPublisher(View):
    def get(self, request):
        return render(request, 'add_publisher.html')

    def post(self, request):
        new_publisher = request.POST.get('new_publisher').strip()
        # 判断出版社名称是否为空
        if not new_publisher:
            err_msg = '不能为空'
        # 判断出版社是否已存在
        elif models.Publisher.objects.filter(name=new_publisher):
            err_msg = '出版社已存在'
        else:
            models.Publisher.objects.create(name=new_publisher)
            return redirect('/publisher_list/')
        return render(request, 'add_publisher.html', {'new_publisher': new_publisher, 'err_msg': err_msg})
  1. 使用
url(r'^add_publisher/', views.AddPublisher.as_view()),
  1. CBV的流程

    1. views.AddPublisher.as_view() 程序加载的时候执行  ——》 view函数
      
    2. 当请求到来的时候执行view函数:

      1. self = AddPublisher()
      2. self.request = request
      3. 执行self.dispatch方法
        1. 判断请求方式是否被允许
          1. 允许时,通过反射获取到AddPublisher中定义的get或者post方法 ——》handler
          2. 不允许时,self.http_method_not_allowed ——》handler
        2. 执行handler 拿到返回结果 Httpresponse对象
  2. 给CBV加装饰器

    from django.utils.decorators import method_decorator
    
    1. 加载某个get/post的方法上:

      @method_decorator(timer)
      def get(self, request):
          pass
      
    2. 加在self.dispatch方法上:

      @method_decorator(timer)
      def dispatch(self, request, *args, **kwargs):
          pass
      
    3. 加在类上:

      @method_decorator(timer, name='post')
      @method_decorator(timer, name='get')
      class AddPublisher(View):
          pass
      
  3. 区别

    1. 不使用method_decorator

    func: <function AddPublisher.dispatch at 0x00000163735176A8>
    args :<app01.views.AddPublisher object at 0x00000163735F7EF0> <WSGIRequest: GET '/add_publisher/'>

    1. 使用method_decorator

    func:<function method_decorator.._dec.._wrapper..bound_func at 0x0000019664B4A378>
    arsgs: <WSGIRequest: GET '/add_publisher/'>

    1. 在普通装饰器中加 def inner(request,*args, **kwargs):,即可起到相同效果

Request对象和Response对象

request对象

当一个页面被请求时,Django就会创建一个包含本次请求原信息的HttpRequest对象。
Django会将这个对象自动传递给响应的视图函数,一般视图函数约定俗成地使用 request 参数承接这个对象。

请求相关的常用值

  • path_info 返回用户访问url,不包括域名
  • method 请求中使用的HTTP方法的字符串表示,全大写表示。
  • GET 包含所有HTTP GET参数的类字典对象
  • POST 包含所有HTTP POST参数的类字典对象
  • body 请求体,byte类型 request.POST的数据就是从body里面提取到的
  • path_info 路径信息
  • get_full_path() 路径信息 + 参数
  • get_host() 获取主机
  • META 一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器
  • FILES 一个类似于字典的对象,包含所有的上传文件信息。

上传文件注意事项:

  1. form表单的enctype = 'multipart/form-data'
  2. request.FILES中获取文件对象
  3. 使用文件对象的chunks()

代码实例

def upload(request):
    '''
    保存上传文件前,数据需要存放在某个位置。默认当上传文件小于2.5M时,django会将上传文件的全部内容读进内存。从内存读取一次,写磁盘一次。
    但当上传文件很大时,django会把上传文件写到临时文件中,然后存放到系统临时文件夹中。
    :param request:
    :return:
    '''
    if request.method == 'POST':
        # 从请求的FILES中获取上传文件的文件名,file为页面上type=files类型input的name属性值
        file_name = request.FILES['file_name'].name
        with open(file_name, 'wb') as f:
            # 从上传的文件对象中一点一点读
            for chunk in request.FILES['file_name'].chunks():
                f.write(chunk)
                # 写入本地文件
        return HttpResponse('上传成功!')
    return render(request, 'upload.html')
Response对象

我们写的每个视图都需要实例化,填充和返回一个HttpResponse。
HttpResponse类位于django.http模块中。

HttpResponse

传递字符串

from django.http import HttpResponse
response = HttpResponse("Here's the text of the Web page.")

设置或删除响应头信息

response = HttpResponse()
response['Content-Type'] = 'text/html; charset=UTF-8'
del response['Content-Type']

属性

  • HttpResponse.content:响应内容
  • HttpResponse.charset:响应内容的编码
  • HttpResponse.status_code:响应的状态码

JsonResponse对象
JsonResponse是HttpResponse的子类,专门用来生成JSON编码的响应。

from django.http import JsonResponse
from django.shortcuts import HttpResponse
def json_test(rerquest):
    data = {'name': 'root', 'pwd': '123'}
    ret = JsonResponse(data)  # <JsonResponse status_code=200, "application/json">
    ret2 = HttpResponse(data)  # <HttpResponse status_code=200, "text/html; charset=utf-8">
    ret3 = HttpResponse(data, content_type='application/json')  # <HttpResponse status_code=200, "application/json">
    return ret

Django shortcut functions

render()
结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。
参数:

  • request: 用于生成响应的请求对象。
  • template_name:要使用的模板的完整名称,可选的参数
  • context:添加到模板上下文的一个字典。默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。
  • content_type:生成的文档要使用的MIME类型。默认为 DEFAULT_CONTENT_TYPE 设置的值。默认为'text/html'
  • status:响应的状态码。默认为200。
  • useing: 用于加载模板的模板引擎的名称。

redirect()
参数可以是:

  • 一个模型:将调用模型的get_absolute_url() 函数
  • 一个视图,可以带有参数:将使用urlresolvers.reverse 来反向解析名称
  • 一个绝对的或相对的URL,将原封不动的作为重定向的位置。
    默认返回一个临时的重定向;传递permanent=True 可以返回一个永久的重定向。

Django路由系统

URL配置(URLconf)就像Django所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表。

我们就是以这种方式告诉Django,遇到哪个URL的时候,要对应执行哪个函数。

URLconf配置

基本格式

from django.conf.urls import url

urlpatterns = [
     url(regex, view, kwargs, name):
]

例如

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

urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/([0-9]{4})/$', views.year_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]

参数说明:

  • regex,正则表达式:一个正则表达式字符串
  • views,视图:一个可调用对象,通常为一个视图函数
  • kwargs,参数:可选的要传递给视图函数的默认参数(字典形式)
  • name,别名:一个可选的name参数

注意:
Django 2.0版本中的路由系统是下面的写法(官方文档):

from django.urls import path,re_path

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    path('articles/<int:year>/', views.year_archive),
    path('articles/<int:year>/<int:month>/', views.month_archive),
    path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
]

2.0版本中re_path和1.11版本的url是一样的用法。

正则表达式详解

基本配置

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/([0-9]{4})/$', views.year_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]

注意事项

  • urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
  • 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(分组匹配)。
  • 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
  • 每个正则表达式前面的'r' 是可选的但是建议加上。

补充说明

# 是否开启URL访问地址后面不为/跳转至带有/的路径的配置项
APPEND_SLASH=True

Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True。 其作用就是自动在网址结尾加'/'。

其效果就是:

我们定义了urls.py:

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

urlpatterns = [
    url(r'^blog/$', views.blog),
]

访问 http://www.example.com/blog 时,默认将网址自动转换为 http://www.example/com/blog/

如果在settings.py中设置了 APPEND_SLASH=False,此时我们再请求 http://www.example.com/blog 时就会提示找不到页面。

分组命名匹配

上面的示例使用简单的正则表达式分组匹配(通过圆括号)来捕获URL中的值并以位置参数形式传递给视图。

在更高级的用法中,可以使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图。

在Python的正则表达式中,分组命名正则表达式组的语法是(?Ppattern),其中name是组的名称,pattern是要匹配的模式

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    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(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]

这个实现与前面的示例完全相同,只有一个细微的差别:捕获的值作为关键字参数而不是位置参数传递给视图函数。

例如,针对URL /articles/2017/12/相当于按以下方式调用视图函数:

views.month_archive(request, year="2017", month="12")

在实际应用中,使用分组命名匹配的方式可以让你的URLconf 更加明晰且不容易产生参数顺序问题的错误,但是有些开发人员则认为分组命名组语法太丑陋、繁琐。

至于究竟应该使用哪一种,你可以根据自己的喜好来决定。

URLconf匹配的位置

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等等 —— 都将路由到相同的函数。

捕获的参数永远都是字符串
视图函数中指定默认值

可以在视图函数中指定默认值,如果没有从URL截获内容,将使用默认值,否则使用URL中的内容。

include其他的URLconfs

可以将同类型的URLconfs放入一个文件中,再用include引入,便于管理。

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

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'', include('app01.urls')),
]
传递额外的参数给视图函数

URLconfs 具有一个钩子,让你传递一个Python 字典作为额外的参数传递给视图函数。

django.conf.urls.url() 可以接收一个可选的第三个参数,它是一个字典,表示想要传递给视图函数的额外关键字参数。
例如:

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

urlpatterns = [
    url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}),
]

在这个例子中,对于/blog/2005/请求,Django 将调用views.year_archive(request, year='2005', foo='bar')。
当传递额外参数的字典中的参数和URL中捕获值的命名关键字参数同名时,函数调用时将使用的是字典中的参数,而不是URL中捕获的参数。

命名URL和URL反向解析

在使用Django 项目时,一个常见的需求是获得URL的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。
人们强烈希望不要硬编码这些URL(费力、不可扩展且容易产生错误)或者设计一种与URLconf 毫不相关的专门的URL 生成机制,因为这样容易导致一定程度上产生过期的URL。
换句话讲,需要的是一个DRY 机制。除了其它有点,它还允许设计的URL 可以自动更新而不用遍历项目的源代码来搜索并替换过期的URL。
获取一个URL 最开始想到的信息是处理它视图的标识(例如名字),查找正确的URL 的其它必要的信息有视图参数的类型(位置参数、关键字参数)和值。
Django 提供一个办法是让URL 映射是URL 设计唯一的地方。你填充你的URLconf,然后可以双向使用它:

根据用户/浏览器发起的URL 请求,它调用正确的Django 视图,并从URL 中提取它的参数需要的值。
根据Django 视图的标识和将要传递给它的参数的值,获取与之关联的URL。
第一种方式是我们在前面的章节中一直讨论的用法。第二种方式叫做反向解析URL、反向URL 匹配、反向URL 查询或者简单的URL 反查。
在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:

在模板中:使用url模板标签。
在Python 代码中:使用django.core.urlresolvers.reverse() 函数。
在更高层的与处理Django 模型实例相关的代码中:使用get_absolute_url() 方法。

说人话就是,可以给我们的URL匹配规则起个名字,一个URL匹配模式起一个名字。
这样我们以后就不需要写死URL代码了,只需要通过名字来调用当前的URL。当修改URLconf中的regex时,其他代码不需要改变。

  1. 普通url

    1. 命名

      url(r'^publisher_list/', views.publisher_list, name='publisher'),
      
    2. 使用

      视图中使用:

      ​ 1.from django.urls import reverse

      ​ reverse('publisher') ——》 ‘/app01/publisher_list/’

      模板中使用:

      ​ {% url 'publisher' %} ——》 ‘/app01/publisher/’

  2. 使用分组

    ​ url(r'^blog/(\d{4})/([1-9]{2})/$', views.blog, name='blog')

    ​ 视图中使用

    ​ reverse('blog',args=('2018','12')) ‘/blog/2018/12/'

    ​ 模板中使用

  3. 使用命名分组

    ​ url(r'^blog/(?P\d{4})/(?P[1-9]{2})/$', views.blog, name='blog')

    ​ 视图中使用

    ​ reverse('blog',args=('2018','12')) ‘/blog/2018/12/'

    ​ reverse('blog', kwargs={'month': '12', 'year': '2018'})

    ​ 模板中使用

    ​ {% url 'blog' '2018' '12' %} ——》 ‘/blog/2018/12/'

    ​ {% url 'blog' year='2018' month='12' %} ——》 ‘/blog/2018/12/'

    ​ {% url 'blog' month='12' year='2018' %} ——》 ‘/blog/2018/12/'

命名空间模式

即使不同的APP使用相同的URL名称,URL的命名空间模式也可以让你唯一反转命名的URL。

举个例子:

project中的urls.py

from django.conf.urls import url, include

urlpatterns = [
    url(r'', include('app01.urls', namespace='app01')),
    url(r'', include('app02.urls', namespace='app02'))
]

app01中的urls.py

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

urlpatterns = [
    url(r'^index/(?P<year>\d{4})/(?P<month>\d{2})/$', views.index, name='index'),
]

app01中的urls.py

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

urlpatterns = [
    url(r'^index/(?P<year>\d{4})/(?P<month>\d{2})/$', views.index, name='index'),
]

现在,我的两个app中 url名称重复了,我反转URL的时候就可以通过命名空间的名称得到我当前的URL。

语法:

'命名空间名称:URL名称'

使用app01中的index
视图中

reverse('app01:index', args=('2018', '19'))
reverse('app01:index', kwargs={'year': 2018, 'month': 45})

模板中

{% url  'app01:index' '2018' '12' %}
{% url  'app01:index' year='2018' month='12' %}
{% url  'app01:index' month='18' year='1200' %}

这样即使app中URL的命名相同,我也可以反转得到正确的URL了。  

Django ORM操作

ORM介绍

ORM概念

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。

简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

ORM在业务逻辑层和数据库层之间充当了桥梁的作用。

ORM由来

让我们从O/R开始。字母O起源于"对象"(Object),而R则来自于"关系"(Relational)。

几乎所有的软件开发过程中都会涉及到对象和关系数据库。在用户层面和业务逻辑层面,我们是面向对象的。当对象的信息发生变化的时候,我们就需要把对象的信息保存在关系数据库中。

按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂很多SQL语句用来增加、读取、修改、删除相关数据,而这些代码通常都是极其相似或者重复的。

ORM的优势

ORM解决的主要问题是对象和关系的映射。它通常将一个类和一张表一一对应,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段。

ORM提供了对数据库的映射,不用直接编写SQL代码,只需操作对象就能对数据库操作数据。

让软件开发人员专注于业务逻辑的处理,提高了开发效率

ORM的劣势

ORM的缺点是会在一定程度上牺牲程序的执行效率。

ORM的操作是有限的,也就是ORM定义好的操作是可以完成的,一些复杂的查询操作是完成不了。

ORM用多了SQL语句就不会写了,关系数据库相关技能退化...

ORM总结

ORM只是一种工具,工具确实能解决一些重复,简单的劳动。这是不可否认的。

但我们不能指望某个工具能一劳永逸地解决所有问题,一些特殊问题还是需要特殊处理的。

但是在整个软件开发过程中需要特殊处理的情况应该都是很少的,否则所谓的工具也就失去了它存在的意义。

Django中的ORM

Django项目使用MySQL数据库

1、 在Django项目的settings.py文件中,配置数据库连接信息:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "你的数据库名称",  # 需要自己手动创建数据库
        "USER": "数据库用户名",
        "PASSWORD": "数据库密码",
        "HOST": "数据库IP",
        "POST": 3306
    }
}

2、 在与Django项目同名的目录下的__init__.py文件中写如下代码,告诉Django使用pymysql模块连接MySQL数据库:

import pymysql
 
pymysql.install_as_MySQLdb()

3、 在app下的models中写类

4、 数据库迁移

    python manage.py makemigrations   # 在app下的migrations文件夹下记录 models的变更记录
    python manage.py migrate    # 将models的变更记录同步到数据库中

Model

在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常,一个模型(model)映射到一个数据库表。

基本情况:

  • 每个模型都是一个Python类,它是django.db.models.Model的子类。
  • 模型的每个属性都代表一个数据库字段。
  • 综上所述,Django为您提供了一个自动生成的数据库访问API
字段和字段的参数
常用字段
  • AutoField

自增的整形字段,必填参数primary_key=True,则成为数据库的主键。无该字段时,django自动创建。

一个model不能有两个AutoField字段。

  • IntegerField

一个整数类型。数值的范围是 -2147483648 ~ 2147483647。

  • BooleanField
    布尔类型

  • CharField

字符类型,必须提供max_length参数。max_length表示字符的长度。

  • TextField

长文本

  • DateField

日期类型,日期格式为YYYY-MM-DD,相当于Python中的datetime.date的实例。

参数:

- auto_now:每次修改时修改为当前日期时间。
- auto_now_add:新创建对象时自动添加当前日期时间。
- auto_now和auto_now_add和default参数是互斥的,不能同时设置。
  • DatetimeField

日期时间字段,格式为YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime的实例。

  • DecimalField
    小数类型

字段类型,详情可点击查询官网。

自定义字段
自定义一个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)
字段参数

字段参数,详情可点击查看官网。

    null                数据库中字段是否可以为空
    db_column           数据库中字段的列名
    default             数据库中字段的默认值
    primary_key         数据库中字段是否为主键
    db_index            数据库中字段是否可以建立索引
    unique              数据库中字段是否可以建立唯一索引
    unique_for_date     数据库中字段【日期】部分是否可以建立唯一索引
    unique_for_month    数据库中字段【月】部分是否可以建立唯一索引
    unique_for_year     数据库中字段【年】部分是否可以建立唯一索引
 
    verbose_name        Admin中显示的字段名称
    blank               Admin中是否允许用户输入为空
    editable            Admin中是否可以编辑
    help_text           Admin中该字段的提示信息
    choices             Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
                        如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1)
多表关系和参数
ForeignKey(ForeignObject) # ForeignObject(RelatedField)
    to,                 # 要进行关联的表名
    to_field=None,      # 要关联的表中的字段名称
    on_delete=None,     # 当删除关联表中的数据时,当前表与其关联的行的行为
                        - models.CASCADE,删除关联数据,与之关联也删除
                        - models.DO_NOTHING,删除关联数据,引发错误IntegrityError
                        - models.PROTECT,删除关联数据,引发错误ProtectedError
                        - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
                        - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
                        - models.SET,删除关联数据,
                               a. 与之关联的值设置为指定值,设置:models.SET(值)
                               b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
 
                                    def func():
                                        return 10
 
                                    class MyModel(models.Model):
                                        user = models.ForeignKey(
                                            to="User",
                                            to_field="id"
                                            on_delete=models.SET(func),)
    related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
    related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
    limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                # 如:
                        - limit_choices_to={'nid__gt': 5}
                        - limit_choices_to=lambda : {'nid__gt': 5}
 
                        from django.db.models import Q
                        - limit_choices_to=Q(nid__gt=10)
                        - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                        - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
    db_constraint=True          # 是否在数据库中创建外键约束
    parent_link=False           # 在Admin中是否显示关联数据
 
 
OneToOneField(ForeignKey)
    to,                 # 要进行关联的表名
    to_field=None       # 要关联的表中的字段名称
    on_delete=None,     # 当删除关联表中的数据时,当前表与其关联的行的行为
 
                        ###### 对于一对一 ######
                        # 1. 一对一其实就是 一对多 + 唯一索引
                        # 2.当两个类之间有继承关系时,默认会创建一个一对一字段
                        # 如下会在A表中额外增加一个c_ptr_id列且唯一:
                                class C(models.Model):
                                    nid = models.AutoField(primary_key=True)
                                    part = models.CharField(max_length=12)
 
                                class A(C):
                                    id = models.AutoField(primary_key=True)
                                    code = models.CharField(max_length=1)
 
ManyToManyField(RelatedField)
    to,                         # 要进行关联的表名
    related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
    related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
    limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                # 如:
                                    - limit_choices_to={'nid__gt': 5}
                                    - limit_choices_to=lambda : {'nid__gt': 5}
 
                                    from django.db.models import Q
                                    - limit_choices_to=Q(nid__gt=10)
                                    - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                    - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
    symmetrical=None,           # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
                                # 做如下操作时,不同的symmetrical会有不同的可选字段
                                    models.BB.objects.filter(...)
 
                                    # 可选字段有:code, id, m1
                                        class BB(models.Model):
 
                                        code = models.CharField(max_length=12)
                                        m1 = models.ManyToManyField('self',symmetrical=True)
 
                                    # 可选字段有: bb, code, id, m1
                                        class BB(models.Model):
 
                                        code = models.CharField(max_length=12)
                                        m1 = models.ManyToManyField('self',symmetrical=False)
 
    through=None,               # 自定义第三张表时,使用字段用于指定关系表
    through_fields=None,        # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
                                    from django.db import models
 
                                    class Person(models.Model):
                                        name = models.CharField(max_length=50)
 
                                    class Group(models.Model):
                                        name = models.CharField(max_length=128)
                                        members = models.ManyToManyField(
                                            Person,
                                            through='Membership',
                                            through_fields=('group', 'person'),
                                        )
 
                                    class Membership(models.Model):
                                        group = models.ForeignKey(Group, on_delete=models.CASCADE)
                                        person = models.ForeignKey(Person, on_delete=models.CASCADE)
                                        inviter = models.ForeignKey(
                                            Person,
                                            on_delete=models.CASCADE,
                                            related_name="membership_invites",
                                        )
                                        invite_reason = models.CharField(max_length=64)
    db_constraint=True,         # 是否在数据库中创建外键约束
    db_table=None,              # 默认创建第三张表时,数据库中表的名称
Model 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"),)   # 应为两个存在的字段

ORM操作

必知必会13条
<1> all():                 查询所有结果

<2> get(**kwargs):         返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。
 
<3> filter(**kwargs):      它包含了与所给筛选条件相匹配的对象
 
<4> exclude(**kwargs):     它包含了与所给筛选条件不匹配的对象
 
<5> values(*field):        返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列
 
<6> values_list(*field):   它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列
 
<7> order_by(*field):      对查询结果排序
 
<8> reverse():             对查询结果反向排序,请注意reverse()通常只能在具有已定义顺序的QuerySet上调用(在model类的Meta中指定ordering或调用order_by()方法)。
 
<9> distinct():            从返回结果中剔除重复纪录(如果你查询跨越多个表,可能在计算QuerySet时得到重复的结果。此时可以使用distinct(),注意只有在PostgreSQL中支持按字段去重。)
 
<10> count():              返回数据库中匹配查询(QuerySet)的对象数量。
 
<11> first():              返回第一条记录
 
<12> last():               返回最后一条记录
 
<13> exists():             如果QuerySet包含数据,就返回True,否则返回False
  • 返回QuerySet对象的方法有
    all()

filter()

exclude()

order_by()

reverse()

distinct()

  • 特殊的QuerySet
    values() 返回一个可迭代的字典序列

values_list() 返回一个可迭代的元祖序列

  • 返回具体对象的
    get()

first()

last()

  • 返回布尔值的方法有:
    exists()

  • 返回数字的方法有
    count()

单表查询之双下划线
models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 获取id大于1 且 小于10的值
 
models.Tb1.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in
 
models.Tb1.objects.filter(name__contains="ven")  # 获取name字段包含"ven"的
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
 
models.Tb1.objects.filter(id__range=[1, 3])      # id范围是1到3的,等价于SQL的bettwen and
 
类似的还有:startswith,istartswith, endswith, iendswith 

date字段还可以:
models.Class.objects.filter(first_day__year=2017)
ForeignKey操作
  • 正向查找
    对象查找(跨表)
    语法:

对象.关联字段.字段

示例:

book_obj = models.Book.objects.first()  # 第一本书对象
print(book_obj.publisher)  # 得到这本书关联的出版社对象
print(book_obj.publisher.name)  # 得到出版社对象的名称

字段查找(跨表)
语法:

关联字段__字段

示例:
print(models.Book.objects.values_list("publisher__name"))

  • 反向查找
    对象查找(跨表)
    语法:

obj.表名_set

示例:

publisher_obj = models.Publisher.objects.first()  # 找到第一个出版社对象
books = publisher_obj.book_set.all()  # 找到第一个出版社出版的所有书
titles = books.values_list("title")  # 找到第一个出版社出版的所有书的书名

字段查找(跨表)
语法:

表名__字段

示例:
titles = models.Publisher.objects.values_list("book__title")

ManyToManyField操作

"关联管理器"是在一对多或者多对多的关联上下文中使用的管理器。

它存在于下面两种情况:

外键关系的反向查询
多对多关联关系
简单来说就是当 点后面的对象 可能存在多个的时候就可以使用以下的方法。

方法

create()
创建一个新的对象,保存对象,并将它添加到关联对象集之中,返回新创建的对象。

>>> import datetime
>>> models.Author.objects.first().book_set.create(title="番茄物语", publish_date=datetime.date.today())

add()
把指定的model对象添加到关联对象集中。
添加对象

>>> author_objs = models.Author.objects.filter(id__lt=3)
>>> models.Book.objects.first().authors.add(*author_objs)

添加id
>>> models.Book.objects.first().authors.add(*[1, 2])
set()

更新model对象的关联对象。

>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.set([2, 3])

remove()

从关联对象集中移除执行的model对象

>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.remove(3)

clear()
从关联对象集中移除一切对象。

>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.clear()

注意:
对于ForeignKey对象,clear()和remove()方法仅在null=True时存在。
举个例子:
ForeignKey字段没设置null=True时,

class Book(models.Model):
    title = models.CharField(max_length=32)
    publisher = models.ForeignKey(to=Publisher)

没有clear()和remove()方法:

>>> models.Publisher.objects.first().book_set.clear()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'RelatedManager' object has no attribute 'clear'

当ForeignKey字段设置null=True时,

class Book(models.Model):
    name = models.CharField(max_length=32)
    publisher = models.ForeignKey(to=Class, null=True)

此时就有clear()和remove()方法:
>>> models.Publisher.objects.first().book_set.clear()

注意:
对于所有类型的关联字段,add()、create()、remove()和clear(),set()都会马上更新数据库。换句话说,在关联的任何一端,都不需要再调用save()方法。

聚合查询和分组查询
聚合

aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。
键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。
聚合: 对一组数据进行统计分析

from app01 import models

from django.db.models import Max, Min, Sum, Avg, Count

ret = models.Book.objects.aggregate(max=Max('price'))
分组
# 统计出每个出版社买的最便宜的书的价格
# 方法一    以出版社的id进行分组
# ret = models.Publisher.objects.all().annotate(Min('book__price')).values()
# for i in ret:
#     print(i)

# 方法二	values指定以什么字段进行分组,后面的values不能写其他额外的字段
ret = models.Book.objects.values('publisher__name').annotate(min=Min('price')).values('min')
for i in ret:
    print(i)
F查询和Q查询

在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?

Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。

F
from django.db.models import F
# 动态地获取字段的值
ret= models.Book.objects.filter(stock__gt=F('sale')).values()

models.Book.objects.all().update(sale=F('sale')*2)  # update更新指定字段
Q
from django.db.models import Q

ret = models.Book.objects.filter(Q(~Q(id__lt=3) | Q(id__gt=5))&Q(id__lt=4))
print(ret)
  • ~ 取反
  • | 或
  • & 与 AND
事务

一系列操作要同时成功执行,或都不执行,保证原子性操作。

import os

if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings")
    import django
    django.setup()

    import datetime
    from app01 import models

    try:
        from django.db import transaction
        with transaction.atomic():
            new_publisher = models.Publisher.objects.create(name="火星出版社")
            models.Book.objects.create(title="橘子物语", publish_date=datetime.date.today(), publisher_id=10)  # 指定一个不存在的出版社id
    except Exception as e:
        print(str(e))
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',
        },
    }
}
在Python脚本中调用Django环境
import os

if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings")
    import django
    django.setup()

    from app01 import models

    books = models.Book.objects.all()
    print(books)

Cookie和Session

Cookie的由来

大家都知道HTTP协议是无状态的。

无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。

一句有意思的话来描述就是人生只如初见,对服务器来说,每次的请求都是全新的。

状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。

什么是Cookie

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

Cookie的原理

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

Cookie特性
  1. 保存在浏览器上的一组组键值对
  2. 服务器让浏览器保存的cookie
  3. 浏览器有权利进行不设置
  4. 下次访问时自动携带响应的cookie

Django中操作Cookie

设置Cookie
rep = HttpResponse(...)
rep = render(request, ...)

rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐',...)

参数:

  • key, 键
  • value='', 值
  • max_age=None, 超时时间
  • expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
  • path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
  • domain=None, Cookie生效的域名
  • secure=False, https传输
  • httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
获取Cookie
request.COOKIES['key']
request.get_signed_cookie('key', default=RAISE_ERROR, salt='', max_age=None)

get_signed_cookie方法的参数:

  • default: 默认值
  • salt: 加密盐
  • max_age: 后台控制过期时间
删除Cookie
def logout(request):
    rep = redirect("/login/")
    rep.delete_cookie("user")  # 删除用户浏览器上之前设置的user的cookie值
    return rep

Session

Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。

问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。

我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。

总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。

另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。

Django中Session相关方法

# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']


# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()

# 会话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失效策略。

Django中的Session配置

Django中默认支持Session,其内部提供了5种类型的Session供开发者使用。
可以在 django.conf.global_settings 修改
默认:
SESSION_ENGINE = 'django.contrib.sessions.backends.db'

1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 

4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

其他公用设置项:
SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

Django中间件

中间件介绍

官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。

但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。

说人话就是,在Django中有一个类,Django框架会在,在请求响应的过程中(视图函数前后)会做一些额外的操作,改变全局的输入、输出。

Django项目的中间件设置在Settings.py文件中。

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

MIDDLEWARE配置项是一个列表,列表中是一个个字符串,这些字符串其实是一个个类,也就是一个个中间件。

自定义中间件

中间件可以定义五个方法,分别是:(主要的是process_request和process_response)

  • process_request(self,request)
  • process_view(self, request, view_func, view_args, view_kwargs)
  • process_exception(self, request, exception)
  • process_template_response(self,request,response)
  • process_response(self, request, response)
自定义一个中间件示例
from django.utils.deprecation import MiddlewareMixin


class MD1(MiddlewareMixin):

    def process_request(self, request):
        print('from MD1 process_request')

    def process_response(self, request, response):
        print('from MD1 process_response')
        return response

process_request(self,request)
  • 执行时间:在视图函数执行之前
  • 参数:request,与视图函数中的request同一个
  • 执行顺序:按照注册表,顺序执行
  • 返回值:
    • None:正常流程
    • HttpResponse对象:不执行后面中间中的process_request方法,不执行视图函数,直接执行当前中间件中的process_response方法,后面正常走
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_exception(self, request, exception)
  • 执行时间:在视图函数中出现异常时执行
  • 参数:request,与视图函数中的request同一个,exception,异常对象
  • 执行顺序:按照注册表,倒叙执行
  • 返回值:
    • None:正常流程
    • HttpResponse对象:不执行后面中间中的process_exception方法,直接执行注册表中最后一个中间件中的process_response方法,后面正常走
process_template_response(self,request,response)
  • 执行时间:在视图函数返回后,返回对象里有一个render的方法
  • 参数:request,与视图函数中的request同一个,视图返回的HttpResponse对象
  • 执行顺序:按照注册表,倒叙执行
  • 返回值:必须返回HttpResponse对象
process_response(self, request, response)
  • 执行时间:在视图函数执行后
  • 参数:request,与视图函数中的request同一个,视图返回的response对象
  • 执行顺序:按照注册表,倒叙执行
  • 返回值:必须返回HttpResponse对象

middleware

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


class MD1(MiddlewareMixin):

    def process_request(self, request):
        print('from MD1 process_request')

    def process_response(self, request, response):
        print('frmo MD1 process_response')
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('from MD1 process_view')
        # return HttpResponse('from MD1 process_view')

    def process_exception(self, request, exception):
        print('from MD1 process_exception')
        # return HttpResponse('from MD1 process_exception')

    def process_template_response(self, request, response):
        print('from MD1 process_template_response')
        return response


class MD2(MiddlewareMixin):

    def process_request(self, request):
        print('from MD2 process_request')

    def process_response(self, request, response):
        print('frmo MD2 process_response')
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('from MD2 process_view')
        # return HttpResponse('from MD2 process_view')

    def process_exception(self, request, exception):
        print('from MD2 process_exception')
        # return HttpResponse('from MD2 process_exception')

    def process_template_response(self, request, response):
        print('from MD2 process_template_response')
        return response



view

from django.shortcuts import HttpResponse


def index(request):
    print('index')

    # raise Exception
    def render():
        print('render')
        return HttpResponse('render')

    rep = HttpResponse('index')
    # rep.render = render
    return rep

中间件的执行流程

请求到达中间件之后,先按照正序执行每个注册中间件的process_reques方法,process_request方法返回的值是None,就依次执行,如果返回的值是HttpResponse对象,不再执行后面的process_request方法,而是执行当前对应中间件的process_response方法,将HttpResponse对象返回给浏览器。也就是说:如果MIDDLEWARE中注册了6个中间件,执行过程中,第3个中间件返回了一个HttpResponse对象,那么第4,5,6中间件的process_request和process_response方法都不执行,顺序执行3,2,1中间件的process_response方法。

process_request方法都执行完后,匹配路由,找到要执行的视图函数,先不执行视图函数,先执行中间件中的process_view方法,process_view方法返回None,继续按顺序执行,所有process_view方法执行完后执行视图函数。假如中间件3 的process_view方法返回了HttpResponse对象,则4,5,6的process_view以及视图函数都不执行,直接从最后一个中间件,也就是中间件6的process_response方法开始倒序执行。

process_template_response和process_exception两个方法的触发是有条件的,执行顺序也是倒序。总结所有的执行流程如下:

Django请求流程图

CSRF中间件

大致流程:

  • 第一次访问页面

    • 首先第一次访问页面,Template中的{% csrf_token %}会启动get_token,生产一个csrf_secret的值。
    • 这个值在_salt_cipher_secret中随机生产一个与csrf_secret长度相同的salt,利用salt加密csrf_secret,两个字符串拼接形成csrf_token,request.META['CSRF_COOKIE'] = csrf_token 并设置到cookie里面。
    • get_token返回的用随机生成的另外一个salt加密csrf_secret,同样拼接返回放入隐藏的input之中
  • 向页面提交表单
    提交的cookie中含有的csrf_token与表单提交的csrfmiddlewaretoken在process_view进行解密,比对,如果解密出来的数值不同直接返回_reject()

  • 向页面提交Ajax
    同样,提交的cookie中含有的csrf_token与Ajax中含有X-csrftoken在process_view进行解密,比对。如果解密出来的结果不同则直接返回_reject()

  • 如果发出的请求是'GET', 'HEAD', 'OPTIONS', 'TRACE' 中的一种,或者view函数中加上了csrf_exempt装饰器,则不进行校验。

详细: https://blog.csdn.net/qq_27952549/article/details/82392790
token验证原理 https://www.jianshu.com/p/7fbb60001018

Ajax

AJAX准备知识:JSON

什么是 JSON ?

  • JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)
  • JSON 是轻量级的文本数据交换格式
  • JSON 独立于语言 *
  • JSON 具有自我描述性,更易理解

AJAX简介

AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)。

AJAX 不是新的编程语言,而是一种使用现有标准的新方法。

AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)

AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。

  • 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
  • 异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。

特点:

  • AJAX使用JavaScript技术向服务器发送异步请求;
  • AJAX请求无须刷新整个页面;
  • 因为服务器响应内容不再是整个页面,而是页面中的部分内容,所以AJAX性能高;

ajax发送请求

$.ajax({
    url: '/ajax_test/',         # 求情的地址
    type: 'post',               # 请求的类型
    data: {                     # 请求的数据
        username: 'root',   
        pwd: '123',
        info: JSON.stringify(['111', '222', '333'])     # json格式数据
    },
    success: function (res) {   # 正常响应的回调函数
        alert(res);
    },
    error: function (res) {     # 错误响应的回调函数
        console.log(res)
    }
})

ajax上传文件

html+js


<form action="" enctype="multipart/form-data"></form>
<input type="file" name="file">
<button id="btn2">上传文件</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
    $('#btn2').click(function () {
        var form_boj = new FormData();
        form_boj.append('file', $('[name="file"]')[0].files[0]);
        $.ajax({
            url: '/upload/',
            type: 'post',
            processData: false, {# 不需要处理数据编码格式 #}
            contentType: false, {# 不需要处理请求头 #}
            data: form_boj,
        })

    })
</script>

view

def upload(request):
    if request.is_ajax():
        file_obj = request.FILES.get('file')
        with open(file_obj.name, 'wb')  as f:
            for i in file_obj.chunks():
                f.write(i)
        return HttpResponse('上传成功')
    return HttpResponse('233')

Ajax发送POST请求

页面中使用{% csrf_token %},给POST提交数据中添加csrfmiddlewaretoken的键值对

    $('#btn3').click(function () {
        $.ajax({
            url: 'index',
            type: 'post',
            data: {
                'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val(),
                data: JSON.stringify({
                    'info': '666',
                })
            },
            success: function (res) {
                alert(res)
            }
        })
    })

添加X-csrftoken的请求头

    $('#btn4').click(function () {
        $.ajax({
            url: 'index',
            type: 'post',
            headers: {
                'X-csrftoken': $('[name="csrfmiddlewaretoken"]').val(),
            },
            data: {
                data: JSON.stringify({
                    'info': '666',
                })
            },
            success: function (res) {
                alert(res)
            }
        })
    })

写个JS文件引入,从cookie中获取值,添加到请求头中,必须确保有csrftoken的cookie。

如果html页面没有包含{% csrf_token %},Django可能不会设置CSRFtoken的cookie,使用ensure_csrf_cookie装饰器,强制设置cookie
from django.views.decorators.csrf import ensure_csrf_cookie

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]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

var csrftoken = getCookie('csrftoken');

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
    beforeSend: function (xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

form组件

form组件

  1. form表单完成的事情

  2. 自动写input框

  3. 对提交的数据进行校验

  4. 提供错误提示

  5. 定义form组件

from django import forms


class RegForm(forms.Form):
   username = forms.CharField(label='用户名')
   pwd = forms.CharField(label='密码')
  1. 使用

视图中:

form_obj = RegForm()   # 实例化form对象
return render(request, 'register.html', {'form_obj': form_obj})

模板中:

form 标签加上novalidate 前端不进行校验

{{ form_obj.as_p }}   ——》  生成所有的p标签   label  input

{{ form_obj.errors }}   ——》所有字段的错误

{{ form_obj.user }}   ——》 该字段的input框
{{ form_obj.user.label }}   ——》 该字段的label  中文提示
{{ form_obj.user.id_for_label }}   ——》 该字段的id
{{ form_obj.user.errors }}   ——》 该字段的所有的错误信息
{{ form_obj.user.errors.0 }} ——》 该字段的第一个的错误信息

字段和参数

常用的字段

​ CharField input

​ ChoiceField select框
MultipleChoiceField 多选框

常用插件

创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

forms.widgets.PasswordInput  密码框
forms.widgets.RadioSelect   radio单选
forms.widgets.Select        select单选
forms.widgets.SelectMultiple    select多选
forms.widgets.CheckboxInput    check单选
forms.widgets.CheckboxSelectMultiple    check多选
字段的参数:
required=True,               是否允许为空
widget=None,                 HTML插件
label=None,                  用于生成Label标签或显示内容
initial=None,                初始值    
error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
validators=[],               自定义验证规则
disabled=False,              是否可以编辑

示例代码

from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator


def check(value):
    if 'aa' in value:
        raise ValidationError('敏感词汇!!')
    # return value


class MyForm(forms.Form):
    username = forms.CharField(
        label='用户名',
        min_length=8,
        initial='张三',
        required=True,
        # disabled=True,
        # validators=[check, ],
        error_messages={
            'required': '不能为空',
            'invalid': '格式错误',
            'min_length': '最短8位'
        }
    )

    pwd = forms.CharField(
        label='密码',
        widget=forms.PasswordInput()
    )
    re_pwd = forms.CharField(
        label='确认密码',
        widget=forms.PasswordInput()
    )

    phone = forms.CharField(
        label='手机号',
        required=True,
        validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式不正确!')]
    )
    gender = forms.fields.ChoiceField(
        choices=((1, '男'), (2, '女'), (3, '保密')),
        label='性别',
        initial=1,
        widget=forms.widgets.RadioSelect()

    )

    hobby = forms.fields.MultipleChoiceField(
        choices=((1, '吃'), (2, '玩'), (3, '睡')),
        label='爱好',
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()

    )

    def clean_username(self):
        value = self.cleaned_data.get('username')
        if 'bb' in value:
            raise ValidationError('敏感词汇!!!!')
        return value

    def clean(self):
        pwd = self.cleaned_data.get('pwd')
        re_pwd = self.cleaned_data.get('re_pwd')
        if pwd != re_pwd:
            self.add_error('re_pwd', '两次密码不一致!')
            raise ValidationError('两次密码输入不一致')
        return self.cleaned_data

校验

校验流程
0. 校验开始 form_obj.is_valid()

  1. 内置校验,self.validate(value)
  2. 自定义校验,self.run_validators(value)
  3. 局部钩子校验,if hasattr(self, 'clean_%s' % name):
  4. 全局钩子校验,self._clean_form() -》 self.clean()
  5. 获取数据,form_obj.cleaned_data
内置校验

校验 required=True,min_length=8,max_length=12,

源码示例:

def validate(self, value):
    if value in self.empty_values and self.required:
        raise ValidationError(self.error_messages['required'], code='required')
自定义校验(自定义校验器)

第一种

from django.core.exceptions import ValidationError

def check(value):
    if 'aa' in value:
        raise ValidationError('敏感词汇!!')
    # return value
    
    
validators=[check]
    

第二种

from django.core.validators import RegexValidator

validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式不正确!')]

局部钩子

定义一个方法 clean_字段名(self,)的方法,如果不通过校验规则,要抛出异常ValidationError,如果通过校验规则,返回通过校验的值
用于校验单个字段

def clean_username(self):
    value = self.cleaned_data.get('username')
    if 'bb' in value:
        raise ValidationError('敏感词汇!!!!')
    return value

全局钩子

定义一个方法 clean(self)的方法,如果不通过校验规则,要抛出异常ValidationError,还可以自己使用self.add_error('re_pwd', '两次密码不一致!')添加错误信息。如果通过校验规则,返回所有通过校验的值
用于校验多个字段或联合字段

def clean(self):
    pwd = self.cleaned_data.get('pwd')
    re_pwd = self.cleaned_data.get('re_pwd')
    if pwd != re_pwd:
        self.add_error('re_pwd', '两次密码不一致!')
        raise ValidationError('两次密码输入不一致')
    return self.cleaned_data

posted @ 2019-02-27 12:56  写bug的日子  阅读(241)  评论(0编辑  收藏  举报