Skip to content

before

想要学好Django,我们需要了解:

  • 网络底层相关概念。
  • 到底什么是web框架?
  • 常见web框架的对比。
  • 同步和异步。
  • Django快速上手,创建项目、虚拟环境、多app应用、纯净版的Django。
  • 逐步深入:路由、模板、视图、静态文件引入、orm、其它内置组件.....

web框架底层

网络通信

局域网:

1832669358900379648.png

广域网:

1832669359646965760.png

个人一般写程序,想要让别人访问:阿里云、腾讯云。

  • 去云平台租服务器(含公网IP)
  • 程序放在云服务器

socket实现

我们自己写时,通过socket模块可以实现网络上的两端进行通信。

同一局域网下

只要你们的电脑在同一个局域网下,都可以跑起来这个示例。

python
import socket

# 1.监听本机的IP和端口
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('192.168.10.3', 8001))  # 我自己的电脑IP,端口8001

# 2.让多少人等待
sock.listen(5)

while True:
    # 3.等待连接请求的申请,有人来连接(阻塞)
    print("waiting.....")
    conn, addr = sock.accept()
	print(f"有客户端连进来了,{conn}")

    # 4.连接成功后立即发送
    conn.sendall("欢迎使用xx系统".encode("utf-8"))

    # 5.断开连接
    conn.close()
# 6.停止服务端程序
sock.close()
python
import socket

# 1. 向指定IP发送连接请求
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('192.168.10.3', 8001))

# 2. 接收你发的消息
message = client.recv(1024)
print(message.decode("utf-8"))

# 3.断开连接
client.close()
python
import socket

# 1. 向指定IP发送连接请求
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('192.168.10.3', 8001))

# 2. 接收你发的消息
message = client.recv(1024)
print(message.decode("utf-8"))

# 3.断开连接
client.close()

广域网下

服务端在云服务器,各个客户端可以在全球的任意电脑上。

python
import socket

# 1.监听本机的IP和端口
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', 8001))  # 监听云服务器的8001端口,注意云服务器的安全组放开8001端口

# 2.让多少人等待
sock.listen(5)

while True:
    # 3.等待连接请求的申请,有人来连接(阻塞)
    print("waiting.....")
    conn, addr = sock.accept()
	print(f"有客户端连进来了,{conn}")
    # 4.连接成功后立即发送
    conn.sendall("欢迎使用xx系统".encode("utf-8"))

    # 5.断开连接
    conn.close()
# 6.停止服务端程序
sock.close()
python
import socket

# 1. 向云服务器发送连接请求
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('www.neeo.cc', 8001))

# 2. 接收云服务器返回的消息
message = client.recv(1024)
print(message.decode("utf-8"))

# 3.断开连接
client.close()
python
import socket

# 1. 向云服务器发送连接请求
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('www.neeo.cc', 8001))

# 2. 接收云服务器返回的消息
message = client.recv(1024)
print(message.decode("utf-8"))

# 3.断开连接
client.close()

常见软件架构

bs架构

浏览器充当客户端。

服务端就是咱们自己写的web应用程序。

cs架构

客户端就是常见桌面软件,电脑桌面软甲、各种app也算是客户端、小程序也算是客户端。

服务端也是对应软件厂家自己的云服务器上跑的各种web应用程序。

从socket到Django

通过之前讲解的web框架的底层实现,我们可以发现,Web框架本质上就是一个socket服务端,而用户的浏览器、移动设备等就是一个个个socket客户端。

接下来,我们通过手撸一个web框架,能让你再次加深web框架的本质和其工作原理。

最简单的web框架:

python
import socket

# 1. 创建socket对象
sk = socket.socket()

# 2. 绑定IP和端口
sk.bind(('127.0.0.1', 8888))

# 3. 监听
sk.listen(5)

# 4. 循环监听

while 1:
    conn, addr = sk.accept()  # 等待连接
    received_data = conn.recv(8192)  # 接收数据
    print(received_data)  # b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8888\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/73.0.3683.103 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\nCookie: csrftoken=FQW7H4SEVPRqdqIr4vSeiP3EdjX2C0JY7VwCdmXevoXcPjiAMBtNptkiZLHqXo4d\r\n\r\n'

    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 接收数据
    conn.send(b'<h1>Successful</h1>')
    conn.close()  # 关闭连接

1832669361001725952.png

可以说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码就是它们的祖宗。

用户在浏览器中输入网址,浏览器会向服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,那互联网还能玩么?

所以,必须有一个统一的规则,让大家发送消息、接收消息的时候都有个格式依据,不能随便写。

这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。

HTTP协议主要规定了客户端和服务器之间的通信格式,那HTTP协议是怎么规定消息格式的呢?

我们可以分析下上面代码中print(received_data)这行代码。

get请求

稍微整下后就这样了:

# 请求头首行
GET /xxx/xxx/?name=xxx&age=111 HTTP/1.1\r\n

# 请求头
Host: 192.168.0.6:9000\r\n
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7\r\n\r\n'

