1. 程式人生 > >Django基礎一

Django基礎一

ant settings 發送 user_list 開放性 目的 connect root rtm

Web框架本質

框架,即framework,特指為解決一個開放性問題而設計的具有一定約束性的支撐結構,使用框架可以幫你快速開發特定的系統,簡單地說,就是你用別人搭建好的舞臺來做表演。

對於所有的Web應用,本質上其實就是一個socket服務端,用戶的瀏覽器其實就是一個socket客戶端

技術分享圖片 技術分享圖片
import socket

def handle_request(client):

    buf = client.recv(1024)
    client.send("HTTP/1.1 200 OK\r\n\r\n".encode("utf8"))
    client.send("<h1 style=‘color:red‘>Hello, yuan</h1>".encode("utf8"))

def main():

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((localhost‘,8001))
    sock.listen(5)

    while True:
        connection, address = sock.accept()
        handle_request(connection)
        connection.close()

if __name__ == __main__:

    main()
技術分享圖片

可以說Web服務本質上都是在這十幾行代碼基礎上擴展出來的。這段代碼就是它們的祖宗。

用戶的瀏覽器一輸入網址,會給服務端發送數據,那瀏覽器會發送什麽數據?怎麽發?這個誰來定? 你這個網站是這個規定,他那個網站按照他那個規定,這互聯網還能玩麽?

所以,必須有一個統一的規則,讓大家發送消息、接收消息的時候有個格式依據,不能隨便寫。

這個規則就是HTTP協議,以後瀏覽器發送請求信息也好,服務器回復響應信息也罷,都要按照這個規則來。

HTTP協議主要規定了客戶端和服務器之間的通信格式,那HTTP協議是怎麽規定消息格式的呢?

讓我們首先看下我們在服務端接收到的消息是什麽。

然後再看下我們瀏覽器收到的響應信息是什麽。

響應頭在瀏覽器的network窗口可以看到,我們看到的HTML頁面內容就是響應體。本質上還是字符串,因為瀏覽器認識HTML,所以才會渲染出頁面。

每個HTTP請求和響應都遵循相同的格式,一個HTTP包含Header和Body兩部分,其中Body是可選的。 HTTP響應的Header中有一個 Content-Type表明響應的內容格式。如 text/html表示HTML網頁

HTTP GET請求的格式:

GET /path HTTP/1.1
header1:v1\r\n
header2:v2\r\n

使用 \r\n分隔多個header

HTTP POST請求格式:

POST /path HTTP/1.1
header1:v1\r\n
header2:v2\r\n
\r\n\r\n
請求體...

當遇到連續兩個 \r\n\r\n時,表示Header部分結束了,後面的數據是Body。

HTTP響應的格式:

200 OK
Header1:v1\r\n
Header2:v2\r\n
\r\n\r\n
響應體...

上述通過socket來實現了其本質。

對於真實開發中的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開發環境用的就是這個模塊來做服務器

最簡單的Web應用就是先把HTML用文件保存好,用一個現成的HTTP服務器軟件,接收用戶請求,從文件中讀取HTML,返回。

如果要動態生成HTML,就需要把上述步驟自己來實現。不過,接受HTTP請求、解析HTTP請求、發送HTTP響應都是苦力活,如果我們自己來寫這些底層代碼,還沒開始寫動態HTML呢,就得花個把月去讀HTTP規範。

正確的做法是底層代碼由專門的服務器軟件實現,我們用Python專註於生成HTML文檔。因為我們不希望接觸到TCP連接、HTTP原始請求和響應格式,所以,需要一個統一的接口,讓我們專心用Python編寫Web業務。

這個接口就是WSGI:Web Server Gateway Interface

技術分享圖片
from wsgiref.simple_server import make_server


def run_server(environ, start_response):
    start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html;charset=utf8‘)])  # 設置HTTP響應的狀態碼和頭信息
    return [bytes("<h1>Hello world!</h1>", encoding="utf8"),]


if __name__ == ‘__main__‘:
    httpd = make_server(‘‘, 8000, run_server)
    print("Serving HTTP on port 8000...")
    httpd.serve_forever()
技術分享圖片

這樣就結束了嗎? 如何讓我們的Web服務根據用戶請求的URL不同而返回不同的內容呢?

小事一樁,我們可以從請求相關數據裏面拿到請求的URL,然後做一個判斷

技術分享圖片
from wsgiref.simple_server import make_server


def run_server(environ, start_response):
    start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html;charset=utf8‘), ])  # 設置HTTP響應的狀態碼和頭信息
    url = environ[‘PATH_INFO‘]  # 取到用戶輸入的url
    if url == "/index/":
        return [bytes("<h1>這是index頁面</h1>", encoding="utf8"), ]
    elif url == "/home/":
        return [bytes("<h1>這是home頁面</h1>", encoding="utf8"), ]
    else:
        return [bytes("404沒有該頁面", encoding="utf8"), ]


