Django
理解部分
软件开发架构
- C/S:客户端/服务端
- B/S:浏览器/服务器
- ps:BS本质也是CS
HTTP协议(超文本传输协议)
-
定义:浏览器客户端与万维网服务器之间通信的相关规则。
-
四大特性
- 基于TCP/IP之上作用于应用层
- 基于请求响应
- 无状态(cookies session token)
- 无连接(one night 情)
- 长连接(websocket——软件加聊天功能,HTTP协议的大补丁)
-
数据格式
- 请求格式:请求首行(请求方式、协议版本)\r\n请求头(一大堆键值对)\r\n\r\n请求体(真正的数据,只有发post请求时才有,get请求不会有)
- 响应格式:响应首行\r\n响应头\r\n\r\n响应体
-
响应状态码:用特定的数字表示一些意思
- 1XX:服务端已经成功接收到数据,正在处理,可继续提交数据
- 2XX:服务端成功响应(200请求成功)
- 3XX:重定向(eg:未登录跳转到登录页面)
- 4XX:请求错误(404:请求资源不存在;403:拒绝访问,请求不符合访问标准)
- 5XX:服务器内部错误(500)
-
服务端
- 24小时提供服务
- 固定的IP和端口
- 承受高并发(多个客户端连接)
-
请求方式
- get请求:朝别人要数据
- post请求:向别人提交数据(eg:用户登录)
-
端口号:标识计算机上某一个应用程序
- 0-1024:操作系统默认
- 1024-8000:常用软件默认端口
- mysql:3306
- Redis:6379
- MongoDB:27017
- flask:5000
- Django:8000
- 8000以后:避免端口冲突
-
url:统一资源定位符,相当于图书馆里面的书分类
-
纯手撸web框架
- 手动书写socket
- 手动处理HTTP格式数据
-
基于wsgiref模块
- 该模块实现了上面两个手动的过程
- 根据功能的不同拆分成不同的py文件
- urls.py——路由(后缀名)与视图函数(将HTML页面放到浏览器渲染)对应关系。
- views.py——存放视图函数(用于处理业务逻辑--函数和类)
- templates——模板文件夹(一堆HTML文件)
-
动静态网页
- 静态网页:数据是写死的,万年不变
- 动态网页:数据是实时获取的
- 后端获取当前时间展示到前端
- 后端获取数据库中的数据展示到前端
-
jinja2模块
-
模板渲染:后端获取的数据类型传递给HTML页面
-
模板语法(极其贴近python后端语法)
-
{{ 变量名 }}
-
{{ 变量名 }}
-
-
-
简易版本请求流程图
-
Python三大主流web框架及框架组成
-
框架组成
- A:socket部分
- B:路由与视图函数对应关系
- C:模板语法
-
Django
- 优点:大而全,自带功能特别多
- 缺点:过于笨重
- A别人wsgiref,B、C自己
-
Flask
- 优点:小而精,自带功能特别少,三方模块特别多
- 缺点:依赖第三方模块
- A别人werkeug(基于wfgiref)、B自己、C别人(jinja2)
-
Tornado
- 异步(任务提交方式)非阻塞(软件运行状态),支持高并发,可开发游戏服务器
- 三者自己
-
-
Django注意事项
- 计算机名称不能用中文
- 一个pycharm窗口就是一个项目
- 项目名尽量不要用中文
-
Django版本问题
- 1.11.9~1.11.13(LTS可维护版本)
- 1.X;2.X
- pycharm配置中安装
-
如何验证Django是否安装成功?
- 命令行输入django-admin
-
如何使用Django?
-
一个Django项目类似于一所大学,而app就类似于大学里面的学院,Django其实就是用来开发一个个应用的,一个app就相当于一块独立的功能,并支持任意多个app
-
命令行使用
- 创建Django项目
- django-admin startproject 项目名
- 启动Django项目
- python manage.py runserver
- 创建应用app
- python manage.py startapp app01
- 注意
- 不会自动创建templates文件夹
- settings文件夹,需要手动配置templates路径
os.path.join(BASE_DIR, 'templates')
- django server配置端口号,保证一个项目一个端口号
- 新创建的app需要settings配置文件中注册
app01.apps.App01Config
,简写app01
,而pycharm只会注册第一个创建项目时的应用。 - 创建其他app:tools->run manage.py task 命令行输入关键字startapp app名,并且一定要注册
- 创建Django项目
-
pycharm使用
-
-
项目名
- 跟项目名同名的文件夹
- settings.py配置文件
- urls.py路由与视图函数对应关系
- 应用名
- migrations文件夹
- 存放数据库迁移记录,类似于日志
- admin.py django后台管理
- apps.py注册相关
- models.py模型类
- tests.py测试文件
- views.py存放视图函数
- migrations文件夹
- templates文件夹
- 存放HTML文件
- manage.py django入口文件
- 跟项目名同名的文件夹
实践部分
需求1:如何实现将服务端的数据发送到浏览器客户端?
思路:
- 如何将浏览器客户端与服务端建立链接
- 根据HTTP协议
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, addr = server.accept()
data = conn.recv(1024)
conn.send(b'hello world')
conn.close()
此时浏览器客户端访问服务端IP和端口报错,这是因为服务端确实发消息了,但是浏览器客户端不认识,即发送的响应无效,那应该怎么解决呢?想到的是发送的消息应该遵循HTTP协议。
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, addr = server.accept()
data = conn.recv(1024)
print(data)
conn.send(b'HTTP:1.1 200 OK \r\n\r\n<h1>hello world</h1>')
conn.close()
效果是酱紫的,并且可以将服务器上的内容渲染到浏览器上,那我们再来看看服务端返回什么结果呢?HTTP协议数据
b'GET / HTTP/1.1\r\n # 请求首行(请求方式和协议版本)
Host: 127.0.0.1:8080\r\n
Connection: keep-alive\r\n
Cache-Control: max-age=0\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36\r\n
Sec-Fetch-Mode: navigate\r\nSec-Fetch-User: ?1\r\n
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n
Sec-Fetch-Site: none\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n #请求头:一大堆键值对,用来标识一些信息
\r\n' #请求体:一定要注意这里是空行并且没有数据,因为这是get请求,如果发post请求就有数据啦
需求2:如何根据用户不同的输入地址,返回不同的内容?
思路:
-
如何拿到用户输入的地址(需求1就能拿到)--请求首行
-
如何拿到后缀--但是此时是bytes格式,必须转成字符串格式才能切割,这里介绍一种万能方法,从此再也不用担心encode与decode绕来绕去了:
byte = b'hello world' s = str(byte, encoding='utf-8') print(s, type(s)) # hello world <class 'str'> b = bytes(s, encoding='utf-8') print(b, type(b)) # b'hello world' <class 'bytes'>
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, addr = server.accept()
conn.send(b'HTTP:1.1 200 OK \r\n\r\n')
data = conn.recv(1024)
current_path = str(data, encoding='utf-8').split('\r\n')[0].split(' ')[1]
if current_path == '/login':
# 业务逻辑
conn.send(b'login')
elif current_path == '/index':
conn.send(b'index')
else:
conn.send(b'404')
conn.close()
此时又出现新问题了,那就是作为一名Python开发攻城狮,始终不能忘了龟叔对Python的定义,那就是简洁优雅,不然如何提升逼格呢?随着后缀名的增多,你看socket套接字重复倒无所谓,但是这么多elif谁受得了呢,所有我们得想一种办法。那就是看看有什么模块能不能帮我们做这些事情,我们直接导包不就行了,还真有一种那就是wsgiref,自己手撸太累了,让别人帮我们撸socket部分和elif部分。
需求3:如何基于wsgiref实现根据用户输入的不同URL后缀,返回的相同内容呢?
from wsgiref.simple_server import make_server
def app(env, response):
"""
:param env: 请求相关的所有数据,以字典形式返回-environ环境变量
:param response: 响应相关的所有数据
:return:
"""
response('200 ok', []) # 固定写法
print(env)
return [b'hello world']
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, app)
# 实时监听URL,只要有客户端是以('127.0.0.1',8080)来连接,后缀名统一交给app函数去处理
server.serve_forever() # 启动服务端
需求4:如何基于wsgiref模块实现根据用户不同的输入地址,返回不同的内容?
from wsgiref.simple_server import make_server
def app(env, response):
"""
:param env: 请求相关的所有数据,以字典形式返回-environ环境变量
:param response: 响应相关的所有数据
:return:
"""
response('200 ok', []) # 固定写法
current_path = env.get('PATH_INFO')
if current_path == '/index':
return [b'index']
elif current_path == '/login':
return [b'login']
else:
return [b'404 error']
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, app)
# 实时监听URL,只要有客户端是以('127.0.0.1',8080)来连接,后缀名统一交给app函数去处理
server.serve_forever() # 启动服务端
需求5:如何定义一种数据类型,存放后缀与函数名之间的对应关系,使得代码既不显得冗余,又可以实现更多的功能?
from wsgiref.simple_server import make_server
def index():
return 'index'
def login():
return 'login'
def error():
return '404 error'
urls = [
('/index', index), # 定义成函数,方便处理业务逻辑
('/login', login),
]
def app(env, response):
"""
:param env: 请求相关的所有数据,以字典形式返回-environ环境变量
:param response: 响应相关的所有数据
:return:
"""
response('200 ok', []) # 固定写法
current_path = env.get('PATH_INFO')
# 先定义一个变量名,用于存储后续得到的变量名
func = None
# 利用for循环取后缀
for url in urls:
if current_path == url[0]:
func = url[1]
break # 主动结束匹配
if func:
res = func()
else:
res = error()
return [bytes(res, encoding='utf-8')] # 所有函数全部返回字符串,再转换成bytes格式
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, app)
# 实时监听URL,只要有客户端是以('127.0.0.1',8080)来连接,后缀名统一交给app函数去处理
server.serve_forever() # 启动服务端
需求5:如何基于wsgiref实现分层,避免杂乱无章?
# urls.py
from views import *
urls = [
('/index', index),
('/login', login),
]
# views.py
def index():
return 'index'
def login():
return 'login'
def error():
return '404 error'
# wsgiref.py
from wsgiref.simple_server import make_server
from urls import urls
from views import *
def app(env, response):
"""
:param env: 请求相关的所有数据,以字典形式返回-environ环境变量
:param response: 响应相关的所有数据
:return:
"""
response('200 ok', []) # 固定写法
current_path = env.get('PATH_INFO')
# 先定义一个变量名,用于存储后续得到的变量名
func = None
# 利用for循环取后缀
for url in urls:
if current_path == url[0]:
func = url[1]
break # 主动结束匹配
if func:
res = func()
else:
res = error()
return [bytes(res, encoding='utf-8')] # 所有函数全部返回字符串,再转换成bytes格式
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, app)
# 实时监听URL,只要有客户端是以('127.0.0.1',8080)来连接,后缀名统一交给app函数去处理
server.serve_forever() # 启动服务端
需求6:如何将后端获取的数据传递给HTML页面,页面上展示当前时间?
- 文件的后缀名是给人看的。
# urls.py
from views import *
urls = [
('/index', index),
('/login', login),
('/get_time', get_time),
]
# wsgiref.py
from wsgiref.simple_server import make_server
from urls import urls
from views import *
def app(env, response):
"""
:param env: 请求相关的所有数据,以字典形式返回-environ环境变量
:param response: 响应相关的所有数据
:return:
"""
response('200 ok', []) # 固定写法
current_path = env.get('PATH_INFO')
# 先定义一个变量名,用于存储后续得到的变量名
func = None
# 利用for循环取后缀
for url in urls:
if current_path == url[0]:
func = url[1]
break # 主动结束匹配
if func:
res = func()
else:
res = error()
return [bytes(res, encoding='utf-8')] # 所有函数全部返回字符串,再转换成bytes格式
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, app)
# 实时监听URL,只要有客户端是以('127.0.0.1',8080)来连接,后缀名统一交给app函数去处理
server.serve_forever() # 启动服务端
# get_time.py
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>获取当前时间</title>
</head>
<body>
<h1>ahkhafgl</h1>
</body>
</html>
# views.py
def index():
return 'index'
def login():
return 'login'
def error():
return '404 error'
def get_time():
import datetime
print(datetime.datetime.now()) # 2019-10-19 15:46:53.518173,转化成人类友好的字符串类型
current_time = datetime.datetime.now().strftime('%Y-%m-%d %X')
with open(r'D:\test\templates\get_time.html', 'r', encoding='utf-8') as f:
data = f.read()
data = data.replace('ahkhafgl', current_time)
return data
需求7:如何实现把一个字典传给HTML页面,并且页面能把它当做字典来使用(点属性或方法得到其中的值)?
# urls.py
from views import *
urls = [
('/get_user', get_user),
]
# wsgiref.py
from wsgiref.simple_server import make_server
from urls import urls
from views import *
def app(env, response):
"""
:param env: 请求相关的所有数据,以字典形式返回-environ环境变量
:param response: 响应相关的所有数据
:return:
"""
response('200 ok', []) # 固定写法
current_path = env.get('PATH_INFO')
# 先定义一个变量名,用于存储后续得到的变量名
func = None
# 利用for循环取后缀
for url in urls:
if current_path == url[0]:
func = url[1]
break # 主动结束匹配
if func:
res = func()
else:
res = error()
return [bytes(res, encoding='utf-8')] # 所有函数全部返回字符串,再转换成bytes格式
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, app)
# 实时监听URL,只要有客户端是以('127.0.0.1',8080)来连接,后缀名统一交给app函数去处理
server.serve_forever() # 启动服务端
# get_user.py
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>获取字典</title>
</head>
<body>
<h1>{{user}}</h1>
<h2>{{user.name}}</h2>
<h3>{{user['age']}}</h3>
<h4>{{user.get('hobby')}}</h4>
</body>
</html>
# views.py
def get_user(env): # env参数是为了方便后续可能使用
from jinja2 import Template
dic = {'name': 'allen', 'age': '18', 'hobby': ['read', 'music']}
with open(r'D:\test\templates\get_user.html', 'r', encoding='utf-8') as f:
data = f.read()
temp = Template(data)
res = temp.render(user=dic)
return res
需求8:如何实现从数据库读取数据展示到前端?
# urls.py
from views import *
urls = [
('/get_db', get_db),
]
# wsgiref.py
from wsgiref.simple_server import make_server
from urls import urls
from views import *
def app(env, response):
"""
:param env: 请求相关的所有数据,以字典形式返回-environ环境变量
:param response: 响应相关的所有数据
:return:
"""
response('200 ok', []) # 固定写法
current_path = env.get('PATH_INFO')
# 先定义一个变量名,用于存储后续得到的变量名
func = None
# 利用for循环取后缀
for url in urls:
if current_path == url[0]:
func = url[1]
break # 主动结束匹配
if func:
res = func(env)
else:
res = error(env)
return [bytes(res, encoding='utf-8')] # 所有函数全部返回字符串,再转换成bytes格式
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, app)
# 实时监听URL,只要有客户端是以('127.0.0.1',8080)来连接,后缀名统一交给app函数去处理
server.serve_forever() # 启动服务端
# get_user.py
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1 class="text-center">用户列表</h1>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>pwd</th>
</tr>
</thead>
<tbody>
{% for user_dict in user_list %}
<tr>
<td>{{ user_dict.id }}</td>
<td>{{ user_dict.name }}</td>
<td>{{ user_dict.pwd }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
# views.py
from pymysql.cursors import DictCursor
from jinja2 import Template
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
passwd='',
db='db02',
charset='utf8',
autocommit=True
)
cursor = conn.cursor(DictCursor) # 这样返回列表套字典,不然是元组
sql = 'select * from user_info'
cursor.execute(sql) # 影响的行数
res = cursor.fetchall() #返回列表套字典
print(res)
with open(r'D:\test\templates\get_db.html', 'r', encoding='utf-8') as f:
data = f.read()
temp = Template(data)
return temp.render(user_list=res)