这里需要了解的是http协议,简单了来说:

http协议特点:无状态的短连接。
http的应用:浏览器向服务端发送请求,就是按照http协议来的。
	- 请求头+请求体 ;请求头和请求体之间用 \r\n\r\n ;请求头之间用 \r\n
	- 一次请求和一次响应后,断开连接。  -> 短连接。  ->无状态如何体现?
	- 后期记住 请求头+cookie

http get请求的格式:

1832669361769283584.png

http get响应的格式:

1832669361911889920.png

post请求

# 请求头首行
POST /xxx/xxx/ HTTP/1.1\r\n

# 请求头
Host: 192.168.0.6:9000\r\n
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7\r\n\r\n
# 请求体
username=zhangkai&password=123
浏览器本质上发送请求时,包含请求头和请求体。
- GET请求
	- 只有请求头 + 没有请求体
	- 请求头之间用 \r\n
	- 请求头和请求体之间用 \r\n\r\n
	
- POST请求
	- 只有请求头 + 有请求体
	- 请求头之间用 \r\n
	- 请求头和请求体之间用 \r\n\r\n

按照http协议我们给浏览器返回了结果,但是这只是最简单的,接下来,我们通过不同的示例,进一步完善我们的web框架。

根据不同的路径返回不同的内容

如何让我们的Web服务根据用户请求的URL不同而返回不同的内容呢?来看示例。

python
"""
根据URL中不同的路径返回不同的内容 
"""
import socket

# 创建socket对象
sk = socket.socket()

# 绑定IP和端口
sk.bind(('127.0.0.1', 8888))

# 监听
sk.listen()

while True:
    # 等待连接
    conn, addr = sk.accept()
    # 接收数据
    data = conn.recv(8192)
    data = data.decode('utf-8')
    url = data.split()[1]
    print(url)
    # 返回状态行
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    if url == '/oumei':
        conn.send(b'<h1>oumei</h1>')
    elif url == '/rihan':
        conn.send(b'<h1>rihan</h1>')
    else:
        conn.send(b'<h1>404</h1>')
    # 关闭连接
    conn.close()
python
"""
前一个示例我们返回的内容是最简单的几个字符,接下来我们对代码进一步封装。
根据URL中不同的路径返回不同的内容--函数版 
"""
import socket

# 1. 创建socket对象
sk = socket.socket()

# 2. 绑定IP和端口
sk.bind(('127.0.0.1', 8888))
# 3. 监听
sk.listen(5)

def func(url):
    return f"this is {url} page!".encode('utf8')


# 4. 循环监听
while True:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按\r\n分割
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    print(url)
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    # 根据不同的路径返回不同内容,response是具体的响应体
    if url == "/index/":
        response = func(url)
    elif url == "/home/":
        response = func(url)
    else:
        response = b"404 not found!"

    conn.send(response)
    conn.close()
python
"""
看起来前面的代码写了一个函数,那肯定可以写多个函数,不同的路径对应执行不同的函数拿到结果,但是我们要一个个判断路径,是不是很麻烦?我们有简单的办法来解决。
"""
import socket

# 1. 创建socket对象
sk = socket.socket()

# 2. 绑定IP和端口
sk.bind(('127.0.0.1', 8888))
# 3. 监听
sk.listen(5)


# 将返回不同的内容部分封装成不同的函数
def index(url):
    return f"{url} page".encode('utf8')


def home(url):
    return f"{url} page".encode('utf8')


# 定义一个url和实际要执行的函数的对应关系
list1 = [
    ("/index/", index),
    ("/home/", home),
]

while True:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按\r\n分割
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    # 根据不同的路径返回不同内容
    func = None  # 定义一个保存将要执行的函数名的变量
    for item in list1:
        if item[0] == url:
            func = item[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"

        # 返回具体的响应消息
    conn.send(response)
    conn.close()

返回HTML页面

上面的示例完美解决了不同URL返回不同内容的问题。 但是我不想仅仅返回几个字符串,我想给浏览器返回完整的HTML内容,这又该怎么办呢?

没问题,不管是什么内容,最后都是转换成字节数据发送出去的。 我们可以打开HTML文件,读取出它内部的二进制数据,然后再发送给浏览器。

python
"""
Python脚本文件和html文件在同一个目录下即可。
"""

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8888))
sk.listen()

# 将返回不同的内容部分封装成不同的函数
def index(url):
    # 读取index.html页面的内容
    with open("index.html", "rb") as f:
        return f.read()


def home(url):
    with open("home.html", "rb") as f:
        return f.read()
# 定义一个url和实际要执行的函数的对应关系
list1 = [
    ("/index/", index),
    ("/home/", home),
]

while True:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按\r\n分割
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    # 根据不同的路径返回不同内容
    print(111, url)
    func = None  # 定义一个保存将要执行的函数名的变量
    for item in list1:
        if item[0] == url:
            func = item[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"
    conn.send(response)
    conn.close()
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>index 页面</h1>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>home 页面</h1>
</body>
</html>

让网页动态起来

目前网页能够显示出来了,但是都是静态的。页面的内容都不会变化的,但真正的网站页面内容都是动态的,这怎么搞呢?

下面的示例使用时间戳来模拟动态的数据。

python
import time
import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8888))
sk.listen()


