Django2 web實戰01-啟動專案

天星技術團隊發表於2018-07-24

作者:Hubery 時間:2018.7.24

0 前言 = 瞎說

能寫web的語言有好多。python算是難度較低,入門較快的指令碼語言。Django是python的web框架,詳情不多敘,見:

Django-wiki介紹

# Django歷史---------->>>>>這段可以不看。
在 Web 早期階段,開發者手動編寫每個頁面。更新網站要編輯 HTML;
重新設計要重新制作每一個網頁,而  且一次只能改一個網頁。
隨著網站體量的增大,這種方式立馬變得繁瑣、浪費時間,最終變得不切實際。

NCSA(National Center for Supercomputing Applications,國家超級計算應用中心,
第一款圖形 Web 瀏覽器 Mosaic 就是在這裡開發出來的)一群富於創新的黑客
解決了這個問題,他們讓 Web 伺服器派生外部程式,  動態生成 HTML。
他們把這一協議稱為通用閘道器介面(Common Gateway Interface,CGI),
自此,Web 完全  變了樣。如今,很難想象 CGI 帶來的變革:
CGI 不再把 HTML 頁面視作硬碟中儲存的檔案,而是把頁面看  做資源,
可以按需動態生成。 

CGI 的開發促使了第一代動態網站的出現。然而,CGI 自身也有問題:
CGI 指令碼包含大量重複的樣板程式碼,  導致程式碼難以複用,而且新手難以編寫和理解。 

PHP 解決了這些問題中的多數,在 Web 開發界引起了一陣風暴。
PHP 現在是建立動態網站最流行的工具,  
多門類似的語言(ASP、JSP,等等)都參照了 PHP 的設計原則。
PHP 的主要創新是易於使用:PHP 程式碼直  接嵌入普通的 HTML 中;
對學過 HTML 的人來說,學習曲線極為平緩。 

但是,PHP 也有自身的問題:就是因為易於使用,寫出的程式碼凌亂、重複,設計不周。
更糟的是,PHP 沒有  為程式設計師提供多少防止安全漏洞的保護機制,
很多 PHP 開發者意識到這一點再去學習相關的知識就晚了。 

上述問題以及類似的缺陷直接促使了“第三代”Web 開發框架的湧現。
Web 開發的新方式也提升了人們的雄  心,現在 Web 開發者每天所做的工作越來越多。 

Django 就是為了迎接這些雄心而誕生的。
Django 是從真實的應用中成長起來的,由美國堪薩斯州勞倫斯的一個 Web 開發團隊編寫。
它誕生於 2003 年秋天,那時 Lawrence Journal-World 報社的 Web 開發者 
Adrian Holovaty 和 Simon Willison 在嘗試使用 Python 構建應用。 

World Online 團隊負責製作和維護本地的幾個新聞網站,在新聞界特有的快節奏開發
環境中逐漸發展壯大。  
那些網站(包括 LJWorld.com、Lawrence.com 和 KUsports.com)的
記者(和管理層)不斷要求增加功能,
而且整個應用要在緊張的週期內快速開發出來,通常只有幾天或幾小時。
因此,Simon 和 Adrian 別無他法,只  能開發一個節省時間的 Web 開發框架,
這樣他們才能在極短的截止日期之前構建出易於維護的應用。 

經過一段時間的開發後,那個框架已經足夠驅動世界上最大的線上網站了。
2005 年夏天,團隊(彼時 Jacob Kaplan-Moss 已經加入)決定把框架作為開源軟體釋出出來。
他們在 2005 年 7 月釋出了那個框架,將其命名  為 Django——取自爵士吉他手 Django Reinhardt。 

這段歷史相當重要,因為說清了兩件要事。首先是 Django 的“發力點”。
Django 誕生於新聞界,因此它提供了幾個特別適合“內容型”網站使用的功能(如管理後臺)。
這些功能適合 Amazon.com、craigslist.org 和 washingtonpost.com 這樣動態的資料庫驅動型網站使用。 

不過,不要因此而灰心。雖然 Django 特別適合開發這種網站,
但是這並沒有阻礙它成為開發任何動態網站的有效工具。(某些方面“特別”高效與某些方面不高效是由區別的。) 

