admin 管理员组

文章数量: 1184232

文章目录

          • 1. 初识web框架
            • 1.1 http协议
            • 1.2 socket简介
            • 1.3 socket服务端概述
            • 1.4 自己写web框架
            • 1.5 web框架的分类
          • 2. 初识django
          • 3. django程序目录
          • 4. 第一个django请求
          • 5. 静态文件以及模板的配置
            • 5.1 静态文件路径的配置
            • 5.2 HttpResponse与render函数
            • 5.3 模板路径的配置
          • 6. 创建程序步骤
            • 6.1 创建project
            • 6.2 配置模板路径
            • 6.3 配置静态文件目录
            • 6.4 额外配置
          • 7. 用户登录示例
          • 8. request.GET与 request.POST
            • 8.1 request.GET
            • 8.2 request.POST
          • 9. django模板语言特殊标记(重点)
            • 9.1 取字符串的值
            • 9.2 取列表的值
            • 9.3 取字典的值
            • 9.4 取嵌套于列表中字典的值
          • 10. 学生信息管理系统
          • 11. 初识cookie
          • 12. 基于cookie的登录验证
          • 13. django操作cookie
          • 14. 设置cookie的签名
          • 18. 三大web框架的区别与联系
          • 19. Django非主流操作到主流操作
          • 20. Django程序目录
          • 21. 路由系统
            • 1. 静态路由
            • 2. 动态路由
            • 3. 路由分发
            • 4. 反向生成路由
          • 22. ORM
            • 1. ORM的功能
            • 2. 数据库设置
            • 3. makemigrations和migrate
            • 4. ORM创建数据表
            • 5. ORM单表CURD
          • 23. CBV
            • 1. CBV的引入
            • 2. CBV的应用
            • 3. dispatch方法
            • 4. CBV中添加装饰器
          • 24. 内置分页
            • 1. 后台分页逻辑
            • 2. 前台分页展示
          • 25. 自定义分页
            • 1. 自定义分页效果
            • 2. 自定义分页的实现
            • 3. 分页插件
          • 26. 解决跨站脚本攻击XSS
            • 1. Django处理XSS攻击
            • 2. 前端标记字符串
            • 3. 后端标记字符串
          • 27. 中间件
            • 1. 中间件执行流程
            • 2. 中间件原理浅析
            • 3. 中间件的应用
          • 28. Form组件
            • 1. Form组件的功能
            • 2. 数据校验
            • 3. 常用字段和参数
            • 4. 保留上次输入内容

1. 初识web框架
1.1 http协议

http协议是无状态,短连接的。客户端连接服务器,发送请求,服务器响应请求后断开连接。

1.2 socket简介

所有的网络请求都是基于socket,浏览器是socket客户端,网站是socket服务端。

1.3 socket服务端概述

根据url的不同返回给用户不同的内容,使用路由系统,路由系统是url与函数的对应关系。返回给用户的内容本质是字符串,基本上返回的内容是动态的,所以需要使用到模板渲染。模板渲染实际上是把html充当模板,自己创造任意数据替换模板中的特殊字符,比如替换特殊字符为数据库中的数据。

1.4 自己写web框架
  • 静态应用
# coding:utf-8
import socket

def f1(request):
    '''
    处理用户请求,并返回相应的内容
    :param request:用户请求的所有信息
    :return:返回相应的内容
    '''
    return b'f1'

def f2(request):
    '''
    处理用户请求,并返回相应的内容
    :param request:
    :return:
    '''
    f = open('index.html', 'rb')
    data = f.read()
    f.close()
    return data


def f3(request):
    '''
    处理用户请求,并返回相应的内容
    '''
    f = open('news.html', 'rb')
    data = f.read()
    f.close()
    return data

routers = [
    ('/user', f1),
    ('/', f2)
]

def run():
    sock = socket.socket()
    sock.bind(('127.0.0.1', 8080))
    sock.listen(5)

    while True:
        conn, addr = sock.accept()
        '''
        有用户来连接,
        获取用户发送的数据
        '''
        data = conn.recv(8096)
        print(data)
        '''请求头:
        GET / HTTP/1.1
        Host: 127.0.0.1:8080
        Connection: keep-alive
        Upgrade-Insecure-Requests: 1\
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
        Purpose: prefetch
        Accept-Encoding: gzip, deflate, 
        Accept-Language: zh-CN,zh;q=0.9
        '''
        # 解析请求头,目标:获取请求头中的url,并根据url向服务端发送请求
        data = str(data, encoding='utf-8')

        headers, bodys = data.split('\r\n\r\n')
        headers_list = headers.split('\r\n')
        methods, url, protocal = headers_list[0].split(' ')
        func_name = None
        for item in routers:
            if item[0] == url:
                func_name = item[1]
                break
        if func_name:
            response = func_name(data)
        else:
            response = '404'

        # if url == '/user':
        #     conn.send(b'user page')
        # else:
        #     conn.send(b'404 is not found!')
        # conn.send(b"HTTP/1.1 200 OK\r\n\r\n")  # 响应头
        # conn.send(b"hello thanlon!")  # 相应体
        conn.close()

if __name__ == '__main__':
    run()
  • 动态应用示例一
# coding:utf-8
import socket

def f1(request):
    '''
    处理用户请求,并动态返回相应的内容
    :param request:
    :return:
    '''
    f = open('news.html', 'r', encoding='utf-8')
    data = f.read()
    f.close()
    import time
    ctime = time.time()
    data = data.replace('%', str(ctime))
    return bytes(data, encoding='utf-8')

routers = [
    ('/user', f1),
]

def run():
    sock = socket.socket()
    sock.bind(('127.0.0.1', 8080))
    sock.listen(5)

    while True:
        conn, addr = sock.accept()
        '''
        有用户来连接,
        获取用户发送的数据
        '''
        data = conn.recv(8096)
        print(data)
        '''请求头:
        GET / HTTP/1.1
        Host: 127.0.0.1:8080
        Connection: keep-alive
        Upgrade-Insecure-Requests: 1\
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
        Purpose: prefetch
        Accept-Encoding: gzip, deflate, 
        Accept-Language: zh-CN,zh;q=0.9
        '''
        # 解析请求头,目标:获取请求头中的url,并根据url向服务端发送请求
        data = str(data, encoding='utf-8')

        headers, bodys = data.split('\r\n\r\n')
        headers_list = headers.split('\r\n')
        methods, url, protocal = headers_list[0].split(' ')
        func_name = None
        for item in routers:
            if item[0] == url:
                func_name = item[1]
                break
        if func_name:
            response = func_name(data)
        else:
            response = '404'

        # if url == '/user':
        #     conn.send(b'user page')
        # else:
        #     conn.send(b'404 is not found!')
        # conn.send(b"HTTP/1.1 200 OK\r\n\r\n")  # 响应头
        # conn.send(b"hello thanlon!")  # 相应体
        conn.close()

if __name__ == '__main__':
    run()
  • 动态应用示例二
# coding:utf-8
import socket

def f1(request):
    import pymysql
    # 创建连接
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='wwwnxl', db='test')
    # 创建游标
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    # 执行sql语句,并返回受影响的行数
    cursor.execute("select id,name,passwd from userinfo")
    user_list = cursor.fetchall()
    cursor.close()
    conn.close()
    # print(user_list)
    content_list = []
    for row in user_list:
        tp = '<tr>%s</tr><tr>%s</tr><tr>%s</tr>' % (row['id'], row['name'], row['passwd'],)
        content_list.append(tp)
    content = ''.join(content_list)
    f = open('userlist.html', 'r', encoding='utf-8')
    template = f.read()
    f.close()
    data = template.replace('{{content}}', content)
    print(data)
    return bytes(data, encoding='utf-8')

routers = [
    ('/user', f1),
]

def run():
    sock = socket.socket()
    sock.bind(('127.0.0.1', 8080))
    sock.listen(5)

    while True:
        conn, addr = sock.accept()
        '''
        有用户来连接,
        获取用户发送的数据
        '''
        data = conn.recv(8096)
        # print(data)
        '''请求头:
        GET / HTTP/1.1
        Host: 127.0.0.1:8080
        Connection: keep-alive
        Upgrade-Insecure-Requests: 1\
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
        Purpose: prefetch
        Accept-Encoding: gzip, deflate, 
        Accept-Language: zh-CN,zh;q=0.9
        '''
        # 解析请求头,目标:获取请求头中的url,并根据url向服务端发送请求
        data = str(data, encoding='utf-8')

        headers, bodys = data.split('\r\n\r\n')
        headers_list = headers.split('\r\n')
        methods, url, protocal = headers_list[0].split(' ')
        func_name = None
        for item in routers:
            if item[0] == url:
                func_name = item[1]
                break
        if func_name:
            response = func_name(data)
        else:
            response = b'404'
        conn.send(response)
        conn.close()

