《Django By Example》第一章 中文翻譯

轉載,譯者:夜夜月。原文鏈接:jianshu.com/p/05810d38f

書籍出處:Django By Example

原作者:Antonio Melé

2016年12月10日發布(沒有進行校對,有很多錯別字以及模糊不清的語句,請大家見諒)

2017年2月7日精校完成(斷斷續續的終於完成了第一章精校,感覺比直接翻譯還要累,繼續加油)

2017年2月10日再次進行精校(感謝大牛@kukoo的精校!)

(譯者註:本人目前在杭州某家互聯網公司工作,崗位是測試研發,非常喜歡python,目前已經使用Django為公司內部搭建了幾個自動化平台,因為沒人教沒人帶,基本靠野路子自學,走過好多彎路,磕磕碰碰一路過來,前段時間偶爾看到《Django By Example》這本書,瞬間淚流滿面,當初怎麼沒有找到這麼好的Django教程。在看書的過程中不知道怎麼搞的突然產生了翻譯全書的想法,正好網上找了下也沒有漢化的版本,所以準備踏上這條不歸路。鑒於本人英文水平極低(四級都沒過),單純靠著有道詞典和自己對上下文的理解以及對書中每行代碼都保證敲一遍並運行的情況下,請各位在讀到語句不通的時候或看不懂的地方請告訴我,我會及時進行改正。翻譯全書,主要也是為了培養自己的英文閱讀水平(口語就算了),誰叫好多最新最有用的計算機文檔都是用英文寫的,另外也可以培養自己的耐心,還可以分享給其他人,就這樣。)

第一章

創建一個blog應用

在這本書中,你將學習如何創建完整的Django項目,可以在生產環境中使用。假如你還沒有安裝Django,在本章的第一部分你將學習如何安裝。本章會覆蓋如何使用Django去創建一個簡單的blog應用。本章的目的是使你對該框架的工作有個基本概念,了解不同的組件之間是如何產生交互,並且教你一些技能通過使用一些基本功能方便地創建Djang項目。你會被引導創建一個完整的項目但是不會對所有的細節都進行詳細說明。不同的框架組件將在本書接下來的章節中進行介紹。

