---
title: \[Django] - 伍、建立資料庫
tags: webdevelopment
---
第五篇主要內容是建立Django資料庫。
資料庫是什麼呢?顧名思義就是一個用來存放資料的地方,一般分為兩的種類,關聯式資料庫以及非關聯式資料庫。
關聯式資料庫(Related database management system):
- 由一個或多個資料表(table)組成
- 每欄的資料型態都是固定的
- 不同的資料表間具有關聯性,可以透過某個欄位的資料將其連結
- 具備ACID特性
- 使用SQL(Structured querying language)操作資料庫
- MySQL, PostgreSQL等都是關聯式資料庫
非關聯式資料庫(NoSQL):
- 採用文件、鍵值、圖形等資料模型儲存資料
- 資料型態不需固定
- 不具備ACID特性
- 水平擴展性非常好,可以透過增加節點達成
- Redis, MongoDB, Neo4j等都是非關聯式資料庫
*ACID特性:
- 原子性(Atomicity):多個指令依照順序進行時,若有一個失敗,則先前已執行過的指令也會失效,讓資料回歸修改之前的模樣。
- 一致性(Consistency):資料的改動需符合欄位的規則,否則無效。
- 隔離性(Isolation):避免同一筆資料被兩個不同的人同時更動,所以在一個人對該筆資料有動作時,會鎖定資料避免出現競爭的情形。
- 持續性(Durability):除非硬體受損,否則都有辦法復原,資料永遠不會流失。
看完簡單的介紹,有對兩種資料庫了解一點嗎?
回過頭來看這次專案需要的兩個資料庫
1. 使用者資訊資料庫
2. 搜尋紀錄資料庫
搜尋紀錄資料庫需要依據使用者來分開,我沒有查過的關鍵字或域名,不應該出現在我的搜尋紀錄裡面。換句話說,搜尋紀錄需要跟使用者有關聯性。除此之外,搜尋紀錄的資料型態只有域名跟關鍵字的字以及排名的整數,不會出現其他的型態。
根據這幾點原因,這次的專案我決定使用關聯式資料庫。~~(絕對不是因為Django已經內建有關聯式資料庫的關係)~~
Django使用的是python中的SQLite,如果你想要用其他的資料庫,Django的文件中也有說明如何操作。這部分就請有需求的讀者自己去找囉~
接下來開始設定資料庫吧!
首先,先把在settings.py中把時區改成你的所在時區

像我的話就是"Asia/Taipei"
接著打開Terminal輸入
```code=python
python manage.py migrate
```
這個指令會檢查settings.py中的INSTALLED_APPS,並且根據他建立需要的資料庫。輸入後會看到這一串東西。