if __name__ == '__main__':
    run()
1.5 web框架的分类

为了方便开发者开发web应用,web框架应用而生。有的web框架帮助开发者构建好了socket服务端,有的web框架帮助开发者写好了模板渲染。总之,借助web框架可以减轻了开发者的工作量。flask框架只有路由系统,没有socket服务端和模板引擎,socket服务端使用是python第三方模块,如wsgiref。模板引擎使用的也是第三方模块jinjia2。django框架有路由系统、模板引擎,但是没有socket服务端,socket服务端使用的是python的第三方内置模块wsgiref,wsgiref把请求交给django做处理。另外,还有一种叫Tornado的框架,Tornado框架包含socket服务端、路由系统、模板引擎。可以将web框架这样分类,django框架和其它框架。因为django框架提供了很多特殊的功能,如缓存、分布式。其它框架是轻量级的web框架。

2. 初识django

安装django:pip3 install django
创建django程序:django-admin startproject 项目名称
运行django程序:python manager.py runserver 127.0.0.1:8080(如果不指定,默认运行在8000端口)

3. django程序目录

manager.py:对当前django程序所有操作可以基于python manager.py runserver

settings.py:django配置文件

url.py:路由系统,url->函数

wsgi.py:用于定义django使用什么socket服务端,如wsgiref,uwsgi(wsgiref性能比较低)

4. 第一个django请求

usr.py:

from django.shortcuts import HttpResponse
# 处理请求的函数
def login(request):  #
    '''
    处理用户请求,返回相响应结果
    :param request:用户请求的相关信息(不是字节,是对象)
    :return:
    '''
    pass
    return HttpResponse('login!')
    
# url
urlpatterns = [
    # path('admin/', admin.site.urls),
    path('login/', login),
]
5. 静态文件以及模板的配置
5.1 静态文件路径的配置

创建静态文件目录也需要配置:

修改settings.py:

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject/en/2.2/howto/static-files/
'''
只要是使用/static/的前缀,就在这个目录(static目录)下找静态文件
'''
STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)
5.2 HttpResponse与render函数
  • 返回字符串
    return HttpResponse(‘login!’)
    return HttpResponse(’< input type=“text”>’)
  • 返回模板
    render函数默认是在“templates”中自动找文件,读取文件内容后返回给用户。
    return render(request, ‘xxx.html’)
    render函数本质上是调用HttpResponse。
5.3 模板路径的配置

模板名称需要与配置文件设定的模板名字一致,

6. 创建程序步骤
6.1 创建project

django-admin startproject project名,也可以在pycharm中选择Django,创建project

6.2 配置模板路径

创建templates目录,然后修改配置文件:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]#BASE_DIR指当前路径
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
6.3 配置静态文件目录

创建static目录,然后修改配置文件:

'''
只要是使用/static/的前缀,就在这个目录下找静态文件
'''
STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)
6.4 额外配置

django.middleware.csrf.CsrfViewMiddleware注释掉:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middlewaremon.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
7. 用户登录示例

urls.py:

from django.contrib import admin
from django.urls import path

from django.shortcuts import HttpResponse, render, redirect

def login(request):  #
    '''
    处理用户请求,返回相响应结果
    :param request:用户请求的相关信息(不是字节,是对象)
    :return:
    '''
    if request.method == 'GET':
        return render(request, 'login.html')  # 本质上是调用HttpResponse,自动找到login.html文件,读取内容并返回给用户
    else:
        # print(request.POST)  # 用户POST提交的数据(请求体)<QueryDict: {'name': ['thanlon'], 'pwd': ['123']}>
        # user = request.POST['username']#直接索引,如果没有username会报错
        username = request.POST.get('username')  # 如果没有username不会报错,返回None
        pwd = request.POST.get('pwd')  # 如果没有username不会报错,返回None
        if username == 'thanlon' and pwd == '123456':
            return redirect('https://www.blueflags')
        else:
            return render(request, 'login.html', {'msg': '用户名或密码错误!'})  # django内部做模板渲染
            
urlpatterns = [
    # path('admin/', admin.site.urls),
    path('login/', login),
]

login.html:

<form action="/login/" method="POST" name="loginForm">
    <div class="form-group">
        <label for="name">用户名</label> <input type="text" class="form-control" name="username" placeholder="请输入用户名">
    </div>
    <div class="form-group">
        <label for="">密码</label> <input type="password" class="form-control" name="pwd" placeholder="请输入密码">
        <div style="color: red;font-weight: bold">{{ msg }}</div>
    </div>
    <button type="submit" class="btn btn-primary" onclick="return checkForm()">登录</button>
</form>

登录效果:

8. request.GET与 request.POST
8.1 request.GET

request.GET是从请求头的url中获取值

8.2 request.POST

request.POST是从请求体中获取值。GET请求时,只有request.GET可以获取值。但POST请求时,request.POST和request.GET都可能获取值。

<form action="/login/?page=1" method="POST" name="loginForm">……

可以通过request.GET获取url中的page

9. django模板语言特殊标记(重点)
9.1 取字符串的值
def index(request):
    return render(request, 'index/index.html', {'username': '一问奈何'})
