Web框架的原理和Django初识

1、Web框架的本质
1、本质
实际上Web应用本质上就是一个socket服务端,
而用户的浏览器就是一个socket客户端。 


2、最原始的web框架
socket服务端
import socket

sk = socket.socket()
sk.bind(("127.0.0.1", 80))
sk.listen()

while True:
    conn, addr = sk.accept()
    data = conn.recv(5120)
    print(data)  # 打印一下接收到的数据是什么
    conn.send(b"Hello World")
    conn.close()

上面这几行代码就是最基础的web框架的服务端,
用户使用浏览器输入网址(IP),就会给服务端发送请求消息,
好比你在浏览器输入上面服务器的ip地址   127.0.0.1:80  
浏览器就会帮你给服务端发送请求消息,服务端就能够接收到你的请求消息,
并对此能够作出相应的操做。

固然了,虽然上面的代码是web框架的本质,可是还不能进行消息的响应,为何呢?
由于没有一个统一的规则啊,你想一想若是每一个人的网站都按照本身的意愿随便定制规则,
那浏览器不就崩了?

因此必须有一个统一的规则,让你们发送消息、接收消息的时候有个格式依据,不能随便写。
这个规则就是HTTP协议,之后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来进行


如今去看一眼刚才收到的data数据是什么(客户端的请求数据)
b'GET /index/ HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n'


而后能够再看一眼平时咱们访问百度时浏览器接收到的响应信息
HTTP/1.1 301 Moved Permanently
Date: Wed, 24 Oct 2018 07:06:00 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 147
Location: https://www.cnblogs.com/
X-UA-Compatible: IE=10
X-Frame-Options: SAMEORIGIN


这样看起来,好像找不出什么规则,那就先了解一下HTTP协议(上一篇博客),在回来看看是否发现什么规则