第二點是,Django 最初的理念塑造了開源社群的文化。Django是從真實程式碼中提取出來的,
而不是科研專案或商業產品,它專注於解決Django的開發者自身所面對的問題。因此,
Django一直在積極改進,幾乎每一天都有變化。
Django框架的維護者一心確保它能節省開發者的時間,確保開發出的應用易於維護,
而且在高負載下的效能良好。 

使用 Django 能在極短的時間內構建全面動態的網站。Django 的主旨是讓你集中精力在有趣
的工作上,減輕  重複勞作的痛苦。為此,它為常用的 Web 開發模式提供了高層抽象,為常
見的程式設計任務提供了捷徑,還為解  決問題提供了清晰的約定。與此同時,Django 儘量做到
不擋路,允許你在必要時脫離框架。 
複製程式碼

寫專案就好比騎自行車,看完上面的背景,接下來就上程式碼體驗了,直接開擼。

按照草擬的專案結構,大致上有5塊內容:

  • 啟動專案
  • 新增使用者
  • 檔案安全
  • 快取列表
  • 專案部署

00.專案結構圖pro.jpg

專案初步規劃要完成一個電影列表/詳情的檢視,評論,投票,得分,支援檔案上傳,內容安全, 註冊/登陸/登出,內容快取等。

大致構思了下,要寫好還是需要處理不少細節問題的。比如瞭解下Django的編碼套路,處理關聯關係,資料庫的CRUD等,一步步的來吧。

本文主要實現專案的第一部分,啟動專案部分。

1 環境準備

  • 安裝python 預設的就是最新的3.6.*

不同平臺安裝方式可能不同,自行百度一個。  www.python.org/downloads/

  • 安裝django 預設的是最新的2.*
pip:pip install packages縮寫,是python的包管理工具,用於安裝python包。
複製程式碼
pip install django // 安裝最新版
pip install django==2.*.* // 安裝具體版本
複製程式碼
  • 安裝pycharm IDE

從網上下載一個就行了,然後搜一個啟用碼完事兒。

  • 安裝mysql

什麼平臺都一樣,mac/linux/win,都記得配置環境變數;我用的Mac環境。

這個軟體安裝部分,還是得多百度,可能存在平臺差異。

順便提一句,如果有任何語言的開發基礎,其實回頭看python都會覺得簡單,老說沒有python基礎,看書啊! 突然宋小寶的畫面出現了,哈哈哈。有時候一門語言可能知道40%左右就可以開幹了,邊擼邊回頭翻書就好。

2 建立專案 MyMovie

命令列建立

django-admin

cd workspace
django-admin startproject MyMovie
複製程式碼

可以用pycharm建立 預設專案結構

MyMovie\
    MyMovie\
        __init__.py\
        settings.py\
        urls.py\
        wsgi.py\
    manage.py\
複製程式碼

專案結構解析:

  • 外層MyMovie根目錄是專案容器。這個外層名稱對Django來說沒什麼用,可以根據喜好改名。

  • 根目錄manage.py 是一個命令列實用指令碼,可以根據不同方式與Django專案互動。如資料庫遷移,跑測試,啟動開發server等,會經常用到manage.py

  • 內層MyMovie目錄,是專案的Python包。匯入這裡面的內容時要用該目錄名稱。如MyMovie.urls。

  • 內層MyMovie/init.py是個空檔案,目的是讓Python知道該目錄是Python包。

  • 內層MyMovie/settings.py是Django專案的配置。

  • 內層MyMovie/urls.py是整個專案的URL配置,即Django驅動的網站的目錄。每個Web app的請求都會被指向urls檔案中已配置過的匹配的第一個view。

  • 內層MyMovie/wsgi.py是相容WSGI的web伺服器的介面,用於服務專案。SWGI,Web Server Gateway Interface,讓Django專案與web伺服器互相互動的介面。如將Django專案部署到Docker上。

2.1 解析settings.py檔案

TIME_ZONE = 'UTC'. # 時區

# Django中自帶的啟用的全部Django應用,自建的app也要配置到這裡面
INSTALLED_APPS = [
   'django.contrib.admin', # 管理後臺
   'django.contrib.auth', # 身份驗證系統
   'django.contrib.contenttypes', # 內容型別框架
   'django.contrib.sessions', # 會話框架
   'django.contrib.messages', # 訊息框架
   'django.contrib.staticfiles', # 管理靜態檔案的框架
]
複製程式碼

Django專案中預設包含這些app,為常見場景做的約定。 如果用到資料庫表,使用之前要在資料庫中先建表,為此,執行以下命令:

