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