Django常用知识

Django 常用知识

####################################################################################################################
# Web 框架的原理
# socket 服务端,收发的消息就是按照 HTTP 协议的格式
# socket 客户端,浏览器
#
# wsgiref模块
# Python内置的一个收发socket消息的模块	
#
# HTTP 协议
# 请求 浏览器给服务端发消息叫请求(request)
# 响应 服务端给浏览器回消息叫响应(response) 
# 请求方法
# 	Get POST ...
# 版本号: HTTP/1.1
# 默认端口:
# 	HTTP:80
# 	HTTP:443
#
# Python Web 框架分类
# a.发送 socket 消息,按照 HTTP 协议解析消息的
# b.字符串替换,用来实现动态网页的
# c.业务逻辑
#
# 框架  
# 	1.自己实现 a、b、c
# 		tornado
# 	2.自己实现 b 和 c,使用第三方的 a
#   	Django
# 	3.自己实现c,使用第三方的 a
# 		Flask
#
# Django
# 	1.Django 的版本
#		1. LTS 版本 Long Team support
#   2.安装
# 		pip install django==1.11.11    
#       pip install django==1.11.11  -i http://pypi.tuna.tsinghua.edu.cn/simple  
#   3.创建 Django 项目
#   	1.命令行
#       	django-admin startproject mysite
#       2.PyCharm
#			File -> new project -> ...
#	4.启动    
#	    1.命令行
#  			1.进入执行的根目录
#			2.执行: python manage.py runserver 127.0.0.8080
#		2.PyCharm
#			1.点绿色三角
#			2.前提是左侧框框中的内容和项目名一致的
#	5.目录结构
#		mysite
#			__init__py
#			settings.py		项目配置文件
#			urls.py			路径和函数的对应关系
#			wsgi.py 		
#		static				项目的静态文件目录	
#		templates			项目的HTML文件目录
#		db.sqlite3			Django 开发环境默认的文件数据库
#		manage.py			项目的命令行入口文件
# 	6.配置文件	mysite/settings.py
#		1.BASE_DIR	--->	项目的根目录
#	7.form表单提交数据的三个要素
#    	1.form 标签必须要有 action 和 method 属性,如果有上传文件还需要设置enctype=multipart/form-data
#		2.所有获取用户输入的标签必须放在 form 表单中,必须要有 name 属性
#		3.必须要有 submit 按钮
####################################################################################################################
# 实例 文件上传
from django.conf import settings
import os
file_obj = request.FILES.get('file_name')
# 判断当前是否存在
file_name = file_obj.name
if os.path.exists(os.path.join(settings.BASE_DIR, file_name)):
# 如果存在同名的文件
name, suffix = file_name.split('.')
name += '2'
file_name = name + '.' + suffix
# 从上传文件对象里一点一点读取数据,写到本地
with open(file_name, 'wb') as f:
	# 从上传文件对象里一点一点读取数据
    for chunk in file_obj.chunks():
		f.write(chunk)
####################################################################################################################
#	8.Django 基础必会三件套	
#		from django.shortcuts import HttpResponse, render, redirect
#			1.HttpResponse('ok')	--->	把字符串的 OK 转成二进制,然后按照 HTTP 响应的格式要求返回
#				返回一个指定的字符串时	
#			2.render(request, 'login.html')	--->	打开文件,读取内容,按照响应格式返回
#				返回一个 HTML 文件
#			3.render(request, 'login.html', {"key": "value"})	--->	打开文件,读取内容,按照响应格式返回 {{ key }}
#				返回一个 HTML 文件
#				1. 注意:特殊符号的替换这一步发生在 Django 服务端		
#			4.redirect("/index/")	--->	在同网站不同地址间跳转(返回的是重定向的)
#			5.redirect('https://www.luffycity.com')		--->	让你的浏览器访问我指定的网址
#				跳转
#				1. 路径可以为相对路径('/book_list/'),绝对路径('https://www.luffycity.com')
#				2. 给浏览器返回的是一个特殊的响应,这个响应类似于一个命令,让浏览器去访问我指定的URL
#	9.request 所有和请求相关的数据都封装在这个 request 对象中
#		1.request.method	-->	返回的是当前这次请求的方法(全大写): GET/POST
#		2.request.GET		--> 取到 URL 里面携带的参数,类似于字典的数据结构
#		3.request.POST 		--> 取到 POST 提交的数据(form表单提交的数据),类似于字典的数据结构
#	10.Django的模板语言(HTML 中的特殊符号)
#		{{ name }}	{'name': '闷骚哥'}
#		{{ 变量名 }}
#	11.程序连 mysql
#		使用 pymysql 模块
#		1.导入 pymysql 模块
#		2.创建连接
#		3.获取执行命令的游标
#		4.用游标去执行 SQL 语句
#		5.获取 SQL 语句的执行结果
#		6.关闭游标
#		7.关闭连接
#
# 		创建一种工具帮你翻译 SQL 语句	--> ORM
#		优点:
#			1.开发效率高
#			2.开发不用直接写 SQL 语句
#			3.后端连得数据库变了,ORM代码不用变(适配性好)	
#		缺点:
#			1.执行效率低
#				1.把ORM语句翻译成SQL语句有一定时间
#				2.ORM翻译的SQL语句有可能不是最优的SQL语句
#
#       ORM的对应关系
#			ORM				DB
#			类   	<===>	数据表
#			属性	<===>	字段
#			对象	<===>	数据行
#
#		以后就不会再想写 SQL 语句了
#	12.Django 项目 app	---> 项目中又分了一级 Python 包,不同的功能放到不同的包里面
#	13.配置settings.py
#		1.创建 app
#			python manage.py startapp app01
#		2.告诉 Django 创建了一个 app
#			在 settings.py 找那个的 INSTALLED_APPS 中添加新创建的 app,INSTALLED_APPS = ['app 名',]
			INSTALLED_APPS = [
			# 告诉Django我自己新建了一个名叫app01的应用,推荐下面的写法
				'app01.apps.App01Config',  
				# 'app01'
			]
#		3.静态文件相关
			STATIC_URL = 'static'    # 别名
			STATICFILES_DIRS = [
				os.path.join(BASE_DIR,'static'),
			]
#		4. TEMPLATES
			'DIRS': [os.path.join(BASE_DIR, 'templates')]	
#	14.Django中ORM的使用
#		1.用处
#			1.操作数据表
#			2.操作数据行				
#		2.使用
#			1.手动创建一个数据库
#				-> create database mysite charset=utf8;
#			2.告诉 Django 连哪个数据库
				DATABASES = {
					'default': {
						'ENGINE': 'django.db.backends.mysql',	# 连接数据库的类型
						'NAME': 'mysite',	# 数据库名
						'HOST': '127.0.0.1',	# 数据库主机地址
						'PORT':	3306,	# 数据库的端口	
						'USER': 'root',
						'PASSWORD': '',
						'OPTIONS': {
							"init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
						}		
					}
				}
#			3.用什么连数据库?
#				利用第三方的包,比如第三方包:pymysql 和 MySQLdb
#				告诉 Django 用 pymysql 模块代替默认的 MySQLdb 去连接 MySQL 数据库
#				和 settings.py 同级的 __init__.py 写上
				import pymysql
				pymysql.install_as_MySQLdb()	
#			4.在 app/models.py 的文件中创建类
#				类必须继承 models.Model
#			5.另一个命令
#				1.python manage.py makemigrations	--->	找个小本本把 models.py 的变更记录一下
#				2.python manage.py migrate			--->	把上面的变更记录翻译成 SQL 语句,去数据库执行
#		3.ORM 查询
#			1.User.objects.filter(name='闷骚哥', pwd='123')
#		4.图书管理系统之出版社的增删改查
#			1.表结构设计
#				1.出版社
#					id	name
#				2.作者
#					id	name
#				3.书
#					id	title	出版社_id
#				4.作者_书_关系表
#					id 书_id	作者_id
#			2.ORM 增删改查
#				1.类名.objects.all()				--->	返回一个列表
#				2.类名.objects.filter(条件)				--->	返回一个列表,可以找到多个,找不到也不报错
#				3.类名.objects.get(条件)			--->	返回一个对象,有且只能找到一个,找不到会报错
#				4.类名.objects.create(name='')		--->	创建一个对象,返回的就是刚创建的对象
#				5.类名.objects.filter(id=).delete	--->	删除
#				6.obj = 类名.objects.get(id='')
#					obj.name = '新值'				--->	修改对象的属性(修改数据行某个字段的值)		
#					obj.save()						--->	把修改同步到数据库
#			3.Django 模板语言
#				1.for 循环
#					{% for i in ret %}
#						{{ i }}
#						{{ forloop.counter }}		--->	for 循环从 1 开始计数
#						{{ forloop.counter0 }}		--->	for 循环从 0 开始计数			
#					{% endfor %}
#			4.出问题就是单词写错了! 99%	
#			5.HTML a 标签 href 属性
#				1.写绝对 URL
#				2.写相对 URL(同网站之间跳转)
#				3.瞄点(同网页标签之间的跳转)	
#			6.ORM 创建字段
				id = models.AutoField(primary_key=True)	# -> 创建一个自增的 ID 列作为主键
				email = models.CharField(max_length=24)	# -> varchar(32) 
#		5.书籍的增删改查			
#			1.表结构设计
#				1.id  title 出版社_id
#				2.SQL
#					create table book (
#						id int primary key auto_increment,
#						title varchar(30) not null,
#						press_id int not null,
#						constraint fk_press foreign key(press_id) references press(id)
#						on delete cascade
#						on update cascade
#					)
#				3. ORM外键的语法
					press = models.ForignKey(to='Press', on_delete=models.CASCADE)
					# 书
					class Book(models.Model):
						id = models.AutoField(primary_key=True)  # 自增id主键
						title = models.CharField(max_length=30)  # 书名
						price = models.IntegerField()  # 价格
						# Django 1.11 默认就是级联删除, Django 2.0之后必须指定on_delete
						# to=关联的表名
						press = models.ForeignKey('Press', on_delete=models.CASCADE)
#				4. 外键查询的语法
#					book_obj.press		-->	取到的是和这本书关联的出版社对象
#					book_obj.press_id	-->	数据库中实际保存的外键id	
#				4.给数据库总已经存在的表添加另外一个字段,这个字段既没有默认值也不能为空,
#			      ORM 就不知道数据库中已经存在的数据该怎么处理
#                 在命令行界面会提示两个选择
#					1.输入1,然后在输入一个一次性的默认值
#					2.输入2,退出此次变更记录,手动在models.py中做修改 
#						null=True	--> 让该字段可以为空	
#						default=默认值	--> 给该字段指定一个默认值
#           2. 书籍的增删改查
#				1.查询
#					1.book_obj.press --> ORM层面封装的,返回的是和我这本书关联的出版社对象
#					2.book_obj.press_id --> 数据库中真正存在的字段,保存的是和我关联的出版社id值
#				2. 增加
#					1. 用select标签把已经存在的出版社在页面上展示出来
#					   让用户去选择
#					   使用的是:模板语言的for循环
#				3. 删除
#					1. 类似于昨天的操作
#					2. 补充一个3秒钟之后跳转到指定页面的操作
#						1. location.href
#						2. setTimeout()
#						3. setinterval()
#						<script>
#							// 每隔一秒钟就修改 s1 的text
#							let currentNum = document.getElementById('s1').innerText;
#							let t2 = setInterval(function () {
#								currentNum = document.getElementById('s1').innerText;
#								document.getElementById('s1').innerText = currentNum - 1;
#								if (currentNum == 1) {
#									clearInterval(t2);
#									location.href = '/book_list/';
#							   }
#							}, 1000);
#						</script>		
#				4. 编辑
#					1. 类似于于昨天的操作
#					2. 用select标签展示默认的出版社
#						1. Django模板语言中的if判断
#							{% if 条件 %}
#								...
#							{% else %}
#								...
#							{% endif %}
#		6.多对多关系    
#			
#			作者 <--> 书籍
#       
#           1.表结构设计
#				1.SQL设计
#					-- 创建作者表
#					create table author(
#						id int primary key auto_increment,
#						name varchar(32) not null
#					);			
#
#					-- 创建作者和书的关系表
#					create table author2book(
#						id int primary key auto_increment,
#						author_id int not null,
#						book_id int not null,
#						constraint fk_auther foreign key (author_id) references author(id) on delete cascade on update cascade
#						constraint fk_book foreign key (book_id) references book(id) on delete cascade on update cascade
#					};
#				2.ORM版
#					1. 第一版:
#						自己创建第三张表
#					2. 第二版
#						让ORM帮我们创建第三张表
#						models.ManyToManyField()
						# 作者
						class Author(models.Model):
							id = models.AutoField(primary_key=True)  # 自增id主键
							name = models.CharField(max_length=32)  # 作者名字
							books = models.ManyToManyField(to='Book')  # 只是ORM层面建立的一个多对多关系,不是作者表的一个字段
#
							def __str__(self):
								return '<这是一个作者对象,它的名字是:{}>'.format(self.name)
#					3. 第三版
#						待补充...(ORM进阶操作的时候)
#			2.作者的增删改查	
#				1.查询
#					author_obj.books        --> 得到的只是一个关联关系,并不能拿到数据
#					author_obj.books.all()  --> 得到和我这个作者关联的所有书籍对象列表
#				2.添加
#					1. add()
#					2. getlist()
#				3.删除
#					
#				4.编辑
#					1. 模板语言中
#						{% if book in author.books.all %}
#					2. ORM编辑多对多
#						1. 不能直接操作第三张关系表
#						2. 借助ORM给提供的方法
#							1. all()  --> 查询所有	
#							2. add(id1,id2)	 --> 修改
#							3. set([id1, id2])	--> 修改
#							4. clear()  --> 清除对应关系
####################################################################################################################
# 实例 作者数据的增删改查
############################
from django.shortcuts import render, HttpResponse, redirect
from app01.models import User, Press, Book, Author
from django.conf import settings
########################################################
# 作者展示函数
def author_list(request):
    # 1.去数据库查询所有的作者数据
    author_data = Author.objects.all()
    # for author in author_data:
    #     print(author)
    #     # 取到每个作者出版的书籍
    #     # print(author.books)  # 是一个 ORM 提供的桥梁(工具),帮我找对应关系
    #     print(author.books.all())
    # 2.在页面上展示出来
    return render(request, 'author_list.html', {'author_list': author_data})