if __name__ == ‘__main__‘:
    httpd = make_server(‘‘, 8000, run_server)
    print("Serving HTTP on port 8000...")
    httpd.serve_forever()
技術分享圖片

解決了不同URL返回不同內容的需求。 但是問題又來了,如果有很多很多頁面怎麽辦?難道要挨個判斷? 當然不用,我們有更聰明的辦法

技術分享圖片
from wsgiref.simple_server import make_server


def index():
    return [bytes("<h1>這是index頁面</h1>", encoding="utf8"), ]


def home():
    return [bytes("<h1>這是home頁面</h1>", encoding="utf8"), ]


# 定義一個url和函數的對應關系
URL_LIST = [
    ("/index/", index),
    ("/home/", home),
]


def run_server(environ, start_response):
    start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html;charset=utf8‘), ])  # 設置HTTP響應的狀態碼和頭信息
    url = environ[‘PATH_INFO‘]  # 取到用戶輸入的url
    func = None  # 將要執行的函數
    for i in URL_LIST:
        if i[0] == url:
            func = i[1]  # 去之前定義好的url列表裏找url應該執行的函數
            break
    if func:  # 如果能找到要執行的函數
        return func()  # 返回函數的執行結果
    else:
        return [bytes("404沒有該頁面", encoding="utf8"), ]


if __name__ == ‘__main__‘:
    httpd = make_server(‘‘, 8000, run_server)
    print("Serving HTTP on port 8000...")
    httpd.serve_forever()
技術分享圖片

完美解決了不同URL返回不同內容的問題。 但是我不想僅僅返回幾個字符串,我想給瀏覽器返回完整的HTML內容,這又該怎麽辦呢?

沒問題,不管是什麽內容,最後都是轉換成字節數據發送出去的。 我可以打開HTML文件,讀取出它內部的二進制數據,然後發送給瀏覽器

技術分享圖片
from wsgiref.simple_server import make_server


def index():
    with open("index.html", "rb") as f:
        data = f.read()
    return [data, ]


def home():
    with open("home.html", "rb") as f:
        data = f.read()
    return [data, ]


# 定義一個url和函數的對應關系
URL_LIST = [
    ("/index/", index),
    ("/home/", home),
]


def run_server(environ, start_response):
    start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html;charset=utf8‘), ])  # 設置HTTP響應的狀態碼和頭信息
    url = environ[‘PATH_INFO‘]  # 取到用戶輸入的url
    func = None  # 將要執行的函數
    for i in URL_LIST:
        if i[0] == url:
            func = i[1]  # 去之前定義好的url列表裏找url應該執行的函數
            break
    if func:  # 如果能找到要執行的函數
        return func()  # 返回函數的執行結果
    else:
        return [bytes("404沒有該頁面", encoding="utf8"), ]


if __name__ == ‘__main__‘:
    httpd = make_server(‘‘, 8000, run_server)
    print("Serving HTTP on port 8000...")
    httpd.serve_forever()
技術分享圖片

這網頁能夠顯示出來了,但是都是靜態的啊。頁面的內容都不會變化的,我想要的是動態網站。

沒問題,我也有辦法解決。我選擇使用字符串替換來實現這個需求

技術分享圖片
from wsgiref.simple_server import make_server


def index():
    with open("index.html", "rb") as f:
        data = f.read()
    import time
    time_str = str(time.time())
    data_str = str(data, encoding="utf8")
    data_str = data_str.replace("@@a@@", time_str)
    return [bytes(data_str, encoding="utf8"), ]


def home():
    with open("home.html", "rb") as f:
        data = f.read()
    return [data, ]


# 定義一個url和函數的對應關系
URL_LIST = [
    ("/index/", index),
    ("/home/", home),
]


def run_server(environ, start_response):
    start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html;charset=utf8‘), ])  # 設置HTTP響應的狀態碼和頭信息
    url = environ[‘PATH_INFO‘]  # 取到用戶輸入的url
    func = None  # 將要執行的函數
    for i in URL_LIST:
        if i[0] == url:
            func = i[1]  # 去之前定義好的url列表裏找url應該執行的函數
            break
    if func:  # 如果能找到要執行的函數
        return func()  # 返回函數的執行結果
    else:
        return [bytes("404沒有該頁面", encoding="utf8"), ]


if __name__ == ‘__main__‘:
    httpd = make_server(‘‘, 8000, run_server)
    print("Serving HTTP on port 8000...")
    httpd.serve_forever()