python manage.py migrate 
複製程式碼

資料庫配置  用mysql示例,前提是系統中有資料庫,且user/password等資訊都OK; 終端建立mymovie資料庫

# 命令列進入mysql 並輸入密碼
mysql -uroot -p
# 檢視當前存在的資料庫
mysql> show databases;
# 建立資料庫
mysql> create database mymovie;
Query OK, 1 row affected (0.10 sec)
mysql> show databases;
+--------------------+
| Database  |
+--------------------+
| blog_project_db |
| information_schema |
| mymdb |
| mymovie |
| mysql |
| performance_schema |
| sys |
+--------------------+
7 rows in set (0.00 sec)
mysql>
複製程式碼

在MyMovie/init.py檔案中配置mysql資料庫:

import pymysql

pymysql.install_as_MySQLdb()
複製程式碼

settings.py檔案中配置mysql

DATABASES = {
   'default': {
     'ENGINE': 'django.db.backends.mysql', # 資料庫引擎
     'NAME': 'mymovie''USER': 'root',
     'PASSWORD': 'root@123456',
     'HOST': '127.0.0.1',
     'PORT': '3306',
   }
}
複製程式碼

2.2 建立core應用

Django應用遵循MVT模式,與傳統的MVC模式無異,只是叫法不同:

MVT模型 職責說明
Models 用來處理資料庫讀寫操作
Views 用來處理HTTP請求,啟動模型的操作然後返回HTTP響應資料
Templates 用來展示響應內容

2.2.1 命令列 建立app

cd MyMovie
python manage.py startapp core
複製程式碼

2.2.2 註冊新建的core app

settings.py檔案中 新增剛建立的core app 每個註冊的app後面必須帶【,】

INSTALLED_APPS = [
   'core',
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
]
複製程式碼

2.2.3 建立Model Movie

Django model是從Model衍生繼承過來的,有多個Fields欄位。

資料庫方面,一個Model對應一個資料庫表,一個Model例項對應一行,Field欄位對應一列。

Django model 對應資料庫
Model類
Model例項
Field欄位

用Django的ORM,用Python和Django來寫model類來處理資料庫,而不是直接寫SQL語句。

core/models.py

from django.db import models
# 編寫第一個model Movie
class Movie(models.Model):
 NOT_RATED = 0
 RATED_G = 1
 RATED_PG = 2
 RATED_R = 3
 # 評分 級別
 RATINGS = (
 (NOT_RATED, 'NR - 沒有評分'),
 (RATED_G, 'G - 普通觀眾'),
 (RATED_PG, 'PG - 父母的引導和規範'),
 (RATED_R, 'R - 限制級'),
 )

 title = models.CharField(max_length=140)
 plot = models.TextField()
 year = models.PositiveIntegerField()
 rating = models.IntegerField(
 choices=RATINGS,
 default=NOT_RATED
 )

 runtime = models.PositiveIntegerField()
 website = models.URLField(blank=True)

 def __str__(self):

 return '{} ({})'.format(self.title, self.year)
複製程式碼
  • Movie繼承自models.Model,models.Model是所有Django模型的基類。

  • title 會轉成資料庫表中的一個列欄位,長度為140,型別為varchar;

  • plot 會轉成資料庫的text列,

  • year 會轉成資料庫的integer列,Django儲存之前會驗證資料,確保是0或者更高。

  • rating 多選列。是一個integer列。可選引數choices有一個集合的遊標。

Django會向model種新增一個例項方法:get_rating_display(),返回儲存在模型中符合第二個引數條件的資料。

  • runtime 與year一樣。

  • website 大多數資料庫列欄位沒有URL型別,但資料驅動web應用經常需要儲存URL。URLField預設是長度200的varchar列,也可以通過max_length引數設定。URLField自帶了驗證邏輯,可以鑑別該URL是否為有效。blank引數由admin應用使用,用來確認該引數是否可以為空。

__str__(self)方法,將Django模型轉化成視覺化的字串,類似java中的toString()。有助於debug以及輸出物件內容。

Django的ORM會自動增加一個字增長的列:id。我們無需關心該欄位。

Django的DRY理論:Donnot Repeat Yourself

2.2.4 資料庫遷移

我們有了模型,需要在資料庫中建立匹配該模型的表table。

用Django可以生成該表。