########################################################
# 添加作者
def add_author(request):
    if request.method == "POST":
        # 1.取到用户填写的信息
        new_author_name = request.POST.get('author_name')
        # book_ids = request.POST.getlist("books")  # --> 这个只能取到一个值
        book_ids = request.POST.getlist("books")
        # 2.添加到数据库
        # 2.1.创建新的作者
        author_id = Author.objects.create(name=new_author_name)
        # 2.2.创建新作者和书的对应关系
        # 两种方法都可以
        author_id.books.add(*book_ids)  # 参数是一个一个单独的id值
        # author_id.books.set(book_ids)   # 参数是书籍id值的列表
        # 3.跳转到作者列表页
        return redirect('/author_list/')
	# 1.获取所有的书籍信息
    book_data = Book.objects.all()
    # 2.返回一个页面给用户,让用户填写作者信息
    return render(request, 'add_author.html', {'book_list': book_data})
########################################################	
# 删除作者
def delete_author(request):
    # 1.取到要删除的作者的id值
    delete_author_id = request.GET.get("id")
    # 2.通过id找到数据,并删除
    Author.objects.filter(id=delete_author_id).delete()
    # 3.让用户再访问作者列表页面
    return redirect('/author_list/')
########################################################
# 编辑作者
def edit_author(request):
    # 1.取到要编辑的作者的id值
    edit_author_id = request.GET.get('id')
    # 2.找到要编辑的作者对象
    edit_author_obj = Author.objects.get(id=edit_author_id)
    if request.method == "POST":
        # 3.拿到编辑之后的数据
        new_author_name = request.POST.get('author_name')
        new_book_ids = request.POST.getlist('book_ids')
        # 4.去数据库修改
        # 4.1.修改作者表
        edit_author_obj.name = new_author_name
        edit_author_obj.save()
        # 4.2.修改作者和书的关系表
        edit_author_obj.books.set(new_book_ids)
        # 5.跳转到作者列表页
        return redirect('/author_list/')

    # 2.2.找到所有的书籍对象
    book_data = Book.objects.all()
    # 3.返回一个页面
    return render(request, 'edit_author.html', {'author': edit_author_obj, 'book_list': book_data})
########################################################
# 作者展示HTML页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>作者列表</title>
</head>
<body>
<a href="/add_author/">添加作者</a>
<table border="1">
    <thread>
        <tr>
            <th>序号</th>
            <th>作者id</th>
            <th>作者姓名</th>
            <th>作者作品</th>
            <th>操作</th>
        </tr>
    </thread>
    <thbody>
        {% for author in author_list %}
            <tr>
                <td>{{ forloop.counter }}</td>
                <td>{{ author.id }}</td>
                <td>{{ author.name }}</td>
                <td style="text-align: center">
                    {% for book in author.books.all %}
                        {% if forloop.last %}
                            《{{ book.title }}》
                        {% else %}
                            《{{ book.title }}》,
                        {% endif %}
                    {% empty %}
                        暂无作品
                    {% endfor %}
                </td>
                <td>
                    <a href="/delete_author/?id={{ author.id }}">删除</a>
                    <a href="/edit_author/?id={{ author.id }}">编辑</a>
                </td>
            </tr>
        {% endfor %}
    </thbody>
</table>
</body>
</html>	
########################################################
# 添加作者HTML页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>添加作者</title>
</head>
<body>
<h1>添加作者</h1>
<form action="" method="post">
    {% csrf_token %}
    <input type="text" name="author_name">
    <select name="books" multiple="multiple">
        {% for book in book_list %}
            <option value="{{ book.id }}">{{ book.title }}</option>
        {% endfor %}
    </select>
    <input type="submit">
</form>
</body>
</html>
########################################################
# 修改作者HTML页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>编辑作者</title>
</head>
<body>
<h1>编辑作者</h1>
<form action="" method="post">
    {% csrf_token %}
    <input type="text" name="author_name" value="{{ author.name }}">
    <select name="book_ids" multiple>
        {% for book in book_list %}
            {% if book in author.books.all %}
                <option value="{{ book.id }}" selected>{{ book.title }}</option>
            {% else %}
                <option value="{{ book.id }}">{{ book.title }}</option>
            {% endif %}

        {% endfor %}
    </select>
    <input type="submit">