技術分享圖片

這是一個簡單的動態,我完全可以從數據庫中查詢數據,然後去替換我html中的對應內容,然後再發送給瀏覽器完成渲染。 這個過程就相當於HTML模板渲染數據。 本質上就是HTML內容中利用一些特殊的符號來替換要展示的數據。 我這裏用的特殊符號是我定義的,其實模板渲染有個現成的工具: jinja2

下載jinja2:

pip install jinja2
技術分享圖片 技術分享圖片
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="x-ua-compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Title</title>
</head>
<body>
    <h1>姓名:{{name}}</h1>
    <h1>愛好:</h1>
    <ul>
        {% for hobby in hobby_list %}
        <li>{{hobby}}</li>
        {% endfor %}
    </ul>
</body>
</html>
技術分享圖片

使用jinja2渲染index2.html文件:

技術分享圖片
from wsgiref.simple_server import make_server
from jinja2 import Template


def index():
    with open("index2.html", "r") as f:
        data = f.read()
    template = Template(data)  # 生成模板文件
    ret = template.render({"name": "Alex", "hobby_list": ["燙頭", "泡吧"]})  # 把數據填充到模板裏面
    return [bytes(ret, encoding="utf8"), ]


def home():
    with open("home.html", "rb") as f:
        data = f.read()
    return [data, ]


# 定義一個url和函數的對應關系
URL_LIST = [
    ("/index/", index),
    ("/home/", home),
]


def run_server(environ, start_response):
    start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html;charset=utf8‘), ])  # 設置HTTP響應的狀態碼和頭信息
    url = environ[‘PATH_INFO‘]  # 取到用戶輸入的url
    func = None  # 將要執行的函數
    for i in URL_LIST:
        if i[0] == url:
            func = i[1]  # 去之前定義好的url列表裏找url應該執行的函數
            break
    if func:  # 如果能找到要執行的函數
        return func()  # 返回函數的執行結果
    else:
        return [bytes("404沒有該頁面", encoding="utf8"), ]


if __name__ == ‘__main__‘:
    httpd = make_server(‘‘, 8000, run_server)
    print("Serving HTTP on port 8000...")
    httpd.serve_forever()
技術分享圖片

現在的數據是我們自己手寫的,那可不可以從數據庫中查詢數據,來填充頁面呢?

使用pymysql連接數據庫:

conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", passwd="xxx", db="xxx", charset="utf8")
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute("select name, age, department_id from userinfo")
user_list = cursor.fetchall()
cursor.close()
conn.close()

模板的原理就是字符串替換,我們只要在HTML頁面中遵循jinja2的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容。

框架總結

自己定義的web框架
a. 自己寫socket處理請求相關的數據(web服務端) wsgiref uWSGI gunicorn
WSGI協議:
Python Web服務端和Python 應用程序之間通訊的一個標準

b. URL -> 函數 --> 函數裏面寫業務邏輯(取到數據)

c. 把數據填充到HTML頁面(字符串替換)

分類:
1. a、b、c都是用自己的 --> Tornado
2. a用別人的,b和c用自己的 --> Django
3. a和c都用別人的,b用自己的 --> Flask

另外一個維度的分類:
1. Django (大而全)
2. 其他 (小而精)

Django

安裝(安裝最新LTS版)

pip3 install django==1.11.9

MVC和MTV模式

Django的MTV模式本質是各組件之間為了保持松耦合關系,Django的MTV分別代表:

Model(模型):負責業務對象與數據庫的對象(ORM)

Template(模版):負責如何把頁面展示給用戶

View(視圖):負責業務邏輯,並在適當的時候調用Model和Template

此外,Django還有一個url分發器,它的作用是將一個個URL的頁面請求分發給不同的view處理,view再調用相應的Model和Template

技術分享圖片

Django基本命令

創建一個django project

django-admin.py startproject mysite

當前目錄下會生成mysite的工程,目錄結構如下:

技術分享圖片

manage.py ----- Django項目裏面的工具,通過它可以調用django shell和數據庫等。

settings.py ---- 包含了項目的默認設置,包括數據庫信息,調試標誌以及其他一些工作的變量。

urls.py ----- 負責把URL模式映射到應用程序。

PyCharm方式:
File -> new project -> 選Django -> 起名字 -> 點右下角create

在mysite目錄下創建應用,比如blog

python manage.py startapp blog

技術分享圖片

啟動django項目

python manage.py runserver 8080

這樣我們的django就啟動起來了!當我們訪問:http://127.0.0.1:8080/時就可以看到:

技術分享圖片

setting文件配置

模板文件配置