cd MyMovie
python manage.py makemigrations
# 一鍵操作  遷移所有的app的資料庫  根據model建表
python manage.py migrate  
複製程式碼

終端執行結果

$ python manage.py migrate
Operations to perform:
 Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
 Applying contenttypes.0001_initial... OK
 Applying auth.0001_initial... OK
 Applying admin.0001_initial... OK
 Applying admin.0002_logentry_remove_auto_add... 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 auth.0007_alter_validators_add_error_messages... OK
 Applying auth.0008_alter_user_username_max_length... OK
 Applying auth.0009_alter_user_last_name_max_length... OK
 Applying sessions.0001_initial... OK
複製程式碼

執行完之後,資料庫中就會生成一張表: core_movie

mysql> show tables;
+----------------------------+
| Tables_in_mymovie |
+----------------------------+
| auth_group  |
| auth_group_permissions  |
| auth_permission |
| auth_user |
| auth_user_groups  |
| auth_user_user_permissions |
| core_movie  |
| django_admin_log  |
| django_content_type |
| django_migrations |
| django_session  |
+----------------------------+
11 rows in set (0.00 sec)
mysql>
複製程式碼

在Django的應用中才會存在資料庫遷移,而不是在專案目錄。

2.2.5 建立第一個Movie

類似python,Django提供了一個互動式的REPL來嘗試一下。 Django的互動指令碼完全連結資料庫,所以我們可以在shell中進行model的增刪改查。

cd MyMovie
python manage.py shell
# 執行CRUD
複製程式碼

也可以在pycharm的python console視窗中進行操作,一樣的道理。 建立一條Movie資料:

sleuth = Movie.objects.create(title='Sleuth', plot='an snobbish writer who loves games', year=1972, runtime=138,)
clear
Traceback (most recent call last):
 File "<input>", line 1, in <module>
NameError: name 'clear' is not defined
sleuth.id
3
sleuth.get_rating_display()
'NR - 沒有評分'
複製程式碼

檢視資料庫表中是否有剛才建立的Movie物件; 可以在命令列中訪問資料庫表,也可以在pycharm的database視窗中視覺化檢視。

mysql> select * from core_movie;
+----+--------+------------------------------------+------+--------+---------+---------+
| id | title | plot  | year | rating | runtime | website |
+----+--------+------------------------------------+------+--------+---------+---------+
| 1 | Sleuth | an snobbish writer who loves games | 1972 | 0 |  138 |  |
+----+--------+------------------------------------+------+--------+---------+---------+
1 row in set (0.00 sec)
mysql>
複製程式碼

objects, 是模型model的預設manager。是一個查詢model表的介面。同時提供了一個**create()**方法來建立和儲存例項。每個model必須至少有一個manager,Django預設提供了一個manager。通常可以自定義manager,這個會在後續會詳述。

id,是資料庫表的主鍵,Django自動生成的。

get_rating_display(), 由Django生成,因為rating欄位提供了一個choices的元祖。我們在呼叫create()的時候沒有提供rating欄位值,因為rating欄位有預設值0.

get_rating_display()方法查詢並返回響應的value。Django將生成一個方法,遍歷擁有choices引數的所有欄位。

接下來用Django Admin app來建立一個後臺,來管理movies。

2.2.6 建立Movie的admin

快速生成一個後端UI,來快速填充專案資料。 為了讓Django的admin app使用我們新建的models,執行下列步驟:

1 註冊我們的model 2 建立一個superuser,可以訪問後臺 3 執行開發server 4 瀏覽器中訪問後臺

將Movie註冊到admin中, core/admin.py

from django.contrib import admin
from core.models import Movie
admin.site.register(Movie)
複製程式碼

註冊成功。 建立一個superuser hubery hubery2018

$ python manage.py createsuperuser
Username (leave blank to use 'hubery'): hubery
Email address: 934531487@qq.com
Password:
Password (again):
Superuser created successfully.
複製程式碼

執行開發server

$ python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
July 19, 2018 - 05:35:58
Django version 2.0.6, using settings 'MyMovie.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
複製程式碼

瀏覽器中開啟提示的連結:

http://127.0.0.1:8000/
複製程式碼

會顯示出Django載入成功的頁面。

01.瀏覽器檢視開發server.jpg

進入admin後臺頁: 瀏覽器中輸入:

http://127.0.0.1:8000/admin
複製程式碼