# 将返回不同的内容部分封装成不同的函数
def index(url):
    # 读取index.html页面的内容
    with open("index.html", "rb") as f:
        return f.read()


def home(url):
    with open("home.html", "rb") as f:
        return f.read()


def timmer(url):
    with open("timmer.html", "r", encoding='utf8') as f:
        return bytes(f.read().replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S")), encoding="utf8")


# 定义一个url和实际要执行的函数的对应关系
list1 = [
    ("/index/", index),
    ("/home/", home),
    ("/timmer/", timmer),
]

while True:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按\r\n分割
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    # 根据不同的路径返回不同内容
    print(111, url)
    func = None  # 定义一个保存将要执行的函数名的变量
    for item in list1:
        if item[0] == url:
            func = item[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"
    conn.send(response)
    conn.close()
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>当前时间是: @@time@@ </h1>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>index 页面</h1>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>home 页面</h1>
</body>
</html>

服务器程序和应用程序

通过上面几个实例,我们发现socket代码部分基本是不变的,变得是根据需求而改动的代码部分。

所以,对于不变的socket代码部分,Python通常提供了专门的模块来处理。这就要介绍两个概念了。

对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。

服务器程序负责对socket服务端进行封装,并在请求到来时,对请求的各种数据进行整理。

应用程序则负责具体的逻辑处理。

为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。

这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。

这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用,一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

而WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。

常用的WSGI服务器有uWSGI、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器,除此之外,还有其他的比如flask框架使用的werkzeug来提供了wsgiref相同的功能。

来个最简单的示例:

python
"""
wsgiref是内置模块,无需下载
"""
from wsgiref.simple_server import make_server

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ]


if __name__ == '__main__':
    httpd = make_server('127.0.0.1', 8888, application)
    httpd.serve_forever()
python
"""
使用前先下载
# pip install werkzeug
pip install werkzeug==3.0.1
"""
from werkzeug.wrappers import Response
from werkzeug.serving import run_simple

def application(environ, start_response):
    response = Response('<h1>Hello, web!</h1>', mimetype='text/html')
    return response(environ, start_response)


if __name__ == '__main__':
    run_simple('127.0.0.1', 8888, application)

接下来的示例,我们使用wsgiref模块来继续完善之前的代码示例。

python
import time
from wsgiref.simple_server import make_server


# 将返回不同的内容部分封装成不同的函数
def index(url):
    # 读取index.html页面的内容
    with open("index.html", "rb") as f:
        return f.read()


def home(url):
    with open("home.html", "r", encoding='utf8') as f:
        return f.read()


def timmer(url):
    with open("timmer.html", "r", encoding='utf8') as f:
        return f.read().replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S"))


# 定义一个url和实际要执行的函数的对应关系
list1 = [
    ("/index/", index),
    ("/home/", home),
    ("/timmer/", timmer),
]


def application(environ, start_response):
    path_info = environ['PATH_INFO']
    # print(path_info)  # /timmer/
    for s, f in list1:
        if path_info == s:
            response = f(path_info)
            break
    else:
        response = "404 not found!"
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [bytes(response, encoding='utf-8'), ]


if __name__ == '__main__':
    httpd = make_server('127.0.0.1', 8888, application)
    httpd.serve_forever()
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>当前时间是: @@time@@ </h1>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>index 页面</h1>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>home 页面</h1>
</body>
</html>

通过wsgiref这些框架帮我们封装了socket相关的代码,让我们的精力能集中于具体的业务上。

但你以为这就结束了吗?远远没有......因为我们在开发中要考虑的还有很多很多:

  • 用户认证:来了个请求,这个请求要访问的资源是否需要登录之后才能访问。
  • 权限:这个请求是否有权限访问某个资源。
  • 限流:对于某些资源,不能任其无限的访问,比如实现投票业务,实现每日只能投一票。
  • 请求头/请求体的封装:为了业务中方便的从请求中提取想要的数据。
  • 数据库操作。
  • ........

以上种种需求,单靠wsgiref这些框架是不行的,我们倒是可以自己来处理,但是每一个项目都要搞一遍,这就是重复造轮子了,所以,Django、flask等web框架应运而生。

例如,Django框架主要集成了wsgiref,然后实现了具体业务之外的其它操作,so,你只管踩油门,剩下的交给Django.........

1832669362075467776.png

Python中主流web框架

根据框架集成功能的多少划分

  • Django,内部提供了很多组件,认证、权限、后台管理、session.....
  • flask、tornado、sanic、fastapi... 本身自己功能很少+第三方组件

异步框架/同步框架

  • 异步非阻塞:tornado、sanic、fastapi、django(从Django3开始支持,到目前Django5了还不是完全体)
  • 同步:django、flask、bottle、webpy..

个人推荐:

传统项目,非常多且复杂的业务需求场景,用Django,对于异步支持这块,可以持续关注。

对于高性能和高并发的api业务,可以选择fastapi,但fastapi对于异步这块要求较高,需要你对异步的底层实现原理、异步数据库连接操作等都要有了解,上手难度小,但深入研究就需要一些知识储备了,并且目前来说各种三方库也不多,且支持的也不够好,遇到问题你可能在网上找不到解决方案,这时就需要你自己牛逼起来了,但也是可以行上手学习并且持续关注后续发展的。

About Django

Django是一个高级Python Web框架,鼓励快速开发和干净,实用的设计。由经验丰富的开发人员构建,它可以解决Web开发的大部分麻烦,因此我们可以专注于编写应用程序而无需重新发明轮子。它是免费和开源的。

虚拟环境

一般的,执行python代码或者运行python项目,都会使用系统默认的python环境,第三方的包也下载到这个python环境下,那么问题来了,如果有多个python项目,这些项目有的需要python2.7的环境,有的需要python3.6的环境,并且依赖的包也不相同,这个时候时候系统的python环境就不太合适了,因为它就是一个公共的python环境,而各个项目都需要一套独立、干净的python环境,该怎么来搞?这个时候,就需要有专门的工具来方便的在原系统的python环境基础上,创建一个个新的python环境,专门服务不同的项目。

virtualenv用来创建一个"隔离"的Python运行环境,这个python环境也被称为虚拟环境,一般的,一个虚拟环境只服务一个项目,我们将这个项目用的包等全部下载到这个虚拟环境中,并且能随时开启和关闭虚拟环境。 virtualenv本身是一个模块,所以使用前保证系统(所有系统平台)已经安装了python环境。 注意,virtualenv创建虚拟环境的目录,不能包含中文,否则激活不生效!!!!

安装virtualenv

打开你电脑的终端执行如下命令。

注意观察,此时此刻,终端中的pip还是我系统默认安装的Python解释器中的pip,你此时执行下载模块的话,所有的模块都将安装到这个解释器中site-packages目录中,而我们也是将将virtualenv模块安装到这个全局的Python解释器环境中。

bash
C:\Users\12061\Downloads>pip -V
pip 23.3.2 from C:\software\miniconda3\lib\site-packages\pip (python 3.10)

C:\Users\12061\Downloads>pip install -i https://pypi.tuna.tsinghua.edu.cn/simple virtualenv
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting virtualenv
  Using cached https://pypi.tuna.tsinghua.edu.cn/packages/83/22/54b1180756d2d6194bcafb7425d437c3034c4bff92129c3e1e633079e2c4/virtualenv-20.25.0-py3-none-any.whl (3.8 MB)
Requirement already satisfied: distlib<1,>=0.3.7 in c:\software\miniconda3\lib\site-packages (from virtualenv) (0.3.8)
Requirement already satisfied: filelock<4,>=3.12.2 in c:\software\miniconda3\lib\site-packages (from virtualenv) (3.13.1)
Requirement already satisfied: platformdirs<5,>=3.9.1 in c:\software\miniconda3\lib\site-packages (from virtualenv) (4.1.0)
Installing collected packages: virtualenv
Successfully installed virtualenv-20.25.0

然后,通过安装好的virtualenv模块,创建虚拟环境,并激活该虚拟环境。

bash
# 执行下面的命令,会在当前路径下创建一个venv的文件夹,这个venv是虚拟环境的名字,这个名字可以随意指定
C:\Users\12061\Downloads>virtualenv venv
created virtual environment CPython3.10.13.final.0-64 in 895ms
  creator CPython3Windows(dest=C:\Users\12061\Downloads\venv, clear=False, no_vcs_ignore=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=C:\Users\12061\AppData\Local\pypa\virtualenv)
    added seed packages: pip==23.3.2, setuptools==68.2.2, wheel==0.42.0
  activators BashActivator,BatchActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator


# 创建好虚拟环境之后,要通过执行虚拟环境中的activate脚本来激活虚拟环境
C:\Users\12061\Downloads>.\venv\Scripts\activate

# 当你激活虚拟环境之后,路径前面会有个括号,括号内是虚拟环境名字,这就表示venv这个虚拟环境激活了,
# 后续所有的python命令和pip命令都来自于这个虚拟环境了,你此时下载模块,也就下载到了这个虚拟环境中了
(venv) C:\Users\12061\Downloads>pip -V
pip 23.3.2 from C:\Users\12061\Downloads\venv\lib\site-packages\pip (python 3.10)

# 可以看到,此时虚拟环境中,还没有其它额外的三方模块,非常干净
(venv) C:\Users\12061\Downloads>pip list
Package    Version
---------- -------
pip        23.3.2
setuptools 68.2.2
wheel      0.42.0

往虚拟环境中安装模块,注意,一定是要确认虚拟环境激活之后,才能进行下载操作。

bash
# 路径前面有个虚拟环境的括号,表示该虚拟环境已激活
(venv) C:\Users\12061\Downloads>pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django==4.2.8

# 你可以执行pip list命令查看当前虚拟环境中安装了哪些模块,这其中就包含了我们刚才下载的django
(venv) C:\Users\12061\Downloads>pip list
Package           Version
----------------- -------
asgiref           3.7.2
Django            4.2.8
pip               23.3.2
setuptools        68.2.2
sqlparse          0.4.4
typing_extensions 4.9.0
tzdata            2023.3
wheel             0.42.0

接下来再说下退出虚拟环境:

bash
# 当你激活虚拟环境之后,你可以直接在终端中输入deactivate命令,来退出这个虚拟环境
(venv) C:\Users\12061\Downloads>deactivate

# 当退出虚拟环境之后,此时你在使用pip就是全局Python解释器环境中的pip了
C:\Users\12061\Downloads>pip -V
pip 23.3.2 from C:\software\miniconda3\lib\site-packages\pip (python 3.10)

安装Django

版本选择

版本选择,参考Django官网的版本支持来说,2023年到2024年这个节点来说:

下图来自:https://www.djangoproject.com/download/#supported-versions

1832669362310348800.png

所以,安装就是下面的命令了,注意,最好的方案就是先创建一个虚拟环境,然后在虚拟环境中下载Django了。

bash
# 清华源中收录的4.2的版本是4.2.8的,所以我们直接下载4.2的最后一个版本就好了,当然只要是4.2版本就行了,更小的版本无所谓的
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django==4.2.8

django-admin --version
4.2.8

创建Django项目

首先,我希望创建的Django项目在C:\Users\12061\Downloads目录下,项目名字叫做mysite。并且使用虚拟环境,所以我应该这样。

所有不懂如何创建Django项目的,都可以参考这个示例来做。

PS:pycharm也能直接创建Django项目,但有问题:创建的项目中,虚拟环境这个pycharm能创建,但是Django的版本没法自己选择,它自动下载的是Django的最新版的。

所以,想要灵活的创建Django项目,还是首推自己在终端自己用命令操作。

1. 打开cmd终端,cd到C:\Users\12061\Downloads目录(你可以是其它非中文不包含空格的目录)下,创建项目目录(也是Django项目名)

bash
# 你想把项目创建到哪个目录,就先cd到哪个目录,我这里是cd c:\Users\12061\Downloads目录
C:\Users\12061>cd c:\Users\12061\Downloads

# mysite是项目名字,也是项目根目录,你也可以指定其他的名字,当然,此时此刻所谓的mysite项目还只是个空文件夹而已
c:\Users\12061\Downloads>mkdir mysite

2. ,创建虚拟环境并激活虚拟环境

bash
# cd到mysite项目目录下
c:\Users\12061\Downloads>cd mysite

# 我希望将虚拟环境创建到项目目录下,并且虚拟环境的名字叫做venv
c:\Users\12061\Downloads\mysite>virtualenv venv
created virtual environment CPython3.10.13.final.0-64 in 759ms
  creator CPython3Windows(dest=C:\Users\12061\Downloads\mysite\venv, clear=False, no_vcs_ignore=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=C:\Users\12061\AppData\Local\pypa\virtualenv)
    added seed packages: pip==23.3.2, setuptools==69.0.2, wheel==0.42.0
  activators BashActivator,BatchActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator

# 激活虚拟环境
c:\Users\12061\Downloads\mysite>.\venv\Scripts\activate

# 通过路径左侧的括号和pip -V命令返回的路径,确认虚拟环境激活成功,返回的路径是当前项目中的虚拟环境目录
# 注意,一定要进行测试,因为有几率当你激活虚拟环境之后,也显示了左边的小括号,但是pip -V仍然会返回你的系统中全局Python解释器环境中的pip所在路径,这就表示激活失败了,所以这一步无比重要
# 如果激活失败,就deactivate命令退出虚拟环境重新尝试在激活,如果仍然失败,那就在打开项目所在文件夹,把虚拟环境删掉,重新创建虚拟环境,然后虚拟环境的名字换个别字,再进行激活
(venv) c:\Users\12061\Downloads\mysite>pip -V
pip 23.3.2 from C:\Users\12061\Downloads\mysite\venv\lib\site-packages\pip (python 3.10)

3. 在虚拟环境中安装Django

bash
# 然后在虚拟环境开启的状态下,安装Django
(venv) c:\Users\12061\Downloads\mysite>pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django==4.2.8
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
......
Successfully installed asgiref-3.7.2 django-4.2.8 sqlparse-0.4.4 tzdata-2023.3

# 返回了Django的版本号,说明Django成功的安装到了虚拟环境中
(venv) c:\Users\12061\Downloads\mysite>django-admin --version
4.2.8

4. 通过命令创建Django项目和应用

bash
# 由于我们在第一步已经创建好了mysite项目名,所以下面创建项目的命令执行成功会生成文件夹和文件,最后的那个点不能少,这个点表示,把这些生成文件夹和文件存放到mysite项目根目录下
(venv) c:\Users\12061\Downloads\mysite>django-admin startproject mysite .

# 紧接着创建app,app的名字叫做api
# 创建app的命令有两个,但我推荐方式1,因为我遇到过通过方式2的形式创建app失败,但用方式1没问题
# 方式1 django-admin startapp api
# 方式2 python manage.py startapp api
(venv) c:\Users\12061\Downloads\mysite>django-admin startapp api

# 此时此刻,项目根目录下应该有这些文件夹和文件才对
(venv) c:\Users\12061\Downloads\mysite>
├── manage.py  						# 项目启动脚本文件,也能执行一些其它命令,比如创建app,做数据库的迁移操作
├── /api  							# app应用
|   ├── __init__.py
|   ├── migrations  				# 存放数据库迁移命令产生的迁移文件,此时除了__init__.py文件,没有其他文件了
|   |	└── __init__.py
|   ├── admin.py    				# 配置后台admin站点相关
|   ├── apps.py     				# app的配置
|   ├── models.py   				# 存放当前app中的模型类
|   ├── tests.py    				# 单元测试文件
|   └── views.py					# 视图
└── /mysite  						# 项目同名文件夹,务必和项目同名,用于存放项目配置相关
|   ├── __init__.py
|   ├── settings.py  				# 项目主配置文件
|   ├── urls.py  					# 路由 --> URL和函数的对应关系
|   └── wsgi.py  					# runserver命令就使用wsgiref模块做简单的web server
└──venv  							# 虚拟环境
    ├── Lib/site-packages  			# 虚拟环境中的三方包都在这个目录中了
    ├── Scripts/activate.bat  		# 激活虚拟环境的脚本文件,mac和Linux应该是点sh结尾
    ├── Scripts/deactivate.bat  	# 关闭虚拟环境的脚本文件
    ├── Scripts/django-admin.exe  	# 创建Django项目和app的可执行程序
    ├── Scripts/python.exe      	# 虚拟环境中的Python解释器可执行程序
    └── Scripts/pip.exe  			# 虚拟环境中的pip可执行程序

5. 启动项目并浏览器访问

bash
# 通过下面的runserver命令运行Django项目,注意,Django项目是由很多的组件组成的一个项目,所以,你必须通过runserver去启动它
# 而不能单独运行某个文件
# 默认监听本地的8000端口
python manage.py runserver 
# 指定监听本地的80端口
python manage.py runserver 80
# 指定ip和端口
python manage.py runserver 192.168.1.2:8888

# 按照默认ip和端口启动项目
(venv) c:\Users\12061\Downloads\mysite>python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
December 25, 2023 - 15:56:51
Django version 4.2.8, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

你浏览器就可以访问这个http://127.0.0.1:8000/地址了。

看到如下图的小火箭就表示项目启动成功了。

1832669362570395648.png

关于纯净版的Django

小白不建议用纯净版的Django

当你创建一个Django项目之后,它默认提供了很多内置的功能:后台管理、用户认证和权限、处理复杂的表结构关系、session和message、静态资源处理(图片、css、js等)......

python
INSTALLED_APPS = [
    'django.contrib.admin',              # django内置后台管理,简单数据库的增删改查
    'django.contrib.auth',               # 用户登录和认证权限
    'django.contrib.contenttypes',       # 复杂表结构关系
    'django.contrib.sessions',           # 如果项目中有登录成功让用户可以访问。
    'django.contrib.messages',           # 消息展示,依赖Session
    'django.contrib.staticfiles',        # 静态资源处理,图片、css、js等
]

无论后续项目中是否真正的用到了这些内置的功能,Django默认都是启用的,这也是为啥人称Django大而全,比较重的原因。

那问题来了,如果你是大佬,除了使用Django基本的功能之外,其他功能都要自己开发,那么内置的功能用不上,我们有办法关闭这些内置的功能吗?答案是有办法,那就是使用纯净版的Django,也就是禁用那些你项目中用不到的内置功能。

纯净版配置

打开你的项目的settings.py文件,按照下面的示例注释你的代码即可:

python
INSTALLED_APPS = [
    # 'django.contrib.admin',
    # 'django.contrib.auth',
    # 'django.contrib.contenttypes',
    # 'django.contrib.sessions',
    # 'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    # 'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    # 'django.contrib.auth.middleware.AuthenticationMiddleware',
    # 'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]


TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / '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',
            ],
        },
    },
]

