HelloDjango 系列教程:第 04 篇:Django 遷移、運算元據庫

削微寒發表於2019-08-01

HelloDjango 系列教程:第 04 篇:Django 遷移、運算元據庫

文中涉及的示例程式碼,已同步更新到 HelloGitHub-Team 倉庫

我們已經編寫了部落格資料庫模型的程式碼,但那還只是 Python 程式碼而已,django 還沒有把它翻譯成資料庫語言,因此實際上這些資料庫表還沒有真正的在資料庫中建立。

遷移資料庫

為了讓 django 完成翻譯,建立好這些資料庫表,我們再一次請出我的工程管理助手 manage.py。切換到 manage.py 檔案所在的目錄(專案根目錄)下,分別執行 pipenv run python manage.py makemigrationspipenv run python manage.py migrate 命令:

> pipenv run python manage.py makemigrations
Migrations for 'blog':
  blog\migrations\0001_initial.py
    - Create model Category
    - Create model Tag
    - Create model Post
    
> pipenv run python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, 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 admin.0003_logentry_add_action_flag_choices... 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 auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying blog.0001_initial... OK
  Applying sessions.0001_initial... OK

當我們執行了 python manage.py makemigrations 後,django 在 blog 應用的 migrations 目錄下生成了一個 0001_initial.py 檔案,這個檔案是 django 用來記錄我們對模型做了哪些修改的檔案。目前來說,我們在 models.py 檔案裡建立了 3 個模型類,django 把這些變化記錄在了 0001_initial.py 裡。

不過此時還只是告訴了 django 我們做了哪些改變,為了讓 django 真正地為我們建立資料庫表,接下來又執行了 python manage.py migrate 命令。django 通過檢測應用中 migrations 目錄下的檔案,得知我們對資料庫做了哪些操作,然後它把這些操作翻譯成資料庫操作語言,從而把這些操作作用於真正的資料庫。

你可以看到命令的輸出除了 Applying blog.0001_initial... OK 外,django 還對其它檔案做了操作。這是因為除了我們自己建立的 blog 應用外,django 自身還內建了很多應用,這些應用本身也是需要儲存資料的。可以在 settings.py 的 INSTALLED_APP 設定裡看到這些應用,當然我們目前不必關心這些。

blogproject/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
]

對於瞭解資料庫語言的人,你可以執行下面的命令看看 django 究竟為我們做了什麼:

> pipenv run python manage.py sqlmigrate blog 0001

你將看到輸出了經 django 翻譯後的資料庫表建立語句,這有助於你理解 django ORM 的工作機制。

選擇資料庫版本

我們沒有安裝任何的資料庫軟體,django 就幫我們遷移了資料庫。這是因為我們使用了 Python 內建的 SQLite3 資料庫。

SQLite3 是一個十分輕巧的資料庫,它僅有一個檔案。你可以看一到專案根目錄下多出了一個 db.sqlite3 的檔案,這就是 SQLite3 資料庫檔案,django 部落格的資料都會儲存在這個資料庫檔案裡。

django 在 settings.py 裡為我們做了一些預設的資料庫配置:

blogproject/settings.py

## 其它配置選項...
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
## 其它配置選項...

可以看到預設的資料庫引擎就是使用的 SQLite3。

當然一些人傾向於使用 MySQL 等大型資料庫,至於 django 如何配置 MySQL 這裡就不贅述了,你可以自行使用搜尋引擎或者查閱 django 的官方文件解決。對於一個小型部落格而言,SQLite3 資料庫足以勝任。

用 django 的方式運算元據庫

資料庫最主要的操作就是往裡面存入資料、從中取出資料、修改已儲存的資料和刪除不再需要的資料(合稱 CURD)。和建立資料庫表一樣,django 為這些操作提供了一整套方法,從而把我們從資料庫語言中解放出來。我們不用學習如何利用資料庫語言去完成這些操作,只要簡單地呼叫幾個 Python 函式就可以滿足我們的需求。

存資料

先在命令列中來探索一下這些函式,感受一下如何用 django 的方式來運算元據庫。在 manage.py 所在目錄下執行 pipenv run python manage.py shell 命令:

> pipenv run python manage.py shell
Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

這開啟了一個互動式命令列。

首先我們來建立一個分類和一個標籤:

>>> from blog.models import Category, Tag, Post
>>> c = Category(name='category test')
>>> c.save()
>>> t = Tag(name='tag test')
>>> t.save()

我們首先匯入 3 個之前寫好的模型類,然後例項化了一個 Category 類和一個 Tag 類,為他們的屬性 name 賦了值。為了讓 django 把這些資料儲存進資料庫,呼叫例項的 save 方法即可。

再建立一篇文章試試,但建立文章之前,我們需要先建立一個 User,用於指定文章的作者。建立 User 的命令 django 已經幫我們寫好了,依然是通過 manage.py 來執行。首先按住 Ctrl + c 退出命令互動欄(一次退不出就連續多按幾次),執行 pipenv run python manage.py createsuperuser 命令並根據提示建立使用者:

> pipenv run python manage.py createsuperuser
Username (leave blank to use 'zmrenwu@163.com'): myuser
Email address: a@aa.com
Password:
Password (again):
Superuser created successfully.