根據提示,輸入剛才建立的superuser資訊,成功登陸進去之後,可以可以線上編輯資料。

02.admin後臺頁面可以視覺化的編輯內容.png

為了方便,我們可以趁熱打鐵的錄入一批movies。

03.admin後臺手動錄入資料.jpg

2.2.7 建立MovieList檢視

當Django接收到一個請求request,它用request的路徑和專案的URLConf來匹配,找到找到在URLConf中配置的view並將該請求傳進去,這個view會返回一個HTTP response。

Django檢視View可以是函式,也可以是

FBVs Function-Based Views 基於函式的View CBVs Class-Based Views 基於類的View

我們寫一個檢視展示movie列表。

core/views.py

from django.http import HttpResponse
from django.views.generic import ListView
from core.models import Movie

# 基於class的檢視
class MovieList(ListView):
   model = Movie
   # 基於函式的檢視
   def test(request):
     return HttpResponse('hello world.')
複製程式碼

ListView至少需要一個model屬性。將會查詢model的所有行rows,將結果傳遞給template展示,在返回結果中渲染出template。同時提供了好多回撥函式,使得我們可以用來替換預設行為。

ListView是怎麼知道如何查詢Movie的所有物件的? 針對這個問題,我們得研究下managerQuerySet類。每個model都有一個預設manager。Manager主要用來提供各種方法來查詢物件,如all(), 這些方法返回QuerySet。

QuerySet類是Django查詢資料庫的結果集,有很多方法,包含:**filter()**來限制查詢結果。QuerySet一個很好的特性是:它是惰性的,在我們從QuerySet獲取model之前,不會賦值。另一個不錯的功能是:例如filter()之類的方法,使用查詢表示式,這些表示式可以是欄位名稱,也可以跨越關係模型。

我們將會在整個專案中這樣處理。

注:所有manager類(objects)都有一個all()方法,返回一個QuerySet,

Movie.objects.all()
複製程式碼

相當於:

select * from core_movie;
複製程式碼

所以,ListView會檢查該ModelList檢視是否擁有model屬性,如果存在,它會知道Model類擁有一個預設manager(即objects),並且這個manager擁有all()方法

2.2.8 新增用來展示movieList的模版template

模版引擎會搜尋project和app工程結構下的templates檔案目錄,根據檢視view來找相應的template;

ListView同時也提供了一個約定/規矩/慣例template的存放目錄位置,我們自定義的模版必須遵守這個約定,如: <app_name>/<model_name>_list.html

core/movie_list.html

即:在core app中, app_name是core, model_name是Movie; 所以模版檔名為:  <model_name>_list.html =>>> movie_list.html

movie_list.html模版的存放位置: core/templates/core/movie_list.html 這樣,模版引擎就能準確找到檢視對應的模版。

測試:

  1. 將movie_list.html的目錄變化一下, core/templates/core/movie_list.html  變成: core/templates/movie_list.html 重新整理瀏覽器就會報錯,在模版引擎中對應的正確位置上沒發現模版:
TemplateDoesNotExist at /movies
core/movie_list.html
複製程式碼
  1. 然後再將目錄還原: core/templates/movie_list.html 變成: core/templates/core/movie_list.html

08自定義模版的存放位置.png

所以, 切記一定要嚴格遵守模版引擎的約定:正確存放自定義模版的位置,且:模版檔名也要按照約定來命名,兩個條件缺一不可。 依照慣例,模版的命名: core/movie_list.html settings.py的配置檔案中,有一個預設的template目錄,將從這個目錄查詢模版。 如果不遵從這個慣例約定,可能就載入不出模版。 這個目錄可以被各個app覆蓋。 'DIRS': [os.path.join(BASE_DIR, 'templates')] 'APP_DIRS': True,

TEMPLATES = [
 {
 'BACKEND': 'django.template.backends.django.DjangoTemplates',
 'DIRS': [os.path.join(BASE_DIR, 'templates')]
 ,
 'APP_DIRS': True,
 'OPTIONS': {
 'context_processors': [
 'django.template.context_processors.debug',
 'django.template.context_processors.request',
 'django.contrib.auth.context_processors.auth',
 'django.contrib.messages.context_processors.messages',
 ],
 },
 },
]
複製程式碼

開始編碼template core/templates/core/movie_list.html 沒有相應目錄,就地建立