</form>
</body>
</html>
####################################################################################################################
# Django模板语言
# 	1. 基础语法
#		1. 变量相关{{ 变量名 }}
#		2. 逻辑相关 {% ... %}
#
#       当模版引擎遇到一个变量,它将计算这个变量,然后用结果替换掉它本身。 变量的命名包括任何字母数字以及下划线 ("_")的组合。 
#		变量名称中不能有空格或标点符号。
#		
#		点(.)在模板语言中有特殊的含义。当模版系统遇到点("."),它将以这样的顺序查询:
#		字典查询(Dictionary lookup)
#		属性或方法查询(Attribute or method lookup)
#		数字索引查询(Numeric index lookup)
#
#		注意事项:
#			1.如果计算结果的值是可调用的,它将被无参数的调用.调用的结果将成为模版的值。
#			2.如果使用的变量不存在,模版系统将插入string_if_invalid选项的值,它被默认设置为'' (空字符串)。
##############################################################
# view中代码:
def template_test(request):
    l = [11, 22, 33]
    d = {"name": "alex"}

    class Person(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age

        def dream(self):
            return "{} is dream...".format(self.name)

    Alex = Person(name="Alex", age=34)
    Egon = Person(name="Egon", age=9000)
    Eva_J = Person(name="Eva_J", age=18)

    person_list = [Alex, Egon, Eva_J]
    return render(request, "template_test.html", {"l": l, "d": d, "person_list": person_list})
##############################################################
# 模板中支持的写法:
{# 取l中的第一个参数 #}
{{ l.0 }}
{# 取字典中key的值 #}
{{ d.name }}
{# 取对象的name属性 #}
{{ person_list.0.name }}
{# .操作只能调用不带参数的方法 #}
{{ person_list.0.dream }}
#
# 注意;当模板系统遇到一个(.)时,会按照如下的顺序去查询
#	1.在字典查询
#	2.属性或方法
#	3.数字索引	
#
#	2. filter(过滤器)
#		1. 基础语法
#			翻译为过滤器,用来修改变量的显示结果
#			{{ 变量|方法名:'参数' }}		
#			注意:|和:左右都不要加空格
#		2.常用的内置filter
#			1.default
#				如果一个变量是false或者为空,使用给定的默认值。 否则,使用变量的值。
#				如果value没有传值或者值为空的话就显示nothing
#				注意:TEMPLATES的OPTIONS可以增加一个选项:string_if_invalid:'找不到',可以替代default的作用
#				如果变量不存在两者都使用了,优先显示选项string_if_invalid,如果有变量,但变量内容为空,显示default的值		
				{{ value|default:"nothing"}}
#			2.length
#				返回值的长度,作用于字符串和列表。
#				返回value的长度,如 value=['a', 'b', 'c', 'd']的话,就显示4
				{{ value|length }}
#			3.filesizeformat
#				将值格式化为一个 “人类可读的” 文件尺寸 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)。例如:
#				如果 value 是 123456789,输出将会是 117.7 MB。
				{{ value|filesizeformat }}			
#			4.slice
#				切片
				{{value|slice:"2:-1"}}
#			5.date(重点)
#				格式化				
				{{value|date:"Y-m-d H:i:s"}}
#				
#				如果所有页面的日期都要用统一格式,可修改配置文件(settings.py)
#					USE_L10N = False
#					DATETIME_FORMAT = 'Y-m-d H:i:s' 或者 DATE_FORMAT = 'Y-m-d'	
#				
				
				# 复习时间操作
				import time
				import datetime
				
				now = datetime.datetime.now()
				now = now - datetime.timedalta(days=7)
				
				# 创建一个时间 2018-10-01
				t1 = time.strptime('2018-10-01', '%Y-%m-%d')
				
				# 结构化时间
				print(t1)
				print(t1.tm_year)
				
				# 把时间格式转化成指定格式字符串:2018.10.01
				t2 = time.localtime()
				print(t2)
				t3 = time.strftime('%Y.%m.%d', t2)
				print(t3)
				
				# 时间间隔
				# 加一天
				t4 = datetime.datetime.now()
				print(t4)
				print(type(t4))
				t5 = t4 + datetime.timedalta(days=1)
				print(t5)
				
				# 加一年
				t6 = t4 + datetime.timedalta(weeks=52)
				print(t6)
#			6.safe
#				Django的模板中会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全。
#				但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,
#				这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。
#				为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码
#				是安全的不必转义。
#				比如:
				value = "<a href='#'>点我</a>"
				{{value|safe}}	
#			7.truncatechars
#				如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列("...")结尾。
#  				参数:截断的字符数				
				{{ value|truncatechars:9}}
#			8.truncatewords
#				在一定数量的字后截断字符串。
				{{ value|truncatewords:9}}
#			9.cut
#				移除value中所有的与给出的变量相同的字符串
#				如果value为'i love you',那么将输出'iloveyou'.
				{{ value|cut:' ' }}
#			10.join
#				使用字符串连接列表,例如Python的str.join(list)
#			11.timesince
#				将日期格式设为自该日期起的时间(例如,“4天,6小时”)。
#				采用一个可选参数,它是一个包含用作比较点的日期的变量(不带参数,比较点为现在)。 
#				例如,如果blog_date是表示2006年6月1日午夜的日期实例,并且comment_date是2006年6月1日08:00的日期实例,则以下将返回“8小时”:
#				分钟是所使用的最小单位,对于相对于比较点的未来的任何日期,将返回“0分钟”。
				{{ blog_date|timesince:comment_date }}
#			12.timeuntil
#				似于timesince,除了它测量从现在开始直到给定日期或日期时间的时间。 例如,如果今天是2006年6月1日,
#				而conference_date是保留2006年6月29日的日期实例,则{{ conference_date | timeuntil }}将返回“4周”。
#				使用可选参数,它是一个包含用作比较点的日期(而不是现在)的变量。 如果from_date包含2006年6月22日,则以下内容将返回“1周”:
				{{ conference_date|timeuntil:from_date }}	
#			13.ljust
#				左对齐
				{{value|ljust:"10"}}
#			14.rjust
#				左对齐
				{{value|rjust:"10"}}
#			15.center
#				左对齐
				{{value|center:"15"}}	
#			16.first
#				取第一个元素
				{{value|first}}
#			17.last
#				取最后一个元素
				{{value|last}}
#		3.自定义filter步骤
#			定义:
#			1. 在app目录下创建一个名为 templatetags 的python包
#			2. 在上面创建的包内部创建一个python文件: ooxx.py
#			3. 在ooxx.py文件中按照固定的格式注册的一个自定义的filter
				from django import template
#				
				# 固定写法,生成一个注册实例对象
				register = template.Library()
#				
				# 告诉Django的模板语言我现在注册一个自定义的filter
				# name可不填,默认使用函数名
				@register.filter(name='dsb')  		
				def add_sb(value):
					"""
					给任意指定的变量添加sb
					:param value: |左边被修饰的那个变量
					:return: 修饰后的变量内容
					"""
					return value + 'sb'	
#				
				@register.filter()
				def add_str(value, arg):
					return value + arg
#					
#				使用:
#				1. 重启Django项目
#				2. 在HTML页面中:{% load python文件名 %}
#				3. {{ name|dsb:'大好人' }}
############################################################
# 装饰器(Decorator)
# 
# 在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),
# 是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
# 
#############################################################
# 实例: 装饰器
def wrapper(fn):
    def inner(*args, **kwargs):
        print('准备点钱')
        ret = fn(*args, **kwargs)  # 真正执行原来的代码
        return ret
    return inner
#	
@wrapper
def yue(tools):
    print('使用%s约一约' % tools)
    return '约成功了'
#	
yue = wrapper(yue)
yue(tools)
##############################################################
# 标签(tag)
##################################
#	1.for
#		1.方式1
#			{% for i in list %}
#				{{ i }}
#			{% endfor %}
#
#		2.方式2		
#			{% for i in list %}
#				{{ i }}
#			{% empty %}
#				空的	
#			{% endfor %}
#		
#		forloop  {}				
#		forloop.counter     当前循环的索引值   索引值从1开始
#		forloop.counter0    当前循环的索引值   索引值从0开始
#		forloop.revcounter  当前循环的索引值(倒序)   索引值到1结束
#		forloop.revcounter0 当前循环的索引值(倒序)   索引值到0结束
#		forloop.first		当前循环是否是第一次  True 
#		forloop.last		当前循环是否是最后一次
#		forloop.parentloop	本层循环的外层循环	
#################################
#	2.if
#		{%  if 条件 %}
#			操作
#		{% endif %}
#			
#		{%  if 条件 %}
#			操作		
#		{% elif 条件 %}
#			操作	
#		{% else %}
#			操作	
#		{% endif %}
#
#       注意:
#			1. 不支持连续判断  a>b>c 需改成 a>b and b>c  
#			2. 不支持算数运算  + - * / % 需使用过滤器
#################################
#	3.with
#		{% with 变量 as 别名%}
#			{{ 别名 }}
#		{% endwith %}
#################################
#	4.csrf_token 基于安全的考虑
#		1.放在form表单中
			{% csrf_token %}
#		2.在表单中自动添加了一个隐藏的input标签
			<input type="hidden" name="csrfmiddlewaretoken" value="l9QxZvEgWsLw2XhifA9cOxf5Vh6oWwpvffqv3iaUTE3e5UJd4c1C2o6QS4PH0TUt">
#################################			
#	5.母版继承
#		1.定义一个母版  普通的HTML代码 base.html
#		2.在母版中定义block块
<ul class="nav nav-sidebar">
	<li class="{% block press_active %}{% endblock %}"><a href="/press_list/">出版社列表 </a></li>
	<li class="{% block book_active %}{% endblock %}"><a href="/book_list/">书籍列表</a></li>
	<li class="{% block author_active %}{% endblock %}" ><a href="/author_list/">作者列表</a></li>
</ul>
#
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
	{% block content %}{% endblock %}
</div>
#		3.子页面中继承的母版 {% extends 'base.html' %}
{% extends 'base.html' %}
{% block press_active %}
    active
{% endblock %}
{% block content%}
    <!--面板 开始-->
	内容不同的部分
    <!--面板 结束-->
{% endblock %}	
#		4.重写block块 
#			
#		注意事项:
#			1. {% extends 'base.html' %}写在第一行
#			2. {% extends name %} name写继承的母版的名字字符串
#			3. 自定义的内容写在block中
#			4. 定义多个block块 一般要有js css	  
#################################
# 6.组件
#	1.写一段的代码 nav.html
#	2.{% include 'nav.html' %}
#################################
# 7.静态文件相关
#	1.{% load static %}
#	2.{% static '相对路径' %}  ——> 去settings.py中获取STATIC_URL  '/static/' 和相对路径进行拼接
<!-- Bootstrap core CSS -->
================================================== -->
{% load static %}
<link href="{% static 'bootstrap-3.3.7/css/bootstrap.css' %}" rel="stylesheet">
<!-- 当前页面用到的自定义样式 -->
<link href="{% static 'dashboard.css' %}" rel="stylesheet">
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="{% static 'jquery-3.3.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.7/js/bootstrap.js' %}"></script>
#	3.{% get_static_prefix %}  ——> 去settings.py中获取STATIC_URL  '/static/',跟2的方式一样的,不同的写法
#	  "{% get_static_prefix %}相对路径"	
<!-- Bootstrap core CSS -->
================================================== -->
{% load static %}
<link href="{% get_static_prefix %}bootstrap-3.3.7/css/bootstrap.css" rel="stylesheet">
<!-- 当前页面用到的自定义样式 -->
<link href="{% get_static_prefix %}dashboard.css" rel="stylesheet">
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="{% get_static_prefix %}jquery-3.3.1.js"></script>
<script src="{% get_static_prefix %}bootstrap-3.3.7/js/bootstrap.js"></script>
##################################
# 8.自定义simple_tag
#	和自定义filter类似,只不过接收更灵活的参数
#	1.在app下创建一个templatetags的python包,templatetags名字不能错
#	2.在包下写py文件myfilter
#	3.编辑文件
		from django import template
		register = template.Library()
#	4.定义注册simple_tag
@register.simple_tag
def join_str(*args, **kwargs):
    print(args)
    print(kwargs)
    return "_".join(args)
#	4.使用自定义simple_tag
{% load myfilter %}
<h1>{% join_str 'alex' 'is' 'dsb' key1='hahah' key2='hehe' %}</h1>
##################################
# 9.自定义inclusion_tag
#	多用于返回html代码片段
#	1.在app下创建一个templatetags的python包,templatetags名字不能错
#	2.在包下写py文件 mytags
#	3.编辑文件
		from django import template
		register = template.Library()		
#	4.定义函数
#		可以接受参数
#		返回一个字典 
#	5.函数上加装饰器
		@register.inclusion_tag('pagination.html')
		
#	6. 函数返回的字典 交给 pagination.html 渲染
##################################
# 实例
#	1.templatetags包下文件mytags.py
from django import template
register = template.Library()
#
# @purpose 分页功能
# @param total 总页数
# @param current_num 当前页
@register.inclusion_tag('pagination.html')
def pagination(total, current_num):
    return {'total': range(1, total+1), 'current_num': current_num}
#	2.templates/pagination.html
<!--分页开始-->
<div>
    <nav aria-label="Page navigation" class="pull-right">
        <ul class="pagination">
            <li>
                <a href="#" aria-label="Previous">
                    <span aria-hidden="true">«</span>
                </a>
            </li>
            {% for i in total %}
                {% if i == current_num %}
                    <li class="active"><a href="#">{{ i }}</a></li>
                {% else %}
                    <li><a href="#">{{ i }}</a></li>
                {% endif %}
            {% endfor %}
            <li>
                <a href="#" aria-label="Next">
                    <span aria-hidden="true">»</span>
                </a>
            </li>
        </ul>
    </nav>
</div>
<!--分页结束-->
#	3.templates/list/press_list.html
<!--分页开始-->
{% load mytags %}
{% pagination 3 1 %}
<!--分页结束-->	
####################################################################################################################
# Django的view(视图)
#
# 一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应。
# 响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片。
# 无论视图本身包含什么逻辑,都要返回响应。代码写在哪里也无所谓,只要它在你当前项目目录下面。除此之外没有更多的要求了——可以说“没有什么神奇的地方”。
# 为了将代码放在某处,大家约定成俗将视图放置在项目(project)或应用程序(app)目录中的名为views.py的文件中。
#	
#	1.CBV 和 FBV
#		FBV function based view
#		CBV class based view
##########################################################
#	2.CBV的流程
#		1.定义CBV 
			from django.views import View
			class AddPress(View):	

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

				def post(self, request):
					print('post')
					press_name = request.POST.get('name')
					Press.objects.create(name=press_name)
					return redirect('/press_list/')
#		2.使用路由
			# 添加出版社
			path('add_press/', views.AddPress.as_view()),  
#		3.流程(查看源码)
#			1.AddPress.as_view() ——> view函数
#			2.当请求到来的时候执行view函数:
#				1.实例化自己写的类  ——> self
#					self = cls(**initkwargs)
#				2.self.request = request
#				3.执行 self.dispatch(request, *args, **kwargs)
#					1. 执行父类中的dispatch方法
#						1. 判断请求方式是否被允许
#							1.允许的情况
#								handler = 通过反射获取get post方法 									
#							2.不允许的情况
#								handler = 不允许的方法
#							3.handler(request, *args, **kwargs)
#						2.返回HttpResponse对象
#				4.返回HttpResponse对象给django
##########################################################
#	3.加装饰器	
#		1.FBV 普通加法
			import time 
			
			def timer(fn):
				def inner(*args, **kwargs):
					start = time.time()
					ret = fn(*args, **kwargs)
					return ret
				return inner
							
			@timer
			def add_press(request):
				if request.method == "POST":
					# 表示用户填写完了,要给我发数据
					# 1.取到用户填写的出版社数据
					press_name = request.POST.get('name')
					# 2.将数据添加到数据库中
					Press.objects.create(name=press_name)
					# 3.跳转到出版社列表页面
					return redirect('/press_list/')
				
				# 1.返回一个添加页面,让用户在上面填写新的出版社信息
				# return render(request, 'add_press.html')
				# 2.添加 bootstrap 样式
				return render(request, 'add_press2.html')
#		2.CBV
#			1.要引用
				from django.utils.decorators import method_decorator	
#			2.有三种方式
#				1.加在方法上
					from django.views import View
			
					class AddPress(View):
					
						def dispatch(self, request, *args, **kwargs):
							print('执行之前')
							ret = super().dispatch(request, *args, **kwargs)
							print('执行之后')
							return ret
						
						@method_decorator(timer)
						def get(self, request):
							return render(request, 'add_press2.html')
						
						@method_decorator(timer)
						def post(self, request):
							# 表示用户填写完了,要给我发数据
							# 1.取到用户填写的出版社数据
							press_name = request.POST.get('name')
							# 2.将数据添加到数据库中
							Press.objects.create(name=press_name)
							# 3.跳转到出版社列表页面
							return redirect('/press_list/')
#				2.加在dispatch上,这种等于所有方法都加上了,因为其它方法调用过程中都会调用 dispatch 方法
					from django.views import View
			
					class AddPress(View):
						
						@method_decorator(timer)
						def dispatch(self, request, *args, **kwargs):
							print('执行之前')
							ret = super().dispatch(request, *args, **kwargs)
							print('执行之后')
							return ret
						
						def get(self, request):
							return render(request, 'add_press2.html')
						
						def post(self, request):
							# 表示用户填写完了,要给我发数据
							# 1.取到用户填写的出版社数据
							press_name = request.POST.get('name')
							# 2.将数据添加到数据库中
							Press.objects.create(name=press_name)
							# 3.跳转到出版社列表页面
							return redirect('/press_list/')
#				3.加在类上
					from django.views import View
			
					@method_decorator(timer,name='post')
					@method_decorator(timer,name='get')
					class AddPress(View):
						
						def dispatch(self, request, *args, **kwargs):
							print('执行之前')
							ret = super().dispatch(request, *args, **kwargs)
							print('执行之后')
							return ret
						
						def get(self, request):
							return render(request, 'add_press2.html')
						
						def post(self, request):
							# 表示用户填写完了,要给我发数据
							# 1.取到用户填写的出版社数据
							press_name = request.POST.get('name')
							# 2.将数据添加到数据库中
							Press.objects.create(name=press_name)
							# 3.跳转到出版社列表页面
							return redirect('/press_list/')
##########################################################
#	4.request(https://docs.djangoproject.com/en/2.2/ref/request-response/)
#		1.request.method 请求中使用的HTTP方法的字符串表示,全大写表示。	
#		2.request.GET 包含所有HTTP  GET参数的类字典对象
#		3.request.POST 包含所有HTTP POST参数的类字典对象
#		4.request.FILES 一个类似于字典的对象,包含所有的上传文件信息。FILES 中的每个键为<input type="file" name="" /> 中的name,值则为对应的数据。
#						注意,FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会
#   					包含数据。否则,FILES 将为一个空的类似于字典的对象。									
#		5.request.path_info 返回用户访问url,不包括域名
#		6.request.body 请求体,byte类型 request.POST的数据就是从body里面提取到的
#		7.request.scheme 表示请求方案的字符串(通常为http或https)
#		8.request.path 一个字符串,表示请求的路径组件(不含域名)。
#		9.request.encoding 一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 'utf-8')。
#		10.request.META 一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器
#		11.HttpRequest.user	一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。
#							如果用户当前没有登录,user 将设置为 django.contrib.auth.models.AnonymousUser 的一个实例。你可以通过 is_authenticated() 区分它们							
								if request.user.is_authenticated():
									# Do something for logged-in users.
								else:
									# Do something for anonymous users.
#							user 只有当Django 启用 AuthenticationMiddleware 中间件时才可用。
#							匿名用户
								class models.AnonymousUser
#								django.contrib.auth.models.AnonymousUser 类实现了django.contrib.auth.models.User 接口,但具有下面几个不同点:
#									id 永远为None。
#									username 永远为空字符串。
#									get_username() 永远返回空字符串。
#									is_staff 和 is_superuser 永远为False。
#									is_active 永远为 False。
#									groups 和 user_permissions 永远为空。
#									is_anonymous() 返回True 而不是False。
#									is_authenticated() 返回False 而不是True。
#									set_password()、check_password()、save() 和delete() 引发 NotImplementedError。
#									New in Django 1.8:
#									新增 AnonymousUser.get_username() 以更好地模拟 django.contrib.auth.models.User。	
#		12.HttpRequest.COOKIES 一个标准的Python 字典,包含所有的cookie。键和值都为字符串。
#		13.HttpRequest.session HttpRequest.session 一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。
#		14.request.get_host() 根据从HTTP_X_FORWARDED_HOST(如果打开 USE_X_FORWARDED_HOST,默认为False)和 HTTP_HOST 头部信息返回请求的原始主机。
#		15.request.get_full_path() 返回 path,如果可以将加上查询字符串。
#		16.request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None) 返回签名过的Cookie 对应的值,如果签名不再合法则返回django.core.signing.BadSignature。 	
#		17.request.is_secure() 如果请求时是安全的,则返回True;即请求通是过 HTTPS 发起的。
#		18.request.is_ajax() 如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。
##########################################################
#	5.response		
		from django.shortcuts import render, HttpResponse, redirect
#		1.HttpResponse HttpResponse('字符串') # Content-Type: text/html; charset=utf-8   
#		2.render(request,'html文件名',{}) # ——> HTML代码
#		3.redirect(跳转的地址) # 调转本质就是加了一个响应头Location:/press_list/
#		4.使用json
#			1.方法1
				import json
				ret = {'name':'alex', 'age':84}
				HttpResponse(json.dumps(ret))  # Content-Type: text/html; charset=utf-8,注意这里HttpResponse可以加上参数content_type,默认值为None
#			2.方法2(django)
				from django.http import JsonResponse
				ret = {'name':'alex', 'age':84}
				JsonResponse(ret) # Content-Type: application/json
####################################################################################################################
# Django的路由	
#
# URL配置(URLconf)就像Django所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表。
# 我们就是以这种方式告诉Django,遇到哪个URL的时候,要对应执行哪个函数。
#############################
#	1.URLconf配置
#		1.1.基本格式
			from django.conf.urls import url

			urlpatterns = [
				url(正则表达式, views视图,参数,别名),
			]
#		2.参数说明
#			正则表达式: 一个正则表达式字符串
#			views视图: 一个可调用对象,通常为一个视图函数
#			参数: 可选的要传递给视图函数的默认参数(字典形式)
#			别名: 一个可选的name参数	
#		3.实例	
			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),
			]
#		4.注意: 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.正则表达式
#		2.1.基本配置
			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),
			]
#		2.2.注意事项	
#			1.urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
#			2.若要从URL中捕获一个值,只需要在它周围放置一对圆括号(分组匹配)。
#			3.不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
#			4.每个正则表达式前面的'r' 是可选的但是建议加上。
#		2.3.补充说明
#			# 是否开启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 时就会提示找不到页面。
##########################################################
#	3.分组命名匹配
#		上面的示例使用简单的正则表达式分组匹配(通过圆括号)来捕获URL中的值并以位置参数形式传递给视图。
#		在更高级的用法中,可以使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图。
# 		在Python的正则表达式中,分组命名正则表达式组的语法是(?P<name>pattern),其中name是组的名称,pattern是要匹配的模式。
# 		下面是以上URLconf 使用命名组的重写:
		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 更加明晰且不容易产生参数顺序问题的错误,但是有些开发人员则认为分组命名组语法太丑陋、繁琐。
# 		至于究竟应该使用哪一种,你可以根据自己的喜好来决定。
#
#		3.1.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等等 —— 都将路由到相同的函数。
#		3.2.捕获的参数永远都是字符串
#			每个在URLconf中捕获的参数都作为一个普通的Python字符串传递给视图,无论正则表达式使用的是什么匹配方式。例如,下面这行URLconf 中:	
			url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
#			传递到视图函数views.year_archive() 中的year参数永远是一个字符串类型。
#		3.3.视图函数中指定默认值
			# urls.py中
			from django.conf.urls import url
			from . import views

			urlpatterns = [
				url(r'^blog/$', views.page),
				url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
			]

			# views.py中,可以为num指定默认值
			def page(request, num="1"):
				pass		
#			在上面的例子中,两个URL模式指向相同的view - views.page - 但是第一个模式并没有从URL中捕获任何东西。
#			如果第一个模式匹配上了,page()函数将使用其默认参数num=“1”,如果第二个模式匹配,page()将使用正则表达式捕获到的num值。
#		3.4.include其他的URLconfs
			# django 1.x
				# 1.这是组件app01下的路由文件 urls.py
					from django.conf.urls import url
					from app01 import views
					
					urlpatterns = [
						url(r'show_li', views.show_li),
						url(r'^home/$', views.show_li),
						url(r'^home/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.home),
					]
				# 2.这是项目下的主路由文件 urls.py		
					#At any point, your urlpatterns can “include” other URLconf modules. This
					#essentially “roots” a set of URLs below other ones.

					#For example, here’s an excerpt of the URLconf for the Django website itself.
					#It includes a number of other URLconfs:

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

					urlpatterns = [
					   url(r'^admin/', admin.site.urls),
					   url(r'^app01/', include('app01.urls')),  # 可以包含其他的URLconfs文件
					]
			# django 2.x
				# 1.这是组件app下的路由文件 urls.py				
				#!/usr/bin/env python
				from django.urls import path
				from .views import LessionTwo
				 
				urlpatterns = [
				  path('two', LessionTwo.as_view(), name='two')
				]
				
				# 2.这是项目下的主路由文件 urls.py
				from django.contrib import admin
				from django.urls import path, include
				from app import urls as app_urls
				 
				urlpatterns = [
					path('admin/', admin.site.urls),
					path('', include(app_urls))
				]
##########################################################
# 4.命名URL和URL反向解析		
# 
# 在使用Django 项目时,一个常见的需求是获得URL的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。
# 人们强烈希望不要硬编码这些URL(费力、不可扩展且容易产生错误)或者设计一种与URLconf 毫不相关的专门的URL 生成机制,因为这样容易导致一定程度上产生过期的URL。
# 换句话讲,需要的是一个DRY 机制。除了其它有点,它还允许设计的URL 可以自动更新而不用遍历项目的源代码来搜索并替换过期的URL。		
# 获取一个URL 最开始想到的信息是处理它视图的标识(例如名字),查找正确的URL 的其它必要的信息有视图参数的类型(位置参数、关键字参数)和值。
# Django 提供一个办法是让URL 映射是URL 设计唯一的地方。你填充你的URLconf,然后可以双向使用它:
#	1.根据用户/浏览器发起的URL 请求,它调用正确的Django 视图,并从URL 中提取它的参数需要的值。
#	2.根据Django 视图的标识和将要传递给它的参数的值,获取与之关联的URL。
# 第一种方式是我们在前面的章节中一直讨论的用法。第二种方式叫做反向解析URL、反向URL 匹配、反向URL 查询或者简单的URL 反查。
# 在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:
# 	1.在模板中:使用url模板标签。
#	2.在Python 代码中:使用django.core.urlresolvers.reverse() 函数。
#	3.在更高层的与处理Django 模型实例相关的代码中:使用get_absolute_url() 方法。
# 上面说了一大堆,你可能并没有看懂。(那是官方文档的生硬翻译)。
# 咱们简单来说就是可以给我们的URL匹配规则起个名字,一个URL匹配模式起一个名字。
# 这样我们以后就不需要写死URL代码了,只需要通过名字来调用当前的URL。
#
# 举个简单的例子:
url(r'^home', views.home, name='home'),  # 给我的url匹配模式起名为 home
url(r'^index/(\d*)', views.index, name='index'),  # 给我的url匹配模式起名为index
#
# 在模板里面可以这样引用:
{% url 'home' %}
#
# 在views函数中可以这样引用:
from django.urls import reverse
reverse("index", args=("2018", ))
#
# 考虑下面的URLconf:
from django.conf.urls import url
from . import views
urlpatterns = [
    # ...
    url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'),
    # ...
]
# 根据这里的设计,某一年nnnn对应的归档的URL是/articles/nnnn/。
# 你可以在模板的代码中使用下面的方法获得它们:
<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>
<ul>
{% for yearvar in year_list %}
	<li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>
{% endfor %}
</ul>
# 在Python 代码中,这样使用:
from django.urls import reverse
from django.shortcuts import redirect
def redirect_to_year(request):
    # ...
    year = 2006
    # ...
    return redirect(reverse('news-year-archive', args=(year,)))
# 如果出于某种原因决定按年归档文章发布的URL应该调整一下,那么你将只需要修改URLconf 中的内容。
# 在某些场景中,一个视图是通用的,所以在URL 和视图之间存在多对一的关系。对于这些情况,当反查URL 时,只有视图的名字还不够。
# 注意:
# 	为了完成上面例子中的URL 反查,你将需要使用命名的URL 模式。URL 的名称使用的字符串可以包含任何你喜欢的字符。不只限制在合法的Python 名称。
# 	当命名你的URL 模式时,请确保使用的名称不会与其它应用中名称冲突。如果你的URL 模式叫做comment,而另外一个应用中也有一个同样的名称,
#	当你在模板中使用这个名称的时候不能保证将插入哪个URL。
#   在URL 名称中加上一个前缀,比如应用的名称,将减少冲突的可能。我们建议使用myapp-comment 而不是comment。
#
# url命名和反向解析
# 	1.命名
		# url(r'^press_list/$', views.press_list,name='press_list'),  
		url(r'^pre/$', views.press_list,name='press_list'),  		
		# 分组:
		url(r'^home/([0-9]{4})/([0-9]{2})/$', views.home,name='home'),
		#命名分组:
		url(r'^home/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.home,name='home'),
#	2.反向解析
		# 1.在视图中应用
			from django.urls import reverse	
			reverse('press_list') ——> '/press_list/'   '/pre/'	
			#分组:
			reverse('home',args=('2008','09')) ——> '/app01/home/2008/09/'	
			# 命名分组:
			reverse('home',args=('2008','09')) ——> '/app01/home/2008/09/'
			reverse('home',kwargs={'year':'2018', 'month':'10'})
		# 2.在模板中的应用
			{% url 'press_list' %} ——> '/press_list/'  '/pre/'
			# 分组:
			{% url 'home' '2009' '10' %} ——> '/app01/home/2008/10/'
			# 命名分组:
			{% url 'home' '2008' '10' %} ——> '/app01/home/2008/10/' 
			{% url 'home' month='10'  year='2018' %} ——> '/app01/home/2018/10/'
##########################################################
# 5.命名空间模式
#
# 即使不同的APP使用相同的URL名称,URL的命名空间模式也可以让你唯一反转命名的URL。
# 举个例子:
# project中的urls.py
from django.conf.urls import url, include
urlpatterns = [
    url(r'^app01/', include('app01.urls', namespace='app01')),
    url(r'^app02/', include('app02.urls', namespace='app02')),
]
# app01中的urls.py
from django.conf.urls import url
from app01 import views
app_name = 'app01'
urlpatterns = [
    url(r'^(?P<pk>\d+)/$', views.detail, name='detail')
]
# app02中的urls.py
from django.conf.urls import url
from app02 import views
app_name = 'app02'
urlpatterns = [
    url(r'^(?P<pk>\d+)/$', views.detail, name='detail')
]
# 现在,我的两个app中 url名称重复了,我反转URL的时候就可以通过命名空间的名称得到我当前的URL。
# 语法:
# '命名空间名称:URL名称'
# 模板中使用:
{% url 'app01:detail' pk=12 pp=99 %}
# views中的函数中使用
v = reverse('app01:detail', kwargs={'pk':11})
##########################
# namespace
url(r'app02/', include('app02.urls',namespace='app02')),
url(r'app01/',include('app01.urls',namespace='app01')),
reverse('app01:home',kwargs={'year':'2018','month':'10'})
reverse('app02:home',kwargs={'year':'2018','month':'10'})
{% url 'app02:home' '2018' '10' %}
##########################################################
# 5.删除功能三合一的实现
#	5.1.路由
		url(r'^delete_(press|book|author)/(\d+)/$', views.delete),  # 删除出版社
#	5.2.视图,使用反射
		from app01 import models
		def delete(request, table, del_id):
		# table_dict = {
		#     'press':Press,
		#     'book':Book,
		#     'author':Author,
		# }
		# table_class = table_dict.get(table)
		table_class = getattr(models, table.capitalize())
		table_class.objects.get(id=del_id).delete()
		return redirect(reverse(table))
####################################################################################################################
# ORM(Object Relational Mapping) 
# 	1.ORM 介绍
#		1.ORM概念
#			对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。
#			简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
#			ORM在业务逻辑层和数据库层之间充当了桥梁的作用。
#		2.ORM由来
#			让我们从O/R开始。字母O起源于"对象"(Object),而R则来自于"关系"(Relational)。
#			几乎所有的软件开发过程中都会涉及到对象和关系数据库。在用户层面和业务逻辑层面,我们是面向对象的。当对象的信息发生变化的时候,我们就需要把对象的信息保存在关系数据库中。
#			按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂很多SQL语句用来增加、读取、修改、删除相关数据,而这些代码通常都是极其相似或者重复的。
#		3.ORM的优势
#			ORM解决的主要问题是对象和关系的映射。它通常将一个类和一张表一一对应,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段。 
#			ORM提供了对数据库的映射,不用直接编写SQL代码,只需操作对象就能对数据库操作数据。
#			让软件开发人员专注于业务逻辑的处理,提高了开发效率。
#		4.ORM的劣势
#			ORM的缺点是会在一定程度上牺牲程序的执行效率。
#			ORM的操作是有限的,也就是ORM定义好的操作是可以完成的,一些复杂的查询操作是完成不了。
#			ORM用多了SQL语句就不会写了,关系数据库相关技能退化...
#		5.ORM总结
#			ORM只是一种工具,工具确实能解决一些重复,简单的劳动。这是不可否认的。
#			但我们不能指望某个工具能一劳永逸地解决所有问题,一些特殊问题还是需要特殊处理的。
#			但是在整个软件开发过程中需要特殊处理的情况应该都是很少的,否则所谓的工具也就失去了它存在的意义。
#######################################################
#	2.Django中的ORM
############################
#		1.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()
#				注:数据库迁移的时候出现一个警告
#					WARNINGS: 
#					?: (mysql.W002) MySQL Strict Mode is not set for database connection 'default'
#					HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL, such as data truncation upon insertion, by escalating warnings into errors. It is strongly recommended you activate it.
#				在配置中多加一个OPTIONS参数:Django官网解释
				'OPTIONS': {
					'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"
				},
############################
#		2.Model
#			在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常,一个模型(model)映射到一个数据库表。
#			基本情况:
# 				每个模型都是一个Python类,它是django.db.models.Model的子类。
#				模型的每个属性都代表一个数据库字段。
# 				综上所述,Django为您提供了一个自动生成的数据库访问API,详询官方文档链接。
############################
#		3.快速入门 
#			下面这个例子定义了一个 Person 模型,包含 first_name 和 last_name。
				from django.db import models
				class Person(models.Model):
					first_name = models.CharField(max_length=30)
					last_name = models.CharField(max_length=30)
#			first_name 和 last_name 是模型的字段。每个字段被指定为一个类属性,每个属性映射到一个数据库列。
#			上面的 Person 模型将会像这样创建一个数据库表:
				CREATE TABLE myapp_person (
					"id" serial NOT NULL PRIMARY KEY,
					"first_name" varchar(30) NOT NULL,
					"last_name" varchar(30) NOT NULL
				);
#			一些说明:
#				1.表myapp_person的名称是自动生成的,如果你要自定义表名,需要在model的Meta类中指定 db_table 参数,强烈建议使用小写表名,特别是使用MySQL作为数据库时。
#				2.id字段是自动添加的,如果你想要指定自定义主键,只需在其中一个字段中指定 primary_key=True 即可。如果Django发现你已经明确地设置了Field.primary_key,它将不会添加自动ID列。
#				3.本示例中的CREATE TABLE SQL使用PostgreSQL语法进行格式化,但值得注意的是,Django会根据配置文件中指定的数据库类型来生成相应的SQL语句。
#				4.Django支持MySQL5.5及更高版本。
############################
#		4.字段
#			常用字段 
#				AutoField
#					自增的整形字段,必填参数primary_key=True,则成为数据库的主键。无该字段时,django自动创建。
# 					一个model不能有两个AutoField字段。
#				IntegerField
# 					一个整数类型。数值的范围是 -2147483648 ~ 2147483647。
#				CharField
#					字符类型,必须提供max_length参数。max_length表示字符的长度。
#				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的实例。
#
#				字段类型,详情可点击查询官网。
#					AutoField(Field)
#						- int自增列,必须填入参数 primary_key=True
#				    BigAutoField(AutoField)
#						- bigint自增列,必须填入参数 primary_key=True
#        				注:当model中如果没有自增列,则自动会创建一个列名为id的列
						from django.db import models
						class UserInfo(models.Model):
							# 自动创建一个列名为id的且为自增的整数列
							username = models.CharField(max_length=32)
						class Group(models.Model):
							# 自定义自增列
							nid = models.AutoField(primary_key=True)
							name = models.CharField(max_length=32)
#				    SmallIntegerField(IntegerField):
#						- 小整数 -32768 ~ 32767
#					PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
#				        - 正小整数 0 ~ 32767
#					IntegerField(Field)
#				        - 整数列(有符号的) -2147483648 ~ 2147483647
#					PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
#				        - 正整数 0 ~ 2147483647
#					BigIntegerField(IntegerField):
#						- 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
#					BooleanField(Field)
#				        - 布尔值类型
# 					NullBooleanField(Field):
#				        - 可以为空的布尔值
#					CharField(Field)
#						- 字符类型
#						- 必须提供max_length参数, max_length表示字符长度
#					TextField(Field)
#						- 文本类型
#					EmailField(CharField):
#						- 字符串类型,Django Admin以及ModelForm中提供验证机制
#					IPAddressField(Field)
#						- 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制
#					GenericIPAddressField(Field)
#						- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
#						- 参数:
#							protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
#							unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both"
#					URLField(CharField)
#						- 字符串类型,Django Admin以及ModelForm中提供验证 URL
#					SlugField(CharField)
#						- 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)
#					CommaSeparatedIntegerField(CharField)
#						- 字符串类型,格式必须为逗号分割的数字
#					UUIDField(Field)
#						- 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证
#					FilePathField(Field)
#						- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
#						- 参数:
#								path,                      文件夹路径
#								match=None,                正则匹配
#								recursive=False,           递归下面的文件夹
#								allow_files=True,          允许文件
#								allow_folders=False,       允许文件夹
#					FileField(Field)
#						- 字符串,路径保存在数据库,文件上传到指定目录
#						- 参数:
#							upload_to = ""      上传文件的保存路径
#							storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
#					ImageField(FileField)
#						- 字符串,路径保存在数据库,文件上传到指定目录
#						- 参数:
#							upload_to = ""      上传文件的保存路径
#							storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
#							width_field=None,   上传图片的高度保存的数据库字段名(字符串)
#							height_field=None   上传图片的宽度保存的数据库字段名(字符串)
#					DateTimeField(DateField)
#						- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
#					DateField(DateTimeCheckMixin, Field)
#						- 日期格式      YYYY-MM-DD
#					TimeField(DateTimeCheckMixin, Field)
#						- 时间格式      HH:MM[:ss[.uuuuuu]]
#					DurationField(Field)
#						- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型
#					FloatField(Field)
#						- 浮点型
#					DecimalField(Field)
#						- 10进制小数
#						- 参数:
#							max_digits,小数总长度
#							decimal_places,小数位长度
#					BinaryField(Field)
#						- 二进制类型
############################
#		5.自定义字段
#			自定义一个二进制字段,以及Django字段与数据库字段类型的对应关系。
			class UnsignedIntegerField(models.IntegerField):
				def db_type(self, connection):
					return 'integer UNSIGNED'
			# PS: 返回值为字段在数据库中的属性。
			# Django字段与数据库字段类型对应关系如下:
				'AutoField': 'integer AUTO_INCREMENT',
				'BigAutoField': 'bigint AUTO_INCREMENT',
				'BinaryField': 'longblob',
				'BooleanField': 'bool',
				'CharField': 'varchar(%(max_length)s)',
				'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
				'DateField': 'date',
				'DateTimeField': 'datetime',
				'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
				'DurationField': 'bigint',
				'FileField': 'varchar(%(max_length)s)',
				'FilePathField': 'varchar(%(max_length)s)',
				'FloatField': 'double precision',
				'IntegerField': 'integer',
				'BigIntegerField': 'bigint',
				'IPAddressField': 'char(15)',
				'GenericIPAddressField': 'char(39)',
				'NullBooleanField': 'bool',
				'OneToOneField': 'integer',
				'PositiveIntegerField': 'integer UNSIGNED',
				'PositiveSmallIntegerField': 'smallint UNSIGNED',
				'SlugField': 'varchar(%(max_length)s)',
				'SmallIntegerField': 'smallint',
				'TextField': 'longtext',
				'TimeField': 'time',
				'UUIDField': 'char(32)',
#			自定义一个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)
############################					
#		6.字段参数
#			字段参数,详情可点击查看官网。		
#				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)			 
#				error_messages      自定义错误信息(字典类型),从而定制想要显示的错误信息;
#									字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date
#									如:{'null': "不能为空.", 'invalid': '格式错误'}			 
#				validators          自定义错误验证(列表类型),从而定制想要的验证规则
										from django.core.validators import RegexValidator
										from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
										MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
#										如:
											test = models.CharField(
												max_length=32,
												error_messages={
													'c1': '优先错信息1',
													'c2': '优先错信息2',
													'c3': '优先错信息3',
												},
												validators=[
													RegexValidator(regex='root_\d+', message='错误了', code='c1'),
													RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'),
													EmailValidator(message='又错误了', code='c3'), ]
											)
############################
#		7.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"),)   # 应为两个存在的字段
###############################
#		8.必知必会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
###############
#			8.1.返回QuerySet对象的方法有
#				all()
#				filter()
#				exclude()
#				order_by()
#				reverse()
#				distinct()
###############
#			8.2.特殊的QuerySet
#				values()       返回一个可迭代的字典序列
#				values_list()  返回一个可迭代的元祖序列
###############
#			8.3.返回具体对象的
#				get()
#				first()
#				last()
###############
#			8.4.返回布尔值的方法有
#				exists()
###############
#			8.5.返回数字的方法有
#				count()	
###############################
#		9.在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)	
###############################
#		10.单表查询之神奇的双下划线 
#			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)
###############################
#		11.外键查询
#			11.1.正向查找
#				11.1.1.对象查找(跨表)
#					语法:对象.关联字段.字段
						book_obj = models.Book.objects.first()  # 第一本书对象
						print(book_obj.publisher)  # 得到这本书关联的出版社对象
						print(book_obj.publisher.name)  # 得到出版社对象的名称
#				11.1.2.字段查找(跨表)		
#					语法:关联字段__字段
						print(models.Book.objects.values_list("publisher__name"))
#			11.2.反向操作
#				11.2.1.对象查找
#					语法:obj.表名_set
						publisher_obj = models.Publisher.objects.first()  # 找到第一个出版社对象
						books = publisher_obj.book_set.all()  # 找到第一个出版社出版的所有书
						titles = books.values_list("title")  # 找到第一个出版社出版的所有书的书名
#				11.2.2.字段查找
#					语法:表名__字段
						titles = models.Publisher.objects.values_list("book__title")			
###############################
#		12.多对多查询
#			12.1.class RelatedManager
#				"关联管理器"是在一对多或者多对多的关联上下文中使用的管理器。
# 				它存在于下面两种情况:
# 					外键关系的反向查询
# 					多对多关联关系
# 				简单来说就是当 点后面的对象 可能存在多个的时候就可以使用以下的方法。
#				12.1.1.方法
#					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()方法。								
###############################
#		13.聚合查询和分组查询
#			13.1.聚合
#				aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。
#				键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。
#				用到的内置函数:
					from django.db.models import Avg, Sum, Max, Min, Count
#				示例
					>>> from django.db.models import Avg, Sum, Max, Min, Count
					>>> models.Book.objects.all().aggregate(Avg("price"))
					{'price__avg': 13.233333}
#				如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
					>>> models.Book.objects.aggregate(average_price=Avg('price'))
					{'average_price': 13.233333}
#				如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:
					>>> models.Book.objects.all().aggregate(Avg("price"), Max("price"), Min("price"))
					{'price__avg': 13.233333, 'price__max': Decimal('19.90'), 'price__min': Decimal('9.90')}
#			13.2.分组
#				我们在这里先复习一下SQL语句的分组。
#				假设现在有一张公司职员表:			
#					employee		
#					id		name	age		salary		province		dept
#					1		张三	23		2000		河北			财务部
#					2		李四	34		4000		山东			人事部
#					3		王五	45      6000		北京			人事部
#					4		赵六	56		8000		南京			教学部
#				我们使用原生SQL语句,按照部门分组求平均工资:
					select dept,AVG(salary) from employee group by dept;
#				ORM查询
					from django.db.models import Avg
					Employee.objects.values("dept").annotate(avg=Avg("salary").values("dept", "avg")	
#				连表查询的分组
#					employee		
#					id		name	age		salary		province		dept		dept_id
#					1		张三	23		2000		河北			财务部		1
#					2		李四	34		4000		山东			人事部		2
#					3		王五	45      6000		北京			人事部		2
#					4		赵六	56		8000		南京			教学部		3			
#		
#					dept
#					id		name		
#					1	    财务部
#					2		人事部
#					3       教学部
#					4	    行政部
#				SQL查询
					select dept.name,AVG(salary) from employee inner join dept on (employee.dept_id=dept.id) group by dept_id;
#				ORM查询
					from django.db.models import Avg
					models.Dept.objects.annotate(avg=Avg("employee__salary")).values("name", "avg")
#				13.2.1.更多示例
#					示例1:统计每一本书的作者个数
						>>> book_list = models.Book.objects.all().annotate(author_num=Count("author"))
						>>> for obj in book_list:
						...     print(obj.author_num)
						...
						2
						1
						1
#					示例2:统计出每个出版社买的最便宜的书的价格	
						>>> publisher_list = models.Publisher.objects.annotate(min_price=Min("book__price"))
						>>> for obj in publisher_list:
						...     print(obj.min_price)
						...     
						9.90
						19.90
#					方法二:
						>>> models.Book.objects.values("publisher__name").annotate(min_price=Min("price"))
						<QuerySet [{'publisher__name': '沙河出版社', 'min_price': Decimal('9.90')}, {'publisher__name': '人民出版社', 'min_price': Decimal('19.90')}]>
#					示例3:统计不止一个作者的图书
						>>> models.Book.objects.annotate(author_num=Count("author")).filter(author_num__gt=1)
						<QuerySet [<Book: 番茄物语>]>	
#					示例4:根据一本图书作者数量的多少对查询集 QuerySet进行排序
						>>> models.Book.objects.annotate(author_num=Count("author")).order_by("author_num")
						<QuerySet [<Book: 香蕉物语>, <Book: 橘子物语>, <Book: 番茄物语>]>	
#					示例5:查询各个作者出的书的总价格
						>>> models.Author.objects.annotate(sum_price=Sum("book__price")).values("name", "sum_price")
						<QuerySet [{'name': '小精灵', 'sum_price': Decimal('9.90')}, {'name': '小仙女', 'sum_price': Decimal('29.80')}, {'name': '小魔女', 'sum_price': Decimal('9.90')}]>	
###############################
#		14.F查询和Q查询
#			14.1.F查询
#				在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
#				Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
#					示例1:查询评论数大于收藏数的书籍
						from django.db.models import F
						models.Book.objects.filter(commnet_num__gt=F('keep_num'))
#					Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。
						models.Book.objects.filter(commnet_num__lt=F('keep_num')*2)
#					修改操作也可以使用F函数,比如将每一本书的价格提高30元
						models.Book.objects.all().update(price=F("price")+30)
#					引申:
#						如果要修改char字段咋办?
#						如:把所有书名后面加上(第一版)	
							>>> from django.db.models.functions import Concat
							>>> from django.db.models import Value
							>>> models.Book.objects.all().update(title=Concat(F("title"), Value("("), Value("第一版"), Value(")")))
#			14.2.Q查询
#				filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。
#				示例1:查询作者名是小仙女或小魔女的
					models.Book.objects.filter(Q(authors__name="小仙女")|Q(authors__name="小魔女"))	
#				你可以组合&和|操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询。
#  				示例:查询作者名字是小仙女并且不是2018年出版的书的书名。
					>>> models.Book.objects.filter(Q(author__name="小仙女") & ~Q(publish_date__year=2018)).values_list("title")
					<QuerySet [('番茄物语',)]>
#				查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。
#				例如:查询出版年份是2017或2018,书名中带物语的所有书。					
					>>> models.Book.objects.filter(Q(publish_date__year=2018) | Q(publish_date__year=2017), title__icontains="物语")
					<QuerySet [<Book: 番茄物语>, <Book: 香蕉物语>, <Book: 橘子物语>]>
###############################
#		15.事务
			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)	
################################
#		16.其他鲜为人知的操作(有个印象即可)
#			16.1.Django ORM执行原生SQL
				# extra
# 				在QuerySet的基础上继续执行子语句
# 				extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

# 				select和select_params是一组,where和params是一组,tables用来设置from哪个表
# 				Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
# 				Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
# 				Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
# 				Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])

#				举个例子:
				models.UserInfo.objects.extra(
                    select={'newid':'select count(1) from app01_usertype where id>%s'},
                    select_params=[1,],
                    where = ['age>%s'],
                    params=[18,],
                    order_by=['-age'],
                    tables=['app01_usertype']
                )
                """
                select 
                    app01_userinfo.id,
                    (select count(1) from app01_usertype where id>1) as newid
                from app01_userinfo,app01_usertype
                where 
                    app01_userinfo.age > 18
                order by 
                    app01_userinfo.age desc
                """


# 				执行原生SQL
# 				更高灵活度的方式执行原生SQL语句
 					from django.db import connection, connections
 					cursor = connection.cursor()  # cursor = connections['default'].cursor()
					cursor.execute("""SELECT * from auth_user where id = %s""", [1])
 					row = cursor.fetchone()	
#			16.2.QuerySet方法大全
				##################################################################
				# PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
				##################################################################

				def all(self)
					# 获取所有的数据对象

				def filter(self, *args, **kwargs)
					# 条件查询
					# 条件可以是:参数,字典,Q

				def exclude(self, *args, **kwargs)
					# 条件查询
					# 条件可以是:参数,字典,Q

				def select_related(self, *fields)
					# 性能相关:表之间进行join连表操作,一次性获取关联的数据。

					# 总结:
					# 1. select_related主要针一对一和多对一关系进行优化。
					# 2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。

				def prefetch_related(self, *lookups)
					# 性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。

					# 总结:
					# 1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
					# 2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。

				def annotate(self, *args, **kwargs)
					# 用于实现聚合group by查询

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

					v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
					# SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id

					v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
					# SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1

					v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
					# SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1

				def distinct(self, *field_names)
					# 用于distinct去重
					models.UserInfo.objects.values('nid').distinct()
					# select distinct nid from userinfo

					# 注:只有在PostgreSQL中才能使用distinct进行去重

				def order_by(self, *field_names)
					# 用于排序
					models.UserInfo.objects.all().order_by('-id','age')

				def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
					# 构造额外的查询条件或者映射,如:子查询

					Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
					Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
					Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
					Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])

				 def reverse(self):
					# 倒序
					models.UserInfo.objects.all().order_by('-nid').reverse()
					# 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序


				 def defer(self, *fields):
					models.UserInfo.objects.defer('username','id')
					# 或
					models.UserInfo.objects.filter(...).defer('username','id')
					# 映射中排除某列数据

				 def only(self, *fields):
					# 仅取某个表中的数据
					 models.UserInfo.objects.only('username','id')
					 或
					 models.UserInfo.objects.filter(...).only('username','id')

				 def using(self, alias):
					# 指定使用的数据库,参数为别名(setting中的设置)


				##################################################
				# PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
				##################################################

				def raw(self, raw_query, params=None, translations=None, using=None):
					# 执行原生SQL
					models.UserInfo.objects.raw('select * from userinfo')

					# 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名
					models.UserInfo.objects.raw('select id as nid from 其他表')

					# 为原生SQL设置参数
					models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])

					# 将获取的到列名转换为指定列名
					name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
					Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)

					# 指定数据库
					models.UserInfo.objects.raw('select * from userinfo', using="default")

					################### 原生SQL ###################
					from django.db import connection, connections
					cursor = connection.cursor()  # cursor = connections['default'].cursor()
					cursor.execute("""SELECT * from auth_user where id = %s""", [1])
					row = cursor.fetchone() # fetchall()/fetchmany(..)


				def values(self, *fields):
					# 获取每行数据为字典格式

				def values_list(self, *fields, **kwargs):
					# 获取每行数据为元祖

				def dates(self, field_name, kind, order='ASC'):
					# 根据时间进行某一部分进行去重查找并截取指定内容
					# kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
					# order只能是:"ASC"  "DESC"
					# 并获取转换后的时间
						- year : 年-01-01
						- month: 年-月-01
						- day  : 年-月-日

					models.DatePlus.objects.dates('ctime','day','DESC')

				def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
					# 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间
					# kind只能是 "year", "month", "day", "hour", "minute", "second"
					# order只能是:"ASC"  "DESC"
					# tzinfo时区对象
					models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
					models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))

					"""
					pip3 install pytz
					import pytz
					pytz.all_timezones
					pytz.timezone(‘Asia/Shanghai’)
					"""

				def none(self):
					# 空QuerySet对象


				####################################
				# METHODS THAT DO DATABASE QUERIES #
				####################################

				def aggregate(self, *args, **kwargs):
				   # 聚合函数,获取字典类型聚合结果
				   from django.db.models import Count, Avg, Max, Min, Sum
				   result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
				   ===> {'k': 3, 'n': 4}

				def count(self):
				   # 获取个数

				def get(self, *args, **kwargs):
				   # 获取单个对象

				def create(self, **kwargs):
				   # 创建对象

				def bulk_create(self, objs, batch_size=None):
					# 批量插入
					# batch_size表示一次插入的个数
					objs = [
						models.DDD(name='r11'),
						models.DDD(name='r22')
					]
					models.DDD.objects.bulk_create(objs, 10)

				def get_or_create(self, defaults=None, **kwargs):
					# 如果存在,则获取,否则,创建
					# defaults 指定创建时,其他字段的值
					obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})

				def update_or_create(self, defaults=None, **kwargs):
					# 如果存在,则更新,否则,创建
					# defaults 指定创建时或更新时的其他字段
					obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})

				def first(self):
				   # 获取第一个

				def last(self):
				   # 获取最后一个

				def in_bulk(self, id_list=None):
				   # 根据主键ID进行查找
				   id_list = [11,21,31]
				   models.DDD.objects.in_bulk(id_list)

				def delete(self):
				   # 删除

				def update(self, **kwargs):
					# 更新

				def exists(self):
				   # 是否有结果