再找到urls.py文件,注释掉:

python
from django.contrib import admin
from django.urls import path

urlpatterns = [
    # path('admin/', admin.site.urls),
]

Ok,现在你的Django就纯净了。

为啥不建议小白使用纯净版的Django呢

随着不断的在Django项目中添加新的功能,你难免会用到了一些三方库,如Django restframework框架,这些三方库的内部的一些组件是基于Django内置的功能组件二次开发的,当你使用这些功能模块时,它内部源码执行时会调用Django内置的这些功能组件的代码,但你使用的是纯净版的Django,没有开启这些内置的功能模块,那你的代码运行就会报错......

无所屌谓,程序员天生就是写bug的,遇到报错那就是家常便饭,根据具体报错具体分析,然后排掉bug就完了,easy!

但人贵有自知之明,"你这个大佬"是谁?"你这个大佬"是Django的初学者,一个可能连虚拟化境都弄不明白;一个只会无比肯定说"我的代码跟xx视频中的一模一样"的,一个暂时连报错都看不明白的初学者,能指望"你这个大佬"去排掉这些可能因为纯净版Django而引起的报错吗?很难啊!!!!

最终,根据我多年的Django教学经验来说,Django纯净版虽好,我怕"你这个大佬"把握不住啊!所以踏实儿了解下啥是纯净版的Django就完了,我们刚开始学Django,把所有的默认功能都启用着,三方库用上就让它用上,用不上的就在那搁着就完了,程序员从不给自己添麻烦........有那个排错时间,吃吃瓜喝喝水,刷刷抖音看大腿不香吗?