<!DOCTYPE html>
<html>
<body>
 <ul>
 {% for movie in object_list %}
 <li>
 {{ movie }}
 </li>
 {% empty %}
 <li>
 沒有電影列表。
 </li>
 {% endfor %}
 </ul>
 <p>
 用的 https?
 {{ request.is_secure|yesno }}
 </p>
</body>
</html>
複製程式碼

Django的template是標準HTML,內建了變數variables和標籤tags。

內建引數 引數說明
{{variables}} variables等待被賦值的變數, 將指定變數的值插入到這裡
{% for item in item_list %} 模版標籤 只要能讓模版系統做些事兒的 就是標籤
{{ ship_date|date: “F J, Y” }} 過濾器類似Unix的管道,把ship_date傳遞給date過濾器,並給date過濾器指定“F J, Y”引數

其他特性用到了再細分討論,先忙正事兒。 [Django內建template和filters](docs.djangoproject.com/en/2.0/ref/…)

按照這個邏輯,模版中,{% extends '' %} {% block %}  {% for movie in object_list %} {% url %} 是標籤;  {{ movie }}是變數。

2.2.9 通過URLConf將請求定位到檢視View

接下來就是將檢視連結到URLConf上。

之前的篇幅中已經準備好了model,view和template,我們得利用URLConf告知Django哪個request應該被路由指向到Movielist檢視上。

什麼是URLConf?每個專案中都有個根URLConf,由Django建立的。

對於Django開發者來說,最好的辦法是每個app應用都有自己的URLConf。那麼接下來,根URLConf會通過**include()**函式 引入各個app目錄下的URLConf。

在core下建立一個URLConf, 即建立檔案:core/urls.py,

from django.urls import path
from core import views

app_name = 'core'

urlpatterns = [
 # 基於class的檢視,必須呼叫View基類的靜態函式as_view()
 # 返回一個可呼叫的字串  傳入path中
 path('movies',
 views.MovieList.as_view(),
 name='MovieList',),
 # 基於函式的檢視,可以直接將函式傳入url,可直接執行
 url(r'$', views.test),
]
複製程式碼

05.檢視屬性不同註冊方式不同.jpg

最簡單的理解,一個URLConf就是一個擁有urlpatterns屬性的module,是一個path集合

一個path由一個描述字串的字串組成,描述了有問題的和可呼叫的字串。

FBVs Function-Based Views 基於函式的View CBVs Class-Based Views 基於類的View

CBVs不可呼叫,所以View的基類用一個靜態函式**as_view()**返回一個可呼叫的字串;

FBVs可以作為回撥直接傳入,不需要**()操作符**,直接可以執行。

每個**path()**都應該起個名字,這樣對於當我們需要在template中引用這個path時很有用。

既然一個URLConf可以被其他URLConf引用include(),所以我們可能不知道view的全路徑。

Django提供了一個reverse()函式和url模版標籤,可以通過一個name找到一個view的全路徑。

app_name變數,設定了URLConf屬於哪個app。這樣,我們可以區分一個被命名的path,不至於多個app中有同名path的時候Django無法區分。類似於名稱空間如,appA:index, appB:index, appC:index。

將core/urls.py連結到MyMovie/urls.py; 編輯MyMovie/urls.py

from django.contrib import admin
from django.urls import path, include
import core.urls

urlpatterns = [
 path('admin/', admin.site.urls),
 path('', include(core.urls, namespace='core')),
]
複製程式碼

2.2.10 執行開發server 檢視結果

cd MyMovie
python manage.py runserver
複製程式碼

瀏覽器中輸入:

http://127.0.0.1:8000/movies
複製程式碼

其中,基於函式的檢視直接返回了HttpResponse物件,可以在任何http測試工具中測試:

http://127.0.0.1:8000/test
複製程式碼

這個外界測試Http介面,後續會單獨介紹如何寫api。

2.3 建立Movie相關頁面

既然已經完成了專案的佈局,那我們可以加快進度。我們已經記錄了每個movie的資訊。我們來建立一個檢視,專門顯示具體的movie資訊。

我們需要做三件事:

2.3.1 建立MovieDetailView

類似Django提供的ListView,同時也提供了DetailView,來顯示單個model的詳細資訊。

core/views.py

from django.http import HttpResponse
from django.shortcuts import render
from django.views.generic import (
 ListView, DetailView
)
from core.models import Movie