################################
#		17.Django终端打印SQL语句	
#			在Django项目的settings.py文件中,在最后复制粘贴如下代码
				LOGGING = {
					'version': 1,
					'disable_existing_loggers': False,
					'handlers': {
						'console':{
							'level':'DEBUG',
							'class':'logging.StreamHandler',
						},
					},
					'loggers': {
						'django.db.backends': {
							'handlers': ['console'],
							'propagate': True,
							'level':'DEBUG',
						},
					}
				}
#			即为你的Django项目配置上一个名为django.db.backends的logger实例即可查看翻译后的SQL语句。 	
####################################################################################################################
# cookie和session
#######################################################
#	1.Cookie
############################
#		1.1.Cookie的由来
#			大家都知道HTTP协议是无状态的。
#			无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。
#			一句有意思的话来描述就是人生只如初见,对服务器来说,每次的请求都是全新的。
#			状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。
############################
#		1.2.什么是Cookie
#			Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。
############################
#		1.3.Cookie的原理
#			cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。
############################
#		1.4.查看Cookie
#			我们使用Chrome浏览器,打开开发者工具。
#######################################################
#	2.Django中操作Cookie
############################
#		2.1.获取Cookie
			request.COOKIES['key']
			request.get_signed_cookie('key', default=RAISE_ERROR, salt='', max_age=None)