关于app

在项目中,我们通常会根据业务功能模块的不同,选择创建多个app来进行开发,一个app实现一个功能,最终达到业务逻辑、代码解耦的目的,使其整个项目架构更清晰。

首先创建app的命令有两个:

bash
# 创建app的命令有两个,但我推荐方式1,因为我遇到过通过方式2的形式创建app失败,但用方式1没问题
# 方式1 django-admin startapp api
# 方式2 python manage.py startapp api

什么情况下使用一个app?什么情况下使用多个app呢

创建项目之前,你肯定对要实现哪些功能心里有数,一般情况下,功能不多,业务也很简单,一个app就够了。

如果项目复杂了,就建议多个app。

比如一个在线教育平台的开发:

bash
# 开发前就明确了的业务
	公司官网开发,主要使用者:学员。
    内部运营使用的功能模块
    导师下载作业、批改作业、评分。
    
# 那么就适用于多个app
app01:  -> 公司官网开发,主要使用者:学员。
app02:  -> 内部运营使用的功能模块
app03:  -> 导师下载作业、批改作业、评分。

单app的创建和配置

单个app的创建,只需要两步就好了。

1. 在项目根目录下创建app

例如mysite项目,只需要一个app,那你直接在项目根目录下,创建app即可。

bash
# 执行下面的命令,app的名字,尽量贴近的业务
django-admin startapp api

