計全攻略:從環(huán)境搭建到避坑實踐)
簡介這是一份面向初學(xué)者的Python Django入門級Web工程源碼覆蓋用戶登錄、頁面渲染、SQLite數(shù)據(jù)存儲等基礎(chǔ)功能適合剛接觸Django框架或希望快速搭建Web應(yīng)用起點的開發(fā)者參考也可作為課程設(shè)計或畢業(yè)設(shè)計的項目底稿。壓縮包共40個文件以13個py源碼文件與24個pyc編譯文件為主另含HTML頁面模板、txt運行說明與sqlite3數(shù)據(jù)庫整體僅155KB體量小、結(jié)構(gòu)清晰。目前已有480人學(xué)習(xí)下載。通過閱讀核心配置、路由、視圖與模型等模塊可以理清Django的MTV分層、路由映射與請求處理流程結(jié)合頁面模板目錄與SQLite數(shù)據(jù)庫能夠直接運行并體驗用戶登錄與頁面渲染的完整鏈路為后續(xù)擴展功能奠定基礎(chǔ)。1. 為什么“簡單Django項目”反而最考驗設(shè)計功力拿到一個“基于Python的簡單Django Web項目設(shè)計源碼”時不少人第一反應(yīng)是Django 不是有startproject一鍵生成骨架嗎怎么還需要“設(shè)計”但恰恰是這個“簡單”二字最能區(qū)分一個人是背過教程還是真正寫過項目。我見過太多初學(xué)者把教程里的投票應(yīng)用跑通后換一個需求就不知道怎么組織文件、怎么改配置、怎么讓多個頁面共用一份布局。所謂“設(shè)計源碼”真正的門檻不在 Django 本身而在于你如何用最小的文件數(shù)量搭出一個結(jié)構(gòu)清晰、能繼續(xù)迭代的 Web 項目。這篇文章會從一個最簡單的任務(wù)管理應(yīng)用入手帶你完整走過環(huán)境搭建、項目骨架、視圖與模板協(xié)作、數(shù)據(jù)庫遷移的全過程并把我在實際開發(fā)中踩過的坑一并交代清楚。如果你是剛學(xué)完 Django 基礎(chǔ)、想獨立完成一個小項目卻不知道從哪下手的開發(fā)者這篇內(nèi)容正好對癥如果你已經(jīng)寫過幾個項目也可以重點看第五章的避坑清單那些問題幾乎每個項目都會遇到。2. 環(huán)境準(zhǔn)備用虛擬環(huán)境隔離你的第一個 Django 項目2.1 環(huán)境創(chuàng)建的三個必做步驟動手寫 Django 項目之前環(huán)境隔離是第一道防線。很多初學(xué)者圖省事直接在全局環(huán)境里pip install django結(jié)果不同項目依賴的 Django 版本互相打架最后只能重裝 Python。常見的做法是用venv為每個項目建立獨立的 Python 環(huán)境這樣項目的依賴清單清晰別人拿到你的源碼后也能按requirements.txt一鍵復(fù)現(xiàn)。# 創(chuàng)建項目目錄并進(jìn)入 mkdir django-todo cd django-todo # 創(chuàng)建虛擬環(huán)境venv 是 Python 3.3 自帶不需要額外安裝 python -m venv venv # 激活虛擬環(huán)境Windows 與 macOS/Linux 命令不同 # Windows: venv\Scripts\activate # macOS / Linux: source venv/bin/activate # 安裝 Django這里固定主版本避免后續(xù)升級帶來不可控變化 pip install django4.2.* # 生成依賴清單方便別人復(fù)現(xiàn)你的環(huán)境 pip freeze requirements.txt這段命令里有兩個關(guān)鍵點需要注意。第一venv目錄必須和項目代碼放在同一層級并在提交源碼時通過.gitignore把它排除掉否則別人 clone 下來會帶著一堆無用的本機文件。第二requirements.txt是“設(shè)計源碼”的一部分它記錄了項目的依賴底線沒有這個文件你的代碼在其他機器上大概率跑不起來。環(huán)境激活后建議順手驗證一下安裝結(jié)果。python -m django --version能看到具體版本號確認(rèn)無誤后再進(jìn)行下一步。如果你用的是 Anaconda 或 pyenv原理相同核心目標(biāo)只有一個讓這個項目的依賴與系統(tǒng)全局環(huán)境徹底隔離。2.2 項目骨架與“設(shè)計源碼”的對應(yīng)關(guān)系環(huán)境就緒后接下來是用django-admin生成項目骨架。這里我一般會先建一個項目級的配置目錄再創(chuàng)建一個應(yīng)用目錄兩個步驟分開做讓配置與業(yè)務(wù)代碼各歸其位。# 生成項目配置目錄項目名用 config避免與業(yè)務(wù)模塊重名 django-admin startproject config . # 創(chuàng)建業(yè)務(wù)應(yīng)用todo 是應(yīng)用名代表“任務(wù)管理”這個業(yè)務(wù)模塊 python manage.py startapp todo # 查看生成的目錄結(jié)構(gòu) tree .執(zhí)行完這兩條命令后你會看到config目錄下是項目的全局配置settings.py、urls.py 等todo目錄下是業(yè)務(wù)代碼models.py、views.py、migrations 等。頂層還有一個manage.py它是所有 Django 管理命令的入口。這個結(jié)構(gòu)對“設(shè)計源碼”來說有一個重要含義config管“這個網(wǎng)站是什么”todo管“這個網(wǎng)站能做什么”。之所以把項目配置目錄命名為config而不是默認(rèn)的mysite是因為默認(rèn)名稱在后續(xù) import 時容易與業(yè)務(wù)模塊產(chǎn)生歧義。比如你建一個應(yīng)用叫site再有一個全局目錄也叫siteimport 時就會互相干擾。用config是社區(qū)里比較普遍的做法團隊協(xié)作時也容易一眼分清職責(zé)邊界。此時項目還不能運行任何業(yè)務(wù)頁面但骨架已經(jīng)完整。你可以先跑一次python manage.py runserver瀏覽器訪問http://127.0.0.1:8000看到 Django 的默認(rèn)歡迎頁就說明環(huán)境與骨架都正常。3. 核心文件逐個拆從 settings.py 到 urls.py 的配置要點3.1 settings.py新手最容易忽略的三個安全配置settings.py是整個項目的配置中心。新手往往只改INSTALLED_APPS和DATABASES卻忽略三個直接影響開發(fā)效率與安全性的配置項ALLOWED_HOSTS、LANGUAGE_CODE與TIME_ZONE、STATIC_URL。# config/settings.py # 允許訪問的主機列表。開發(fā)環(huán)境先留空或填 localhost # 部署時必須改成你的域名或服務(wù)器 IP否則訪問直接報 400。 ALLOWED_HOSTS [localhost, 127.0.0.1] # 語言與時區(qū)改為中文和上海時區(qū)避免后續(xù) datetime 顯示偏差 LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai # 靜態(tài)文件 URL 前綴。模板里用 {% static %} 時最終渲染出的路徑 # 就是 STATIC_URL 文件相對路徑。 STATIC_URL static/第一個配置ALLOWED_HOSTS是新手最容易“翻車”的地方。本地開發(fā)時用127.0.0.1沒問題但如果你用手機或局域網(wǎng) IP 訪問項目Django 會直接拒絕請求報DisallowedHost錯誤。原因就是 Django 默認(rèn)只允許ALLOWED_HOSTS列表里的主機訪問。把局域網(wǎng) IP 加進(jìn)列表即可解決。第二個配置是LANGUAGE_CODE和TIME_ZONE。Django 默認(rèn)語言是英文時區(qū)是 UTC。如果你不做修改Django Admin 后臺全是英文而且datetime.now()獲取到的時間會比北京時間慢 8 小時。這個坑在后文第五節(jié)會專門展開。第三個配置是STATIC_URL。它決定模板中靜態(tài)文件的請求前綴。很多人把 CSS、JS 文件放到項目根目錄就完事結(jié)果頁面樣式死活不加載就是因為沒有同時配置靜態(tài)文件查找路徑。開發(fā)環(huán)境最簡單的做法是在設(shè)置里追加一行STATICFILES_DIRS把項目根目錄下的static文件夾加入查找范圍。3.2 urls.py 與應(yīng)用級路由的分層設(shè)計Django 的路由設(shè)計講究“項目級路由分發(fā)應(yīng)用級路由收口”。項目級urls.py只負(fù)責(zé)把不同應(yīng)用的請求分發(fā)到對應(yīng)模塊具體路徑由應(yīng)用內(nèi)部自己管理。這樣做的好處是一個大型項目幾十個應(yīng)用每個應(yīng)用都能獨立調(diào)整路徑不用反復(fù)改動頂級路由文件。# config/urls.py項目級路由 from django.contrib import admin from django.urls import path, include urlpatterns [ # Django Admin 自帶的路由暫時保留 path(admin/, admin.site.urls), # 所有以 todo/ 開頭的請求交給 todo 應(yīng)用自己的 urls.py 處理 path(todo/, include(todo.urls)), ]# todo/urls.py應(yīng)用級路由需要手動創(chuàng)建 from django.urls import path from . import views urlpatterns [ # 訪問 /todo/ 時調(diào)用 views 里的 task_list 視圖函數(shù) path(, views.task_list, nametask_list), ]這里要解釋一下nametask_list的作用它是這條路由的“別名”。模板中可以用{% url task_list %}直接根據(jù)名稱生成 URL而不是硬編碼/todo/。當(dāng)你改了路徑只需調(diào)整urls.py模板無需任何改動。這種設(shè)計在源碼閱讀時尤其重要——別人拿到你的項目通過name就能快速追蹤頁面邏輯。我在一個模擬項目X中見過反面教材所有路由全部寫在項目級urls.py里應(yīng)用一多幾百行的路由文件根本沒法維護(hù)。每次加一個功能都要在那個龐然大物里搜半天。分層路由的設(shè)計從項目第一天就立好規(guī)矩后續(xù)擴展才不會失控。4. 讓頁面真正跑起來視圖、模板與靜態(tài)資源的協(xié)作4.1 視圖函數(shù)的最簡形態(tài)與上下文傳參視圖是 Django 處理請求的核心邏輯單元。一個最簡單的視圖函數(shù)接收request對象返回render渲染后的模板響應(yīng)。以任務(wù)管理應(yīng)用為例我們希望首頁能顯示所有任務(wù)記錄。# todo/views.py from django.shortcuts import render from .models import Task def task_list(request): # 從數(shù)據(jù)庫取出所有任務(wù)按創(chuàng)建時間倒序排列 tasks Task.objects.all().order_by(-created_at) # 把任務(wù)列表放進(jìn)模板上下文模板里通過 tasks 變量訪問 return render(request, todo/task_list.html, {tasks: tasks})這里的邏輯可以拆成兩部分理解。第一行查詢Task.objects.all()會執(zhí)行SELECT * FROM task之類的 SQL 語句.order_by(-created_at)表示按創(chuàng)建時間倒序減號前綴代表反向排序。然后把查詢結(jié)果字典傳給render函數(shù)第三個參數(shù)就是模板上下文。模板上下文的概念值得多說一句Django 模板只認(rèn)字典形式的上下文鍵名就是模板里可用的變量名。這里傳了{(lán)tasks: tasks}模板里就能用{% for task in tasks %}遍歷任務(wù)。如果你傳的是一個對象而不是字典模板直接報錯。4.2 模板繼承與靜態(tài)文件的路徑配置模板文件放在應(yīng)用目錄下的templates文件夾里但有一個容易繞暈的細(xì)節(jié)Django 在多個應(yīng)用間查找模板時會按INSTALLED_APPS順序搜索如果你的兩個應(yīng)用都有同名模板后加載的應(yīng)用會覆蓋先加載的。為了避免這種“模板污染”規(guī)范做法是在應(yīng)用下的templates目錄里再套一層與應(yīng)用同名的子目錄。!-- todo/templates/todo/task_list.html -- {% extends base.html %} {% block content %} div classcontainer h1我的任務(wù)清單/h1 ul {% for task in tasks %} li{{ task.title }} - {% if task.done %}已完成{% else %}待完成{% endif %}/li {% empty %} li還沒有任務(wù)去添加一個吧。/li {% endfor %} /ul /div {% endblock %}!-- templates/base.html項目級通用模板 -- !DOCTYPE html html langzh-hans head meta charsetUTF-8 title{% block title %}任務(wù)管理{% endblock %}/title !-- 通過 static 標(biāo)簽引用 CSS 文件 -- {% load static %} link relstylesheet href{% static css/style.css %} /head body main {% block content %} {% endblock %} /main /body /htmlbase.html是全局布局模板task_list.html通過{% extends base.html %}繼承它并用{% block content %}填充頁面主體。這種設(shè)計讓公共的導(dǎo)航、頁腳、CSS 引用只寫一次每個頁面只需關(guān)心自己的內(nèi)容塊。這里大概率會遇到一個經(jīng)典問題CSS 文件放在哪個目錄答案是你的項目根目錄下建一個static目錄然后在settings.py里配置STATICFILES_DIRS。沒有這個配置{% static %}標(biāo)簽會報TemplateSyntaxError頁面樣式全丟。# config/settings.py 追加 STATICFILES_DIRS [ BASE_DIR / static, ]配置完成后把style.css放到static/css/下刷新頁面就能看到樣式生效。整個視圖、模板、靜態(tài)文件的協(xié)作路徑就打通了。5. Django 項目避坑5 個高頻“翻車”現(xiàn)場與排查方法5.1 遷移報錯表已存在卻無法關(guān)聯(lián)現(xiàn)象運行python manage.py makemigrations提示No changes detected但數(shù)據(jù)庫中根本沒有對應(yīng)的表。原因最常見的情況是你手動刪過數(shù)據(jù)庫文件或者把 app 的migrations目錄清空過。Django 通過遷移記錄文件追蹤模型變化記錄與數(shù)據(jù)庫不一致時就會“失憶”。解決先確認(rèn) app 是否真的加入INSTALLED_APPS再刪掉數(shù)據(jù)庫和migrations目錄中的所有文件init.py 除外然后重新執(zhí)行makemigrations和migrate。不要保留任何舊的遷移記錄。5.2 靜態(tài)文件 404開發(fā)環(huán)境與生產(chǎn)環(huán)境姿勢完全不同現(xiàn)象本地開發(fā)時頁面樣式加載正常換成runserver 0.0.0.0:8000通過局域網(wǎng) IP 訪問CSS 全部 404。原因runserver在默認(rèn)DEBUGTrue時才會自動托管靜態(tài)文件。當(dāng)你用0.0.0.0啟動Django 仍然會處理靜態(tài)文件請求但瀏覽器請求地址變成了http://局域網(wǎng)IP:8000/static/css/style.css如果ALLOWED_HOSTS沒有把該 IP 加進(jìn)去請求會被拒絕。解決把局域網(wǎng) IP 加進(jìn)ALLOWED_HOSTS同時確認(rèn)STATIC_URL與STATICFILES_DIRS配置正確。生產(chǎn)環(huán)境則必須用collectstatic把靜態(tài)文件收集到指定目錄再由 Nginx 托管Django 本身不負(fù)責(zé)生產(chǎn)環(huán)境的靜態(tài)文件服務(wù)。5.3 時區(qū)與 datetime.now() 的“玄學(xué)”偏差現(xiàn)象任務(wù)創(chuàng)建時間顯示比當(dāng)前時間慢了 8 小時或者 Admin 后臺時間與本地時間不一致。原因settings.py里TIME_ZONE沒改成Asia/Shanghai同時USE_TZ為True時數(shù)據(jù)庫存的是 UTC 時間展示時沒有做時區(qū)轉(zhuǎn)換。解決把TIME_ZONE Asia/Shanghai和LANGUAGE_CODE zh-hans配置好。如果你不想處理時區(qū)轉(zhuǎn)換的復(fù)雜度可以把USE_TZ FalseDjango 就會直接使用本地時間不再做 UTC 轉(zhuǎn)換。但要注意USE_TZ False后所有涉及時間的邏輯都要多留個心建議還是規(guī)范使用USE_TZ True。5.4 CSRF 驗證失敗表單提交被攔的真相現(xiàn)象在模板里寫了一個form methodpost提交時報CSRF verification failed。原因Django 默認(rèn)開啟 CSRF 防護(hù)所有 POST 請求必須帶上 CSRF token。模板里忘記加{% csrf_token %}標(biāo)簽或者使用了 Ajax 但沒有在請求頭中攜帶 token都會被攔截。解決在模板表單內(nèi)加入{% csrf_token %}。Ajax 請求則在 JavaScript 中從 cookie 讀取csrftoken然后寫入請求頭X-CSRFToken。記住一點凡是 POST 請求必須通過某種方式攜帶 CSRF token。5.5 ALLOWED_HOSTS換 IP 訪問就報錯的排查路徑現(xiàn)象本地用127.0.0.1:8000一切正常手機通過局域網(wǎng) IP 訪問時頁面提示DisallowedHost。原因ALLOWED_HOSTS列表只包含localhost和127.0.0.1局域網(wǎng) IP 不在允許范圍。Django 出于安全考慮默認(rèn)拒絕所有未列入白名單的主機訪問。解決把當(dāng)前機器的局域網(wǎng) IP例如192.168.1.100加入ALLOWED_HOSTS然后重啟runserver。如果只是臨時調(diào)試可以直接設(shè)置ALLOWED_HOSTS [*]但不建議長期這么做。6. 從“能跑”到“能看”用一個 Bootstrap 模板給項目加分6.1 用模板繼承改造現(xiàn)有頁面“設(shè)計源碼”如果只是功能跑通遠(yuǎn)遠(yuǎn)不夠。一個能拿得出手的項目至少得有一套看得過去的界面。最快的方式是引入現(xiàn)成的 CSS 框架這里以 Bootstrap 為例進(jìn)行改造。!-- templates/base.html改造后 -- !DOCTYPE html html langzh-hans head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}任務(wù)管理{% endblock %}/title !-- 直接引用 public CDN省去本地靜態(tài)文件配置 -- link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.3/dist/css/bootstrap.min.css relstylesheet {% block extra_head %}{% endblock %} /head body classbg-light nav classnavbar navbar-expand-lg navbar-dark bg-primary div classcontainer a classnavbar-brand href{% url task_list %}任務(wù)管理/a /div /nav main classcontainer py-4 {% block content %}{% endblock %} /main /body /html改造后的base.html引入了 Bootstrap 的公共 CDN內(nèi)容分發(fā)網(wǎng)絡(luò)即把靜態(tài)資源放在第三方服務(wù)器上用戶訪問時從就近節(jié)點加載速度更快且不占用你的服務(wù)器流量。這樣做的好處是項目不需要下載維護(hù)一份 Bootstrap 源文件模板可以直接使用 Bootstrap 的類名。注意一個細(xì)節(jié)CDN 方式只適合開發(fā)階段或?qū)虞d速度不敏感的場景。正式部署到內(nèi)網(wǎng)或生產(chǎn)環(huán)境最好把 Bootstrap 文件下載到項目的static目錄下避免外網(wǎng)中斷導(dǎo)致樣式全崩。6.2 學(xué)會這個排查習(xí)慣省掉一半調(diào)試時間最后一個技巧也是我每次項目必用的排查習(xí)慣開啟 Django 的 Debug 日志輸出。在settings.py里配置一個簡單的日志字典Django 會把你所有 SQL 查詢、頁面渲染耗時、異常堆棧全部打印在控制臺。遇到“頁面空白”“數(shù)據(jù)不對”這類問題先看日志不要盲目改代碼。# config/settings.py 追加日志配置 LOGGING { version: 1, disable_existing_loggers: False, handlers: { console: { class: logging.StreamHandler, }, }, loggers: { # 打印所有 SQL 語句排查數(shù)據(jù)庫查詢問題非常有效 django.db.backends: { handlers: [console], level: DEBUG, }, }, }我自己的習(xí)慣是項目出現(xiàn)任何異常第一件事不是打開源碼而是看終端輸出。SQL 日志能告訴你 Django 到底執(zhí)行了什么查詢、查詢了幾次。比如頁面加載慢看到django.db.backends日志里一個頁面觸發(fā)了 50 條 SQL馬上就能判斷是 N1 查詢問題然后去優(yōu)化 ORM 邏輯。這個習(xí)慣幫我省過無數(shù)個小時的排查時間。哪怕現(xiàn)在寫一個“簡單”項目我也會從第一天就把日志配好。一個整潔、可觀測的開發(fā)環(huán)境比多寫幾個功能更能體現(xiàn)一個開發(fā)者的成熟度。希望這些內(nèi)容能幫你把 Django 項目從“能跑”推進(jìn)到“能看、能維護(hù)”少走我當(dāng)年走過的彎路。本文還有配套的精品資源點擊獲取