本章會覆蓋以下幾點:

  • 安裝Django並創建你的第一個項目
  • 設計模型(models)並且生成模型(model)資料庫遷移
  • 給你的模型(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)n[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwinnType "help", "copyright", "credits" or "license" for more information.n>>>n

如果你計算機中安裝的Python版本低於3,或者沒有安裝,下載並安裝Python 3.5.0 從Download Python (譯者註:最新已經是3.6.0了,Django2.0將不再支持pytyon2.7,所以大家都從3版本以上開始學習吧)。

由於你使用的是Python3,所以你沒必要再安裝一個資料庫。這個Python版本自帶SQLite資料庫。SQLLite是一個輕量級的資料庫,你可以在Django中進行使用用來開發。如果你準備在生產環境中部署你的應用,你應該使用一個更高級的資料庫,比如PostgreSQL,MySQL或Oracle。你能獲取到更多的信息關於資料庫和Django的集成通過訪問 How to install Django 。

創建一個獨立的Python環境

強烈建議你使用virtualenv來創建獨立的Python環境,這樣你可以使用不同的包版本對應不同的項目,這比直接在真實系統中安裝Python包更加的實用。另一個高級之處在於當你使用virtualenv你不需要任何管理員許可權來安裝Python包。在終端中運行以下命令來安裝virtualenv:

pip install virtualenvn

(譯者註:如果你本地有多個python版本,注意Python3的pip命令可能是pip3)

當你安裝好virtualenv之後,通過以下命令來創建一個獨立的環境:

virtualenv my_envn

以上命令會創建一個包含你的Python環境的my_env/目錄。當你的virtualenv被激活的時候所有已經安裝的Python庫都會帶入 my_env/lib/python3.5/site-packages 目錄中。

如果你的系統自帶Python2.X然後你又安裝了Python3.X,你必須告訴virtualenv使用後者Python3.X。通過以下命令你可以定位Python3的安裝路徑然後使用該安裝路徑來創建virtualenv:

zenx$ *which python3* n/Library/Frameworks/Python.framework/Versions/3.5/bin/python3nzenx$ *virtualenv my_env -p n/Library/Frameworks/Python.framework/Versions/3.5/bin/python3*n

通過以下命令來激活你的virtualenv:

source my_env/bin/activaten

shell提示將會附上激活的virtualenv名,被包含在括弧中,如下所示:

(my_evn)laptop:~ zenx$n

你可以使用deactivate命令隨時停用你的virtualenv。

你可以獲取更多的信息關於virtualenv通過訪問 Virtualenv - virtualenv 15.2.0.dev0 documentation 。

在virtualenv之上,你可以使用virtualenvwrapper工具。這個工具提供一些封裝用來方便的創建和管理你的虛擬環境。你可以在 virtualenvwrapper 4.7.3.dev7 下載該工具。

使用pip安裝Django

(譯者註:請注意以下的操作都在激活的虛擬環境中使用)

pip是安裝Django的第一選擇。Python3.5自帶預安裝的pip,你可以找到pip的安裝指令通過訪問 Installation - pip 9.0.1 documentation 。運行以下命令通過pip安裝Django:

pip install Django==1.8.6n

Django將會被安裝在你的虛擬環境的Python的site-packages/目錄下。

現在檢查Django是否成功安裝。在終端中運行python並且導入Django來檢查它的版本:

>>> import djangon>>> django.VERSIONnDjangoVERSION(1, 8, 5, final, 0)n

如果你獲得了以上輸出,Django已經成功安裝在你的機器中。

Django也可以使用其他方式來安裝。你可以找到更多的信息通過訪問 How to install Django 。

創建你的第一個項目

我們的第一個項目將會是一個完整的blog站點。Django提供了一個命令允許你方便的創建一個初始化的項目文件結構。在終端中運行以下命令:

django-admin startproject mysiten

該命令將會創建一個名為mysite的項目。

讓我們來看下生成的項目結構:

mysite/n manage.pyn mysite/n __init__.pyn settings.pyn urls.pyn wsgi.pyn

讓我們來了解一下這些文件:

  • 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應用列表,這些應用會默認添加到你的項目中。我們需要為這些初始應用在資料庫中創建表。

打開終端執行以下命令:

cd mysitenpython manage.py migraten

你將會看到以下的類似輸出:

Rendering model states... DONEnApplying contenttypes.ooo1_initial... OKnApplying auth.0001_initial... OKnApplying admin.0001_initial... OKnApplying contenttypes.0002_remove_content_type_name... OKnApplying auth.0002_alter_permission_name_max_length... OKnApplying auth.0003_alter_user_email_max_length...OKnApplying auth.0004_alter_user_username_opts... OKnApplying auth.0005_alter_user_last_login_null... OKnApplying auth.0006_require_contenttypes_0002... OKnApplying sessions.0001_initial... OKn

這些初始應用表將會在資料庫中創建。過一會兒你就會學習到一些關於migrate的管理命令。

運行開發伺服器

Django自帶一個輕量級的web伺服器來快速運行你的代碼,不需要花費額外的時間來配置一個生產伺服器。當你運行Django的開發伺服器,它會一直檢查你的代碼變化。當代碼有改變,它會自動重啟,將你從手動重啟中解放出來。但是,它可能無法注意到一些操作,例如在項目中添加了一個新文件,所以你在某些場景下還是需要手動重啟。

打開終端,在你的項目主目錄下運行以下代碼來開啟開發伺服器:

python manage.py runservern

你會看到以下類似的輸出:

Performing system checks...nnSystem check identified no issues (0 silenced).nNovember 5, 2015 - 19:10:54nDjango version 1.8.6, using settings mysite.settingsnStarting development server at http://127.0.0.1:8000/nQuit the server with CONTROL-C.n

現在,在瀏覽器中打開 127.0.0.1:8000/ ,你會看到一個告訴你項目成功運行的頁面,如下圖所示:

django-1-1

你可以指定Django在定製的host和埠上運行開發服務,或者告訴它你想要運行你的項目通過讀取一個不同的配置文件。例如:你可以運行以下 manage.py命令:

python manage.py runserver 127.0.0.1:8001 n--settings=mysite.settingsn

這個命令遲早會對處理需要不同設置的多套環境啟到作用。記住,這個伺服器只是單純用來開發,不適合在生產環境中使用。為了在生產環境中部署Django,你需要使用真實的web服務讓它運行成一個WSGI應用例如Apache,Gunicorn或者uWSGI(譯者註:強烈推薦 nginx+uwsgi+Django)。你能夠獲取到更多關於如何在不同的web服務中部署Django的信息,訪問 How to deploy with WSGI 。

本書外額外的需要下載的章節第十三章,Going Live包含為你的Django項目設置一個生產環境。

項目設置

讓我們打開settings.py文件來看看你的項目的配置。在該文件中有許多設置是Django內置的,但這些只是所有Django可用配置的一部分。你可以通過訪問 Settings | Django documentation | Django 看到所有的設置和它們默認的值。

以下列出的設置非常值得一看:

  • DEBUG 一個布爾型用來開啟或關閉項目的debug模式。如果設置為True,當你的應用拋出一個未被捕獲的異常時Django將會顯示一個詳細的錯誤頁面。當你準備部署項目到生產環境,請記住一定要關閉debug模式。永遠不要在生產環境中部署一個打開debug模式的站點因為那會暴露你的項目中的敏感數據。
  • ALLOWED_HOSTS 當debug模式開啟或者運行測試的時候不會起作用(譯者註:最新的Django版本中,不管有沒有開啟debug模式該設置都會啟作用)。一旦你準備部署你的項目到生產環境並且關閉了debug模式,為了允許訪問你的Django項目你就必須添加你的域或host在這個設置中。
  • 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 指明你的應用定義的主URL模式存放在哪個Python模塊中。
  • DATABASES 是一個包含了所有在項目中使用的資料庫的設置的字典。裡面一定有一個默認的資料庫。默認的配置使用的是SQLite3資料庫。
  • LANGUAGE_CODE 定義Django站點的默認語言編碼。

不要擔心你目前還看不懂這些設置的含義。你將會在之後的章節中熟悉這些設置。

項目和應用

貫穿全書,你會反覆的讀到項目和應用的地位。在Django中,一個項目被認為是一個安裝了一些設置的Django;一個應用是一個包含模型(models),視圖(views),模板(templates)以及URLs的組合。應用之間的交互通過Django框架提供的一些特定功能,並且應用可能被各種各樣的項目重複使用。你可以認為項目就是你的網站,這個網站包含多個應用,例如blog,wiki或者論壇,這些應用都可以被其他的項目使用。(譯者註:我去,我竟然漏翻了這一節- -|||,罪過罪過,阿米頭髮)

創建一個應用

現在讓我們創建你的第一個Django應用。我們將要創建一個勉強湊合的blog應用。在你的項目主目錄下,運行以下命令:

python manage.py startapp blogn

這個命令會創建blog應用的基本目錄結構,如下所示:

blog/n __init__.pyn admin.pyn migrations/n __init__.pyn models.pyn tests.pyn views.pyn

這些文件的含義:

  • admin.py: 在這兒你可以註冊你的模型(models)並將它們包含到Django的管理頁面中。使用Django的管理頁面是可選的。
  • migrations: 這個目錄將會包含你的應用的資料庫遷移。Migrations允許Django跟蹤你的模型(model)變化並因此來同步資料庫。
  • models.py: 你的應用的數據模型(models)。所有的Django應用都需要擁有一個models.py文件,但是這個文件可以是空的。
  • tests.py:在這兒你可以為你的應用創建測試。
  • views.py:你的應用邏輯將會放在這兒。每一個視圖(view)都會接受一個HTTP請求,處理該請求,最後返回一個響應。

設計blog數據架構

我們將要開始為你的blog設計初始的數據模型(models)。一個模型(model)就是一個Python類,該類繼承了django.db.models.model,在其中的每一個屬性表示一個資料庫欄位。Django將會為models.py中的每一個定義的模型(model)創建一張表。當你創建好一個模型(model),Django會提供一個非常實用的API來方便的查詢資料庫。

首先,我們定義一個POST模型(model)。在blog應用下的models.py文件中添加以下內容:

from django.db import modelsnfrom django.utils import timezonenfrom django.contrib.auth.models import Usernnnclass Post(models.Model):n STATUS_CHOICES = (n (draft, Draft),n (published, Published),n )n title = models.CharField(max_length=250)n slug = models.SlugField(max_length=250,n unique_for_date=publish)n author = models.ForeignKey(User,n related_name=blog_posts)n body = models.TextField()n publish = models.DateTimeField(default=timezone.now)n created = models.DateTimeField(auto_now_add=True)n updated = models.DateTimeField(auto_now=True)n status = models.CharField(max_length=10,n choices=STATUS_CHOICES,n default=draft)nn class Meta:n ordering = (-publish,)nn def __str__(self):n return self.titlen

這就是我們給blog帖子使用的基礎模型(model)。讓我們來看下剛才在這個模型(model)中定義的各個欄位含義:

  • title: 這個欄位對應帖子的標題。它是CharField,在SQL資料庫中會被轉化成VARCHAR。
  • slug:這個欄位將會在URLs中使用。slug就是一個短標籤,該標籤只包含字母,數字,下劃線或連接線。我們將通過使用slug欄位給我們的blog帖子構建漂亮的,友好的URLs。我們給該欄位添加了unique_for_date參數,這樣我們就可以使用日期和帖子的slug來為所有帖子構建URLs。在相同的日期中Django會阻止多篇帖子擁有相同的slug。
  • author:這是一個ForeignKey。這個欄位定義了一個多對一(many-to-one)的關係。我們告訴Django一篇帖子只能由一名用戶編寫,一名用戶能編寫多篇帖子。根據這個欄位,Django將會在資料庫中通過有關聯的模型(model)主鍵來創建一個外鍵。在這個場景中,我們關聯上了Django許可權系統的User模型(model)。我們通過related_name屬性指定了從UserPost的反向關係名。我們將會在之後學習到更多關於這方面的內容。
  • body:這是帖子的主體。它是TextField,在SQL資料庫中被轉化成TEXT
  • publish:這個日期表明帖子什麼時間發布。我們使用Djnago的timezonenow方法來設定默認值。This is just a timezone-aware datetime.now(譯者註:這句該咋翻譯好呢)。
  • created:這個日期表明帖子什麼時間創建。因為我們在這兒使用了auto_now_add,當一個對象被創建的時候這個欄位會自動保存當前日期。
  • updated:這個日期表明帖子什麼時候更新。因為我們在這兒使用了auto_now,當我們更新保存一個對象的時候這個欄位將會自動更新到當前日期。
  • status:這個欄位表示當前帖子的展示狀態。我們使用了一個choices參數,這樣這個欄位的值只能是給予的選擇參數中的某一個值。(譯者註:傳入元組,比如(1,2),那麼該欄位只能選擇1或者2,沒有其他值可以選擇)

就像你所看到的的,Django內置了許多不同的欄位類型給你使用,這樣你就能夠定義你自己的模型(models)。通過訪問 Model field reference 你可以找到所有的欄位類型。

在模型(model)中的類Meta包含元數據。我們告訴Django查詢資料庫的時候默認返回的是根據publish欄位進行降序排列過的結果。我們使用負號來指定進行降序排列。

str()方法是當前對象默認的可讀表現。Django將會在很多地方用到它例如管理站點中。

如果你之前使用過Python2.X,請注意在Python3中所有的strings都使用unicode,因此我們只使用str()方法。unicode()方法已經廢棄。(譯者註:Python3大法好,Python2別再學了,直接學Python3吧)

在我們處理日期之前,我們需要下載pytz模塊。這個模塊給Python提供時區的定義並且SQLite也需要它來對日期進行操作。在終端中輸入以下命令來安裝pytz

pip install pytzn

Django內置對時區日期處理的支持。你可以在你的項目中的settings.py文件中通過USE_TZ來設置激活或停用對時區的支持。當你通過startproject命令來創建一個新項目的時候這個設置默認為True

激活你的應用

為了讓Django能保持跟蹤你的應用並且根據你的應用中的模型(models)來創建資料庫表,我們必須激活你的應用。因此,編輯settings.py文件,在INSTALLED_APPS設置中添加blog。看上去如下所示:

INSTALLED_APPS = ( n django.contrib.admin, n django.contrib.auth, n django.contrib.contenttypes, n django.contrib.sessions, n django.contrib.messages, n django.contrib.staticfiles,n blog,n )n

(譯者註:該設置中應用的排列順序也會對項目的某些方面產生影響,具體情況後幾章會有介紹,這裡提醒下)

現在Django已經知道在項目中的我們的應用是激活狀態並且將會對其中的模型(models)進行自審。

創建和進行資料庫遷移

讓我們為我們的模型(model)在資料庫中創建一張數據表格。Django自帶一個資料庫遷移(migration)系統來跟蹤你對模型(models)的修改,然後會同步到資料庫。migrate命令會應用到所有在INSTALLED_APPS中的應用,它會根據當前的模型(models)和資料庫遷移(migrations)來同步資料庫。

首先,我們需要為我們剛才創建的新模型(model)創建一個資料庫遷移(migration)。在你的項目主目錄下,執行以下命令:

python manage.py makemigrations blogn

你會看到以下輸出:

Migrations for blog:n 0001_initial.py;n - Create model Postn

Django在blog應用下的migrations目錄中創建了一個0001——initial.py文件。你可以打開這個文件來看下一個資料庫遷移的內容。

讓我們來看下Django根據我們的模型(model)將會為在資料庫中創建的表而執行的SQL代碼。sqlmigrate命令帶上資料庫遷移(migration)的名字將會返回它們的SQL,但不會立即去執行。運行以下命令來看下輸出:

python manage.py sqlmigrate blog 0001n

輸出類似如下:

BEGIN;nCREATE 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"));nCREATE INDEX "blog_post_2dbcba41" ON "blog_post" ("slug");nCREATE INDEX "blog_post_4f331e2f" ON "blog_post" ("author_id");nCOMMIT;n

Django會根據你正在使用的資料庫進行以上精準的輸出。以上SQL語句是為SQLite資料庫準備的。如你所見,Django生成的表名前綴為應用名之後跟上模型(model)的小寫(blog_post),但是你也可以通過在模型(models)的Meta類中使用db_table屬性來指定表名。Django會自動為每個模型(model)創建一個主鍵,但是你也可以通過在模型(model)中的某個欄位上設置primarry_key=True來指定主鍵。

讓我們根據新模型(model)來同步資料庫。運行以下的命令來應用已存在的數據遷移(migrations):

python manage.py migraten

你應該會看到以下行跟在輸出的末尾:

Applying blog.0001_initial... OKn

我們剛剛為INSTALLED_APPS中所有的應用進行了資料庫遷移(migrations),包括我們的blog應用。在進行了資料庫遷移(migrations)之後,資料庫會反映我們模型的當前狀態。

如果為了添加,刪除,或是改變了存在的模型(models)中欄位,或者你添加了新的模型(models)而編輯了models.py文件,你都需要通過使用makemigrations命令做一次新的資料庫遷移(migration)。資料庫遷移(migration)允許Django來保持對模型(model)改變的跟蹤。之後你必須通過migrate命令來保持資料庫與我們的模型(models)同步。

為你的模型(models)創建一個管理站點

現在我們已經定義好了Post模型(model),我們將要創建一個簡單的管理站點來管理blog帖子。Django內置了一個管理介面,該介面對編輯內容非常的有用。這個Django管理站點會根據你的模型(model)元數據進行動態構建並且提供一個可讀的介面來編輯內容。你可以對這個站點進行自由的定製,配置你的模型(models)在其中如何進行顯示。

請記住,django.contrib.admin已經被包含在我們項目的INSTALLED_APPS設置中,我們不需要再額外添加。

創建一個超級用戶

首先,我們需要創建一名用戶來管理這個管理站點。運行以下的命令:

python manage.py createsuperusern

你會看下以下輸出。輸入你想要的用戶名,郵箱和密碼:

Username (leave blank to use admin): adminnEmail address: admin@admin.comnPassword: ********nPassword (again): ********nSuperuser created successfully.n

Django管理站點

現在,通過python manage.py runserver命令來啟動開發伺服器,之後在瀏覽器中打開 127.0.0.1:8000/admin/ 。你會看到管理站點的登錄頁面,如下所示:

django-1-2

使用你在上一步中創建的超級用戶信息進行登錄。你將會看到管理站點的首頁,如下所示:

django-1-3

GroupUser 模型(models) 位於django.contrib.auth,是Django許可權管理框架的一部分。如果你點擊Users,你將會看到你之前創建的用戶信息。你的blog應用的Post模型(model)和User(model)關聯在了一起。記住,它們是通過author欄位進行關聯的。

在管理站點中添加你的模型(models)

讓我們在管理站點中添加你的blog模型(models)。編輯blog應用下的admin.py文件,如下所示:

from django.contrib import adminnfrom .models import Postnnadmin.site.register(Post)n

現在,在瀏覽器中刷新管理站點。你會看到你的Post模型(model)已經在頁面中展示,如下所示:

django-1-4

這很簡單,對吧?當你在Django的管理頁面註冊了一個模型(model),Django會通過對你的模型(models)進行內省然後提供給你一個非常友好有用的介面,這個介面允許你非常方便的排列,編輯,創建,以及刪除對象。

點擊Posts右側的Add鏈接來添加一篇新帖子。你將會看到Django根據你的模型(model)動態生成了一個表單,如下所示:

django-1-5

Django給不同類型的欄位使用了不同的表單控制項。即使是複雜的欄位例如DateTimeField也被展示成一個簡單的介面類似一個JavaScript日期選擇器。

填寫好表單然後點擊Save按鈕。你會被重定向到帖子列頁面並且得到一條帖子成功創建的提示,如下所示:

django-1-6

定製models的展示形式

現在我們來看下如何定製管理站點。編輯blog應用下的admin.py文件,使之如下所示:

from django.contrib import adminnfrom .models import Postnnclass PostAdmin(admin.ModelAdmin):n list_display = (title, slug, author, publish,n status)nadmin.site.register(Post, PostAdmin)n

我們使用繼承了ModelAdmin的定製類來告訴Django管理站點中需要註冊我們自己的模型(model)。在這個類中,我們可以包含一些關於如何在管理站點中展示模型(model)的信息以及如何與該模型(model)進行交互。list_display屬性允許你在設置一些你想要在管理對象列表頁面顯示的模型(model)欄位。

讓我們通過更多的選項來定製管理模型(model),如使用以下代碼:

class PostAdmin(admin.ModelAdmin):n list_display = (title, slug, author, publish,n status)n list_filter = (status, created, publish, author)n search_fields = (title, body)n prepopulated_fields = {slug: (title,)}n raw_id_fields = (author,)n date_hierarchy = publishn ordering = [status, publish]n

回到瀏覽器刷新管理站點頁面,現在應該如下所示:

django-1-7

你可以看到帖子列頁面中展示的欄位都是你在list-dispaly屬性中指定的。這個列頁面現在包含了一個右側邊欄允許你根據list_filter屬性中指定的欄位來過濾返回結果。一個搜索框也應用在頁面中。這是因為我們還通過使用search_fields屬性定義了一個搜索欄位列。在搜索框的下方,有個可以通過時間層快速導航的欄,該欄通過定義date_hierarchy屬性出現。你還能看到這些帖子默認的通過StatusPublish列進行排序。這是因為你通過使用ordering屬性指定了默認排序。

現在,點擊Add post鏈接。你還會在這兒看到一些改變。當你輸入完成新帖子的標題,slug欄位將會自動填充。我們通過使用prepoupulated_fields屬性告訴Django通過輸入的標題來填充slug欄位。同時,現在的author欄位展示顯示為了一個搜索控制項,這樣當你的用戶量達到成千上萬級別的時候比再使用下拉框進行選擇更加的人性化,如下圖所示:

django-1-8

通過短短的幾行代碼,我們就在管理站點中自定義了我們的模型(model)的展示形式。還有更多的方式可以用來定製Django的管理站點。在這本書的後面,我們還會進一步講述。

使用查詢集(QuerySet)和管理器(managers)

現在,你已經有了一個完整功能的管理站點來管理你的blog內容,是時候學習如何從資料庫中檢索信息並且與這些信息進行交互了。Django自帶了一個強大的資料庫抽象API可以讓你輕鬆的創建,檢索,更新以及刪除對象。Django的Object-relational Mapper(ORM)可以兼容MySQL,PostgreSQL,SQLite以及Oracle。請記住你可以在你項目下的setting.py中編輯DATABASES設置來指定資料庫。Django可以同時與多個資料庫進行工作,這樣你可以編寫資料庫路由通過任何你喜歡的方式來操作數據。

一旦你創建好了你的數據模型(models),Django會提供你一個API來與它們進行交互。你可以找到數據模型(model)的官方參考文檔通過訪問 Models | Django documentation | Django 。

創建對象

打開終端運行以下命令來打開Python shell:

python manage.py shelln

然後依次輸入以下內容:

>>> from django.contrib.auth.models import Usern>>> from blog.models import Postn>>> user = User.objects.get(username=admin)n>>> post = Post.objects.create(title=One more post,n slug=one-more-post,n body=Post body.,n author=user)n>>> post.save()n

讓我們來研究下這些代碼做了什麼。首先,我們取回了一個username是admin的用戶對象:

user = User.objects.get(username=admin)n

get()方法允許你從資料庫取回一個單獨的對象。注意這個方法只希望在查詢中有唯一的一個匹配。如果在資料庫中沒有返回結果,這個方法會拋出一個DoesNotExist異常,如果資料庫返回多個匹配結果,將會拋出一個MultipleObjectsReturned異常。當查詢執行的時候,所有的異常都是模型(model)類的屬性。

接著,我們來創建一個擁有定製標題標題,slug和內容的Post實例,然後我們設置之前取回的user胃這篇帖子的作者如下所示:

post = Post(title=Another post, slug=another-post, body=Postbody., author=user)n

這個對象只是存在內存中不會執行到資料庫中

最後,我們通過使用save()方法來保存該對象到資料庫中:

post.save()n

這步操作將會執行一段SQL的插入語句。我們已經知道如何在內存中創建一個對象並且之後才在資料庫中進行插入,但是我們也可以通過使用create()方法直接在資料庫中創建對象,如下所示:

Post.objects.create(title=One more post, slug=one-more-post,body=Post body., author=user)n

更新對象

現在,改變這篇帖子的標題並且再次保存對象:

>>> post.title = New titlen>>> post.save()n

這一次,save()方法執行了一條更新語句。

你對對象的改變一直存在內存中直到你執行到save()方法。

取回對象

Django的Object-relational mapping(ORM)是基於查詢集(QuerySet)。查詢集(QuerySet)是從你的資料庫中根據一些過濾條件範圍取回的結果對象進行的採集。你已經知道如何通過get()方法從資料庫中取回單獨的對象。如你所見:我們通過Post.objects.get()來使用這個方法。每一個Django模型(model)至少有一個管理器(manager),默認管理器(manager)叫做objects。你通過使用你的模型(models)的管理器(manager)就能獲得一個查詢集(QuerySet)對象。獲取一張表中的所有對象,你只需要在默認的objects管理器(manager)上使用all()方法即可,如下所示:

>>> all_posts = Post.objects.all()n

這就是我們如何創建一個用於返回資料庫中所有對象的查詢集(QuerySet)。注意這個查詢集(QuerySet)並還沒有執行。Django的查詢集(QuerySets)是惰性(lazy)的,它們只會被動的去執行。這樣的行為可以保證查詢集(QuerySet)非常有效率。如果我們沒有把查詢集(QuerySet)設置給一個變數,而是直接在Python shell中編寫,因為我們迫使它輸出結果,這樣查詢集(QuerySet)的SQL語句將立馬執行:

>>> Post.objects.all()n

使用filter()方法

為了過濾查詢集(QuerySet),你可以在管理器(manager)上使用filter()方法。例如,我們可以返回所有在2015年發布的帖子,如下所示:

Post.objects.filter(publish__year=2015)n

你也可以使用多個欄位來進行過濾。例如,我們可以返回2015年發布的所有作者用戶名為admin的帖子,如下所示:

Post.objects.filter(publish__year=2015, author__username=admin)n

上面的寫法和下面的寫法產生的結果是一致的:

Post.objects.filter(publish__year=2015).filter(author__username=admin)n

我們構建了欄位的查找方法,通過使用兩個下劃線(publish__year)來查詢,除此以外我們也可以通過使用兩個下劃線(author__username)訪問關聯的模型(model)欄位。

使用exclude()

你可以在管理器(manager)上使用exclude()方法來排除某些返回結果。例如:我們可以返回所有2015年發布的帖子但是這些帖子的題目開頭不能是Why:

Post.objects.filter(publish__year=2015).exclude(title__startswith=Why)n

使用order_by()

通過在管理器(manager)上使用order_by()方法來對不同的欄位進行排序,你可以對結果進行排序。例如:你可以取回所有對象並通過它們的標題進行排序:

Post.objects.order_by(title)n

默認是升序。你可以通過負號來指定使用降序,如下所示:

Post.objects.order_by(-title)n

刪除對象

如果你想刪除一個對象,你可以對對象實例進行下面的操作:

post = Post.objects.get(id=1)npost.delete()n

請注意,刪除對象也將刪除任何的依賴關係

查詢集(QuerySet)什麼時候會執行

只要你喜歡,你可以連接許多的過濾給查詢集(QuerySet)而且不會立馬在資料庫中執行直到這個查詢集(QuerySet)被執行。查詢集(QuerySet)只有在以下情況中才會執行:

* 在你第一次迭代它們的時候n* 當你對它們的實例進行切片:例如`Post.objects.all()[:3]`n* 當你對它們進行了打包或緩存n* 當你對它們調用了`repr()`或`len()`方法n* 當你明確的對它們調用了`list()`方法n* 當你在一個聲明中測試它,例如*bool()*, or, and, or ifn

創建model manager

我們之前提到過, objects是每一個模型(models)的默認管理器(manager),它會返回資料庫中所有的對象。但是我們也可以為我們的模型(models)定義一些定製的管理器(manager)。我們準備創建一個定製的管理器(manager)來返回所有狀態為已發布的帖子。

有兩種方式可以為你的模型(models)添加管理器(managers):你可以添加額外的管理器(manager)方法或者繼承管理器(manager)的查詢集(QuerySets)進行修改。第一種方法類似Post.objects.my_manager(),第二種方法類似Post.my_manager.all()。我們的管理器(manager)將會允許我們返回所有帖子通過使用Post.published。

編輯你的blog應用下的models.py文件添加如下代碼來創建一個管理器(manager):

class PublishedManager(models.Manager):n def get_queryset(self):n return super(PublishedManager,n self).get_queryset().filter(status=published)nnclass Post(models.Model):n # ...n objects = models.Manager() # The default manager.n published = PublishedManager() # Our custom manager.n

get_queryset()是返回執行過的查詢集(QuerySet)的方法。我們通過使用它來包含我們定製的過濾到完整的查詢集(QuerySet)中。我們定義我們定製的管理器(manager)然後添加它到Post 模型(model)中。我們現在可以來執行它。例如,我們可以返回所有標題開頭為Who的並且是已經發布的帖子:

Post.published.filter(title__startswith=Who)n

構建列和詳情視圖(views)

現在你已經學會了一些如何使用ORM的基本知識,你已經準備好為blog應用創建視圖(views)了。一個Django視圖(view)就是一個Python方法,它可以接收一個web請求然後返回一個web響應。在視圖(views)中通過所有的邏輯處理返回期望的響應。

首先我們會創建我們的應用視圖(views),然後我們將會為每個視圖(view)定義一個URL模式,我們將會創建HTML模板(templates)來渲染這些視圖(views)生成的數據。每一個視圖(view)都會渲染模板(template)傳遞變數給它然後會返回一個經過渲染輸出的HTTP響應。

創建列和詳情views

讓我們開始創建一個視圖(view)來展示帖子列。編輯你的blog應用下中views.py文件,如下所示:

from django.shortcuts import render, get_object_or_404nfrom .models import Postndef post_list(request):n posts = Post.published.all()n return render(request,n blog/post/list.html,n {posts: posts})n

你剛創建了你的第一個Django視圖(view)。post_list視圖(view)將request對象作為唯一的參數。記住所有的的視圖(views)都有需要這個參數。在這個視圖(view)中,我們獲取到了所有狀態為已發布的帖子通過使用我們之前創建的published管理器(manager)。

最後,我們使用Django提供的快捷方法render()通過給予的模板(template)來渲染帖子列。這個函數將request對象作為參數,模板(template)路徑以及變數來渲染的給予的模板(template)。它返回一個渲染文本(一般是HTML代碼)HttpResponse對象。render()方法考慮到了請求內容,這樣任何模板(template)內容處理器設置的變數都可以帶入給予的模板(template)中。你會在第三章,擴展你的blog應用學習到如何使用它們。

讓我們創建第二個視圖(view)來展示一篇單獨的帖子。添加如下代碼到views.py文件中:

def post_detail(request, year, month, day, post):n post = get_object_or_404(Post, slug=post,n status=published,n publish__year=year,n publish__month=month,n publish__day=day)n return render(request,n blog/post/detail.html,n {post: post})n

這是一個帖子詳情視圖(view)。這個視圖(view)使用year,month,day以及post作為參數通過給予slug和日期來獲取到一篇已經發布的帖子。請注意,當我們創建Post模型(model)的時候,我們給slgu欄位添加了unique_for_date參數。這樣我們可以確保在給予的日期中只有一個帖子會帶有一個slug,因此,我們能通過日期和slug取回單獨的帖子。在這個詳情視圖(view)中,我們通過使用get_object_or_404()快捷方法來檢索期望的Post。這個函數能取回匹配給予的參數的對象,或者當沒有匹配的對象時返回一個HTTP 404(Not found)異常。最後,我們使用render()快捷方法來使用一個模板(template)去渲染取回的帖子。

為你的視圖(views)添加URL模式

一個URL模式是由一個Python正則表達,一個視圖(view),一個全項目範圍內的命名組成。Django在運行中會遍歷所有URL模式直到第一個匹配的請求URL才停止。之後,Django導入匹配的URL模式中的視圖(view)並執行它,使用關鍵字或指定參數來執行一個HttpRequest類的實例。

如果你之前沒有接觸過正則表達式,你需要去稍微了解下,通過訪問 docs.python.org/3/howto

在blog應用目錄下創建一個urls.py文件,輸入以下代碼:

from django.conf.urls import urlnfrom . import viewsnurlpatterns = [n # post viewsn url(r^$, views.post_list, name=post_list),n url(r^(?P<year>d{4})/(?P<month>d{2})/(?P<day>d{2})/(?P<post>[-w]+)/$,n views.post_detail,n name=post_detail),n]n

第一條URL模式沒有帶入任何參數,它映射到post_list視圖(view)。第二條URL模式帶上了以下4個參數映射到post_detail視圖(view)中。讓我們看下這個URL模式中的正則表達式:

  • year:需要四位數
  • month:需要兩位數。不及兩位數,開頭帶上0,比如 01,02
  • day:需要兩位數。不及兩位數開頭帶上0
  • post:可以由單詞和連字元組成

為每一個應用創建單獨的urls.py文件是最好的方法,可以保證你的應用能給別的項目再度使用。

現在你需要將你blog中的URL模式包含到項目的主URL模式中。編輯你的項目中的mysite文件夾中的urls.py文件,如下所示:

from django.conf.urls import include, urlnfrom django.contrib import adminnnurlpatterns = [n url(r^admin/, include(admin.site.urls)), n url(r^blog/, include(blog.urls,n namespace=blog,n app_name=blog)),n]n

通過這樣的方式,你告訴Django在blog/路徑下包含了blog應用中的urls.py定義的URL模式。你可以給它們一個命名空間叫做blog,這樣你可以方便的引用這個URLs組。

模型(models)的標準URLs

你可以使用之前定義的post_detail URL給Post對象構建標準URL。Django的慣例是給模型(model)添加get_absolute_url()方法用來返回一個對象的標準URL。在這個方法中,我們使用reverse()方法允許你通過它們的名字和可選的參數來構建URLS。編輯你的models.py文件添加如下代碼:

from django.core.urlresolvers import reversenClass Post(models.Model):n # ...n def get_absolute_url(self):n return reverse(blog:post_detail,n args=[self.publish.year,n self.publish.strftime(%m),n self.publish.strftime(%d),n self.slug])n

請注意,我們通過使用strftime()方法來保證個位數的月份和日期需要帶上0來構建URL(譯者註:也就是01,02,03)。我們將會在我們的模板(templates)中使用get_absolute_url()方法。

為你的視圖(views)創建模板(templates)

我們為我們的應用創建了視圖(views)和URL模式。現在該添加模板(templates)來展示界面友好的帖子了。

在你的blog應用目錄下創建以下目錄結構和文件:

templates/n blog/n base.htmln post/n list.htmln detail.htmln

以上就是我們的模板(templates)的文件目錄結構。base.html文件將會包含站點主要的HTML結構以及分割內容區域和一個導航欄。list.htmldetail.html文件會繼承base.html文件來渲染各自的blog帖子列和詳情視圖(view)。

Django有一個強大的模板(templates)語言允許你指定數據的如何進行展示。它基於模板標籤(templates tags), 例如 {% tag %}, {{ variable }}以及模板過濾器(templates filters),可以對變數進行過濾,例如 {{ variable|filter }}。你可以通過訪問 Django documentation ref/templates/builtins/ 找到所有的內置模板標籤(templates tags)和過濾器(filters)。

讓我們來編輯base.html文件並添加如下代碼:

{% load staticfiles %}n<!DOCTYPE html>n<html>n<head>n <title>{% block title %}{% endblock %}</title>n <link href="{% static "css/blog.css" %}" rel="stylesheet">n</head>n<body>n <div id="content">n {% block content %}n {% endblock %}n </div>n <div id="sidebar">n <h2>My blog</h2>n <p>This is my blog.</p>n </div>n</body>n</html>n

{% load staticfiles %}告訴Django去載入django.contrib.staticfiles應用提供的staticfiles模板標籤(temaplate tags)。通過載入它,你可以在這個模板(template)中使用{% static %}模板過濾器(template filter)。通過使用這個模板過濾器(template filter),你可以包含一些靜態文件比如說blog.css文件,你可以在本書的範例代碼例子中找到該文件,在blog應用的static/目錄中(譯者註:給大家個地址去拷貝 levelksk/django-by-example-book )拷貝這個目錄到你的項目下的相同路徑來使用這些靜態文件。

你可以看到有兩個{% block %}標籤(tags)。這些是用來告訴Django我們想在這個區域中定義一個區塊(block)。繼承這個模板(template)的其他模板(templates)可以使用自定義的內容來填充區塊(block)。我們定義了一個區塊(block)叫做title,另一個區塊(block)叫做content

讓我們編輯post/list.html文件使它如下所示:

{% extends "blog/base.html" %}nn{% block title %}My Blog{% endblock %}nn{% block content %}n <h1>My Blog</h1>n {% for post in posts %}n <h2>n <a href="{{ post.get_absolute_url }}">n {{ post.title }}n </a>n </h2>n <p class="date">n Published {{ post.publish }} by {{ post.author }}n </p>n {{ post.body|truncatewords:30|linebreaks }}n {% endfor %}n{% endblock %}n

通過{% extends %}模板標籤(template tag),我們告訴Django需要繼承blog/base.html模板(template)。然後我們在titlecontent區塊(blocks)中填充內容。我們通過循環迭代帖子來展示它們的標題,日期,作者和內容,在標題中還集成了帖子的標準URL鏈接。在帖子的內容中,我們應用了兩個模板過濾器(template filters): truncatewords用來縮短內容限制在一定的字數內,linebreaks用來轉換內容中的換行符為HTML的換行符。只要你喜歡你可以連接許多模板標籤(tempalte filters),每一個都會應用到上個輸出生成的結果上。

打開終端執行命令python manage.py runserver來啟動開發伺服器。在瀏覽器中打開 127.0.0.1:8000/blog/ 你會看到運行結果。注意,你需要添加一些發布狀態的帖子才能在這兒看到它們。你會看到如下圖所示:

django-1-9

這之後,讓我們來編輯post/detail.html文件使它如下所示:

{% extends "blog/base.html" %}nn{% block title %}{{ post.title }}{% endblock %}nn{% block content %}n <h1>{{ post.title }}</h1>n <p class="date">n Published {{ post.publish }} by {{ post.author }}n </p>n {{ post.body|linebreaks }}n{% endblock %}n

現在,你可以在瀏覽器中點擊其中一篇帖子的標題來看帖子的詳細視圖(view)。你會看到類似以下頁面:

django-1-10

添加頁碼

當你開始給你的blog添加內容,你很快會意識到你需要將帖子分頁顯示。Django有一個內置的Paginator類允許你方便的管理分頁。

編輯blog應用下的views.py文件導入Django的頁碼類修改post_list如下所示:

from django.core.paginator import Paginator, EmptyPage, PageNotAnIntegernndef post_list(request):n object_list = Post.published.all()n paginator = Paginator(object_list, 3) # 3 posts in each pagen page = request.GET.get(page)n try:n posts = paginator.page(page)n except PageNotAnInteger:n # If page is not an integer deliver the first pagen posts = paginator.page(1)n except EmptyPage:n # If page is out of range deliver last page of resultsn posts = paginator.page(paginator.num_pages)n return render(request,n blog/post/list.html,n {page: page, n posts: posts})n

Paginator是如何工作的:

  • 我們使用希望在每頁中顯示的對象的數量來實例化Paginator類。
  • 我們獲取到page GET參數來指明頁數
  • 我們通過調用Paginatorpage()方法在期望的頁面中獲得了對象。
  • 如果page參數不是一個整數,我們就返回第一頁的結果。如果這個參數數字超出了最大的頁數,我們就展示最後一頁的結果。
  • 我們傳遞頁數並且獲取對象給這個模板(template)。

現在,我們必須創建一個模板(template)來展示分頁處理,它可以被任意的模板(template)包含來使用分頁。在blog應用的templates文件夾下創建一個新文件命名為pagination.html。在該文件中添加如下HTML代碼:

<div class="pagination">n <span class="step-links">n {% if page.has_previous %}n <a href="?page={{ page.previous_page_number }}">Previous</a>n {% endif %}n <span class="current">n Page {{ page.number }} of {{ page.paginator.num_pages }}.n </span>n {% if page.has_next %}n <a href="?page={{ page.next_page_number }}">Next</a>n {% endif %}n </span>n</div>n

為了渲染上一頁與下一頁的鏈接並且展示當前頁面和所有頁面的結果,這個分頁模板(template)期望一個Page對象。讓我們回到blog/post/list.html模板(tempalte)中將pagination.html模板(template)包含在{% content %}區塊(block)中,如下所示:

{% block content %}n ...n {% include "pagination.html" with page=posts %}n{% endblock %}n

我們傳遞給模板(template)的Page對象叫做posts,我們將分頁模板(tempalte)包含在帖子列模板(template)中指定參數來對它進行正確的渲染。這種方法你可以反覆使用,用你的分頁模板(template)對不同的模型(models)視圖(views)進行分頁處理。

現在,在你的瀏覽器中打開 http://127.0.0.1:8000/blog/。 你會看到帖子列的底部已經有分頁處理:

django-1-11

使用基於類的視圖(views)

因為一個視圖(view)的調用就是得到一個web請求並且返回一個web響應,你可以將你的視圖(views)定義成類方法。Django為此定義了基礎的視圖(view)類。它們都從View類繼承而來,View類可以操控HTTP方法調度以及其他的功能。這是一個可替代的方法來創建你的視圖(views)。

我們準備通過使用Django提供的通用ListView使我們的post_list視圖(view)轉變為一個基於類的視圖。這個基礎視圖(view)允許你對任意的對象進行排列。

編輯你的blog應用下的views.py文件,如下所示:

from django.views.generic import ListViewnclass PostListView(ListView):n queryset = Post.published.all()n context_object_name = postsn paginate_by = 3n template_name = blog/post/list.htmln

這個基於類的的視圖(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。

現在,打開你的blog應用下的urls.py文件,注釋到之前的post_listURL模式,在之後添加一個新的URL模式來使用PostlistView類,如下所示:

urlpatterns = [n # post viewsn # url(r^$, views.post_list, name=post_list),n url(r^$, views.PostListView.as_view(),name=post_list),n url(r^(?P<year>d{4})/(?P<month>d{2})/(?P<day>d{2})/n r(?P<post>[-w]+)/$,n views.post_detail,n name=post_detail),n]n

為了保持分頁處理能工作,我們必須將正確的頁面對象傳遞給模板(tempalte)。Django的ListView通過叫做page_obj的變數來傳遞被選擇的頁面,所以你必須編輯你的post_list_html模板(template)去包含使用了正確的變數的分頁處理,如下所示:

{% include "pagination.html" with page=page_obj %}n

在你的瀏覽器中打開 127.0.0.1:8000/blog/ 然後檢查每一樣功能是否都和之前的post_list視圖(view)一樣工作。這是一個簡單的,通過使用Django提供的通用類的基於類視圖(view)的例子。你將在第十章,創建一個在線學習平台以及相關的章節中學到更多的基於類的視圖(views)。

總結

在本章中,你通過創建一個基礎的blog應用學習了Django web框架的基礎。你為你的項目設計了數據模型(models)並且進行了資料庫遷移。你為你的blog創建了視圖(views),模板(templates)以及URLs,還包括對象分頁。

在下一章中,你會學習到如何增強你的blog應用,例如評論系統,標籤(tag)功能,並且允許你的用戶通過郵件來分享帖子。

譯者總結

終於將第一章勉強翻譯完成了,很多翻譯的句子我自己都讀不懂 - -|||

大家看到有錯誤有歧義的地方請幫忙指出,之後還會隨時進行修改保證基本能讀懂。

按照第一章的翻譯速度,全書都翻譯下來估計要2,3個月,這是非常非常樂觀的估計,每天只有中午休息和下班後大概有兩三小時的翻譯時間。

2016年12月10日發布(沒有進行校對,有很多錯別字以及模糊不清的語句,請大家見諒)

2017年2月7日精校完成(斷斷續續的終於完成了第一章精校,感覺比直接翻譯還要累,繼續加油)

2017年2月10日再次進行精校(感謝大牛@kukoo的精校!)


推薦閱讀:

10min手寫(b六):b面試題解析丨Python實現多連接下載器
python爬蟲抓下來的網頁,中間的中文亂碼怎麼解決?

TAG:Python | Python入门 | Django框架 |