HTTP协议对收发消息的格式要求: 每一个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的(就是能够不写)。 HTTP响应的Header中有一个 Content
-Type代表响应的内容格式。如 text/html表示HTML网页。 那么上面的请求数据data,咱们也能够给它分出Header和Body两部分(\r\n表明回车换行),以下: GET /index/ HTTP/1.1 Host: 127.0.0.1:8080 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 请求数据 因此咱们想要开始那段代码能顺利给客户端返回消息,咱们也按照HTTP协议进行发送就行了 import socket sk = socket.socket() sk.bind(('127.0.0.1',80)) sk.listen() while True: conn,addr = sk.accept() data = conn.recv(5120) print(data) conn.send(b'HTTP/1.1 200 OK\r\n\r\nHello World') # HTTP/1.1 200 OK\r\n\r\n 是响应头部为空,响应正文为Hello World的HTTP响应格式 conn.close() 这个时候你在浏览器输入127.0.0.1:80就能够收到服务端给你返回的消息了 3、服务器例子 1、根据不一样的路径返回不一样的内容 注意:咱们服务器接收到的请求数据全是bytes类型的,回车换行是用\r\n表示的 原始请求数据:b'GET /index/ HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n' """ Web框架原理: 根据不一样的URL返回不一样的内容 1. 先拿到用户访问的URL是什么 2. 返回不一样的内容 """ import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode('utf8') # 按照\r\n分割字符串 list1 = data_str.split('\r\n') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 对不一样的url返回不一样的消息 if url == '/index/': msg = 'This is index page' elif url == '/home/': msg = 'This is home page' else: msg = '404 Not Found' conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 先发送响应行 conn.send(msg.encode('utf8')) conn.close() 此时你在浏览器输入: http://127.0.0.1:8080 ---> 404 Not Found http://127.0.0.1:8080/index/ ---> This is index page http://127.0.0.1:8080/home/ ---> This is home page 2、根据不一样的路径返回不一样的内容函数版 """ Web框架原理: 根据不一样的URL返回不一样的内容函数版 1. 先拿到用户访问的URL是什么 2. 返回不一样的内容 """ import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() # 定义处理用户请求的函数 def index(url): s = '你访问的是%s页面' %url return s.encode('utf8') def home(url): s = '你访问的是%s页面' %url return s.encode('utf8') while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode('utf8') # 按照\r\n分割字符串 list1 = data_str.split('\r\n') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 对不一样的url返回不一样的消息 if url == '/index/': msg = index(url) elif url == '/home/': msg = home(url) else: msg = b'404 Not Found' # 由于有中文,全部要在响应头部添加Content-Type: text/html; charset=utf-8 conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n') # 先发送响应行 conn.send(msg) conn.close() 3、根据不一样的路径返回不一样的内容函数进阶版 """ Web框架原理: 根据不一样的URL返回不一样的内容函数进阶版 1. 先拿到用户访问的URL是什么 2.设置一个url和函数的对应关系 3. 根据对应关系返回不一样的内容 """ import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() # 定义处理用户请求的函数 def index(url): s = '你访问的是%s页面' %url return s.encode('utf8') def home(url): s = '你访问的是%s页面' %url return s.encode('utf8') # 定义一个用户访问的url和要执行的函数的对应关系 url_func = [ ('/index/', index), ('/home/', home) ] while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode('utf8') # 按照\r\n分割字符串 list1 = data_str.split('\r\n') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 循环url列表,对不一样的url返回不一样的消息 func = None for i in url_func: if i[0] == url: func = i[1] break if func: msg = func(url) else: msg = b'404 Not Found' # 由于有中文,全部要在响应头部添加Content-Type: text/html; charset=utf-8 conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n') # 先发送响应行 conn.send(msg) conn.close() 4、返回具体的html页面 import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() # 定义处理用户请求的函数 def index(url): s = '你访问的是%s页面' %url return s.encode('utf8') def home(url): s = '你访问的是%s页面' %url return s.encode('utf8') def table(url): # 返回TableOp.html with open('TableOp.html', mode='rb') as f: return f.read() # 定义一个用户访问的url和要执行的函数的对应关系 url_func = [ ('/index/', index), ('/home/', home), ('/table/',table) ] while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode('utf8') # 按照\r\n分割字符串 list1 = data_str.split('\r\n') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 循环url列表,对不一样的url返回不一样的消息 func = None for i in url_func: if i[0] == url: func = i[1] break if func: msg = func(url) else: msg = b'404 Not Found' # 由于有中文,全部要在响应头部添加Content-Type: text/html; charset=utf-8 conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n') # 先发送响应行 conn.send(msg) conn.close() 5、返回动态的html页面 """ 由于时间是会动的,因此这里用时间戳表明动态的事件 """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>这是index页面!</h1>
<p>如今的时间是:@t@</p>

</body>
</html>
index页面代码
import time
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()


# 定义处理用户请求的函数
def index(url):
    with open('index.html', 'r', encoding='utf8') as f1:
        html_s = f1.read()
        # 根据用户不一样,取出不一样的数据
        # 用不一样的数据去替换页面上的特殊符号
        now = str(time.strftime("%H:%M:%S"))
        msg = html_s.replace('@t@', now)
        return msg.encode('utf8')


def home(url):
    s = '你访问的是%s页面' %url
    return s.encode('utf8')


def table(url):
    # 返回TableOp.html
    with open('TableOp.html', mode='rb') as f:
        return f.read()


# 定义一个用户访问的url和要执行的函数的对应关系
url_func = [
    ('/index/', index),
    ('/home/', home),
    ('/table/', table)
]


while 1:
    conn, addr = sk.accept()
    # socket收到的消息是bytes类型的
    data = conn.recv(5120)
    # 从请求的消息中拿到请求的URL是什么
    data_str = data.decode('utf8')
    # 按照\r\n分割字符串
    list1 = data_str.split('\r\n')
    # url在第一个元素里面再进行分割取到
    url = list1[0].split()[1]

    # 循环url列表,对不一样的url返回不一样的消息
    func = None
    for i in url_func:
        if i[0] == url:
            func = i[1]
            break

    if func:
        msg = func(url)
    else:
        msg = b'404 Not Found'

    # 由于有中文,全部要在响应头部添加Content-Type: text/html; charset=utf-8
    conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n')  # 先发送响应行
    conn.send(msg)
    conn.close()