# 基於class的檢視
class MovieList(ListView):
 model = Movie

# movie詳情 檢視
class MovieDetail(DetailView):
 model = Movie
複製程式碼

DetailView需要一個path()物件,引入一個pk或者slug,因此DetailView可以向QuerySet傳遞引數來查詢特定的model例項。

一個slug是一個簡短的URL友好標籤,通常用於內容繁多的網站。

2.3.2 建立movie_detail.html模版

已經有了檢視view,那麼緊接著建立一個對應的模版。

Django的template支援複用,標記block部分,其他模版可以重寫該block部分。這樣就可以抽象出一個基類模版,其他模版可以繼承該基類模版進行擴充。

基類模版, MyMovie/templates/base.html

目錄預設可能沒有,手動建立

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>
 {% block title %} {% endblock %}
 </title>
 <style>
 .mymdb-masthead {
 background-color: #EEEEEE;
 margin-bottom: 1em;
 }
 </style>
</head>
<body>
 <div class="mymdb-masthead">
 <div class="container">
 <nav class="nav">
 <div class="navbar-brand">MyMDB</div>
 <a class="nav-link"
 href="{% url 'core:MovieList' %}">
 Movies
 </a>
 </nav>
 </div>
 </div>
 <div class="container">
 <div class="row">
 <div class="clo-sm-8 mymdb-main">
 {% block main %} {% endblock %}
 </div>
 <div class="col-sm-3 offset-sm-1 mymdb-sidebar">
 {% block sidebar %}
 {% endblock %}
 </div>
 </div>
 </div>
</body>
</html>
複製程式碼

該base.html包含三塊block:title/main/sidebar

{%block %}: 告訴模版引擎,這部分內容可以被子模版覆蓋。

{% block title %}MyMovie{% endblock %}, 建立了一個title的block,其他templates可以替換該部分。如果其他模版沒替換,即沿用預設基類的。

href="{% url 'core:MovieList' %}" url標籤會生成一個URL的path,URL的名字應該這樣命名:<app_namespace>:, 比如:core是namespace,name是MovieList,那麼url標籤後面跟的就是core:MovieList。

建立一個簡單的模版 core/templates/core/movie_detail.html

{% extends 'base.html' %}
{% block title %}
 {{ object.title }} - {{ block.super }}
{% endblock %}
{% block main %}
 <h1>{{ object }}</h1>
 <p class="lead">
 {{ object.plot }}
 </p>
{% endblock %}
{% block sidebar %}
 <div>
  這個電影排名:
 <span class="badge badge-primary">
 {{ object.get_rating_display }}
 </span>
 </div>
{% endblock %}
複製程式碼

這個模版只有很少的HTML部分,因為大部分HTML在base.html中已經包含了。movie_detail.html需要做的就是給base.html中定義的blocks提供數值。

看下新的標籤:

{% extends 'base.html' %} 用extends來實現繼承另外一個模版。Django先找到base模版先執行,然後再替換blocks。

{% object.title %} - {% block.super %}

{% block.super %} 魔法變數,從base模版中的block中獲取內容,提供base模版中渲染後的文字。

{{ object.get_rating_display }} Django模版不用()來執行函式,直接用函式名字就可以執行。

2.3.3 將MovieDetail檢視配置到URLConf中

from django.conf.urls import url
from django.urls import path
from core import views

app_name = 'core'
urlpatterns = [
 # 基於class的檢視,必須呼叫View基類的靜態函式as_view()
 # 返回一個可呼叫的字串  傳入path中
 path('movies',
 views.MovieList.as_view(),
 name='MovieList',),
 # 向MovieDetail檢視中傳遞pk引數 獲取特定的movie物件
 path('movie/<int:pk>',
 views.MovieDetail.as_view()),
 # 基於函式的檢視,可以直接將函式傳入url,可直接執行
 url(r'$', views.test),
]
複製程式碼

瀏覽器中輸入:

http://127.0.0.1:8000/movie/2
複製程式碼

待截圖顯示

2.4 處理Movie列表分頁 點選跳轉

2.4.1 給MovieList.html增加點選跳轉到詳情頁功能

core/movie_list.html

{% extends 'base.html' %}
{% block title %}
 所有Movies
{% endblock %}
{% block main %}
 <ul>
 {% for movie in object_list %}
 <li>
 <a href="{% url 'core:MovieDetail' pk=movie.id %}">
 {{ movie }}
 </a>
 </li>
 {% endfor %}
 </ul>
{% endblock %}
複製程式碼