<p>{{ username }}</p> # 一问奈何
9.2 取列表的值
def index(request):
    # return render(request, 'index/index.html', {'username': '一问奈何'})
    return render(request, 'index/index.html', {'username': ['thanlon','Kiku']})
  • 直接通过索引
{#<p>{{ username }}</p>#}
{{ username }}
{{ username.0 }}
{{ username.1 }}
  • 通过循环遍历
{% for item in username %}
    {{ item }}
{% endfor %}

9.3 取字典的值
def index(request):
    return render(request, 'index/index.html', {
        'user_dict': {'name': '一问奈何', 'age': 23}
    })
<body>
	{{ user_dict.name }}
	{{ user_dict.age }}
<body>

9.4 取嵌套于列表中字典的值
def index(request):
    return render(request, 'index/index.html', {
        'user_list_dict': [
            {'id': 1, 'name': 'thanlon'},
            {'id': 2, 'name': 'kuku'},
        ]
    })
  • 通过索引取值
{{ user_list_dict.0.id}}--{{ user_list_dict.0.name}}
{{ user_list_dict.1.id}}--{{ user_list_dict.0.name}}
  • 通过循环取值
{% for row in user_list_dict %}
    {{ row.id }}--{{ row.name }}
{% endfor %}

10. 学生信息管理系统

Django实战项目 ------ 学生信息管理系统

11. 初识cookie

cookie是保存在浏览器端的特殊"键值对",服务端可以向浏览器端写cookie,客户端每次发送请求时需要携带本地cookie,此时cookie放到请求头中。服务端回写cookie会放到响应头中,render函数和redirect以及HttpResponse函数都可以在向服务端浏览器返回响应体的时候回写cookie。cookie的应用:可以用于投票,也可以用于用户登录

12. 基于cookie的登录验证

views.py:

def classes(request):
    '''
    查询班级id、班级名称
    :param request:对象相关的数据
    :return:渲染后的模板
    '''
    ticket = request.COOKIES.get('ticket')
    if not ticket:
        return redirect('/login/')
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123456', db='test')
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    cursor.execute("select id,title from class")
    classes_list = cursor.fetchall()
    cursor.close()
    conn.close()
    return render(request, 'classes.html', {'classes_list': classes_list})
    
def login(request):
    '''
    登录
    :param request:
    :return:
    '''
    username = request.POST.get('username')
    pwd = request.POST.get('pwd')
    if request.method == 'POST':
        if username == 'thanlon' and pwd == '123456':
            obj = redirect('/classes/')
            # obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM')
            return obj
    else:
        return render(request, 'login.html')

读所有的cookie使用request.COOKIES

13. django操作cookie

(1) 设置超时时间有两种方式,下面是设置cookie的函数源码:

def set_cookie(self, key, value='', max_age=None, expires=None, path='/',domain=None, secure=False, httponly=False, samesite=None):

第一种方式:

def login(request):
    username = request.POST.get('username')
    pwd = request.POST.get('pwd')
    if request.method == 'POST':
        if username == 'thanlon' and pwd == '123456':
            obj = redirect('/classes/')
            obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM',max_age=10)
            return obj
    else:
        return render(request, 'login.html')

max_age=10表示超时时间是10秒,10秒过后不管客户端有没有操作,都会跳转到登录页面。

第二种方式:

def login(request):
    username = request.POST.get('username')
    pwd = request.POST.get('pwd')
    if request.method == 'POST':
        if username == 'thanlon' and pwd == '123456':
            obj = redirect('/classes/')
            from datetime import timedelta
            import datetime
            ct = datetime.datetime.utcnow()
            v = timedelta(seconds=10)  # 加10s
            value = ct + v
            obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM', expires=value)
            return obj
    else:
        return render(request, 'login.html')

使用第一种方式和第二种方式效果是一样的,但推荐使用第一种方式。因为第一种方式设置max_age后,内部会转换成datatime类型,把expires也设置一遍,所以推荐使用第一种方式。

(2) 设置在某个url上读取cookie

下面设置只有/class/的url才可以读取到cookie:

obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM', path='/class/')

默认path=/,表示所有url都可以读取到cookie

(3) 设置不同的域名访问cookie

obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM',domain=)

默认是当前域名,当然也可以设置子域名,表示只有子域名才可以获取到,一般用不到。

(4) 设置安全相关

obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM',httponly=False,secure=False)

httponly表示写的cookie只能通过http来回发送,如果为True,cookie是写到浏览器上了,但是通过javascript找不到,没有权限对其进行操作,当然可以通过抓包获取。secure与https相关的,网站如果是https的,要把secure设置为True

14. 设置cookie的签名

对cookie进行签名是指对cookie的值进行操作,使用set_signed_cookie方法。Django中默认使用的是时间戳来签名,当然也可以自定制签名,只需要自己定义一个类继承TimestampSigner,然后重写sign和unsign方法。最后在配置文件文件中指定自己定义的类:SIGNING_BACKEND=Python文件名.类名就可以了。下面是对cookie进行签名的案例:

def classes(request):
    ticket = request.get_signed_cookie('ticket', salt='123456')
    if not ticket:
        return redirect('/login/')
     ……
 def login(request):
    username = request.POST.get('username')
    pwd = request.POST.get('pwd')
    if request.method == 'POST':
        if username == 'thanlon' and pwd == '123456':
            obj = redirect('/classes/')
            obj.set_signed_cookie('ticket', 'thanlon', salt='123456')
            return obj
    else:
        return render(request, 'login.html')
18. 三大web框架的区别与联系

Django、Flask和Tornado框架都有路由、视图和模板,但是Tornado的模板是自己写的,Flask的模板不是自己写的,采用的是第三方组建(Jinjia2)。Django自带ORM,其它两个框架中没有。Django中在写sql的时候不应该再使用pymysql,而应该使用ORM。其实ORM只是做了类和对象的映射,类对应表,对象对应数据行。Django中把类和对象转换成sql语句,但是本质上还是需要pymysql去连接数据库。对于Flask框架,可以使用pymysql,也可以使用SqlAchemy,Tornado也是一样的。其实,对于Django,一般使用的是ORM,但也可以使用pymysql,但是这是非主流的。对于非常复杂的sql语句,ORM完成不了,只能写原生sql。Django中ORM也支持原生sql,所以没必要使用pymysql。

19. Django非主流操作到主流操作

以上Django的使用中至少有两点是非主流的:第一点体现在创建app上;第二点体现在数据库操作上。以后,我们将使用Django的主流操作来进行开发。在创建app方面,使用命令的方式来创建。在操作数据库方面,不再使用pymysql或者SqlAchemy,而是使用ORM。

20. Django程序目录

创建一个完整的Django程序:

# 创建Django工程
thanlon@thanlon-master:~$ django-admin startproject mysite
# 进入工程目录
thanlon@thanlon-master:~$ cd mysite/
# 创建Django应用
thanlon@thanlon-master:~/mysite$ python3 manage.py startapp app01

主要目录介绍:

migrations:数据库相关
admin.py:Django自带后台管理相关配置
models.py:写类,根据类创建数据库表	
test.py:单元测试
views.py:写试图函数的,用于做业务处理(不一定只有一个views.py,可以在app目录下单独创建一个views的目录用存放不同的视图函数)
21. 路由系统
1. 静态路由

路由就是用户访问网站资源的路径,在Django中路由要和视图函数对应起来,路由和视图的对应关系有一对一和多对一。静态路由是比较常见的路由,不使用尖括号和正则表达式定义的路由,与视图函数是一对一关系。如默认的 admin/ 和我们自己定义的 /login/edit/1/

urlpatterns = [
	# 匹配admin/
    path('admin/', admin.site.urls),
    # 匹配login/
    path('login/', views.login),
    # 匹配edit/1
    path('edit/1', views.add_user),
    # 匹配edit/1/,和不加/是有区别的
    path('edit/1/', views.add_user),
]

注意:最后加不加 / 匹配到的路径是不同的。

2. 动态路由

使用尖括号定义的路由是动态的,可以定义传入变量并且指定变量的类型,其它类型还有字符串,用 str 表示:

urlpatterns = [
	# 匹配任意整型,视图函数中必须用名为id的变量接收,当然也可以使用万能的**kwargs来接收,转换成字典后再找到索引id的值即为传入的参数
    path('add-user/<int:id>', views.add_user),
    # 匹配任意整型,和不加/是有区别的
    path('add-user/<int:id>/', views.add_user),
    # 匹配任意字符串类型
    path('add-user/<str:str_name>', views.add_user),
    # 匹配任意字符串类型,和不加/是有区别的
    path('add-user/<str:str_name>/', views.add_user),
]

PS:尖括号的方式下也可以使用转换器,可以自定义转换器,暂时用不到就不说。

使用正则表达式定义的路由也是动态的,同样可以实现多个路由对应一个视图函数。Django3.0版本使用正则表达式定义路由需要使用到 re_path

from django.urls import path, re_path

urlpatterns = [
    # 如果只想匹配add-user,可以在最后加上终止符号$
    re_path('^add-user$', views.add_user),
    # 可以匹配到add-user/
    re_path('^add-user/', views.add_user),
    # 可以匹配到add-user,也可以匹配到add-user/
    re_path('^add-user', views.add_user),
    # 匹配任意一位数字
    re_path('^add-user/(\d)/', views.add_user),
    # 匹配任意数字,数字会被当作传参传递,add_user方法中必须有形式参数接收
    re_path('^add-user/(\d+)/', views.add_user),
    # 匹配任意一个字符(大小写),字符会被当作传参传递,add_user方法中必须有形式参数接收
    re_path('^add-user/([A-Za-z])/', views.add_user),
    # 伪静态匹配数字.html,数字部分会被当作传参传递,add_user方法中必须有形式参数接收
    re_path('^add-user/(\d+).html$', views.add_user),
    # 伪静态匹配任意字符串.html,字符串中也可以只有一个字符,字符串部分会被当作传参传递,add_user方法中必须有形式参数接收
    re_path('^add-user/(\w+).html$', views.add_user),
    # 位置传参,所以视图函数中形参必须有a和b接收,a、b的位置随意。注意如果是'^add-user/(\d+)/(?P<b>\d+),视图是不接收(\d+)部分的参数。
    re_path('^add-user/(?P<a>\d+)/(?P<b>\d+)', views.add_user),
]

注意:Django中的路径匹配是顺序匹配。也就是从上到下匹配,上面的路由匹配成功就停止匹配下面的路由。

3. 路由分发

多个app时可以使用路由分发,可以实现不同应用使用不同的路由,方便管理路由。可以在app01目录下新建 urls.py 文件用作app01应用的路由:

urlpatterns = [
    path('login/', views.login),
]

在主要的 urls.py 文件中要导入include函数才能实现路由的分发:

from django.urls import path, include

urlpatterns = [
	# 注意路由后面必须加上/
    path('app01/', include('app01.urls'))
]

下面就可以通过 http://127.0.0.1:8000/app01/login/ 来访问app01下的视图函数login,这样就实现了路由的分发。

4. 反向生成路由

在写路由的时候,给路由命个名字,以后可使用这个名字找到这个路由,在存储用户路由信息的信息也可以存储路由的名字,节约空间,方便使用。可以在Python代码中反向生成路由,也可以在模板中反向生成路由

① 在Python代码中使用。需要设置路由、业务逻辑:

urls.py:

urlpatterns = [
    path('index/', views.index, name="index"),
]

views.py:

def index(request, id):
    v = reverse('index_id', kwargs={'id': id})
    print(v)  # /index/1/
    return HttpResponse('成功执行!')

② 在模板中使用。需要设置路由、写视图逻辑和模板:

urls.py:

urlpatterns = [
    path('index/', views.index, name="index"),
]

views.py:

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

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% url 'index' %} # 
</body>
</html>