2、服务器程序和应用程序
1、本质介绍
对于真实开发中的python web程序来讲,通常会分为两部分:服务器程序和应用程序,而后经过一个协议链接起来,以下:
    收发socket消息(Web服务器程序)  --> uWsgi、Gunicorn、wsgiref (Nginx和tomcat)
                        WSGI协议
    业务逻辑不一样(Web应用程序)      --> Django、Flask、Webpy、bottle、Tornado
    
服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各类数据进行整理。
应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不一样的框架有不一样的开发方式,可是不管如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。
这样,服务器程序就须要为不一样的框架提供不一样的支持。这样混乱的局面不管对于服务器仍是框架,都是很差的。对服务器来讲,须要支持各类不一样框架,对框架来讲,只有支持它的服务器才能被开发出的应用使用。
这时候,标准化就变得尤其重要。咱们能够设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就能够配合使用。一旦标准肯定,双方各自实现。这样,服务器能够支持更多支持标准的框架,框架也可使用更多支持标准的服务器。
WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。
经常使用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来作服务器。


Python中Web框架的分类
    a. 收发socket消息
    b. 根据不一样的URL执行不一样的函数(业务逻辑)
    c. 字符串替换(动态网页)

    1. 第一种分类:(按照上面的三部分功能划分)
        1. 本身实现b和c,使用第三方的a      --> Django
        2. 本身实现b,使用第三方的a和c      --> Flask
        3. 本身实现a、b、c                           --> Tornado
    2. 第二种分类:
        1. Django(大而全)
        2. 其余


        
        
2、例子
1、wsgiref版web开发
"""
利用wsgiref模块来替换咱们本身写的web框架的socket server部分
"""

import time
from wsgiref.simple_server import make_server


# 定义处理用户请求的函数
def index(url):
    with open('index.html', 'r', encoding='utf8') as f1:
        html_s = f1.read()
        # 根据用户不一样,取出不一样的数据
        # 用不一样的数据去替换页面上的特殊符号
        now = str(time.strftime("%H:%M:%S"))
        msg = html_s.replace('@t@', now)
        return msg.encode('utf8')


def home(url):
    s = '你访问的是%s页面' %url
    return s.encode('utf8')


def table(url):
    # 返回TableOp.html
    with open('TableOp.html', mode='rb') as f:
        return f.read()


# 定义一个用户访问的url和要执行的函数的对应关系
url_func = [
    ('/index/', index),
    ('/home/', home),
    ('/table/', table)
]

