首页 > 技术文章 > 初识Django

maoruqiang 2019-06-05 21:44 原文

一 使用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有三大主流框架:

  1. Django:大而全,自带了很多功能模块,类似航空母舰(缺点:有点笨重),目前用的比较多
  2. Flask:短小精悍,自带的功能模块很少,大部分都依赖于第三方模块(小而轻)
  3. 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 \r\n\r\n'))
    2. 想要实现网页地址加不同的后缀能显示不同的内容(如:127.0.0.1:8080/index,网页显示index)
    3. data = conn.recv(1024), 打印该data,发现里面装的二进制存的是数据格式之请求
    4. 将data解码为字符串,字符串二次切分(1:'\r\n', 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)
仿Django框架推导(随便看看就好,别当真)

   二话不说,直接上代码。

  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 \r\n\r\n')
    conn.send(b'hello')
    conn.close()
1. 简单实现网页连接服务端

    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('\r\n')[0]
    current_path = data.split(' ')[1]
    # 先发送一个头信息,让接下去发送的消息符合HTTP协议
    conn.send(b'HTTP/1.1 200 OK \r\n\r\n')
    if current_path == '/index':
        conn.send(b'index')
    else:
        conn.send(b'hello')
    conn.close()
2. 拿127.0.0.1:8081/index 举例

  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 \r\n\r\n')
    # 然后头信息固定是列表里面套元组的形式,元组里必须要有两个元素,元组的个数至少一个
    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()
wsgiref实现根据地址后缀展示不同内容

  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()
服务器servser代码
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)
抽离出的views.py代码
from views import *

urls = [
    ('/index', index),
    ('/get_time', get_time),
    ('/get_user', get_user),
    ('/get_db', get_db)
]
抽离出的urls.py代码
<!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>
views.py get_time函数需要用的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>
views.py get_user函数需要用的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>
views.py get_db函数需要用的html文件

  上述就是用Django之前的最终版本了,里面还加了一点东西:

  1. 用jinja2模块渲染网页,实现后端数据发给前端后,前端也能像后端一样处理数据。(语法看上面4.模块渲染)。
  2. 用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),上图。

 

推荐阅读