如果路由中没有传值,模板中可以使用上面的方式。但是,如果路由中有传值,如:

urls.py:

urlpatterns = [
    path('index/<int:id>/', views.index, name="index"),
]

views.py:

def index(request,id):
    # v = reverse('index_id', kwargs={'id': id})
    # print(v)  # /index/1/
    # print(id)
    # 把id传到模板中
    return render(request,'index.html',{'id':id})

模板中反向生成路由的方式:

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% url 'index' id %}
</body>
</html>

注意:如果有多个参数,在id后面追加就可以,使用空格隔开,如:{% url 'index' id page %}

22. ORM
1. ORM的功能

Django的ORM可以操作数据库的表结构和数据行。操作表结构体现在创建表、修改表和删除表,操作数据行则体现在对表的CURD操作,也就是增删改查。Django的ORM本身不支持连接数据库,实质上使用的是第三方工具如 pymysql 或者是 sqlalchemy 来连接数据库

2. 数据库设置

Django默认使用的是SQLite,也是Django中自带的数据库,但是我们一般不会使用。我们通常会使用SQLite之外的数据库,如mysql、oracle、postgresql等。所以,需要在Django中修改默认使用的SQLite为其它的数据库。在 settings.py 中找到 DATABASES 变量:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

如果使用的是mysql数据,修改为:

DATABASES = {
    'default': {
        # 'ENGINE': 'django.db.backends.sqlite3',
        'ENGINE': 'django.db.backends.mysql',
        'NAME': '数据库的名称',
        'USER': '用户名',
        'PASSWORD': '密码',
        'HOST': '数据库地址',
        'PORT': '数据库端口'
    }
}

设置ORM使用 pymysql 连接数据库,只需要在与 settings.py 同级目录下的 __init__.py 这个空文件中写入:

import pymysql
pymysql.install_as_MySQLdb()

配置文件 setting.py 中的 INSTALLED_APPS 下包含了在Django实例中激活的所有Django应用程序的名称,这些应用程序都是随Django一起提供的:

django.contrib.admin:管理站点

django.contrib.auth:认证系统

django.contrib.contenttypes :内容类型的框架

django.contrib.sessions:会话框架

django.contrib.messages:消息传递框架

django.contrib.staticfiles:用于管理静态文件的框架

这些应用中的一些应用程序 至少使用一个数据库表,因此我们需要在数据库中创建表之后才能使用它们,执行下面的命令来创建:

(django_test) thanlon@thanlon:~/projects/PycharmProjects/django_test$ python manage.py migrate

可以在数据库看到创建的数据表:

mysql> show tables;
+----------------------------+
| Tables_in_django_test      |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
+----------------------------+
10 rows in set (0.00 sec)

migrate命令获取所有尚未应用的迁移(Django使用名为的特殊表在数据库中跟踪应用了哪些迁移django_migrations),并针对数据库运行它们。本质上,将你对模型所做的更改与数据库。迁移功能非常强大,可以在开发项目时随时间更改模型,而无需删除数据库或表并创建新的模型。它专门用于实时升级数据库而不会丢失数据。

3. makemigrations和migrate

接下来需要到模型中创建类和字段,创建完成后需要先使用 python manage.py makemigrations 命令来为数据表的更改创建迁移,然后使用 python manage.py migrate 将更改应用到数据库。之所以使用单独的命令来进行和应用迁移,是因为你会将迁移提交到版本控制系统,并随应用程序一起交付。这些单独的命令不仅使您的开发更加容易,而且还可以被其他开发人员和生产环境使用

4. ORM创建数据表

models.py 文件中写模型,这里分别创建员工表和部门表:

from django.db import models

class Depart(models.Model):
    """
    创建部门表
    """
    title = models.CharField(max_length=32, unique=True)

class EmpInfo(models.Model):
    """
    创建员工信息表
    """
    # 主键默认是id,写与不写名字都是id,类型是int且为主键和自动递增
    nid = models.AutoField(primary_key=True)
    username = models.CharField(max_length=32)
    pwd = models.CharField(max_length=64)
    # 'max_length' is ignored when used with IntegerField.
    age = models.IntegerField()
    # 会自动创建dp_id字段
    dp = models.ForeignKey("Depart", on_delete=models.CASCADE, null=True)

执行创建迁移和应用更改的命令后会创建两张表:

部门表:
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int         | NO   | PRI | NULL    | auto_increment |
| title | varchar(32) | NO   | UNI | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
员工表:
+----------+-------------+------+-----+---------+----------------+
| Field    | Type        | Null | Key | Default | Extra          |
+----------+-------------+------+-----+---------+----------------+
| nid      | int         | NO   | PRI | NULL    | auto_increment |
| username | varchar(32) | NO   |     | NULL    |                |
| pwd      | varchar(64) | NO   |     | NULL    |                |
| age      | int         | NO   |     | NULL    |                |
| dp_id    | int         | YES  | MUL | NULL    |                |
+----------+-------------+------+-----+---------+----------------+

生成迁移记录和应用迁移有问题可以在评论区留言!

5. ORM单表CURD

数据的增加:

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

# Create your views here.
def index(request):
    models.Depart.objects.create(title='技术部')
    models.Depart.objects.create(title='运维部')
    models.EmpInfo.objects.create(username='Thanlon', pwd='123456', age=23, dp_id=3)
    return render(request, 'index.html')
部门表:
+----+-----------+
| id | title     |
+----+-----------+
|  2 | 技术部    |
|  1 | 运维部    |
+----+-----------+
员工表:
+-----+----------+--------+-----+-------+
| nid | username | pwd    | age | dp_id |
+-----+----------+--------+-----+-------+
|   1 | Thanlon  | 123456 |  23 |     1 |
+-----+----------+--------+-----+-------+

数据的查询:

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

def index(request):
    # 查询所有部门信息
    dp_list = models.Depart.objects.all()
    print(dp_list)  # <QuerySet [<Depart: Depart object (2)>, <Depart: Depart object (1)>]>
    for item in dp_list:
        print(item.id, item.title)
        """
        2 技术部
        1 运维部
        """
    # 查询满足条件的数据行
    # emp_list_id = models.EmpInfo.objects.filter(name='thanlon').first()
    # dp_list_id = models.Depart.objects.filter(id=1)
    # dp_list_id = models.Depart.objects.filter(id__lt=2)
    dp_list_id = models.Depart.objects.filter(id__gt=1)
    for item in dp_list_id:
        print(item.id, item.title)  # 2 技术部
    return render(request, 'index.html')

数据的删除:

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

# Create your views here.
def index(request):
    # 删除id=2的部门
    models.Depart.objects.filter(id=2).delete()
    return render(request, 'index.html')
+----+-----------+
| id | title     |
+----+-----------+
|  1 | 运维部    |
+----+-----------+

数据表的修改:

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

# Create your views here.
def index(request):
    # 修改id=1的部门信息
    models.Depart.objects.filter(id=1).update(title="人事部")
    return render(request, 'index.html')
+----+-----------+
| id | title     |
+----+-----------+
|  1 | 人事部    |
+----+-----------+
23. CBV
1. CBV的引入

      FBV 即 Function-Based-View,是 通过函数来处理请求。CBV 即 Class-Based-View,是 通过类来处理请求。相对于传统的FBV方式,CBV的优点体现在:① 提高了代码的复用性,可以使用面向对象的技术,比如Mixin(多继承);② 可以用不同的函数针对不同的HTTP方法处理,而不是通过很多if判断,提高代码可读性。

2. CBV的应用

      登录功能的实现,定义一个Login类用于处理用户的登录请求。路由URL匹配成功后,开始匹配请求的方法method。如果是GET请求,执行Login类的get方法,获取到登录页面。如果是POST请求,将登录表单的数据发送到后台验证。无须使用if语句来判断请求的方法是什么:

urls.py

from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('login.html/', views.Login.as_view()),
]

views.py

class Login(View):
    """
    get:查询
    post:创建
    put:更新
    delete:删除
    """

    def get(self, request):
        """
        请求的方法是get时执行
        :param request:
        :return:
        """
        return render(request, 'login.html')

    def post(self, request):
        """
        请求的方法是post时执行
        :param request:
        :return:
        """
        username = request.POST.get('username')
        print(username)
        return HttpResponse('登录成功!')

login.html

...
<div class="container">
    <div class="row">
        <div class="col-md-4 col-md-offset-4">
            <div class="panel panel-danger">
                <div class="panel-heading" style="font-weight: bold">用户登录
                    <button type="button" class="close" id="close_add_modal"><span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="panel-body">
                    <form method="post">
                        <div class="form-group">
                            <label for="">用户名</label>
                            <input type="text" name="username" class="form-control" id="name" placeholder="请输入用户名"
                                   autofocus>
                        </div>
                        <div class="form-group">
                            <label for="">密码</label>
                            <input type="password" name="pwd" class="form-control" id="pwd" placeholder="请输入密码">
                        </div>
                        <input type="submit" class="btn" value="登录" id="add_student" style="background: #f2dede;">
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
...

表单只支持POST和GET方式提交!AJAX支持所有的请求!

3. dispatch方法

请求的方法有很多,如果使用 if 来一一判断就要写很多判断语句。但是,实际上使用的并不是条件判断,而是 反射。用户发送请求,URL会匹配到类。找类中的方法时,使用getattr方法判断请求的方法是否对应类中的方法。查看源码的时候发现在执行get和post方法之前,还执行了dispatch方法。dispatch方法里执行就是这个操作:

def dispatch(self, request, *args, **kwargs):
    # Try to dispatch to the right method; if a method doesn't exist,
    # defer to the error handler. Also defer to the error handler if the
    # request method isn't on the approved list.
    if request.method.lower() in self.http_method_names:
    	# 在当前对象中找用户提交的方法request.method.lower(),handler就是找到的方法名
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    else:
        handler = self.http_method_not_allowed
    # handler加括号就是执行这些方法
    return handler(request, *args, **kwargs)

dispatch方法的功能类似 装饰器 的功能,在执行请求的方法之前和之后做其它操作:

class Login(View):

    def dispatch(self, request, *args, **kwargs):
        print('Before')
        obj = super(Login, self).dispatch(request, *args, **kwargs)
        print('After')
        return obj

    def get(self, request):
        print('get')
        return render(request, 'login.html')

    def post(self, request):
        print('post')
        return HttpResponse('登录成功!')

执行记录:

[06 / Jun / 2020 01: 32:33] "GET /login.html/ HTTP/1.1" 200 1813
Before
get
After
[06 / Jun / 2020 01: 32:38] "GET /login.html/ HTTP/1.1" 200 1813
[06 / Jun / 2020 01: 32:41] "POST /login.html/ HTTP/1.1" 200 15
Before
post
After

如果要对get和post等请求方法做批量操作时,没必要在每个方法中都写一遍,只要在dispatch写一遍就可以了

4. CBV中添加装饰器

CBV中添加装饰器有两种方法,分别是在指定的方法中添加装饰器和在类上添加装饰器。在方法中添加装饰器:

views.py:

from django.views import View
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator

def wrapper(func):
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

class Login(View):
    @method_decorator(csrf_protect)
    def get(self, request):
        pass

    @method_decorator(wrapper)
    def post(self, request):
        pass

在类上添加装饰器不可以直接把装饰器放在类上,这样时错误的,如:

from django.views import View
from django.views.decorators.csrf import csrf_protect

@csrf_protect
class Login(View):
    def get(self, request):
        pass

    def post(self, request):
        pass

在类上加装饰器,需要用到 @method_decorator:

from django.views import View
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator

# 对类中所有的方法添加csrf_protect装饰器
@method_decorator(csrf_protect)
class Login(View):
    def get(self, request):
        pass

    def post(self, request):
        pass

还可以通过类方法名称来指定方法添加装饰器:

from django.views import View
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator

@method_decorator(csrf_protect, name='get')
@method_decorator(csrf_protect, name='post')
class Login(View):
    def get(self, request):
        pass

    def post(self, request):
        pass

csrf的装饰器在使用CBV的情况下不能加到类方法上!

以上两种方法都具有为所有的函数加装饰器的功能,不过如果 想为所有类方法添加装饰器显得比较麻烦! 这里还有一种方法可以 为所有的方法加上某个装饰器,那就是把装饰器加到dispatch方法上:

from django.views import View
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator

def wrapper(func):
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@method_decorator(csrf_protect, name='dispatch')
@method_decorator(wrapper, name='dispatch')
class Login(View):
    def get(self, request):
        pass

    def post(self, request):
        pass
24. 内置分页
1. 后台分页逻辑
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage

def student(request):
    # 获取所有的结果集
    emp_list = models.StuInfo.objects.all()  # <QuerySet [<StuInfo: StuInfo object (1)>]>
    """
    emp_list也可以是使用pymysql拿到的列表:student_list = cursor.fetchall()
    [{'id': 1, 'name': '超越'}, {'id': 2, 'name': '奈何'}] <class 'list'>
    """
    # 创建Paginator对象,可直接设置每页显示的条目数量
    pagenator = Paginator(emp_list, 3) 
    """
    pagenator.page(1):显示第1页
    
    pagenator.per_page:每页显示的条目数量
    pagenator.count:数据总个数
    pagenator.num_pages:总页数
    pagenator.page_range:总页数的索引范围,如(1,10)
    """
    # 获取传入的页码
    current_page = request.GET.get('page')
    try:
        posts = pagenator.page(current_page)
    except PageNotAnInteger as e:
        posts = pagenator.page(1)  # 如果传入的页码不是整型,则默认显示第一页,如传入的是/student.html/?page=abc,/student.html/
	"""
	posts.has_next:是否有下一页
	posts.next_page_number:下一页页码
	posts.has_previous:是否有上一页
	posts.previous_page_number:上一页页码
	posts.object_list:分页之后的数据列表
	
	posts.number:当前页
	posts.paginator:paginator对象
	"""
    # 如果传入的页码不是int类型,如传入的是/student.html/?page=abc
    except EmptyPage as e:
        posts = pagenator.page(1)  # 如果传入的页码是空页,则默认显示第一页,如传入的是/student.html/?page=-10
    return render(request, 'student.html', {'posts': posts})
2. 前台分页展示
<table class="table table-hover text-center" style="background: white;margin-bottom: 0">
    <tr>
        <th class="text-center">ID</th>
        <th class="text-center">姓名</th>
        <th class="text-center">年龄</th>
    </tr>
    {% for row in posts.object_list %}
        <tr>
            <td>{{ row.id }}</td>
            <td>{{ row.name }}</td>
            <td>{{ row.age }}</td>
        </tr>
    {% endfor %}
</table>
<nav aria-label="Page navigation" class="text-left">
    <ul class="pagination">
        {% if posts.has_previous %}
            <li>
                <a href="/student.html?page={{ posts.previous_page_number }}">上一页</a>
            </li>
        {% endif %}
        {% if posts.has_next %}
            <li>
                <a href="/student.html?page={{ posts.next_page_number }}">下一页</a>
            </li>
        {% endif %}
    </ul>
</nav>
25. 自定义分页
1. 自定义分页效果

可以实现查找 首页和最后一页,上一页和下一页,以及指定页

2. 自定义分页的实现

模型Model部分的代码:

models.py:

from django.db import models

class UserType(models.Model):
    """
    用户类型表
    """
    title = models.CharField(max_length=32)

class UserInfo(models.Model):
    """
    用户表
    """
    name = models.CharField(max_length=16)
    age = models.IntegerField()
    ut = models.ForeignKey('UserType', on_delete=models.CASCADE)

控制器Controller部分的代码:

vews.py:

from django.shortcuts import render, HttpResponse
from app01 import models
from utils.pagger import PageInfo