#			
#			get_signed_cookie方法的参数:
#				1.default: 默认值
#				2.salt: 加密盐
#				3.max_age: 后台控制过期时间
############################
#		2.2.设置Cookie
			rep = HttpResponse(...)
			rep = render(request, ...)
			rep.set_cookie(key,value,...)
			rep.set_signed_cookie(key,value,salt='加密盐',...)
#
#			参数:
#				1.key, 键
#				2.value='', 值
#				3.max_age=None, 超时时间
#				4.expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
#				5.path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
#				6.domain=None, Cookie生效的域名
#				7.secure=False, https传输
#				8.httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
############################
#		2.3.删除Cookie
			def logout(request):
				rep = redirect("/login/")
				rep.delete_cookie("user")  # 删除用户浏览器上之前设置的user的cookie值
				return rep
############################				
#		2.4.Cookie版登陆校验
			def check_login(func):
				@wraps(func)
				def inner(request, *args, **kwargs):
					next_url = request.get_full_path()
					if request.get_signed_cookie("login", salt="SSS", default=None) == "yes":
						# 已经登录的用户...
						return func(request, *args, **kwargs)
					else:
						# 没有登录的用户,跳转刚到登录页面
						return redirect("/login/?next={}".format(next_url))
				return inner


			def login(request):
				if request.method == "POST":
					username = request.POST.get("username")
					passwd = request.POST.get("password")
					if username == "xxx" and passwd == "dashabi":
						next_url = request.GET.get("next")
						if next_url and next_url != "/logout/":
							response = redirect(next_url)
						else:
							response = redirect("/class_list/")
						response.set_signed_cookie("login", "yes", salt="SSS")
						return response
				return render(request, "login.html")