url標籤,用了一個叫pk(primary key)的引數,因為MovieDetail URL需要一個pk引數。如果沒有引數,渲染過程中Django會拋一個NoReverseMatch的異常,導致500錯誤。

重新整理瀏覽器

http://127.0.0.1:8000/movies
複製程式碼

會發現movies列表顯示的是超連結樣式,點選可以進入到詳情頁。 截圖:

2.4.2 設定排序

Model中新增一個內部類 Meta 指定ordering欄位

from django.db import models
# 編寫第一個model Movie
class Movie(models.Model):
 #省略之前的部分 
 # Model內部類 可以指定Model的資訊。
 class Meta:
 ordering = ('-year', 'title')
 def __str__(self):
 return '{} ({})'.format(self.title, self.year)
複製程式碼

ordering欄位,指定排序參照的欄位,year降序,title。對應sql語句:

order by year desc, title;
複製程式碼

重新整理瀏覽器,可以發現,movie列表是降序排列。

2.4.3 新增分頁

分頁有點兒問題 程式碼先放上, 先跳過這部分

既然movies已經排序了,直接給加上分頁。Django的ListView檢視額外已經內建了分頁,所以直接用就好。

Pagination由GET引數控制page頁的顯示。

將分頁功能加到block main的底部;

{% extends 'base.html' %}
{% block title %}
 所有Movies
{% endblock %}
{% block main %}
 <ul>
 {% for movie in object_list %}
 <li>
 <a href="{% url 'core:MovieDetail' pk=movie.id %}">
 {{ movie }}
 </a>
 </li>
 {% endfor %}
 </ul>
 {% comment 新增分頁 先註釋掉 %}{% if is_paginated %}
 <nav>
 <ul class="pagination">
 <li class="page-item">
 <a href="{% url 'core:MovieList' %}?page=1"
 class="page-link">First</a>
 </li>
 {% if page_obj.has_previous %}
 <li class="page-item">
 <a class="page-link"
 href="{% url 'core:MovieList' %}?page={{ page_obj.previous_page_num}}">
 {{ page_obj.previous_page_number }}
 </a>
 </li>
 {% endif %}
 <li class="page-item active">
 <a href="{% url 'core:MovieList' %}?page={{ page_obj.number }}">
 {{ page_obj.number }}
 </a>
 </li>
 {% if page_obj.has_next %}
 <li class="page-item">
 <a href="{% url 'core:MovieList' %}?page={{ page_obj.next_page_number }}"
 class="page-link">
 {{ page_obj.next_page_number }}
 </a>
 </li>
 {% endif %}
 <li>
 <a href="{% url 'core:MovieList' %}?page=last"
 class="page-link">
 Last
 </a>
 </li>
 </ul>
 </nav>
 {% endif %}{% endcomment %}
{% endblock %}
複製程式碼

2.4.4 404錯誤

如果URL輸入錯誤,就會出現404,資源不存在錯誤;可以自定義404提示頁面,這個也無傷大雅,回頭補充。

2.4.5 測試檢視和模版

類似於java的junit測試,先不寫了。

2.5 新增Person和model關係

建模部分,涉及到外來鍵, 一對多, 多對多關係的梳理。

Django Web實戰-01建立專案-model 擴充套件

3 小結

綜上,你會發現,用Django的套路是:

  • 配置專案屬性,settings.py
  • 建立一個應用core
  • 在core/models.py中編寫模型處理資料庫資料
  • 再core/views.py中編寫檢視用來處理HTTP
  • 在core/urls.py中配置應用路徑,使得檢視view可到達
  • 再將core/urls.py連結到Django專案的urls.py中
  • 如果需要展示檢視的http返回內容或者資料庫返回內容,藉助Django模版

Django用的是MVT模式,本質就是MVC。

至於MVT各個模組之間怎麼銜接起來的,那就問Django了呵。開個玩笑,Django框架本身處理的非常完美,後續好好梳理下這個呼叫的流程。

宗旨:儘量不重複造輪子。

Django2 Web實戰01-啟動專案-model 擴充套件

關於原始碼,還在梳理整合,需要留言,回頭釋出到github上。

天星技術團QQ:557247785

歡迎來擾

相關文章