Django

理解部分

软件开发架构

  • C/S:客户端/服务端
  • B/S:浏览器/服务器
  • ps:BS本质也是CS

HTTP协议(超文本传输协议)

  • 定义:浏览器客户端与万维网服务器之间通信的相关规则。

  • 四大特性

    • 基于TCP/IP之上作用于应用层
    • 基于请求响应
    • 无状态(cookies session token)
    • 无连接(one night 情)
      • 长连接(websocket——软件加聊天功能,HTTP协议的大补丁)
  • 数据格式

    • 请求格式:请求首行(请求方式、协议版本) 请求头(一大堆键值对) 请求体(真正的数据,只有发post请求时才有,get请求不会有)
    • 响应格式:响应首行 响应头 响应体
  • 响应状态码:用特定的数字表示一些意思

    • 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后端语法)

      • {{ 变量名 }}

      • {% for %}

        {{ 变量名 }}

        {% endfor %}

  • 简易版本请求流程图

    img

  • 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名,并且一定要注册
    • pycharm使用

  • 项目名

    • 跟项目名同名的文件夹
      • settings.py配置文件
      • urls.py路由与视图函数对应关系
    • 应用名
      • migrations文件夹
        • 存放数据库迁移记录,类似于日志
      • admin.py django后台管理
      • apps.py注册相关
      • models.py模型类
      • tests.py测试文件
      • views.py存放视图函数
    • 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()

img

此时浏览器客户端访问服务端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 

<h1>hello world</h1>')
    conn.close()

img

效果是酱紫的,并且可以将服务器上的内容渲染到浏览器上,那我们再来看看服务端返回什么结果呢?HTTP协议数据

b'GET / HTTP/1.1
 # 请求首行(请求方式和协议版本)
Host: 127.0.0.1:8080

Connection: keep-alive

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36

Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3

Sec-Fetch-Site: none

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.9
 #请求头:一大堆键值对,用来标识一些信息

' #请求体:一定要注意这里是空行并且没有数据,因为这是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'>
    

img

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 

')
    data = conn.recv(1024)
    current_path = str(data, encoding='utf-8').split('
')[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()

img

此时又出现新问题了,那就是作为一名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()  # 启动服务端

img

需求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:	est	emplatesget_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:	est	emplatesget_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:	est	emplatesget_db.html', 'r', encoding='utf-8') as f:
        data = f.read()
    temp = Template(data)
    return temp.render(user_list=res)

img

原文地址:https://www.cnblogs.com/daizongqi/p/11743690.html