#######################################################
#	3.Session
############################
#		3.1.Session的由来
#			Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,
#			因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。
#			问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。
#			我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,
#			在服务器上保存一段时间的私密资料,如“账号密码”等等。
#			总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,
#			对应的在Session里保存私密的信息以及超过4096字节的文本。
#			另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。
#######################################################
#	4.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数据,不删除cookie
		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失效策略。
############################			
#		4.1.Session版登陆验证
			from functools import wraps

			def check_login(func):
				@wraps(func)
				def inner(request, *args, **kwargs):
					next_url = request.get_full_path()
					if request.session.get("user"):
						return func(request, *args, **kwargs)
					else:
						return redirect("/login/?next={}".format(next_url))
				return inner


			def login(request):
				if request.method == "POST":
					user = request.POST.get("user")
					pwd = request.POST.get("pwd")

					if user == "alex" and pwd == "alex1234":
						# 设置session
						request.session["user"] = user
						# 获取跳到登陆页面之前的URL
						next_url = request.GET.get("next")
						# 如果有,就跳转回登陆之前的URL
						if next_url:
							return redirect(next_url)
						# 否则默认跳转到index页面
						else:
							return redirect("/index/")
				return render(request, "login.html")


			@check_login
			def logout(request):
				# 删除所有当前请求相关的session,不删除cookie
				request.session.delete()
				return redirect("/login/")


			@check_login
			def index(request):
				current_user = request.session.get("user", None)
				return render(request, "index.html", {"user": current_user})	
############################				
#		4.2.Django中的Session配置
			from django.conf import global_settings	
			from django.contrib.session.backends.db
#			Django中默认支持Session,其内部提供了5种类型的Session供开发者使用。
#				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'   # 引擎
#				6.其他公用设置项:
					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,默认修改之后才保存(默认)	
####################################################################################################################
# 实例:登录功能
#	1.包括功能
# 		1.1.cookie和session的使用
#		1.2.在登录前检查是否已经登录,使用装饰器
#		1.3.如果登录前有返回链接,如何登录后回到返回链接	
#		1.4.注销登录
#######################################################
#	2.新建项目login_demo 
		django-admin startproject login_demo
#######################################################
#	3.在项目login_demo下,新建应用app01
		cd login_demo
		python manage.py startapp app01
#######################################################
#	4.在项目login_demo下目录login_demo,修改文件 settings.py
############################
#		4.1.添加应用app01
			INSTALLED_APPS = [
				'django.contrib.admin',
				'django.contrib.auth',
				'django.contrib.contenttypes',
				'django.contrib.sessions',
				'django.contrib.messages',
				'django.contrib.staticfiles',
				'app01.apps.App01Config',
			]
############################			
#		4.2.在项目login_demo下添加目录templates
############################
#		4.3.在项目login_demo下目录login_demo,修改文件 settings.py,添加模板路径
			TEMPLATES = [
				{
					'BACKEND': 'django.template.backends.django.DjangoTemplates',
					'DIRS': [os.path.join(BASE_DIR, 'templates')],
					'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',
						],
					},
				},
			]
#######################################################
#	5.在项目login_demo下目录login_demo,修改文件 urls.py
		from django.contrib import admin
		from django.urls import path
		from app01 import views

		urlpatterns = [
			path('admin/', admin.site.urls),
			path('login/', views.login),
			path('home/', views.home),
			path('index/', views.index),
			path('logout/', views.logout),
		]		
#######################################################
#	6.在项目login_demo下目录app01,修改文件 views.py
		from django.shortcuts import render, redirect


		def login_required(func):
			def inner(request, *args, **kwargs):
				if not request.COOKIES.get('is_login') == '1':
					return_url = request.path_info
					return redirect('/login/?return_url={}'.format(return_url))
				ret = func(request, *args, **kwargs)
				return ret
			return inner


		def login(request):
			if request.method == "POST":
				user = request.POST.get('user')
				pwd = request.POST.get('pwd')
				if user == 'alex' and pwd == 'alex123456':
					return_url = request.GET.get('return_url')
					if return_url:
						ret = redirect(return_url)
					else:
						ret = redirect('/index/')
					ret.set_cookie('is_login', '1', max_age=5)
					return ret
			return render(request, 'login.html')


		@login_required
		def home(request):
			return render('这是home页面')


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


		def logout(request):
			request.session.delete()
			ret = redirect('/login/')
			# ret.delete_cookie('is_login')
			return ret
#######################################################
#	7.在项目login_demo下目录templates,修改文件login.html
		<!DOCTYPE html>
		<html lang="zh-CN">
		<head>
			<meta http-equiv="content-Type" charset="UTF-8">
			<meta http-equiv="x-ua-compatible" content="IE=edge">
			<meta name="viewport" content="width=device-width, initial-scale=1">
			<title>登录</title>
		</head>
		<body>
		<div>
			<form action="{{ request.get_full_path }}" method="post">
				{% csrf_token %}
				<p>
					用户名:
					<input type="text" name="user">
				</p>
				<p>
					密码:
					<input type="password" name="pwd">
				</p>
				<button type="submit">提交</button>
			</form>
		</div>
		</body>
		</html>	
#######################################################
#	8.在项目login_demo下目录templates,修改文件index.html 
		<!DOCTYPE html>
		<html lang="zh-CN">
		<head>
			<meta http-equiv="content-Type" charset="UTF-8">
			<meta http-equiv="x-ua-compatible" content="IE=edge">
			<meta name="viewport" content="width=device-width, initial-scale=1">
			<title>登录</title>
		</head>
		<body>
		<div>
			<form action="{{ request.get_full_path }}" method="post">
				{% csrf_token %}
				<p>
					用户名:
					<input type="text" name="user">
				</p>
				<p>
					密码:
					<input type="password" name="pwd">
				</p>
				<button type="submit">提交</button>
			</form>
		</div>	
#######################################################
#	9.在项目login_demo下目录templates,修改文件home.html
		<!DOCTYPE html>
		<html lang="zh-CN">
		<head>
			<meta http-equiv="content-Type" charset="UTF-8">
			<meta http-equiv="x-ua-compatible" content="IE=edge">
			<meta name="viewport" content="width=device-width, initial-scale=1">
			<title>Title</title>
		</head>
		<body>
		</body>
		</html>	
#######################################################
#	10.在项目login_demo下目录app01,备份文件 views.py 改为 view_cookie.py
#######################################################
#	11.在项目login_demo下目录app01,修改文件 views.py,把cookie改成session
		def login_required(func):
			def inner(request, *args, **kwargs):
				# if not request.COOKIES.get('is_login') == '1':
				if not request.session.get('is_login') == '1':
					return_url = request.path_info
					return redirect('/login/?return_url={}'.format(return_url))
				ret = func(request, *args, **kwargs)
				return ret
			return inner


		def login(request):
			if request.method == "POST":
				user = request.POST.get('user')
				pwd = request.POST.get('pwd')
				if user == 'alex' and pwd == 'alex123456':
					return_url = request.GET.get('return_url')
					if return_url:
						ret = redirect(return_url)
					else:
						ret = redirect('/index/')
					# ret.set_cookie('is_login', '1', max_age=5)
					request.session['is_login'] = '1'
					return ret
			return render(request, 'login.html')
#######################################################
#	12.测试后报错,原因是没有设置数据库
#######################################################
#	13.在项目login_demo下配置数据库文件db.sqlite3
#		13.1.在ide工具中配置好文件的路径,可能会提示需要下载安装,下载安装后点击测试按钮测试
#		13.2.生成后台数据,查看数据库,其中生成了一个表django_session
			python manage.py makemigrations
			python manage.py migrate
#######################################################
#	14.再次登录测试,没有问题了
#######################################################
#	15.打开表django_session,session数据保存在表里面了,默认过期时间是两周
#######################################################
#	12.测试后报错,原因是没有设置数据库			
####################################################################################################################
# Django的中间件
#	我们在前面的课程中已经学会了给视图函数加装饰器来判断是用户是否登录,把没有登录的用户请求跳转到登录页面。
#	我们通过给几个特定视图函数加装饰器实现了这个需求。但是以后添加的视图函数可能也需要加上装饰器,这样是不是稍微有点繁琐。
#	学完今天的内容之后呢,我们就可以用更适宜的方式来实现类似给所有请求都做相同操作的功能了。	
#######################################################
#	1.中间件介绍
############################
#		1.1.什么是中间件?
#			官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,
#			用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。
#			但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。
#			说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,
#			Django框架会在处理请求的特定的时间去执行这些方法。
#			我们一直都在使用中间件,只是没有注意到而已,打开Django项目的Settings.py文件,看到下图的MIDDLEWARE配置项。
				MIDDLEWARE = [
					'django.middleware.security.SecurityMiddleware',
					'django.contrib.sessions.middleware.SessionMiddleware',
					'django.middleware.common.CommonMiddleware',
					'django.middleware.csrf.CsrfViewMiddleware',
					'django.contrib.auth.middleware.AuthenticationMiddleware',
					'django.contrib.messages.middleware.MessageMiddleware',
					'django.middleware.clickjacking.XFrameOptionsMiddleware',
				]