# 此时此刻,项目根目录下应该有这些文件夹和文件才对
(venv) c:\Users\12061\Downloads\mysite>
├── manage.py  						# 项目启动脚本文件,也能执行一些其它命令,比如创建app,做数据库的迁移操作
├── /api  							# app应用
└── /mysite  						# 项目同名文件夹,务必和项目同名,用于存放项目配置相关
└──venv  							# 虚拟环境

2. 重要的一步,不能少

在项目的settings.py文件中注册app,只有在INSTALLED_APPS列表中,注册了app,Django程序运行起来后,才识别到你的应用,好多初学者都会犯这个,创建完app,然后忘了注册app的事儿。

python
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 'api.apps.ApiConfig',   # 注册app,完整写法
    'api',   # 注册app,单个app的话,可以简写,Django内部会自动找到app应用目录内的apps文件中的ApiConfig类
]

那为啥是是api.apps.ApiConfig这样写呢?你可以看下app中的apps文件内的代码,是不是正好对应上ApiConfig这个类。

python
from django.apps import AppConfig


class ApiConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'api'

多app的创建和配置

对于多app的创建,命令不变,但如何管理这些app我们要进行充分考虑,通常也有两种管理方式。

所有的app都直接创建到项目根目录下

把所有的app都直接创建到项目根目录下,这是比较简单的,也是代码改动最小的方案了,就是会让你的项目看着比较臃肿。