class PageInfo:
    def __init__(self, current_page, per_page, count_sum, base_url, show_page=11):
        """
        :param current_page:当前的页码
        :param per_page:每页显示的数据量
        :param count_sum:总的数据量
        """
        try:
            self.current_page = int(current_page)
        except Exception as e:
            self.current_page = 1
        self.per_page = per_page
        self.count_sum = count_sum
        a, b = divmod(count_sum, per_page)
        if b:
            a += 1
        # page_num:所有页码的数量
        self.sum_page_num = a
        self.show_page = show_page
        self.base_url = base_url

    def start(self):
        return (self.current_page - 1) * self.per_page

    def end(self):
        return self.current_page * self.per_page

    def pager(self):
        half = int((self.show_page - 1) / 2)
        # 如果总页数小于11
        if self.sum_page_num < self.show_page:
            begin = 1
            stop = self.sum_page_num + 1
        else:
            if self.current_page <= half:
                begin = 1
                stop = self.show_page + 1
            else:
                if self.current_page + half > self.sum_page_num:
                    begin = self.sum_page_num - self.show_page + 1
                    stop = self.sum_page_num + 1
                else:
                    begin = self.current_page - half
                    stop = self.current_page + half + 1
        page_num_list = []
        index_page = '<li><a href="%s?page=1"><span aria-hidden="true">首页</span></a></li>' % (self.base_url)
        page_num_list.append(index_page)
        if self.current_page <= 1:
            prev_page = '<li><a href="#"><span aria-hidden="true">&laquo;</span></a></li>'
        else:
            prev_page = '<li><a href="%s?page=%s"><span aria-hidden="true">&laquo;</span></a></li>' % (
                self.base_url, self.current_page - 1,)

        page_num_list.append(prev_page)
        for i in range(begin, stop):
            if i == self.current_page:
                page_num_list.append(
                    "<li class='active'><a class='active' href='%s?page=%s'>%s</a></li>" % (self.base_url, i, i))
            else:
                page_num_list.append("<li><a class='active' href='%s?page=%s'>%s</a></li>" % (self.base_url, i, i))
        if self.current_page >= self.sum_page_num:
            next_page = '<li><a href="#"><span aria-hidden="true">&raquo;</span></a></li>'
        else:
            next_page = '<li><a href="%s?page=%s"><span aria-hidden="true">&raquo;</span></a></li>' % (
                self.base_url, self.current_page + 1,)
        page_num_list.append(next_page)

        final_page = '<li><a href="%s?page=%s"><span aria-hidden="true"></span>最后一页</a></li>' % (
            self.base_url, self.sum_page_num)
        page_num_list.append(final_page)
        return ''.join(page_num_list)


def index(request):
    count_sum = models.UserInfo.objects.count()
    page_info = PageInfo(request.GET.get('page'), 1, count_sum, '/index.html')
    user_info = models.UserInfo.objects.all()[page_info.start():page_info.end()]
    return render(request, 'index.html', {'user_info': user_info, 'page_info': page_info})

视图View部分的代码:

...
<table class="table table-bordered">
    <tr>
        <th>用户名</th>
        <th>年龄</th>
    </tr>
    {% for item in user_info %}
        <tr>
            <td> {{ item.name }}</td>
            <td> {{ item.age }}</td>
        </tr>
    {% endfor %}
</table>
<nav aria-label="Page navigation">
    <ul class="pagination">
        {{ page_info.pager|safe }}
    </ul>
</nav>
...
3. 分页插件

可以把控制器部分的PageInfo类放在单独的Python文件中,可以复用。新建utils目录,然后将其放在utils目录下:

pager.py

class PageInfo:
    def __init__(self, current_page, per_page, count_sum, base_url, show_page=11):
        """
        :param current_page:当前的页码
        :param per_page:每页显示的数据量
        :param count_sum:总的数据量
        """
        try:
            self.current_page = int(current_page)
        except Exception as e:
            self.current_page = 1
        self.per_page = per_page
        self.count_sum = count_sum
        a, b = divmod(count_sum, per_page)
        if b:
            a += 1
        # page_num:所有页码的数量
        self.sum_page_num = a
        self.show_page = show_page
        self.base_url = base_url

    def start(self):
        return (self.current_page - 1) * self.per_page

    def end(self):
        return self.current_page * self.per_page

    def pager(self):
        half = int((self.show_page - 1) / 2)
        # 如果总页数小于11
        if self.sum_page_num < self.show_page:
            begin = 1
            stop = self.sum_page_num + 1
        else:
            if self.current_page <= half:
                begin = 1
                stop = self.show_page + 1
            else:
                if self.current_page + half > self.sum_page_num:
                    begin = self.sum_page_num - self.show_page + 1
                    stop = self.sum_page_num + 1
                else:
                    begin = self.current_page - half
                    stop = self.current_page + half + 1
        page_num_list = []
        index_page = '<li><a href="%s?page=1"><span aria-hidden="true">首页</span></a></li>' % (self.base_url)
        page_num_list.append(index_page)
        if self.current_page <= 1:
            prev_page = '<li><a href="#"><span aria-hidden="true">&laquo;</span></a></li>'
        else:
            prev_page = '<li><a href="%s?page=%s"><span aria-hidden="true">&laquo;</span></a></li>' % (
                self.base_url, self.current_page - 1,)

        page_num_list.append(prev_page)
        for i in range(begin, stop):
            if i == self.current_page:
                page_num_list.append(
                    "<li class='active'><a class='active' href='%s?page=%s'>%s</a></li>" % (self.base_url, i, i))
            else:
                page_num_list.append("<li><a class='active' href='%s?page=%s'>%s</a></li>" % (self.base_url, i, i))
        if self.current_page >= self.sum_page_num:
            next_page = '<li><a href="#"><span aria-hidden="true">&raquo;</span></a></li>'
        else:
            next_page = '<li><a href="%s?page=%s"><span aria-hidden="true">&raquo;</span></a></li>' % (
                self.base_url, self.current_page + 1,)
        page_num_list.append(next_page)

        final_page = '<li><a href="%s?page=%s"><span aria-hidden="true"></span>最后一页</a></li>' % (
            self.base_url, self.sum_page_num)
        page_num_list.append(final_page)
        return ''.join(page_num_list)
26. 解决跨站脚本攻击XSS
1. Django处理XSS攻击

Django本身默认可以处理XSS攻击,例如:

views.py:

def test(request):
    ret = '<script>alert("XSS")</script>'
    return render(request, 'test.html', {'ret': ret})

test.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{{ ret }}
</body>
</html>

并没有解析传入的字符串,当作js脚本来执行。

但是当传入到模板的数据是我们自己的并且是安全的,我们想要告诉系统这是安全的应该如何去做呢!有两种方法,分别是后端标记字符串和前端标记字符串。

2. 前端标记字符串

在模板上写 |safe 的方法可以标记字符串是安全的,可以去解析执行:

test.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{{ ret|safe }}
</body>
</html>

3. 后端标记字符串

后台把字符串标记成安全的需要在传入模板之前就操作,使用到的是 django.utils.safestring 模块中的 mark_safe 函数:

views.py:

def test(request):
    from django.utils.safestring import mark_safe
    ret = '<script>alert("XSS")</script>'
    ret = mark_safe(ret)
    return render(request, 'test.html', {'ret': ret})

test.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{{ ret }}
</body>
</html>

与前端标记一样:

27. 中间件
1. 中间件执行流程

Django 1.10之前,请求到达第一个中间件,会找到最后一个中间件的 process_response 返回:

而Django 1.10之后,会找到自己的 process_response 返回:

中间件是有序执行的!

2. 中间件原理浅析

中间件是类,首先查看Django源码中的中间件:


所以,我们写的中间件也要继承MiddlewareMixin类,这里把中间件类写在middleware.py,并放置在与manage.py同级目录下:

middleware.py:

from django.utils.deprecation import MiddlewareMixin

class M1(MiddlewareMixin):
    def process_request(self, request):
        print('m1.process_request')

    def process_response(self, request, response):
        print('m1.process_response')
        return response

class M2(MiddlewareMixin):
    def process_request(self, request):
        print('m2.process_request')

    def process_response(self, request, response):
        print('m2.process_response')
        return response
"""
m1.process_request
m2.process_request
test
m2.process_response
m1.process_response
"""

还要在配置文件 settings.py 中注册中间件:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middlewaremon.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'middleware.M1',
    'middleware.M2'
]

可以看到 先执行process_request再执行process_response。还有个process_view方法:

from django.utils.deprecation import MiddlewareMixin

class M1(MiddlewareMixin):
    def process_request(self, request):
        print('m1.process_request')

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print('m1.process_view')
        # return callback(*callback_args, **callback_kwargs)

    def process_response(self, request, response):
        print('m1.process_response')
        return response