可以看到Django幫我們建立了一些東西。大概是一些管理後台的資料庫和功能。
完成之後,接著就是開始建立我們需要的資料庫了!
不過,不過,不過,在建立資料庫前還需要了解一個重要的原則:
DRY(Don't repeat yourself)
這個原則簡單來說,是為了確保程式太複雜或是冗長而需要遵守。
以一言蔽之則是,一個程式核心只能存在一個,且只能存在一個地方。
用在Django資料庫中就是,建立過的物件,我們要重複拿來利用,避免出現不必要的錯誤或是讓程式碼過於冗長。
了解這個核心原則之後,在IDE中打開models.py
```code=python
from django.db import models
# 引用Django Auth中的「使用者」
# 可以獲取內建或是自定義的「User」model
from django.contrib.auth import get_user_model
User = get_user_model()
# 使用者資訊資料庫
class UserProfileInfo(models.Model):
# user欄位一對一對應「User」model
# User包含username, password, email, firstname, lastname
# on_delete=models.CASCADE: 當參照的物件被刪除時,一併刪除參照該物件的所有物件
user = models.OneToOneField(User, on_delete=models.CASCADE)
# 新增一個暱稱欄位
nickname = models.CharField(max_length = 10)
# 會印出username
def __str__(self):
return self.user.username
```
使用者資訊資料庫完成啦,接下來要建立的是搜尋紀錄資料庫
在原先的預想中搜尋紀錄資料庫大概是長得像下面的表格這樣

使用一個資料表紀錄所有的搜尋紀錄,再透過User跟Domain去找對應的紀錄
但是,大家還記得前面提到的DRY原則嗎?
沒錯,User跟域名都有可能重複出現。所以為了防止這個狀況發生
我們把資料表設計成這個樣式,來讓後續處理資料時比較輕鬆一點

當然一定還有其他的方式,大家可以想想有什麼更好的方式可以建立我們這次需要資料庫喔!
接下來一樣打開models.py
輸入下面的程式碼
```code=python
# 域名類別,會依據這個類別建立資料庫
class Domain(models.Model):
# 用user當作Foreign key,關聯至User model
# 反向關聯domains,可以透過User.domains去找該User底下所搜尋過的domain
# verbose_name在後台可以看到對應的名稱
user = models.ForeignKey(User, on_delete=models.CASCADE,
related_name="domains",
verbose_name="所屬使用者")
# domain,字元格式,限制長度255位元
domain = models.CharField(max_length=255, verbose_name="域名")
# Meta是Django內建的一些方法(method),可以用來設定一些屬性(attributes)
class Meta:
# 確保一個user不會有重複的domain名稱
unique_together = ("user", "domain",)
# 單個的名稱
verbose_name = "域名"
# 複數個的名稱
verbose_name_plural = "域名"
# 索引,有很多屬性可以自行調整
indexes = [
models.Index(fields=["user", "domain"]),
]
# 印出使用者名稱 - 域名
def __str__(self):
return f"{self.user.username} - {self.domain}"
class Keyword(models.Model):
domain = models.ForeignKey(Domain, on_delete=models.CASCADE,
related_name="keywords",
verbose_name="所屬域名")
keyword = models.CharField(max_length=255, verbose_name="關鍵字")
class Meta:
unique_together = ("domain", "keyword",)
verbose_name = "關鍵字"
verbose_name_plural = "關鍵字"
indexes = [
models.Index(fields=["domain", "keyword"]),
]
def __str__(self):
return f"{self.domain.user.username} - {self.domain.domain} - {self.keyword}"
class KeywordRankHistory(models.Model):
keyword = models.ForeignKey(Keyword, on_delete=models.CASCADE,
related_name='rank', # 通過 keyword.rank_history.all() 訪問該關鍵字的歷史排名
verbose_name="所屬關鍵字")
rank = models.IntegerField(null=True, blank=True, verbose_name="排名") # 可為空,表示尚未抓取或無排名
# 建立日期:auto_now_add=True 會在物件首次創建時自動設置為當前時間
created_at = models.DateTimeField(auto_now_add=True, verbose_name="建立日期")
class Meta:
verbose_name = "排名歷史"
verbose_name_plural = "排名歷史"
# 按建立日期降序排列
ordering = ['-created_at']
indexes = [
models.Index(fields=['keyword_name', 'created_at']),
# models.Index(fields=['created_at']),
]
def __str__(self):
return f"{self.keyword.domain.user.username} - {self.keyword.domain.domain} - {self.keyword.keyword} - {self.rank if self.rank is not None else 'N/A'} @ {self.created_at.strftime('%Y-%m-%d %H:%M')}"
```
輸入完之後,來到settings.py
在INSTALLED_APPS中加入我們的app
我的是ranking.apps.RankingConfig
如果不知道自己的該輸入什麼怎麼辦呢?
打開apps.py就可以看到你的是什麼名稱了
在前面加上你的app的名稱(會跟你的apps.py所在的資料夾一樣)
大概會是這個樣子
資料夾名稱.apps.XXXConfig
完成後記得儲存!
最後在Terminal輸入
```code=python
python manage.py makemigrations ranking
# "ranking"記得換成你自己的app名稱
```
成功的話會看到這些訊息

如果想玩一下可以參考Django的文件
我們這邊直接繼續在Terminal輸入
```code=python
python manage.py migrate
```

看到這些訊息就代表沒有問題啦!!!
下一篇會進行如何設定一個superuser
並用瀏覽器參觀一下我們建立好的幾個資料庫
請大家拭目以待!
如果有任何想法或是問題歡迎寄信到 chantinghsien@gmail.com
可以一起討論和分享新知!