一 使用Django前的准备
1. http协议
Django是python的web框架之一,既然要学习web框架,首先就要明白什么是http协议。
首先,超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的WWW文件都必须遵守这个标准。它规定了客户端与服务端消息传输的格式,是客户端和服务器端请求和应答的标准(TCP)。
1.1 四大特性:
1. 基于TCP/IP协议,作用于应用层的协议
2. 基于请求响应(每一次点击都是一次请求)
3. 无状态(同一客户端无论连接多少次,它都当你第一次来)
4. 无连接(websocket,数据请求完成之后就断开)
1.2 数据格式之请求:
请求首行()
请求头(一堆k,v键值对)
(这里是空格,数据格式请求之一!!!)
请求体(post请求携带的数据)
1.3 数据格式之响应
响应首行()
响应头(一堆k,v键值对)
(这里是空格,数据格式响应之一!!!)
响应体(post请求携带的数据)
1.4 响应状态码
1xx 服务器已经成功接收到你的数据正在处理,你可以继续提交其他数据
2xx 请求成功 服务器已经将你的请求的数据发送给你了
3xx 重定向
4xx 请求资源错误/不存在
5xx 服务器错误
注意:我们通常使用谷歌浏览器,可以通过 检查->Network查看网页加载的信息,里面有数据格式的信息。
2. 动静态页面
静态页面:页面上的数据都是写死的,万年不变
动态页面:
页面上的数据是从后端动态获取的
比如后端获取当前时间
后端获取数据库数据然后传递给前端页面
3. 什么是web框架
Web应用框架(Web application framework)是一种开发框架,用来支持动态网站、网络应用程序及网络服务的开发。其类型有基于请求的和基于组件的两种框架。
目前python有三大主流框架:
- Django:大而全,自带了很多功能模块,类似航空母舰(缺点:有点笨重),目前用的比较多
- Flask:短小精悍,自带的功能模块很少,大部分都依赖于第三方模块(小而轻)
- Tornado:异步非阻塞,主要用来处理高IO 多路复用的情况,可以写游戏后端
4. 模块渲染
后端生成的数据直接传递给前端页面使用(并且前端页面可以灵活操作该数据)》》》模板语法
模板渲染 模板语法需要依赖于第三方模块(这里使用jinja2模块):
pip install jinja2
from jinja2 import Template
模板语法 jinja2支持前端直接使用类似于python的语法操作数据
1. 后端打开HTML文件,用r模式读取数据,用data变量接收
2. tem = Template(data) //提交渲染页面
3. tem.render(user_dic = {'name': 'jason', 'password': 123})
//这样过后,前端可以通过user_dic拿到该字典,固定语法如下{{}},里面的字典中写的语法类似于python
<p>{{user_dic}}</p>
<p>{{user_dic.name}}</p>
<p>{{user_dic['password']}}</p>
<p>{{user_dic.get('name')}}</p>
//for模板循环语法
{%for user in user_dict%} //user_dict是数据库cursor.fetchall()回来的数据:[{},{},{}]
循环体代码
{%endfor%}
//实现将改数据发给前端,前端用table美化数据展示
该for循环放在table的tbody里面,每一次循环都生成一次tr
//templates文件夹用来存放所有的HTML文件
5. 仿Django框架推导
1. 新建一个socket TCP服务端,为了实现浏览器连接服务端, 让服务端消息符合HTTP协议(在开头加一个conn.send(b'HTTP/1.1 200 OK ')) 2. 想要实现网页地址加不同的后缀能显示不同的内容(如:127.0.0.1:8080/index,网页显示index) 3. data = conn.recv(1024), 打印该data,发现里面装的二进制存的是数据格式之请求 4. 将data解码为字符串,字符串二次切分(1:' ', 2:' ')列表list索引1取到的就是地址后缀 5. if list[1] == '/index': conn.send('index');这样就实现了 6. 还不够方便,要实现并发就要导入模块,这使用wsgire模块(自动将请求与接收的数据封装成符合HTTP协议的格式) (使用同socketserver有点像,第二个参数传run(socketserver是传一个类名,这里run是函数名)) 7. run会接收到两个参数env, response,env就是之前data解码后的字符串,不过被自动切分并组成大字典的形式, env['PATH_INFO']就是后缀名,response用来设置HTTP响应的状态码和头信息 8. 后缀名多了之后要写一堆elif判断,所以抽离出一个对应关系urls = [('后缀名', 函数名), ('后缀名', 函数名)] 9. urls叫路由,函数叫做视图函数,我们将它们各放入其他模块里(urls.py, views.py),之后导入模块即可。 10. 为了后端数据(比如字典)发给前端使用,引入jinja2模块。 from jinja2 import Template def reg(env): user_dic = {'name': 'jason', 'password': 123} with open('html文件名', 'r', encoding='utf-8') as f: data = f.read() tem = Template(data) //提交渲染页面 return tem.render(user_dic=user_dic)
二话不说,直接上代码。
5.1 第一步,我们通过发送头conn.send('HTTP/1.1 200 ok /r/n/r/n')消息能成功展示在网页上
import socket server = socket.socket() server.bind(('127.0.0.1', 8081)) server.listen(5) while True: conn, addr = server.accept() # 一定要先接收一下信息,因为网页作为客户端连接会发送GET请求 data = conn.recv(1024) # 先发送一个头信息,让接下去发送的消息符合HTTP协议 conn.send(b'HTTP/1.1 200 OK ') conn.send(b'hello') conn.close()
5.2 第二步,根据地址后缀的不同展示不同的内容
import socket server = socket.socket() server.bind(('127.0.0.1', 8081)) server.listen(5) while True: conn, addr = server.accept() # 一定要先接收一下信息,因为网页作为客户端连接会发送GET请求 data = conn.recv(1024) # 将data输出,然后复制出来看看,可以找到规律用split切分 print(data) data = data.decode('utf-8').split(' ')[0] current_path = data.split(' ')[1] # 先发送一个头信息,让接下去发送的消息符合HTTP协议 conn.send(b'HTTP/1.1 200 OK ') if current_path == '/index': conn.send(b'index') else: conn.send(b'hello') conn.close()
5.3 第三步, 导入wsgiref模块实现并发(它会封装请求和接收的数据,让数据符合HTTP协议)
from wsgiref.simple_server import make_server def run(env, response): # response是response用来设置HTTP响应的状态码和头信息,状态码固定'200 OK' # 状态码类似我们之前的conn.send(b'HTTP/1.1 200 OK ') # 然后头信息固定是列表里面套元组的形式,元组里必须要有两个元素,元组的个数至少一个 response('200 ok', [('name', 'egon'), ('password', '123')]) current_path = env['PATH_INFO'] if current_path == '/index': res = 'index' else: res = '404 not find' # return的也要求是列表里面一个二进制数据 return [res.encode('utf-8')] if __name__ == '__main__': server = make_server('127.0.0.1', 8081, run) server.serve_forever()
5.4 第四步,当后缀名越来越多,if判断分支变多,所以需要抽离代码来优化。
from wsgiref.simple_server import make_server def index(): return 'index' urls = [ ('/index', index) ] def error(): return '404 not found' def run(env, response): # response响应状态码和头信息 response('200 ok', [('name', 'egon'), ('password', '123')]) # 取出地址后缀 current_path = env['PATH_INFO'] func = None for url in urls: if current_path == url[0]: func = url[1] # 只要找到了就终止for循环 break if func: res = func() else: res = error() return [res.encode('utf-8')] if __name__ == '__main__': server = make_server('127.0.0.1', 8080, run) server.serve_forever()
5.5 第五步, 再次优化, 将抽离出来的urls和响应执行的函数分别作为一个模块。
from wsgiref.simple_server import make_server from urls import * def run(env, response): response('200 ok', [('name', 'egon'), ('password', '123')]) current_path = env['PATH_INFO'] func = None for url in urls: if current_path == url[0]: func = url[1] break if func: res = func() else: res = error() return [res.encode('utf-8')] if __name__ == '__main__': server = make_server('127.0.0.1', 8080, run) server.serve_forever()
import time from jinja2 import Template import pymysql def index(): return 'index' def error(): return '404 not found' def get_time(): with open('templates/get_time.html', 'r', encoding='utf-8') as f: data = f.read() res = data.replace('time', time.strftime('%Y-%m-%d %X')) return res def get_user(): user_dic = {'name': 'egon', 'password': '123'} with open('templates/get_user.html', 'r', encoding='utf-8') as f: data = f.read() tem = Template(data) return tem.render(user_dic=user_dic) def get_db(): # 连接数据库 conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='root', database='day54', charset='utf8', autocommit=True ) cursor = conn.cursor(pymysql.cursors.DictCursor) sql = 'select * from web_frame' cursor.execute(sql) # 得到数据库里web_frame表所有的数据:[{}, {}, {}] user_list = cursor.fetchall() with open('templates/get_db.html', 'r', encoding='utf-8') as f: data = f.read() tem = Template(data) return tem.render(user_list=user_list)
from views import * urls = [ ('/index', index), ('/get_time', get_time), ('/get_user', get_user), ('/get_db', get_db) ]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>get_time</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> </head> <body> @@time@@ </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>get_user</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <p>{{user_dic}}</p> <p>{{user_dic.name}}</p> <p>{{user_dic['name']}}</p> <p>{{user_dic.get('password')}}</p> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>get_db</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <style> th { text-align: center; } </style> </head> <body> <div class="container"> <div class="row"> <div class="col-md-8"> <table class="table table-bordered table-striped table-hover"> <thead> <tr> <th>id</th> <th>框架名称</th> <th>socket</th> <th>路由器与视图函数</th> <th>模块渲染</th> </tr> </thead> <tbody class="text-center"> {%for user_dic in user_list%} <tr> <td>{{user_dic['id']}}</td> <td>{{user_dic['框架名称']}}</td> <td>{{user_dic['socket']}}</td> <td>{{user_dic['路由器与视图函数']}}</td> <td>{{user_dic['模块渲染']}}</td> </tr> {%endfor%} </tbody> </table> </div> </div> </div> </body> </html>
上述就是用Django之前的最终版本了,里面还加了一点东西:
- 用jinja2模块渲染网页,实现后端数据发给前端后,前端也能像后端一样处理数据。(语法看上面4.模块渲染)。
- 用pymysql模块连接数据库,并将数据库取出的内容发给前端(该HTML页面也先用jinja2渲染)。前端运动Bootstrap的table展示内容。
总结:
关于wsfiref.simple_server中的make_server的使用
- wsfiref.simple_server中的make_server的使用与socketserver模块使用有点像。不过它的第二个参数是函数名,函数会接收到两个参数env和response(这两个只是变量名),第一个是大字典(客户端连接时发送的GET请求数据被切分后组成的字典,其中env['PATH_INFO']能拿到地址的后缀)。然后response是用来设置HTTP想要的状态码和信息头,固定写法--列表里面两个元素['200 OK',[('信息1', '信息2')]] ,第一个元素是状态码字符串,第二个元素是列表嵌套元组(元组必须有两个元素,但是元组个数不限)。
- run函数必须return一个存放二进制数据的列表。 ['二进制字符串']
-
jinja2渲染页面基本写法:
def reg(): user_dic = {'name': 'jason', 'password': '123'} with open('html文件名', 'r', encoding='utf-8') as f: data = f.read() tem = Template(data) #提交渲染页面 # 将user_dic传给前端 return tem.render(user_dic=user_dic)
二. 关于Django的部分知识
1. 要正常运行Django的注意事项
1.1 注意事项(要正常跑Django,遵循以下原则):
1. 计算机名称不能有中文
2. 一个pycharm窗口就是一个项目,不要多项目放同一个窗口里面
3. 项目名不能有中文
1.2 强调:
1. 用Django一定要保证只有一个在运行状态
2. 记得清楚浏览器缓存(谷歌浏览器:检查->右上角三个点->settings->Network->勾选其中的Disable cache)
2. 版本问题
Django下载(LTS代表官方还在维护):
推荐下载1.11.11版本
1. 命令行直接下载:pip install django==1.11.11
2. pycharm下载settings里面project->interpreter->+搜索django下载
验证是否下载成功(用cmd窗口):
django-admin
3. Django中app的概念
如果说Django是一个学校,app就是各个学院,每个app对应一个功能。所以Django项目新建的时候我们肯定要建app,空荡荡的学校多恐怖。
4. 创建Django项目的两种方式:
4.1 方式一(命令行创建)
创建Django项目:
django-admin startproject 项目名
cd 项目名
创建app应用
python3 manage.py startapp app01
启动Django服务
python3 manage.py runserver
关闭Django服务
ctrl + c
ps:用命令行创建Django项目时不会自动创建templates文件夹
需要手动创建(随后需要去配置文件配置TEMPLATES,该该文件夹路径加入其中)
4.2 方式二(pycharm创建)
new project-> Django-> Application name写上(环境不要用虚拟的)->勾选左下角Enable Django admin(后台管理)
创建app:
1. pycharm命令行创建(tab键可以补全)
python3 manage.py startapp app01
2. tools -> run manage.py task
5. Django各个文件的作用
5.1 应用名
migrations 数据库迁移记录相关数据
admin.py Django后台管理相关
models.py 模型表相关
views.py 视图函数相关(视图函数不一定就是函数,也可以是类),然后每个视图函数都必须返回一个HttpResponse(render和redirect内部也是return的HttpResponse)。
5.2 项目名
settings.py 配置文件
urls.py 路由与视图函数的映射关系
wsgi.py wsgiref
5.3 其他:
templates 项目用到的所有HTML文件
manage.py Django入口文件
5.3 settings中需要注意的地方
确认templates已加入环境变量
DATABASES是用来连接数据库的,可以修改数据库名字来实现连接其他数据库。
新建的app一定要加入到INSTALLED_APPS
中间件MIDDLEWARE
6. Django给前端传值的两种方式
6.1 Django小白必会三板斧(每个视图函数都要有一个request参数,因为Django会自动传一个参数,我们就要拿一个参数接收)
from django.shorcuts import render, HttpResponse, redirect
HttpResponse 返回字符串
redirect 重定向,既可以重定向至别人的网址,也可以是自己的网址
render 返回一个HTML页面 render(request, 'html文件名')
6.2 两种给前端传值的方式
第一种:
def reg(request): user_dic = {'name': 'jason', 'password': 123} #locals()会把这个函数内出现的所有变量名都传给html文件 return render(request, 'html文件名', locals())
第二种:
def reg(request): user_dic = {'name': 'jason', 'password': 123} return render(request, 'html文件名', {'user_dic': user_dic})
注意:
- 第一种有时候效率会比较低,如果变量名很多的话,而且我们并不是所有变量名都要用到。
- django识别到你的代码变化之后会自动重启,但是有时候速度比较慢,我们可以手动重启,也可以多刷新几次前端页面。
- django的render使用与jinja2的render不一样,不要搞混了。比如说要把一个字典user_dic给前端,jinja2是render(user_dic = user_dic);django是render(request, 'html文件名', {'user_dic': user_dic})或者是render(request, 'html文件名', locals())
- django使用render时如果用locals(),它会把所在的那个函数里所有变量名都传给前端,简单粗暴。
- 当我们在网页中输入URL比如:127.0.0.1:8080/login,并回车时,会发现URL变成了127.0.0.1:8080/login/,这并不是浏览器自动帮我们加上的,而是login在Django路由中匹配不到,它会帮我们变成login/再去匹配一次,内部走的是重定向(响应状态码3xx),上图。