web框架之Django
web开发介绍
互联网刚兴起的那段时间大家都热衷于CS架构,也就是Client/Server模式。每个用户的电脑上安装一个Client,就像QQ这种终端软件。
随着互联网的发展,开发客户端显得越来越‘重’,BS架构(Browser/Server模式)越来越流行,也就是用户只需要一个浏览器就足够了。
前端性能优化的方式
- 减少DOM操作
- 部署前,图片压缩,代码压缩
- 优化JS代码结构,减少冗余代码
- 减少HTTP请求,合理设置 HTTP缓存
- 使用内容分发CDN加速
- 静态资源缓存
- 图片延迟加载
一个页面从输入URL到页面加载显示完成,这个过程中都发生了什么?
输入地址之后:
- 浏览器查找域名的IP地址
- 这一步包括DNS具体的查找过程,包括:浏览器缓存->系统缓存->路由器缓存…
- 浏览器向Web服务器发送一个HTTP请求
- 服务器的永久重定向响应(从 http://example.com 到 http://www.example.com)
- 浏览器跟踪重定向地址
- 服务器处理请求
- 服务器返回一个HTTP响应
- 浏览器显示HTML
- 浏览器发送请求获取嵌入在HTML 中的资源(如图片、音频、视频、CSS、JS等等)
- 浏览器发送异步请求
Web框架
我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。
这样我们就可以自己实现Web框架了。
1 import socket 2 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 3 sock.bind(('127.0.0.1', 8000)) 4 sock.listen(5) 5 while True: 6 conn, addr = sock.accept() 7 data = conn.recv(8096) 8 conn.send(b"OK") 9 conn.close()
可以说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码就是它们的祖宗。
用户的浏览器一输入网址,会给服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定?
你这个网站是这个规定,他那个网站按照他那个规定,这互联网还能玩么?
所以,必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。
这个规则就是HTTP协议,以后你发送请求信息也好,回复响应信息也罢,都要按照这个规则来。
那HTTP协议是怎么规定消息格式的呢?
让我们首先看下我们在服务端接收到的消息是什么。
然后再看下我们浏览器收到的响应信息是什么。
响应头在浏览器的network窗口可以看到,我们看到的HTML页面内容就是响应体。本质上还是字符串,因为浏览器认识HTML,所以才会渲染出页面。
每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。
HTTP响应的Header中有一个Content-Type
表明响应的内容格式。如text/html
表示HTML网页。
1 // 报头信息 2 HTTP/1.1 200 OK\r\n 3 Header1:v1\r\n 4 Header2:v2\r\n 5 \r\n\r\n ----------> 与内容之间隔着两个换行符 6 // --------------------------------- 内容 7 Body...
让我们的Web框架在给客户端回复响应的时候按照HTTP协议的规则加上响应头,这样我们就实现了一个正经的Web框架了。
1 import socket 2 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 3 sock.bind(('127.0.0.1', 8000)) 4 sock.listen(5) 5 while True: 6 conn, addr = sock.accept() 7 data = conn.recv(8096) 8 conn.send(b"HTTP/1.1 200 OK\r\n\r\n") 9 conn.send(b"OK") 10 conn.close()
这样就结束了吗?
如何让我们的Web服务根据用户请求的URL不同而返回不同的内容呢?
小事一桩,我们可以从请求头里面拿到请求的URL,然后做一个判断。
1 import socket 2 sk = socket.socket() 3 sk.bind(("127.0.0.1", 8080)) 4 sk.listen(5) 5 while True: 6 conn, addr = sk.accept() 7 data = conn.recv(8096) 8 data_str = str(data, encoding="utf-8") # 把收到的字节数据转换成字符串类型 9 header = data_str.split("\r\n\r\n") # 将请求的数据按照\r\n\r\n分割 10 header_list = header[0].split("\r\n") # 按\r\n分割header 11 temp = header_list[0].split("") # 按空格分割第一行 12 url = temp[1] # 取到请求的URL 13 conn.send(b"HTTP/1.1 200 OK\r\n\r\n") # 发送响应头 14 if url == "/index/": 15 conn.send(b"this is index page.") 16 else: 17 conn.send(b"404 not found.") 18 conn.close()
解决了不同URL返回不同内容的需求。
但是问题又来了,如果有很多很多页面怎么办?难道要挨个判断?
当然不用,我们有更聪明的办法。
1 import socket 2 def index(): 3 return b"this is index page." 4 def login(): 5 return b"this is login page." 6 url_func_map = [ 7 ("/index/", index), 8 ("/login/", login) 9 ] 10 sk = socket.socket() 11 sk.bind(("127.0.0.1", 8080)) 12 sk.listen(5) 13 while True: 14 conn, addr = sk.accept() 15 data = conn.recv(8096) 16 data_str = str(data, encoding="utf-8") # 把收到的字节数据转换成字符串类型 17 header = data_str.split("\r\n\r\n") # 将请求的数据按照\r\n\r\n分割 18 header_list = header[0].split("\r\n") # 按\r\n分割header 19 temp = header_list[0].split("") # 按空格分割第一行 20 url = temp[1] # 取到请求的URL 21 conn.send(b"HTTP/1.1 200 OK\r\ncontent-type:text/html; charset=utf-8\r\n\r\n") # 发送响应头 22 func_name = None # 将要执行的函数 23 for i in url_func_map: 24 if i[0] == url: 25 func_name = i[1] 26 break # 跳出循环 27 if func_name: # 如果找到了要执行的函数 28 response = func_name() 29 else: 30 response = b"404 not found." 31 conn.send(response) 32 conn.close()
完美解决了不同URL返回不同内容的问题。
但是我不想仅仅返回几个字符串,我想给浏览器返回完整的HTML内容,这又该怎么办呢?
没问题,不管是什么内容,最后都是转换成字节数据发送出去的。
我可以打开HTML文件,读取出它内部的二进制数据,然后发送给浏览器。
1 import socket 2 def index(): 3 with open("index.html", "rb") as f: 4 html_data = f.read() 5 return html_data 6 def login(): 7 with open("login.html", "rb") as f: 8 html_data = f.read() 9 return html_data 10 url_func_map = [ 11 ("/index/", index), 12 ("/login/", login) 13 ] 14 sk = socket.socket() 15 sk.bind(("127.0.0.1", 8080)) 16 sk.listen(5) 17 while True: 18 conn, addr = sk.accept() 19 data = conn.recv(8096) 20 data_str = str(data, encoding="utf-8") # 把收到的字节数据转换成字符串类型 21 header = data_str.split("\r\n\r\n") # 将请求的数据按照\r\n\r\n分割 22 header_list = header[0].split("\r\n") # 按\r\n分割header 23 temp = header_list[0].split("") # 按空格分割第一行 24 url = temp[1] # 取到请求的URL 25 conn.send(b"HTTP/1.1 200 OK\r\ncontent-type:text/html; charset=utf-8\r\n\r\n") # 发送响应头 26 func_name = None # 将要执行的函数 27 for i in url_func_map: 28 if i[0] == url: 29 func_name = i[1] 30 break # 跳出循环 31 if func_name: # 如果找到了要执行的函数 32 response = func_name() 33 else: 34 response = b"404 not found." 35 conn.send(response) 36 conn.close()
这网页能够显示出来了,但是都是静态的啊。页面的内容都不会变化的,我想要的是动态网站。
没问题,我也有办法解决。我选择使用字符串替换来实现这个需求。
1 import socket 2 def index(): 3 with open("index.html", "rb") as f: 4 html_data = f.read() 5 return html_data 6 def login(): 7 with open("login.html", "rb") as f: 8 html_data = f.read() 9 import time 10 time_str = str(time.time()) 11 html_str = str(html_data, encoding="utf-8") 12 new_html = html_str.replace("@@a@@", time_str) 13 return bytes(new_html, encoding="utf-8") 14 url_func_map = [ 15 ("/index/", index), 16 ("/login/", login) 17 ] 18 sk = socket.socket() 19 sk.bind(("127.0.0.1", 8080)) 20 sk.listen(5) 21 while True: 22 conn, addr = sk.accept() 23 data = conn.recv(8096) 24 data_str = str(data, encoding="utf-8") # 把收到的字节数据转换成字符串类型 25 header = data_str.split("\r\n\r\n") # 将请求的数据按照\r\n\r\n分割 26 header_list = header[0].split("\r\n") # 按\r\n分割header 27 temp = header_list[0].split("") # 按空格分割第一行 28 url = temp[1] # 取到请求的URL 29 conn.send(b"HTTP/1.1 200 OK\r\ncontent-type:text/html; charset=utf-8\r\n\r\n") # 发送响应头 30 func_name = None # 将要执行的函数 31 for i in url_func_map: 32 if i[0] == url: 33 func_name = i[1] 34 break # 跳出循环 35 if func_name: # 如果找到了要执行的函数 36 response = func_name() 37 else: 38 response = b"404 not found." 39 conn.send(response) 40 conn.close()
这是一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。
这个过程就相当于HTML模板渲染数据。
本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。
我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具:jinja2
1 from jinja2 import Template 2 template = Template(data) # 生成模板实例 3 template.render(data=data) # 渲染数据
pymysql连接数据库:
1 conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", passwd="xxx", db="xxx", charset="utf8") 2 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) 3 cursor.execute("select name, age, department_id from userinfo") 4 user_list = cursor.fetchall() 5 cursor.close() 6 conn.close()
创建一个测试的user表:
1 CREATE TABLE user( 2 id int auto_increment PRIMARY KEY, 3 name CHAR(10) NOT NULL, 4 habit CHAR(20) NOT NULL 5 )engine=innodb DEFAULT charset=UTF8; 6
上述代码模拟了一个web服务的本质.
而对于真实开发中的Python Web程序来说,一般分为两部分:服务器程序和应用程序。
服务器程序负责对Socket
服务器进行封装,并在请求到来时,对请求的各种数据进行整理。
应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py等。
不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。
这样,服务器程序就需要为不同的框架提供不同的支持。
这样混乱的局面无论对于服务器还是框架,都是不好的。
对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。
这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么它们就可以配合使用。
一旦标准确定,双方根据约定的标准各自进行开发即可。
WSGI(Web Server Gateway Interface)就是这样一种规范,它定义了使用Python编写的Web APP与Web Server之间接口格式,实现Web APP与Web Server间的解耦。
Python标准库提供的独立WSGI服务器称为wsgiref
。
1 from wsgiref.simple_server import make_server 2 def run_server(environ, start_response): 3 start_response('200 OK', [('Content-Type', 'text/html')]) 4 return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ] 5 if __name__ == '__main__': 6 httpd = make_server('', 8000, run_server) 7 print("Serving HTTP on port 8000...") 8 httpd.serve_forever() 9
Django开发模式就是使用的wsgiref
模块作为Web Server。
Django
安装与基本配置
1 pip3 install django
1 // 命令行创建项目 2 django-admin startproject mysite 3 4 // 也可以用pycharm创建 5
目录介绍:
1 mysite/ 2 ├── manage.py # 管理文件 3 └── mysite # 项目目录 4 ├── __init__.py 5 ├── settings.py # 配置 6 ├── urls.py # 路由 --> URL和函数的对应关系 7 └── wsgi.py # webserver --> wsgiref模块 8
运行
1 // 命令行启动方式 2 python manage.py runserver 127.0.0.1:8000
模板文件配置:
1 # setting配置文件 2 TEMPLATES = [ 3 { 4 'BACKEND': 'django.template.backends.django.DjangoTemplates', 5 'DIRS': [os.path.join(BASE_DIR, "template")], # template文件夹位置 6 'APP_DIRS': True, 7 'OPTIONS': { 8 'context_processors': [ 9 'django.template.context_processors.debug', 10 'django.template.context_processors.request', 11 'django.contrib.auth.context_processors.auth', 12 'django.contrib.messages.context_processors.messages', 13 ], 14 }, 15 }, 16 ] 17 18 # 也就是说以后存放的模板文件(html)存放位置, 可以改名字,但是相应的名字也要修改
静态文件配置:
1 # 静态文件存放位置 2 STATIC_URL = '/static/' # HTML中使用的静态文件夹前缀 3 STATICFILES_DIRS = ( 4 os.path.join(BASE_DIR, "static"), # 静态文件存放位置 5 ) 6 # 也就是以后 css,js 存放文件的位置
STATIC_URL = '/static/' # 别名 STATICFILES_DIRS = ( os.path.join(BASE_DIR,"static"), #实际名 ,即实际文件夹的名字 ) ''' 注意点1: django对引用名和实际名进行映射,引用时,只能按照引用名来,不能按实际名去找 <script src="/statics/jquery-3.1.1.js"></script> ------error-----不能直接用,必须用STATIC_URL = '/static/': <script src="/static/jquery-3.1.1.js"></script> 注意点2: STATICFILES_DIRS = ( ("app01",os.path.join(BASE_DIR, "app01/statics")), ) <script src="/static/app01/jquery.js"></script> '''
media配置:
# in settings: MEDIA_URL="/media/" MEDIA_ROOT=os.path.join(BASE_DIR,"app01","media","upload") # in urls: from django.views.static import serve url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
http://127.0.0.1:8000/media/1.png
''' 静态文件的处理又包括STATIC和MEDIA两类,这往往容易混淆,在Django里面是这样定义的: MEDIA:指用户上传的文件,比如在Model里面的FileFIeld,ImageField上传的文件。如果你定义 MEDIA_ROOT=c:\temp\media,那么File=models.FileField(upload_to="abc/")#,上传的文件就会被保存到c:\temp\media\abc eg: class blog(models.Model): Title=models.charField(max_length=64) Photo=models.ImageField(upload_to="photo") 上传的图片就上传到c:\temp\media\photo,而在模板中要显示该文件,则在这样写 在settings里面设置的MEDIA_ROOT必须是本地路径的绝对路径,一般是这样写: BASE_DIR= os.path.abspath(os.path.dirname(__file__)) MEDIA_ROOT=os.path.join(BASE_DIR,'media/').replace('\\','/') MEDIA_URL是指从浏览器访问时的地址前缀,举个例子: MEDIA_ROOT=c:\temp\media\photo MEDIA_URL="/data/" 在开发阶段,media的处理由django处理: 访问http://localhost/data/abc/a.png就是访问c:\temp\media\photo\abc\a.png 在模板里面这样写<img src="/media/abc/a.png"> 在部署阶段最大的不同在于你必须让web服务器来处理media文件,因此你必须在web服务器中配置, 以便能让web服务器能访问media文件 以nginx为例,可以在nginx.conf里面这样: location ~/media/{ root/temp/ break; } 具体可以参考如何在nginx部署django的资料。 '''
urls文件
urls文件:
from django.conf.urls import url from . import view urlpatterns = [
url(r'^admin/', admin.site.urls),
]
url参数
regex: 正则表达式,与之匹配的 URL 会执行对应的第二个参数 view。
view: 用于执行与正则表达式匹配的 URL 请求。 --- 相当于通过url路由执行对应的函数
kwargs: 视图使用的字典类型的参数。 可以传入视图中的参数。
name: 用来反向获取 URL。
regex:
NOTE: 1 一旦匹配成功则不再继续 2 若要从URL 中捕获一个值,只需要在它周围放置一对圆括号。 3 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。 4 每个正则表达式前面的'r' 是可选的但是建议加上。 一些请求的例子: /articles/2005/3/ 不匹配任何URL 模式,因为列表中的第三个模式要求月份应该是两个数字。 /articles/2003/ 将匹配列表中的第一个模式不是第二个,因为模式按顺序匹配,第一个会首先测试是否匹配。 /articles/2005/03/ 请求将匹配列表中的第三个模式。Django 将调用函数 views.month_archive(request, '2005', '03')。
#设置项是否开启URL访问地址后面不为/跳转至带有/的路径 APPEND_SLASH=True
无参分组
url(r'^articles/([0-9]{4})/$', views.year_archive),
def del_class(request, d): ...
有参分组
上面的示例使用简单的、没有命名的正则表达式组(通过圆括号)来捕获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), ]
这个实现与前面的示例完全相同,只有一个细微的差别:捕获的值作为关键字参数而不是位置参数传递给视图函数。例如:
/articles/2005/03/ 请求将调用views.month_archive(request, year='2005', month='03')函数 /articles/2003/03/03/ 请求将调用函数views.article_detail(request, year='2003', month='03', day='03')。
在实际应用中,这意味你的URLconf 会更加明晰且不容易产生参数顺序问题的错误 —— 你可以在你的视图函数定义中重新安排参数的顺序。当然,这些好处是以简洁为代价;有些开发人员认为命名组语法丑陋而繁琐。
url(r'^delClass/(?P<del_id>\d+)', views.del_class, name='del'),
def del_class(request, del_id): ...
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
等等 —— 都将路由到相同的函数。
指定视图函数的默认值
# URLconf 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), ] # View (in blog/views.py) def page(request, num="1"):
在上面的例子中,两个URL模式指向同一个视图views.page
—— 但是第一个模式不会从URL 中捕获任何值。如果第一个模式匹配,page()
函数将使用num
参数的默认值"1"。如果第二个模式匹配,page()
将使用正则表达式捕获的num
值。
view之Including other URLconfs(url管理)
#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 urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^blog/', include('blog.urls')), ]
如果一个项目下有多个app应用, 但是url都写在一个urls.py文件里, 就会显的非常杂乱, 而且耦合性很强, 如果一个app的url出了问题,就会影响到其他的url使用, 所以为了解决这个可以使用 include来解决:
使用方法,可以在app里的urls文件里,创建对应关系, 然后在项目的urls的文件里指定文件路径。
kwargs 参数
// urls文件 from django.conf.urls import url from django.contrib import admin from django.shortcuts import HttpResponse, render, redirect def demo(request, msg): print('-------------------') print(msg) return HttpResponse('ok') urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^demo/', demo, {'msg': 'hello world'}) ]
name参数 用来当前url的路由地址
// html文件 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title>Title</title> </head> <body> <span>当前网页的路由地址是 {% url 'flag' %}</span> </body> </html>
结果:
新手三件套
from django.shortcuts import HttpResponse, render, redirect
HttpResponse: 带有字符串作为内容的HTTP响应类。用于回复浏览器的一种封装工具, 可以是接受字符串,也可以接收字符串类型的html标签
render: 把HttpResponse 封装在函数里,实现了更好的功能,可以返回一个html文件内容。
redirect:可以实现跳转网页的功能。
HttpResponse
from django.conf.urls import url from django.contrib import admin from django.shortcuts import HttpResponse, render, redirect def demo(request, msg): return HttpResponse('<h1>Hello World!</h1>') urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^demo/', demo, {'msg': 'hello world'}, name='flag') ]
属性
属性: django将请求报文中的请求行、首部信息、内容主体封装成 HttpRequest 类中的属性。 除了特殊说明的之外,其他均为只读的。 ''' 1.HttpRequest.body 一个字符串,代表请求报文的主体。在处理非 HTTP 形式的报文时非常有用,例如:二进制图片、XML,Json等。 但是,如果要处理表单数据的时候,推荐还是使用 HttpRequest.POST 。 另外,我们还可以用 python 的类文件方法去操作它,详情参考 HttpRequest.read() 。 2.HttpRequest.path 一个字符串,表示请求的路径组件(不含域名)。 例如:"/music/bands/the_beatles/" 3.HttpRequest.method 一个字符串,表示请求使用的HTTP 方法。必须使用大写。 例如:"GET"、"POST" 4.HttpRequest.encoding 一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 'utf-8')。 这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。 接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。 如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。 5.HttpRequest.GET 一个类似于字典的对象,包含 HTTP GET 的所有参数。详情请参考 QueryDict 对象。 6.HttpRequest.POST 一个类似于字典的对象,如果请求中包含表单数据,则将这些数据封装成 QueryDict 对象。 POST 请求可以带有空的 POST 字典 —— 如果通过 HTTP POST 方法发送一个表单,但是表单中没有任何的数据,QueryDict 对象依然会被创建。 因此,不应该使用 if request.POST 来检查使用的是否是POST 方法;应该使用 if request.method == "POST" 另外:如果使用 POST 上传文件的话,文件信息将包含在 FILES 属性中。 7.HttpRequest.REQUEST 一个类似于字典的对象,它首先搜索POST,然后搜索GET,主要是为了方便。灵感来自于PHP 的 $_REQUEST。 例如,如果 GET = {"name": "john"} 而 POST = {"age": '34'} , REQUEST["name"] 将等于"john", REQUEST["age"] 将等于"34"。 强烈建议使用 GET 和 POST 而不要用REQUEST,因为它们更加明确。 8.HttpRequest.COOKIES 一个标准的Python 字典,包含所有的cookie。键和值都为字符串。 9.HttpRequest.FILES 一个类似于字典的对象,包含所有的上传文件信息。 FILES 中的每个键为<input type="file" name="" /> 中的name,值则为对应的数据。 注意,FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会 包含数据。否则,FILES 将为一个空的类似于字典的对象。 10.HttpRequest.META 一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器,下面是一些示例: CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。 CONTENT_TYPE —— 请求的正文的MIME 类型。 HTTP_ACCEPT —— 响应可接收的Content-Type。 HTTP_ACCEPT_ENCODING —— 响应可接收的编码。 HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。 HTTP_HOST —— 客服端发送的HTTP Host 头部。 HTTP_REFERER —— Referring 页面。 HTTP_USER_AGENT —— 客户端的user-agent 字符串。 QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。 REMOTE_ADDR —— 客户端的IP 地址。 REMOTE_HOST —— 客户端的主机名。 REMOTE_USER —— 服务器认证后的用户。 REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。 SERVER_NAME —— 服务器的主机名。 SERVER_PORT —— 服务器的端口(是一个字符串)。 从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,请求中的任何 HTTP 首部转换为 META 的键时, 都会将所有字母大写并将连接符替换为下划线最后加上 HTTP_ 前缀。 所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。 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.session 一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。 完整的细节参见会话的文档。 13.HttpRequest.resolver_match 一个 ResolverMatch 的实例,表示解析后的URL。这个属性只有在 URL 解析方法之后才设置,这意味着它在所有的视图中可以访问, 但是在 URL 解析发生之前执行的中间件方法中不可以访问(比如process_request,但你可以使用 process_view 代替)。 '''
方法
''' 1.HttpRequest.get_host() 根据从HTTP_X_FORWARDED_HOST(如果打开 USE_X_FORWARDED_HOST,默认为False)和 HTTP_HOST 头部信息返回请求的原始主机。 如果这两个头部没有提供相应的值,则使用SERVER_NAME 和SERVER_PORT,在PEP 3333 中有详细描述。 USE_X_FORWARDED_HOST:一个布尔值,用于指定是否优先使用 X-Forwarded-Host 首部,仅在代理设置了该首部的情况下,才可以被使用。 例如:"127.0.0.1:8000" 注意:当主机位于多个代理后面时,get_host() 方法将会失败。除非使用中间件重写代理的首部。 2.HttpRequest.get_full_path() 返回 path,如果可以将加上查询字符串。 例如:"/music/bands/the_beatles/?print=true" 3.HttpRequest.build_absolute_uri(location) 返回location 的绝对URI。如果location 没有提供,则使用request.get_full_path()的返回值。 如果URI 已经是一个绝对的URI,将不会修改。否则,使用请求中的服务器相关的变量构建绝对URI。 例如:"http://example.com/music/bands/the_beatles/?print=true" 4.HttpRequest.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None) 返回签名过的Cookie 对应的值,如果签名不再合法则返回django.core.signing.BadSignature。 如果提供 default 参数,将不会引发异常并返回 default 的值。 可选参数salt 可以用来对安全密钥强力攻击提供额外的保护。max_age 参数用于检查Cookie 对应的时间戳以确保Cookie 的时间不会超过max_age 秒。 复制代码 >>> request.get_signed_cookie('name') 'Tony' >>> request.get_signed_cookie('name', salt='name-salt') 'Tony' # 假设在设置cookie的时候使用的是相同的salt >>> request.get_signed_cookie('non-existing-cookie') ... KeyError: 'non-existing-cookie' # 没有相应的键时触发异常 >>> request.get_signed_cookie('non-existing-cookie', False) False >>> request.get_signed_cookie('cookie-that-was-tampered-with') ... BadSignature: ... >>> request.get_signed_cookie('name', max_age=60) ... SignatureExpired: Signature age 1677.3839159 > 60 seconds >>> request.get_signed_cookie('name', False, max_age=60) False 复制代码 5.HttpRequest.is_secure() 如果请求时是安全的,则返回True;即请求通是过 HTTPS 发起的。 6.HttpRequest.is_ajax() 如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。 大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。 如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的 cache middleware, 你应该使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 装饰你的视图以让响应能够正确地缓存。 7.HttpRequest.read(size=None) 像文件一样读取请求报文的内容主体,同样的,还有以下方法可用。 HttpRequest.readline() HttpRequest.readlines() HttpRequest.xreadlines() 其行为和文件操作中的一样。 HttpRequest.__iter__():说明可以使用 for 的方式迭代文件的每一行。'''
注意:键值对的值是多个的时候,比如checkbox类型的input标签,select标签,需要用:
request.POST.getlist("hobby")
render
render(request, template_name[, context])
结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。
参数:
request: 用于生成响应的请求对象。
template_name:要使用的模板的完整名称,可选的参数
context:添加到模板上下文的一个字典。默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。
content_type:生成的文档要使用的MIME类型。默认为DEFAULT_CONTENT_TYPE 设置的值。
status:响应的状态码。默认为200。
// html文件 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title>Title</title> </head> <body> <span>这是render渲染的页面</span>> </body> </html>
from django.conf.urls import url from django.contrib import admin from django.shortcuts import HttpResponse, render, redirect def demo(request, msg): return render(request, 'demo.html') urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^demo/', demo, {'msg': 'hello world'}, name='flag') ]
render还可以接收字典类型的参数,与模板语言实现交互:
from django.conf.urls import url from django.contrib import admin from django.shortcuts import HttpResponse, render, redirect lst = [1, 2, 3, 4, 5] def demo(request, msg): return render(request, 'demo1.html', {'lst': lst}) urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^demo/', demo, {'msg': 'hello world'}, name='flag') ]
<!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, maximum-scale=1, user-scalable=no"> <title>Title</title> </head> <body> <ul> {% for i in lst %} <li>{{ i }}</li> {% endfor %} </ul> </body> </html>
redirect
参数可以是:
- 一个模型:将调用模型的get_absolute_url() 函数
- 一个视图,可以带有参数:将使用urlresolvers.reverse 来反向解析名称
- 一个绝对的或相对的URL,将原封不动的作为重定向的位置。
默认返回一个临时的重定向;传递permanent=True 可以返回一个永久的重定向。
示例:
你可以用多种方式使用redirect() 函数。
from django.conf.urls import url
from django.contrib import admin
from django.shortcuts import HttpResponse, render, redirect
lst = [1, 2, 3, 4, 5]
def demo(request, msg):
return redirect('http://www.baidu.com')
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^demo/', demo, {'msg': 'hello world'}, name='flag')
]
传递对象
将调用get_absolute_url() 方法来获取重定向的URL:
from django.shortcuts import redirect def my_view(request): ... object = MyModel.objects.get(...) return redirect(object)
传递视图名称
可以带有位置参数和关键字参数;将使用reverse() 方法反向解析URL:
def my_view(request): ... return redirect('some-view-name', foo='bar')
传递url
def my_view(request): ... return redirect('/some/url/')
def my_view(request): ... return redirect('http://example.com/')
默认情况下,redirect() 返回一个临时重定向。以上所有的形式都接收一个permanent 参数;如果设置为True,将返回一个永久的重定向:
def my_view(request): ... object = MyModel.objects.get(...) return redirect(object, permanent=True)
重定向
redirect关键点:两次请求过程,掌握流程。 注意:render和redirect的区别: 1、 if 页面需要模板语言渲染,需要的将数据库的数据加载到html,那么render方法则不会显示这一部分。 2、 the most important: url没有跳转到/yuan_back/,而是还在/login/,所以当刷新后又得重新登录。