目錄
一、創建虛擬環境(Windows)
二、創建項目
三、創建應用程序
四、創建網頁:學習筆記主頁
五、創建其他網頁
六、用戶輸入數據
七、用戶賬戶
八、讓用戶擁有自己的數據
九、設置應用程序樣式
十、部署“學習筆記”
一、創建虛擬環境(Windows)
1、python語言有很多很多的庫,但是python有2和3不兼容的情況,而且很多框架,如django框架的不同版本之間也會有些兼容問題,所以有時在開發或維護不同版本的框架時就會造成沖突。而虛擬環境很好的解決了這個問題。虛擬環境就像是一個容器,我們可以在這個容器內安裝自己需要的包和模塊,並且對外界沒有任何影響。
2、如果系統中只有python2或只有python3,可以直接打開命令行,輸入pip install virtualenv 來下載創建虛擬環境的包(如果提示pip不是內部命令,把python文件下的Scripts的文件路徑加入計算機環境變量即可可)
3、選擇一個或創建一個文件夾用來存放創建的虛擬環境(以E盤下的E:\Python\learning_log為例)。
4、進入cmd窗口,使用cd導航來到這個文件夾內
5、使用命令:virtualenv 虛擬環境名 來創建一個虛擬環境,創建成功后如下圖:
6、cd 進入虛擬環境下的Scripts文件夾,輸入命令activate 激活虛擬環境
當命令行前面有(ll_env)時,就表示現在處於虛擬環境里了,在這里使用pip安裝自己要使用的模塊即可
7、想要退出虛擬環境時 使用deactivate
如果系統中py2和py3同時都有,那么使用pip2和pip3分別安裝好py2和py3的virtualenv包后,可以將python2目錄下的Scripts目錄里的virtualenv.exe改為virtualenv2.exe,在保證python的環境變量都加到了計算機環境變量的情況下,我們就可以使用’virtualenv2 虛擬環境名’ 來創建py2的虛擬環境,用’virtualenv3 虛擬環境名’ 創建py3的虛擬環境了。
二、創建項目
1、要使用Django,首先需要創建虛擬工作環境。虛擬環境是系統的一個位置,你可以在其中安裝包,並將其與其他Python包隔離,為了將項目部署到服務器,隔離是必須的。
2、首先要新建一個文件夾E:\Python\learning_log,在終端中導航到該文件夾,創建一個名為ll_env的虛擬環境,前提是你安裝了模塊virtualenv,然后導航到ll_env文件夾下的Scripts文件夾,運行activate激活虛擬環境,然后就可以在環境中安裝Django:pip install Django,Django只有在虛擬環境處於活動狀態時才能用。
3、導航到E:\Python\learning_log,確保環境處於活動狀態,新建一個名為learning_log的項目,這個命令末尾的句點讓新項目使用合適的目錄結構。learning_log文件夾中出現四個文件,其中settings.py指定Django如何與你的系統交互以及如何管理項目,文件urls.py告訴Django應該創建哪些網頁來響應瀏覽器請求;文件wsgi.py幫助Django提供它創建的文件,這個文件名是webserver gateway interface (Web服務器網關接口)的縮寫。
4、Django將大部分與項目相關的信息都存儲在數據庫中,因此我們需要創建一個供Django使用的數據庫。
- 我們將修改數據庫稱為遷移數據庫。首次執行命令migrate時,將讓Django確保數據庫與項目的當前狀態匹配,operations to perform表示它將創建必要的數據庫表,用於存儲我們將在這個項目中使用的信息,再確保數據庫結構與當前代碼匹配。又出現了一個新的文件db.sqlite3,SQLite是一種使用單個文件的數據庫,是編寫簡單應用程序的理想選擇,因為它讓你不用關注數據庫管理的問題。
5、核實Django是否正確創建了項目,可執行命令python manage.py runserver,如下圖所示:
- 項目的URL為http://127.0.0.1:8000/,表明項目在你的計算機(即localhost)的端口8000上偵聽。localhost是一種只處理當前系統發出的請求,而不允許其他人查看你正在開發的網頁的服務器。現在打開一款web瀏覽器,並輸入http://127.0.0.1:8000/,出現下圖:
注意: 如果出現錯誤消息‘that port is already in use’(指定端口已被占用),請執行命令python manage.py runserver8001,讓Django使用另一個端口,如果還不行,不斷執行上述命令,知道找到可用端口。
6、Django項目由一系列應用程序組成,他們協同工作,讓項目成為一個整體,暫時只創建一個應用程序,它將完成大部分工作。剛才的終端窗口還運行着runserver,所以請再打開一個窗口,導航到manage.py所在文件夾,激活虛擬環境,再執行命令startapp:
- 命令startapp appname 讓Django建立創建應用程序所需的基礎設施。新增文件夾learning_logs,我們使用其中的文件models.py來定義我們要在應用程序中管理的數據。
三、創建應用程序
1、定義模型:打開learning_logs文件夾中的models.py,編輯如下:
from django.db import models class Topic(models.Model): '''用戶學習主題的類''' text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) def __str__(self): '''返回模型的字符串表示'''
return self.text
- 創建一個繼承models.Model的類,Model是Django中一個定義了模型基本功能的類,該類只有兩個屬性:text和date_added。text是一個CharField——由字符或文本組成的數據,需要存儲少量的文本,如名稱、標題或城市時,可使用CharField。定義CharField屬性時,必須告訴Django要在數據庫中預留多少空間,這里設置為200個字符,對應存儲名稱標題已經足夠了。date_added是一個DateTimeField——記錄日期和時間的數據,實參auto_now_add=True,每當用戶創建新主題時,Django都將這個屬性自動設置成系統當前的日期和時間。
- 我們需要告訴Django,默認使用哪一個屬性來顯示有關主題的信息,Django調用方法__str__()來顯示模型的簡單表示。在這里,我們調用方法__str__(self)返回存儲在屬性text中的字符串。
- 提示:如果你想知道可在模型中使用的各種字段,請參閱Django model field reference(Django模型字段參考),網址為:https://docs.djangoproject.com/en/1.8/ref/models/fields/
2、激活模型:要使用模型,必須讓Django將應用程序包含到項目中。打開E:\Python\learning_log\learning_log中的settings.py,你將看到這樣一個片段,即告訴Django哪些應用程序安裝在項目中:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', #我的應用程序
'learning_logs' ]
- 這是一個列表,告訴Django項目是由哪些應用程序組成的,將當前的learning_logs應用程序添加到這個列表中。接下來,需要讓Django修改數據庫,使其能夠存儲與模型Topic相關的信息,在終端執行下面的命令:
- 命令makegrations讓Django確定該如何修改數據庫,輸出表明Django創建了一個名為0001_initial.py的遷移文件,這個文件將在數據庫中為模型Topic創建一個表。
- 下面來應用這種遷移,讓Django替我們修改數據庫:
- 每當需要修改‘學習筆記’管理的數據時,都采用如下三個步驟:修改models.py,對learning_logs調用makemigrations,讓Django遷移項目。
3、創建超級用戶
- Django允許你創建一個擁有所有權限的用戶——超級用戶。創建超級用戶的命令如下:
- 用戶名可以任意輸入,郵件地址可以為空,密碼需要輸入兩次,且兩次相同,否則會報錯,如上圖所示。
- 注意:可能會對網站管理員隱藏有些敏感信息。例如,Django並不存儲你輸入的密碼,而是存儲從改密碼派生出來的一個字符串——散列值。每當你輸入密碼時,Django都計算其散列值,並將結果與存儲的散列值進行對比,如果這連個散列值相同,就通過了身份驗證。通過存儲散列值,即便黑客獲得了網站數據庫的訪問權,也只能獲取其中的散列值,而無法獲得密碼。在網站配置正確的情況下,幾乎無法根據散列值推導出密碼。
4、向管理網站注冊模型
- 我們創建應用程序learning_logs時,Django在models.py所在文件夾創建了一個admin.py文件,打開輸入一
下代碼:
from django.contrib import admin from learning_logs.models import Topic admin.site.register(Topic)
- 這些代碼導入我們要注冊的模型Topic,再使用admin.site.register()讓Django通過管理網站管理我們的模型。
- 現在使用超級用戶訪問管理網站:http://127.0.0.1:8000/admin/,結果如下圖所示
- 注意:如果你在瀏覽器上看到http://127.0.0.1:8000/ 拒絕了我們的連接請求,請確認你在終端窗口中是否運行
着Django服務器,如果沒有,請激活虛擬環境,並執行命令python manage.py runserver
5、添加主題
- 點擊Topics后面的加號,在text方框中輸入chess,保存。
6、定義模型Entry
- 要記錄學習到的攀岩和國際象棋的知識,需要為用戶可在學習筆記中添加的條目定義模型。
- 每個條目都與特定的主題相關聯,這種關系被稱為多對一關系,即多個條目可關聯到同一個主題
from django.db import models class Topic(models.Model): '''用戶學習主題的類''' text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) def __str__(self): '''返回模型的字符串表示''' return self.text class Entry(models.Model): '''學到的有關某個主題的具體知識''' topic = models.ForeignKey(Topic,on_delete=models.DO_NOTHING) text = models.TextField() date_added = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = 'entries' def __str__(self): return self.text[:50] + '...'
- 像Topic一樣,Entry也繼承了Django基類Model。
- 第一個屬性topic是一個ForeignKey(外鍵)實例,外鍵是一個數據庫術語,它引用了數據庫中的另一條記錄;這些代碼將每個條目關聯到特定的主題,每個主題創建時,都給他分配一個鍵(或ID)。需要在兩項數據之間建立聯系時,Django使用與每項信息相關聯的鍵。定義外鍵和一對一關系的時候需要加on_delete=models.DO_NOTHING,此參數為了避免兩個表里的數據不一致問題,不然會報錯:TypeError: __init__() missing 1 required positional argument: 'on_delete'。
- 屬性text是一個TextField實例,這種字段不需要長度限制,因為我們不想限制條目的長度。
- 屬性date_added讓我們能夠按創建順序呈現條目,並在每個條目旁邊設置時間戳。
- 接着我們在Entry類中嵌套了Meta類,該類存儲用於管理模型的額外信息,在這里,它讓我們能夠設置一個特殊屬性,讓Django在需要時使用entries來表示多個條目,如果沒有這個類,Django將使用entrys來表示多個條目。
- 最后,方法__str__()告訴Django,呈現條目時應顯示哪些信息。由於條目包含的文本可能很長,我們讓Django只顯示text的前50個字符,我們還添加了個省略號,表示顯示的並非整個條目。
7、遷移模型
- 由於又添加了一個模型,因此需要再次遷移數據庫。還是之前說過的三個步驟:修改models.py,執行python manage.py makemigrations,再執行python manage.py migrate。下面來遷移數據庫並查看輸出:
- 生成了一個新的遷移文件——0002_entry.py,它告訴Django如何修改數據庫,使其能夠存儲與模型Entry
有關的信息。
8、向管理網站注冊Entry
from django.contrib import admin from learning_logs.models import Topic,Entry admin.site.register(Topic) admin.site.register(Entry)
9、Django shell交互式環境
- 通過交互式終端會話以編程方式查看輸入的數據
- 在活動的虛擬環境中執行命令python manage.py shell,會啟動一個Python編譯器,可使用它來探索存儲在項目中的數據。接下來我們導入了模塊learning_logs.models中的模型Topic,然后使用方法Topic.objects.all()來獲取模型中所有實例,返回一個列表,稱為查詢集(QuerySet)
- 查看每個主題的ID,知道ID后,就可以獲取該對象並查看其任何屬性
- 前面我們給模型Entry定義了屬性topic,這是一個ForeignKey(外鍵),將條目與主題關聯起來。
- 為通過外鍵獲取數據,我們可使用相關模型的小寫名稱、下划線和單詞set。例如,假設你有模型Pizza和Topping,而Topping通過一個外鍵關聯到Pizza;如果你有一個名為my_pizza的對象,表示一張披薩,就可以使用代碼my_pizza.topping_set.all()來獲取這張披薩的所有配料。
- 編寫用戶可請求的網頁時,我們將使用這種方法。確認代碼能夠獲取所需的數據時,shell很有幫助,如果代碼在shell中的行為符合預期,那么他們在項目文件中也能正常工作。如果代碼引發了錯誤或獲取的數據不符合預期,那么在簡單的shell環境中排除故障要比在生成網頁的文件中排除故障容易的多。
- 注意:每次修改模型后,都要重啟shell,這樣才能看到修改的效果。要退出shell會話,可按Ctrl+D;如果是Windows,Ctrl+Z,再按回車。
四、創建網頁:學習筆記主頁
1、映射URL
- 用戶通過在瀏覽器中輸入URL以及單擊鏈接來請求網頁,因此我們需要確定項目需要哪些URL。
- 主頁的URL最重要,它是用戶用來訪問項目的基礎URL,當前基礎URL(http://127.0.0.1:8000/)返回默認的Django網站,讓我們知道正確的建立了項目,我們將修改這一點,將這個基礎URL映射到‘學習筆記’的主頁。
- 打開項目主文件夾learning_log中的文件urls.py:
1 from django.contrib import admin 2 from django.urls import path 3 from django.conf.urls import include,url 4 5 urlpatterns = [ 6 path('admin/', admin.site.urls), 7 path('',include('learning_logs.urls', namespace='learning_logs')), 8 ]
- 前兩行導入了為項目和管理網站URL的函數和模塊,在這個針對整個項目的urls.py文件中,變量urlpatterns包含項目中的應用程序的URL。
- 第6行代碼包含模塊admin.site.urls,該模塊定義了可在管理網站中請求的所有URL。
- 第7行代碼,我們添加了一行包含模塊learning_logs.urls的代碼。其中實參namespace讓我們能夠將應用程序learning_logs的URL同項目中的其他URL區分開來,對項目擴展很有幫助。
- 接下來我們需要在learning_logs文件夾中創建另一個urls.py文件。
'''定義learning_logs的URL模式''' from django.conf.urls import url from . import views app_name='[app_name]' urlpatterns = [ # 主頁 url(r'^$',views.index,name='index'), ]
- 導入函數url,用它來講URL映射到視圖。
- 導入模塊view,其中的句點表示從當前的urls.py所在的文件夾中導入view.py文件。這個模塊中變量urlpatterns是一個列表,包含可在應用程序learning_logs中請求的網頁。
- app_name指定該應用程序的名稱。python3 Django 環境下,如果你遇到namespace沒有注冊以及在根目錄下urls.py中的include方法的第二個參數namespace添加之后就出錯的問題。請在[app_name]目錄下的urls.py中的urlpatterns前面加上app_name='[app_name]',[app_name]代表你的應用的名稱。
- 實際的URL模式是一個對函數url()的調用,這個函數接受三個實參,第一個是一個正則表達式r'^$',其中r讓Python將接下來的字符串視為原始字符串,而引號告訴Python正則表達式始於和終於何處。脫字符(^)讓Python查看字符串的開頭,美元符號讓Python查看字符串的結尾,總體而言,這個表達式讓Python查找開頭和結尾之間沒有任何東西的URL。Python忽略項目的基礎URL(http://127.0.0.1:8000/),因此這個表達式和基礎URL匹配。如果請求的URL不與任何URL模式匹配,Django將返回一個錯誤頁面。
- url()的第二個實參,指定了要調用的視圖函數。請求的URL與正則表達式匹配時,Django將調用views.index。
- 第三個實參指定這個URL模式的名稱為index,讓我們在代碼的其他地方可以引用它,每當需要提供這個主頁的鏈接時,都使用這個名稱,而不編寫URL
- 注意:正則表達式一般被稱為regex,幾乎每種語言都使用它。
2、編寫視圖
- 視圖函數接受請求中的信息,准備好生成網頁所需的數據,再將這些數據發給瀏覽器。
- 打開learning_logs文件夾中的view.py文件,為主頁編寫視圖命令如下:
1 from django.shortcuts import render 2 def index(request): 3 '''學習筆記的主頁''' 4 return render(request,'learning_logs/index.html')
- URL請求與我們剛才定義的模式匹配時,Django將在文件view.py中查找函數index(),再將請求對象傳遞給這個視圖函數。
- 函數render()根據視圖提供的數據渲染響應。其中兩個實參:原始請求對象以及一個可用於創建網頁的模板。
3、編寫模板
- 模板定義了網頁的結構,模板能讓你訪問視圖提供的任何數據。
- 在文件夾learning_logs中新建文件夾templates,在templates文件夾中新建文件夾learning_logs,在最里面的learning_logs
- 文件夾中新建文件index.html,在文件中編寫如下命令:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Learning Log</title> </head> <body> <p>Learning Log</p> <p>Learning log help you keep track of your learning,for any topic you'are learning about.</p> </body> </html>博客地址:https://home.cnblogs.com/
- 兩個<head>之間的表示網站名稱,接下來兩行代碼分別表示可以輸入中文,兩個<title>之間的代碼表示網站的名稱。
- 兩個<body>之間的是網站首頁的內容
- <p></p>標識段落,<p>指出了段落的開頭位置,</p>指出段落的結束位置,這里定義了兩個段落,一個充當標題一個闡述可以用學習筆記來干什么。
- </html>后面可以放鏈接。重新訪問主頁:
五、創建其他網頁
1、模板繼承:父模板
- 創建網站時,幾乎都有一些所有網頁都包含的元素,我們可以編寫一個包含通用元素的父模板,並讓每個網頁都繼承這個模板。
- 首先,我們來創建一個base.html文件,放在index.html所在文件夾中,當前,所有頁面都包含的元素只有頂端的標題,因此我們將標題設置為到主頁的鏈接:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Learning Log</title> 6 </head> 7 <body> 8 <p> 9 <a href="{% url 'learning_logs:index' %}">Learning Log</a> 10 </p> 11 {% block content %}{% endblock content %} 12 </body> 13 </html>
- 第9行代碼創建了一個包含項目名的段落,該段落也是一個到主頁的鏈接。我們使用了一個模板標簽來創建鏈接,它是用大括號和兩個%表示的;模板標簽是一小段代碼,生成要在網頁中顯示的信息,在這個實例中,模板標簽生成一個URL,該URL與learning_logs\urls.py中定義的名為index的URL模式匹配。在這個實例中,learning_logs是一個命名空間,而index是該命名空間中一個名稱獨特的URL模式。
- 在簡單的HTML頁面中,鏈接是使用錨標簽定義的:<a href="link_url">link text</a>
- 注意:讓模板標簽來生成URL,可讓鏈接保持最新容易的多,要修改項目中的URL,只需要修改urls.py中的URL模式。在我們的項目中,每一個網頁都繼承base.html,因此從現在開始,每個網頁都包含回到主頁的鏈接。
- 第11行我們插入了一對塊標簽,這個塊名為content,是一個占位符,其中包含的信息將由子模板指定。子模板並非必須定義父模板中的每個塊,因此在父模板中可使用任意多個塊來預留空間,而子模板可根據需要定義相應數量的塊。
- 注意:在Python代碼中,我們幾乎總是縮進四個空格,而模板文件的縮進層級更多,因此每個層級通常只縮進兩個空格。
2、模板繼承:子模板
- 先在要重新編寫index.html文件,使其繼承base.html:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Learning Log</title> 6 </head> 7 <body> 8 {% extends 'learning_logs\base.html' %} 9 {% block content %} 10 <p>Learning log help you keep track of your learning,for any topic you'are 11 learning about.</p> 12 {% endblock content %} 13 </body> 14 </html>
- 子模板的第一行必須包含標簽{% extends %},讓Django知道它繼承了哪個父模板,extends后面加上父模板的路徑,這行代碼導入父模板中的所有內容,讓子模板能夠指定要在content塊預留的空間中添加的內容。
- 第9行我們插入了一個名為content的{% block %}標簽,以定義content塊。不是從父模板中繼承的內容都包含在這個content塊中,標簽{% endblock content %}指出了內容定義的結束位置。
- 注意:在大型項目中,通常有一個用於整個網站的父模板——base.html,且網站的每個主要部分都有一個父模板。每個部分的父模板都繼承base.html,而網站的每個網頁都繼承相應部分的父模板。
3、顯示所有主題的頁面
(1)定義URL模式,使用單詞topics,因此http://127.0.0.1:8000/topics/將返回顯示所有主題的頁面。
- 修改文件urls.py如下:
1 '''定義learning_logs的URL模式''' 2 from django.conf.urls import url 3 from . import views 4 app_name='[app_name]' 5 urlpatterns = [ 6 # 主頁 7 url(r'^$',views.index,name='index'), 8 #顯示所有主題的頁面 9 url(r'^topics/$',views.topics,name='topics'), 10 ]
- 我們只是在正則表達式中添加了topics/,Django檢查請求的URL時,這個模式與這樣的URL匹配:基礎URL后面跟着topics,可以在末尾處包含斜杠,也可以省略它,但單詞topics后面不能有任何東西,否則就與該模式不匹配。其URL與該模式匹配的請求都將交給view.py中的函數topics()來處理。
(2)修改視圖view.py
1 from django.shortcuts import render 2 from .models import Topic 3 def index(request): 4 '''學習筆記的主頁''' 5 return render(request,r'learning_logs\index.html') 6 def topics(request): 7 '''顯示所有主題的函數''' 8 topics = Topic.objects.order_by('date_added') 9 context = {'主題':topics} 10 return render(request,r'learning_logs\topics.html',context)
- 首先導入與所需數據相關的模型,函數topics包含一個形參:Django從服務器那里收到的request對象(見6)。在8處,我們查詢數據庫——請求提供Topic對象,並按屬性date_added對他們排序,將返回的查詢存入topics。
- 在9處,我們定義了一個將要發給模板的上下文,上下文是一個字典,其中的鍵是我們將在模板中用來訪問數據的名稱,而值是我們要發送給模板的數據,這里只有一個鍵值對,它包含我們將在網頁中顯示的一組主題。創建使用數據的網頁時,除對象request和模板的路徑外,我們還將變量context傳遞給render().
(3)創建顯示所有主題的HTML模板文件:topics.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Learning Log</title> 6 </head> 7 <body> 8 {% extends 'learning_logs\base.html' %} 9 {% block content %} 10 <p>Topics</p> 11 <ul> 12 {% for topic in topics %} 13 <li>{{ topic }}</li> 14 {% empty %} 15 <li>No topics have added yet.</li> 16 {% endfor %} 17 </ul> 18 {% endblock content %} 19 </body> 20 </html>
- 首先使用標簽{% extends %}來繼承base.html,再開始定義content塊。這個網頁的主題是一個項目列表,其中列出了用戶輸入的主題,在標准HTML中,項目列表被稱為無需列表,用標簽對<ul></ul>表示,包含所有主題的項目列表始於11處。
- 在12處,我們使用了一個相當於for循環的模板標簽,它遍歷字典context中列表topics,模板中的for循環都要使用{% endfor %}來指出結束位置。
- 在循環中,我們要將每個主題轉換為一個項目列表項。要在模板中打印變量,需要將變量名用雙花括號括起來。每次循環,{{ topic }}都被替換為topic的當前值。<li></li>表示一個項目列表項,在標簽對<ul></ul>內部,位於標簽<li></li>之間的內容都是一個項目列表項。
- 標簽{% empty %}告訴Django,列表為空時該怎么辦,這里是打印一個消息,告訴用戶還沒有添加任何主題。
- 最后結束for循環和項目列表
(4)修改父模板,使其包含到顯示所有主題的頁面的鏈接
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Learning Log</title> 6 </head> 7 <body> 8 <p> 9 <a href="{% url 'learning_logs:index' %}">學習筆記</a> - 10 <a href="{% url 'learning_logs:topics' %}">Topics</a> 11 </p> 12 {% block content %}{% endblock content %} 13 </body> 14 </html>
- 這里我們在主頁鏈接后面添加了一個連字符(-),然后添加了顯示所有主題的鏈接。
- 刷新瀏覽器中的主頁,點擊Topics,就看到所有主題了
4、顯示特定主題的頁面
(1)URL模式
- 與前面的所有URL模式都不同,它將使用主題的ID屬性來指出請求的是哪個主題。例如,主題chess的ID為1,URL為http://127.0.0.1:8000/topics/1/,下面修改learning_logs中的urls.py:
#特定主題的詳細頁面 url(r'^topics/(?P<topic_id>\d+)/$',views.topic,name='topic'),
- 正則表達式的第二部分(/(?P<topic_id>\d+)/)與包含在兩個斜杠內的整數匹配,並將這個整數存儲在一個名為topic_id的實參中;這部分表達式兩邊的括號捕獲URL中的值;?P<topic_id>將匹配的值存儲到topic_id中,而表達式\d+與包含在兩個斜杠內的任何數字都匹配,不管這個數字為多少位。
- 發現URL與這個模式匹配時,Django將調用視圖函數topic(),並將存儲在topic_id中的值作為他的實參傳遞給它,在這個函數中,我們間使用topic_id來獲取相應的主題。
(2)視圖
- 在view.py文件中創建topic()函數
1 def topic(request,topic_id): 2 '''顯示單個主題及其所有的條目''' 3 topic = Topic.objects.get(id=topic_id) 4 entries = topic.entry_set.order_by('-date_added') 5 context = {'topic':topic,'entries':entries} 6 return render(request,r'learning_logs\topic.html',context)
- 這個函數接受正則表達式(?P<topic_id>\d+)捕獲的值,並將其存儲到topic_id中,在3中我們使用get()來獲取指定的主題。在4處我們獲得與該主題相關的條目,並將它們按時間排序,-表示降序排列,即先顯示最近的條目。我們將主題和條目都存儲在字典中,並將字典發送給模板topic.html。
- 注意:3和4處的代碼被稱為查詢,因為它們向數據庫查詢特定的信息。在自己的項目中編寫這樣的查詢時,最好先在Django shell中嘗試,在shell中執行代碼可更快的獲得反饋。
(3)模板
- 這個模板需要顯示主題的名稱和所有條目,如果當前主題不包含任何條目,我們還需要向用戶指出這一點。
- topic.html
1 {% extends 'learning_logs\base.html' %} 2 {% block content %} 3 <p>Topics: {{ topic }}</p> 4 <p>Entries:</p> 5 <ul> 6 {% for entry in entries %} 7 <li> 8 <p>{{ entry.date_added|date:'M d,Y H:i' }}</p> 9 <p>{{ entry.text|linebreaks }}</p> 10 </li> 11 {% empty %} 12 <li>There are no entries for this topic yet.</li> 13 {% endfor %} 14 </ul> 15 {% endblock content %}
- 3中顯示當前的主題,它存儲在變量{{ topic }}中,變量topic包含在字典context中,所以這里可以使用。接下來,定義一個顯示每個條目的項目列表,遍歷每個主題的所有條目。
- 每個項目列表都列出兩項信息:條目的時間戳和完整的文本。豎線(|)表示模板過濾器——對模板變量的值進行修改的函數,過濾器date:'M d,Y H:i' 以這樣的方式顯示時間,接下來的一行顯示完整的文本,而不再是前50個字符。過濾器linebreaks將包含換行符的長條目轉換為瀏覽器能夠理解的格式,以免顯示為一個不間斷的文本塊。
(4)將每個主題都設置為鏈接
- 在瀏覽器中查看特定主題的頁面前,我們需要修改topics.html,讓每個主題都鏈接到相應的網頁。
1 {% extends 'learning_logs\base.html' %} 2 {% block content %} 3 <p>Topics</p> 4 <ul> 5 {% for topic in topics %} 6 <li> 7 <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a> 8 </li> 9 {% empty %} 10 <li>No topics have been added yet.</li> 11 {% endfor %} 12 </ul> 13 {% endblock content %}
- 我們使用模板標簽url根據learning_logs中名為topic的URL模式來生成合適的鏈接。這個模式要求提供實參top_id,因此我們在模板標簽中添加了屬性topic_id。現在,主題列表的每個主題都是一個鏈接,鏈接到相應主題的頁面,如http://127.0.0.1:8000/topics/6/
- 注意:少一個空格都不行
- 刷新瀏覽器
六、用戶輸入數據
1、添加新主題
(1)創建用於添加主題的表單:forms.py,存儲到models.py所在文件夾。
1 from django import forms 2 from .models import Topic 3 class TopicForm(forms.ModelForm): 4 class Meta: 5 model = Topic 6 fields = {'text'} 7 labels = {'text':''}
- 最簡單的ModelForm版本只包含一個內嵌的Meta類,它告訴Django根據哪個模型創建表單,以及在表單中包含哪些字段。
- 這里我們根據模型Topic創建一個表單,該表單只包含字段text(見6),7處的代碼讓Django不要為字段text生成標簽。
(2)URL模式:new_topic
- 用戶要添加主題時,我們要切換到http://127.0.0.1:8000/new_topic/,下面將new_topic的URL模式添加到learning_logs\urls.py:
#添加新主題頁面 url(r'^new_topic/$',views.new_topic,name='new_topic'),
(3)修改視圖views.py:添加函數new_topic()
- 函數new_topic()需要處理兩種情形:剛進入new_topic網頁,應顯示一個空表單;對提交的表單數據進行處理,並將用戶重定向到網頁topics。
1 from django.shortcuts import render 2 from django.http.response import HttpResponseRedirect 3 from django.urls import reverse 4 from .models import Topic 5 from .forms import TopicForm 6 7 def new_topic(request): 8 '''添加新主題''' 9 if request.method != 'POST': 10 #未提交數據,創建一個新表單 11 form = TopicForm() 12 else: 13 #POST提交的數據,對數據進行處理 14 form = TopicForm(request.POST) 15 if form.is_valid(): 16 form.save() 17 return HttpResponseRedirect(reverse('learning_logs:topics')) 18 context = {'form':form} 19 return render(request,r'learning_logs\new_topic.html',context)
- 導入HttpResposeRedirect類,用戶提交主題后,我們將使用這個類將用戶重定向到網頁topics;
- 函數reverse()根據指定的URL模型確定URL,這意味着Django將在頁面被請求時生成URL;
- 記得導入剛才創建的空表單TopicForm()
(4)GET請求和POST請求
- 創建WEB應用時,我們將用到兩種請求類型是GET請求和POST請求。對於只是從服務器讀取數據的頁面,使用GET請求;在用戶需要表單提交信息時,通常使用POST請求。函數new_topic( )將請求對象作為參數,用戶初次請求該網頁時,瀏覽器將發送GET請求;用戶填寫並提交表單時,其瀏覽器將發送POST請求;根據請求類型,我們可以確定用戶請求的是空表單(GET請求)還是要求對填寫好的表單進行處理(POST請求)。
- 這里我們使用request.method判斷請求類型,如果不是POST請求,我們就返回一個空表單(即便請求是其他類型的,返回一個空表單也不會有問題),我們創建一個TopicForm實例,將其存儲在變量form中,再通過上下文字典將這個表單發送給模板,由於我們實例化TopicForm時沒有指定任何實參,Django將創建一個可供用戶填寫的空表單。
- 如果請求是POST類型,對提交的表單數據進行處理,我們使用用戶輸入的數據(它們存儲在request.POST中)創建一個TopicForm實例,這樣對象form將包含用戶提交的信息。
- 要將提交的數據保存到數據庫,必須確定他們是有效的。函數is_valid()核實用戶填寫了所有必不可少的字段(表單默認字段都是必不可少的),且輸入的數據與要求的字段類型一致(例如,字段text少於200個字符,這是我們在models.py中指定的),如果所有字段都有效,我們就可以保存數據。
- 函數reverse( )獲取頁面topics的URL,並將其傳遞給HttpResposeRedirect(),函數將用戶的瀏覽器重定向到頁面topics,在topics頁面中,用戶將在主題列表中看到剛才輸入的主題。
(5)模板:new_topic.html
1 {% extends 'learning_logs\base.html' %} 2 {% block content %} 3 <p>Add a new topic:</p> 4 <form action="{% url 'learning_logs:new_topic' %}" method="post"> 5 {% csrf_token %} 6 {{ form.as_p }} 7 <button name="submit">提交</button> 8 </form> 9 {% endblock content %}
- 4處的action告訴服務器將提交的表單數據發送到哪里,這里我們將它發送給視圖函數new_topic( ),實參method讓瀏覽器以POST請求的方式提交數據。
- 模板標簽{% csrf_token %}用來防止攻擊者利用表單來獲取對服務器未經授權的訪問,這種攻擊被稱為跨站請求偽造。
- {{ form.as_p }}用來顯示表單,修飾符as_p讓Django以段落格式渲染所有表單元素,這是一種整潔的顯示表單的簡單方式。
- 7處的代碼為表單創建提交按鈕。
(6)鏈接到頁面new_topic
- 在topics.html中添加一個到new_topic的鏈接
{% extends 'learning_logs\base.html' %} {% block content %} <p>Topics</p> <ul> --snip-- </ul> <a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a> {% endblock content %}
2、為主題添加新條目
(1)用於添加新條目的表單
1 from django import forms 2 from .models import Topic,Entry 3 class TopicForm(forms.ModelForm): 4 --snip-- 5 class EntryForm(forms.ModelForm): 6 class Meta: 7 model = Entry 8 fields = {'text'} 9 labels = {'text':''} 10 widgets = {'text':forms.Textarea(attrs={'cols':80})}
- 小部件widget是一個HTML表單元素,如單行文本框、多行文本區域或下拉列表。通過設置屬性widgets,可覆蓋Django選擇的默認小部件。form.Textarea( )制定了字段text的輸入小部件,將文本區域的寬度設置為80列,而不是默認的40列,cols代表列數。
(2)URL模式new_entry:需要包含實參topic_id,因為條目必須與特定的主題相關聯。
#添加新條目的頁面 url(r'^new_entry/(?P<topic_id>\d+)/$',views.new_entry,name='new_entry'),
(3)視圖函數:new_entry( )
1 --snip-- 2 from .forms import TopicForm,EntryForm 3 --snip-- 4 def new_entry(request,topic_id): 5 '''添加新條目''' 6 topic = Topic.objects.get(id=topic_id) 7 if request.method != 'POST': 8 #未提交數據,創建一個新表單 9 form = EntryForm() 10 else: 11 #POST提交的數據,對數據進行處理 12 form = EntryForm(request.POST) 13 if form.is_valid(): 14 new_entry = form.save(commit=False) 15 new_entry.topic = topic 16 new_entry.save() 17 return HttpResponseRedirect(reverse('learning_logs:topic', 18 args=[topic_id])) 19 context = {'topic':topic,'form': form} 20 return render(request, r'learning_logs\new_entry.html', context)
- 調用reverse()時,需要兩個實參:需要根據它來生成URL的URL模式的名稱;列表args包含要包含在URL中的所有實參,這里只有一個實參topic_id。
(4)模板:new_entry.html
1 {% extends 'learning_logs\base.html' %} 2 {% block content %} 3 <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p> 4 <p>Add a new entry:</p> 5 <form action="{% url 'learning_logs:new_entry' topic.id %}" method="post"> 6 {% csrf_token %} 7 {{ form.as_p }} 8 <button name="submit">提交</button> 9 </form> 10 {% endblock content %}
- 3處在頁面頂端顯示了主題,讓用戶知道自己在哪個主題下添加條目;該主題也是一個鏈接,可返回到該主題的主頁面。
(5)鏈接到頁面new_entry:在顯示特定主題的頁面中添加到頁面new_entry的鏈接。
--snip-- <p>Entries:</p> <p> <a href="{% url 'learning_logs:new_entry' topic.id %}">Add new entry</a> </p> <ul> --snip--
刷新瀏覽器:
3、編輯條目
(1)URL模式:edit_entry
#用於編輯條目的頁面 url(r'^edit_entry/(?P<entry_id>\d+)/$',views.edit_entry,name='edit_entry'),
(2)視圖函數:edit_entry( )
1 --snip-- 2 from .models import Topic,Entry 3 from .forms import TopicForm,EntryForm 4 --snip-- 5 def edit_entry(request,entry_id): 6 '''編輯既有條目''' 7 entry = Entry.objects.get(id=entry_id) 8 topic = entry.topic 9 if request.method != 'POST': 10 #初次請求,使用當前條目填充表單 11 form = EntryForm(instance=entry) 12 else: 13 #POST提交的數據,對數據進行處理 14 form = EntryForm(instance=entry,data=request.POST) 15 if form.is_valid(): 16 form.save() 17 return HttpResponseRedirect(reverse('learning_logs:topic', 18 args=[topic.id])) 19 context = {'entry':entry,'topic': topic, 'form': form} 20 return render(request, r'learning_logs\edit_entry.html', context)
- 在9的if語句中,我們使用實參instance=entry創建一個EntryForm實例,這個實參讓Django創建一個表單,並使用既有條目對象中的信息填充它,用戶能看到既有條目信息,並能夠編輯它們。
- 處理POST請求時,我們傳遞實參instance=entry,data=request.POST,讓Django根據既有條目創建一個表單實例,並根據request.POST中的相關數據對其進行修改。
(3)模板edit_entry.html
{% extends 'learning_logs\base.html' %} {% block content %} <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p> <p>Edit entry:</p> <form action="{% url 'learning_logs:edit_entry' entry.id %}" method="post"> {% csrf_token %} {{ form.as_p }} <button name="submit">保存</button> </form> {% endblock content %}
(4)鏈接到頁面:在顯示特定主題的頁面中添加到頁面edit_entry的鏈接。
--snip-- {% for entry in entries %} <li> <p>{{ entry.date_added|date:'M d,Y H:i' }}</p> <p>{{ entry.text|linebreaks }}</p> <p> <a href="{% url 'learning_logs:edit_entry' entry.id %}">Edit entry</a> </p> </li> --snip--
七、用戶賬戶
1、應用程序users
(1)使用命令startapp 來創建一個名為users的應用程序:
(2)將應用程序users添加到settings.py中
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'learning_logs' 'users' ]
(3)修改項目根目錄learning_log中的urls.py,添加應用程序users的URL
path(r'^users/',include('users.urls', namespace='users')),
2、登陸頁面
(1)在users中新建urls.py,使用默認登陸視圖,編輯如下:
1 '''為應用程序users定義URL模式''' 2 from django.conf.urls import include,url 3 from django.contrib.auth.views import LoginView 4 from . import views 5 app_name = 'users' 6 urlpatterns = [ 7 #登陸頁面 8 url(r'^login/$', LoginView.as_view(template_name = 'users\login.html'), 9 name='login'), 10 ]
(2)模板login.html:在users文件夾下新建一個templates目錄,並在其中新建users文件夾,在其中新建login.html
1 {% extends 'learning_logs\base.html' %} 2 {% block content %} 3 {% if form.errors %} 4 <p>用戶名或密碼錯誤!</p> 5 {% endif %} 6 <form method="post" action="{% url 'users:login' %}"> 7 {% csrf_token %} 8 {{ form.as_p }} 9 <button name="submit">登陸</button> 10 <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/> 11 </form> 12 {% endblock content %}
- 這個模板繼承了base.html,確保登陸界面的外觀和網站的其他頁面相同。
- 我們要讓登陸視圖處理表單,因此將實參action設置為登陸頁面的URL。
- 在10處,我們隱藏了一個表單元素next,其中的實參value告訴Django在用戶登陸成功后將其重新定位到什么地方,這里是主頁。
(3)鏈接到登陸頁面
- 修改文件夾learning_log\learning_logs\templates\learning_logs中的base.html,添加到登陸頁面的鏈接,讓所有頁面都包含它,用戶已登陸時,我們不想顯示這個鏈接,因此將它嵌套在一個{% if %}標簽中。
1 <p> 2 <a href="{% url 'learning_logs:index' %}">Learning Log</a> - 3 <a href="{% url 'learning_logs:topics' %}">Topics</a> - 4 {% if user.is_authenticated %} 5 Hello,{{ user.username }}. 6 {% else %} 7 <a href="{% url 'users:login' %}">登陸</a> 8 {% endif %} 9 </p> 10 {% block content %}{% endblock content %}
- 在Django身份驗證系統中,每個模板都可以使用變量user,這個變量有一個屬性is_authenticated:如果用戶已登錄,該屬性將為True,則顯示登陸成功,否則就顯示一個登陸界面的鏈接。
先登陸http://127.0.0.1:8000/admin/,這里是以管理員身份登陸的,先注銷
- 重新登陸http://127.0.0.1:8000
- 點擊登陸
3、注銷頁面
(1)URL模式
#注銷頁面 url(r'^logout/$', views.logout_view,name='logout'),
(2)視圖logout_view
from django.http.response import HttpResponseRedirect from django.urls import reverse from django.contrib.auth import logout def logout_view(request): '''注銷用戶''' logout(request) return HttpResponseRedirect(reverse('learning_logs:index'))
(3)鏈接到注銷視圖:在base.html中添加注銷鏈接
--snip-- {% if user.is_authenticated %} Hello,{{ user.username }}. <a href="{% url 'users:logout' %}">注銷</a> {% else %} <a href="{% url 'users:login' %}">登陸</a> {% endif %} --snip--
4、注冊頁面
(1)注冊頁面的URL模式
#注冊頁面 url(r'^register/$', views.register,name='register'),
(2)視圖函數register( )
1 from django.shortcuts import render 2 from django.urls import reverse 3 from django.http.response import HttpResponseRedirect 4 from django.contrib.auth import logout,login,authenticate 5 from django.contrib.auth.forms import UserCreationForm 6 --snip-- 7 def register(request): 8 '''注冊新用戶''' 9 if request.method != 'POST': 10 #顯示空的注冊表單 11 form = UserCreationForm() 12 else: 13 #處理填好的注冊表單 14 form = UserCreationForm(data=request.POST) 15 if form.is_valid(): 16 new_user = form.save() 17 #讓用戶自動登陸,再重定向到主頁 18 authenticated_user = authenticate(username=new_user.username, 19 password=request.POST['password']) 20 login(request,authenticated_user) 21 return HttpResponseRedirect(reverse('learning_logs:index')) 22 context = {'form':form} 23 return render(request, r'users\register.html', context)
(3)注冊模板
{% extends 'learning_logs\base.html' %} {% block content %} {% if form.errors %} <p>用戶名或密碼錯誤!</p> {% endif %} <form method="post" action="{% url 'users:register' %}"> {% csrf_token %} {{ form.as_p }} <button name="submit">注冊</button> <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/> </form> {% endblock content %}
(4)鏈接到注冊頁面
--snip-- {% if user.is_authenticated %} Hello,{{ user.username }}. <a href="{% url 'users:logout' %}">注銷</a> {% else %} <a href="{% url 'users:register' %}">注冊</a> <a href="{% url 'users:login' %}">登陸</a> {% endif %} --snip--
八、讓用戶擁有自己的數據
1、使用@login_required限制訪問
- 我們將創建一個系統,確定各項數據所屬的用戶,再限制對頁面的訪問,讓用戶只能使用自己的數據。
- 限制對topics頁面的訪問。在learning_log\learning_logs\views.py中添加如下代碼:
1 from django.urls import reverse 2 from django.contrib.auth.decorators import login_required 3 from .models import Topic,Entry 4 --snip-- 5 @login_required 6 def topics(request): 7 '''顯示所有主題的函數''' 8 --snip--
- 導入函數login_required(),我們把該函數作為裝飾器放在顯示所有主題的函數前,每次先檢查用戶是否已經登錄,只有登錄時,Django才運行topics()。如果未登錄,就重定向到登陸頁面,為實現這種定向,需要修改settings.py,讓Django知道去哪里查找登陸頁面。在settings.py末尾添加如下代碼:
--snip-- #我的設置 LOGIN_URL = '/%5Eusers/login/'
- 先查看自己的登陸界面的URL,我的登陸界面:http://127.0.0.1:8000/%5Eusers/login/,再設置LOGIN_URL。
- 我們可以在learning_log\learning_logs\views.py文件中除了index()外的每個視圖都應用裝飾器@login_required(),如果你在未登錄狀態下,嘗試訪問這些頁面,將會被重定向到登陸頁面。
2、將數據關聯到用戶
- 我們只需要將最高層的數據關聯到用戶,這樣更底層的數據會自動關聯到用戶。
(1)修改模型Topic。打開models.py,編輯如下:
from django.db import models from django.contrib.auth.models import User class Topic(models.Model): '''用戶學習主題的類''' text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey(User) --SNIP--
(2)確定當前有哪些用戶
- 啟動一個Django shell會話,查看已經創建的用戶的ID。
(3)windows系統,ctrl+z,然后回車,退出shell會話,然后遷移數據庫
- You are trying to .....這里Django指出你試圖給既有模型Topic添加一個必不可少(不能為空)的字段,而該字段默認值為空,Django給我們提供兩個選擇,要么現在提供默認值,要么突出並在models.py中添加默認值,我選擇了第一個選項,因此讓我輸入默認值。
- 為了將既有主題全部關聯到超級用戶,就是ID為1的用戶,我輸入了用戶ID值1,Django使用這個值來遷移數據,並生成了遷移文件0003,它在模型Topic中添加字段owner。
- 現在執行遷移:
- 驗證遷移是否符合預期:
- 如果你想重置數據庫,可執行命令python manage.py flush,這樣將得到一個全新的數據庫,你必須重建超級用戶,且原來的所有數據都將丟失。
(3)只允許用戶訪問自己的主題
'''顯示所有主題的函數''' topics = Topic.objects.filter(owner=request.user).order_by('date_added')
- 用戶登錄后,request將有一個user屬性,這個屬性存儲了有關該用戶的信息,代碼讓Django只從數據庫中獲取owner屬性為當前用戶的Topic對象。
(4)保護用戶的主題
1 --snip-- 2 from django.http.response import HttpResponseRedirect,Http404 3 --snip-- 4 @login_required 5 def topic(request,topic_id): 6 '''顯示單個主題及其所有的條目''' 7 topic = Topic.objects.get(id=topic_id) 8 #確認請求的主題屬於當前用戶 9 if topic.owner != request.user: 10 raise Http404
- 導入異常Http404,如果請求的主題不屬於當前用戶,就引發該異常,讓Django返回一個404錯誤頁面。
(5)保護頁面edit_entry:禁止用戶輸入類似http://127.0.0.1:8000/edit_entry/2/這樣的URL來訪問別人的條目。
--snip-- entry = Entry.objects.get(id=entry_id) topic = entry.topic # 確認請求的主題屬於當前用戶 if topic.owner != request.user: raise Http404 --snip--
(6)將新主題關聯到當前用戶。
- 當前用於添加新主題的頁面存在問題,因為他沒有將新主題關聯到特定用戶,如果你添加新主題,會看到錯誤IntegrityError,learning_logs_topic.user_id不能為空,意思是說創建新主題時,必須指定其owner字段的值。
--snip-- if form.is_valid(): new_topic = form.save(commit=False) new_topic.owner = request.user new_topic.save() return HttpResponseRedirect(reverse('learning_logs:topics')) --snip--
- 向函數save()傳遞實參commit=False,當你通過表單獲取你的模型數據,但是需要給模型里null=False字段添加一些非表單的數據,該方法會非常有用。如果你指定commit=False,那么save方法不會理解將表單數據直接存儲到數據庫,而是給你返回一個當前對象,這時你可以添加表單以外的額外數據,再一起存儲。這里我們先將新主題的owner屬性設置為當前用戶,再對剛才定義的主題實例調用save()。
九、設置應用程序樣式
1、在活動的虛擬環境下,執行pip install django-bootstrap3
2、設置settings.py
- 我們需要讓django-bootstrap3包含jQuery,這是一個JavaScript庫,讓你能夠使用Bootstrap模板提供的一些交互元素。
--snip-- #我的應用程序 'learning_logs', 'users', #第三方應用程序 'bootstrap3' ] --snip-- #我的設置 LOGIN_URL = '/%5Eusers/login/' #django-bootstrap3的設置 BOOTSTRAP3 = { 'include_jquery':True, }
3、使用Bootstrap來設置項目‘學習筆記’的樣式
- Bootstrap基本上就是一個大型的樣式設置工具集,它還提供了大量的模板,對於初學者來說這些模板比各個樣式設置工具用起來要方便的多。要查看Bootstrap提供的模板,可訪問網站http://getbootstrap.com/,單擊Examples,找到Navbars ,這里我們將使用模板Navbar static。它提供了簡單的頂部導航條、頁面標題和和放置頁面內容的容器。模板如下:
(1)修改base.html:定義HTML頭部
1 {% load bootstrap3 %} 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="UTF-8"> 6 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 7 <meta name="viewport" content="width=device-width,initial-scale=1"> 8 <title>學習筆記</title> 9 {% bootstrap_css %} 10 {% bootstrap_javascript %} 11 </head>
- 在1處,加載了django-bootstrap3中的模板標簽集,3處將這個文件聲明為使用英語編寫的HTML文檔,HTML分為兩個主要部分:頭部(head)和主體(body),這里頭部只包含了一個標題和其他正確顯示頁面所需的信息。
- 在9處,使用了django-bootstrap3的一個自定義模板標簽,它將讓Django包含所有的Bootstrap樣式文件,10處的標簽啟用接下來可能在頁面中使用的所有交互式行為,例如可折疊的導航欄。
(2)定義導航欄
1 --snip-- 2 <body> 3 <!-- Static navbar --> 4 <nav class="navbar navbar-dafault navbar-static-top"> 5 <div class="container"> 6 <div class="navbar-header"> 7 <button type="button" class="navbar-toggle collapsed" 8 data-toggle="collapse" data-target="#navbar" 9 aria-expanded="false" aria-controls="navbar"> 10 </button> 11 <a class="navbar-brand" href="{% url 'learning_logs:index' %}"> 12 Learning Log</a> 13 </div> 14 <div id="navbar" class="navbar-collapse collapse" > 15 <ul class="nav navbar-nav"> 16 <li><a href="{% url 'learning_logs:topics' %}">Topics</a></li> 17 </ul> 18 <ul class="nav navbar-nav navbar-right"> 19 {% if user.is_authenticated %} 20 <li><a>Hello,{{ user.username }}.</a></li> 21 <li><a href="{% url 'users:logout' %}">注銷</a></li> 22 {% else %} 23 <li><a href="{% url 'users:register' %}">注冊</a></li> 24 <li><a href="{% url 'users:login' %}">登陸</a></li> 25 {% endif %} 26 </ul> 27 </div><!--/.nav-collapse --> 28 </div> 29 </nav>
- 主體包括用戶在頁面上看到的內容。4處是一個<nav>元素,表示頁面的導航鏈接部分,對於這個元素內的所有內容,都將根據選擇器(selector)navbar、navbar-dafault和 navbar-static-top定義的Bootstrap樣式規則來設置樣式,選擇器決定了特定樣式規則將應用於頁面上的哪些元素。
- 在7處,模板定義了一個按鈕,它將在瀏覽器窗口太窄、無法水平顯示整個導航欄時顯示出來,如果用戶單擊這個按鈕,將出現一個下拉列表,其中包括所有的導航元素,在用戶縮小瀏覽器窗口或在較小屏幕上顯示網站時,collapse將會使導航欄折疊起來。
- 在11處,在導航欄的最左邊顯示項目名,並將其設置為到主頁的鏈接,因為它將出現在這個項目的每個頁面中。
- 在14處,定義了一組能讓用戶在網站中導航的鏈接。導航欄其實就是一個以<ul>打頭的列表,其中每個鏈接都是一個列表項(<li>)。要插入更多的鏈接,可插入更多使用下面結構的行:
<li><a href="{% url 'learning_logs:topics' %}">Title</a></li>
- 在18處我們插入了另一個導航鏈接列表,這里使用的選擇器為navbar-right設置一組鏈接的樣式,使其出現在導航欄右邊——登陸鏈接和注冊鏈接通常出現在這里,
(3)定義頁面的主要部分
1 --snip-- 2 </nav> 3 <div class="container"> 4 <div class="page-header"> 5 {% block header %}{% endblock header %} 6 </div> 7 <div> 8 {% block content %}{% endblock content %} 9 </div> 10 </div><!--/.nav-collapse --> 11 </body> 12 </html>
- 3處是一個div起始標簽,其class屬性為container。div是網頁的一部分,可用於任何目的,並可通過邊框、元素周圍的空間(外邊距)、內容和邊框之間的邊距(內邊距)、背景色和其他樣式規則來設置其樣式。這個div是一個容器,其中包含連個元素:header的塊和content塊,header塊的內容告訴用戶頁面包含哪些信息以及用戶可在頁面上執行哪些操作;其class屬性值page-header將一系列樣式應用於這個塊;content塊是一個獨立的div,未使用class屬性指定樣式。
- 重新加載學習筆記主頁:
4、使用jumbotron設置主頁的樣式
- 下面來使用新定義的header塊以及另個名為jumbotron的Bootstrap元素修改主頁。jumbotron元素是一個大框,相比於頁面的其他部分顯的鶴立雞群,你想在其中包含什么東西都可以;它通常用於在主頁中呈現項目的簡要描述,還可以修改主頁顯示的消息,index.html代碼修改如下:
1 {% extends 'learning_logs\base.html' %} 2 {% block header %} 3 <div class="jumbotron"> 4 <h1>Track your learning.</h1> 5 </div> 6 {% endblock header %} 7 {% block content %} 8 <h2> 9 <a href="{% url 'users:register' %}">Register an account</a>to make 10 your own Learning Log,and list the topics you're learning about. 11 </h2> 12 <h2> 13 Whenever you learn something new about a topic,make an entry 14 summarizing what you've learned. 15 </h2> 16 {% endblock content %}
- 在2處,定義header包含的內容,在一個jumbotron元素中(見3),放置一條簡短的標語Track your learning,讓首次訪問者大致知道‘學習筆記’是做什么的。
- 在7和16之間,通過添加一些文本,做了更詳細的說明。邀請用戶建立賬戶,並描述了用戶可以進行的兩種操作:添加新主題以及在主題中添加條目。刷新瀏覽器看到下面的頁面:
5、設置登錄界面的樣式
- 修改login.html
1 {% extends 'learning_logs\base.html' %} 2 {% load bootstrap3 %} 3 {% block header %} 4 <h2>Log in to your account.</h2> 5 {% endblock header %} 6 {% block content %} 7 <form method="post" action="{% url 'users:login' %}" class="form"> 8 {% csrf_token %} 9 {% bootstrap_form form %} 10 {% buttons %} 11 <button name="submit" class="btn btn-primary">登錄</button> 12 {% endbuttons %} 13 <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/> 14 </form> 15 {% endblock content %}
- 在2處首先導入了bootstrap3模板標簽,3處定義了header塊,描述了這個頁面是做什么的,我刪除了{% if form.errors %}代碼塊,因為django-bootstrap3會自動管理表單錯誤。
- 7處添加了class="form",然后使用模板標簽{% bootstrap_form form %}來顯示表單,這個標簽替換了之前使用的{{ form.as_p }};模板標簽{% bootstrap_form form %}將Bootstrap樣式規則應用於各個表單元素,10處是bootstrap3起始標簽模板{% buttons %},它將Bootstrap樣式應用於按鈕。現在打開登陸頁面:
6、設置new_topic頁面的樣式
- 修改new_topic.html
1 {% extends 'learning_logs\base.html' %} 2 {% load bootstrap3 %} 3 {% block header %} 4 <h2>Add a new topic.</h2> 5 {% endblock header %} 6 7 {% block content %} 8 <p>Add a new topic:</p> 9 <form action="{% url 'learning_logs:new_topic' %}" method="post" 10 class="form"> 11 {% csrf_token %} 12 {% bootstrap_form form %} 13 {% buttons %} 14 <button name="submit" class="btn btn-primary">add topic</button> 15 {% endbuttons %} 16 </form> 17 {% endblock content %}
7、設置topics頁面的樣式
- 修改topics.html
1 {% extends 'learning_logs\base.html' %} 2 {% block header %} 3 <h1>Topics.</h1> 4 {% endblock header %} 5 {% block content %} 6 <ul> 7 {% for topic in topics %} 8 <li> 9 <h3> 10 <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a> 11 </h3> 12 </li> 13 {% empty %} 14 <li>No topics have been added yet.</li> 15 {% endfor %} 16 </ul> 17 <h3><a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a></h3> 18 {% endblock content %}
- 為設置每個主題的樣式,我將它們都設置為<h3>元素,讓他們在頁面上顯的大些;在顯示主題的頁面,我們依然對顯示添加新主題的鏈接做了同樣的處理(見17)。
8、設置topic頁面的樣式
- 修改topic.html,topic頁面包含的內容比其他大部分頁面都要多,因此要做的樣式設置工作也多。我將使用Bootstrap面板(panel)來突出每個條目。面板是一個帶預定義樣式的div,非常適合用於顯示主題的條目:
1 {% extends 'learning_logs\base.html' %} 2 {% block header %} 3 <h2>{{ topic }}</h2> 4 {% endblock header %} 5 {% block content %} 6 <p> 7 <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a> 8 </p> 9 {% for entry in entries %} 10 <div class="panel panel-default"> 11 <div class="panel-heading"> 12 <h3> 13 {{ entry.date_added|date:'M d,Y H:i' }} 14 <small> 15 <a href="{% url 'learning_logs:edit_entry' entry.id %}"> 16 edit entry</a> 17 </small> 18 </h3> 19 </div> 20 <div class="panel-body"> 21 {{ entry.text|linebreaks }} 22 </div> 23 </div><!-- panel --> 24 {% empty %} 25 There are no entries for this topic yet. 26 {% endfor %} 27 {% endblock content %}
- 刷新瀏覽器:
注意:要使用其他Bootstrap模板,可采用類似的流程:將這個模板復制到base.html中,並修改包含實際內容的元素,以使用該模板來顯示項目的信息,然后,使用Bootstrap的樣式設置工具來設置這個各個頁面中內容的樣式。
十、部署“學習筆記”
1、建立Heroku賬戶
- 訪問https://www.heroku.com/,注冊賬戶,免費使用服務有一些限制,比如可部署的應用程序數量和用戶訪問應用程序的頻率。
- 注冊時需要使用代理才能顯示下面的驗證碼,郵箱要使用外國郵箱,如@gmail注冊之后需要到郵箱驗證,才能設置密碼
2、安裝Heroku Toolbelt
https://devcenter.heroku.com/articles/heroku-cli#download-and-install
3、安裝必要的包
1 (ll_env) E:\Python\learning_log>pip install dj-database-url 2 (ll_env) E:\Python\learning_log>pip install dj-static 3 (ll_env) E:\Python\learning_log>pip install static3 4 (ll_env) E:\Python\learning_log>pip install gunicorn
- 第一個包幫助Django與Heroku使用的數據庫進行通信,第二個和第三個幫助Django正確的管理靜態文件,第四個是一個服務器軟件,能夠在在線環境中支持應用程序提供的服務。(靜態文件包括樣式規則和JavaScript文件)
4、創建包含包列表的文件requirements.txt
- Heroku需要知道我們的項目依賴於哪些包,因此需要使用pip來生成一個文件,其中包含了這些包。
(ll_env) E:\Python\learning_log>pip freeze > requirements.txt
- freeze讓項目中當前安裝的所有包都的名稱都寫入到文件requirements.txt中。
- 我們部署“學習筆記”時,heroku會安裝上述所有的包,從而創建一個環境,其中包含我們在本地使用的所有包。
- 我們需要在txt中添加一個包名稱:psycopg2>=2.7.5,>表示如果有更高版本,heroku會自動下載高版本,它幫助heroku管理活動數據庫。
5、指定Python版本
- 在虛擬環境中輸入python,查看當前的默認Python版本
- 然后在manage.py所在文件夾,新建一個runtime.txt的文件,寫入python-3.7.1
6、修改settings.py,在文件末尾添加一段:
1 #Heroku設置 2 if os.getcwd() == '/app': 3 import dj_database_url 4 DATABASES = { 5 'default':dj_database_url.config(default='postgress://localhost') 6 } 7 #讓request.is_secure()承認X-FORWARDED_PROTO頭 8 SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 9 #支持所有的主機頭(host header) 10 ALLOWED_HOSTS = ['*'] 11 # 靜態資產配置 12 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 13 STATIC_ROOT = 'staticfiles' 14 STATICFILES_DIRS = ( 15 os.path.join(BASE_DIR,'static'), 16 )
- 2處使用os.getcwd()獲取當前的工作目錄(當前運行的文件所在的目錄)。在Heroku部署中,這個目錄總是'/app',這個if語句確保僅當部署到heroku時才運行這個代碼塊。這種結構讓我們能夠將同一設置文件用於本地開發環境和在線服務器。
- 3處導入dj_database_url,用於配置服務器。Heroku使用PostgreSQL(也叫Postgres)——一種比SQLite更高級的數據庫。這些設置對項目進行配置,使其在Heroku上使用PostgreSQL數據庫;其他設置分別如下:支持HTTPS請求;讓Django能夠使用Heroku的URL來提供項目提供的服務;設置項目,使其能夠在Heroku上正確的提供靜態文件。
7、創建啟動進程的Procfile
- Procfile告訴Heroku該啟動哪些進程。這個文件只有一行內容,將其命名為Procfile(其中P為大寫),不指定文件擴展名,並保存到manage.py所在文件夾。
web:gunicorn learning_log.wsgi --log-file -
- 這行代碼讓Heroku將gunicorn用作服務器,並使用learning_log\wsgi.py文件中的設置來啟動應用程序,標志log-file告訴Heroku應將哪些類型的事件寫入日志。
8、修改文件wsgi.py
1 --snip-- 2 import os 3 4 from django.core.wsgi import get_wsgi_application 5 from dj_static import Cling 6 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'learning_log.settings') 7 application = Cling(get_wsgi_application())
- 這里導入了幫助正確的提供靜態文件的Cling,並使用它來啟動應用程序。這些代碼在本地也適用,因此無需將其放在if語句中。
9、創建用於存儲靜態文件的目錄
- 在Heroku上,Django搜集所有的靜態文件,並將他們放在一個地方,以便高效的管理它們。在learning_log\learning_log文件夾中新建一個名為static的文件夾,並在其中創建一個占位文件,因為項目被推送到Heroku時,它將不會包含原來為空的文件。在learning_log\learning_log\static中新建一個名為placeholder.text的文件,里面隨便添加幾句話,例如說明添加這個文件的原因:
10、在本地使用gunicorn服務器
- 如果你使用的是Linux或OS系統,可在部署到Heroku之前,在本地嘗試使用gunicorn服務器,在活動的虛擬環境中,執行命令heroku local以啟動Procfile指定的進程;
為停止進程,請按crtl+c;gunicorn不能再Windows上運行,直接跳過這一步。
11、使用Git跟蹤項目文件
- Git是一個版本控制程序,每次成功實現新功能后都拍攝項目代碼的快照,無論出現了什么問題,你都可以輕松的恢復到最后一個可行的快照,每個快照都被稱為提交。
(1)查看Git版本
(2)配置Git
- Git跟蹤誰修改了項目,即便又一個人開發項目時也是如此。為進行跟蹤,Git需要知道你的用戶名和郵箱。由於練習,可以偽造一個:
(3)忽略文件
- 無需跟蹤沒一個文件,因此需要忽略一些文件。在manage.py所在文件夾新建名為.gitignore的文件。文件以點打頭,不包含擴展名。方法如下:在文件夾中右鍵打開Git bash here,然后輸入touch .gitignore
- 用記事本打開寫入一下內容:
ll_env\ __pycache__\ *.sqlite3
- 讓Git忽略ll_env,因為我們隨時可以自動重新創建它;__pycache__包含Django運行.py文件時自動創建的.pyc文件;不跟蹤對本地數據庫的修改,因為如果你在本地服務器上使用的是SQLite,當你推送到服務器上時,可能會不小心用本地測試數據庫覆蓋在線數據庫。
(4)提交項目
- 首先修改配置,全局修改禁止自動轉換CRLF(Windows換行符),因為在Linux系統里換行符為LF,不做這一步,執行git add .之后會報錯。
- 我們需要為學習筆記初始化一個Git倉庫,輸入git init
- 給倉庫添加文件,輸入git add . 不要忘記最后面的句點,這一步需要執行個一兩分鍾,耐心等待。
- git commit -am commit message,其中的標志-a讓Git在提交中包含所有修改過的文件,-m讓Git記錄一條日志消息。耐心等待。
- git status ,輸出表明結果表明當前位於分支master中,而工作目錄是干凈的(clean)。
12、推送到Heroku
- 前面的一切終於為項目推送做好了准備,在活動的虛擬環境中依次執行如下命令:
- heroku login:登陸heroku
- heroku create:讓heroku創建一個空倉庫,heroku生成的項目名由兩個單詞和一個數字組成
- git push heroku master:它讓Git將項目的分支master推送到heroku剛才創建的倉庫中,這里列出了用於訪問這個項目的URL。
- 核實是否正確啟動服務器進程,執行命令heroku ps
- 使用heroku open在瀏覽器中打開這個應用程序,你將看到學習筆記的主頁,但是你還無法使用,因為還沒有建立數據庫。
13、在Heroku上建立數據庫
- 為建立在線數據庫,在活動的虛擬環境中執行命令:heroku run python manage.py migrate
14、改進Heroku部署
(1)在Heroku上創建超級用戶,輸入命令:heroku run bash打開Bash終端會話,使用Bash終端創建超級用戶,以便能夠訪問在線應用程序的管理網站。輸入命令python manage.py createsuperuser,和之前一樣輸入相關信息,然后exit返回到本地系統的終端會話。現在你可以在應用程序的URL末尾添加/admin/來登陸管理網站了。如果已經有其他用戶在使用這個項目,你可以訪問他們的所有數據。
(2)在heroku上創建對用戶有好的URL。使用命令來重命名應用程序:heroku apps:rename charliedaifu-learning-log
15、確保項目的安全
- 這個項目存在一個嚴重的安全問題:settings.py包含設置DEBUG=True,它在發生錯誤時顯示調試信息,如果項目部署到服務器上之后依然保留這個設置,將給攻擊者提供大量的可利用信息,我們需確保任何人都無法看到這些信息,也不能冒充項目托管網站來重定向請求。下面來修改settings.py:
#讓request.is_secure()承認X-FORWARDED_PROTO頭 SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') #只允許heroku托管這個項目 ALLOWED_HOSTS = ['charliedaifu-learning-log.herokuapp.ocm'] DEBUG = False # 靜態資產配置
16、提交並推送修改
(1)現將對settings.py所做的修改提交到倉庫
(2)將修改后的倉庫提交到Heroku:git push heroku master
17、創建自定義錯誤頁面
- 404錯誤通常意味着你的Django代碼是正確的,但請求的對象不存在;500錯誤代表你的代碼有問題。當前,這兩種情況下,都返回通用的錯誤頁面,我們可以編寫外觀與學習筆記一致的404和500錯誤頁面模板,必須放在根模板目錄中。
(1)創建自定義模板
- 在learning_log\learning_log文件夾中新建一個文件夾名叫templates,在其中新建一個名為404.html的文件,在其中輸入如下內容:
{% extends 'learning_logs\base.html' %} {% block header %} <h2>The item you requested is not availible.</h2> {% endblock header %}
- 新建505.html
{% extends 'learning_logs\base.html' %} {% block header %} <h2>There has been an internal error.</h2> {% endblock header %}
- 修改settings.py
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR,r'learning_log\templates')], 'APP_DIRS': True,
--snip--
(2)在本地查看錯誤頁面,將ALLOWED_HOSTS指定一個主機,請求一個不屬於你的主題,查看錯誤頁面,請求保存在的URL,查看500錯誤頁面。確定你修改的是本地環境的setting部分,而不是Heroku部分,查看過后,將DEBUG重新設置為True,以便進一步開發。
SECRET_KEY = '9(m*h6v7jouxq8a526y*r@4fkdor-381!(tm0@0#oy)ryod=b6' # SECURITY WARNING: don't run with debug turned on in production! #安全警告:不要在在線環境中啟用調試 DEBUG = False ALLOWED_HOSTS = ['localhost']
(3)將修改推送到Heroku
- git add .
- git commit -am "Added custom 404 and 500 error pages."
- git push heroku master
(4)使用方法get_object_or_404()
該函數嘗試從數據庫獲取請求的對象,如果這個對象不存在,就引發404異常,我們在view.py中導入這個函數,並用它替換函數get():
topic = get_object_or_404(Topic,id=topic_id)
18、將項目從Heroku刪除
第一種方法:登陸網站https://heroku.com/,點擊你的項目名稱,向下滾動,點擊delete app,系統會提示你輸入完整的項目名稱。
第二種方法:在活動的虛擬環境中輸入命令:heroku apps:destroy --app appname,其中appname是要刪除的項目名。