# wsgiref模块的格式要求
def run_server(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 设置HTTP响应的状态码和头信息
    url = environ['PATH_INFO']  # 取到用户输入的url
    # 循环url列表,对不一样的url返回不一样的消息
    func = None
    for i in url_func:
        if i[0] == url:
            func = i[1]
            break

    if func:
        msg = func(url)
    else:
        msg = b'404 Not Found'

    return [msg]


if __name__ == '__main__':
    httpd = make_server('127.0.0.1', 8080, run_server)
    httpd.serve_forever()



    
二、wsgiref+jinja2版web开发
上面的代码实现了一个简单的动态,我彻底能够从数据库中查询数据,而后去替换我html中的对应内容,而后再发送给浏览器完成渲染。 这个过程就至关于HTML模板渲染数据。
本质上就是HTML内容中利用一些特殊的符号来替换要展现的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2

下载jinja2:在cmd命令行输入    pip install jinja2   安装第三方包
""" 利用wsgiref和jinja2模块动态渲染userinfo页面 """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <title>用户列表</title>
</head>
<body>
    <table border="1">
        <thead>
            <tr>
                <th>id值</th>
                <th>姓名</th>
                <th>密码</th>
            </tr>
        </thead>
        <tbody>
            {% for user in user_dict %}
                <tr>
                    <td>{{user.id}}</td>
                    <td>{{user.username}}</td>
                    <td>{{user.password}}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>

</body>
</html>
userinfo代码
 
 
import time
import pymysql
from wsgiref.simple_server import make_server
from jinja2 import Template


# 定义处理用户请求的函数
def index(url):
    with open('index.html', 'r', encoding='utf8') as f1:
        html_s = f1.read()
        # 根据用户不一样,取出不一样的数据
        # 用不一样的数据去替换页面上的特殊符号
        now = str(time.strftime("%H:%M:%S"))
        msg = html_s.replace('@t@', now)
        return msg.encode('utf8')


def home(url):
    s = '你访问的是%s页面' %url
    return s.encode('utf8')


def table(url):
    # 返回TableOp.html
    with open('TableOp.html', mode='rb') as f:
        return f.read()


def userinfo(url):
    # 1. 链接数据库,把全部的用户数据拿到
    conn = pymysql.connect(
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123abc',
        database='userinfo',
        charset='utf8'
    )
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    cursor.execute('select * from userinfo;')
    # 从数据库中拿到的数据user_dict
    user_dict = cursor.fetchall()
    with open('userinfo.html','r',encoding='utf8') as f:
        data = f.read()
    # 读取网页内容,生成jinja2模板对象
    template = Template(data)
    # 让jinja2用数据替换HTML中的特殊符号,拿到新的html内容并返回
    msg = template.render({'user_dict':user_dict})
    return bytes(msg, encoding="utf8")



# 定义一个用户访问的url和要执行的函数的对应关系
url_func = [
    ('/index/', index),
    ('/home/', home),
    ('/table/', table),
    ('/userinfo/', userinfo)
]

# wsgiref模块的格式要求
def run_server(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 设置HTTP响应的状态码和头信息
    url = environ['PATH_INFO']  # 取到用户输入的url
    # 循环url列表,对不一样的url返回不一样的消息
    func = None
    for i in url_func:
        if i[0] == url:
            func = i[1]
            break

    if func:
        msg = func(url)
    else:
        msg = b'404 Not Found'

    return [msg]


if __name__ == '__main__':
    httpd = make_server('127.0.0.1', 8080, run_server)
    httpd.serve_forever()







3、Django初识
1、安装
命令安装:pip install django==1.11.16

PyCharm安装:
2、建立一个django项目 命令行建立:django-admin startproject first_django(项目名) PyCharm建立:File -> new project -> Django --> 右边填项目名而且选择Python解释器

三、Django app
切换到项目目录下
命令行建立:python manage.py startapp appName
PyCharm建立:File --> new project --> Django --> More Settings --> Application name

4、运行Django项目 命令行 切换到项目的目录下 python manage.py runserver # 默认8000端口 python manage.py runserver 127.0.0.1:8080 # 能够本身设置ip和端口 python manage.py runserver 8090 # 也能够只设置端口,ip默认是本地ip127.0.0.1 PyCharm 点绿色的三角(注意左侧名称要与项目名相同)



5、目录介绍 first_django/ ├── manage.py # 管理文件 └── first_django # 项目目录 ├── __init__.py ├── settings.py # 配置文件 ├── urls.py # 路由 --> URL和函数的对应关系 └── wsgi.py # runserver命令就使用wsgiref模块作简单的web server
4、django基础
# urls.py from django.shortcuts import HttpResponse, render, redirect 1HttpResponse 内部传入一个字符串参数,返回给浏览器。 例如: def index(request): # request: 表示全部和请求相关的数据都封装在这个参数里面(固定传入这个参数) # HttpResponse: # 按照HTTP协议的格式返回 return HttpResponse("OK")
2render 返回一个html页面 除request参数外还接受一个待渲染的模板文件和一个保存具体数据的字典参数。 将数据填充进模板文件,最后把结果返回给浏览器。(相似于咱们上面用到的jinja2) 例如:无参数时,表示打开这个网页并返回给客户端 def meun(request): # render: # 1. 找到那个html文件 # 2. 读取文件内容 # 3. 按照HTTP协议的格式返回 return render(request, "meun.html")


有参数时,表明打开这个网页,并用这个参数(字典)去替换网页内的特殊字符,而后返回给客户端
def meun(request):
  # 找到那个html文件
  # 读取文件内容
  # 用参数去替换文件内的特殊字符
  # 按照http协议的格式返回
  rerurn render(request,'meun.html',{"name":"ming","age":18})


3redirect 重定向 接受一个URL参数,表示跳转到指定的URL。 例如: def home(request): # 业务逻辑代码 return redirect("/index/") 5、启动Django报错 Django 启动时报错 “UnicodeEncodeError ...” 报这个错误一般是由于计算机名为中文,改为英文的计算机名重启下电脑就能够了。 Django 启动报错“SyntaxError: Generator expression must be parenthesized” 报这个错很大多是由于使用了Python3.7.0,而目前(2018-06-12)Python3.7.0和Django还有点兼容性问题,换回Python3.6的环境便可。



6、urls.py
主要是写页面与函数的对应关系,好比
from django.conf.urls import url
from django.contrib import admin
from django.shortcuts import HttpResponse,render,redirect # 导入django的模块

def index(request):
# request: 表示全部和请求相关的数据都封装在这个参数里面(固定传入这个参数)
# HttpResponse:
# 按照HTTP协议的格式返回
return HttpResponse('OK')

def meun(request):
# render:
# 1. 找到那个html文件
# 2. 读取文件内容
# 3. 按照HTTP协议的格式返回
return render(request, "meun.html")


def dashboard(request):
return render(request,'Dashboard.html')


def home(request):
# 业务逻辑代码
return redirect("/index/")

urlpatterns = [ # 这里写页面和函数的对应关系,前面是你在浏览器输入的页面url,后面是你要定义的函数
url(r'^admin/', admin.site.urls),
url(r'^index/',index),
url(r'^meun/',meun),
url(r'^Dashboard/',dashboard),
url(r'^home/',home),
]





7、settings.py简单分析

# 项目的起始路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# 你自定义的app都要在这里注册
INSTALLED_APPS = [
  'django.contrib.admin',
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.messages',
  'django.contrib.staticfiles',
  'appName.apps.AppnameConfig',

]


# 全部和HTML文件相关的配置
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')] # 告诉Django框架去哪里找HTML文件,因此htnl页面应该放在templates目录里面
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]


# 静态文件的配置,/static/是下面STATICFILES_DIRS里面全部路径的别名(这里/static/就能够表明mystatic的路径)
# 所以你要引用什么静态文件。好比css、js文件等都要在路径前添加/static/

STATIC_URL = '/static/'

# 固定的配置项,告诉Django框架个人静态文件保存在哪些目录下
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'mystatic')
]







8、request
一、request.POST
经过POST方法提交给服务器的数据都会存在request.POST里面,request.POST是一个相似于字典的类型,取值方法跟字典同样,能够根据键去取值,也能够经过get方法,例如:
request.POST["name"] # 拿到键为name的数据
request.POST.get("name",None) # 拿到键为name的数据,若这个数据不存在,则返回None

二、request.method
request.method就是获取客户端提交数据的方式(POST、GET等)


9、总结Django基础 建立Django项目的步骤  1. PyCharm或者命令行建立一个Django项目      django-admin startproject 项目名  2. 建立一个初始的app,而且在settings.py中告诉Django      python manage.py startapp app的名字  3. 检查settings.py 专门存放HTML文件的Templates配置项  4. 配置静态文件相关      1. STATIC_URL = '/static/'      2. STATICFILES_DIRS = [          os.path.join(BASE_DIR, 'static'),         ]      3. 在项目的根目录建立一个用来存放静态文件的static目录  5. 注释csrf相关的那一行(大概在46行)      不然表单没办法提交数据,就会提示Forbidden 403的错误
相关文章
相关标签/搜索