class M2(MiddlewareMixin):
    def process_request(self, request):
        print('m2.process_request')

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print('m2.process_view')
        # return callback(*callback_args, **callback_kwargs)

    def process_response(self, request, response):
        print('m2.process_response')
        return response

"""
m1.process_request
m2.process_request
m1.process_view
m2.process_view
test
m2.process_response
m1.process_response
"""

process_request有返回值会直接执行自己的process_response。如果执行的是process_view有返回值,会跳过下一个中间件的process_view,执行视图函数(这里是自动调用的)。接下来,不是执行自己的process_response返回给用户,而是 最开始 的process_response(把所有的process_response都执行一遍):

from django.utils.deprecation import MiddlewareMixin


class M1(MiddlewareMixin):
    def process_request(self, request):
        print('m1.process_request')

    def process_view(self, request, callback, callback_args, callback_kwargs):
        # print(callback, callback_args, callback_kwargs) #<function test at 0x7fd39b986040> () {}
        print('m1.process_view')
        response = callback(request, *callback_args, **callback_kwargs)
        return response

    def process_response(self, request, response):
        print('m1.process_response')
        return response


class M2(MiddlewareMixin):
    def process_request(self, request):
        print('m2.process_request')

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print('m2.process_view')

    def process_response(self, request, response):
        print('m2.process_response')
        return response
        
"""
m1.process_request
m2.process_request
m1.process_view
test
m2.process_response
m1.process_response
"""

中间件的类中还有 process_exception 方法,这是关于异常的方法。为了进行测试首先在视图函数中使代码产生异常:

def test(request):
    print('test')
    a = int('erics')
    return HttpResponse()

通过下面的代码测试发现,请求经过所有的process_request、process_view到达视图函数,没有错误的情况下会执行process_response,出现错误会执行process_exception在执行process_response:

from django.utils.deprecation import MiddlewareMixin

class M1(MiddlewareMixin):
    def process_request(self, request):
        print('m1.process_request')

    def process_view(self, request, callback, callback_args, callback_kwargs):
        # print(callback, callback_args, callback_kwargs) #<function test at 0x7fd39b986040> () {}
        print('m1.process_view')
        # response = callback(request, *callback_args, **callback_kwargs)
        # return response
    #
    def process_response(self, request, response):
        print('m1.process_response')
        return response

    def process_exception(self, request, exception):
        print('m1.process_exception')


class M2(MiddlewareMixin):
    def process_request(self, request):
        print('m2.process_request')

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print('m2.process_view')

    def process_response(self, request, response):
        print('m2.process_response')
        return response

    def process_exception(self, request, exception):
        print('m2.process_exception')

"""
m1.process_request
m2.process_request
m1.process_view
m2.process_view
test
m2.process_exception
m1.process_exception
m2.process_response
m1.process_response
"""

在process_exception使用HttpResponse函数返回异常,可以看这个时候中间件方法的执行顺序:

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

class M1(MiddlewareMixin):
    def process_request(self, request):
        print('m1.process_request')

    def process_view(self, request, callback, callback_args, callback_kwargs):
        # print(callback, callback_args, callback_kwargs) #<function test at 0x7fd39b986040> () {}
        print('m1.process_view')
        # response = callback(request, *callback_args, **callback_kwargs)
        # return response
    #
    def process_response(self, request, response):
        print('m1.process_response')
        return response

    def process_exception(self, request, exception):
        print('m1.process_exception')

class M2(MiddlewareMixin):
    def process_request(self, request):
        print('m2.process_request')

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print('m2.process_view')

    def process_response(self, request, response):
        print('m2.process_response')
        return response

    def process_exception(self, request, exception):
        # print('m2.process_exception')
        return HttpResponse('出现异常!')

"""
m1.process_request
m2.process_request
m1.process_view
m2.process_view
test
m2.process_response
m1.process_response
"""

异常被M2的process_exception处理了,跳过M1的process_exception,直接执行process_response。页面也不没有报错了:

中间件中还有process_template_response方法,但是这个方法在视图函数的返回值中有render方法才会执行,没有render方法不会被执行,所以这里修改视图函数:

class Foo:
    def __init__(self, req):
        self.req = req

    def render(self):
        return HttpResponse()

def test(request):
    obj = Foo(request)
    return obj

接下来可以看到process_template_response方法被执行,

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

class M1(MiddlewareMixin):
    def process_request(self, request):
        print('m1.process_request')

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print('m1.process_view')

    def process_response(self, request, response):
        print('m1.process_response')
        return response

    def process_exception(self, request, exception):
        print('m1.process_exception')

    def process_template_response(self, request, response):
        """
        视图函数的返回值中有render方法才会执行
        :param request:
        :param response:
        :return:
        """
        print('m1.process_template_response')
        return response

class M2(MiddlewareMixin):
    def process_request(self, request):
        print('m2.process_request')

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print('m2.process_view')

    def process_response(self, request, response):
        print('m2.process_response')
        return response

    def process_exception(self, request, exception):
        return HttpResponse('出现异常!')

    def process_template_response(self, request, response):
        print('m2.process_template_response')
        return response

"""
m1.process_request
m2.process_request
m1.process_view
m2.process_view
m2.process_template_response
m1.process_template_response
m2.process_response
m1.process_response
"""

process_template_response方法的使用,可以帮助我们把一些可以复用的功能组件拆分出来,如JSON序列化:

class Foo:
    def __init__(self, req, status, msg):
        self.req = req
        self.status = status
        self.msg = msg

    def render(self):
        import json
        ret = {
            'status': self.status,
            'msg': self.msg
        }
        return HttpResponse(json.dumps(ret))

def test(request):
    return Foo(request, True, '错误信息!')

在视图函数中打造功能,改造另外一个对象,让这个对象把我们的数据封装起来:

class JsonResponse:
    """
    内部帮助我们序列化,
    """
    def __init__(self, req, status, msg):
        self.req = req
        self.status = status
        self.msg = msg

    def render(self):
        import json
        ret = {
            'status': self.status,
            'msg': self.msg
        }
        return HttpResponse(json.dumps(ret))


def test(request):
    return JsonResponse(request, True, '错误信息!')

视图函数返回的对象且对象中有render方法才会执行process_template_response方法。

3. 中间件的应用

中间件可以对 所有请求或一部分请求做批量处理。使用缓存时,需要对 所有请求进行判断,缓存中如果有就把缓存中的数据返回,没有就执行视图函数。对所有的请求进行判断就需要使用到中间件。

28. Form组件
1. Form组件的功能

对用户提交表单数据进行校验、保留上次输入内容。

2. 数据校验

Form组件对Form表单的方式提交的数据进行校验:

login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">
</head>
<body>
<div class="container" style="margin-top: 10px">
    <div class="row">
        <div class="col-md-4 col-md-offset-4">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    登录
                </div>
                <div class="panel-body">
                    <form action="/login.html/" method="POST" name="loginForm">
                        <div class="form-group">
                            <label for="name">用户名</label>
                            <input type="text" class="form-control" name="name" placeholder="请输入用户名">
                            <div style="color: red;font-weight: bold">{{ obj.errors.name.0 }}</div>
                        </div>
                        <div class="form-group">
                            <label for="">密码</label>
                            <input type="password" class="form-control" name="pwd" placeholder="请输入密码">
                            <div style="color: red;font-weight: bold">{{ obj.errors.pwd.0 }}</div>
                        </div>
                        {% csrf_token %}
                        <button type="submit" class="btn btn-primary">登录</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

class LoginForm(Form):
    name = fields.CharField(
        required=True,
        min_length=6,
        max_length=20,
        error_messages={
            'required': '用户名不能为空!',
            'min_length': '用户名长度不能小于6!',
            'max_length': '用户名长度不能大于20!'
        }
    )
    pwd = fields.CharField(
        required=True,
        min_length=6,
        max_length=20,
        error_messages={
            'required': '密码不能为空!',
            'min_length': '密码长度不能小于6!',
            'max_length': '密码长度不能大于20!'
        }
    )
    
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        obj = LoginForm(request.POST)
        """
        1、LoginForm实例化时
        self.field={'name':正则表达式,'pwd':正则表达式}
        2、循环self.field
        for k,v in self.field.items():
            input_value = request.POST.get(k)
            正则表达式和input_value进行匹配
        """
        if obj.is_valid():
            # 校验通过的数据
            print(obj.cleaned_data)
        else:
        	print(obj.errors, type(obj.errors))  # <class 'django.forms.utils.ErrorDict'>
            """
            <ul class="errorlist">
                <li>name
                    <ul class="errorlist">
                        <li>用户名不能为空</li>
                    </ul>
                </li>
                <li>pwd
                    <ul class="errorlist">
                        <li>用户名不能为空</li>
                    </ul>
                </li>
            </ul>
            """
        return render(request, 'login.html', {'obj': obj})