#			MIDDLEWARE配置项是一个列表,列表中是一个个字符串,这些字符串其实是一个个类,也就是一个个中间件。
#			我们之前已经接触过一个csrf相关的中间件了?我们一开始让大家把他注释掉,再提交post请求的时候,就不会被forbidden了,后来学会使用csrf_token之后就不再注释这个中间件了。
#			那接下来就学习中间件中的方法以及这些方法什么时候被执行。	
#######################################################
#	2.自定义中间件
#		中间件可以定义五个方法,分别是:(主要的是process_request和process_response)
			process_request(self,request)
			process_view(self, request, view_func, view_args, view_kwargs)
			process_template_response(self,request,response)
			process_exception(self, request, exception)
			process_response(self, request, response)
#		以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。	
############################
#		2.1.自定义一个中间件示例
			from django.utils.deprecation import MiddlewareMixin

			class MD1(MiddlewareMixin):

				def process_request(self, request):
					print("MD1里面的 process_request")

				def process_response(self, request, response):
					print("MD1里面的 process_response")
					return response		
############################
#		2.2.process_request
#			process_request有一个参数,就是request,这个request和视图函数中的request是一样的。
#			它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,
#			Django将不执行视图函数,而将响应对象返回给浏览器。
#			我们来看看多个中间件时,Django是如何执行其中的process_request方法的。
				from django.utils.deprecation import MiddlewareMixin

				class MD1(MiddlewareMixin):

					def process_request(self, request):
						print("MD1里面的 process_request")

				class MD2(MiddlewareMixin):
					def process_request(self, request):
						print("MD2里面的 process_request")
#			在settings.py的MIDDLEWARE配置项中注册上述两个自定义中间件:
				MIDDLEWARE = [
					'django.middleware.security.SecurityMiddleware',
					'django.contrib.sessions.middleware.SessionMiddleware',
					'django.middleware.common.CommonMiddleware',
					'django.middleware.csrf.CsrfViewMiddleware',
					'django.contrib.auth.middleware.AuthenticationMiddleware',
					'django.contrib.messages.middleware.MessageMiddleware',
					'django.middleware.clickjacking.XFrameOptionsMiddleware',
					'middlewares.MD1',  # 自定义中间件MD1
					'middlewares.MD2'  # 自定义中间件MD2
				]			
#			此时,我们访问一个视图,会发现终端中打印如下内容:
				MD1里面的 process_request
				MD2里面的 process_request
				app01 中的 index视图
#			把MD1和MD2的位置调换一下,再访问一个视图,会发现终端中打印的内容如下:
				MD2里面的 process_request
				MD1里面的 process_request
				app01 中的 index视图
#			看结果我们知道:视图函数还是最后执行的,MD2比MD1先执行自己的process_request方法。
#			在打印一下两个自定义中间件中process_request方法中的request参数,会发现它们是同一个对象。
#			由此总结一下:
#				1.中间件的process_request方法是在执行视图函数之前执行的。
#				2.当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
#				3.不同中间件之间传递的request都是同一个对象			
############################					
#		2.3.process_response
#			它有两个参数,一个是request,一个是response,request就是上述例子中一样的对象,response是视图函数返回的HttpResponse对象。
#			该方法的返回值也必须是HttpResponse对象。给上述的M1和M2加上process_response方法:
				from django.utils.deprecation import MiddlewareMixin

				class MD1(MiddlewareMixin):

					def process_request(self, request):
						print("MD1里面的 process_request")

					def process_response(self, request, response):
						print("MD1里面的 process_response")
						return response

				class MD2(MiddlewareMixin):
					def process_request(self, request):
						print("MD2里面的 process_request")

					def process_response(self, request, response):
						print("MD2里面的 process_response")
						return response
#			访问一个视图,看一下终端的输出
				MD2里面的 process_request
				MD1里面的 process_request
				app01 中的 index视图
				MD1里面的 process_response
				MD2里面的 process_response		
#			看结果可知:
#				process_response方法是在视图函数之后执行的,并且顺序是MD1比MD2先执行。(此时settings.py中 MD2比MD1先注册)
#				多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,
#				而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。				
############################
#		2.4.process_view
#			process_view(self, request, view_func, view_args, view_kwargs)
#			该方法有四个参数
#				1.request是HttpRequest对象。
#				2.view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)
#				3.view_args是将传递给视图的位置参数的列表.
#				4.view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。
#			Django会在调用视图函数之前调用process_view方法。
#			它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 
#			如果它返回一个HttpResponse对象,Django不会调用适当的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。
#			给MD1和MD2添加process_view方法:			
				from django.utils.deprecation import MiddlewareMixin

				class MD1(MiddlewareMixin):

					def process_request(self, request):
						print("MD1里面的 process_request")

					def process_response(self, request, response):
						print("MD1里面的 process_response")
						return response

					def process_view(self, request, view_func, view_args, view_kwargs):
						print("-" * 80)
						print("MD1 中的process_view")
						print(view_func, view_func.__name__)

				class MD2(MiddlewareMixin):
					def process_request(self, request):
						print("MD2里面的 process_request")

					def process_response(self, request, response):
						print("MD2里面的 process_response")
						return response

					def process_view(self, request, view_func, view_args, view_kwargs):
						print("-" * 80)
						print("MD2 中的process_view")
						print(view_func, view_func.__name__)
#			访问index视图函数,看一下输出结果:			
				MD2里面的 process_request
				MD1里面的 process_request
				--------------------------------------------------------------------------------
				MD2 中的process_view
				<function index at 0x000001DE68317488> index
				--------------------------------------------------------------------------------
				MD1 中的process_view
				<function index at 0x000001DE68317488> index
				app01 中的 index视图
				MD1里面的 process_response
				MD2里面的 process_response
#			process_view方法是在process_request之后,视图函数之前执行的,执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的		
############################
#		2.5.process_exception
#			process_exception(self, request, exception)
#			该方法两个参数:
#				1.一个HttpRequest对象
#				2.一个exception是视图函数异常产生的Exception对象。
#			这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,
#			并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。
#			给MD1和MD2添加上这个方法:
				from django.utils.deprecation import MiddlewareMixin

				class MD1(MiddlewareMixin):

					def process_request(self, request):
						print("MD1里面的 process_request")

					def process_response(self, request, response):
						print("MD1里面的 process_response")
						return response

					def process_view(self, request, view_func, view_args, view_kwargs):
						print("-" * 80)
						print("MD1 中的process_view")
						print(view_func, view_func.__name__)

					def process_exception(self, request, exception):
						print(exception)
						print("MD1 中的process_exception")


				class MD2(MiddlewareMixin):
					def process_request(self, request):
						print("MD2里面的 process_request")

					def process_response(self, request, response):
						print("MD2里面的 process_response")
						return response

					def process_view(self, request, view_func, view_args, view_kwargs):
						print("-" * 80)
						print("MD2 中的process_view")
						print(view_func, view_func.__name__)

					def process_exception(self, request, exception):
						print(exception)
						print("MD2 中的process_exception")
#			如果视图函数中无异常,process_exception方法不执行。
#			想办法,在视图函数中抛出一个异常:			
				def index(request):
					print("app01 中的 index视图")
					raise ValueError("呵呵")
					return HttpResponse("O98K")
#			在MD1的process_exception中返回一个响应对象:
				class MD1(MiddlewareMixin):

					def process_request(self, request):
						print("MD1里面的 process_request")

					def process_response(self, request, response):
						print("MD1里面的 process_response")
						return response

					def process_view(self, request, view_func, view_args, view_kwargs):
						print("-" * 80)
						print("MD1 中的process_view")
						print(view_func, view_func.__name__)

					def process_exception(self, request, exception):
						print(exception)
						print("MD1 中的process_exception")
						return HttpResponse(str(exception))  # 返回一个响应对象
#			看输出结果:
				MD2里面的 process_request
				MD1里面的 process_request
				--------------------------------------------------------------------------------
				MD2 中的process_view
				<function index at 0x0000022C09727488> index
				--------------------------------------------------------------------------------
				MD1 中的process_view
				<function index at 0x0000022C09727488> index
				app01 中的 index视图
				呵呵
				MD1 中的process_exception
				MD1里面的 process_response
				MD2里面的 process_response
#			注意,这里并没有执行MD2的process_exception方法,因为MD1中的process_exception方法直接返回了一个响应对象。	
############################	
#		2.6.process_template_response(用的比较少)
#			process_template_response(self, request, response)
#			它的参数,一个HttpRequest对象,response是TemplateResponse对象(由视图函数或者中间件产生)。
#			process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。	
				class MD1(MiddlewareMixin):

					def process_request(self, request):
						print("MD1里面的 process_request")

					def process_response(self, request, response):
						print("MD1里面的 process_response")
						return response

					def process_view(self, request, view_func, view_args, view_kwargs):
						print("-" * 80)
						print("MD1 中的process_view")
						print(view_func, view_func.__name__)

					def process_exception(self, request, exception):
						print(exception)
						print("MD1 中的process_exception")
						return HttpResponse(str(exception))

					def process_template_response(self, request, response):
						print("MD1 中的process_template_response")
						return response


				class MD2(MiddlewareMixin):
					def process_request(self, request):
						print("MD2里面的 process_request")

					def process_response(self, request, response):
						print("MD2里面的 process_response")
						return response

					def process_view(self, request, view_func, view_args, view_kwargs):
						print("-" * 80)
						print("MD2 中的process_view")
						print(view_func, view_func.__name__)

					def process_exception(self, request, exception):
						print(exception)
						print("MD2 中的process_exception")

					def process_template_response(self, request, response):
						print("MD2 中的process_template_response")
						return response
#			views.py中:	
				def index(request):
					print("app01 中的 index视图")

					def render():
						print("in index/render")
						return HttpResponse("O98K")
					rep = HttpResponse("OK")
					rep.render = render
					return rep	
#			访问index视图,终端输出的结果:
				MD2里面的 process_request
				MD1里面的 process_request
				--------------------------------------------------------------------------------
				MD2 中的process_view
				<function index at 0x000001C111B97488> index
				--------------------------------------------------------------------------------
				MD1 中的process_view
				<function index at 0x000001C111B97488> index
				app01 中的 index视图
				MD1 中的process_template_response
				MD2 中的process_template_response
				in index/render
				MD1里面的 process_response
				MD2里面的 process_response	
#			从结果看出:
#				视图函数执行完之后,立即执行了中间件的process_template_response方法,顺序是倒序,先执行MD1的,在执行MD2的,
#				接着执行了视图函数返回的HttpResponse对象的render方法,返回了一个新的HttpResponse对象,接着执行中间件的process_response方法。				
#######################################################
#	3.中间件的执行流程
#		上一部分,我们了解了中间件中的5个方法,它们的参数、返回值以及什么时候执行,现在总结一下中间件的执行流程。
#		请求到达中间件之后,先按照正序执行每个注册中间件的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两个方法的触发是有条件的,执行顺序也是倒序。
#######################################################
# 	4.中间件版登录验证 
#		中间件版的登录验证需要依靠session,所以数据库中要有django_session表。
############################
#		4.1.urls.py	
			from django.conf.urls import url
			from django.contrib import admin
			from app01 import views

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

			
			def index(request):
				return HttpResponse('this is index')


			def home(request):
				return HttpResponse('this is home')

				
			def login(request):
				if request.method == "POST":
					user = request.POST.get("user")
					pwd = request.POST.get("pwd")

					if user == "alex" and pwd == "alex3714":
						# 设置session
						request.session["user"] = user
						# 获取跳到登陆页面之前的URL
						next_url = request.GET.get("next")
						# 如果有,就跳转回登陆之前的URL
						if next_url:
							return redirect(next_url)
						# 否则默认跳转到index页面
						else:
							return redirect("/index/")
				return render(request, "login.html")			
############################
#		4.3.login.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>登录页面</title>
			</head>
			<body>
			<form action="{% url 'login' %}" method="post">
				{% csrf_token %}
				<p>
					<label for="user">用户名:</label>
					<input type="text" name="user" id="user">
				</p>
				<p>
					<label for="pwd">密 码:</label>
					<input type="text" name="pwd" id="pwd">
				</p>
				<input type="submit" value="登录">
			</form>
			</body>
			</html>				
############################
#		4.4.middlewares.py
			from django.utils.deprecation import MiddlewareMixin

			class AuthMD(MiddlewareMixin):
				white_list = ['/login/', ]  # 白名单
				black_list = ['/black/', ]  # 黑名单

				def process_request(self, request):
					from django.shortcuts import redirect, HttpResponse

					next_url = request.path_info
					print(request.path_info, request.get_full_path())
					# 黑名单的网址限制访问
					if next_url in self.black_list:
						return HttpResponse('This is an illegal URL')
					# 白名单的网址或者登陆用户不做限制
					elif next_url in self.white_list or request.session.get("user"):
						return
					else:
						return redirect("/login/?next={}".format(next_url))			
############################
#		4.5.在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',
				'middlewares.AuthMD'
			]
#			AuthMD中间件注册后,所有的请求都要走AuthMD的process_request方法。
#			如果URL在黑名单中,则返回This is an illegal URL的字符串;
#			访问的URL在白名单内或者session中有user用户名,则不做阻拦走正常流程;
#			正常的URL但是需要登录后访问,让浏览器跳转到登录页面。
#			注:AuthMD中间件中需要session,所以AuthMD注册的位置要在session中间的下方。			
#######################################################
#	5.中间件中限制访问频率(1分钟只能访问3次)
############################
#		5.1.思路
#			1. 获取IP
#			2. 有访问记录
#			3. 根据访问记录做判断
############################
#		5.2.代码
			import time
			visit_dic = {}
			class Throttle(MiddlewareMixin):
				def process_request(self, request):
					# 1.获取IP
					ip = request.META.get('REMOTE_ADDR')
					# 2.有访问记录
					now = time.time()
					if not visit_dic.get(ip):
						visit_dic[ip] = []
					history = visit_dic[ip]
					# 3.根据访问记录做判断
					
					# 方法一
					# l = []
					# for i in history:
					# 	if now - i < 60:
					#		l.append(i)
					# visit_dic[ip] = l
					# history = visit_dic[ip]
					
					#方法二
					while histroy and now - history[-1] > 60:
						histroy.pop()
					
					if len(history) > 2:
						return HttpResponse('你的频率太快了')
					histroy.insert(0, now)			