1. 直接在项目根目录下按需创建多个app

例如mysite项目,直接在项目根目录下,创建多个app即可。

bash
# 执行下面的命令,app的名字,尽量贴近的业务
django-admin startapp api
django-admin startapp app01
django-admin startapp app02

# 然后你的项目结构
(venv) c:\Users\12061\Downloads\mysite>
├── manage.py  						# 项目启动脚本文件,也能执行一些其它命令,比如创建app,做数据库的迁移操作
├── /api  							# app应用
├── /app01  						# app应用
├── /app02  						# app应用
└── /mysite  						# 项目同名文件夹,务必和项目同名,用于存放项目配置相关
└──venv  							# 虚拟环境

2. 重要的一步,不能少

在项目的settings.py文件中注册app,只有在INSTALLED_APPS列表中,注册了app,Django程序运行起来后,才识别到这些应用,好多初学者都会犯这个,创建完app,然后忘了注册app的事儿。

python
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'api.apps.ApiConfig',       # 注册app,完整写法
    'app01.apps.App01Config',   # 注册app,完整写法
    'app02.apps.App02Config',   # 注册app,完整写法
]

多个app都创建到一个python包内

如果有预料,将来app会有很多个,那么采用这种方式比较好。

将多个app都创建到一个python的包内,方便后续开发的模块导入操作。

