(譯者註:本人目前在杭州某家互聯網公司工作,崗位是測試研發,非常喜歡python,目前已經使用Django為公司內部搭建了幾個自動化平臺,因為沒人教沒人帶,基本靠野路子自學,走過好多彎路,磕磕碰碰一路過來,前段時間偶爾看到《Django By Example》這本書,瞬間淚流滿面,當初怎麼沒有找到這 ...
(譯者註:本人目前在杭州某家互聯網公司工作,崗位是測試研發,非常喜歡python,目前已經使用Django為公司內部搭建了幾個自動化平臺,因為沒人教沒人帶,基本靠野路子自學,走過好多彎路,磕磕碰碰一路過來,前段時間偶爾看到《Django By Example》這本書,瞬間淚流滿面,當初怎麼沒有找到這麼好的Django教程。在看書的過程中不知道怎麼搞的突然產生了翻譯全書的想法,正好網上找了下也沒有漢化的版本,所以準備踏上這條不歸路。鑒於本人英文水平極低(四級都沒過),單純靠著有道詞典和自己對上下文的理解以及對書中每行代碼都保證敲一遍並運行的情況下,請各位在讀到語句不通的時候或看不懂的地方請告訴我,我會及時進行改正。翻譯全書,主要也是為了培養自己的英文閱讀水平(口語就算了),誰叫好多最新最有用的電腦文檔都是用英文寫的,另外也可以培養自己的耐心,還可以分享給其他人,就這樣。)
第一章
創建一個博客應用
在這本書中,你將學習如何創建一個完整的Django項目,併在生產環境中使用。假如你還沒有安裝Django,在這一章的第一部分你將學習如何安裝。本章會覆蓋如何使用Django去創建一個簡單的博客應用。本章的目的是使你對該框架的工作有個基本概念,懂得不同的組件之間是如何產生交互,並且給你一些簡單的技能來創建Django項目通過使用一些基本功能。你會被引導創建好一個完整的項目但是不會對所有的細節進行詳細說明。不同的框架組件將在本書以後的章節中進行介紹。
本章會覆蓋一下幾點:
- 安裝Django並創建你的第一個項目
- 設計models並且生成遷移
- 給你的models創建一個管理站點
- QuerySet和managers的操作
- 創建views,templates和URLs
- 給列表views添加頁數
- 使用Django的內置views
安裝Django
如果你已經安裝好了Django,你可以直接略過這部分跳到創建你的第一個項目。Django是python的一個包因此將在安裝在python的環境中。如果你還沒有安裝Django,這裡有一個快速的指南幫助你在本地開發環境安裝Django。
Django需要在Python2.7或者3版本上才能更好的工作。在本書的這個例子中,我們使用的將是Python 3。如果你使用LInux或者Max OSX,你可能已經安裝好了Python。如果你不確定你的電腦中是否安裝了Python,你可以在終端中輸入 python 來確定。如果你看到以下類似的提示,說明你的電腦中已經安裝好了Python:
Python 3.5.0 (v3.5.0:374f501f4567, Sep 12 2015, 11:00:19)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.>>>
如果你電腦中安裝的Python版本低於3,或者沒有安裝,下載並安裝Python 3.5.0 從http://www.python.org/download/。
由於你將使用Python3,你沒必要再安裝一個資料庫。這個Python版本自帶SQLite資料庫。SQLLite是一個輕量級的資料庫你可以在Django開發環境中使用。如果你準備在生產環境中部署你的應用,你應該使用一個更高級的資料庫,比如PostgreSQL,MySQL或Oracle。你能獲取到更多的信息關於數據和Django的結合通過訪問https://docs.djangoproject.com/en/1.8/topics/install/#database-installation 。
創建一個獨立的Python環境
強烈建議你使用virtualenv來創建一個獨立的Python環境,這樣你可以使用不同的包版本對應不同的項目,這比直接安裝Python包更加的實用。其他高級的地方是你在virtualenv中你不需要任何管理員許可權來安裝Python包。在終端中運行以下命令來安裝virtualenv:
pip install virtualenv
當你安裝好virtualenv之後,通過以下命令來創建一個獨立的環境:
virtualenv my_env
這個命令會在你的Python環境中創建一個my_env/目錄。當你的虛擬環境被激活的時候所有已經存在的Python庫都會自動帶入 my_env/lib/python3.5/site-packages dircory。
如果你的系統既有Python2.X又有Python3.X,你必須告訴虛擬環境使用哪個版本。通過以下命令你可以定位已安裝的Python3來創建虛擬環境:
zenx\$ which python3
/LibraryFrameworks/Python.framework/Versions/3.5/bin/python3
zenx\$ virtualenv my_env -p /Library/Frameworks/Python.framework/Versions/3.5/bin/python3
通過以下命令來激活你的虛擬環境:
source my_env/bin/activate
shell提示將會包含激活的虛擬環境名,像下麵一樣:
(my_evn)laptop:~ zenx$
你可以使用deactivate命令隨時停用你的虛擬環境。
你可以獲取更多的信息關於虛擬環境通過訪問https://virtualenv.pypa.io/en/latest/。
你可以使用virtualenvwrapper工具來方便的創建和管理你的虛擬環境。你可以在http://virtualenvwrapper.readthedocs.org/en/latest/ 下載該工具。
使用pip安裝Django
pip是安裝Django的第一選擇。Python3.5自帶pip,你可以找到pip的安裝指令通過訪問 https://pip.pypa.io/en/stable/installing/
運行以下命令來通過pip安裝Django:
pip install Django==1.8.6
Django將會被安裝在虛擬環境的site-packages/目錄下。
現在檢查Django是否成功安裝。在終端中運行python並且導入Django來檢查它的版本:
>>> import django
>>> django.VERSION
DjangoVERSION(1, 8, 5, 'final', 0)
如果你獲得了以上輸出,恭喜你,Django已經在你的電腦里成功安裝。
Django也可以使用別的方法來安裝。你可以找到更多的信息通過訪問https://docs.djangoproject.com/en/1.8/topics/install/。
創建你的第一個項目
我們的第一個項目是一個完整的博客站點。Django提供了一個命令允許你方便的創建一個初始化的項目文件結構。在終端中運行以下命令:
django-admin startproject mysite
該命令將會創建一個名為mysite的項目。
讓我們來看下生成的項目結構:
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py
讓我們來瞭解一下這些文件的功能:
- manage.py:一個用來操作項目的使用命令行。它是django-admin.py工具的包裝。你不需要編輯這個文件。
mysite/:你的項目目錄,由以下的文件組成:
init.py:一個空文件告訴Python這個mysite目錄是一個Python模塊。
settings.py:項目的設置和配置。裡面包含一些初始化的設置信息。
urls.py:這是你存放URL的地方。這裡定義的每一個URL都映射到一個view。
wsgi.py:配置你的項目可以通過WSGI應用運行
settings.py文件包含使用SQLite資料庫的基礎配置和一個添加到你的項目中的預設Django應用列表。我們需要為初始化的應用創建一些tables在資料庫中。
打開終端執行以下命令:cd mysite
python manage.py migrate
你將會看到以下的類似輸出:
Rendering model states... DONE
Applying contenttypes.ooo1_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length...OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying sessions.0001_initial... OK
這些初始化的應用tables將會在資料庫中創建。你馬上會學習到一些關於migrate的管理命令。
運行開發環境
Django自帶一個輕量級的web服務來快速運行你的代碼,而不需要花額外的時間來配置一個生產用的服務。當你運行Django的開發服務,它會一直檢查你的代碼變化。它會自動重啟,解放你的雙手在代碼變化後不用手動進行重啟。然而,它可能無法註意到一些操作,例如在項目中添加了一個新文件,所以你在某些情況下還是需要手動進行重啟操作。
在你的項目主目錄下運行一下代碼來啟動開發服務:
python manage.py runserver
你會看到以下類似的輸出:
Performing system checks...
System check identified no issues (0 silenced).
November 5, 2015 - 19:10:54
Django version 1.8.6, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
現在,在瀏覽器中打開 http://127.0.0.1:8000/ ,你會看到一個告訴你項目成功運行的頁面,如下圖所示:
你可以指定Django在自定義的host和port上運行開發服務,或者告訴它讀取不同的設置文件來運行你的項目。例如:你可以運行以下 manage.py命令:
python manage.py runserver 127.0.0.1:8001 --settings=mysite.settings
這個命令可以方便的處理多套環境對於不同設置的需求。記住,這個服務只是單純用來開發使用,不適合在生產環境中使用。為了在生產環境中部署Django,你需要使用真實的web服務讓它運行成一個WSGI應用例如Apache,Gunicorn或者uWSGI。你能夠獲取到更多的信息關於如何在不同的web服務中部署Django,訪問 https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/
項目設置
讓我們打開settings.py文件來看看你的項目配置內容。在文件中有許多設置是Django內置的,但這些只是所有Django可用配置的一部分。你可以看到所有的設置和預設值通過訪問 https://docs.djangoproject.com/en/1.8/ref/settings/
以下列出的設置非常值得一看:
- DEBUG 一個布爾型用來開啟或關閉項目的debug模式。如果設置為True,Django將會在你的應用拋出沒有被捕獲的異常時顯示一個詳細的錯誤頁面。當你準備部署項目到生產環境,請記住一定要關閉debug模式。永遠不要在生產環境中打開debug模式因為那會暴露你項目中的敏感數據。
- ALLOWED_HOSTS 當debug模式開啟或者運行測試的時候不會起作用。一旦你準備部署你的項目到生產環境並且關閉了debug模式,你就必須添加你的功能變數名稱或host在這個設置中來允許訪問你的Django站點。
- INSTALLED_APPS 這個設置你將會在所有的項目中都進行筆記。這個設置告訴Django有哪些應用會激活在這個項目中。預設是的,Django包含以下應用:
- django.contrib.admin:這是一個管理站點。
- django.contrib.auth:這是一個許可權框架。
- django.contrib.contenttypes:這是一個內容類型的框架。
- django.contrib.sessions:這是一個session會話框架。
- django.contrib.messages:這是一個消息框架。
- django.contrib.staticfiles:這是一個用來管理靜態文件的框架
- MIDDLEWARE_CLASSES 是一個包含可執行中間件的元祖。
- ROOT_URLCONF 指明你的應用定義的Python模塊的主URL模式。
- DATABASES 是一個包含了所有在項目中使用的資料庫的設置的字典。裡面一定有一個預設的資料庫。預設的配置使用的是SQLite3資料庫。
- LANGUAGE_CODE 定義Django站點的預設語言。
不要擔心你目前還看不懂這些設置的含義。你將會熟悉這些設置在接下里的章節中。
項目和應用
現在讓我們創建你的第一個Django應用。我們將要創建一個博客應用。在你的項目主目錄下,運行以下命令:
python manage.py startapp blog
這個命令會創建應用的基本目錄結構,類似下方結構:
blog/
__init__.py
admin.py
migrations/
__init__.py
models.py
tests.py
views.py
這些文件的含義:
- admin.py: 在這兒你可以註冊你的models使它們在Django的管理頁面中展示。不一定要使用Django的管理頁面。
- migrations: 這個目錄將會包含你的應用的資料庫migrations。Migrations允許Django跟蹤你的model變化來同步資料庫。
- models.py: 應用的數據models。所有的Django應用都需要有一個models.py,儘管這個文件可以是空的。
- tests.py:在這兒你可以為你的應用創建測試用例。
- views.py:你的應用邏輯處理將會放在這兒。每一個view都會受到一個HTTP請求,處理處理,並且返回一個響應。
設計博客的數據模型
我們將要為你的博客設計初始的數據模型。每一個model都是一個Python類,繼承了django.db.models.model,其中的每一個屬性對應了一個資料庫欄位。Djnago將會為models.py中的每一個定義好的model創建好一張表。當你創建好一個model,Django就能提供一個非常實用的API來方便的查詢資料庫。
首先,我們定義一個POST model。在blog應用下的models.py文件中添加以下內容:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Post(models.Model):
STATUS_CHOICES = (
('draft', 'Draft'),
('published', 'Published'),
)
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250,
unique_for_date='publish')
author = models.ForeignKey(User,
related_name='blog_posts')
body = models.TextField()
publish = models.DateTimeField(default=timezone.now)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=10,
choices=STATUS_CHOICES,
default='draft')
class Meta:
ordering = ('-publish',)
def __str__(self):
return self.title
這是一個給博客帖子使用的基礎model。讓我們來看下剛纔在model中定義的各個欄位含義:
- title: 這個欄位對應帖子的標題。這是一個CharField,在SQL資料庫中對應VARCHAR類型
- slug:這是一個用在URLs中的欄位。一個slug是一個短標簽之包含字母,數字,下劃線或連接線。我們將通過使用slug欄位來構建一個漂亮的,友好的URLs給我們的博客帖子。我們可以給每個帖子使用日期和slug來構建URLs通過添加 unique_for_date參數給這個欄位。
- author:這是一個 ForeignKey。這個欄位定義了一個many-to-one(多對一)的關係。我們告訴Django每一個帖子是被一個用戶編輯而一個用戶又能編輯多個帖子。通過這個欄位,Django將會在資料庫中通過有關聯的model主鍵來創建一個外鍵。在這個例子中,我們關聯上了django許可權系統的User model。我們指定了User和Post之間關聯的名字,通過related_name屬性。在之後我們將會學習到更多相關的內容。
- body:這是帖子的內容。這個欄位是TextField,在SQL數據中對應TEXT類型。
- publish:這個欄位存儲了帖子發佈的時間。我們使用Djnago的 timezone now方法來設定預設值。
- created:這個欄位存儲了帖子創建的時間。因為我們在這兒使用了auto_now_add,當一個對象被創建的時候這個欄位會自動保存當前日期。
- updated:這個欄位存儲了帖子更新的時間。因為我們在這兒使用了auto_now,當我們保存一個對象的時候當前欄位將會自動更新到當前日期。
- status:這個欄位是用來展示帖子的狀態。我們使用了一個choices參數,所有這個欄位的值只能在給予的選擇內容中選擇。
就像你所看到的的,Django帶來了不同的欄位類型所以你能夠定義你的models。你可以找到所有的欄位類型通過訪問 https://docs.djangoproject.com/en/1.8/ref/models/fields/
在model中的Meta類包含元數據。我們告訴Django查詢資料庫的時候預設返回的是根據publish欄位進行降序排列過的結果。我們使用負號來指定降序排列。
str()方法是對象預設的可讀表現。Django將會在很多地方用到它例如管理平臺。
如果你之前使用過Python2.X,請註意在Python3中所有的strings都使用unicode,因此我們只使用str()方法。unicode()方法已經廢棄。
在我們處理日期之前,我們需要下載pytz模塊。這個模塊給Python提供時區的定義並且SQLite也需要它來對日期進行操作。在終端中輸入以下命令來安裝pytz:
pip install pytz
Django內置支持對日期時區的處理。你可以在項目的settings.py文件中通過USE_TZ來設置激活或停用對時區的支持。當你通過startproject命令來創建一個新項目的時候這個設置預設為True。
激活你的應用
為了讓Django能保持跟蹤你的應用並且通過models來創建數據表,我們必須激活你的應用。為起到效果,編輯settings.py文件在INSTALLED_APPS設置中添加blog。類似如下顯示:
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
)
現在Django已經知道我們項目中的應用是激活的狀態並且將會審查其中的models。
創建和進行資料庫遷移
讓我們為model在資料庫中創建一張表格。Django自帶一個遷移系統來跟蹤每一次models的變化並且會同步到資料庫。migrate命令會應用到所有在INSTALLED_APPS中的應用,它會根據models來同步資料庫。
首先,我們需要為我們剛纔創建的心model生成一個遷移。在你的項目主目錄下,執行以下命令:
python manage.py makemigrations blog
你會看到以下輸出:
Migrations for 'blog':
0001_initial.py;
- Create model Post
Django已經在blog應用下的migrations目錄中創建了一個0001——initial.py文件。你可以打開這個文件來看下。
讓我們來看下Django根據我們的model將會為在資料庫中創建的表格而執行的SQL代碼。sqlmigrate命令會返回一串SQL而不會去執行。執行以下命令來看下輸出:
python manage.py sqlmigrate blog 0001
輸出類似如下:
BEGIN;
CREATE TABLE "blog_post" ("id" integer NOT NULL PRIMARY KEY
AUTOINCREMENT, "title" varchar(250) NOT NULL, "slug" varchar(250) NOT
NULL, "body" text NOT NULL, "publish" datetime NOT NULL, "created"
datetime NOT NULL, "updated" datetime NOT NULL, "status" varchar(10)
NOT NULL, "author_id" integer NOT NULL REFERENCES "auth_user" ("id"));
CREATE INDEX "blog_post_2dbcba41" ON "blog_post" ("slug");
CREATE INDEX "blog_post_4f331e2f" ON "blog_post" ("author_id");
COMMIT;
這些輸出是根據你正在使用的資料庫。這些SQL語句是為SQLite準備的。就像你所看見的,Django生成的表名首碼為應用名之後跟上modle的小寫(blog_post),但是你也可以自定義表名在models的Meta類中通過db_table屬性。Django會自動為每個model創建一個主鍵,但是你可以自己指定主鍵通過設置primarry_key=True在期中一個model的欄位你上。
讓我們根據新的model來同步資料庫。運行以下的命令來應用存在的數據遷移:
python manage.py migrate
你應該會看到以下的類似輸出:
Applying blog.0001_initial... OK
我們剛剛為INSTALLED_APPS中所有的應用進行了數據遷移,包括我們的blog應用。在進行了數據遷移之後,資料庫將會和我們的models對應。
如果你編輯了models.py文件無論你是添加,刪除,還是改變了之前存在的欄位,或者你添加了一個新的models,你將必須做一次新的遷移通過使用makemigrations命令。數據遷移允許Django來保持對model改變的跟蹤。然後你必須通過migrate命令來保持資料庫與我們的models同步。
為你的models創建一個管理站點
如今,我們已經定義好了Post model,我們將要創建一個簡單的管理站點來管理博客的帖子。Django內置了一個非常有用的管理介面來編輯內容。這個Django管理站點會根據你的model元數據進行動態構建並且提供可讀的介面來編輯內容。你可以對這個站點進行自由的定製,配置你的models在其中的展示方式。
請記住,django.contrib.admin已經被包含在我們項目的INSTALLED_APPS設置中,我們不需要再額外添加。
創建一個超級用戶
首先,我們需要創建一個用戶來管理這個管理站點。運行以下的命令:
python manage.py createsuperuser
你將會看下以下的輸出。輸入你想要的用戶名,郵箱和密碼:
Username (leave blank to use 'admin'): admin
Email address: [email protected]
Password: ********
Password (again): ********
Superuser created successfully.
Django管理站點
現在,通過python manage.py runserver
命令來啟動開發服務,之後在瀏覽器中打開 http://127.0.0.1:8000/admin/ 。你會看到管理站點的登錄頁面,如下所示:
使用你之前創建的超級用戶信息登錄。你將會看到管理站點的首頁,如下所示:
Group和User models 是Django許可權管理框架django.contrib.auth的一部份。如果你點擊Users,你將會看到你之前創建的用戶信息。博客應用的Postmodels和User model關聯在了一起。記住,它們是通過author欄位來進行關聯的。
在管理站點中添加你的models
讓我們在管理站點中添加你的博客models。編輯博客應用下的admin.py文件,如下所示:
from django.contrib import admin
from .models import Post
admin.site.register(Post)
現在,在瀏覽器中刷新管理站點頁面。你會看到你的Post model已經在頁面中展示,如下所示:
這很簡單,對吧?當你在Django的管理頁面註冊了一個model,你會獲得一個非常友好有用的介面通過簡單的方式允許你在列表中編輯,創建,刪除對象操作。
點擊Posts右側的Add鏈接來添加一個新帖子。你將會看到Django為你的model動態生成了一個可編輯輸入頁面,如下所示:
Django為不同類型的欄位使用了不通的控制項來展示。即使是複雜的欄位例如DateTimeField也被展示成一個簡單的介面類似一個JavaScript date picker。
填寫好表單並點擊Save按鈕。你會被重定向到帖子列表頁面獲取到一條成功創建帖子的提示,如下所示:
自定義models的展示形式
現在我們來看下如何自動管理站點。編輯博客應用下的admin.py文件,使之如下所示:
from django.contrib import admin
from .models import Post
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug', 'author', 'publish',
'status')
admin.site.register(Post, PostAdmin)
我們通過使用繼承ModelAdmin的自定義類來告訴Django管理站點註冊了哪些我們自己的model。在這個類中,我們可以包含一些信息來定製管理頁面中如何展示和交互model。list_display屬性允許你在管理對象的列表頁面展示你想展示的model中的欄位。
讓我們來通過更多的選項來展示model,如使用以下代碼:
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug', 'author', 'publish',
'status')
list_filter = ('status', 'created', 'publish', 'author')
search_fields = ('title', 'body')
prepopulated_fields = {'slug': ('title',)}
raw_id_fields = ('author',)
date_hierarchy = 'publish'
ordering = ['status', 'publish']
重新刷新管理站點的頁面,現在應該如下所示:
你可以看到在帖子列表頁中展示的欄位都是你在list-dispaly屬性中指定的。列表頁面現在包含了一個右邊欄允許你根據list_filter屬性中指定的欄位來過濾返回結果。在頁面還出現了一個搜索框。這是因為我們還定義了一個搜索欄位通過使用search_fields屬性。在搜索框的下方,有個可以通過時間層快速操作的欄。這是通過定義date_hierarchy屬性。你還能看到這些帖子可以通過Status和Publish列進行了預設排序。那是因為你指定了預設排序通過使用ordering屬性。
現在,點擊Add post鏈接。你還會在這兒看到更多的改變。當你輸入完成新帖子的標題,slug欄位將會自動填充。我們告訴Django通過輸入的標題來填充slug欄位是通過使用prepoupulated_fields屬性。同時,如今的author欄位展示變為了一個搜索控制項,這樣當你的用戶達到成千上萬的時候比使用下拉的選擇更加的人性化,如下圖所示:
通過短短的幾行代碼,我們就在管理站點中自定義了我們的model的展示形式。還有更多的方式可以用來自定義Django的管理站點。在這本書的後面,我們還會進一步講述。
使用QuerySet和managers
現在,你已經有個完整的管理站點來管理你的博客內容,是時候學習如何從資料庫中檢索信息和操作了。Django自帶了一個強大的資料庫抽象API可以讓你輕鬆的創建,檢索,更新以及刪除對象。Django的Object-relational Mapper(ORM)可以相容MySQL,PostgreSQL,SQLite以及Oracle。請記住你可以在你項目下的setting.py中編輯DATABASES設置來指定資料庫。Django可以同時與多個資料庫進行工作,這樣你可以編寫資料庫路由來操作數據通過任何你喜歡的方式。
一旦你創建好了你的數據models,Django會提供你一個API來與它們進行交互。你可以找到數據model的官方參考文檔通過訪問 https://docs.djangoproject.com/en/1.8/ref/models/
創建對象
打開終端運行以下命令來打開Python shell:
python manage.py shell
然後依次輸入以下內容:
> >>> from django.contrib.auth.models import User
>>> from blog.models import Post
>>> user = User.objects.get(username='admin')
>>> Post.objects.create(title='One more post',
slug='one-more-post',
body='Post body.',
author=user)
>>> post.save()
讓我們來研究下這些代碼做了什麼。首先,我們取回了一個username為admin的用戶對象:
user = User.objects.get(username='admin')
get()方法允許你從資料庫取回一個單獨的對象。註意這個方法只希望有唯一的一個匹配在查詢中。如果在資料庫中沒有返回結果,這個方法會拋出一個DoesNotExist異常,如果資料庫返回多個匹配結果,將會拋出一個MultipleObjectsReturned異常。當查詢執行的時候,所有的異常都是model類的屬性。
然後,我們來創建一個擁有自定義標題,slug和內容的Post實例,該實例中author關聯上我們之前去除的user,如下所示:
post = Post(title='Another post', slug='another-post', body='Postbody.', author=user)
這個對象只是存在記憶體中,並沒有執行到資料庫中
最後,我們通過使用save()方法來保存該對象到資料庫中:
post.save()
這步操作將會在之後執行一段SQL的插入語句。我們已經知道如何在記憶體中創建一個對象並且之後才在資料庫中進行插入,但是我們也可以直接在資料庫中創建對象通過使用create()方法,如下所示:
Post.objects.create(title='One more post', slug='one-more-post',body='Post body.', author=user)
更新對象
現在,改變這篇帖子的標題並且再次保存對象:
>>> post.title = 'New title'
>>> post.save()
這一次,save()方法執行了一條更新語句。
你對對象的改變一直存在記憶體中直到你執行到save()方法。
取回對象
Django的Object-relational mapping(ORM)是以QuerySet為基礎。QuerySet是從你的資料庫中根據一些過濾條件範圍取回的結果進行採集產生的對象。你已經知道通過get()方法可以從資料庫中取回單獨的對象。就像你所看到的:Post.objects.get()
。每一個Django model至少有一個manager,預設叫做objects。你能獲得一個QuerySet對象就是通過你的models的manager。獲取table中所有的對象,你只需要在預設的objects manager上使用all()方法即可:
>>> all_posts = Post.objects.all()
我們創建一個返回資料庫中所有對象的QuerySet。註意這個QuerySet並還沒有執行。Django的QuerySets是惰性的,他們只會被動的去執行。這樣可以保證QuerySet非常效率。如果沒有把QuerySet賦值給一個變數,直接在Python shell中編寫,這樣QuerySet的SQL語句將立馬執行因為我們迫使它在輸出中返回結果:
>>> Post.objects.all()
使用filter()方法
過濾QuerySet,你可以在manager上使用filter()方法。舉個例子,我們可以返回所有在2015年發佈的帖子,如下所示:
Post.objects.filter(publish__year=2015)
你也可以使用多個欄位來進行過濾。舉個例子,我們可以返回2015年發佈的所有作者用戶名為admin的帖子,如下所示:
Post.objects.filter(publish__year=2015, author__username='admin')
上面的寫法和下麵的寫法產生的結果是一致的:
Post.objects.filter(publish__year=2015).filter(author__username='admin')
使用exclude()
你可以在manager上使用exclude()方法來排除某些返回結果。例如:我們可以返回所有2015年發佈的帖子但是這些帖子的題目開頭不能是Why:
Post.objects.filter(publish__year=2015).exclude(title__startswith='Why')
使用order_by()
你可以對結果進行排序通過在manager上使用order_by()方法來對不同的欄位進行排序。例如:你可以取回所有對象通過它們的標題進行排序:
Post.objects.order_by('title')
預設是升序。你可以通過負號來指定使用降序:
Post.objects.order_by('-title')
刪除對象
如果你想刪除一個對象,你可以對對象實例進行下麵的操作:
post = Post.objects.get(id=1)
post.delete()
請註意,刪除對象也將刪除其中的依賴關係
QuerySet什麼時候會執行
你可以連接許多過濾給QuerySet只要你喜歡而且不會在資料庫中執行直到這個QuerySet被執行。QuerySet只有在以下情況中才會執行:
* 在你第一次迭代它們的時候
* 當你對它們的實例進行切片:Post.objects.all()[:3]
* 當你對它們進行了打包或緩存
* 當你對它們調用了repr()或len()方法
* 當你明確的對它們調用了list()方法
* 當你再一些聲明中測試它們,例如bool(), or, and, or if
創建model manager
我們之前提到過, objects是每一個models的預設manager,會返回資料庫中所有的結果。但是我們也可以我們的models定義一些自定義的manager。我們準備創建一個自定義的manager來返回所有狀態為已發佈的帖子。
有兩種方式來為你的models添加managers:你可以添加臨時的manager方法或者繼承manager的QuerySets進行修改。第一種方法類似Post.objects.my_manager()
,第二種方法類似Post.my_manager.all()
。我們的manager將會允許我們返回所有帖子通過使用Post.published
。
編輯你的博客應用下的*models.py文件添加如下代碼來創建一個manager:
class PublishedManager(models.Manager):
def get_queryset(self):
return super(PublishedManager,
self).get_queryset()\
.filter(status='published')
class Post(models.Model):
# ...
objects = models.Manager() # The default manager.
published = PublishedManager() # Our custom manager.
get_queryset()是返回執行的QuerySet的方法。我們通過使用它來包含我們自定義的過濾在完整的QuerySet中。我們定義我們自定義的manager然後添加到Post model中。我們現在可以來執行它。例如,
我們可以返回所有標題開頭為Who的並且是已經發佈的帖子:
Post.published.filter(title__startswith='Who')
構建列表和詳情views
現在你學會了一些ORM的基本使用方法,你已經準備好為博客應用創建views了。一個Django view 就是一個Python方法可以接收一個web請求然後返回一個web響應。在vies中通過邏輯處理返回期望的響應。
首先我們會創建一個應用view,然後我們將會為每個view定義一個URL,最後,我們將會為每個views生成的數據通過創建HTML templates來進行渲染。每個view將會通過一個變數來渲染template並且會返回一個經過渲染輸出的HTTP響應。
創建列表和詳情views
讓我們開始創建一個視圖來展示帖子的列表。編輯你的博客應用下中views.py文件,如下所示:
from django.shortcuts import render, get_object_or_404
from .models import Post
def post_list(request):
posts = Post.published.all()
return render(request,
'blog/post/list.html',
{'posts': posts})
你剛創建了你的第一個Django view。post_list view將request對象作為唯一的參數。記住所有的的view都有需要這個參數。在這個view中,我們獲取到了所有狀態為已發佈的帖子通過使用我們之前創建的published manager。
最後,我們使用Django提供的快捷方法render()來渲染帖子列表通過給予的template。這個函數將request對象,template路徑,和給予渲染的變數最為參數。它返回一個渲染文本(一般是HTML代碼)HttpResponse對象。render()方法將許多變數投入template環境中,以便template環境中進行調用。你會在第三章,擴展你的博客應用學習到。
讓我們創建第二個view來展示一篇單獨的帖子。添加如下代碼到views.py文件中:
def post_detail(request, year, month, day, post):
post = get_object_or_404(Post, slug=post,
status='published',
publish__year=year,
publish__month=month,
publish__day=day)
return render(request,
'blog/post/detail.html',
{'post': post})
這是一個帖子詳情view。這個view使用year,month,day以及post參數來獲取一個發佈的帖子通過給予的slug和日期。請註意,當我們創建Post model的時候,我們在slgu欄位上添加了unique_for_date參數。這樣我們可以確保只有一個帖子會帶有給予的slug,因此,我們能取回單獨的帖子通過日期和slug。在這個詳情view中,我們通過使用get_object_or_404()快捷方法來檢索期望的帖子。這個函數能取回匹配給予的參數的對象,或者返回一個HTTP 404異常當沒有匹配的對象找到。最後,我們使用render()快捷方法來使用template去渲染取回的帖子。
為你的views添加URL patterns
一個URL pattern 是由一個Python正則表達,一個view,一個全項目範圍內的命名組成。Django在運行中會遍歷所有URL pattern直到第一個匹配的請求URL才停止。之後,Django導入匹配的URL pattern中的view並且執行它,使用關鍵字或指定參數來執行一個HttpRequest類的實例。
如果你之前沒有接觸過正則表達式,你需要去稍微瞭解下,通過訪問 https://docs.python.org/3/howto/regex.html 。
在博客應用目錄下創建一個urls.py文件,輸入以下代碼:
from django.conf.urls import url
from . import views
urlpatterns = [
# post views
url(r'^$', views.post_list, name='post_list'),
url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/'\
r'(?P<post>[-\w]+)/$',
views.post_detail,
name='post_detail'),
]
第一條URL pattern並沒有帶入任何參數,它映射post_list view。第二條URL pattern帶上了一下4個參數映射到post_detail view。讓我們看下這個URL pattern中的正則表達式:
* year:需要四位數
* month:需要兩位數。不及兩位數,開頭帶上0,比如 01,02
* day:需要兩位數。不及兩位數開頭帶上0
* post:可以由單詞和連字元組成
為每一個應用創建單獨的urls.py文件是最好的方法來保證你的應用可以為別的項目再度使用。
現在你需要將你博客中的URL patterns包含到項目的主URL patterns中。編輯你的項目中的mysite文件夾中urls.py文件,如下所示:
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)), url(r'^blog/', include('blog.urls',
namespace='blog',
app_name='blog')),
]
通過這樣的方式,你告訴Django在blog/路徑下包含了博客應用中的urls.py定義的URL patterns。你可以給他們一個命名空間叫做blog這樣你可以方便的引用這個URLs組。
models的標準URLs
你可以使用之前定義的post_detil URL對Post對象構建標準URL。Django的慣例是添加get_absolute_url()方法給model用來返回一個對象的標準URL。在這個方法中,我們使用reverse()方法允許你通過名字和可選的參數來構建URLS。編輯你的models.py文件添加如下代碼:
from django.core.urlresolvers import reverse
Class Post(models.Model):
# ...
def get_absolute_url(self):
return reverse('blog:post_detail',
args=[self.publish.year,
self.publish.strftime('%m'),
self.publish.strftime('%d'),
self.slug])
請註意,我們通過使用strftime()方法來保證個位數的月份和日期需要帶上0來構建URL.我們將會在我們的templates中使用get_absolute_url()方法。
為你的views創建templates
我們為我們的應用創建了views和URL patterns。現在該添加模板來展示人性化的帖子了。
在你的博客應用目錄下創建一下目錄結構和文件:
templates/
blog/
base.html
post/
list.html
detail.html
以上就是我們的templates的文件目錄結構。base.html文件將會包含站點主要的HTML結構,分割內容區域和導航欄。list.html和detail.html文件會繼承base.html文件來渲染各自的博客帖子列表和詳情view。
Django有一個強大的templates語言允許你指定數據的展示形式。它立足於templates tags, 例如 {% tag %}
, {{ variable }}
以及templates filters,可以對變數進行過濾,例如 {{ variable|gilter }}
。你可以找到所有的內置templates tags和filters通過訪問 https://docs.djangoproject.com/en/1.8/ ref/templates/builtins/ 。
讓我們來編輯base.html文件添加如下代碼:
{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
<link href="{% static "css/blog.css" %}" rel="stylesheet">
</head>
<body>
<div id="content">
{% block content %}
{% endblock %}
</div>
<div id="sidebar">
<h2>My blog</h2>
<p>This is my blog.</p>
</div>
</body>
</html>
{% load staticfiles %}
告訴Django去載入django.contrib.staticfiles應用提供的staticfiles temaplate tags。通過載入,你可以在這個template中使用{% static %}
template filter。通過使用這個template filter,你可以包含一些靜態文件比如說blog.css文件,你可以在本書的範例代碼例子中知道,在博客應用的static/目錄中(譯者註:給大家個地址去拷貝 https://github.com/levelksk/django-by-example-book )拷貝這個目錄到你的項目下的相同路徑來使用這些靜態文件。
你可以看到有兩個{% block %}
tags。這些是用來告訴Django我們想在這個區域中定義一個block。繼承這個template的templates可以使用自定義的內容來填充block。我們定義了一個block叫做title,另一個block叫做content。
讓我們編輯post/list.html文件使它如下所示:
{% extends "blog/base.html" %}
{% block title %}My Blog{% endblock %}
{% block content %}
<h1>My Blog</h1>
{% for post in posts %}
<h2>
<a href="{{ post.get_absolute_url }}">
{{ post.title }}
</a>
</h2>
<p class="date">
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body|truncatewords:30|linebreaks }}
{% endfor %}
{% endblock %}
通過{% extends %}
template tag,我們告訴Django需要繼承blog/base.html template。然後我們在title和content blocks中填充內容。我們通過迴圈迭代帖子來展示他們的標題,日期,作者和內容,在中標題還包括一個帖子的標準URL鏈接。在帖子的內容中,我們應用了兩個template filters: truncatewords用來縮短內容限制在一定的字數內,linebreaks用來轉換內容中的換行符為HTML的換行符。你可以連接許多tempalte filters只要你喜歡,每一個都會應用到上個輸出生成的結果上。
打開終端執行命令python manage.py runserver
來啟動開發環境。瀏覽器中打開 http://127.0.0.1:8000/blog/ 你會看到運行的結果。註意,你需要添加一些發佈狀態的帖子才能在這兒看到它們。你會看到如下圖所示:
這之後,讓我們來編輯post/detail.html文件使它如下所示:
{% extends "blog/base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<h1>{{ post.title }}</h1>
<p class="date">
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body|linebreaks }}
{% endblock %}
現在,你可以返回你的瀏覽器中點擊其中一個帖子的標題來看帖子的詳細view。你會看到類似的以下輸出:
添加頁碼
當你開始給你的博客添加內容,你很快會意識到你需要將帖子分頁顯示。Django有一個內置的pagination類允許你方便的管理分頁。
編輯博客應用下的views.py文件導入Django的paginator類修改post_list如下所示:
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def post_list(request):
object_list = Post.published.all()
paginator = Paginator(object_list, 3) # 3 posts in each page
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer deliver the first page
posts = paginator.page(1)
except EmptyPage:
# If page is out of range deliver last page of results
posts = paginator.page(paginator.num_pages)
return render(request,
'blog/post/list.html',
{'page': page, 'posts': posts})
pagination是如何工作的:
* 1. 我們通過希望在每頁中顯示的對象的數量實例化了Paginator類。
* 2. 我們獲取到page GET參數來致命頁碼
* 3. 我們通過調用Paginator的 page()方法在期望的頁面中獲得了對象。
* 4. 如果page參數不是一個整形數字,我們就返回第一頁的結果。如果這個參數的數字超出了最後的頁數,我們就展示最後一頁的結果。
* 5. 我們傳遞頁數並且取到對象給template。
現在,我們必須創建一個template來展示分頁處理,它可以被任意的template包含來使用頁碼。在博客應用的templates文件夾下創建一個新文件命名為pagination.html。在文件中添加如下HTML代碼:
<div class="pagination">
<span class="step-links">
{% if page.has_previous %}
<a href="?page={{ page.previous_page_number }}">Previous</a>
{% endif %}
<span class="current">
Page {{ page.number }} of {{ page.paginator.num_pages }}.
</span>
{% if page.has_next %}
<a href="?page={{ page.next_page_number }}">Next</a>
{% endif %}
</span>
</div>
這個分頁template期望一個Page對象為了渲染上一頁與下一頁的鏈接並且展示頁面數據和所有頁面的結果。讓我們回到blog/post/list.htmltempalte中將pagination.htmltemplate包含在{% content %}
block中,如下所示:
{% block content %}
...
{% include "pagination.html" with page=posts %}
{% endblock %}
我們傳遞給template的Page對象叫做posts,我們將分頁tempalte包含在帖子列表template中指定參數來對它進行渲染。這是一種方法你可以重覆利用你的分頁template對不同的models views進行分頁處理。
現在,在你的瀏覽器中打開 http://127.0.0.1:8000/blog/。 你會看到帖子列表的底部有分頁信息:
使用基於類的views
當一個view被調用通過web請求並且返回一個web響應,你還可以將你的views定義成類方法。Django為此定義了基礎的view類。它們都從View類繼承而來,View類可以操控HTTP方法分派和其他的功能。這是一個可代替的方法來創建你的views。
我們準備使我們的post_list view轉變為一個基於類的視圖通過使用Django提供的通用ListView。這個基礎view允許你對任意的對象進行排列。
編輯你的博客應用下的views.py文件,如下所示:
from django.views.generic import ListView
class PostListView(ListView):
queryset = Post.published.all()
context_object_name = 'posts'
paginate_by = 3
template_name = 'blog/post/list.html'
這個基於類的的view類似與之前的post_list view。這兒,我們告訴ListView做了以下操作:
- 使用一個特定的QuerySet代替取回所有的對象。代替定義一個queryset屬性,我們可以指定
model = Post
並且Django將會構建一個通過的Post.objects.all() QuerySet給我們。 - 使用環境變數posts給查詢結果。如果我們不指定任意的context_object_name預設的變數將會是object_list。
- 對結果進行分頁處理每頁只顯示3個對象。
- 使用自定義的template來渲染頁面。如果我們不設置預設的template,ListView將會使用
blog/post_list.html
。
現在,打開你的博客應用下的urls.py文件,在post_list URL pattern之前添加一個新的URL pattern使用PostlistView類,如下所示:
urlpatterns = [
# post views
# url(r'^$', views.post_list, name='post_list'),
url(r'^$', views.PostListView.as_view(),name='post_list'),
url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/'\
r'(?P<post>[-\w]+)/$',
views.post_detail,
name='post_detail'),
]
為了保持分頁處理能工作,我們必須將正確的頁面對象傳遞給tempalte。Django的ListView通過叫做page_obj的變數來傳遞被選擇的頁面,所以你必須編輯你的post_list_html template因此去包含使用了正確的變數的分頁處理,如下所示:
{% include "pagination.html" with page=page_obj %}
在你的瀏覽器中打開 http://127.0.0.1:8000/blog/ 然後檢查每一樣事情是否都和之前的post_list view一樣工作。這是一個簡單的基於類的view例子通過使用Django提供的通用類。你將會學到更多的基於類的views在第十章,創建一個線上學習平臺和相關聯的章節。
總結
在本章中,你學習了Django web框架的基礎通過創建一個基礎的博客應用。你為你的項目設計了數據models並且進行了數據遷移。你為你的博客創建了views,templates以及URLs,還包括對象分頁。
在下一章中,你會學習到如何增強你的博客應用,例如評論系統,tag功能,並且允許你的用戶通過郵件來分享帖子。
譯者總結
終於將第一章勉強翻譯完成了,很多翻譯的句子我自己都讀不懂 - -|||
大家看到有錯誤有歧義的地方請幫忙指出,之後還會隨時進行修改保證基本能讀懂。
按照第一章的翻譯速度,全書都翻譯下來估計要2,3個月,這是非常非常樂觀的估計,每天只有中午休息和下班後大概有兩三小時的翻譯時間。