技術分享圖片
TEMPLATES = [
    {
        ‘BACKEND‘: ‘django.template.backends.django.DjangoTemplates‘,
        ‘DIRS‘: [os.path.join(BASE_DIR, "template")],  # template文件夾位置,裏面放網頁文件
        ‘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‘,
            ],
        },
    },
]
技術分享圖片

靜態文件配置

# 在HTML頁面上引用靜態文件時寫的名字
STATIC_URL = ‘/static/‘  # HTML中使用的靜態文件夾前綴
# 靜態文件(CSS文件和JS文件)存放的實際位置
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),  # 靜態文件存放位置
]

技術分享圖片

技術分享圖片
#3、STATIC文件還可以配置STATICFILES_DIRS,指定額外的靜態文件存儲位置。
    #  STATIC_URL的含義與MEDIA_URL類似。

    # ----------------------------------------------------------------------------
    #註意1:
        #為了後端的更改不會影響前端的引入,避免造成前端大量修改

        STATIC_URL = ‘/static/‘               #引用名
        STATICFILES_DIRS = (
            os.path.join(BASE_DIR,"statics")  #實際名 ,即實際文件夾的名字
        )

        #django對引用名和實際名進行映射,引用時,只能按照引用名來,不能按實際名去找
        #<script src="/statics/jquery-3.1.1.js"></script>
        #------error-----不能直接用,必須用STATIC_URL = ‘/static/‘:
        #<script src="/static/jquery-3.1.1.js"></script>

    #註意2(statics文件夾寫在不同的app下,靜態文件的調用):

        STATIC_URL = ‘/static/‘

        STATICFILES_DIRS=(
            (‘hello‘,os.path.join(BASE_DIR,"app01","statics")) ,
        )

        #<script src="/static/hello/jquery-1.8.2.min.js"></script>

    #註意3:
        STATIC_URL = ‘/static/‘
        {% load staticfiles %}
       # <script src={% static "jquery-1.8.2.min.js" %}></script>
技術分享圖片

剛開始學習時可在配置文件中暫時禁用csrf中間件,方便表單提交測試

技術分享圖片
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‘,
]
技術分享圖片

Django基礎必備三件套

from django.shortcuts import HttpResponse, render, redirect

HttpResponse

內部傳入一個字符串參數,返回給瀏覽器。

例如:

def index(request):
    # 業務邏輯代碼
    return HttpResponse("OK")

render

除request參數外還接受一個待渲染的模板文件和一個保存具體數據的字典參數。

將數據填充進模板文件,最後把結果返回給瀏覽器。(類似於我們上面用到的jinja2)

例如:

def index(request):
    # 業務邏輯代碼
    return render(request, "index.html", {"name": "alex", "hobby": ["燙頭", "泡吧"]})
技術分享圖片
---------------render(request, template_name[, context])

結合一個給定的模板和一個給定的上下文字典,並返回一個渲染後的 HttpResponse 對象。

參數:
     request: 用於生成響應的請求對象。

     template_name:要使用的模板的完整名稱,可選的參數

     context:添加到模板上下文的一個字典。默認是一個空字典。如果字典中的某個值是可調用的,視圖將在渲染模板之前調用它。

     content_type:生成的文檔要使用的MIME類型。默認為DEFAULT_CONTENT_TYPE 設置的值。

     status:響應的狀態碼。默認為200。
技術分享圖片

redirect

接受一個URL參數,表示跳轉到指定的URL。

例如:

def index(request):
    # 業務邏輯代碼
    return redirect("/home/")

技術分享圖片
-----------------------------------url.py

 url(r"login",   views.login),
 url(r"yuan_back",   views.yuan_back),

-----------------------------------views.py
def login(req):
    if req.method=="POST":
        if 1:
            # return redirect("/yuan_back/")
            name="yuanhao"

            return render(req,"my backend.html",locals())

    return render(req,"login.html",locals())


def yuan_back(req):

    name="苑昊"

    return render(req,"my backend.html",locals())

-----------------------------------login.html

<form action="/login/" method="post">
    <p>姓名<input type="text" name="username"></p>
    <p>性別<input type="text" name="sex"></p>
    <p>郵箱<input type="text" name="email"></p>
    <p><input type="submit" value="submit"></p>
</form>
-----------------------------------my backend.html
<h1>用戶{{ name }}你好</h1>

#總結: render和redirect的區別:
#   1 if render的頁面需要模板語言渲染,需要的將數據庫的數據加載到html,那麽所有的這一部分
#     除了寫在yuan_back的視圖函數中,必須還要寫在login中,代碼重復,沒有解耦.

#   2 the most important: url沒有跳轉到/yuan_back/,而是還在/login/,所以當刷新後
#     又得重新登錄.

Django基礎一