普通文件夹和python的包有啥区别

所谓python的包,其实就是在普通文件夹中,多了个__init__.py文件,这个文件用于包导入的初始化操作,如果没有啥要初始化的,就保持这个文件为空就好了,注意,这个__init__.py文件可以为空,但不能没有!

有较劲的可能会说,我日常使用起来,没有啥区别呀,那你是对的!因为你可能没有遇到:

1832669363145015296.png

但根据我的经验来看,用包比用普通文件夹更不容易出错。

言归正传,来看如何操作吧。

1. 在项目根目录下创建apps包

注意,命令看不懂的话,可以手动的在本地自己创建:

bash
# 先在根目录下创建apps文件夹
(venv) C:\Users\12061\Downloads\mysite>mkdir apps

# 再在apps文件内创建一个空的__init__.py文件将其声明为一个python的包
(venv) C:\Users\12061\Downloads\mysite>echo "" > apps\__init__.py

2. cd到apps包路径内,创建多个app

bash
# 命令
cd apps
django-admin startapp app03
django-admin startapp app04

# 示例
(venv) C:\Users\12061\Downloads\mysite>cd apps 
(venv) C:\Users\12061\Downloads\mysite\apps>django-admin startapp app03
(venv) C:\Users\12061\Downloads\mysite\apps>django-admin startapp app04

3. settins.py注册app,必不可少

在项目的settings.py文件中注册app:

python
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'api.apps.ApiConfig',             # 注册app,完整写法
    'app01.apps.App01Config',   	  # 注册app,完整写法
    'app02.apps.App02Config',   	  # 注册app,完整写法
    'apps.app03.apps.App03Config',    # 注册app,完整写法
    'apps.app04.apps.App04Config',    # 注册app,完整写法
]

但是你此时如果运行项目的话,会报错,django.core.exceptions.ImproperlyConfigured: Cannot import 'app03'. Check that 'apps.app03.apps.App03Config.name' is correct.

1832669364818542592.png

其原因就是各自app内的apps文件中的Config类中的name需要调整一下,具体看下一步操作。

4. 重要的一步,对各自app内的apps文件进行代码改造,解决django.core.exceptions.ImproperlyConfigured: Cannot import 'app03'. Check that 'apps.app03.apps.App03Config.name' is correct.问题

python
from django.apps import AppConfig


class App03Config(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    # name = 'app03'  #
    name = 'apps.app03'  #
python
from django.apps import AppConfig


class App04Config(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    # name = 'app04'  #
    name = 'apps.app04'  #

完事你在尝试运行下项目,应该就不报错了。

当Django项目开发完毕,我们需要做的事儿

1. 导出项目使用的数据库的数据

如果用的是MySQL数据库,在项目根目录打开终端:

bash
mysqldump -uroot -p --default-character-set=utf8 -B 要备份的数据库 >要保存的数据文件名.sql

# 我的项目用到的数据库是day13,所以,我只需要将这一个数据库备份出来即可。
mysqldump -uroot -p --default-character-set=utf8 -B day13 >data.sql

这一步是为了将来万一谁拿到你的项目,想要立马跑起来,他能根据这个sql文件,快速的把你项目中用到的表和数据恢复到他电脑上的MySQL中,有了这些数据,有些功能就能正常的访问了。

2. 生成requirements.txt文件,重要的一步

Django项目中必须包含一个requirements.txt 文件,用于记录该项目的所有依赖包及其精确的版本号,以便在新的环境中快速把项目跑起来。

如果你发现哪个项目中,没有这个文件,那就显得很不专业了!你可以在心底大声喊一声——"野生程序员!!!!"

生成这个文件的命令,在你的项目根目录打开终端,注意激活了当前项目用到的虚拟环境:

bash
(venv) C:\Users\12061\Downloads\mysite>pip freeze >requirements.txt

requirements.txt文件内容:

text
asgiref==3.7.2
Django==4.2.8
sqlparse==0.4.4
typing_extensions==4.9.0
tzdata==2023.3

借用别的项目的截图额外的说下可能遇到的情况,如下图,生成的requirements.txt中,看看是不是有不合法的格式,比如有些时候用的是conda下载的包,有可能就不太合法,如果我们不注意的话,线上pip下载时就报错了,我们这里要提前避开这个问题。

1832669365607071744.png

bash
# 合法的
async-timeout==4.0.2

# 不合法的,我们可以终端执行下pip list,然后定位到这个不合法的包,看它的版本,然后手动调整下就好了
# certifi @ file:///C:/b/abs_85o_6fm0se/croot/certifi_1671487778835/work/certifi  # 把原来的不合法的注释掉就完了
# 根据pip list 结果,手动修改之后如下所示
certifi==2022.12.7

1832669366366240768.png

当拿到一个现成的Django项目时,我们如何把项目跑起来

��把项目跑起来