#######################################################
#	6.CSRF中间件源码分析					
############################
#		6.1.CSRF跨站请求伪造					
############################	
#		6.2.补充两个装饰器
#			from django.views.decorators.csrf import csrf_exempt, csrf_protect
#			csrf_exempt   给单个视图排除校验
#			csrf_protect  给单个视图必须校验				
############################				
#		6.3.process_request
#			从请求的cookie中获取csrftoken的值 ——> csrf_token ——> request.META['CSRF_COOKIE']			
############################						
#		6.4.process_view
#			1.如果视图函数加上了csrf_exempt的装饰器,不做校验
#			2.如果请求方式是'GET', 'HEAD', 'OPTIONS', 'TRACE',也不做校验
#			3.其他的请求方式做校验
				request.META.get('CSRF_COOKIE') ——> csrf_token 
				request_csrf_token = "" 
				
				# 从request.POST中获取csrfmiddlewaretoken对应的值
				request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
				
				# 从请求头中获取X-csrftoken的值 
				request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')
				
				# request_csrf_token 和 csrf_token 做对比  
					# 如果校验成功 正常走
					# 如果校验不成功 拒绝
####################################################################################################################
# AJAX   						
#######################################################
#	1.JSON
##########################
#		1.1.什么是 JSON ?
#			1.JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)
#			2.JSON 是轻量级的文本数据交换格式
#			3.JSON 独立于语言 *
#			4.JSON 具有自我描述性,更易理解
#			JSON 使用 JavaScript 语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。
#			合格的json对象
				["one", "two", "three"]
				{ "one": 1, "two": 2, "three": 3 }
				{"names": ["张三", "李四"] }
				[ { "name": "张三"}, {"name": "李四"} ] 
#			不合格的json对象
				{ name: "张三", 'age': 32 }  // 属性名必须使用双引号
				[32, 64, 128, 0xFFF] // 不能使用十六进制值
				{ "name": "张三", "age": undefined }  // 不能使用undefined
				{ "name": "张三",
				  "birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),
				  "getName":  function() {return this.name;}  // 不能使用函数和日期对象
				}	
##########################
#		1.2.stringify与parse方法
#			JavaScript中关于JSON对象和字符串转换的两个方法:
#			JSON.parse():用于将一个 JSON 字符串转换为JavaScript对象
				JSON.parse('{"name":"Q1mi"}');
				JSON.parse('{name:"Q1mi"}') ;   // 错误
				JSON.parse('[18,undefined]') ;   // 错误 	
#			JSON.stringify(): 用于将 JavaScript 值转换为 JSON 字符串。
				JSON.stringify({"name":"Q1mi"})				
##########################				
#		1.3.和XML的比较
#			JSON 格式于2001年由 Douglas Crockford 提出,目的就是取代繁琐笨重的 XML 格式。
#			JSON 格式有两个显著的优点:书写简单,一目了然;符合 JavaScript 原生语法,可以由解释引擎直接处理,不用另外添加解析代码。
#			所以,JSON迅速被接受,已经成为各大网站交换数据的标准格式,并被写入ECMAScript 5,成为标准的一部分。
#			XML和JSON都使用结构化方法来标记数据,下面来做一个简单的比较。
#			用XML表示中国部分省市数据如下:
				<?xml version="1.0" encoding="utf-8"?>
					<country>
						<name>中国</name>
						<province>
							<name>黑龙江</name>
							<cities>
								<city>哈尔滨</city>
								<city>大庆</city>
							</cities>
						</province>
						<province>
							<name>广东</name>
							<cities>
								<city>广州</city>
								<city>深圳</city>
								<city>珠海</city>
							</cities>
						</province>
						<province>
							<name>台湾</name>
							<cities>
								<city>台北</city>
								<city>高雄</city>
							</cities>
						</province>
						<province>
							<name>新疆</name>
							<cities>
								<city>乌鲁木齐</city>
							</cities>
						</province>
					</country>
#			用JSON表示如下
				{
					"name": "中国",
					"province": [{
						"name": "黑龙江",
						"cities": {
							"city": ["哈尔滨", "大庆"]
						}
					}, {
						"name": "广东",
						"cities": {
							"city": ["广州", "深圳", "珠海"]
						}
					}, {
						"name": "台湾",
						"cities": {
							"city": ["台北", "高雄"]
						}
					}, {
						"name": "新疆",
						"cities": {
							"city": ["乌鲁木齐"]
						}
					}]
				}
#			由上面的两端代码可以看出,JSON 简单的语法格式和清晰的层次结构明显要比 XML 容易阅读,并且在数据交换方面,
#			由于 JSON 所使用的字符要比 XML 少得多,可以大大得节约传输数据所占用得带宽。 				
#######################################################
#	2.AJAX简介
#		AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)。
#		AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
#		AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)
#		AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。
#		同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
#		异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。		
##########################				
#		2.1.示例
#			页面输入两个整数,通过AJAX传输到后端计算出结果并返回。
#				1.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>AJAX局部刷新实例</title>
					</head>
					<body>

						<input type="text" id="i1">+
						<input type="text" id="i2">=
						<input type="text" id="i3">
						<input type="button" value="AJAX提交" id="b1">

						<script src="/static/jquery-3.2.1.min.js"></script>
						<script>
							$("#b1").on("click", function () {
								$.ajax({
									url:"/ajax_add/",
									type:"GET",
									data:{"i1":$("#i1").val(),"i2":$("#i2").val()},
									success:function (data) {
										$("#i3").val(data);
									}
								})
							})
						</script>
					</body>
					</html>
#				2.views.py
					def ajax_demo1(request):
						return render(request, "ajax_demo1.html")

					def ajax_add(request):
						i1 = int(request.GET.get("i1"))
						i2 = int(request.GET.get("i2"))
						ret = i1 + i2
						return JsonResponse(ret, safe=False)	
#				3.urls.py
					urlpatterns = [
						...
						url(r'^ajax_add/', views.ajax_add),
						url(r'^ajax_demo1/', views.ajax_demo1),
						...   
					]			
##########################	
#		2.2.AJAX常见应用情景
#			搜索引擎根据用户输入的关键字,自动提示检索关键字。
#			还有一个很重要的应用场景就是注册时候的用户名的查重。
#			其实这里就使用了AJAX技术!当文件框发生了输入变化时,使用AJAX技术向服务器发送一个请求,然后服务器会把查询到的结果响应给浏览器,最后再把后端返回的结果展示出来。
#				1.整个过程中页面没有刷新,只是刷新页面中的局部位置而已!
#				2.当请求发出后,浏览器还可以进行其他操作,无需等待服务器的响应!	
#			当输入用户名后,把光标移动到其他表单项上时,浏览器会使用AJAX技术向服务器发出请求,服务器会查询名为lemontree7777777的用户是否存在,
#			最终服务器返回true表示名为lemontree7777777的用户已经存在了,浏览器在得到结果后显示“用户名已被注册!”。
#			整个过程中页面没有刷新,只是局部刷新了;
#			在请求发出后,浏览器不用等待服务器响应结果就可以进行其他操作;
##########################
#		2.3.AJAX的优缺点
#			优点:
#				AJAX使用JavaScript技术向服务器发送异步请求;
#				AJAX请求无须刷新整个页面;
#				因为服务器响应内容不再是整个页面,而是页面中的部分内容,所以AJAX性能高; 					
#######################################################
#	3.jQuery实现的AJAX
#		最基本的jQuery发送AJAX请求示例
			<!DOCTYPE html>
			<html lang="zh-CN">
			<head>
				<meta charset="UTF-8">
				<meta http-equiv="x-ua-compatible" content="IE=edge">
				<meta name="viewport" content="width=device-width, initial-scale=1">
				<title>ajax test</title>
				<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
			</head>
			<body>
			<button id="ajaxTest">AJAX 测试</button>
			<script>
				$("#ajaxTest").click(function () {
					$.ajax({
						url: "/ajax_test/",
						type: "POST",
						data: {username: "Q1mi", password: 123456},
						success: function (data) {
							alert(data)
						}
					})
				})
			</script>
			</body>
			</html>
##########################
#		3.1.views.py
			def ajax_test(request):
				user_name = request.POST.get("username")
				password = request.POST.get("password")
				print(user_name, password)
				return HttpResponse("OK")		
##########################
#		3.2.$.ajax参数
#			data参数中的键值对,如果值值不为字符串,需要将其转换成字符串类型。
			$("#b1").on("click", function () {
				$.ajax({
					url:"/ajax_add/",
					type:"GET",
					data:{"i1":$("#i1").val(),"i2":$("#i2").val(),"hehe": JSON.stringify([1, 2, 3])},
					success:function (data) {
						$("#i3").val(data);
					}
				})
			})		
#######################################################
#	4.JS实现AJAX
		var b2 = document.getElementById("b2");
		b2.onclick = function () {
			// 原生JS
			var xmlHttp = new XMLHttpRequest();
			xmlHttp.open("POST", "/ajax_test/", true);
			xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			xmlHttp.send("username=q1mi&password=123456");
			xmlHttp.onreadystatechange = function () {
				if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
					alert(xmlHttp.responseText);
				}
			};
		};
#######################################################
#	5.AJAX请求如何设置csrf_token
##########################
#		5.1.方式1
#			通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送。
				$.ajax({
					url: "/cookie_ajax/",
					type: "POST",
					data: {
						"username": "Q1mi",
						"password": 123456,
						"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val()  // 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中
					},
					success: function (data) {
						console.log(data);
					}
				})		
##########################
#		5.2.方式2
#			通过获取返回的cookie中的字符串 放置在请求头中发送。
#			注意:需要引入一个jquery.cookie.js插件。
				$.ajax({
					url: "/cookie_ajax/",
					type: "POST",
					headers: {"X-CSRFToken": $.cookie('csrftoken')},  // 从Cookie取csrftoken,并设置到请求头中
					data: {"username": "Q1mi", "password": 123456},
					success: function (data) {
						console.log(data);
					}
				})
#			或者用自己写一个getCookie方法
				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');		
#			每一次都这么写太麻烦了,可以使用$.ajaxSetup()方法为ajax请求统一设置。
				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);
						}
					}
				});
#			注意:
#				1.如果使用从cookie中取csrftoken的方式,需要确保cookie存在csrftoken值。
#				2.如果你的视图渲染的HTML文件中没有包含 {% csrf_token %},Django可能不会设置CSRFtoken的cookie。
#				3.这个时候需要使用ensure_csrf_cookie()装饰器强制设置Cookie。
				django.views.decorators.csrf import ensure_csrf_cookie
				@ensure_csrf_cookie
				def login(request):
					pass			
#######################################################
#	6.AJAX上传文件
#		XMLHttpRequest 是一个浏览器接口,通过它,我们可以使得 Javascript 进行 HTTP (S) 通信。XMLHttpRequest 在现在浏览器中是一种常用的前后台交互数据的方式。
#		2008年 2 月,XMLHttpRequest Level 2 草案提出来了,相对于上一代,它有一些新的特性,其中 FormData 就是 XMLHttpRequest Level 2 新增的一个对象,利用它来提交表单、模拟表单提交,
#		当然最大的优势就是可以上传二进制文件。下面就具体
#		首先看一下formData的基本用法:FormData对象,可以把所有表单元素的name与value组成一个queryString,提交到后台。只需要把 form 表单作为参数传入 FormData 构造函数即可:
#		介绍一下如何利用 FormData 来上传文件。
			// 上传文件示例
			$("#b3").click(function () {
				var formData = new FormData();
				formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
				formData.append("f1", $("#f1")[0].files[0]);
				$.ajax({
					url: "/upload/",
					type: "POST",
					processData: false,  // 告诉jQuery不要去处理发送的数据
					contentType: false,  // 告诉jQuery不要去设置Content-Type请求头
					data: formData,
					success:function (data) {
						console.log(data)
					}
				})
			})
#		或者使用
			var form = document.getElementById("form1");
			var fd = new FormData(form);
#			这样也可以直接通过ajax 的 send() 方法将 fd 发送到后台。
#			注意:由于 FormData 是 XMLHttpRequest Level 2 新增的接口,现在 低于IE10 的IE浏览器不支持 FormData。			
############################
#		6.1.练习(用户名是否已被注册)
#			6.1.1.功能介绍
#				在注册表单中,当用户填写了用户名后,把光标移开后,会自动向服务器发送异步请求。服务器返回这个用户名是否已经被注册过。				
#			6.1.2.案例分析
#				页面中给出注册表单;
#				在username input标签中绑定onblur事件处理函数。
#				当input标签失去焦点后获取 username表单字段的值,向服务端发送AJAX请求;
#				django的视图函数中处理该请求,获取username值,判断该用户在数据库中是否被注册,如果被注册了就返回“该用户已被注册”,否则响应“该用户名可以注册”。
#######################################################
#	7.序列化
#		7.1.Django内置的serializers
			def books_json(request):
				book_list = models.Book.objects.all()[0:10]
				from django.core import serializers
				ret = serializers.serialize("json", book_list)
				return HttpResponse(ret)
#######################################################
#	8.补充一个SweetAlert插件示例
#		点击下载Bootstrap-sweetalert项目https://github.com/lipis/bootstrap-sweetalert
			$(".btn-danger").on("click", function () {
				swal({
					title: "你确定要删除吗?",
					text: "删除可就找不回来了哦!",
					type: "warning",
					showCancelButton: true,
					confirmButtonClass: "btn-danger",
					confirmButtonText: "删除",
					cancelButtonText: "取消",
					closeOnConfirm: false
				},
				function () {
					var deleteId = $(this).parent().parent().attr("data_id");
					$.ajax({
						url: "/delete_book/",
						type: "post",
						data: {"id": deleteId},
						success: function (data) {
							if (data.status === 1) {
								swal("删除成功!", "你可以准备跑路了!", "success");
							} else {
								swal("删除失败", "你可以再尝试一下!", "error")
							}
						}
					})
				});
			})	

  

posted on 2020-04-03 10:25  herisson_pan  阅读(8)  评论(0)    收藏  举报

导航