在上一部分已經完成一個簡單的 視圖和請求響應的編寫。
Django採用類似MVC架構的MVT模型
今天主要學習一下model(模型)部分,模式指的是定義數據庫相關的內容,一般放在models.py文件中。
模型是後臺數據唯一的準確來源,它包含數據部分和數據的行爲。一般來說,每一個模型都映射一個數據庫表。
基礎:
- 每個模型都是一個Python的類,這些類繼承
django.db.models.Model
- 模型類的每個屬性都相當於一個數據庫的細分。
- 綜訴說,Django給你一個自動生成訪問數據庫的API;
模型因爲是定義數據庫相關的內容、所以得先在Django框架中進行數據庫的配置。
一、數據庫配置
Django的數據庫配置在項目的配置文件目錄下的settings.py中,即mysite/mysite/settings.py中。
settings.py前面有說過是整個項目的配置中心。Django默認使用的SQLite數據庫,因爲Python源生支持SQLite數據庫,如果使用默認數據庫的話也不需要安裝其他的庫。
在settings.py中數據庫部分如下:
如果使用其他數據庫的話、需要進行一些配置和安裝創建操作
安裝所要使用的數據庫類型的操作模塊,修改DATABASES中default
的鍵值。
-
ENGINE(引擎):
django.db.backends.sqlite3
、django.db.backends.postgresql
、django.db.backends.mysql
、django.db.backends.oracle
,等等。 -
NAME(名稱):數據庫名稱,如果使用的是默認的SQLite,那麼數據庫將作爲一個文件存放於本機,此時的NAME應該是這個文件的完整絕對路徑包括文件名,默認值
os.path.join(BASE_DIR, ’db.sqlite3’)
,將把該文件儲存在你的項目目錄下。
下面舉一個mysql的配置例子:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME':'test',
'HOST':'localhost',
'USER':'root',
'PASSWORD':'123456',
'PORT':'3306',
}
}
- 在使用非SQLite的數據庫時,需要先對數據庫進行創建。
- settings文件中所配置的數據庫用戶也應該具有響應的權限,比如建表,增刪改查等。
下面說一下settings中的其他必要設置
1、TIME_ZONE
設置爲國內所在的時區Asia/Shanghai
。根據需要去配置不同時區。
2、settings文件中頂部的INSTALLED_APPS
設置項。代表所有項目中被激活的Django應用(app)。如果要啓用自建的app的話、需要在這裏進行註冊。前面說過一個項目可以有多個app,一個app也可以被多個項目使用。
INSTALLED_APPS
中會自動包含以下默認條目,它們都是Django自動生成的:
- django.contrib.admin:admin管理後臺站點
- django.contrib.auth:身份認證系統
- django.contrib.contenttypes:內容類型框架
- django.contrib.sessions:會話框架
- django.contrib.messages:消息框架
- django.contrib.staticfiles:靜態文件管理框架
上面的一些應用也需要建立一些數據庫表,所以在使用它們之前我們要在數據庫中創建這些表。使用下面的命令創建數據表:
$ python manage.py migrate
migrate命令將遍歷INSTALLED_APPS
設置中的所有項目,在數據庫中創建對應的表,並打印出每一條動作信息。
同時也可以在你配置的數據庫中去詳細查看創建出來的表。
二、創建模型
Django通過自定義Python類的形式來定義具體的模型,每個模型的物理存在方式就是一個Python的類Class,每個模型代表數據庫中的一張表,每個類的實例代表數據表中的一行數據,類中的每個變量代表數據表中的一列字段。Django通過模型,將Python代碼和數據庫操作結合起來,實現對SQL查詢語言的封裝。Django通過ORM對數據庫進行操作,奉行代碼優先的理念,將Python程序員和數據庫管理員進行分工解耦。
按照投票應用的需求,建立兩個模型:Question
和Choice,問題和選項。
Question包含一個問題和一個發佈日期。Choice包含兩個字段:該選項的文本描述和該選項的投票數。每一條Choice都關聯到一個Question。這些都是由Python的類來體現,編寫的全是Python的代碼,不接觸任何SQL語句。現在,編輯polls/models.py
文件,具體代碼如下:
# polls/models.py
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
每一個類都是django.db.models.Model
的子類。每一個字段都是Field
類的一個實例,例如用於保存字符數據的CharField和用於保存時間類型的DateTimeField,它們告訴Django每一個字段保存的數據類型。
每一個Field實例的名字就是字段的名字(如: question_text 或者 pub_date )。在你的Python代碼中會使用這個值,你的數據庫也會將這個值作爲表的列名。
你也可以在每個Field中使用一個可選的第一位置參數用於提供一個人類可讀的字段名,讓你的模型更友好,更易讀,並且將被作爲文檔的一部分來增強代碼的可讀性。例如 發佈日期。
一些Field類必須提供某些特定的參數。例如CharField需要你指定max_length。這不僅是數據庫結構的需要,同樣也用於數據驗證功能。
有必填參數,當然就會有可選參數,比如在votes裏我們將其默認值設爲0.
最後請注意,我們使用ForeignKey
定義了一個外鍵關係。它告訴Django,每一個Choice關聯到一個對應的Question(注意要將外鍵寫在‘多’的一方)。Django支持通用的數據關係:一對一,多對一和多對多。
三、使用模型
第一步首先要把我們要啓用的app在項目中進行註冊,這樣migrate纔會創建我們註冊了的app的數據庫表和模型的api。
- 創建該app對應的數據庫表結構
- 爲Question和Choice對象創建基於Python的數據庫訪問API
在settings中進行APP的註冊、順便把時區改一下。
要將應用添加到項目中,需要在INSTALLED_APPS
設置中增加指向該應用的配置文件的鏈接。對於本例的投票應用,它的配置類文件PollsConfig是polls/apps.py
,所以它的點式路徑爲polls.apps.PollsConfig
。我們需要在INSTALLED_APPS
中,將該路徑添加進去:
這裏看到說還可以直接簡寫爲“polls”,這個我也沒試過,初學還是嚴謹一點寫全乎吧。
順便把時區從UTC改掉、
TIME_ZONE = 'UTC' ====》 TIME_ZONE = 'Asia/Shanghai'
因爲我們之前已經進行了一次模型的數據庫表操作,這次我們新加入了一個應用的模型。
我們需要執行一個命令,進行模型的改動、並且會先把改動內容記錄下來、最簡單的解釋就是 生成了改動腳本
$ python manage.py makemigrations polls
通過運行makemigrations
命令,Django 會檢測你對模型文件的修改,也就是告訴Django你對模型有改動,並且你想把這些改動保存爲一個“遷移(migration)”。(簡單理解爲數據庫歸檔)migrations
是Django保存模型修改記錄的文件,這些文件保存在磁盤上。在例子中,它就是polls/migrations/0001_initial.py
,你可以打開它看看,裏面保存的都是人類可讀並且可編輯的內容,方便你隨時手動修改。
執行命令後得到下面的結果
生成了要執行的修改腳本、創建了兩個模型。
我們可以先看看腳本
$ python manage.py sqlmigrate polls 0001
打印出來了腳本內容、圖我沒截全
- 實際的輸出內容將取決於使用的數據庫會有所不同。上面的是默認的SQLite庫的的輸出。
- 表名是自動生成的,通過組合應用名 (polls) 和小寫的模型名
question
和choice
。 ( 你可以重寫此行爲。) - 主鍵 (ID) 是自動添加的。( 你也可以重寫此行爲。)
- 按照慣例,Django 會在外鍵字段名上附加 "_id" 。 (你仍然可以重寫此行爲。)
- 生成SQL語句時針對你所使用的數據庫,會爲你自動處理特定於數據庫的字段,例如 auto_increment (MySQL), serial (PostgreSQL), 或integer primary key (SQLite) 。 在引用字段名時也是如此 – 比如使用雙引號或單引號。
- 這些SQL命令並沒有在你的數據庫中實際運行,它只是在屏幕上顯示出來,以便讓你瞭解Django真正執行的是什麼。
這次就按照自動生成的去執行、後面如果正式項目的話、比如就需要對主鍵名稱、以及外建的名稱等去做修改
下面可以繼續去按照最新的修改去執行了
$ python manage.py migrate
migrate命令對所有還未實施的遷移記錄進行操作,本質上就是將你對模型的修改體現到數據庫中具體的表上面。Django通過一張叫做django_migrations的表,記錄並跟蹤已經實施的migrate動作,通過對比獲得哪些migrations尚未提交。
migrations的功能非常強大,允許你隨時修改你的模型,而不需要刪除或者新建你的數據庫或數據表,在不丟失數據的同時,實時動態更新數據庫。我們將在後面的章節對此進行深入的闡述,但是現在,只需要記住修改模型時的操作分三步:
- 在models.py中修改模型;
- 運行
python manage.py makemigrations,
爲改動創建遷移記錄; - 運行
python manage.py migrate
,將操作同步到數據庫。
之所以要將創建和實施遷移的動作分成兩個命令兩步走是因爲你也許要通過版本控制系統(例如github,svn)提交你的項目代碼,如果沒有一箇中間過程的保存文件(migrations),那麼github如何知道以及記錄、同步、實施你所進行過的模型修改動作呢?畢竟,github不和數據庫直接打交道,也沒法和你本地的數據庫通信。但是分開之後,你只需要將你的migration文件(例如上面的0001)上傳到github,它就會知道一切。
四、通過模型API進行操作
進入python交互環境。
可以執行命令進入
python manage.py shell
相比較直接輸入“python”命令的方式進入Python環境,調用manage.py
參數能將DJANGO_SETTINGS_MODULE
環境變量導入,它將自動按照mysite/settings.py
中的設置,配置好你的python shell環境,這樣,你就可以導入和調用任何你項目內的模塊了。
或者你也可以這樣,先進入一個純淨的python shell環境,然後啓動Django,具體如下:
>>> import django
>>> django.setup()
我這裏是直接在pycharm中的Python交互環境來執行的
下面記錄一下練習的內容:
>>> from polls.models import Question, Choice # 導入我們寫的模型類
# 現在系統內還沒有questions對象
>>> Question.objects.all()
<QuerySet []>
# 創建一個新的question對象
# Django推薦使用timezone.now()代替python內置的datetime.datetime.now()
# 這個timezone就來自於Django的依賴庫pytz
from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
# 你必須顯式的調用save()方法,才能將對象保存到數據庫內
>>> q.save()
# 默認情況,你會自動獲得一個自增的名爲id的主鍵
>>> q.id
1
# 通過python的屬性調用方式,訪問模型字段的值
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)
# 通過修改屬性來修改字段的值,然後顯式的調用save方法進行保存。
>>> q.question_text = "What's up?"
>>> q.save()
# objects.all() 用於查詢數據庫內的所有questions
>>> Question.objects.all()
<QuerySet [<Question: Question object>]>
上面的<Question: Question object>
是一個不可讀的內容展示,無法明確的看出來這個類的具體內容,我們可以給模型中的兩個類添加str方法、讓Django在打印對象時顯示一些我們指定的信息。
返回polls/models.py
文件,修改一下question和Choice這兩個類,代碼如下:
from django.db import models
class Question(models.Model):
# ...
def __str__(self):
return self.question_text
class Choice(models.Model):
# ...
def __str__(self):
return self.choice_text
在使用Django的admin站點時也同樣有幫助。
再在Question
類中添加一個判斷是否爲近期發佈的方法
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
# ...
def was_published_recently(self):
# 發佈時間大於當前時間的前一天(一天內發佈的)
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
繼續進行一些練習:
>>> from polls.models import Question, Choice
# 先看看__str__()的效果,直觀多了吧?
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>
# Django提供了大量的關鍵字參數查詢API
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>
# 獲取今年發佈的問卷
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>
# 查詢一個不存在的ID,會彈出異常
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.
# Django爲主鍵查詢提供了一個縮寫:pk。下面的語句和Question.objects.get(id=1)效果一樣.
>>> Question.objects.get(pk=1)
<Question: What's up?>
# 看看我們自定義的方法用起來怎麼樣
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True
# 讓我們試試主鍵查詢
>>> q = Question.objects.get(pk=1)
# 顯示所有與q對象有關係的choice集合,目前是空的,還沒有任何關聯對象。
>>> q.choice_set.all()
<QuerySet []>
# 創建3個choices.
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
# Choice對象可通過API訪問和他們關聯的Question對象
>>> c.question
<Question: What's up?>
# 同樣的,Question對象也可通過API訪問關聯的Choice對象
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3
# API會自動進行連表操作,通過雙下劃線分割關係對象。連表操作可以無限多級,一層一層的連接。
# 下面是查詢所有的Choices,它所對應的Question的發佈日期是今年。(重用了上面的current_year結果)
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
# 使用delete方法刪除對象
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
這部分內容是Django項目的核心,也是動態網站與數據庫交互的核心,對於初學者,再難理解也要理解。