上面是Form组件校验以Form表单的方式的提交数据,下面是以AJAX的方式提交数据:

login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <link rel="stylesheet" href="../static/css/bootstrap.min.css">
</head>
<body>
<div class="container" style="margin-top: 10px">
    <div class="row">
        <div class="col-md-4 col-md-offset-4">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    登录
                </div>
                <div class="panel-body">
                    <form id="form" action="" method="POST" name="loginForm" novalidate>
                        <div class="form-group">
                            <label for="name">用户名</label>
                            <input type="text" class="form-control" name="name" placeholder="请输入用户名">
                            <div style="color: red;font-weight: bold">{{ obj.errors.name.0 }}</div>
                        </div>
                        <div class="form-group">
                            <label for="">密码</label>
                            <input type="password" class="form-control" name="pwd" placeholder="请输入密码">
                            <div style="color: red;font-weight: bold">{{ obj.errors.pwd.0 }}</div>
                        </div>
                        <div class="form-group">
                            <label for="">邮箱</label>
                            <input type="email" class="form-control" name="email" placeholder="请输入密码">
                            <div style="color: red;font-weight: bold">{{ obj.errors.email.0 }}</div>
                        </div>
                        <div class="form-group">
                            <label for="">手机号</label>
                            <input type="text" class="form-control" name="phone" placeholder="请输入密码">
                            <div style="color: red;font-weight: bold">{{ obj.errors.phone.0 }}</div>
                        </div>
                        {% csrf_token %}
                        <button id="loginBtn" type="button" class="btn btn-primary">登录</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>
    $(function () {
        ajaxLogin();
    })

    function ajaxLogin() {
        $('#loginBtn').click(function () {
            var formData = $('#form').serialize();//会忽略字段左右的空格
            console.log(formData)//name=thanlon&pwd=123456&email=thanlon@sina&phone=18523332005&csrfmiddlewaretoken=xxx
            $.ajax({
                url: '/login.html/',
                type: 'post',
                data: formData,
                dataType: 'json',
                success: function (arg) {
                    console.log(arg);//{"status": 0, "msg": null}
                    if (arg.status) {
                        //location.href = 'https://www.baidu'
                    } else {
                        //清除之前的错误提示信息
                        $('.clearTag').remove();
                        console.log(arg.msg)//对象类型
                        $.each(arg.msg, function (index, value) {
                            /**
                             * alert(index);//错误字段,pwd
                             * alert(value);//错误消息,如This field is required.
                             * alert(typeof (index)) //string
                             * alert(typeof (value))//object
                             * alert(value[0])
                             */
                            var tag = document.createElement('span');
                            tag.innerHTML = value[0];
                            tag.className = 'clearTag';
                            $('#form').find('input[name="' + index + '"]').after(tag);
                        })
                    }
                }
            })
        })
    }
</script>
</html>

views.py:

from django.forms import Form, fields
from django.shortcuts import render, HttpResponse
from app01 import models
import json

class LoginForm(Form):
    name = fields.CharField()
    pwd = fields.CharField()
    email = fields.EmailField()
    phone = fields.RegexField('185\d+')

def ajax_login(request):
    if request.method=='GET':
        return render(request,'login.html')
    ret = {'status': True, 'msg': None}
    print(type(json.dumps(ret)))  # <class 'str'>
    obj = LoginForm(request.POST)
    if obj.is_valid():
        print(obj.cleaned_data)#{'name': 'thanlon', 'pwd': '123456', 'email': 'thanlon@sina', 'phone': '18523332005'}
    else:
        print(obj.errors)  # obj.errors是对象
        ret['status'] = False
        ret['msg'] = obj.errors
    return HttpResponse(json.dumps(ret))  # <class 'str'>

AJAX仅用来验证功能,Form可以验证功能也可以生成HTML标签。

3. 常用字段和参数
Field:
	required = True,  # 是否允许为空
	label = None,  # 用于生成Label标签
	initial = None,  # 初始值
	help_text = '',  # 帮助信息(在标签旁边显示)
	error_messages = {},  # 错误信息{'invalid':'格式错误'}
	show_hidden_initial = None,  # 是否在当前插件后面再加上一个隐藏的且具有默认值的插件(可用于检验两次输入是否一致)
	validators = [],  # 自定义验证规则
	localize = False,  # 是否支持本地化
	disabled = False,  # 是否可以编辑
	label_suffix = None,  # Label内容后缀
	widget = None,  # HTML插件

CharField类和IntegerField 类都继承Field类,但是除了具有Field中的所有属性之外还有其它属性:

CharField(Field):
    min_length = None,
    max_length = None,
    strip = True,  # 是否移除空白
    
IntegerField(Field):
	min_length = None,
    max_length = None,

DecimalField(IntegerField):
    max_value=None,  # 最大值
    min_value=None,  # 最小值
    max_digits=None,  # 总长度
 
BaseTemporalField(Field)
	input_formats=None,  # 时间格式化

BaseTemporalField(Field)
	input_formats=None,  # 时间格式化

DateField(BaseTemporalField): # 格式:2020-10-12
TimeField(BaseTemporalField): # 格式:12:50
DateTimeField(BaseTemporalField): # 格式:2020-10-12 12:50

DurationField(Field): # 时间间隔:%d %H:%M:%S.%f

RegexField(CharField):
	regex='185\d+',
	max_length=None,
    min_length=None,
    error_messages={}

EmailField(CharField)
URLField(CharField)
SlugField(CharField)
GenericIPAddressField(CharField)
4. 保留上次输入内容

Form表单的方式提交数据,页面会刷新会失去提交的内容。AJAX方式提交时页面不刷新,上次输入内容自动保存。使用Form表单提交数据保存上次输入的内容也是可以实现的,需要应用到Form组件提供的第二个功能 生成HTML标签。只需要在 input 框中加入验证通过的数据 obj.cleaned_data

views.py:

class LoginForm(Form):
    name = fields.CharField(
        label='用户名',
    )
    pwd = fields.CharField(
        label='密码',
    )
    email = fields.EmailField(
        label='邮箱'
    )
    phone = fields.RegexField(
        label='手机号',
        regex='185\d+',
    )

def ajax_login(request):
    if request.method == 'GET':
        obj = LoginForm()
        return render(request, 'login.html',{'obj': obj})
    else:
        obj = LoginForm(request.POST)
        if obj.is_valid():
            print(obj.cleaned_data)
        else:
            print(obj.errors)
        return render(request, 'login.html', {'obj': obj})

login.html:

...
<form id="form" action="" method="POST" name="loginForm" novalidate>
	<!--{{ obj.as_p }}-->
    <div class="form-group">
        {{ obj.name.label }}
        <input type="text" class="form-control" name="name" placeholder="请输入用户名" value="{{ obj.cleaned_data.name }}">
        <div style="color: red;font-weight: bold">{{ obj.errors.name.0 }}</div>
    </div>
    <div class="form-group">
        {{ obj.pwd.label }}
        <input type="password" class="form-control" name="pwd" placeholder="请输入密码" value="{{ obj.cleaned_data.pwd }}">
        <div style="color: red;font-weight: bold">{{ obj.errors.pwd.0 }}</div>
    </div>
    <div class="form-group">
        {{ obj.email.label }}
        <input type="email" class="form-control" name="email" placeholder="请输入邮箱" value="{{ obj.cleaned_data.email }}">
        <div style="color: red;font-weight: bold">{{ obj.errors.email.0 }}</div>
    </div>
    <div class="form-group">
        {{ obj.phone.label }}
        <input type="text" class="form-control" name="phone" placeholder="请输入手机号" value="{{ obj.cleaned_data.phone }}">
        <div style="color: red;font-weight: bold">{{ obj.errors.phone.0 }}</div>
    </div>
    {% csrf_token %}
    <button id="loginBtn" type="submit" class="btn btn-primary">登录</button>
</form>
...

上一次验证通过的数据才会被保存:

本文标签: 实战 入门 Django