《Python編程:從入門到實踐》筆記。html
從本篇開始將是該書的最後一個項目,將用3篇文章來介紹Django的基礎。完成一個「學習筆記」的小網站。python
在本篇中,咱們將:linux
不過在開始以前,請先新建一個虛擬環境並安裝Django。若是沒有虛擬環境,經過pip
安裝的全部庫都會保存到python的site-packages
目錄下。開發多個項目時,都會用同一個python,而某些項目並不須要其中的全部第三方庫,但若是將這些不須要庫的刪除,又會影響到其餘項目。並且,若是A項目須要Django2.0.4,B項目須要Django2.0.0,這該怎麼辦?此時就須要虛擬環境。它其實就至關於一個新的文件夾,將新項目與其餘項目隔離,本項目的庫不與其餘項目的庫相關聯,相似於操做系統的多用戶概念。程序員
若是使用的是Python 3,可使用命令:web
python -m venv ll_env
複製代碼
若是該命令不成功,多是沒有安裝virtualenv
模塊:正則表達式
pip install virtualenv
複製代碼
而後建立並激活虛擬環境:sql
virtualenv ll_env
# linux:
source ll_env/bin/activate
# windows:
ll_env\Scripts\activate
# 停用:
deactivate
複製代碼
管理虛擬環境的庫還有不少,有興趣的話能夠到網上搜一搜。shell
若是你使用的是新版的PyCharm,那麼它在新建項目的時候默認就會建立新的虛擬環境。數據庫
激活虛擬環境後就能夠安裝Django了:django
pip install django
複製代碼
在虛擬環境中執行以下命令:
# 主要最後有個實心句號!
# 這個句點讓新項目使用合適的目錄結構,這樣開發完成後能夠輕鬆地將應用程序部署到服務器
django-admin startproject learning_log .
複製代碼
執行上述命令後,將多出一個manage.py
文件和一個learning_log
文件夾,固然還有自己的一個ll_env
文件夾。
而在learning_log
文件夾中,又有四個文件:__init__.py
,settings.py
,urls.py
,wsgi.py
。
manage.py
是一個簡單的程序,它接收命令並將其交給Django的相關部分去運行;settings.py
指定Django如何與你的系統交互以及如何管理項目,其實就是配置文件;urls.py
告訴Django應建立哪些網頁來響應瀏覽器請求;wsgi.py
是web server gateway interface(Web服務器網關接口)的縮寫,幫助Django提供它建立的文件。至於__init__.py
,它是個空文件,Python的每一個模塊下必需要有這個文件。
Django將大部分與項目相關的信息都存儲在數據庫中,全部還須要建立一個供Django使用的數據庫。依然是在虛擬環境下執行以下命令:
python manage.py migrate
複製代碼
在PyCharm中的話,能夠經過點擊工具欄Tools中的Run manage.py Task(Ctrl+Alt+R),在彈出的命令行中直接輸入原命令中manage.py
後面的部分,後面的命令也能夠這樣執行([appname]
是自動提示)。
"migrate"這個單詞實際上是遷移的意思,並非「建立(create)」。之因此使用這個詞,是由於通常將修改數據庫的過程稱爲遷移數據庫(筆者數據庫學得渣,這段解釋直接從書裏照搬的,但願哪位大神在留言區解釋一波)。若是是剛建立的項目,而且第一次執行,將會獲得以下輸出:
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
-- snip --
Applying sessions.0001_initial... OK
複製代碼
從第2行結果能夠看出,Django將建立和修改數據庫看作是對數據庫的遷移,Apply all migrations確保數據庫結構與當前代碼匹配(好比你修改了類的結構,添加了屬性,這就至關於修改了數據表)。
執行命令後,項目的根目錄下會多出一個名爲db.sqlite3
的數據庫文件。SQLite是一種使用單個文件的輕量級數據庫,經常使用於開發簡單應用程序,它讓你不用太關注數據庫管理的問題。
依然在項目的虛擬環境下輸入以下命令:
python manage.py runserver
複製代碼
獲得以下輸出:
Performing system checks...
System check identified no issues (0 silenced).
April 21, 2018 - 20:46:48
Django version 2.0.4, using settings 'learning_log.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
複製代碼
如今在瀏覽器中地址欄輸入localhost:8000
(或者127.0.0.1:8000
),將獲得以下頁面:
這是最新版的Django的默認啓動界面。
Django項目由一系列應用程序組成,它們協同工做,讓項目成爲一個總體。咱們在項目根目錄下執行以下命令,建立一個名爲learning_logs的應用程序:
python manage.py startapp learning_logs
複製代碼
執行命令後,根目錄下會多出一個名爲learning_logs
的文件夾(筆者第一次接觸Django的時候發現這玩意兒竟然叫作APP,和平時用的手機上的各類APP相差也太大了,很不適應),它的結構以下:
重要的是其中的models.py
、admin.py
和views.py
文件,咱們將使用models.py
來定義咱們要在應用程序中管理的數據。另外兩個文件稍後再介紹。
打開models.py
文件,發現其中自帶兩行代碼:
from django.db import models
# Create your models here.
複製代碼
在代碼層面,模型就是一個類,和以前的文章中的類同樣,包含屬性和方法。下面建立一個「主題」類:
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
複製代碼
Model
類是Django中的一個定義了模型基本功能的類。Topic
類只有兩個屬性text
和date_added
。模型中如CharField
,DateTimeField
這些字段還有不少,Django自動根據數據庫的不一樣調用不一樣的SQL語句建立數據表,即屏蔽底層數據庫的差別。
同時還重寫了__str__()
方法,之因此說「重寫」是由於每一個類都有這個方法,當直接將一個類A
放入print()
之類的語句中時,就會調用A
的__str__()
方法。若是沒有重寫這個方法,通常會輸出這個對象的內存地址之類的,你們能夠蘇隨便寫個類試一試。
使用模型前,必須將APP包含到項目中,打開settings.py
文件,將APP添加到INSTALLED_APPS
中:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 添加你的app,上面的都是自帶的
"learning_logs.apps.LearningLogsConfig",
]
複製代碼
這裏有個須要注意的地方,因爲這本書的2016年出版的,當時Django尚未到達2.0版本,因此在書中,註冊APP是這樣寫的:
INSTALLED_APPS = [
-- snip --
# 添加你的app,上面的都是自帶的
"learning_logs",
]
複製代碼
如今官方文檔中是按照第一種方式註冊APP,而且,最新版的Django在新建APP後,在APP的目錄下還多了一個apps.py
文件,該文件默認有一個根據APP名稱建立的類,此處爲LearningLogsConfig
,內容以下:
from django.apps import AppConfig
class LearningLogsConfig(AppConfig):
name = 'learning_logs'
複製代碼
回到主線,在終端中輸入:
python manage.py makemigrations learning_logs
# 輸出
Migrations for 'learning_logs':
learning_logs\migrations\0001_initial.py
- Create model Topic
複製代碼
makemigrations
讓Django
肯定該如何修改數據庫,使其可以存儲與咱們定義的新模型相關聯的數據。從輸出能夠看出,在APP目錄下的migrations
文件夾中建立了一個名爲0001_initial.py
的遷移文件,該文件將在數據庫中爲模型Topic
建立一個表。
最後,在命令行中輸入:
python manage.py migrate
# 輸出:
Running migrations:
Applying learning_logs.0001_initial... OK
複製代碼
總結:每當須要修改模型時,都採起以下三個步驟:修改models.py
,對你的APP調用makemigrations
,讓Django遷移項目migrate
。
Django自帶管理後臺。首先爲網站建立一個超級用戶。在中斷輸入:
python manage.py createsuperuser
複製代碼
隨後按提示輸入用戶名和密碼便可,郵箱地址能夠留空(直接回車)。
Django自動在管理網站中添加一些模型,如User
和Group
,但對於咱們建立的模型,必須手工註冊。
注意前面提到的和models.py
在同一目錄的admin.py
文件,這就是註冊自行編寫的模型的地方,在該文件中加入後兩行代碼:
# 第一行代碼是自帶的
from django.contrib import admin
from learning_logs.models import Topic
admin.site.register(Topic)
複製代碼
如今登陸Django自帶的網站管理頁面 http://localhost:8000/admin/ 登陸剛纔建立的超級用戶和密碼後將出現以下界面:
在這裏你能夠管理用戶和組,以及和模型Topic相關的數據。
如今先手動添加兩個主題:點擊Add
建立Chess
和Rock Climbing
。
爲使能在每一個主題下添加條目,須要定義Entry
模型,Entry
與Topic
的關係是多對一。一樣是在models.py
中添加模型:
from django.db import models
class Topic(models.Model):
-- snip --
class Entry(models.Model):
"""學到的有關某個主題的具體知識"""
# 因爲和「主題」是多對一的關係,因此「主題」是「條目」的外鍵
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
text = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = "entries"
def __str__(self):
"""返回模型的字符串表示"""
# 因爲條目包含的文本可能很長,只顯示前50個字符
return self.text[:50] + "..."
複製代碼
注意其中嵌套了一個Meta
類,它用於管理模型的額外信息。它讓咱們可以設置一個特殊屬性,讓Django在須要時使用Entries
來表示多個條目。若是沒有這個類,Django將使用Entrys
來表示多個條目(保證英語語法正確......不得不說,本書做者仍是很細心的)。
添加了新模型,因此須要再次遷移數據庫,過程就是前面講的三個步驟中的後兩步。而後在admin.py
中註冊Entry
。
爲了讓這個網站有一些初試數據,添加三個條目:兩個Chess
的,一個Rock Climbing
的。在管理頁面中點擊Entries
的Add
按鈕,你將看到一個下拉列表,用於選擇Topic
,還有個文本框,用於輸入內容。隨便輸入一點內容就能夠,具體內容再也不詳細列出。
輸入一些數據後,可經過交互式終端會話以編程方式查看這些數據。這種交互式環境稱爲Django shell,經常使用語測試項目和排除故障。如下是在shell中的一些操做:
(ll_env)learning_log$ python manage.py shell # 啓動shell
>>> from learning_logs.models import Topic
>>> Topic.objects.all() # 得到模型Topic的全部實例
<QuerySet [<Topic: Chess>, <Topic: Rock Climbing>]> # 返回了一個查詢集QuerySet
>>> topics = Topic.objects.all() # 查詢每一個Topic對象
>>> for topic in topics:
... print(topic.id, topic)
...
1 Chess
2 Rock Climbing
>>> t = Topic.objects.get(id=1) # 根據id查看Chess模型的具體內容
>>> t.text
'Chess'
>>> t.date_added
datetime.datetime(2018, 4, 22, 2, 4, 3, 723045, tzinfo=<UTC>)
>>> t.entry_set.all() # 查看該主題下的全部條目,經過外鍵查詢,進行了人爲換行
<QuerySet [<Entry: The opening is the first part of the game, roughly...>,
<Entry: In the opening phase of the game, it's important t...>]>
複製代碼
爲了經過外鍵獲取數據,注意查詢時的語法:模型小寫名稱+下劃線+set
,如第19行代碼。編寫用戶可請求的網頁時將使用這樣的語法。若是代碼在shell中的行爲符合預期,那麼它們在項目文件中也能正確工做。
這一部分,書中內容和新版的Django出入比較多。
Django建立網頁的過程一般分三個階段:定義URL、編寫視圖和編寫模板。
URL模式描述了URL是如何設計的,讓Django知道如何將瀏覽器請求與網站URL匹配,以肯定返回哪一個網頁。每一個URL都被映射到特定的視圖——視圖函數獲取並處理網頁所需的數據。視圖函數一般調用一個模板,後者生成瀏覽器可以理解的網頁。
目前,基礎URL(http://localhost:8000/ )返回默認的Django頁面,如今修改這個映射,將其映射到咱們本身編寫的主頁。
打開learning_log
文件夾中的urls.py
,將看到以下內容:
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
# 書中的內容和實際的內容有些出入,如下是書中內容:
# 新版Django簡化了URL路由寫法
# from django.conf.urls import include, url
# from django.contrib import admin
#
# from django.conf.urls import include, url
# from django.contrib import admin
#
# urlpatterns = [
# url(r'^admin/', include(admin.site.urls)),
# ]
複製代碼
變量urlpatterns
包含項目中的APP的URL,admin.site.urls
模塊定義了可在管理網站中請求的全部URL。如今添加代碼:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path("", include("learning_logs.urls")),
# 書中代碼:
# url(r"", include("learning_logs.urls", namespace="learning_logs")),
]
複製代碼
注意:書中在此處的include()
函數中傳入了關鍵字參數namespace="learning_logs"
,但在新版中,命名空間(namespace) 是在APP的urls.py
中設置的:在urlpatterns
變量前新建一個值爲"learning_logs"
的app_name
變量。
namespace
讓learning_logs
的URL同項目中的其餘URL區分開,對項目進行擴展時,這樣作十分有用。
還須要在learning_logs
中建立另外一個urls.py
文件:
"""定義learning_logs的URL模式"""
from django.urls import path
# 從當前的urls.py模塊所在的文件夾中導入視圖
from . import views
# 該變量包含該APP中可請求的網頁
urlpatterns = [
# 主頁
path("", views.index, name="index"),
]
複製代碼
path()
的第一個參數是正則表達式,第二個參數是要調用的視圖函數(當請求的URL和第一個參數匹配時調用),第三個參數爲這個URL模式指定一個名字,至關於將這個模式保存在變量index
中,之後每當須要提供這個主頁的鏈接時都使用這個名字,而不用再編寫URL。
視圖函數接收請求中的信息,準備好生成網頁所需的數據,再將這些數據發送給瀏覽器,在發送以前,還套用了網頁的模板(若是有模板的話)。
當咱們在建立APP時,它的文件夾中有一個views.py
文件,該文件默認只有一個導入語句,導入了函數render()
,如今編寫這個文件:
from django.shortcuts import render
def index(request):
"""學習筆記主頁"""
# 將請求的數據套用到模板中,而後返回給瀏覽器
# 第一個參數是原始請求對象,第二是可用於建立網頁的模板
return render(request, "learning_logs/index.html")
複製代碼
模板定義了網頁的結構,指定了網頁時什麼樣的。每當網頁被請求時,Django將填入相關的數據。模板中能訪問視圖提供的任何數據。
如今建立上面代碼中的index.html
模板:在learning_logs
文件夾中新建一個templates
文件夾,再在這個文件夾中新建一個和APP同名的文件夾,即learning_logs
文件夾,最後,在這個learning_logs
文件夾中新建index.html
文件。看起來好像有點多餘,但這是Django可以解析的目錄結構。index.html
文件的內容以下:
<p>Learning Log</p>
<p>Learning Log helps you keep track of your learning,
for any topic your're learning about.</p>
複製代碼
如今當你訪問http://localhost:8000 時,將看到以下主頁:
建立網頁的過程看起來可能很複雜,實際上這就是一個簡單的MVC
(Model
,View
,Controller
)模型,但在Django中被稱爲MVT
(Model
,View
,Template
)
這種方式使代碼結構清晰,修改方便,也讓咱們能各司其職,好比,數據庫專家就專一於Model
,程序員專一於View
,Web
設計人員專一於Template
。
咱們繼續擴展咱們的項目。建立兩個用於顯示數據的網頁,其中給一個列出全部的主題,另外一個顯示特定主題的全部條目。對於每一個網頁都指定URL模式,編寫一個視圖函數,並編寫一個模板。但這麼作以前,咱們先建立一個父模板,項目中的其餘模板都將繼承它。
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a>
</p>
{% block content %}{% endblock content %}
複製代碼
該文件存儲在index.html
所在的目錄中。該文件包含全部頁面都有的元素,其餘模板繼承自它。目前爲止,全部頁面共有的元素還只有頂端的標題。
咱們將這個標題設置爲到主頁的連接。爲了建立這個連接,使用了模板標籤(花括號加百分號的組合),其中learning_logs
是項目的命名空間,index就是這個項目主頁的URL模式名(注意翻看前面小節的代碼)。
還建立了一個塊標籤(block
),這個塊名爲content
,是一個佔位符,其中包含的信息將由子模板指定。
重新編寫index.html
,使其繼承base.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Learning Log helps you keep track of your learning,
for any topic you're learning about.</p>
{% endblock content %}
複製代碼
第1行代碼導入父模板中的全部內容;extends
後面跟父模板所在的路徑,雖然這倆文件都在同一目錄下,但不能省略掉前面的learning_logs/
。
3到6行代碼插入了一個名爲content
的塊標籤,不是從父模板繼承的內容都包含在content
塊中。第6行代碼指出內容定義的結束位置。
注意:在大型項目中,一般有一個用於整個網站的父模板base.html
,且網站的每一個主要部分都有一個父模板。每一個主要部分的父模板又都繼承自base.html
,而網站的每一個網頁都繼承相應部分的父模板。
該頁面顯示用戶建立的全部主題。他是第一個須要使用數據的頁面。
在APP的urls.py
中添加能轉到topics.html
的URL模式
urlpatterns = [
# 主頁
path("", views.index, name="index"),
path("topics/", views.topics, name="topics")
]
複製代碼
在views.py
中添加相應的函數:
from django.shortcuts import render
from .models import Topic
def index(request):
-- snip --
def topics(request):
"""顯示全部的主題"""
topics = Topic.objects.order_by("date_added")
# 一個上下文字典,傳遞給模板
context = {"topics": topics}
return render(request, "learning_logs/topics.html", context)
複製代碼
建立一個模板,用於顯示全部的主題:
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics</p>
<ul>
{% for topic in topics %}
<li>{{ topic }}</li>
{% empty %}
<li>No topics have been added yet.</li>
{% endfor %}
</ul>
{% endblock content %}
複製代碼
在這個模板中,咱們使用了一個至關於for
循環的模板標籤,它遍歷字典context
中的列表topics
。而且注意,該模板中沒有出現context
字樣,至關於模板自動從context
取得topics
的內容。
模板中使用的代碼與Python代碼存在一些重要差異:Python使用縮進來指出哪些代碼行是for
循環的組成部分,而在模板中,每一個for
循環都必須明確的指出結束爲止。
要在模板中打印變量,須要將變量名用雙花括號括起來,每次循環時,該代碼都會被替換爲topic
的當前值。
還使用了空模板標籤(empty),它告訴Django在列表topics
爲空時該怎麼辦。
這些花括號都不會出如今網頁中,它們只是用於告訴Django咱們使用了一個模板變量。
修改父模板base.html
,使其包含能轉到主題頁面的鏈接(第3行爲添加的代碼,第2行最後添加了一個連字符):
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a> -
<a href="{% url 'learning_logs:topics' %}">Topics</a>
</p>
複製代碼
如今再次輸入http://localhost:8000 將看到咱們添加的Topics
連接,點擊它,將跳轉到以下頁面:
該頁面用於顯示該主題下的全部條目。和上面的步驟同樣,定義URL模式,編寫views.py
中的處理函數,編寫網頁模板。
urlpatterns = [
-- snip --
path("topics/<int:topic_id>/", views.topic, name="topic"),
]
複製代碼
def topic(request, topic_id):
"""顯示單個主題及其全部的條目"""
# 經過Topic的id得到全部條目
topic = Topic.objects.get(id=topic_id)
# 前面的減號表示降序排序
entries = topic.entry_set.order_by("-date_added")
context = {"topic": topic, "entries": entries}
return render(request, "learning_logs/topic.html", context)
複製代碼
第4,6行的代碼叫作查詢,若是你和筆者同樣是個初學者,比起代碼都寫完了,最後發現查詢語句有問題,再來修改,那麼先在Django shell中運行下代碼看看結果,這樣更高效。
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topic: {{ topic }}</p>
<p>Entries:</p>
<ul>
{% for entry in entries %}
<li>
<p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
<p>{{ entry.text|linebreaks }}</p>
</li>
{% empty %}
<li>
There are no entries for this topic yet.
</li>
{% endfor %}
</ul>
{% endblock content %}
複製代碼
在Django模板中,豎線(|)表示模板過濾器(對模板變量的值進行修改的函數)。過濾器date: 'M d, Y H:i'以這樣的格式顯示時間戳:April 22, 2018 16:09。接下來的一行顯示txt的完整值,而不只僅是entry
的前50個字符。過濾器linebreaks
將包含換行符的長條目轉換成瀏覽器可以理解的格式,以避免顯示一個不換行的文本塊。
-- snip --
{% for topic in topics %}
<li>
<a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
</li>
{% empty %}
-- snip --
複製代碼
如今咱們回到http://localhost:8000/topics/ 頁面,隨便點擊一個主題,好比第一個,將獲得如下界面:
上述內容主要有:
下一篇中,咱們將:
迎你們關注個人微信公衆號"代碼港" & 我的網站 www.vpointer.net ~