一、MVC和MTV模式
MVC:将web应用分为模型(M),控制器(C),视图(V)三层;他们之间以一种插件似的,松耦合的方式连接在一起。
模型负责业务对象与数据库的对象(ORM),视图负责与用户的交互(页面),控制器(C)接受用户的输入调用模型和视图完成用户的请求。
Django的MTV模型本质上与MVC没有什么差别,也是各组件之间为了保持松耦合关系,只不过定义上有些不同,Django的MTV分别是:
• Model(模型):负责业务对象与数据库的对象(ORM)
• Template(模板):负责如何把页面展示给用户
• View(视图):负责业务逻辑,并在适当的时候调用Model和Template
二 Django的流程和命令行工具
1 django 2 #安装: pip3 install django 3 4 添加环境变量 5 6 #1 创建project 7 django-admin startproject mysite 8 9 ---mysite 10 11 ---settings.py 12 ---url.py 13 ---wsgi.py 14 15 ---- manage.py(启动文件) 16 17 #2 创建APP 18 python mannage.py startapp app01 19 20 #3 settings配置 21 22 TEMPLATES 23 24 STATICFILES_DIRS=( 25 os.path.join(BASE_DIR,"statics"), 26 ) 27 28 STATIC_URL = ‘/static/‘ 29 # 我们只能用 STATIC_URL,但STATIC_URL会按着你的STATICFILES_DIRS去找#4 根据需求设计代码 30 url.py 31 view.py 32 33 #5 使用模版 34 render(req,"index.html") 35 36 #6 启动项目 37 python manage.py runserver 127.0.0.1:8090 38 39 #7 连接数据库,操作数据 40 model.py
命令行创建
文件介绍:
• manage.py --- Django项目里面的工具,通过它可以调用django shell和数据库等
• settings.py --- 包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量。
• urls.py --- 负责把url模式映射到应用程序
- 生成同步数据库的脚本:python manage.py makemigrations
同步数据库:python manage.py migrate
注:开发过程中,数据库同步误操作之后,难免会遇到后面不能同步成功的情况,解决这个问题的一个粗暴方法是将migrations目录下的脚本(除了__init__.py之外)全部删掉,再把数据库删掉之后创建一个新的数据库,数据库同步操作再重新做一遍。
-- 访问http://127.0.0.1:8000/admin/login/?next=/admin/时,需要为进入这个项目后台创建超级管理员:python manage.py createsuperuser,设置好账户密码就可以登陆
-- 清空数据库:python manage.py flush
-- 查询某个命令的详细信息:django-admin.py help startapp
-- 启动交互界面:python manage.py shell(和直接运行python进入shell的区别是:你可以在这个shell里面调用当前项目的models.py中的API,对于操作数据,还有一些小测试非常方便)
-- 终端上输入python manage.py 可以看到详细的列表,在忘记子名称的时候特别有用
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <h1>创建个人信息</h1> 9 10 <form action="/userInfor/" method="post"> 11 12 <p>姓名<input type="text" name="username"></p> 13 <p>性别<input type="text" name="sex"></p> 14 <p>邮箱<input type="text" name="email"></p> 15 <p><input type="submit" value="submit"></p> 16 17 </form> 18 19 <hr> 20 21 <h1>信息展示</h1> 22 23 <table border="1"> 24 25 <tr> 26 <td>姓名</td> 27 <td>性别</td> 28 <td>邮箱</td> 29 </tr> 30 {% for i in info_list %} 31 32 <tr> 33 <td>{{ i.username }}</td> 34 <td>{{ i.sex }}</td> 35 <td>{{ i.email }}</td> 36 </tr> 37 38 {% endfor %} 39 40 </table> 41 42 </body> 43 </html> 44 45 46 -----------------------url.py--------------------------------------- 47 url(r‘^userInfor/‘, views.userInfor) 48 49 -----------------------views.py-------------------------------------- 50 51 info_list=[] 52 53 def userInfor(req): 54 55 if req.method=="POST": 56 username=req.POST.get("username",None) 57 sex=req.POST.get("sex",None) 58 email=req.POST.get("email",None) 59 60 info={"username":username,"sex":sex,"email":email} 61 info_list.append(info) 62 63 return render(req,"userInfor.html",{"info_list":info_list})
提交数据并展示
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <h1>创建个人信息</h1> 9 10 <form action="/userInfor/" method="post"> 11 12 <p>姓名<input type="text" name="username"></p> 13 <p>性别<input type="text" name="sex"></p> 14 <p>邮箱<input type="text" name="email"></p> 15 <p><input type="submit" value="submit"></p> 16 17 </form> 18 19 <hr> 20 21 <h1>信息展示</h1> 22 23 <table border="1"> 24 25 <tr> 26 <td>姓名</td> 27 <td>性别</td> 28 <td>邮箱</td> 29 </tr> 30 {% for i in info_list %} 31 32 <tr> 33 <td>{{ i.username }}</td> 34 <td>{{ i.sex }}</td> 35 <td>{{ i.email }}</td> 36 </tr> 37 38 {% endfor %} 39 40 </table> 41 42 </body> 43 </html> 44 45 46 ----------------------------------------------models.py 47 from django.db import models 48 49 # Create your models here. 50 51 52 class UserInfor(models.Model): 53 54 username=models.CharField(max_length=64) 55 sex=models.CharField(max_length=64) 56 email=models.CharField(max_length=64) 57 58 ----------------------------------------------views.py 59 60 from django.shortcuts import render 61 62 from app01 import models 63 # Create your views here. 64 65 66 def userInfor(req): 67 68 if req.method=="POST": 69 u=req.POST.get("username",None) 70 s=req.POST.get("sex",None) 71 e=req.POST.get("email",None) 72 73 74 #---------表中插入数据方式一 75 # info={"username":u,"sex":e,"email":e} 76 # models.UserInfor.objects.create(**info) 77 78 #---------表中插入数据方式二 79 models.UserInfor.objects.create( 80 username=u, 81 sex=s, 82 email=e 83 ) 84 85 info_list=models.UserInfor.objects.all() 86 87 return render(req,"userInfor.html",{"info_list":info_list}) 88 89 return render(req,"userInfor.html")
提交数据并展示(数据库)
三 Django URL(路由系统)
URL配置(URLconf)就像Django所支持网站的目录。它的本质是URL模式以及要为该URL模式调用的视图函数之间的映射表;就是以这种方式告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码。
1 urlpatterns = [ 2 url(正则表达式,views视图函数,参数,别名), 3 ]
1 from django.conf.urls import url 2 from django.contrib import admin 3 4 from app01 import views 5 6 urlpatterns = [ 7 8 url(r‘^articles/2003/$‘, views.special_case_2003), 9 10 #url(r‘^articles/[0-9]{4}/$‘, views.year_archive), 11 12 url(r‘^articles/([0-9]{4})/$‘, views.year_archive), #no_named group 13 14 url(r‘^articles/([0-9]{4})/([0-9]{2})/$‘, views.month_archive), 15 16 url(r‘^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$‘, views.article_detail), 17 18 ]
示例
四 Django Views(视图函数)
Http请求中产生两个核心对象:
http请求:HttpRequest对象
http响应:HttpResponse对象
1 # path: 请求页面的全路径,不包括域名 2 # 3 # method: 请求中使用的HTTP方法的字符串表示。全大写表示。例如 4 # 5 # if req.method=="GET": 6 # 7 # do_something() 8 # 9 # elseif req.method=="POST": 10 # 11 # do_something_else() 12 # 13 # GET: 包含所有HTTP GET参数的类字典对象 14 # 15 # POST: 包含所有HTTP POST参数的类字典对象 16 # 17 # 服务器收到空的POST请求的情况也是可能发生的,也就是说,表单form通过 18 # HTTP POST方法提交请求,但是表单中可能没有数据,因此不能使用 19 # if req.POST来判断是否使用了HTTP POST 方法;应该使用 if req.method=="POST" 20 # 21 # 22 # 23 # COOKIES: 包含所有cookies的标准Python字典对象;keys和values都是字符串。 24 # 25 # FILES: 包含所有上传文件的类字典对象;FILES中的每一个Key都是<input type="file" name="" />标签中 name属性的值,FILES中的每一个value同时也是一个标准的python字典对象,包含下面三个Keys: 26 # 27 # filename: 上传文件名,用字符串表示 28 # content_type: 上传文件的Content Type 29 # content: 上传文件的原始内容 30 # 31 # 32 # user: 是一个django.contrib.auth.models.User对象,代表当前登陆的用户。如果访问用户当前 33 # 没有登陆,user将被初始化为django.contrib.auth.models.AnonymousUser的实例。你 34 # 可以通过user的is_authenticated()方法来辨别用户是否登陆: 35 # if req.user.is_authenticated();只有激活Django中的AuthenticationMiddleware 36 # 时该属性才可用 37 # 38 # session: 唯一可读写的属性,代表当前会话的字典对象;自己有激活Django中的session支持时该属性才可用。 39 40 #方法 41 get_full_path(), 比如:http://127.0.0.1:8000/index33/?name=123 ,req.get_full_path()得到的结果就是/index33/?name=123 42 req.path:/index33
HttpResponse对象的属性和方法
注:常用request.POST.getlist(‘‘)
对于HttpReques对象来说,是由django自动创建的。但是,HttpResponse对象就必须我们自己创建。每个view请求处理方法必须返回一个HttpResponse对象。
HttpResponse类在django.http.HttpResponse
1 HttpResponse对象上扩展的常用方法: 2 3 页面渲染: render() render_to_response() 4 页面跳转: redirect("路径") 5 locals():可以直接将函数中所有的变量传给模板
五 Template基础
模板语法
• 模板的组成
1 HTML代码+逻辑控制代码
• 逻辑控制代码的组成
1、变量(使用双大括号来引用变量)
1 格式: {{ var_name }}
---------Template和Context对象
Django模板解析非常便捷。大部分的解析工作都是在后台通过对简短正则表达式一次性调用来完成。相比于XML的模板引擎形成鲜明对比,那些引擎承担了XML解析器的开销,且往往比Django模板渲染引擎要慢的多。
1 from django.shortcuts import render,HttpResponse 2 from django.template.loader import get_template #记得导入 3 # Create your views here. 4 5 6 import datetime 7 from django.template import Template,Context 8 9 # def current_time(req): 10 #原始的视图函数 11 # now=datetime.datetime.now() 12 # html="<html><body>现在时刻:<h1>%s.</h1></body></html>" %now 13 # return HttpResponse(html) 14 15 16 17 # def current_time(req): 18 19 #django模板修改的视图函数 20 # now=datetime.datetime.now() 21 # t=Template(‘<html><body>现在时刻是:<h1 style="color:red">{{current_date}}</h1></body></html>‘) 22 #t=get_template(‘current_datetime.html‘) 23 # c=Context({‘current_date‘:now}) 24 # html=t.render(c) 25 # return HttpResponse(html) 26 27 #另一种写法(推荐) 28 29 def current_time(req): 30 31 now=datetime.datetime.now() 32 33 return render(req, ‘current_datetime.html‘, {‘current_date‘:now}) 34 35 推荐方式
推荐
---------深度变量的查找(万能的句点号)
以上的例子中,通过context传递的简单参数值主要是字符串,然而,模板系统能够非常简洁地处理更加复杂的数据结构,如:list、dictionary和自定义的对象。
Django模板中遍历复杂数据结构的关键是句点字符(.)。
1 #最好是用几个例子来说明一下。 2 # 首先,句点可用于访问列表索引,例如: 3 4 >>> from django.template import Template, Context 5 >>> t = Template(‘Item 2 is {{ items.2 }}.‘) 6 >>> c = Context({‘items‘: [‘apples‘, ‘bananas‘, ‘carrots‘]}) 7 >>> t.render(c) 8 ‘Item 2 is carrots.‘ 9 10 #假设你要向模板传递一个 Python 字典。 要通过字典键访问该字典的值,可使用一个句点: 11 >>> from django.template import Template, Context 12 >>> person = {‘name‘: ‘Sally‘, ‘age‘: ‘43‘} 13 >>> t = Template(‘{{ person.name }} is {{ person.age }} years old.‘) 14 >>> c = Context({‘person‘: person}) 15 >>> t.render(c) 16 ‘Sally is 43 years old.‘ 17 18 #同样,也可以通过句点来访问对象的属性。 比方说, Python 的 datetime.date 对象有 19 #year 、 month 和 day 几个属性,你同样可以在模板中使用句点来访问这些属性: 20 21 >>> from django.template import Template, Context 22 >>> import datetime 23 >>> d = datetime.date(1993, 5, 2) 24 >>> d.year 25 >>> d.month 26 >>> d.day 27 >>> t = Template(‘The month is {{ date.month }} and the year is {{ date.year }}.‘) 28 >>> c = Context({‘date‘: d}) 29 >>> t.render(c) 30 ‘The month is 5 and the year is 1993.‘ 31 32 # 这个例子使用了一个自定义的类,演示了通过实例变量加一点(dots)来访问它的属性,这个方法适 33 # 用于任意的对象。 34 >>> from django.template import Template, Context 35 >>> class Person(object): 36 ... def __init__(self, first_name, last_name): 37 ... self.first_name, self.last_name = first_name, last_name 38 >>> t = Template(‘Hello, {{ person.first_name }} {{ person.last_name }}.‘) 39 >>> c = Context({‘person‘: Person(‘John‘, ‘Smith‘)}) 40 >>> t.render(c) 41 ‘Hello, John Smith.‘ 42 43 # 点语法也可以用来引用对象的方法。 例如,每个 Python 字符串都有 upper() 和 isdigit() 44 # 方法,你在模板中可以使用同样的句点语法来调用它们: 45 >>> from django.template import Template, Context 46 >>> t = Template(‘{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}‘) 47 >>> t.render(Context({‘var‘: ‘hello‘})) 48 ‘hello -- HELLO -- False‘ 49 >>> t.render(Context({‘var‘: ‘123‘})) 50 ‘123 -- 123 -- True‘ 51 52 # 注意这里调用方法时并* 没有* 使用圆括号 而且也无法给该方法传递参数;你只能调用不需参数的 53 # 方法。
---------变量的过滤器(filter)的使用
1 格式: {{obj|filter:param}}
1 # 1 add : 给变量加上相应的值 2 # 3 # 2 addslashes : 给变量中的引号前加上斜线 4 # 5 # 3 capfirst : 首字母大写 6 # 7 # 4 cut : 从字符串中移除指定的字符 8 # 9 # 5 date : 格式化日期字符串 10 # 11 # 6 default : 如果值是False,就替换成设置的默认值,否则就是用本来的值 12 # 13 # 7 default_if_none: 如果值是None,就替换成设置的默认值,否则就使用本来的值 14 15 16 #实例: 17 18 #value1="aBcDe" 19 {{ value1|upper }}<br> 20 21 #value2=5 22 {{ value2|add:3 }}<br> 23 24 #value3=‘he llo wo r ld‘ 25 {{ value3|cut:‘ ‘ }}<br> 26 27 #import datetime 28 #value4=datetime.datetime.now() 29 {{ value4|date:‘Y-m-d‘ }}<br> 30 31 #value5=[] 32 {{ value5|default:‘空的‘ }}<br> 33 34 #value6=‘<a href="#">跳转</a>‘ 35 36 {{ value6 }} 37 38 {% autoescape off %} 39 {{ value6 }} 40 {% endautoescape %} 41 42 {{ value6|safe }}<br> 43 44 {{ value6|striptags }} 45 46 #value7=‘1234‘ 47 {{ value7|filesizeformat }}<br> 48 {{ value7|first }}<br> 49 {{ value7|length }}<br> 50 {{ value7|slice:":-1" }}<br> 51 52 #value8=‘http://www.baidu.com/?a=1&b=3‘ 53 {{ value8|urlencode }}<br> 54 value9=‘hello I am yuan‘
2、标签(tag)的使用(使用大括号和百分比的组合来表示使用tag)
1 {%tags%}
---------{% if %} 的使用
{% if %}标签计算一个变量值,如果是"true",即它不存在、不为空并且不是false的boolean值,系统则会显示{% if %}和{% endif %}间的所有内容
1 {% if num >= 100 and 8 %} 2 3 {% if num > 200 %} 4 <p>num大于200</p> 5 {% else %} 6 <p>num大于100小于200</p> 7 {% endif %} 8 9 {% elif num < 100%} 10 <p>num小于100</p> 11 12 {% else %} 13 <p>num等于100</p> 14 15 {% endif %} 16 17 18 19 {% if %} 标签接受and,or或者not来测试多个变量值或者否定一个给定的变量 20 {% if %} 标签不允许同一标签里同时出现and和or,否则逻辑容易产生歧义,例如下面的标签是不合法的: 21 22 {% if obj1 and obj2 or obj3 %}
---------{% for %} 的使用
{% for %}标签允许你按顺序遍历一个序列中的各个元素,每次循环模板系统都会渲染{% for %}和{% endfor %}之间的所有哦内容
1 <ul> 2 {% for obj in list %} 3 <li>{{ obj.name }}</li> 4 {% endfor %} 5 </ul> 6 7 8 #在标签里添加reversed来反序循环列表: 9 10 {% for obj in list reversed %} 11 ... 12 {% endfor %} 13 14 #{% for %}标签可以嵌套: 15 16 {% for country in countries %} 17 <h1>{{ country.name }}</h1> 18 <ul> 19 {% for city in country.city_list %} 20 <li>{{ city }}</li> 21 {% endfor %} 22 </ul> 23 {% endfor %} 24 25 26 #系统不支持中断循环,系统也不支持continue语句,{% for %}标签内置了一个forloop模板变量, 27 #这个变量含有一些属性可以提供给你一些关于循环的信息 28 29 1,forloop.counter表示循环的次数,它从1开始计数,第一次循环设为1: 30 31 {% for item in todo_list %} 32 <p>{{ forloop.counter }}: {{ item }}</p> 33 {% endfor %} 34 2,forloop.counter0 类似于forloop.counter,但它是从0开始计数,第一次循环设为0 35 3,forloop.revcounter 36 4,forloop.revcounter0 37 5,forloop.first当第一次循环时值为True,在特别情况下很有用: 38 39 40 {% for object in objects %} 41 {% if forloop.first %}<li class="first">{% else %}<li>{% endif %} 42 {{ object }} 43 </li> 44 {% endfor %} 45 46 # 富有魔力的forloop变量只能在循环中得到,当模板解析器到达{% endfor %}时forloop就消失了 47 # 如果你的模板context已经包含一个叫forloop的变量,Django会用{% for %}标签替代它 48 # Django会在for标签的块中覆盖你定义的forloop变量的值 49 # 在其他非循环的地方,你的forloop变量仍然可用 50 51 52 #{% empty %} 53 54 {{li }} 55 {% for i in li %} 56 <li>{{ forloop.counter0 }}----{{ i }}</li> 57 {% empty %} 58 <li>this is empty!</li> 59 {% endfor %} 60 61 # [11, 22, 33, 44, 55] 62 # 0----11 63 # 1----22 64 # 2----33 65 # 3----44 66 # 4----55
---------{% csrf_token%}:csrf_token标签
用于生成csrf_token的标签,用于防治跨站攻击验证。注:若在view的index里用的是render_to_response方法,不会生效
这里生成一个input标签,和其他表单标签一起提交给 后台
---------{% url %}:引用路由配置的地址
1 <form action="{% url "bieming"%}" > 2 <input type="text"> 3 <input type="submit"value="提交"> 4 {%csrf_token%} 5 </form>
---------{% with %}:用更简单的变量名替代复杂的变量名
1 {% with total=fhjsaldfhjsdfhlasdfhljsdal %} {{ total }} {% endwith %}
---------{% verbatim%}:禁止render
1 {% verbatim %} 2 {{ hello }} 3 {% endverbatim %}
---------{% load%}:加载标签库
3、自定义filter和simple_tag
a.在app中创建templatetags模块(必须的)
b.创建任意.py文件,如:my_tags.py
1 from django import template 2 from django.utils.safestring import mark_safe 3 4 register = template.Library() #register的名字是固定的,不可改变 5 6 7 @register.filter 8 def filter_multi(v1,v2): 9 return v1 * v2 10 11 12 @register.simple_tag 13 def simple_tag_multi(v1,v2): 14 return v1 * v2 15 16 17 @register.simple_tag 18 def my_input(id,arg): 19 result = "<input type=‘text‘ id=‘%s‘ class=‘%s‘ />" %(id,arg,) 20 return mark_safe(result)
c.在使用自定义simple_tag和filter的html文件中导入之间创建的 my_tags.py:{% load my_tags.py %}
d.使用simple_tag和filter
1 -------------------------------.html 2 {% load xxx %} #首行 3 4 5 6 7 # num=12 8 {{ num|filter_multi:2 }} #24 9 10 {{ num|filter_multi:"[22,333,4444]" }} 11 12 13 {% simple_tag_multi 2 5 %} 参数不限,但不能放在if for语句中 14 {% simple_tag_multi num 5 %}
e.在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag
1 {% if num|filter_multi:30 > 100 %} 2 {{ num|filter_multi:30 }} 3 {% endif %}
注:filter可以用在if等语句后,simple_tag不可以
4、extend模板继承
---------include 模板标签
{% include %}:该标签允许在(模块中)包含其它的模板的内容。标签的参数是所要包含的模板名称,可以是一个变量,也可以是用单/双引号硬编码的字符串。每当在多个模板中出现相同的代码时,就应该考虑是否要使用{% include %}来减少重复。
---------extend(继承)模板标签
在实际应用中,将用Django模板系统来创建整个HTML页面。这就带来一个常见Web开发问题:在整个网站中,如何减少共用页面区域(如站点导航)所引起的重复和冗余代码?
解决该问题的传统做法是使用 服务器端的includes,可以在HTML页面中使用该制定将一个网页嵌入到另一个中。事实上,Django通过上面的{% include %}支持这种方式。但是用Django解决此类问题的首选方法是使用更加优雅的策略----模板继承。
本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模版中对它所包含站点公用部分和定义块进行重载。
第一步:定义基础模板,该框架将由子模板所继承
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> 2 <html lang="en"> 3 <head> 4 <title>{% block title %}{% endblock %}</title> 5 </head> 6 <body> 7 <h1>My helpful timestamp site</h1> 8 {% block content %}{% endblock %} 9 {% block footer %} 10 <hr> 11 <p>Thanks for visiting my site.</p> 12 {% endblock %} 13 </body> 14 </html>
以上定义了一个简单的HTML框架文档,将在本站点的所有页面中使用。子模板的作用就是重载、添加或保留那些块的内容。
所有的{% block %}标签告诉模板引擎,子模板可以重载这些部分。每个{% block %}标签所要做的是告诉模板引擎,该模板下的这一块内容将有可能被子模板覆盖。
1 {% extends "base.html" %} 2 3 {% block title %}The current time{% endblock %} 4 5 {% block content %} 6 <p>It is now {{ current_date }}.</p> 7 {% endblock %}
1 {% extends "base.html" %} 2 3 {% block title %}Future time{% endblock %} 4 5 {% block content %} 6 <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> 7 {% endblock %}
这样一来,每个模板只包含对自己而言独一无二的代码。无需多余的部分。如果想进行站点级的设计修改,仅需修改基础模板即可,所有其他模板都会被修改。
六 Models
数据库配置
1、django默认支持sqlite,mysql,oracle,postgresql数据库。
a.sqlite
django默认使用sqlite的数据库,默认自带sqlite的数据库驱动,引擎名称:django.db.backends.sqlite3
b.mysql
引擎名称:django.db.backends.mysql
2、mysql驱动程序
a.MySQLdb(mysql python)
b.mysqlclient
c.MySQL
d.PyMySQL(纯python的mysql驱动程序)
3、在django的项目中会默认使用sqlite数据库,在settings有如下配置:
1 DATABASES = { 2 3 ‘default‘: { 4 5 ‘ENGINE‘: ‘django.db.backends.mysql‘, 6 7 ‘NAME‘: ‘books‘, #你的数据库名称 8 9 ‘USER‘: ‘root‘, #你的数据库用户名 10 11 ‘PASSWORD‘: ‘‘, #你的数据库密码 12 13 ‘HOST‘: ‘‘, #你的数据库主机,留空默认为localhost 14 15 ‘PORT‘: ‘3306‘, #你的数据库端口 16 17 } 18 19 }
1 NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建 2 3 USER和PASSWORD分别是数据库的用户名和密码。 4 5 设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。 6 7 然后,启动项目,会报错:no module named MySQLdb 8 9 这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb对于py3有很大问题,所以我们需要的驱动是PyMySQL 10 11 所以,我们只需要找到项目名文件下的__init__,在里面写入: 12 13 import pymysql 14 pymysql.install_as_MySQLdb() 15 16 问题解决!
注意!!!
ORM(对象关系映射)
用于实现面向对象编程语言里不同类型系统的数据之间的转换,换言之,就是用面向对象的方式去操作数据库的创建表以及增删改查等操作。
优点:
1、ORM使得我们的通用数据库交互变得简单易行,而且完全不用考虑该死的SQL语句。快速开发,由此而来的。
2、可以避免一些新手程序员写SQL语句带来的性能问题。
缺点:
1、性能有所牺牲,不过现在的各种ORM框架都在尝试各种方法,比如缓存,延迟加载等来减轻这个问题。
2、对于个别复杂查询,ORM仍然力不从心,为了解决这个问题,ORM一般也支持写raw sql。
3、通过QuerySet的query属性查询对应操作的sql语句。
1 author_obj=models.Author.objects.filter(id=2) 2 print(author_obj.query)
1 <1> CharField 2 #字符串字段, 用于较短的字符串. 3 #CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层限制该字段所允许的最大字符数. 4 5 <2> IntegerField 6 #用于保存一个整数. 7 8 <3> FloatField 9 # 一个浮点数. 必须 提供两个参数: 10 # 11 # 参数 描述 12 # max_digits 总位数(不包括小数点和符号) 13 # decimal_places 小数位数 14 # 举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段: 15 # 16 # models.FloatField(..., max_digits=5, decimal_places=2) 17 # 要保存最大值一百万(小数点后保存10位)的话,你要这样定义: 18 # 19 # models.FloatField(..., max_digits=19, decimal_places=10) 20 # admin 用一个文本框(<input type="text">)表示该字段保存的数据. 21 22 <4> AutoField 23 # 一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段; 24 # 自定义一个主键:my_id=models.AutoField(primary_key=True) 25 # 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model. 26 27 <5> BooleanField 28 # A true/false field. admin 用 checkbox 来表示此类字段. 29 30 <6> TextField 31 # 一个容量很大的文本字段. 32 # admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框). 33 34 <7> EmailField 35 # 一个带有检查Email合法性的 CharField,不接受 maxlength 参数. 36 37 <8> DateField 38 # 一个日期字段. 共有下列额外的可选参数: 39 # Argument 描述 40 # auto_now 当对象被保存时,自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳. 41 # auto_now_add 当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间. 42 #(仅仅在admin中有意义...) 43 44 <9> DateTimeField 45 # 一个日期时间字段. 类似 DateField 支持同样的附加选项. 46 47 <10> ImageField 48 # 类似 FileField, 不过要校验上传对象是否是一个合法图片.#它有两个可选参数:height_field和width_field, 49 # 如果提供这两个参数,则图片将按提供的高度和宽度规格保存. 50 <11> FileField 51 # 一个文件上传字段. 52 #要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime #formatting, 53 #该格式将被上载文件的 date/time 54 #替换(so that uploaded files don‘t fill up the given directory). 55 # admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) . 56 57 #注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤: 58 #(1)在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件. 59 # (出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对 60 # WEB服务器用户帐号是可写的. 61 #(2) 在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django 62 # 使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT). 63 # 出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举例来说,如果你的 ImageField 64 # 叫作 mug_shot, 你就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径. 65 66 <12> URLField 67 # 用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且 68 # 没有返回404响应). 69 # admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框) 70 71 <13> NullBooleanField 72 # 类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项 73 # admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据. 74 75 <14> SlugField 76 # "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.#它们通常用于URLs 77 # 若你使用 Django 开发版本,你可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50. #在 78 # 以前的 Django 版本,没有任何办法改变50 这个长度. 79 # 这暗示了 db_index=True. 80 # 它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate 81 # the slug, via JavaScript,in the object‘s admin form: models.SlugField 82 # (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields. 83 84 <13> XMLField 85 #一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema #的文件系统路径. 86 87 <14> FilePathField 88 # 可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的. 89 # 参数 描述 90 # path 必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目. 91 # Example: "/home/images". 92 # match 可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名. 93 # 注意这个正则表达式只会应用到 base filename 而不是 94 # 路径全名. Example: "foo.*\.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif. 95 # recursive可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录. 96 # 这三个参数可以同时使用. 97 # match 仅应用于 base filename, 而不是路径全名. 那么,这个例子: 98 # FilePathField(path="/home/images", match="foo.*", recursive=True) 99 # ...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif 100 101 <15> IPAddressField 102 # 一个字符串形式的 IP 地址, (i.e. "24.124.1.30"). 103 <16># CommaSeparatedIntegerField 104 # 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.
模型常用的字段类型参数
1 <1> null : 数据库中字段是否可以为空 2 3 <2> blank: django的 Admin 中添加数据时是否可允许空值 4 5 <3> default:设定缺省值 6 7 <4> editable:如果为假,admin模式下将不能改写。缺省为真 8 9 <5> primary_key:设置主键,如果没有设置django创建表时会自动加上: 10 id = meta.AutoField(‘ID‘, primary_key=True) 11 primary_key=True implies blank=False, null=False and unique=True. Only one 12 primary key is allowed on an object. 13 14 <6> unique:数据唯一 15 16 <7> verbose_name Admin中字段的显示名称 17 18 <8> validator_list:有效性检查。非有效产生 django.core.validators.ValidationError 错误 19 20 21 <9> db_column,db_index 如果为真将为此字段创建索引 22 23 <10>choices:一个用来选择值的2维元组。第一个值是实际存储的值,第二个用来方便进行选择。 24 如SEX_CHOICES= (( ‘F’,‘Female’),(‘M’,‘Male’),) 25 gender = models.CharField(max_length=2,choices = SEX_CHOICES)
Field重要参数
1 from django.shortcuts import render,HttpResponse 2 from app01.models import * 3 # Create your views here. 4 5 6 def index(req): 7 return render(req,"index.html") 8 9 10 def addbook(req): 11 # 方式一 12 # b = Book(name="python基础", price=99, author="alex", pub_date="2019-03-30") 13 # b.save() 14 # 方式二 15 Book.objects.create(name="linux", price=99, author="alex", pub_date="2019-03-30") 16 return HttpResponse("添加成功!")
表单的添加--> views.py
1 from django.shortcuts import render,HttpResponse 2 from app01.models import * 3 # Create your views here. 4 5 6 def index(req): 7 return render(req, "index.html") 8 9 10 def update(req): 11 # 方式一 12 # Book.objects.filter(name="linux").update(price=199) 13 # 方式二 14 b = Book.objects.get(name="linux") 15 b.price = 299 16 b.save() 17 # 注:update是QuerySet对象的方法,get返回的是一个model对象,它没有update方法,而filter返回的是一个QuerySet对象(filter里面的条件可能有多个条件符合) 18 19 # 若想查看sql语句,需要在settings中加上日志 20 # LOGGING = { 21 # ‘version‘: 1, 22 # ‘disable_existing_loggers‘: False, 23 # ‘handlers‘: { 24 # ‘console‘: { 25 # ‘level‘: ‘DEBUG‘, 26 # ‘class‘: ‘logging.StreamHandler‘, 27 # }, 28 # }, 29 # ‘loggers‘: { 30 # ‘django.db.backends‘: { 31 # ‘handlers‘: [‘console‘], 32 # ‘propagate‘: True, 33 # ‘level‘: ‘DEBUG‘, 34 # }, 35 # } 36 # } 37 return HttpResponse("修改成功!")
表单的修改-->views.py
1 from django.shortcuts import render,HttpResponse 2 from app01.models import * 3 # Create your views here. 4 5 6 def index(req): 7 return render(req, "index.html") 8 9 def delete(req): 10 Book.objects.filter(name="linux").delete() 11 return HttpResponse("删除成功!")
表单的删除-->views.py
1 # 查询相关API: 2 3 # <1>filter(**kwargs): 它包含了与所给筛选条件相匹配的对象 4 5 # <2>all(): 查询所有结果 6 7 # <3>get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。 8 9 #-----------下面的方法都是对查询的结果再进行处理:比如 objects.filter.values()-------- 10 11 # <4>values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列 12 13 # <5>exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象 14 15 # <6>order_by(*field): 对查询结果排序 16 17 # <7>reverse(): 对查询结果反向排序 18 19 # <8>distinct(): 从返回结果中剔除重复纪录 20 21 # <9>values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列 22 23 # <10>count(): 返回数据库中匹配查询(QuerySet)的对象数量。 24 25 # <11>first(): 返回第一条记录 26 27 # <12>last(): 返回最后一条记录 28 29 # <13>exists(): 如果QuerySet包含数据,就返回True,否则返回False。
表单的查询-->查询API
1 #扩展查询,有时候DJANGO的查询API不能方便的设置查询条件,提供了另外的扩展查询方法extra: 2 #extra(select=None, where=None, params=None, tables=None,order_by=None, select_params=None 3 4 (1) Entry.objects.extra(select={‘is_recent‘: "pub_date > ‘2006-01-01‘"}) 5 (2) Blog.objects.extra( 6 select=SortedDict([(‘a‘, ‘%s‘), (‘b‘, ‘%s‘)]), 7 select_params=(‘one‘, ‘two‘)) 8 9 (3) q = Entry.objects.extra(select={‘is_recent‘: "pub_date > ‘2006-01-01‘"}) 10 q = q.extra(order_by = [‘-is_recent‘]) 11 12 (4) Entry.objects.extra(where=[‘headline=%s‘], params=[‘Lennon‘])
表单的查询-->补充
1 from django.shortcuts import render,HttpResponse 2 from app01.models import * 3 # Create your views here. 4 5 6 def index(req): 7 return render(req, "index.html") 8 9 10 def addbook(req): 11 # 方式一 12 # b = Book(name="python基础", price=99, author="alex", pub_date="2019-03-30") 13 # b.save() 14 # 方式二 15 Book.objects.create(name="linux", price=99, author="alex", pub_date="2019-03-30") 16 return HttpResponse("添加成功!") 17 18 19 def update(req): 20 # 方式一 21 # Book.objects.filter(name="linux").update(price=199) 22 # 方式二 23 b = Book.objects.get(name="linux") 24 b.price = 299 25 b.save() 26 # 注:update是QuerySet对象的方法,get返回的是一个model对象,它没有update方法,而filter返回的是一个QuerySet对象(filter里面的条件可能有多个条件符合) 27 28 # 若想查看sql语句,需要在settings中加上日志 29 # LOGGING = { 30 # ‘version‘: 1, 31 # ‘disable_existing_loggers‘: False, 32 # ‘handlers‘: { 33 # ‘console‘: { 34 # ‘level‘: ‘DEBUG‘, 35 # ‘class‘: ‘logging.StreamHandler‘, 36 # }, 37 # }, 38 # ‘loggers‘: { 39 # ‘django.db.backends‘: { 40 # ‘handlers‘: [‘console‘], 41 # ‘propagate‘: True, 42 # ‘level‘: ‘DEBUG‘, 43 # }, 44 # } 45 # } 46 return HttpResponse("修改成功!") 47 48 49 def delete(req): 50 Book.objects.filter(name="linux").delete() 51 return HttpResponse("删除成功!") 52 53 54 def select(req): 55 # book_list = Book.objects.all() 56 # print(book_list[0]) 57 58 book_list = Book.objects.filter(id=2) 59 60 # book_list = Book.objects.all()[:3] 61 62 # book_list = Book.objects.all().first() 63 64 # book_list = Book.objects.all().last() 65 66 # book_list = Book.objects.get(id=2) # 只能取一条记录时才不会报错 67 68 # book_list = Book.objects.filter(author="alex").values("name") 69 70 # book_list = Book.objects.filter(author="alex").values_list("name") 71 72 # book_list = Book.objects.exclude(author="alex").values("name") 73 74 # book_list = Book.objects.filter(author="alex").values("name").distinct() 75 76 # book_list = Book.objects.filter(author="alex").values("name").distinct().count() 77 return render(req, "index.html", {"book_list": book_list})
1 <1>Django的queryset是惰性的 2 3 Django的queryset对应于数据库的若干记录(row),通过可选的查询来过滤。例如,下面的代码会得 4 到数据库中名字为‘Dave’的所有的人:person_set = Person.objects.filter(first_name="Dave") 5 上面的代码并没有运行任何的数据库查询。你可以使用person_set,给它加上一些过滤条件,或者将它传给某个函数, 6 这些操作都不会发送给数据库。这是对的,因为数据库查询是显著影响web应用性能的因素之一。 7 8 <2>要真正从数据库获得数据,你可以遍历queryset或者使用if queryset,总之你用到数据时就会执行sql. 9 为了验证这些,需要在settings里加入 LOGGING(验证方式) 10 obj=models.Book.objects.filter(id=3) 11 # for i in obj: 12 # print(i) 13 14 # if obj: 15 # print("ok") 16 17 <3>queryset是具有cache的 18 当你遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model。这被称为执行 19 (evaluation).这些model会保存在queryset内置的cache中,这样如果你再次遍历这个queryset, 20 你不需要重复运行通用的查询。 21 obj=models.Book.objects.filter(id=3) 22 23 # for i in obj: 24 # print(i) 25 ## models.Book.objects.filter(id=3).update(title="GO") 26 ## obj_new=models.Book.objects.filter(id=3) 27 # for i in obj: 28 # print(i) #LOGGING只会打印一次 29 30 <4> 31 简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些 32 数据!为了避免这个,可以用exists()方法来检查是否有数据: 33 34 obj = Book.objects.filter(id=4) 35 # exists()的检查可以避免数据放入queryset的cache。 36 if obj.exists(): 37 print("hello world!") 38 39 <5>当queryset非常巨大时,cache会成为问题 40 41 处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统 42 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法 43 来获取数据,处理完数据就将其丢弃。 44 objs = Book.objects.all().iterator() 45 # iterator()可以一次只从数据库获取少量数据,这样可以节省内存 46 for obj in objs: 47 print(obj.name) 48 #BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了 49 for obj in objs: 50 print(obj.name) 51 52 #当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使 53 #用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询 54 55 总结: 56 queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 57 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 58 会造成额外的数据库查询。
QuerySet的特性
1 from django.db.models import Avg,Min,Sum,Max 2 3 从整个查询集生成统计值。比如,你想要计算所有在售书的平均价钱。Django的查询语法提供了一种方式描述所有 4 图书的集合。 5 6 >>> Book.objects.all().aggregate(Avg(‘price‘)) 7 {‘price__avg‘: 34.35} 8 9 aggregate()子句的参数描述了我们想要计算的聚合值,在这个例子中,是Book模型中price字段的平均值 10 11 aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的 12 标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定 13 一个名称,可以向聚合子句提供它: 14 >>> Book.objects.aggregate(average_price=Avg(‘price‘)) 15 {‘average_price‘: 34.35} 16 17 18 如果你也想知道所有图书价格的最大值和最小值,可以这样查询: 19 >>> Book.objects.aggregate(Avg(‘price‘), Max(‘price‘), Min(‘price‘)) 20 {‘price__avg‘: 34.35, ‘price__max‘: Decimal(‘81.20‘), ‘price__min‘: Decimal(‘12.99‘)}
聚合查询和分组查询aggregate(*args,**kwargs)
1 # F 使用查询条件的值,专门取对象中某列值的操作 2 3 # from django.db.models import F 4 # models.Tb1.objects.update(num=F(‘num‘)+1) 5 6 7 # Q 构建搜索条件 8 from django.db.models import Q 9 10 #1 Q对象(django.db.models.Q)可以对关键字参数进行封装,从而更好地应用多个查询 11 q1=models.Book.objects.filter(Q(title__startswith=‘P‘)).all() 12 print(q1)#[<Book: Python>, <Book: Perl>] 13 14 # 2、可以组合使用&,|操作符,当一个操作符是用于两个Q的对象,它产生一个新的Q对象。 15 Q(title__startswith=‘P‘) | Q(title__startswith=‘J‘) 16 17 # 3、Q对象可以用~操作符放在前面表示否定,也可允许否定与不否定形式的组合 18 Q(title__startswith=‘P‘) | ~Q(pub_date__year=2005) 19 20 # 4、应用范围: 21 22 # Each lookup function that takes keyword-arguments (e.g. filter(), 23 # exclude(), get()) can also be passed one or more Q objects as 24 # positional (not-named) arguments. If you provide multiple Q object 25 # arguments to a lookup function, the arguments will be “AND”ed 26 # together. For example: 27 28 Book.objects.get( 29 Q(title__startswith=‘P‘), 30 Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) 31 ) 32 33 #sql: 34 # SELECT * from polls WHERE question LIKE ‘P%‘ 35 # AND (pub_date = ‘2005-05-02‘ OR pub_date = ‘2005-05-06‘) 36 37 # import datetime 38 # e=datetime.date(2005,5,6) #2005-05-06 39 40 # 5、Q对象可以与关键字参数查询一起使用,不过一定要把Q对象放在关键字参数查询的前面。 41 # 正确: 42 Book.objects.get( 43 Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), 44 title__startswith=‘P‘) 45 # 错误: 46 Book.objects.get( 47 question__startswith=‘P‘, 48 Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
F查询和Q查询
1 python manage.py makemigrations 2 python manage.py migrate
创建表
1 LOGGING = { 2 ‘version‘: 1, 3 ‘disable_existing_loggers‘: False, 4 ‘handlers‘: { 5 ‘console‘:{ 6 ‘level‘:‘DEBUG‘, 7 ‘class‘:‘logging.StreamHandler‘, 8 }, 9 }, 10 ‘loggers‘: { 11 ‘django.db.backends‘: { 12 ‘handlers‘: [‘console‘], 13 ‘propagate‘: True, 14 ‘level‘:‘DEBUG‘, 15 }, 16 } 17 }
在settings加上日志记录
注:若创建表失败,请看:https://www.cnblogs.com/chenyanbin/p/10628646.html
七 admin的配置
admin是Django强大功能之一,它能从数据库中读取数据,呈现在页面中,进行管理。默认情况下,它的功能已经非常强大,如果你不需要复杂的功能,它已经够用了,但是有时候,一些特殊的功能还需要定制,比如搜索功能。
1 在setting.py中修改以下选项 2 3 LANGUAGE_CODE = ‘en-us‘ #LANGUAGE_CODE = ‘zh-hans‘
修改页面字
1 1、register 2 admin.site.register(Book,MyAdmin) 3 4 2、register的装饰器 5 @admin.register(Book)
注册medel类到admin的两种方式
创建Django账号
登陆admin
操作admin
一些常用的设置技巧
• list_display:指定要显示的字段
• search_fields:指定搜索的字段
• list_filter:指定列表过滤器
• ordering:指定排序字段
1 from django.contrib import admin 2 from app01.models import * 3 # Register your models here. 4 5 # @admin.register(Book)#----->单给某个表加一个定制 6 class MyAdmin(admin.ModelAdmin): 7 list_display = ("title","price","publisher") 8 search_fields = ("title","publisher") 9 list_filter = ("publisher",) 10 ordering = ("price",) 11 fieldsets =[ 12 (None, {‘fields‘: [‘title‘]}), 13 (‘price information‘, {‘fields‘: [‘price‘,"publisher"], ‘classes‘: [‘collapse‘]}), 14 ] 15 16 admin.site.register(Book,MyAdmin) 17 admin.site.register(Publish) 18 admin.site.register(Author)
参考文献:http://www.admin10000.com/document/2220.html
原文地址:https://www.cnblogs.com/chenyanbin/p/10514121.html