執行 python manage.py createsuperuser 開始建立使用者,之後會提示你輸入使用者名稱、郵箱、密碼和確認密碼,按照提示輸入即可。注意一點的是密碼輸入過程中不會有任何字元顯示,不要誤以為你的鍵盤出問題了,正常輸入即可。最後出現 Superuser created successfully. 說明使用者建立成功了。

再次執行 python manage.py shell 進入 Python 命令互動欄,開始建立文章:

>>> from blog.models import Category, Tag, Post
>>> from django.utils import timezone
>>> from django.contrib.auth.models import User

>>> user = User.objects.get(username='myuser')
>>> c = Category.objects.get(name='category test')

>>> p = Post(title='title test', body='body test', created_time=timezone.now(), modified_time=timezone.now(), category=c, author=user)
>>> p.save()

由於我們重啟了 shell,因此需要重新匯入了 CategoryTagPost 以及 User。我們還匯入了一個 django 提供的輔助模組 timezone,這是因為我們需要呼叫它的 now() 方法為 created_timemodified_time 指定時間,容易理解 now 方法返回當前時間。然後我們根據使用者名稱和分類名,通過 get 方法取出了存在資料庫中的 UserCategory(取資料的方法將在下面介紹)。接著我們為文章指定了 titlebodycreated_timemodified_time值,並把它和前面建立的 Category 以及 User 關聯了起來。允許為空 excerpttags 我們就沒有為它們指定值了。

注意:

我們這裡使用 get 方法根據 Categoryname 屬性的值獲取分類的一條記錄。Category.objects.get(name='category test') 的含義是從資料庫中取出 name 的值為 category test 的分類記錄。確保資料庫中只有一條值為 category test 的記錄,否則 get 方法將返回一個 MultipleObjectsReturned 異常。如果你不小心已經存了多條記錄,請刪掉多餘的記錄。如何刪除資料請看下文。

取資料

資料已經存入資料庫了,現在要把它們取出來看看:

>>> Category.objects.all()
<QuerySet [<Category: Category object>]>
>>> Tag.objects.all()
<QuerySet [<Tag: Tag object>]>
>>> Post.objects.all()
<QuerySet [<Post: Post object>]>
>>>

objects 是我們的模型管理器,它為我們提供一系列從資料庫中取資料方法,這裡我們使用了 all 方法,表示我們要把對應的資料全部取出來。可以看到 all 方法都返回了資料,這些資料應該是我們之前存進去的,但是顯示的字串有點奇怪,無法看出究竟是不是我們之前存入的資料。為了讓顯示出來的資料更加人性化一點,我們為 3 個模型分別增加一個 __str__ 方法:

blog/models.py

class Category(models.Model):
    ...

    def __str__(self):
        return self.name
    
class Tag(models.Model):
    ...

    def __str__(self):
        return self.name
    
class Post(models.Model):
    ...
    
    def __str__(self):
        return self.title

定義好 __str__ 方法後,直譯器顯示的內容將會是 __str__ 方法返回的內容。這裡 Category 返回分類名 nameTag 返回標籤名,而 Post 返回它的 title

先按 Ctrl + c 退出 Shell,再重新執行 python manage.py shell 進入 Shell。

>>> from blog.models import Category, Tag, Post
>>> Category.objects.all()
<QuerySet [<Category: category test>]>

>>> Tag.objects.all()
<QuerySet [<Tag: tag test>]>

>>> Post.objects.all()
<QuerySet [<Post: title test>]>

>>> Post.objects.get(title='title test')
<Post: title test>

可以看到返回的是我們之前存入的資料。

此外我們在建立文章時提到了通過 get 方法來獲取資料,這裡 all 方法和 get 方法的區別是:all 方法返回全部資料,是一個類似於列表的資料結構(QuerySet);而 get 返回一條記錄資料,如有多條記錄或者沒有記錄,get 方法均會丟擲相應異常。

改資料

嘗試修改資料:

>>> c = Category.objects.get(name='category test')
>>> c.name = 'category test new'
>>> c.save()
>>> Category.objects.all()
<QuerySet [<Category: test category new>]>

首先通過 get 方法根據分類名 name 獲取值為 category test 到分類,修改它的 name 屬性為新的值 category test new,然後呼叫 save 方法把修改儲存到資料庫,之後可以看到資料庫返回的資料已經是修改後的值了。TagPost 的修改也一樣。

刪資料

刪除掉資料:

>>> p = Post.objects.get(title='title test')
>>> p
<Post: title test>
>>> p.delete()
(1, {'blog.Post_tags': 0, 'blog.Post': 1})
>>> Post.objects.all()
<QuerySet []>

先根據標題 title 的值從資料庫中取出 Post,儲存在變數 p 中,然後呼叫它的delete 方法,最後看到 Post.objects.all() 返回了一個空的 QuerySet(類似於一個列表),表明資料庫中已經沒有 Post,Post 已經被刪除了。

這就是 django 對資料庫增、刪、改、查的操作。除了上述演示的方法外,django 還為我們提供了大量其它的方法,這些方法有一部分會在教程中使用,用到時我會講解它們的用法。但以後你開發自己的專案時,你就需要通過閱讀 django 的官方文件 來了解有哪些方法可用以及如何使用它們。

HelloDjango 系列教程:第 04 篇:Django 遷移、運算元據庫

相關文章